comparison OrthancServer/FromDcmtkBridge.cpp @ 790:331eaf9d9d69

ParsedDicomFile
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 05 May 2014 18:55:10 +0200
parents 55dae8c5a6ab
children 777b6b694da6
comparison
equal deleted inserted replaced
789:55dae8c5a6ab 790:331eaf9d9d69
129 #include <boost/math/special_functions/round.hpp> 129 #include <boost/math/special_functions/round.hpp>
130 #include <glog/logging.h> 130 #include <glog/logging.h>
131 #include <dcmtk/dcmdata/dcostrmb.h> 131 #include <dcmtk/dcmdata/dcostrmb.h>
132 132
133 133
134 static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
135
136
137
138 namespace Orthanc 134 namespace Orthanc
139 { 135 {
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) 136 static inline uint16_t GetCharValue(char c)
180 { 137 {
181 if (c >= '0' && c <= '9') 138 if (c >= '0' && c <= '9')
182 return c - '0'; 139 return c - '0';
183 else if (c >= 'a' && c <= 'f') 140 else if (c >= 'a' && c <= 'f')
193 return ((GetCharValue(c[0]) << 12) + 150 return ((GetCharValue(c[0]) << 12) +
194 (GetCharValue(c[1]) << 8) + 151 (GetCharValue(c[1]) << 8) +
195 (GetCharValue(c[2]) << 4) + 152 (GetCharValue(c[2]) << 4) +
196 GetCharValue(c[3])); 153 GetCharValue(c[3]));
197 } 154 }
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
824 if (tag == DICOM_TAG_SOP_CLASS_UID)
825 {
826 Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent);
827 }
828
829 if (tag == DICOM_TAG_SOP_INSTANCE_UID)
830 {
831 Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent);
832 }
833 }
834
835
836 void ParsedDicomFile::Answer(RestApiOutput& output)
837 {
838 std::string serialized;
839 if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset()))
840 {
841 output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM);
842 }
843 }
844
845
846
847 bool ParsedDicomFile::GetTagValue(std::string& value,
848 const DicomTag& tag)
849 {
850 DcmTagKey k(tag.GetGroup(), tag.GetElement());
851 DcmDataset& dataset = *file_->getDataset();
852 DcmElement* element = NULL;
853 if (!dataset.findAndGetElement(k, element).good() ||
854 element == NULL)
855 {
856 return false;
857 }
858
859 std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element));
860
861 if (v.get() == NULL)
862 {
863 value = "";
864 }
865 else
866 {
867 value = v->AsString();
868 }
869
870 return true;
871 }
872
873
874
875 DicomInstanceHasher ParsedDicomFile::GetHasher()
876 {
877 std::string patientId, studyUid, seriesUid, instanceUid;
878
879 if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) ||
880 !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
881 !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) ||
882 !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID))
883 {
884 throw OrthancException(ErrorCode_BadFileFormat);
885 }
886
887 return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid);
888 }
889
890 155
891 void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) 156 void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset)
892 { 157 {
893 target.Clear(); 158 target.Clear();
894 for (unsigned long i = 0; i < dataset.card(); i++) 159 for (unsigned long i = 0; i < dataset.card(); i++)
1735 { 1000 {
1736 buffer.clear(); 1001 buffer.clear();
1737 return false; 1002 return false;
1738 } 1003 }
1739 } 1004 }
1740
1741
1742 void ParsedDicomFile::SaveToFile(const std::string& path)
1743 {
1744 // TODO Avoid using a temporary memory buffer, write directly on disk
1745 std::string content;
1746 SaveToMemoryBuffer(content);
1747 Toolbox::WriteFile(content, path);
1748 }
1749
1750
1751 ParsedDicomFile::ParsedDicomFile()
1752 {
1753 file_.reset(new DcmFileFormat);
1754 Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
1755 Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
1756 Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
1757 Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
1758 }
1759
1760 } 1005 }