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

big reorganization to cleanly separate framework vs. server
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 29 Aug 2017 21:17:35 +0200
parents OrthancServer/ParsedDicomFile.cpp@b8969010b534
children 75c779ca948c
comparison
equal deleted inserted replaced
2381:b8969010b534 2382:7284093111b0
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017 Osimis, Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34
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 "ToDcmtkBridge.h"
86 #include "Internals/DicomFrameIndex.h"
87 #include "../Logging.h"
88 #include "../OrthancException.h"
89 #include "../Toolbox.h"
90 #include "../SystemToolbox.h"
91
92 #if ORTHANC_ENABLE_JPEG == 1
93 # include "../Images/JpegReader.h"
94 #endif
95
96 #if ORTHANC_ENABLE_PNG == 1
97 # include "../Images/PngReader.h"
98 #endif
99
100 #include <list>
101 #include <limits>
102
103 #include <boost/lexical_cast.hpp>
104
105 #include <dcmtk/dcmdata/dcchrstr.h>
106 #include <dcmtk/dcmdata/dcdicent.h>
107 #include <dcmtk/dcmdata/dcdict.h>
108 #include <dcmtk/dcmdata/dcfilefo.h>
109 #include <dcmtk/dcmdata/dcuid.h>
110 #include <dcmtk/dcmdata/dcmetinf.h>
111 #include <dcmtk/dcmdata/dcdeftag.h>
112
113 #include <dcmtk/dcmdata/dcvrae.h>
114 #include <dcmtk/dcmdata/dcvras.h>
115 #include <dcmtk/dcmdata/dcvrcs.h>
116 #include <dcmtk/dcmdata/dcvrda.h>
117 #include <dcmtk/dcmdata/dcvrds.h>
118 #include <dcmtk/dcmdata/dcvrdt.h>
119 #include <dcmtk/dcmdata/dcvrfd.h>
120 #include <dcmtk/dcmdata/dcvrfl.h>
121 #include <dcmtk/dcmdata/dcvris.h>
122 #include <dcmtk/dcmdata/dcvrlo.h>
123 #include <dcmtk/dcmdata/dcvrlt.h>
124 #include <dcmtk/dcmdata/dcvrpn.h>
125 #include <dcmtk/dcmdata/dcvrsh.h>
126 #include <dcmtk/dcmdata/dcvrsl.h>
127 #include <dcmtk/dcmdata/dcvrss.h>
128 #include <dcmtk/dcmdata/dcvrst.h>
129 #include <dcmtk/dcmdata/dcvrtm.h>
130 #include <dcmtk/dcmdata/dcvrui.h>
131 #include <dcmtk/dcmdata/dcvrul.h>
132 #include <dcmtk/dcmdata/dcvrus.h>
133 #include <dcmtk/dcmdata/dcvrut.h>
134 #include <dcmtk/dcmdata/dcpixel.h>
135 #include <dcmtk/dcmdata/dcpixseq.h>
136 #include <dcmtk/dcmdata/dcpxitem.h>
137
138
139 #include <boost/math/special_functions/round.hpp>
140 #include <dcmtk/dcmdata/dcostrmb.h>
141 #include <boost/algorithm/string/predicate.hpp>
142
143
144 #if DCMTK_VERSION_NUMBER <= 360
145 # define EXS_JPEGProcess1 EXS_JPEGProcess1TransferSyntax
146 #endif
147
148
149 static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
150
151
152
153 namespace Orthanc
154 {
155 struct ParsedDicomFile::PImpl
156 {
157 std::auto_ptr<DcmFileFormat> file_;
158 std::auto_ptr<DicomFrameIndex> frameIndex_;
159 };
160
161
162 static void SendPathValueForDictionary(RestApiOutput& output,
163 DcmItem& dicom)
164 {
165 Json::Value v = Json::arrayValue;
166
167 for (unsigned long i = 0; i < dicom.card(); i++)
168 {
169 DcmElement* element = dicom.getElement(i);
170 if (element)
171 {
172 char buf[16];
173 sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
174 v.append(buf);
175 }
176 }
177
178 output.AnswerJson(v);
179 }
180
181 static inline uint16_t GetCharValue(char c)
182 {
183 if (c >= '0' && c <= '9')
184 return c - '0';
185 else if (c >= 'a' && c <= 'f')
186 return c - 'a' + 10;
187 else if (c >= 'A' && c <= 'F')
188 return c - 'A' + 10;
189 else
190 return 0;
191 }
192
193 static inline uint16_t GetTagValue(const char* c)
194 {
195 return ((GetCharValue(c[0]) << 12) +
196 (GetCharValue(c[1]) << 8) +
197 (GetCharValue(c[2]) << 4) +
198 GetCharValue(c[3]));
199 }
200
201 static void ParseTagAndGroup(DcmTagKey& key,
202 const std::string& tag)
203 {
204 DicomTag t = FromDcmtkBridge::ParseTag(tag);
205 key = DcmTagKey(t.GetGroup(), t.GetElement());
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 static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData,
225 E_TransferSyntax transferSyntax)
226 {
227 DcmPixelSequence* pixelSequence = NULL;
228 if (pixelData.getEncapsulatedRepresentation
229 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
230 {
231 return pixelSequence->card();
232 }
233 else
234 {
235 return 1;
236 }
237 }
238
239
240 namespace
241 {
242 class DicomFieldStream : public IHttpStreamAnswer
243 {
244 private:
245 DcmElement& element_;
246 uint32_t length_;
247 uint32_t offset_;
248 std::string chunk_;
249 size_t chunkSize_;
250
251 public:
252 DicomFieldStream(DcmElement& element,
253 E_TransferSyntax transferSyntax) :
254 element_(element),
255 length_(element.getLength(transferSyntax)),
256 offset_(0),
257 chunkSize_(0)
258 {
259 static const size_t CHUNK_SIZE = 64 * 1024; // Use chunks of max 64KB
260 chunk_.resize(CHUNK_SIZE);
261 }
262
263 virtual HttpCompression SetupHttpCompression(bool /*gzipAllowed*/,
264 bool /*deflateAllowed*/)
265 {
266 // No support for compression
267 return HttpCompression_None;
268 }
269
270 virtual bool HasContentFilename(std::string& filename)
271 {
272 return false;
273 }
274
275 virtual std::string GetContentType()
276 {
277 return "";
278 }
279
280 virtual uint64_t GetContentLength()
281 {
282 return length_;
283 }
284
285 virtual bool ReadNextChunk()
286 {
287 assert(offset_ <= length_);
288
289 if (offset_ == length_)
290 {
291 return false;
292 }
293 else
294 {
295 if (length_ - offset_ < chunk_.size())
296 {
297 chunkSize_ = length_ - offset_;
298 }
299 else
300 {
301 chunkSize_ = chunk_.size();
302 }
303
304 OFCondition cond = element_.getPartialValue(&chunk_[0], offset_, chunkSize_);
305
306 offset_ += chunkSize_;
307
308 if (!cond.good())
309 {
310 LOG(ERROR) << "Error while sending a DICOM field: " << cond.text();
311 throw OrthancException(ErrorCode_InternalError);
312 }
313
314 return true;
315 }
316 }
317
318 virtual const char *GetChunkContent()
319 {
320 return chunk_.c_str();
321 }
322
323 virtual size_t GetChunkSize()
324 {
325 return chunkSize_;
326 }
327 };
328 }
329
330
331 static bool AnswerPixelData(RestApiOutput& output,
332 DcmItem& dicom,
333 E_TransferSyntax transferSyntax,
334 const std::string* blockUri)
335 {
336 DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(),
337 DICOM_TAG_PIXEL_DATA.GetElement());
338
339 DcmElement *element = NULL;
340 if (!dicom.findAndGetElement(k, element).good() ||
341 element == NULL)
342 {
343 return false;
344 }
345
346 try
347 {
348 DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
349 if (blockUri == NULL)
350 {
351 // The user asks how many blocks are present in this pixel data
352 unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax);
353
354 Json::Value result(Json::arrayValue);
355 for (unsigned int i = 0; i < blocks; i++)
356 {
357 result.append(boost::lexical_cast<std::string>(i));
358 }
359
360 output.AnswerJson(result);
361 return true;
362 }
363
364
365 unsigned int block = boost::lexical_cast<unsigned int>(*blockUri);
366
367 if (block < GetPixelDataBlockCount(pixelData, transferSyntax))
368 {
369 DcmPixelSequence* pixelSequence = NULL;
370 if (pixelData.getEncapsulatedRepresentation
371 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
372 {
373 // This is the case for JPEG transfer syntaxes
374 if (block < pixelSequence->card())
375 {
376 DcmPixelItem* pixelItem = NULL;
377 if (pixelSequence->getItem(pixelItem, block).good() && pixelItem)
378 {
379 if (pixelItem->getLength() == 0)
380 {
381 output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM);
382 return true;
383 }
384
385 Uint8* buffer = NULL;
386 if (pixelItem->getUint8Array(buffer).good() && buffer)
387 {
388 output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM);
389 return true;
390 }
391 }
392 }
393 }
394 else
395 {
396 // This is the case for raw, uncompressed image buffers
397 assert(*blockUri == "0");
398 DicomFieldStream stream(*element, transferSyntax);
399 output.AnswerStream(stream);
400 }
401 }
402 }
403 catch (boost::bad_lexical_cast&)
404 {
405 // The URI entered by the user is not a number
406 }
407 catch (std::bad_cast&)
408 {
409 // This should never happen
410 }
411
412 return false;
413 }
414
415
416
417 static void SendPathValueForLeaf(RestApiOutput& output,
418 const std::string& tag,
419 DcmItem& dicom,
420 E_TransferSyntax transferSyntax)
421 {
422 DcmTagKey k;
423 ParseTagAndGroup(k, tag);
424
425 DcmSequenceOfItems* sequence = NULL;
426 if (dicom.findAndGetSequence(k, sequence).good() &&
427 sequence != NULL &&
428 sequence->getVR() == EVR_SQ)
429 {
430 SendSequence(output, *sequence);
431 return;
432 }
433
434 DcmElement* element = NULL;
435 if (dicom.findAndGetElement(k, element).good() &&
436 element != NULL &&
437 //element->getVR() != EVR_UNKNOWN && // This would forbid private tags
438 element->getVR() != EVR_SQ)
439 {
440 DicomFieldStream stream(*element, transferSyntax);
441 output.AnswerStream(stream);
442 }
443 }
444
445 void ParsedDicomFile::SendPathValue(RestApiOutput& output,
446 const UriComponents& uri)
447 {
448 DcmItem* dicom = pimpl_->file_->getDataset();
449 E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
450
451 // Special case: Accessing the pixel data
452 if (uri.size() == 1 ||
453 uri.size() == 2)
454 {
455 DcmTagKey tag;
456 ParseTagAndGroup(tag, uri[0]);
457
458 if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() &&
459 tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement())
460 {
461 AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]);
462 return;
463 }
464 }
465
466 // Go down in the tag hierarchy according to the URI
467 for (size_t pos = 0; pos < uri.size() / 2; pos++)
468 {
469 size_t index;
470 try
471 {
472 index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
473 }
474 catch (boost::bad_lexical_cast&)
475 {
476 return;
477 }
478
479 DcmTagKey k;
480 DcmItem *child = NULL;
481 ParseTagAndGroup(k, uri[2 * pos]);
482 if (!dicom->findAndGetSequenceItem(k, child, index).good() ||
483 child == NULL)
484 {
485 return;
486 }
487
488 dicom = child;
489 }
490
491 // We have reached the end of the URI
492 if (uri.size() % 2 == 0)
493 {
494 SendPathValueForDictionary(output, *dicom);
495 }
496 else
497 {
498 SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax);
499 }
500 }
501
502
503 void ParsedDicomFile::Remove(const DicomTag& tag)
504 {
505 InvalidateCache();
506
507 DcmTagKey key(tag.GetGroup(), tag.GetElement());
508 DcmElement* element = pimpl_->file_->getDataset()->remove(key);
509 if (element != NULL)
510 {
511 delete element;
512 }
513 }
514
515
516 void ParsedDicomFile::Clear(const DicomTag& tag,
517 bool onlyIfExists)
518 {
519 InvalidateCache();
520
521 DcmItem* dicom = pimpl_->file_->getDataset();
522 DcmTagKey key(tag.GetGroup(), tag.GetElement());
523
524 if (onlyIfExists &&
525 !dicom->tagExists(key))
526 {
527 // The tag is non-existing, do not clear it
528 }
529 else
530 {
531 if (!dicom->insertEmptyElement(key, OFTrue /* replace old value */).good())
532 {
533 throw OrthancException(ErrorCode_InternalError);
534 }
535 }
536 }
537
538
539 void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep)
540 {
541 InvalidateCache();
542
543 DcmDataset& dataset = *pimpl_->file_->getDataset();
544
545 // Loop over the dataset to detect its private tags
546 typedef std::list<DcmElement*> Tags;
547 Tags privateTags;
548
549 for (unsigned long i = 0; i < dataset.card(); i++)
550 {
551 DcmElement* element = dataset.getElement(i);
552 DcmTag tag(element->getTag());
553
554 // Is this a private tag?
555 if (tag.isPrivate())
556 {
557 bool remove = true;
558
559 // Check whether this private tag is to be kept
560 if (toKeep != NULL)
561 {
562 DicomTag tmp = FromDcmtkBridge::Convert(tag);
563 if (toKeep->find(tmp) != toKeep->end())
564 {
565 remove = false; // Keep it
566 }
567 }
568
569 if (remove)
570 {
571 privateTags.push_back(element);
572 }
573 }
574 }
575
576 // Loop over the detected private tags to remove them
577 for (Tags::iterator it = privateTags.begin();
578 it != privateTags.end(); ++it)
579 {
580 DcmElement* tmp = dataset.remove(*it);
581 if (tmp != NULL)
582 {
583 delete tmp;
584 }
585 }
586 }
587
588
589 static void InsertInternal(DcmDataset& dicom,
590 DcmElement* element)
591 {
592 OFCondition cond = dicom.insert(element, false, false);
593 if (!cond.good())
594 {
595 // This field already exists
596 delete element;
597 throw OrthancException(ErrorCode_InternalError);
598 }
599 }
600
601
602 void ParsedDicomFile::Insert(const DicomTag& tag,
603 const Json::Value& value,
604 bool decodeDataUriScheme)
605 {
606 if (pimpl_->file_->getDataset()->tagExists(ToDcmtkBridge::Convert(tag)))
607 {
608 throw OrthancException(ErrorCode_AlreadyExistingTag);
609 }
610
611 if (decodeDataUriScheme &&
612 value.type() == Json::stringValue &&
613 (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
614 tag == DICOM_TAG_PIXEL_DATA))
615 {
616 if (EmbedContentInternal(value.asString()))
617 {
618 return;
619 }
620 }
621
622 InvalidateCache();
623 std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
624 InsertInternal(*pimpl_->file_->getDataset(), element.release());
625 }
626
627
628 static bool CanReplaceProceed(DcmDataset& dicom,
629 const DcmTagKey& tag,
630 DicomReplaceMode mode)
631 {
632 if (dicom.findAndDeleteElement(tag).good())
633 {
634 // This tag was existing, it has been deleted
635 return true;
636 }
637 else
638 {
639 // This tag was absent, act wrt. the specified "mode"
640 switch (mode)
641 {
642 case DicomReplaceMode_InsertIfAbsent:
643 return true;
644
645 case DicomReplaceMode_ThrowIfAbsent:
646 throw OrthancException(ErrorCode_InexistentItem);
647
648 case DicomReplaceMode_IgnoreIfAbsent:
649 return false;
650
651 default:
652 throw OrthancException(ErrorCode_ParameterOutOfRange);
653 }
654 }
655 }
656
657
658 void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag,
659 const std::string& utf8Value,
660 bool decodeDataUriScheme)
661 {
662 if (tag != DICOM_TAG_SOP_CLASS_UID &&
663 tag != DICOM_TAG_SOP_INSTANCE_UID)
664 {
665 return;
666 }
667
668 std::string binary;
669 const std::string* decoded = &utf8Value;
670
671 if (decodeDataUriScheme &&
672 boost::starts_with(utf8Value, "data:application/octet-stream;base64,"))
673 {
674 std::string mime;
675 if (!Toolbox::DecodeDataUriScheme(mime, binary, utf8Value))
676 {
677 throw OrthancException(ErrorCode_BadFileFormat);
678 }
679
680 decoded = &binary;
681 }
682 else
683 {
684 Encoding encoding = GetEncoding();
685 if (GetEncoding() != Encoding_Utf8)
686 {
687 binary = Toolbox::ConvertFromUtf8(utf8Value, encoding);
688 decoded = &binary;
689 }
690 }
691
692 /**
693 * dcmodify will automatically correct 'Media Storage SOP Class
694 * UID' and 'Media Storage SOP Instance UID' in the metaheader, if
695 * you make changes to the related tags in the dataset ('SOP Class
696 * UID' and 'SOP Instance UID') via insert or modify mode
697 * options. You can disable this behaviour by using the -nmu
698 * option.
699 **/
700
701 if (tag == DICOM_TAG_SOP_CLASS_UID)
702 {
703 ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded);
704 }
705
706 if (tag == DICOM_TAG_SOP_INSTANCE_UID)
707 {
708 ReplacePlainString(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded);
709 }
710 }
711
712
713 void ParsedDicomFile::Replace(const DicomTag& tag,
714 const std::string& utf8Value,
715 bool decodeDataUriScheme,
716 DicomReplaceMode mode)
717 {
718 InvalidateCache();
719
720 DcmDataset& dicom = *pimpl_->file_->getDataset();
721 if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
722 {
723 // Either the tag was previously existing (and now removed), or
724 // the replace mode was set to "InsertIfAbsent"
725
726 if (decodeDataUriScheme &&
727 (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
728 tag == DICOM_TAG_PIXEL_DATA))
729 {
730 if (EmbedContentInternal(utf8Value))
731 {
732 return;
733 }
734 }
735
736 std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
737 FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, GetEncoding());
738
739 InsertInternal(dicom, element.release());
740 UpdateStorageUid(tag, utf8Value, false);
741 }
742 }
743
744
745 void ParsedDicomFile::Replace(const DicomTag& tag,
746 const Json::Value& value,
747 bool decodeDataUriScheme,
748 DicomReplaceMode mode)
749 {
750 InvalidateCache();
751
752 DcmDataset& dicom = *pimpl_->file_->getDataset();
753 if (CanReplaceProceed(dicom, ToDcmtkBridge::Convert(tag), mode))
754 {
755 // Either the tag was previously existing (and now removed), or
756 // the replace mode was set to "InsertIfAbsent"
757
758 if (decodeDataUriScheme &&
759 value.type() == Json::stringValue &&
760 (tag == DICOM_TAG_ENCAPSULATED_DOCUMENT ||
761 tag == DICOM_TAG_PIXEL_DATA))
762 {
763 if (EmbedContentInternal(value.asString()))
764 {
765 return;
766 }
767 }
768
769 InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, GetEncoding()));
770
771 if (tag == DICOM_TAG_SOP_CLASS_UID ||
772 tag == DICOM_TAG_SOP_INSTANCE_UID)
773 {
774 if (value.type() != Json::stringValue)
775 {
776 throw OrthancException(ErrorCode_BadParameterType);
777 }
778
779 UpdateStorageUid(tag, value.asString(), decodeDataUriScheme);
780 }
781 }
782 }
783
784
785 void ParsedDicomFile::Answer(RestApiOutput& output)
786 {
787 std::string serialized;
788 if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset()))
789 {
790 output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM);
791 }
792 }
793
794
795
796 bool ParsedDicomFile::GetTagValue(std::string& value,
797 const DicomTag& tag)
798 {
799 DcmTagKey k(tag.GetGroup(), tag.GetElement());
800 DcmDataset& dataset = *pimpl_->file_->getDataset();
801
802 if (tag.IsPrivate() ||
803 FromDcmtkBridge::IsUnknownTag(tag) ||
804 tag == DICOM_TAG_PIXEL_DATA ||
805 tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
806 {
807 const Uint8* data = NULL; // This is freed in the destructor of the dataset
808 long unsigned int count = 0;
809
810 if (dataset.findAndGetUint8Array(k, data, &count).good())
811 {
812 if (count > 0)
813 {
814 assert(data != NULL);
815 value.assign(reinterpret_cast<const char*>(data), count);
816 }
817 else
818 {
819 value.clear();
820 }
821
822 return true;
823 }
824 else
825 {
826 return false;
827 }
828 }
829 else
830 {
831 DcmElement* element = NULL;
832 if (!dataset.findAndGetElement(k, element).good() ||
833 element == NULL)
834 {
835 return false;
836 }
837
838 std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
839 (*element, DicomToJsonFlags_Default,
840 ORTHANC_MAXIMUM_TAG_LENGTH, GetEncoding()));
841
842 if (v.get() == NULL ||
843 v->IsNull())
844 {
845 value = "";
846 }
847 else
848 {
849 // TODO v->IsBinary()
850 value = v->GetContent();
851 }
852
853 return true;
854 }
855 }
856
857
858 DicomInstanceHasher ParsedDicomFile::GetHasher()
859 {
860 std::string patientId, studyUid, seriesUid, instanceUid;
861
862 if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) ||
863 !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
864 !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) ||
865 !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID))
866 {
867 throw OrthancException(ErrorCode_BadFileFormat);
868 }
869
870 return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid);
871 }
872
873
874 void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer)
875 {
876 FromDcmtkBridge::SaveToMemoryBuffer(buffer, *pimpl_->file_->getDataset());
877 }
878
879
880 void ParsedDicomFile::SaveToFile(const std::string& path)
881 {
882 // TODO Avoid using a temporary memory buffer, write directly on disk
883 std::string content;
884 SaveToMemoryBuffer(content);
885 SystemToolbox::WriteFile(content, path);
886 }
887
888
889 ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl)
890 {
891 pimpl_->file_.reset(new DcmFileFormat);
892
893 if (createIdentifiers)
894 {
895 ReplacePlainString(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
896 ReplacePlainString(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
897 ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
898 ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
899 }
900 }
901
902
903 void ParsedDicomFile::CreateFromDicomMap(const DicomMap& source,
904 Encoding defaultEncoding)
905 {
906 pimpl_->file_.reset(new DcmFileFormat);
907
908 const DicomValue* tmp = source.TestAndGetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET);
909 if (tmp != NULL)
910 {
911 Encoding encoding;
912 if (tmp->IsNull() ||
913 tmp->IsBinary() ||
914 !GetDicomEncoding(encoding, tmp->GetContent().c_str()))
915 {
916 throw OrthancException(ErrorCode_ParameterOutOfRange);
917 }
918 else
919 {
920 SetEncoding(encoding);
921 }
922 }
923 else
924 {
925 SetEncoding(defaultEncoding);
926 }
927
928 for (DicomMap::Map::const_iterator
929 it = source.map_.begin(); it != source.map_.end(); ++it)
930 {
931 if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET &&
932 !it->second->IsNull())
933 {
934 ReplacePlainString(it->first, it->second->GetContent());
935 }
936 }
937 }
938
939
940 ParsedDicomFile::ParsedDicomFile(const DicomMap& map,
941 Encoding defaultEncoding) :
942 pimpl_(new PImpl)
943 {
944 CreateFromDicomMap(map, defaultEncoding);
945 }
946
947
948 ParsedDicomFile::ParsedDicomFile(const DicomMap& map) :
949 pimpl_(new PImpl)
950 {
951 CreateFromDicomMap(map, GetDefaultDicomEncoding());
952 }
953
954
955 ParsedDicomFile::ParsedDicomFile(const void* content,
956 size_t size) : pimpl_(new PImpl)
957 {
958 pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(content, size));
959 }
960
961 ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl)
962 {
963 if (content.size() == 0)
964 {
965 pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(NULL, 0));
966 }
967 else
968 {
969 pimpl_->file_.reset(FromDcmtkBridge::LoadFromMemoryBuffer(&content[0], content.size()));
970 }
971 }
972
973
974 ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other) :
975 pimpl_(new PImpl)
976 {
977 pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone()));
978
979 // Create a new instance-level identifier
980 ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
981 }
982
983
984 ParsedDicomFile::ParsedDicomFile(DcmDataset& dicom) : pimpl_(new PImpl)
985 {
986 pimpl_->file_.reset(new DcmFileFormat(&dicom));
987 }
988
989
990 ParsedDicomFile::ParsedDicomFile(DcmFileFormat& dicom) : pimpl_(new PImpl)
991 {
992 pimpl_->file_.reset(new DcmFileFormat(dicom));
993 }
994
995
996 ParsedDicomFile::~ParsedDicomFile()
997 {
998 delete pimpl_;
999 }
1000
1001
1002 DcmFileFormat& ParsedDicomFile::GetDcmtkObject() const
1003 {
1004 return *pimpl_->file_.get();
1005 }
1006
1007
1008 ParsedDicomFile* ParsedDicomFile::Clone()
1009 {
1010 return new ParsedDicomFile(*this);
1011 }
1012
1013
1014 bool ParsedDicomFile::EmbedContentInternal(const std::string& dataUriScheme)
1015 {
1016 std::string mime, content;
1017 if (!Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme))
1018 {
1019 return false;
1020 }
1021
1022 Toolbox::ToLowerCase(mime);
1023
1024 if (mime == "image/png")
1025 {
1026 #if ORTHANC_ENABLE_PNG == 1
1027 EmbedImage(mime, content);
1028 #else
1029 LOG(ERROR) << "Orthanc was compiled without support of PNG";
1030 throw OrthancException(ErrorCode_NotImplemented);
1031 #endif
1032 }
1033 else if (mime == "image/jpeg")
1034 {
1035 #if ORTHANC_ENABLE_JPEG == 1
1036 EmbedImage(mime, content);
1037 #else
1038 LOG(ERROR) << "Orthanc was compiled without support of JPEG";
1039 throw OrthancException(ErrorCode_NotImplemented);
1040 #endif
1041 }
1042 else if (mime == "application/pdf")
1043 {
1044 EmbedPdf(content);
1045 }
1046 else
1047 {
1048 LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime;
1049 throw OrthancException(ErrorCode_NotImplemented);
1050 }
1051
1052 return true;
1053 }
1054
1055
1056 void ParsedDicomFile::EmbedContent(const std::string& dataUriScheme)
1057 {
1058 if (!EmbedContentInternal(dataUriScheme))
1059 {
1060 throw OrthancException(ErrorCode_BadFileFormat);
1061 }
1062 }
1063
1064
1065 #if (ORTHANC_ENABLE_JPEG == 1 && \
1066 ORTHANC_ENABLE_PNG == 1)
1067 void ParsedDicomFile::EmbedImage(const std::string& mime,
1068 const std::string& content)
1069 {
1070 if (mime == "image/png")
1071 {
1072 PngReader reader;
1073 reader.ReadFromMemory(content);
1074 EmbedImage(reader);
1075 }
1076 else if (mime == "image/jpeg")
1077 {
1078 JpegReader reader;
1079 reader.ReadFromMemory(content);
1080 EmbedImage(reader);
1081 }
1082 else
1083 {
1084 throw OrthancException(ErrorCode_NotImplemented);
1085 }
1086 }
1087 #endif
1088
1089
1090 void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor)
1091 {
1092 if (accessor.GetFormat() != PixelFormat_Grayscale8 &&
1093 accessor.GetFormat() != PixelFormat_Grayscale16 &&
1094 accessor.GetFormat() != PixelFormat_SignedGrayscale16 &&
1095 accessor.GetFormat() != PixelFormat_RGB24 &&
1096 accessor.GetFormat() != PixelFormat_RGBA32)
1097 {
1098 throw OrthancException(ErrorCode_NotImplemented);
1099 }
1100
1101 InvalidateCache();
1102
1103 if (accessor.GetFormat() == PixelFormat_RGBA32)
1104 {
1105 LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM";
1106 }
1107
1108 // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html
1109
1110 Remove(DICOM_TAG_PIXEL_DATA);
1111 ReplacePlainString(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth()));
1112 ReplacePlainString(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight()));
1113 ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "1");
1114 ReplacePlainString(DICOM_TAG_NUMBER_OF_FRAMES, "1");
1115
1116 if (accessor.GetFormat() == PixelFormat_SignedGrayscale16)
1117 {
1118 ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "1");
1119 }
1120 else
1121 {
1122 ReplacePlainString(DICOM_TAG_PIXEL_REPRESENTATION, "0"); // Unsigned pixels
1123 }
1124
1125 ReplacePlainString(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved
1126 ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2");
1127
1128 unsigned int bytesPerPixel = 0;
1129
1130 switch (accessor.GetFormat())
1131 {
1132 case PixelFormat_Grayscale8:
1133 ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
1134 ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
1135 ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
1136 bytesPerPixel = 1;
1137 break;
1138
1139 case PixelFormat_RGB24:
1140 case PixelFormat_RGBA32:
1141 ReplacePlainString(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB");
1142 ReplacePlainString(DICOM_TAG_SAMPLES_PER_PIXEL, "3");
1143 ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "8");
1144 ReplacePlainString(DICOM_TAG_BITS_STORED, "8");
1145 ReplacePlainString(DICOM_TAG_HIGH_BIT, "7");
1146 bytesPerPixel = 3;
1147 break;
1148
1149 case PixelFormat_Grayscale16:
1150 case PixelFormat_SignedGrayscale16:
1151 ReplacePlainString(DICOM_TAG_BITS_ALLOCATED, "16");
1152 ReplacePlainString(DICOM_TAG_BITS_STORED, "16");
1153 ReplacePlainString(DICOM_TAG_HIGH_BIT, "15");
1154 bytesPerPixel = 2;
1155 break;
1156
1157 default:
1158 throw OrthancException(ErrorCode_NotImplemented);
1159 }
1160
1161 assert(bytesPerPixel != 0);
1162
1163 DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(),
1164 DICOM_TAG_PIXEL_DATA.GetElement());
1165
1166 std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key));
1167
1168 unsigned int pitch = accessor.GetWidth() * bytesPerPixel;
1169 Uint8* target = NULL;
1170 pixels->createUint8Array(accessor.GetHeight() * pitch, target);
1171
1172 for (unsigned int y = 0; y < accessor.GetHeight(); y++)
1173 {
1174 switch (accessor.GetFormat())
1175 {
1176 case PixelFormat_RGB24:
1177 case PixelFormat_Grayscale8:
1178 case PixelFormat_Grayscale16:
1179 case PixelFormat_SignedGrayscale16:
1180 {
1181 memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch);
1182 target += pitch;
1183 break;
1184 }
1185
1186 case PixelFormat_RGBA32:
1187 {
1188 // The alpha channel is not supported by the DICOM standard
1189 const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y));
1190 for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4)
1191 {
1192 target[0] = source[0];
1193 target[1] = source[1];
1194 target[2] = source[2];
1195 }
1196
1197 break;
1198 }
1199
1200 default:
1201 throw OrthancException(ErrorCode_NotImplemented);
1202 }
1203 }
1204
1205 if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good())
1206 {
1207 throw OrthancException(ErrorCode_InternalError);
1208 }
1209 }
1210
1211
1212 Encoding ParsedDicomFile::GetEncoding() const
1213 {
1214 return FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset(),
1215 GetDefaultDicomEncoding());
1216 }
1217
1218
1219 void ParsedDicomFile::SetEncoding(Encoding encoding)
1220 {
1221 if (encoding == Encoding_Windows1251)
1222 {
1223 // This Cyrillic codepage is not officially supported by the
1224 // DICOM standard. Do not set the SpecificCharacterSet tag.
1225 return;
1226 }
1227
1228 std::string s = GetDicomSpecificCharacterSet(encoding);
1229 ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s);
1230 }
1231
1232 void ParsedDicomFile::DatasetToJson(Json::Value& target,
1233 DicomToJsonFormat format,
1234 DicomToJsonFlags flags,
1235 unsigned int maxStringLength)
1236 {
1237 FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(),
1238 format, flags, maxStringLength, GetDefaultDicomEncoding());
1239 }
1240
1241
1242 void ParsedDicomFile::DatasetToJson(Json::Value& target)
1243 {
1244 FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset());
1245 }
1246
1247
1248 void ParsedDicomFile::HeaderToJson(Json::Value& target,
1249 DicomToJsonFormat format)
1250 {
1251 FromDcmtkBridge::ExtractHeaderAsJson(target, *pimpl_->file_->getMetaInfo(), format, DicomToJsonFlags_None, 0);
1252 }
1253
1254
1255 bool ParsedDicomFile::HasTag(const DicomTag& tag) const
1256 {
1257 DcmTag key(tag.GetGroup(), tag.GetElement());
1258 return pimpl_->file_->getDataset()->tagExists(key);
1259 }
1260
1261
1262 void ParsedDicomFile::EmbedPdf(const std::string& pdf)
1263 {
1264 if (pdf.size() < 5 || // (*)
1265 strncmp("%PDF-", pdf.c_str(), 5) != 0)
1266 {
1267 LOG(ERROR) << "Not a PDF file";
1268 throw OrthancException(ErrorCode_BadFileFormat);
1269 }
1270
1271 InvalidateCache();
1272
1273 ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
1274 ReplacePlainString(FromDcmtkBridge::Convert(DCM_Modality), "OT");
1275 ReplacePlainString(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
1276 ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf");
1277 //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
1278
1279 std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
1280
1281 size_t s = pdf.size();
1282 if (s & 1)
1283 {
1284 // The size of the buffer must be even
1285 s += 1;
1286 }
1287
1288 Uint8* bytes = NULL;
1289 OFCondition result = element->createUint8Array(s, bytes);
1290 if (!result.good() || bytes == NULL)
1291 {
1292 throw OrthancException(ErrorCode_NotEnoughMemory);
1293 }
1294
1295 // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) )
1296 bytes[s - 1] = 0;
1297
1298 memcpy(bytes, pdf.c_str(), pdf.size());
1299
1300 DcmPolymorphOBOW* obj = element.release();
1301 result = pimpl_->file_->getDataset()->insert(obj);
1302
1303 if (!result.good())
1304 {
1305 delete obj;
1306 throw OrthancException(ErrorCode_NotEnoughMemory);
1307 }
1308 }
1309
1310
1311 bool ParsedDicomFile::ExtractPdf(std::string& pdf)
1312 {
1313 std::string sop, mime;
1314
1315 if (!GetTagValue(sop, DICOM_TAG_SOP_CLASS_UID) ||
1316 !GetTagValue(mime, FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument)) ||
1317 sop != UID_EncapsulatedPDFStorage ||
1318 mime != "application/pdf")
1319 {
1320 return false;
1321 }
1322
1323 if (!GetTagValue(pdf, DICOM_TAG_ENCAPSULATED_DOCUMENT))
1324 {
1325 return false;
1326 }
1327
1328 // Strip the possible pad byte at the end of file, because the
1329 // encapsulated documents must always have an even length. The PDF
1330 // format expects files to end with %%EOF followed by CR/LF. If
1331 // the last character of the file is not a CR or LF, we assume it
1332 // is a pad byte and remove it.
1333 if (pdf.size() > 0)
1334 {
1335 char last = *pdf.rbegin();
1336
1337 if (last != 10 && last != 13)
1338 {
1339 pdf.resize(pdf.size() - 1);
1340 }
1341 }
1342
1343 return true;
1344 }
1345
1346
1347 ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json,
1348 DicomFromJsonFlags flags)
1349 {
1350 const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false;
1351 const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false;
1352
1353 std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers));
1354 result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding()));
1355
1356 const Json::Value::Members tags = json.getMemberNames();
1357
1358 for (size_t i = 0; i < tags.size(); i++)
1359 {
1360 DicomTag tag = FromDcmtkBridge::ParseTag(tags[i]);
1361 const Json::Value& value = json[tags[i]];
1362
1363 if (tag == DICOM_TAG_PIXEL_DATA ||
1364 tag == DICOM_TAG_ENCAPSULATED_DOCUMENT)
1365 {
1366 if (value.type() != Json::stringValue)
1367 {
1368 throw OrthancException(ErrorCode_BadRequest);
1369 }
1370 else
1371 {
1372 result->EmbedContent(value.asString());
1373 }
1374 }
1375 else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
1376 {
1377 result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent);
1378 }
1379 }
1380
1381 return result.release();
1382 }
1383
1384
1385 void ParsedDicomFile::GetRawFrame(std::string& target,
1386 std::string& mime,
1387 unsigned int frameId)
1388 {
1389 if (pimpl_->frameIndex_.get() == NULL)
1390 {
1391 pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_));
1392 }
1393
1394 pimpl_->frameIndex_->GetRawFrame(target, frameId);
1395
1396 E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
1397 switch (transferSyntax)
1398 {
1399 case EXS_JPEGProcess1:
1400 mime = "image/jpeg";
1401 break;
1402
1403 case EXS_JPEG2000LosslessOnly:
1404 case EXS_JPEG2000:
1405 mime = "image/jp2";
1406 break;
1407
1408 default:
1409 mime = "application/octet-stream";
1410 break;
1411 }
1412 }
1413
1414
1415 void ParsedDicomFile::InvalidateCache()
1416 {
1417 pimpl_->frameIndex_.reset(NULL);
1418 }
1419
1420
1421 unsigned int ParsedDicomFile::GetFramesCount() const
1422 {
1423 return DicomFrameIndex::GetFramesCount(*pimpl_->file_);
1424 }
1425
1426
1427 void ParsedDicomFile::ChangeEncoding(Encoding target)
1428 {
1429 Encoding source = GetEncoding();
1430
1431 if (source != target) // Avoid unnecessary conversion
1432 {
1433 ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, GetDicomSpecificCharacterSet(target));
1434 FromDcmtkBridge::ChangeStringEncoding(*pimpl_->file_->getDataset(), source, target);
1435 }
1436 }
1437
1438
1439 void ParsedDicomFile::ExtractDicomSummary(DicomMap& target) const
1440 {
1441 FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset());
1442 }
1443
1444
1445 void ParsedDicomFile::ExtractDicomAsJson(Json::Value& target) const
1446 {
1447 FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset());
1448 }
1449
1450
1451 bool ParsedDicomFile::LookupTransferSyntax(std::string& result)
1452 {
1453 return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_);
1454 }
1455
1456
1457 bool ParsedDicomFile::LookupPhotometricInterpretation(PhotometricInterpretation& result) const
1458 {
1459 DcmTagKey k(DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetGroup(),
1460 DICOM_TAG_PHOTOMETRIC_INTERPRETATION.GetElement());
1461
1462 DcmDataset& dataset = *pimpl_->file_->getDataset();
1463
1464 const char *c = NULL;
1465 if (dataset.findAndGetString(k, c).good() &&
1466 c != NULL)
1467 {
1468 result = StringToPhotometricInterpretation(c);
1469 return true;
1470 }
1471 else
1472 {
1473 return false;
1474 }
1475 }
1476 }