comparison OrthancServer/ParsedDicomFile.cpp @ 948:e57e08ed510f dicom-rt

integration mainline -> dicom-rt
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 25 Jun 2014 13:57:05 +0200
parents 87791ebc1f50
children
comparison
equal deleted inserted replaced
767:c19552f604d5 948:e57e08ed510f
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
4 * Belgium
5 *
6 * This program is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * In addition, as a special exception, the copyright holders of this
12 * program give permission to link the code of its release with the
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it
14 * that use the same license as the "OpenSSL" library), and distribute
15 * the linked executables. You must obey the GNU General Public License
16 * in all respects for all of the code used other than "OpenSSL". If you
17 * modify file(s) with this exception, you may extend this exception to
18 * your version of the file(s), but you are not obligated to do so. If
19 * you do not wish to do so, delete this exception statement from your
20 * version. If you delete this exception statement from all source files
21 * in the program, then also delete it here.
22 *
23 * This program is distributed in the hope that it will be useful, but
24 * WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
26 * General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 **/
31
32
33
34 /*=========================================================================
35
36 This file is based on portions of the following project:
37
38 Program: GDCM (Grassroots DICOM). A DICOM library
39 Module: http://gdcm.sourceforge.net/Copyright.html
40
41 Copyright (c) 2006-2011 Mathieu Malaterre
42 Copyright (c) 1993-2005 CREATIS
43 (CREATIS = Centre de Recherche et d'Applications en Traitement de l'Image)
44 All rights reserved.
45
46 Redistribution and use in source and binary forms, with or without
47 modification, are permitted provided that the following conditions are met:
48
49 * Redistributions of source code must retain the above copyright notice,
50 this list of conditions and the following disclaimer.
51
52 * Redistributions in binary form must reproduce the above copyright notice,
53 this list of conditions and the following disclaimer in the documentation
54 and/or other materials provided with the distribution.
55
56 * Neither name of Mathieu Malaterre, or CREATIS, nor the names of any
57 contributors (CNRS, INSERM, UCB, Universite Lyon I), may be used to
58 endorse or promote products derived from this software without specific
59 prior written permission.
60
61 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
62 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
63 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
64 ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
65 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
66 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
67 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
68 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
69 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
70 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
71
72 =========================================================================*/
73
74
75 #include "PrecompiledHeadersServer.h"
76
77 #ifndef NOMINMAX
78 #define NOMINMAX
79 #endif
80
81 #include "ParsedDicomFile.h"
82
83 #include "FromDcmtkBridge.h"
84 #include "ToDcmtkBridge.h"
85 #include "Internals/DicomImageDecoder.h"
86 #include "../Core/Toolbox.h"
87 #include "../Core/OrthancException.h"
88 #include "../Core/ImageFormats/ImageBuffer.h"
89 #include "../Core/ImageFormats/PngWriter.h"
90 #include "../Core/Uuid.h"
91 #include "../Core/DicomFormat/DicomString.h"
92 #include "../Core/DicomFormat/DicomNullValue.h"
93 #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
94 #include "../Core/ImageFormats/PngReader.h"
95
96 #include <list>
97 #include <limits>
98
99 #include <boost/lexical_cast.hpp>
100
101 #include <dcmtk/dcmdata/dcchrstr.h>
102 #include <dcmtk/dcmdata/dcdicent.h>
103 #include <dcmtk/dcmdata/dcdict.h>
104 #include <dcmtk/dcmdata/dcfilefo.h>
105 #include <dcmtk/dcmdata/dcistrmb.h>
106 #include <dcmtk/dcmdata/dcuid.h>
107 #include <dcmtk/dcmdata/dcmetinf.h>
108
109 #include <dcmtk/dcmdata/dcvrae.h>
110 #include <dcmtk/dcmdata/dcvras.h>
111 #include <dcmtk/dcmdata/dcvrcs.h>
112 #include <dcmtk/dcmdata/dcvrda.h>
113 #include <dcmtk/dcmdata/dcvrds.h>
114 #include <dcmtk/dcmdata/dcvrdt.h>
115 #include <dcmtk/dcmdata/dcvrfd.h>
116 #include <dcmtk/dcmdata/dcvrfl.h>
117 #include <dcmtk/dcmdata/dcvris.h>
118 #include <dcmtk/dcmdata/dcvrlo.h>
119 #include <dcmtk/dcmdata/dcvrlt.h>
120 #include <dcmtk/dcmdata/dcvrpn.h>
121 #include <dcmtk/dcmdata/dcvrsh.h>
122 #include <dcmtk/dcmdata/dcvrsl.h>
123 #include <dcmtk/dcmdata/dcvrss.h>
124 #include <dcmtk/dcmdata/dcvrst.h>
125 #include <dcmtk/dcmdata/dcvrtm.h>
126 #include <dcmtk/dcmdata/dcvrui.h>
127 #include <dcmtk/dcmdata/dcvrul.h>
128 #include <dcmtk/dcmdata/dcvrus.h>
129 #include <dcmtk/dcmdata/dcvrut.h>
130 #include <dcmtk/dcmdata/dcpixel.h>
131 #include <dcmtk/dcmdata/dcpixseq.h>
132 #include <dcmtk/dcmdata/dcpxitem.h>
133
134
135 #include <boost/math/special_functions/round.hpp>
136 #include <glog/logging.h>
137 #include <dcmtk/dcmdata/dcostrmb.h>
138
139
140 static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
141
142
143
144 namespace Orthanc
145 {
146 struct ParsedDicomFile::PImpl
147 {
148 std::auto_ptr<DcmFileFormat> file_;
149 };
150
151
152 // This method can only be called from the constructors!
153 void ParsedDicomFile::Setup(const char* buffer, size_t size)
154 {
155 DcmInputBufferStream is;
156 if (size > 0)
157 {
158 is.setBuffer(buffer, size);
159 }
160 is.setEos();
161
162 pimpl_->file_.reset(new DcmFileFormat);
163 pimpl_->file_->transferInit();
164 if (!pimpl_->file_->read(is).good())
165 {
166 delete pimpl_; // Avoid a memory leak due to exception
167 // throwing, as we are in the constructor
168
169 throw OrthancException(ErrorCode_BadFileFormat);
170 }
171 pimpl_->file_->loadAllDataIntoMemory();
172 pimpl_->file_->transferEnd();
173 }
174
175
176 static void SendPathValueForDictionary(RestApiOutput& output,
177 DcmItem& dicom)
178 {
179 Json::Value v = Json::arrayValue;
180
181 for (unsigned long i = 0; i < dicom.card(); i++)
182 {
183 DcmElement* element = dicom.getElement(i);
184 if (element)
185 {
186 char buf[16];
187 sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
188 v.append(buf);
189 }
190 }
191
192 output.AnswerJson(v);
193 }
194
195 static inline uint16_t GetCharValue(char c)
196 {
197 if (c >= '0' && c <= '9')
198 return c - '0';
199 else if (c >= 'a' && c <= 'f')
200 return c - 'a' + 10;
201 else if (c >= 'A' && c <= 'F')
202 return c - 'A' + 10;
203 else
204 return 0;
205 }
206
207 static inline uint16_t GetTagValue(const char* c)
208 {
209 return ((GetCharValue(c[0]) << 12) +
210 (GetCharValue(c[1]) << 8) +
211 (GetCharValue(c[2]) << 4) +
212 GetCharValue(c[3]));
213 }
214
215 static void ParseTagAndGroup(DcmTagKey& key,
216 const std::string& tag)
217 {
218 DicomTag t = FromDcmtkBridge::ParseTag(tag);
219 key = DcmTagKey(t.GetGroup(), t.GetElement());
220 }
221
222
223 static void SendSequence(RestApiOutput& output,
224 DcmSequenceOfItems& sequence)
225 {
226 // This element is a sequence
227 Json::Value v = Json::arrayValue;
228
229 for (unsigned long i = 0; i < sequence.card(); i++)
230 {
231 v.append(boost::lexical_cast<std::string>(i));
232 }
233
234 output.AnswerJson(v);
235 }
236
237
238 static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData,
239 E_TransferSyntax transferSyntax)
240 {
241 DcmPixelSequence* pixelSequence = NULL;
242 if (pixelData.getEncapsulatedRepresentation
243 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
244 {
245 return pixelSequence->card();
246 }
247 else
248 {
249 return 1;
250 }
251 }
252
253
254 static void AnswerDicomField(RestApiOutput& output,
255 DcmElement& element,
256 E_TransferSyntax transferSyntax)
257 {
258 // This element is nor a sequence, neither a pixel-data
259 std::string buffer;
260 buffer.resize(65536);
261 Uint32 length = element.getLength(transferSyntax);
262 Uint32 offset = 0;
263
264 output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL);
265
266 while (offset < length)
267 {
268 Uint32 nbytes;
269 if (length - offset < buffer.size())
270 {
271 nbytes = length - offset;
272 }
273 else
274 {
275 nbytes = buffer.size();
276 }
277
278 OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes);
279
280 if (cond.good())
281 {
282 output.GetLowLevelOutput().Send(&buffer[0], nbytes);
283 offset += nbytes;
284 }
285 else
286 {
287 LOG(ERROR) << "Error while sending a DICOM field: " << cond.text();
288 return;
289 }
290 }
291
292 output.MarkLowLevelOutputDone();
293 }
294
295
296 static bool AnswerPixelData(RestApiOutput& output,
297 DcmItem& dicom,
298 E_TransferSyntax transferSyntax,
299 const std::string* blockUri)
300 {
301 DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(),
302 DICOM_TAG_PIXEL_DATA.GetElement());
303
304 DcmElement *element = NULL;
305 if (!dicom.findAndGetElement(k, element).good() ||
306 element == NULL)
307 {
308 return false;
309 }
310
311 try
312 {
313 DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
314 if (blockUri == NULL)
315 {
316 // The user asks how many blocks are presents in this pixel data
317 unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax);
318
319 Json::Value result(Json::arrayValue);
320 for (unsigned int i = 0; i < blocks; i++)
321 {
322 result.append(boost::lexical_cast<std::string>(i));
323 }
324
325 output.AnswerJson(result);
326 return true;
327 }
328
329
330 unsigned int block = boost::lexical_cast<unsigned int>(*blockUri);
331
332 if (block < GetPixelDataBlockCount(pixelData, transferSyntax))
333 {
334 DcmPixelSequence* pixelSequence = NULL;
335 if (pixelData.getEncapsulatedRepresentation
336 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
337 {
338 // This is the case for JPEG transfer syntaxes
339 if (block < pixelSequence->card())
340 {
341 DcmPixelItem* pixelItem = NULL;
342 if (pixelSequence->getItem(pixelItem, block).good() && pixelItem)
343 {
344 if (pixelItem->getLength() == 0)
345 {
346 output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM);
347 return true;
348 }
349
350 Uint8* buffer = NULL;
351 if (pixelItem->getUint8Array(buffer).good() && buffer)
352 {
353 output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM);
354 return true;
355 }
356 }
357 }
358 }
359 else
360 {
361 // This is the case for raw, uncompressed image buffers
362 assert(*blockUri == "0");
363 AnswerDicomField(output, *element, transferSyntax);
364 }
365 }
366 }
367 catch (boost::bad_lexical_cast&)
368 {
369 // The URI entered by the user is not a number
370 }
371 catch (std::bad_cast&)
372 {
373 // This should never happen
374 }
375
376 return false;
377 }
378
379
380
381 static void SendPathValueForLeaf(RestApiOutput& output,
382 const std::string& tag,
383 DcmItem& dicom,
384 E_TransferSyntax transferSyntax)
385 {
386 DcmTagKey k;
387 ParseTagAndGroup(k, tag);
388
389 DcmSequenceOfItems* sequence = NULL;
390 if (dicom.findAndGetSequence(k, sequence).good() &&
391 sequence != NULL &&
392 sequence->getVR() == EVR_SQ)
393 {
394 SendSequence(output, *sequence);
395 return;
396 }
397
398 DcmElement* element = NULL;
399 if (dicom.findAndGetElement(k, element).good() &&
400 element != NULL &&
401 //element->getVR() != EVR_UNKNOWN && // This would forbid private tags
402 element->getVR() != EVR_SQ)
403 {
404 AnswerDicomField(output, *element, transferSyntax);
405 }
406 }
407
408 void ParsedDicomFile::SendPathValue(RestApiOutput& output,
409 const UriComponents& uri)
410 {
411 DcmItem* dicom = pimpl_->file_->getDataset();
412 E_TransferSyntax transferSyntax = pimpl_->file_->getDataset()->getOriginalXfer();
413
414 // Special case: Accessing the pixel data
415 if (uri.size() == 1 ||
416 uri.size() == 2)
417 {
418 DcmTagKey tag;
419 ParseTagAndGroup(tag, uri[0]);
420
421 if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() &&
422 tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement())
423 {
424 AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]);
425 return;
426 }
427 }
428
429 // Go down in the tag hierarchy according to the URI
430 for (size_t pos = 0; pos < uri.size() / 2; pos++)
431 {
432 size_t index;
433 try
434 {
435 index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
436 }
437 catch (boost::bad_lexical_cast&)
438 {
439 return;
440 }
441
442 DcmTagKey k;
443 DcmItem *child = NULL;
444 ParseTagAndGroup(k, uri[2 * pos]);
445 if (!dicom->findAndGetSequenceItem(k, child, index).good() ||
446 child == NULL)
447 {
448 return;
449 }
450
451 dicom = child;
452 }
453
454 // We have reached the end of the URI
455 if (uri.size() % 2 == 0)
456 {
457 SendPathValueForDictionary(output, *dicom);
458 }
459 else
460 {
461 SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax);
462 }
463 }
464
465
466
467
468
469 static DcmElement* CreateElementForTag(const DicomTag& tag)
470 {
471 DcmTag key(tag.GetGroup(), tag.GetElement());
472
473 switch (key.getEVR())
474 {
475 // http://support.dcmtk.org/docs/dcvr_8h-source.html
476
477 /**
478 * TODO.
479 **/
480
481 case EVR_OB: // other byte
482 case EVR_OF: // other float
483 case EVR_OW: // other word
484 case EVR_AT: // attribute tag
485 throw OrthancException(ErrorCode_NotImplemented);
486
487 case EVR_UN: // unknown value representation
488 throw OrthancException(ErrorCode_ParameterOutOfRange);
489
490
491 /**
492 * String types.
493 * http://support.dcmtk.org/docs/classDcmByteString.html
494 **/
495
496 case EVR_AS: // age string
497 return new DcmAgeString(key);
498
499 case EVR_AE: // application entity title
500 return new DcmApplicationEntity(key);
501
502 case EVR_CS: // code string
503 return new DcmCodeString(key);
504
505 case EVR_DA: // date string
506 return new DcmDate(key);
507
508 case EVR_DT: // date time string
509 return new DcmDateTime(key);
510
511 case EVR_DS: // decimal string
512 return new DcmDecimalString(key);
513
514 case EVR_IS: // integer string
515 return new DcmIntegerString(key);
516
517 case EVR_TM: // time string
518 return new DcmTime(key);
519
520 case EVR_UI: // unique identifier
521 return new DcmUniqueIdentifier(key);
522
523 case EVR_ST: // short text
524 return new DcmShortText(key);
525
526 case EVR_LO: // long string
527 return new DcmLongString(key);
528
529 case EVR_LT: // long text
530 return new DcmLongText(key);
531
532 case EVR_UT: // unlimited text
533 return new DcmUnlimitedText(key);
534
535 case EVR_SH: // short string
536 return new DcmShortString(key);
537
538 case EVR_PN: // person name
539 return new DcmPersonName(key);
540
541
542 /**
543 * Numerical types
544 **/
545
546 case EVR_SL: // signed long
547 return new DcmSignedLong(key);
548
549 case EVR_SS: // signed short
550 return new DcmSignedShort(key);
551
552 case EVR_UL: // unsigned long
553 return new DcmUnsignedLong(key);
554
555 case EVR_US: // unsigned short
556 return new DcmUnsignedShort(key);
557
558 case EVR_FL: // float single-precision
559 return new DcmFloatingPointSingle(key);
560
561 case EVR_FD: // float double-precision
562 return new DcmFloatingPointDouble(key);
563
564
565 /**
566 * Sequence types, should never occur at this point.
567 **/
568
569 case EVR_SQ: // sequence of items
570 throw OrthancException(ErrorCode_ParameterOutOfRange);
571
572
573 /**
574 * Internal to DCMTK.
575 **/
576
577 case EVR_ox: // OB or OW depending on context
578 case EVR_xs: // SS or US depending on context
579 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name)
580 case EVR_na: // na="not applicable", for data which has no VR
581 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor
582 case EVR_item: // used internally for items
583 case EVR_metainfo: // used internally for meta info datasets
584 case EVR_dataset: // used internally for datasets
585 case EVR_fileFormat: // used internally for DICOM files
586 case EVR_dicomDir: // used internally for DICOMDIR objects
587 case EVR_dirRecord: // used internally for DICOMDIR records
588 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image
589 case EVR_pixelItem: // used internally for pixel items in a compressed image
590 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
591 case EVR_PixelData: // used internally for uncompressed pixeld data
592 case EVR_OverlayData: // used internally for overlay data
593 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR
594 default:
595 break;
596 }
597
598 throw OrthancException(ErrorCode_InternalError);
599 }
600
601
602
603 static void FillElementWithString(DcmElement& element,
604 const DicomTag& tag,
605 const std::string& value)
606 {
607 DcmTag key(tag.GetGroup(), tag.GetElement());
608 bool ok = false;
609
610 try
611 {
612 switch (key.getEVR())
613 {
614 // http://support.dcmtk.org/docs/dcvr_8h-source.html
615
616 /**
617 * TODO.
618 **/
619
620 case EVR_OB: // other byte
621 case EVR_OF: // other float
622 case EVR_OW: // other word
623 case EVR_AT: // attribute tag
624 throw OrthancException(ErrorCode_NotImplemented);
625
626 case EVR_UN: // unknown value representation
627 throw OrthancException(ErrorCode_ParameterOutOfRange);
628
629
630 /**
631 * String types.
632 **/
633
634 case EVR_DS: // decimal string
635 case EVR_IS: // integer string
636 case EVR_AS: // age string
637 case EVR_DA: // date string
638 case EVR_DT: // date time string
639 case EVR_TM: // time string
640 case EVR_AE: // application entity title
641 case EVR_CS: // code string
642 case EVR_SH: // short string
643 case EVR_LO: // long string
644 case EVR_ST: // short text
645 case EVR_LT: // long text
646 case EVR_UT: // unlimited text
647 case EVR_PN: // person name
648 case EVR_UI: // unique identifier
649 {
650 ok = element.putString(value.c_str()).good();
651 break;
652 }
653
654
655 /**
656 * Numerical types
657 **/
658
659 case EVR_SL: // signed long
660 {
661 ok = element.putSint32(boost::lexical_cast<Sint32>(value)).good();
662 break;
663 }
664
665 case EVR_SS: // signed short
666 {
667 ok = element.putSint16(boost::lexical_cast<Sint16>(value)).good();
668 break;
669 }
670
671 case EVR_UL: // unsigned long
672 {
673 ok = element.putUint32(boost::lexical_cast<Uint32>(value)).good();
674 break;
675 }
676
677 case EVR_US: // unsigned short
678 {
679 ok = element.putUint16(boost::lexical_cast<Uint16>(value)).good();
680 break;
681 }
682
683 case EVR_FL: // float single-precision
684 {
685 ok = element.putFloat32(boost::lexical_cast<float>(value)).good();
686 break;
687 }
688
689 case EVR_FD: // float double-precision
690 {
691 ok = element.putFloat64(boost::lexical_cast<double>(value)).good();
692 break;
693 }
694
695
696 /**
697 * Sequence types, should never occur at this point.
698 **/
699
700 case EVR_SQ: // sequence of items
701 {
702 ok = false;
703 break;
704 }
705
706
707 /**
708 * Internal to DCMTK.
709 **/
710
711 case EVR_ox: // OB or OW depending on context
712 case EVR_xs: // SS or US depending on context
713 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name)
714 case EVR_na: // na="not applicable", for data which has no VR
715 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor
716 case EVR_item: // used internally for items
717 case EVR_metainfo: // used internally for meta info datasets
718 case EVR_dataset: // used internally for datasets
719 case EVR_fileFormat: // used internally for DICOM files
720 case EVR_dicomDir: // used internally for DICOMDIR objects
721 case EVR_dirRecord: // used internally for DICOMDIR records
722 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image
723 case EVR_pixelItem: // used internally for pixel items in a compressed image
724 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
725 case EVR_PixelData: // used internally for uncompressed pixeld data
726 case EVR_OverlayData: // used internally for overlay data
727 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR
728 default:
729 break;
730 }
731 }
732 catch (boost::bad_lexical_cast&)
733 {
734 ok = false;
735 }
736
737 if (!ok)
738 {
739 throw OrthancException(ErrorCode_InternalError);
740 }
741 }
742
743
744 void ParsedDicomFile::Remove(const DicomTag& tag)
745 {
746 DcmTagKey key(tag.GetGroup(), tag.GetElement());
747 DcmElement* element = pimpl_->file_->getDataset()->remove(key);
748 if (element != NULL)
749 {
750 delete element;
751 }
752 }
753
754
755
756 void ParsedDicomFile::RemovePrivateTags()
757 {
758 typedef std::list<DcmElement*> Tags;
759
760 Tags privateTags;
761
762 DcmDataset& dataset = *pimpl_->file_->getDataset();
763 for (unsigned long i = 0; i < dataset.card(); i++)
764 {
765 DcmElement* element = dataset.getElement(i);
766 DcmTag tag(element->getTag());
767 if (!strcmp("PrivateCreator", tag.getTagName()) || // TODO - This may change with future versions of DCMTK
768 tag.getPrivateCreator() != NULL)
769 {
770 privateTags.push_back(element);
771 }
772 }
773
774 for (Tags::iterator it = privateTags.begin();
775 it != privateTags.end(); ++it)
776 {
777 DcmElement* tmp = dataset.remove(*it);
778 if (tmp != NULL)
779 {
780 delete tmp;
781 }
782 }
783 }
784
785
786
787 void ParsedDicomFile::Insert(const DicomTag& tag,
788 const std::string& value)
789 {
790 std::auto_ptr<DcmElement> element(CreateElementForTag(tag));
791 FillElementWithString(*element, tag, value);
792
793 if (!pimpl_->file_->getDataset()->insert(element.release(), false, false).good())
794 {
795 // This field already exists
796 throw OrthancException(ErrorCode_InternalError);
797 }
798 }
799
800
801 void ParsedDicomFile::Replace(const DicomTag& tag,
802 const std::string& value,
803 DicomReplaceMode mode)
804 {
805 DcmTagKey key(tag.GetGroup(), tag.GetElement());
806 DcmElement* element = NULL;
807
808 if (!pimpl_->file_->getDataset()->findAndGetElement(key, element).good() ||
809 element == NULL)
810 {
811 // This field does not exist, act wrt. the specified "mode"
812 switch (mode)
813 {
814 case DicomReplaceMode_InsertIfAbsent:
815 Insert(tag, value);
816 break;
817
818 case DicomReplaceMode_ThrowIfAbsent:
819 throw OrthancException(ErrorCode_InexistentItem);
820
821 case DicomReplaceMode_IgnoreIfAbsent:
822 return;
823 }
824 }
825 else
826 {
827 FillElementWithString(*element, tag, value);
828 }
829
830
831 /**
832 * dcmodify will automatically correct 'Media Storage SOP Class
833 * UID' and 'Media Storage SOP Instance UID' in the metaheader, if
834 * you make changes to the related tags in the dataset ('SOP Class
835 * UID' and 'SOP Instance UID') via insert or modify mode
836 * options. You can disable this behaviour by using the -nmu
837 * option.
838 **/
839
840 if (tag == DICOM_TAG_SOP_CLASS_UID)
841 {
842 Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent);
843 }
844
845 if (tag == DICOM_TAG_SOP_INSTANCE_UID)
846 {
847 Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent);
848 }
849 }
850
851
852 void ParsedDicomFile::Answer(RestApiOutput& output)
853 {
854 std::string serialized;
855 if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, pimpl_->file_->getDataset()))
856 {
857 output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM);
858 }
859 }
860
861
862
863
864 static bool GetTagValueInternal(std::string& value,
865 DcmItem& item,
866 const DicomTag& tag)
867 {
868 DcmTagKey k(tag.GetGroup(), tag.GetElement());
869 DcmElement* element = NULL;
870 if (!item.findAndGetElement(k, element).good() ||
871 element == NULL)
872 {
873 return false;
874 }
875
876 std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element));
877
878 if (v.get() == NULL)
879 {
880 value = "";
881 }
882 else
883 {
884 value = v->AsString();
885 }
886
887 return true;
888 }
889
890
891 bool ParsedDicomFile::GetTagValue(std::string& value,
892 const DicomTag& tag)
893 {
894 DcmDataset& dataset = *pimpl_->file_->getDataset();
895 return GetTagValueInternal(value, dataset, tag);
896 }
897
898
899
900 DicomInstanceHasher ParsedDicomFile::GetHasher()
901 {
902 std::string patientId, studyUid, seriesUid, instanceUid;
903
904 if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) ||
905 !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
906 !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) ||
907 !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID))
908 {
909 throw OrthancException(ErrorCode_BadFileFormat);
910 }
911
912 return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid);
913 }
914
915
916 static void StoreElement(Json::Value& target,
917 DcmElement& element,
918 unsigned int maxStringLength);
919
920 static void StoreItem(Json::Value& target,
921 DcmItem& item,
922 unsigned int maxStringLength)
923 {
924 target = Json::Value(Json::objectValue);
925
926 for (unsigned long i = 0; i < item.card(); i++)
927 {
928 DcmElement* element = item.getElement(i);
929 StoreElement(target, *element, maxStringLength);
930 }
931 }
932
933
934 static void StoreElement(Json::Value& target,
935 DcmElement& element,
936 unsigned int maxStringLength)
937 {
938 assert(target.type() == Json::objectValue);
939
940 DicomTag tag(FromDcmtkBridge::GetTag(element));
941 const std::string formattedTag = tag.Format();
942
943 #if 0
944 const std::string tagName = FromDcmtkBridge::GetName(tag);
945 #else
946 // This version of the code gives access to the name of the private tags
947 DcmTag tagbis(element.getTag());
948 const std::string tagName(tagbis.getTagName());
949 #endif
950
951 if (element.isLeaf())
952 {
953 Json::Value value(Json::objectValue);
954 value["Name"] = tagName;
955
956 if (tagbis.getPrivateCreator() != NULL)
957 {
958 value["PrivateCreator"] = tagbis.getPrivateCreator();
959 }
960
961 std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element));
962 if (v->IsNull())
963 {
964 value["Type"] = "Null";
965 value["Value"] = Json::nullValue;
966 }
967 else
968 {
969 std::string s = v->AsString();
970 if (maxStringLength == 0 ||
971 s.size() <= maxStringLength)
972 {
973 value["Type"] = "String";
974 value["Value"] = s;
975 }
976 else
977 {
978 value["Type"] = "TooLong";
979 value["Value"] = Json::nullValue;
980 }
981 }
982
983 target[formattedTag] = value;
984 }
985 else
986 {
987 Json::Value children(Json::arrayValue);
988
989 // "All subclasses of DcmElement except for DcmSequenceOfItems
990 // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
991 // etc. are not." The following cast is thus OK.
992 DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
993
994 for (unsigned long i = 0; i < sequence.card(); i++)
995 {
996 DcmItem* child = sequence.getItem(i);
997 Json::Value& v = children.append(Json::objectValue);
998 StoreItem(v, *child, maxStringLength);
999 }
1000
1001 target[formattedTag]["Name"] = tagName;
1002 target[formattedTag]["Type"] = "Sequence";
1003 target[formattedTag]["Value"] = children;
1004 }
1005 }
1006
1007
1008 template <typename T>
1009 static void ExtractPngImageTruncate(std::string& result,
1010 DicomIntegerPixelAccessor& accessor,
1011 PixelFormat format)
1012 {
1013 assert(accessor.GetInformation().GetChannelCount() == 1);
1014
1015 PngWriter w;
1016
1017 std::vector<T> image(accessor.GetInformation().GetWidth() * accessor.GetInformation().GetHeight(), 0);
1018 T* pixel = &image[0];
1019 for (unsigned int y = 0; y < accessor.GetInformation().GetHeight(); y++)
1020 {
1021 for (unsigned int x = 0; x < accessor.GetInformation().GetWidth(); x++, pixel++)
1022 {
1023 int32_t v = accessor.GetValue(x, y);
1024 if (v < static_cast<int32_t>(std::numeric_limits<T>::min()))
1025 *pixel = std::numeric_limits<T>::min();
1026 else if (v > static_cast<int32_t>(std::numeric_limits<T>::max()))
1027 *pixel = std::numeric_limits<T>::max();
1028 else
1029 *pixel = static_cast<T>(v);
1030 }
1031 }
1032
1033 w.WriteToMemory(result, accessor.GetInformation().GetWidth(), accessor.GetInformation().GetHeight(),
1034 accessor.GetInformation().GetWidth() * sizeof(T), format, &image[0]);
1035 }
1036
1037
1038 void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer)
1039 {
1040 FromDcmtkBridge::SaveToMemoryBuffer(buffer, pimpl_->file_->getDataset());
1041 }
1042
1043
1044 void ParsedDicomFile::SaveToFile(const std::string& path)
1045 {
1046 // TODO Avoid using a temporary memory buffer, write directly on disk
1047 std::string content;
1048 SaveToMemoryBuffer(content);
1049 Toolbox::WriteFile(content, path);
1050 }
1051
1052
1053 ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl)
1054 {
1055 pimpl_->file_.reset(new DcmFileFormat);
1056 Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
1057 Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
1058 Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
1059 Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
1060 }
1061
1062
1063 ParsedDicomFile::ParsedDicomFile(const char* content, size_t size) : pimpl_(new PImpl)
1064 {
1065 Setup(content, size);
1066 }
1067
1068 ParsedDicomFile::ParsedDicomFile(const std::string& content) : pimpl_(new PImpl)
1069 {
1070 if (content.size() == 0)
1071 {
1072 Setup(NULL, 0);
1073 }
1074 else
1075 {
1076 Setup(&content[0], content.size());
1077 }
1078 }
1079
1080
1081 ParsedDicomFile::ParsedDicomFile(ParsedDicomFile& other) :
1082 pimpl_(new PImpl)
1083 {
1084 pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone()));
1085 }
1086
1087
1088 ParsedDicomFile::~ParsedDicomFile()
1089 {
1090 delete pimpl_;
1091 }
1092
1093
1094 void* ParsedDicomFile::GetDcmtkObject()
1095 {
1096 return pimpl_->file_.get();
1097 }
1098
1099
1100 ParsedDicomFile* ParsedDicomFile::Clone()
1101 {
1102 return new ParsedDicomFile(*this);
1103 }
1104
1105
1106 void ParsedDicomFile::EmbedImage(const std::string& dataUriScheme)
1107 {
1108 std::string mime, content;
1109 Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme);
1110
1111 std::string decoded;
1112 Toolbox::DecodeBase64(decoded, content);
1113
1114 if (mime == "image/png")
1115 {
1116 PngReader reader;
1117 reader.ReadFromMemory(decoded);
1118 EmbedImage(reader);
1119 }
1120 else
1121 {
1122 throw OrthancException(ErrorCode_NotImplemented);
1123 }
1124 }
1125
1126
1127 void ParsedDicomFile::EmbedImage(const ImageAccessor& accessor)
1128 {
1129 if (accessor.GetFormat() != PixelFormat_Grayscale8 &&
1130 accessor.GetFormat() != PixelFormat_Grayscale16 &&
1131 accessor.GetFormat() != PixelFormat_RGB24 &&
1132 accessor.GetFormat() != PixelFormat_RGBA32)
1133 {
1134 throw OrthancException(ErrorCode_NotImplemented);
1135 }
1136
1137 if (accessor.GetFormat() == PixelFormat_RGBA32)
1138 {
1139 LOG(WARNING) << "Getting rid of the alpha channel when embedding a RGBA image inside DICOM";
1140 }
1141
1142 // http://dicomiseasy.blogspot.be/2012/08/chapter-12-pixel-data.html
1143
1144 Remove(DICOM_TAG_PIXEL_DATA);
1145 Replace(DICOM_TAG_COLUMNS, boost::lexical_cast<std::string>(accessor.GetWidth()));
1146 Replace(DICOM_TAG_ROWS, boost::lexical_cast<std::string>(accessor.GetHeight()));
1147 Replace(DICOM_TAG_SAMPLES_PER_PIXEL, "1");
1148 Replace(DICOM_TAG_NUMBER_OF_FRAMES, "1");
1149 Replace(DICOM_TAG_PIXEL_REPRESENTATION, "0"); // Unsigned pixels
1150 Replace(DICOM_TAG_PLANAR_CONFIGURATION, "0"); // Color channels are interleaved
1151 Replace(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "MONOCHROME2");
1152 Replace(DICOM_TAG_BITS_ALLOCATED, "8");
1153 Replace(DICOM_TAG_BITS_STORED, "8");
1154 Replace(DICOM_TAG_HIGH_BIT, "7");
1155
1156 unsigned int bytesPerPixel = 1;
1157
1158 switch (accessor.GetFormat())
1159 {
1160 case PixelFormat_RGB24:
1161 case PixelFormat_RGBA32:
1162 Replace(DICOM_TAG_PHOTOMETRIC_INTERPRETATION, "RGB");
1163 Replace(DICOM_TAG_SAMPLES_PER_PIXEL, "3");
1164 bytesPerPixel = 3;
1165 break;
1166
1167 case PixelFormat_Grayscale8:
1168 break;
1169
1170 case PixelFormat_Grayscale16:
1171 Replace(DICOM_TAG_BITS_ALLOCATED, "16");
1172 Replace(DICOM_TAG_BITS_STORED, "16");
1173 Replace(DICOM_TAG_HIGH_BIT, "15");
1174 bytesPerPixel = 2;
1175 break;
1176
1177 default:
1178 throw OrthancException(ErrorCode_NotImplemented);
1179 }
1180
1181 DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(),
1182 DICOM_TAG_PIXEL_DATA.GetElement());
1183
1184 std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key));
1185
1186 unsigned int pitch = accessor.GetWidth() * bytesPerPixel;
1187 Uint8* target = NULL;
1188 pixels->createUint8Array(accessor.GetHeight() * pitch, target);
1189
1190 for (unsigned int y = 0; y < accessor.GetHeight(); y++)
1191 {
1192 switch (accessor.GetFormat())
1193 {
1194 case PixelFormat_RGB24:
1195 case PixelFormat_Grayscale8:
1196 case PixelFormat_Grayscale16:
1197 case PixelFormat_SignedGrayscale16:
1198 {
1199 memcpy(target, reinterpret_cast<const Uint8*>(accessor.GetConstRow(y)), pitch);
1200 target += pitch;
1201 break;
1202 }
1203
1204 case PixelFormat_RGBA32:
1205 {
1206 // The alpha channel is not supported by the DICOM standard
1207 const Uint8* source = reinterpret_cast<const Uint8*>(accessor.GetConstRow(y));
1208 for (unsigned int x = 0; x < accessor.GetWidth(); x++, target += 3, source += 4)
1209 {
1210 target[0] = source[0];
1211 target[1] = source[1];
1212 target[2] = source[2];
1213 }
1214
1215 break;
1216 }
1217
1218 default:
1219 throw OrthancException(ErrorCode_NotImplemented);
1220 }
1221 }
1222
1223 if (!pimpl_->file_->getDataset()->insert(pixels.release(), false, false).good())
1224 {
1225 throw OrthancException(ErrorCode_InternalError);
1226 }
1227 }
1228
1229
1230 void ParsedDicomFile::ExtractImage(ImageBuffer& result,
1231 unsigned int frame)
1232 {
1233 DcmDataset& dataset = *pimpl_->file_->getDataset();
1234
1235 if (!DicomImageDecoder::Decode(result, dataset, frame))
1236 {
1237 throw OrthancException(ErrorCode_BadFileFormat);
1238 }
1239 }
1240
1241
1242 void ParsedDicomFile::ExtractImage(ImageBuffer& result,
1243 unsigned int frame,
1244 ImageExtractionMode mode)
1245 {
1246 DcmDataset& dataset = *pimpl_->file_->getDataset();
1247
1248 bool ok = false;
1249
1250 switch (mode)
1251 {
1252 case ImageExtractionMode_UInt8:
1253 ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale8);
1254 break;
1255
1256 case ImageExtractionMode_UInt16:
1257 ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale16);
1258 break;
1259
1260 case ImageExtractionMode_Int16:
1261 ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_SignedGrayscale16);
1262 break;
1263
1264 case ImageExtractionMode_Preview:
1265 ok = DicomImageDecoder::DecodePreview(result, dataset, frame);
1266 break;
1267
1268 default:
1269 throw OrthancException(ErrorCode_ParameterOutOfRange);
1270 }
1271
1272 if (!ok)
1273 {
1274 throw OrthancException(ErrorCode_BadFileFormat);
1275 }
1276 }
1277
1278
1279 void ParsedDicomFile::ExtractPngImage(std::string& result,
1280 unsigned int frame,
1281 ImageExtractionMode mode)
1282 {
1283 ImageBuffer buffer;
1284 ExtractImage(buffer, frame, mode);
1285
1286 ImageAccessor accessor(buffer.GetConstAccessor());
1287 PngWriter writer;
1288 writer.WriteToMemory(result, accessor);
1289 }
1290
1291
1292 bool ParsedDicomFile::GetTagValue(std::string& value,
1293 const SequencePath& path,
1294 const DicomTag& tag)
1295 {
1296 if (path.size() == 0)
1297 {
1298 return GetTagValue(value, tag);
1299 }
1300
1301 DcmItem* current = pimpl_->file_->getDataset();
1302 assert(current != NULL);
1303
1304 for (SequencePath::const_iterator it = path.begin(); it != path.end(); it++)
1305 {
1306 DcmTagKey k(it->first.GetGroup(), it->first.GetElement());
1307
1308 DcmSequenceOfItems* sequence = NULL;
1309 if (!current->findAndGetSequence(k, sequence).good() ||
1310 sequence == NULL ||
1311 sequence->getVR() != EVR_SQ)
1312 {
1313 return false;
1314 }
1315
1316 if (it->second < 0 || it->second > sequence->card())
1317 {
1318 return false;
1319 }
1320
1321 current = sequence->getItem(it->second);
1322
1323 if (current == NULL)
1324 {
1325 return false;
1326 }
1327 }
1328
1329 return GetTagValueInternal(value, *current, tag);
1330 }
1331
1332 }