Mercurial > hg > orthanc
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 } |