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