comparison OrthancServer/FromDcmtkBridge.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 8cfc6119a5bd 87791ebc1f50
children 98d6ba37c7dc
comparison
equal deleted inserted replaced
767:c19552f604d5 948:e57e08ed510f
29 * along with this program. If not, see <http://www.gnu.org/licenses/>. 29 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30 **/ 30 **/
31 31
32 32
33 33
34 /*========================================================================= 34 #include "PrecompiledHeadersServer.h"
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 35
75 #ifndef NOMINMAX 36 #ifndef NOMINMAX
76 #define NOMINMAX 37 #define NOMINMAX
77 #endif 38 #endif
78 39
40 #include "Internals/DicomImageDecoder.h"
41
79 #include "FromDcmtkBridge.h" 42 #include "FromDcmtkBridge.h"
80
81 #include "ToDcmtkBridge.h" 43 #include "ToDcmtkBridge.h"
82 #include "../Core/Toolbox.h" 44 #include "../Core/Toolbox.h"
83 #include "../Core/OrthancException.h" 45 #include "../Core/OrthancException.h"
84 #include "../Core/FileFormats/PngWriter.h" 46 #include "../Core/ImageFormats/PngWriter.h"
85 #include "../Core/Uuid.h" 47 #include "../Core/Uuid.h"
86 #include "../Core/DicomFormat/DicomString.h" 48 #include "../Core/DicomFormat/DicomString.h"
87 #include "../Core/DicomFormat/DicomNullValue.h" 49 #include "../Core/DicomFormat/DicomNullValue.h"
88 #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h" 50 #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
89 51
129 #include <boost/math/special_functions/round.hpp> 91 #include <boost/math/special_functions/round.hpp>
130 #include <glog/logging.h> 92 #include <glog/logging.h>
131 #include <dcmtk/dcmdata/dcostrmb.h> 93 #include <dcmtk/dcmdata/dcostrmb.h>
132 94
133 95
134 static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
135
136
137
138 namespace Orthanc 96 namespace Orthanc
139 { 97 {
140 void ParsedDicomFile::Setup(const char* buffer, size_t size)
141 {
142 DcmInputBufferStream is;
143 if (size > 0)
144 {
145 is.setBuffer(buffer, size);
146 }
147 is.setEos();
148
149 file_.reset(new DcmFileFormat);
150 file_->transferInit();
151 if (!file_->read(is).good())
152 {
153 throw OrthancException(ErrorCode_BadFileFormat);
154 }
155 file_->loadAllDataIntoMemory();
156 file_->transferEnd();
157 }
158
159
160 static void SendPathValueForDictionary(RestApiOutput& output,
161 DcmItem& dicom)
162 {
163 Json::Value v = Json::arrayValue;
164
165 for (unsigned long i = 0; i < dicom.card(); i++)
166 {
167 DcmElement* element = dicom.getElement(i);
168 if (element)
169 {
170 char buf[16];
171 sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
172 v.append(buf);
173 }
174 }
175
176 output.AnswerJson(v);
177 }
178
179 static inline uint16_t GetCharValue(char c) 98 static inline uint16_t GetCharValue(char c)
180 { 99 {
181 if (c >= '0' && c <= '9') 100 if (c >= '0' && c <= '9')
182 return c - '0'; 101 return c - '0';
183 else if (c >= 'a' && c <= 'f') 102 else if (c >= 'a' && c <= 'f')
193 return ((GetCharValue(c[0]) << 12) + 112 return ((GetCharValue(c[0]) << 12) +
194 (GetCharValue(c[1]) << 8) + 113 (GetCharValue(c[1]) << 8) +
195 (GetCharValue(c[2]) << 4) + 114 (GetCharValue(c[2]) << 4) +
196 GetCharValue(c[3])); 115 GetCharValue(c[3]));
197 } 116 }
198
199 static void ParseTagAndGroup(DcmTagKey& key,
200 const std::string& tag)
201 {
202 DicomTag t = FromDcmtkBridge::ParseTag(tag);
203 key = DcmTagKey(t.GetGroup(), t.GetElement());
204 }
205
206
207 static void SendSequence(RestApiOutput& output,
208 DcmSequenceOfItems& sequence)
209 {
210 // This element is a sequence
211 Json::Value v = Json::arrayValue;
212
213 for (unsigned long i = 0; i < sequence.card(); i++)
214 {
215 v.append(boost::lexical_cast<std::string>(i));
216 }
217
218 output.AnswerJson(v);
219 }
220
221
222 static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData,
223 E_TransferSyntax transferSyntax)
224 {
225 DcmPixelSequence* pixelSequence = NULL;
226 if (pixelData.getEncapsulatedRepresentation
227 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
228 {
229 return pixelSequence->card();
230 }
231 else
232 {
233 return 1;
234 }
235 }
236
237
238 static void AnswerDicomField(RestApiOutput& output,
239 DcmElement& element,
240 E_TransferSyntax transferSyntax)
241 {
242 // This element is nor a sequence, neither a pixel-data
243 std::string buffer;
244 buffer.resize(65536);
245 Uint32 length = element.getLength(transferSyntax);
246 Uint32 offset = 0;
247
248 output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL);
249
250 while (offset < length)
251 {
252 Uint32 nbytes;
253 if (length - offset < buffer.size())
254 {
255 nbytes = length - offset;
256 }
257 else
258 {
259 nbytes = buffer.size();
260 }
261
262 OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes);
263
264 if (cond.good())
265 {
266 output.GetLowLevelOutput().Send(&buffer[0], nbytes);
267 offset += nbytes;
268 }
269 else
270 {
271 LOG(ERROR) << "Error while sending a DICOM field: " << cond.text();
272 return;
273 }
274 }
275
276 output.MarkLowLevelOutputDone();
277 }
278
279
280 static bool AnswerPixelData(RestApiOutput& output,
281 DcmItem& dicom,
282 E_TransferSyntax transferSyntax,
283 const std::string* blockUri)
284 {
285 DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(),
286 DICOM_TAG_PIXEL_DATA.GetElement());
287
288 DcmElement *element = NULL;
289 if (!dicom.findAndGetElement(k, element).good() ||
290 element == NULL)
291 {
292 return false;
293 }
294
295 try
296 {
297 DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
298 if (blockUri == NULL)
299 {
300 // The user asks how many blocks are presents in this pixel data
301 unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax);
302
303 Json::Value result(Json::arrayValue);
304 for (unsigned int i = 0; i < blocks; i++)
305 {
306 result.append(boost::lexical_cast<std::string>(i));
307 }
308
309 output.AnswerJson(result);
310 return true;
311 }
312
313
314 unsigned int block = boost::lexical_cast<unsigned int>(*blockUri);
315
316 if (block < GetPixelDataBlockCount(pixelData, transferSyntax))
317 {
318 DcmPixelSequence* pixelSequence = NULL;
319 if (pixelData.getEncapsulatedRepresentation
320 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
321 {
322 // This is the case for JPEG transfer syntaxes
323 if (block < pixelSequence->card())
324 {
325 DcmPixelItem* pixelItem = NULL;
326 if (pixelSequence->getItem(pixelItem, block).good() && pixelItem)
327 {
328 if (pixelItem->getLength() == 0)
329 {
330 output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM);
331 return true;
332 }
333
334 Uint8* buffer = NULL;
335 if (pixelItem->getUint8Array(buffer).good() && buffer)
336 {
337 output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM);
338 return true;
339 }
340 }
341 }
342 }
343 else
344 {
345 // This is the case for raw, uncompressed image buffers
346 assert(*blockUri == "0");
347 AnswerDicomField(output, *element, transferSyntax);
348 }
349 }
350 }
351 catch (boost::bad_lexical_cast&)
352 {
353 // The URI entered by the user is not a number
354 }
355 catch (std::bad_cast&)
356 {
357 // This should never happen
358 }
359
360 return false;
361 }
362
363
364
365 static void SendPathValueForLeaf(RestApiOutput& output,
366 const std::string& tag,
367 DcmItem& dicom,
368 E_TransferSyntax transferSyntax)
369 {
370 DcmTagKey k;
371 ParseTagAndGroup(k, tag);
372
373 DcmSequenceOfItems* sequence = NULL;
374 if (dicom.findAndGetSequence(k, sequence).good() &&
375 sequence != NULL &&
376 sequence->getVR() == EVR_SQ)
377 {
378 SendSequence(output, *sequence);
379 return;
380 }
381
382 DcmElement* element = NULL;
383 if (dicom.findAndGetElement(k, element).good() &&
384 element != NULL &&
385 //element->getVR() != EVR_UNKNOWN && // This would forbid private tags
386 element->getVR() != EVR_SQ)
387 {
388 AnswerDicomField(output, *element, transferSyntax);
389 }
390 }
391
392 void ParsedDicomFile::SendPathValue(RestApiOutput& output,
393 const UriComponents& uri)
394 {
395 DcmItem* dicom = file_->getDataset();
396 E_TransferSyntax transferSyntax = file_->getDataset()->getOriginalXfer();
397
398 // Special case: Accessing the pixel data
399 if (uri.size() == 1 ||
400 uri.size() == 2)
401 {
402 DcmTagKey tag;
403 ParseTagAndGroup(tag, uri[0]);
404
405 if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() &&
406 tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement())
407 {
408 AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]);
409 return;
410 }
411 }
412
413 // Go down in the tag hierarchy according to the URI
414 for (size_t pos = 0; pos < uri.size() / 2; pos++)
415 {
416 size_t index;
417 try
418 {
419 index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
420 }
421 catch (boost::bad_lexical_cast&)
422 {
423 return;
424 }
425
426 DcmTagKey k;
427 DcmItem *child = NULL;
428 ParseTagAndGroup(k, uri[2 * pos]);
429 if (!dicom->findAndGetSequenceItem(k, child, index).good() ||
430 child == NULL)
431 {
432 return;
433 }
434
435 dicom = child;
436 }
437
438 // We have reached the end of the URI
439 if (uri.size() % 2 == 0)
440 {
441 SendPathValueForDictionary(output, *dicom);
442 }
443 else
444 {
445 SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax);
446 }
447 }
448
449
450
451
452
453 static DcmElement* CreateElementForTag(const DicomTag& tag)
454 {
455 DcmTag key(tag.GetGroup(), tag.GetElement());
456
457 switch (key.getEVR())
458 {
459 // http://support.dcmtk.org/docs/dcvr_8h-source.html
460
461 /**
462 * TODO.
463 **/
464
465 case EVR_OB: // other byte
466 case EVR_OF: // other float
467 case EVR_OW: // other word
468 case EVR_AT: // attribute tag
469 throw OrthancException(ErrorCode_NotImplemented);
470
471 case EVR_UN: // unknown value representation
472 throw OrthancException(ErrorCode_ParameterOutOfRange);
473
474
475 /**
476 * String types.
477 * http://support.dcmtk.org/docs/classDcmByteString.html
478 **/
479
480 case EVR_AS: // age string
481 return new DcmAgeString(key);
482
483 case EVR_AE: // application entity title
484 return new DcmApplicationEntity(key);
485
486 case EVR_CS: // code string
487 return new DcmCodeString(key);
488
489 case EVR_DA: // date string
490 return new DcmDate(key);
491
492 case EVR_DT: // date time string
493 return new DcmDateTime(key);
494
495 case EVR_DS: // decimal string
496 return new DcmDecimalString(key);
497
498 case EVR_IS: // integer string
499 return new DcmIntegerString(key);
500
501 case EVR_TM: // time string
502 return new DcmTime(key);
503
504 case EVR_UI: // unique identifier
505 return new DcmUniqueIdentifier(key);
506
507 case EVR_ST: // short text
508 return new DcmShortText(key);
509
510 case EVR_LO: // long string
511 return new DcmLongString(key);
512
513 case EVR_LT: // long text
514 return new DcmLongText(key);
515
516 case EVR_UT: // unlimited text
517 return new DcmUnlimitedText(key);
518
519 case EVR_SH: // short string
520 return new DcmShortString(key);
521
522 case EVR_PN: // person name
523 return new DcmPersonName(key);
524
525
526 /**
527 * Numerical types
528 **/
529
530 case EVR_SL: // signed long
531 return new DcmSignedLong(key);
532
533 case EVR_SS: // signed short
534 return new DcmSignedShort(key);
535
536 case EVR_UL: // unsigned long
537 return new DcmUnsignedLong(key);
538
539 case EVR_US: // unsigned short
540 return new DcmUnsignedShort(key);
541
542 case EVR_FL: // float single-precision
543 return new DcmFloatingPointSingle(key);
544
545 case EVR_FD: // float double-precision
546 return new DcmFloatingPointDouble(key);
547
548
549 /**
550 * Sequence types, should never occur at this point.
551 **/
552
553 case EVR_SQ: // sequence of items
554 throw OrthancException(ErrorCode_ParameterOutOfRange);
555
556
557 /**
558 * Internal to DCMTK.
559 **/
560
561 case EVR_ox: // OB or OW depending on context
562 case EVR_xs: // SS or US depending on context
563 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name)
564 case EVR_na: // na="not applicable", for data which has no VR
565 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor
566 case EVR_item: // used internally for items
567 case EVR_metainfo: // used internally for meta info datasets
568 case EVR_dataset: // used internally for datasets
569 case EVR_fileFormat: // used internally for DICOM files
570 case EVR_dicomDir: // used internally for DICOMDIR objects
571 case EVR_dirRecord: // used internally for DICOMDIR records
572 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image
573 case EVR_pixelItem: // used internally for pixel items in a compressed image
574 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
575 case EVR_PixelData: // used internally for uncompressed pixeld data
576 case EVR_OverlayData: // used internally for overlay data
577 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR
578 default:
579 break;
580 }
581
582 throw OrthancException(ErrorCode_InternalError);
583 }
584
585
586
587 static void FillElementWithString(DcmElement& element,
588 const DicomTag& tag,
589 const std::string& value)
590 {
591 DcmTag key(tag.GetGroup(), tag.GetElement());
592 bool ok = false;
593
594 try
595 {
596 switch (key.getEVR())
597 {
598 // http://support.dcmtk.org/docs/dcvr_8h-source.html
599
600 /**
601 * TODO.
602 **/
603
604 case EVR_OB: // other byte
605 case EVR_OF: // other float
606 case EVR_OW: // other word
607 case EVR_AT: // attribute tag
608 throw OrthancException(ErrorCode_NotImplemented);
609
610 case EVR_UN: // unknown value representation
611 throw OrthancException(ErrorCode_ParameterOutOfRange);
612
613
614 /**
615 * String types.
616 **/
617
618 case EVR_DS: // decimal string
619 case EVR_IS: // integer string
620 case EVR_AS: // age string
621 case EVR_DA: // date string
622 case EVR_DT: // date time string
623 case EVR_TM: // time string
624 case EVR_AE: // application entity title
625 case EVR_CS: // code string
626 case EVR_SH: // short string
627 case EVR_LO: // long string
628 case EVR_ST: // short text
629 case EVR_LT: // long text
630 case EVR_UT: // unlimited text
631 case EVR_PN: // person name
632 case EVR_UI: // unique identifier
633 {
634 ok = element.putString(value.c_str()).good();
635 break;
636 }
637
638
639 /**
640 * Numerical types
641 **/
642
643 case EVR_SL: // signed long
644 {
645 ok = element.putSint32(boost::lexical_cast<Sint32>(value)).good();
646 break;
647 }
648
649 case EVR_SS: // signed short
650 {
651 ok = element.putSint16(boost::lexical_cast<Sint16>(value)).good();
652 break;
653 }
654
655 case EVR_UL: // unsigned long
656 {
657 ok = element.putUint32(boost::lexical_cast<Uint32>(value)).good();
658 break;
659 }
660
661 case EVR_US: // unsigned short
662 {
663 ok = element.putUint16(boost::lexical_cast<Uint16>(value)).good();
664 break;
665 }
666
667 case EVR_FL: // float single-precision
668 {
669 ok = element.putFloat32(boost::lexical_cast<float>(value)).good();
670 break;
671 }
672
673 case EVR_FD: // float double-precision
674 {
675 ok = element.putFloat64(boost::lexical_cast<double>(value)).good();
676 break;
677 }
678
679
680 /**
681 * Sequence types, should never occur at this point.
682 **/
683
684 case EVR_SQ: // sequence of items
685 {
686 ok = false;
687 break;
688 }
689
690
691 /**
692 * Internal to DCMTK.
693 **/
694
695 case EVR_ox: // OB or OW depending on context
696 case EVR_xs: // SS or US depending on context
697 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name)
698 case EVR_na: // na="not applicable", for data which has no VR
699 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor
700 case EVR_item: // used internally for items
701 case EVR_metainfo: // used internally for meta info datasets
702 case EVR_dataset: // used internally for datasets
703 case EVR_fileFormat: // used internally for DICOM files
704 case EVR_dicomDir: // used internally for DICOMDIR objects
705 case EVR_dirRecord: // used internally for DICOMDIR records
706 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image
707 case EVR_pixelItem: // used internally for pixel items in a compressed image
708 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
709 case EVR_PixelData: // used internally for uncompressed pixeld data
710 case EVR_OverlayData: // used internally for overlay data
711 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR
712 default:
713 break;
714 }
715 }
716 catch (boost::bad_lexical_cast&)
717 {
718 ok = false;
719 }
720
721 if (!ok)
722 {
723 throw OrthancException(ErrorCode_InternalError);
724 }
725 }
726
727
728 void ParsedDicomFile::Remove(const DicomTag& tag)
729 {
730 DcmTagKey key(tag.GetGroup(), tag.GetElement());
731 DcmElement* element = file_->getDataset()->remove(key);
732 if (element != NULL)
733 {
734 delete element;
735 }
736 }
737
738
739
740 void ParsedDicomFile::RemovePrivateTags()
741 {
742 typedef std::list<DcmElement*> Tags;
743
744 Tags privateTags;
745
746 DcmDataset& dataset = *file_->getDataset();
747 for (unsigned long i = 0; i < dataset.card(); i++)
748 {
749 DcmElement* element = dataset.getElement(i);
750 DcmTag tag(element->getTag());
751 if (!strcmp("PrivateCreator", tag.getTagName()) || // TODO - This may change with future versions of DCMTK
752 tag.getPrivateCreator() != NULL)
753 {
754 privateTags.push_back(element);
755 }
756 }
757
758 for (Tags::iterator it = privateTags.begin();
759 it != privateTags.end(); ++it)
760 {
761 DcmElement* tmp = dataset.remove(*it);
762 if (tmp != NULL)
763 {
764 delete tmp;
765 }
766 }
767 }
768
769
770
771 void ParsedDicomFile::Insert(const DicomTag& tag,
772 const std::string& value)
773 {
774 std::auto_ptr<DcmElement> element(CreateElementForTag(tag));
775 FillElementWithString(*element, tag, value);
776
777 if (!file_->getDataset()->insert(element.release(), false, false).good())
778 {
779 // This field already exists
780 throw OrthancException(ErrorCode_InternalError);
781 }
782 }
783
784
785 void ParsedDicomFile::Replace(const DicomTag& tag,
786 const std::string& value,
787 DicomReplaceMode mode)
788 {
789 DcmTagKey key(tag.GetGroup(), tag.GetElement());
790 DcmElement* element = NULL;
791
792 if (!file_->getDataset()->findAndGetElement(key, element).good() ||
793 element == NULL)
794 {
795 // This field does not exist, act wrt. the specified "mode"
796 switch (mode)
797 {
798 case DicomReplaceMode_InsertIfAbsent:
799 Insert(tag, value);
800 break;
801
802 case DicomReplaceMode_ThrowIfAbsent:
803 throw OrthancException(ErrorCode_InexistentItem);
804
805 case DicomReplaceMode_IgnoreIfAbsent:
806 return;
807 }
808 }
809 else
810 {
811 FillElementWithString(*element, tag, value);
812 }
813
814
815 /**
816 * dcmodify will automatically correct 'Media Storage SOP Class
817 * UID' and 'Media Storage SOP Instance UID' in the metaheader, if
818 * you make changes to the related tags in the dataset ('SOP Class
819 * UID' and 'SOP Instance UID') via insert or modify mode
820 * options. You can disable this behaviour by using the -nmu
821 * option.
822 **/
823 if (tag == DICOM_TAG_SOP_CLASS_UID)
824 Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent);
825
826 if (tag == DICOM_TAG_SOP_INSTANCE_UID)
827 Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent);
828 }
829
830
831 void ParsedDicomFile::Answer(RestApiOutput& output)
832 {
833 std::string serialized;
834 if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset()))
835 {
836 output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM);
837 }
838 }
839
840
841
842 static bool GetTagValueInternal(std::string& value,
843 DcmItem& item,
844 const DicomTag& tag)
845 {
846 DcmTagKey k(tag.GetGroup(), tag.GetElement());
847 DcmElement* element = NULL;
848 if (!item.findAndGetElement(k, element).good() ||
849 element == NULL)
850 {
851 return false;
852 }
853
854 std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element));
855
856 if (v.get() == NULL)
857 {
858 value = "";
859 }
860 else
861 {
862 value = v->AsString();
863 }
864
865 return true;
866 }
867
868
869 bool ParsedDicomFile::GetTagValue(std::string& value,
870 const DicomTag& tag)
871 {
872 DcmDataset& dataset = *file_->getDataset();
873 return GetTagValueInternal(value, dataset, tag);
874 }
875
876
877
878 bool ParsedDicomFile::GetTagValue(std::string& value,
879 const SequencePath& path,
880 const DicomTag& tag)
881 {
882 if (path.size() == 0)
883 {
884 return GetTagValue(value, tag);
885 }
886
887 DcmItem* current = file_->getDataset();
888 assert(current != NULL);
889
890 for (SequencePath::const_iterator it = path.begin(); it != path.end(); it++)
891 {
892 DcmTagKey k(it->first.GetGroup(), it->first.GetElement());
893
894 DcmSequenceOfItems* sequence = NULL;
895 if (!current->findAndGetSequence(k, sequence).good() ||
896 sequence == NULL ||
897 sequence->getVR() != EVR_SQ)
898 {
899 return false;
900 }
901
902 if (it->second < 0 || it->second > sequence->card())
903 {
904 return false;
905 }
906
907 current = sequence->getItem(it->second);
908
909 if (current == NULL)
910 {
911 return false;
912 }
913 }
914
915 return GetTagValueInternal(value, *current, tag);
916 }
917
918
919
920 DicomInstanceHasher ParsedDicomFile::GetHasher()
921 {
922 std::string patientId, studyUid, seriesUid, instanceUid;
923
924 if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) ||
925 !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
926 !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) ||
927 !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID))
928 {
929 throw OrthancException(ErrorCode_BadFileFormat);
930 }
931
932 return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid);
933 }
934
935 117
936 void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) 118 void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset)
937 { 119 {
938 target.Clear(); 120 target.Clear();
939 for (unsigned long i = 0; i < dataset.card(); i++) 121 for (unsigned long i = 0; i < dataset.card(); i++)
1244 FromDcmtkBridge::ToJson(target, *dicom.getDataset(), maxStringLength); 426 FromDcmtkBridge::ToJson(target, *dicom.getDataset(), maxStringLength);
1245 } 427 }
1246 } 428 }
1247 429
1248 430
1249 static void ExtractPngImageColorPreview(std::string& result,
1250 DicomIntegerPixelAccessor& accessor)
1251 {
1252 assert(accessor.GetChannelCount() == 3);
1253 PngWriter w;
1254
1255 std::vector<uint8_t> image(accessor.GetWidth() * accessor.GetHeight() * 3, 0);
1256 uint8_t* pixel = &image[0];
1257
1258 for (unsigned int y = 0; y < accessor.GetHeight(); y++)
1259 {
1260 for (unsigned int x = 0; x < accessor.GetWidth(); x++)
1261 {
1262 for (unsigned int c = 0; c < 3; c++, pixel++)
1263 {
1264 int32_t v = accessor.GetValue(x, y, c);
1265 if (v < 0)
1266 *pixel = 0;
1267 else if (v > 255)
1268 *pixel = 255;
1269 else
1270 *pixel = v;
1271 }
1272 }
1273 }
1274
1275 w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(),
1276 accessor.GetWidth() * 3, PixelFormat_RGB24, &image[0]);
1277 }
1278
1279
1280 static void ExtractPngImageGrayscalePreview(std::string& result,
1281 DicomIntegerPixelAccessor& accessor)
1282 {
1283 assert(accessor.GetChannelCount() == 1);
1284 PngWriter w;
1285
1286 int32_t min, max;
1287 accessor.GetExtremeValues(min, max);
1288
1289 std::vector<uint8_t> image(accessor.GetWidth() * accessor.GetHeight(), 0);
1290 if (min != max)
1291 {
1292 uint8_t* pixel = &image[0];
1293 for (unsigned int y = 0; y < accessor.GetHeight(); y++)
1294 {
1295 for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++)
1296 {
1297 int32_t v = accessor.GetValue(x, y);
1298 *pixel = static_cast<uint8_t>(
1299 boost::math::lround(static_cast<float>(v - min) /
1300 static_cast<float>(max - min) * 255.0f));
1301 }
1302 }
1303 }
1304
1305 w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(),
1306 accessor.GetWidth(), PixelFormat_Grayscale8, &image[0]);
1307 }
1308
1309
1310 template <typename T>
1311 static void ExtractPngImageTruncate(std::string& result,
1312 DicomIntegerPixelAccessor& accessor,
1313 PixelFormat format)
1314 {
1315 assert(accessor.GetChannelCount() == 1);
1316
1317 PngWriter w;
1318
1319 std::vector<T> image(accessor.GetWidth() * accessor.GetHeight(), 0);
1320 T* pixel = &image[0];
1321 for (unsigned int y = 0; y < accessor.GetHeight(); y++)
1322 {
1323 for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++)
1324 {
1325 int32_t v = accessor.GetValue(x, y);
1326 if (v < static_cast<int32_t>(std::numeric_limits<T>::min()))
1327 *pixel = std::numeric_limits<T>::min();
1328 else if (v > static_cast<int32_t>(std::numeric_limits<T>::max()))
1329 *pixel = std::numeric_limits<T>::max();
1330 else
1331 *pixel = static_cast<T>(v);
1332 }
1333 }
1334
1335 w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(),
1336 accessor.GetWidth() * sizeof(T), format, &image[0]);
1337 }
1338
1339
1340 static bool DecodePsmctRle1(std::string& output,
1341 DcmDataset& dataset)
1342 {
1343 static const DicomTag tagContent(0x07a1, 0x100a);
1344 static const DicomTag tagCompressionType(0x07a1, 0x1011);
1345
1346 DcmElement* e;
1347 char* c;
1348
1349 // Check whether the DICOM instance contains an image encoded with
1350 // the PMSCT_RLE1 scheme.
1351 if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(tagCompressionType), e).good() ||
1352 e == NULL ||
1353 !e->isaString() ||
1354 !e->getString(c).good() ||
1355 c == NULL ||
1356 strcmp("PMSCT_RLE1", c))
1357 {
1358 return false;
1359 }
1360
1361 // OK, this is a custom RLE encoding from Philips. Get the pixel
1362 // data from the appropriate private DICOM tag.
1363 Uint8* pixData = NULL;
1364 if (!dataset.findAndGetElement(ToDcmtkBridge::Convert(tagContent), e).good() ||
1365 e == NULL ||
1366 e->getUint8Array(pixData) != EC_Normal)
1367 {
1368 return false;
1369 }
1370
1371 // The "unsigned" below IS VERY IMPORTANT
1372 const uint8_t* inbuffer = reinterpret_cast<const uint8_t*>(pixData);
1373 const size_t length = e->getLength();
1374
1375 /**
1376 * The code below is an adaptation of a sample code for GDCM by
1377 * Mathieu Malaterre (under a BSD license).
1378 * http://gdcm.sourceforge.net/html/rle2img_8cxx-example.html
1379 **/
1380
1381 // RLE pass
1382 std::vector<uint8_t> temp;
1383 temp.reserve(length);
1384 for (size_t i = 0; i < length; i++)
1385 {
1386 if (inbuffer[i] == 0xa5)
1387 {
1388 temp.push_back(inbuffer[i+2]);
1389 for (uint8_t repeat = inbuffer[i + 1]; repeat != 0; repeat--)
1390 {
1391 temp.push_back(inbuffer[i+2]);
1392 }
1393 i += 2;
1394 }
1395 else
1396 {
1397 temp.push_back(inbuffer[i]);
1398 }
1399 }
1400
1401 // Delta encoding pass
1402 uint16_t delta = 0;
1403 output.clear();
1404 output.reserve(temp.size());
1405 for (size_t i = 0; i < temp.size(); i++)
1406 {
1407 uint16_t value;
1408
1409 if (temp[i] == 0x5a)
1410 {
1411 uint16_t v1 = temp[i + 1];
1412 uint16_t v2 = temp[i + 2];
1413 value = (v2 << 8) + v1;
1414 i += 2;
1415 }
1416 else
1417 {
1418 value = delta + (int8_t) temp[i];
1419 }
1420
1421 output.push_back(value & 0xff);
1422 output.push_back(value >> 8);
1423 delta = value;
1424 }
1425
1426 if (output.size() % 2)
1427 {
1428 output.resize(output.size() - 1);
1429 }
1430
1431 return true;
1432 }
1433
1434
1435 void FromDcmtkBridge::ExtractPngImage(std::string& result,
1436 DcmDataset& dataset,
1437 unsigned int frame,
1438 ImageExtractionMode mode)
1439 {
1440 // See also: http://support.dcmtk.org/wiki/dcmtk/howto/accessing-compressed-data
1441
1442 std::auto_ptr<DicomIntegerPixelAccessor> accessor;
1443
1444 DicomMap m;
1445 FromDcmtkBridge::Convert(m, dataset);
1446
1447 std::string privateContent;
1448
1449 DcmElement* e;
1450 if (dataset.findAndGetElement(ToDcmtkBridge::Convert(DICOM_TAG_PIXEL_DATA), e).good() &&
1451 e != NULL)
1452 {
1453 Uint8* pixData = NULL;
1454 if (e->getUint8Array(pixData) == EC_Normal)
1455 {
1456 accessor.reset(new DicomIntegerPixelAccessor(m, pixData, e->getLength()));
1457 accessor->SetCurrentFrame(frame);
1458 }
1459 }
1460 else if (DecodePsmctRle1(privateContent, dataset))
1461 {
1462 LOG(INFO) << "The PMSCT_RLE1 decoding has succeeded";
1463 Uint8* pixData = NULL;
1464 if (privateContent.size() > 0)
1465 pixData = reinterpret_cast<Uint8*>(&privateContent[0]);
1466 accessor.reset(new DicomIntegerPixelAccessor(m, pixData, privateContent.size()));
1467 accessor->SetCurrentFrame(frame);
1468 }
1469
1470 if (accessor.get() == NULL)
1471 {
1472 throw OrthancException(ErrorCode_BadFileFormat);
1473 }
1474
1475 PixelFormat format;
1476 bool supported = false;
1477
1478 if (accessor->GetChannelCount() == 1)
1479 {
1480 switch (mode)
1481 {
1482 case ImageExtractionMode_Preview:
1483 supported = true;
1484 format = PixelFormat_Grayscale8;
1485 break;
1486
1487 case ImageExtractionMode_UInt8:
1488 supported = true;
1489 format = PixelFormat_Grayscale8;
1490 break;
1491
1492 case ImageExtractionMode_UInt16:
1493 supported = true;
1494 format = PixelFormat_Grayscale16;
1495 break;
1496
1497 case ImageExtractionMode_Int16:
1498 supported = true;
1499 format = PixelFormat_SignedGrayscale16;
1500 break;
1501
1502 default:
1503 supported = false;
1504 break;
1505 }
1506 }
1507 else if (accessor->GetChannelCount() == 3)
1508 {
1509 switch (mode)
1510 {
1511 case ImageExtractionMode_Preview:
1512 supported = true;
1513 format = PixelFormat_RGB24;
1514 break;
1515
1516 default:
1517 supported = false;
1518 break;
1519 }
1520 }
1521
1522 if (!supported)
1523 {
1524 throw OrthancException(ErrorCode_NotImplemented);
1525 }
1526
1527 if (accessor.get() == NULL ||
1528 accessor->GetWidth() == 0 ||
1529 accessor->GetHeight() == 0)
1530 {
1531 PngWriter w;
1532 w.WriteToMemory(result, 0, 0, 0, format, NULL);
1533 }
1534 else
1535 {
1536 switch (mode)
1537 {
1538 case ImageExtractionMode_Preview:
1539 if (format == PixelFormat_Grayscale8)
1540 ExtractPngImageGrayscalePreview(result, *accessor);
1541 else
1542 ExtractPngImageColorPreview(result, *accessor);
1543 break;
1544
1545 case ImageExtractionMode_UInt8:
1546 ExtractPngImageTruncate<uint8_t>(result, *accessor, format);
1547 break;
1548
1549 case ImageExtractionMode_UInt16:
1550 ExtractPngImageTruncate<uint16_t>(result, *accessor, format);
1551 break;
1552
1553 case ImageExtractionMode_Int16:
1554 ExtractPngImageTruncate<int16_t>(result, *accessor, format);
1555 break;
1556
1557 default:
1558 throw OrthancException(ErrorCode_NotImplemented);
1559 }
1560 }
1561 }
1562
1563
1564 void FromDcmtkBridge::ExtractPngImage(std::string& result,
1565 const std::string& dicomContent,
1566 unsigned int frame,
1567 ImageExtractionMode mode)
1568 {
1569 DcmInputBufferStream is;
1570 if (dicomContent.size() > 0)
1571 {
1572 is.setBuffer(&dicomContent[0], dicomContent.size());
1573 }
1574 is.setEos();
1575
1576 DcmFileFormat dicom;
1577 if (dicom.read(is).good())
1578 {
1579 ExtractPngImage(result, *dicom.getDataset(), frame, mode);
1580 }
1581 else
1582 {
1583 throw OrthancException(ErrorCode_BadFileFormat);
1584 }
1585 }
1586
1587
1588 431
1589 std::string FromDcmtkBridge::GetName(const DicomTag& t) 432 std::string FromDcmtkBridge::GetName(const DicomTag& t)
1590 { 433 {
1591 // Some patches for important tags because of different DICOM 434 // Some patches for important tags because of different DICOM
1592 // dictionaries between DCMTK versions 435 // dictionaries between DCMTK versions
1701 result[GetName(it->first)] = it->second->AsString(); 544 result[GetName(it->first)] = it->second->AsString();
1702 } 545 }
1703 } 546 }
1704 547
1705 548
1706 std::string FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel level) 549 std::string FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType level)
1707 { 550 {
1708 char uid[100]; 551 char uid[100];
1709 552
1710 switch (level) 553 switch (level)
1711 { 554 {
1712 case DicomRootLevel_Patient: 555 case ResourceType_Patient:
1713 // The "PatientID" field is of type LO (Long String), 64 556 // The "PatientID" field is of type LO (Long String), 64
1714 // Bytes Maximum. An UUID is of length 36, thus it can be used 557 // Bytes Maximum. An UUID is of length 36, thus it can be used
1715 // as a random PatientID. 558 // as a random PatientID.
1716 return Toolbox::GenerateUuid(); 559 return Toolbox::GenerateUuid();
1717 560
1718 case DicomRootLevel_Instance: 561 case ResourceType_Instance:
1719 return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); 562 return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
1720 563
1721 case DicomRootLevel_Series: 564 case ResourceType_Series:
1722 return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); 565 return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT);
1723 566
1724 case DicomRootLevel_Study: 567 case ResourceType_Study:
1725 return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); 568 return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT);
1726 569
1727 default: 570 default:
1728 throw OrthancException(ErrorCode_ParameterOutOfRange); 571 throw OrthancException(ErrorCode_ParameterOutOfRange);
1729 } 572 }