comparison OrthancServer/ParsedDicomFile.cpp @ 790:331eaf9d9d69

ParsedDicomFile
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 05 May 2014 18:55:10 +0200
parents
children 381f90e2b69d
comparison
equal deleted inserted replaced
789:55dae8c5a6ab 790:331eaf9d9d69
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 #ifndef NOMINMAX
76 #define NOMINMAX
77 #endif
78
79 #include "ParsedDicomFile.h"
80
81 #include "FromDcmtkBridge.h"
82 #include "ToDcmtkBridge.h"
83 #include "../Core/Toolbox.h"
84 #include "../Core/OrthancException.h"
85 #include "../Core/FileFormats/PngWriter.h"
86 #include "../Core/Uuid.h"
87 #include "../Core/DicomFormat/DicomString.h"
88 #include "../Core/DicomFormat/DicomNullValue.h"
89 #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
90
91 #include <list>
92 #include <limits>
93
94 #include <boost/lexical_cast.hpp>
95
96 #include <dcmtk/dcmdata/dcchrstr.h>
97 #include <dcmtk/dcmdata/dcdicent.h>
98 #include <dcmtk/dcmdata/dcdict.h>
99 #include <dcmtk/dcmdata/dcfilefo.h>
100 #include <dcmtk/dcmdata/dcistrmb.h>
101 #include <dcmtk/dcmdata/dcuid.h>
102 #include <dcmtk/dcmdata/dcmetinf.h>
103
104 #include <dcmtk/dcmdata/dcvrae.h>
105 #include <dcmtk/dcmdata/dcvras.h>
106 #include <dcmtk/dcmdata/dcvrcs.h>
107 #include <dcmtk/dcmdata/dcvrda.h>
108 #include <dcmtk/dcmdata/dcvrds.h>
109 #include <dcmtk/dcmdata/dcvrdt.h>
110 #include <dcmtk/dcmdata/dcvrfd.h>
111 #include <dcmtk/dcmdata/dcvrfl.h>
112 #include <dcmtk/dcmdata/dcvris.h>
113 #include <dcmtk/dcmdata/dcvrlo.h>
114 #include <dcmtk/dcmdata/dcvrlt.h>
115 #include <dcmtk/dcmdata/dcvrpn.h>
116 #include <dcmtk/dcmdata/dcvrsh.h>
117 #include <dcmtk/dcmdata/dcvrsl.h>
118 #include <dcmtk/dcmdata/dcvrss.h>
119 #include <dcmtk/dcmdata/dcvrst.h>
120 #include <dcmtk/dcmdata/dcvrtm.h>
121 #include <dcmtk/dcmdata/dcvrui.h>
122 #include <dcmtk/dcmdata/dcvrul.h>
123 #include <dcmtk/dcmdata/dcvrus.h>
124 #include <dcmtk/dcmdata/dcvrut.h>
125 #include <dcmtk/dcmdata/dcpixel.h>
126 #include <dcmtk/dcmdata/dcpixseq.h>
127 #include <dcmtk/dcmdata/dcpxitem.h>
128
129
130 #include <boost/math/special_functions/round.hpp>
131 #include <glog/logging.h>
132 #include <dcmtk/dcmdata/dcostrmb.h>
133
134
135 static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
136
137
138
139 namespace Orthanc
140 {
141 void ParsedDicomFile::Setup(const char* buffer, size_t size)
142 {
143 DcmInputBufferStream is;
144 if (size > 0)
145 {
146 is.setBuffer(buffer, size);
147 }
148 is.setEos();
149
150 file_.reset(new DcmFileFormat);
151 file_->transferInit();
152 if (!file_->read(is).good())
153 {
154 throw OrthancException(ErrorCode_BadFileFormat);
155 }
156 file_->loadAllDataIntoMemory();
157 file_->transferEnd();
158 }
159
160
161 static void SendPathValueForDictionary(RestApiOutput& output,
162 DcmItem& dicom)
163 {
164 Json::Value v = Json::arrayValue;
165
166 for (unsigned long i = 0; i < dicom.card(); i++)
167 {
168 DcmElement* element = dicom.getElement(i);
169 if (element)
170 {
171 char buf[16];
172 sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag());
173 v.append(buf);
174 }
175 }
176
177 output.AnswerJson(v);
178 }
179
180 static inline uint16_t GetCharValue(char c)
181 {
182 if (c >= '0' && c <= '9')
183 return c - '0';
184 else if (c >= 'a' && c <= 'f')
185 return c - 'a' + 10;
186 else if (c >= 'A' && c <= 'F')
187 return c - 'A' + 10;
188 else
189 return 0;
190 }
191
192 static inline uint16_t GetTagValue(const char* c)
193 {
194 return ((GetCharValue(c[0]) << 12) +
195 (GetCharValue(c[1]) << 8) +
196 (GetCharValue(c[2]) << 4) +
197 GetCharValue(c[3]));
198 }
199
200 static void ParseTagAndGroup(DcmTagKey& key,
201 const std::string& tag)
202 {
203 DicomTag t = FromDcmtkBridge::ParseTag(tag);
204 key = DcmTagKey(t.GetGroup(), t.GetElement());
205 }
206
207
208 static void SendSequence(RestApiOutput& output,
209 DcmSequenceOfItems& sequence)
210 {
211 // This element is a sequence
212 Json::Value v = Json::arrayValue;
213
214 for (unsigned long i = 0; i < sequence.card(); i++)
215 {
216 v.append(boost::lexical_cast<std::string>(i));
217 }
218
219 output.AnswerJson(v);
220 }
221
222
223 static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData,
224 E_TransferSyntax transferSyntax)
225 {
226 DcmPixelSequence* pixelSequence = NULL;
227 if (pixelData.getEncapsulatedRepresentation
228 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
229 {
230 return pixelSequence->card();
231 }
232 else
233 {
234 return 1;
235 }
236 }
237
238
239 static void AnswerDicomField(RestApiOutput& output,
240 DcmElement& element,
241 E_TransferSyntax transferSyntax)
242 {
243 // This element is nor a sequence, neither a pixel-data
244 std::string buffer;
245 buffer.resize(65536);
246 Uint32 length = element.getLength(transferSyntax);
247 Uint32 offset = 0;
248
249 output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL);
250
251 while (offset < length)
252 {
253 Uint32 nbytes;
254 if (length - offset < buffer.size())
255 {
256 nbytes = length - offset;
257 }
258 else
259 {
260 nbytes = buffer.size();
261 }
262
263 OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes);
264
265 if (cond.good())
266 {
267 output.GetLowLevelOutput().Send(&buffer[0], nbytes);
268 offset += nbytes;
269 }
270 else
271 {
272 LOG(ERROR) << "Error while sending a DICOM field: " << cond.text();
273 return;
274 }
275 }
276
277 output.MarkLowLevelOutputDone();
278 }
279
280
281 static bool AnswerPixelData(RestApiOutput& output,
282 DcmItem& dicom,
283 E_TransferSyntax transferSyntax,
284 const std::string* blockUri)
285 {
286 DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(),
287 DICOM_TAG_PIXEL_DATA.GetElement());
288
289 DcmElement *element = NULL;
290 if (!dicom.findAndGetElement(k, element).good() ||
291 element == NULL)
292 {
293 return false;
294 }
295
296 try
297 {
298 DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element);
299 if (blockUri == NULL)
300 {
301 // The user asks how many blocks are presents in this pixel data
302 unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax);
303
304 Json::Value result(Json::arrayValue);
305 for (unsigned int i = 0; i < blocks; i++)
306 {
307 result.append(boost::lexical_cast<std::string>(i));
308 }
309
310 output.AnswerJson(result);
311 return true;
312 }
313
314
315 unsigned int block = boost::lexical_cast<unsigned int>(*blockUri);
316
317 if (block < GetPixelDataBlockCount(pixelData, transferSyntax))
318 {
319 DcmPixelSequence* pixelSequence = NULL;
320 if (pixelData.getEncapsulatedRepresentation
321 (transferSyntax, NULL, pixelSequence).good() && pixelSequence)
322 {
323 // This is the case for JPEG transfer syntaxes
324 if (block < pixelSequence->card())
325 {
326 DcmPixelItem* pixelItem = NULL;
327 if (pixelSequence->getItem(pixelItem, block).good() && pixelItem)
328 {
329 if (pixelItem->getLength() == 0)
330 {
331 output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM);
332 return true;
333 }
334
335 Uint8* buffer = NULL;
336 if (pixelItem->getUint8Array(buffer).good() && buffer)
337 {
338 output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM);
339 return true;
340 }
341 }
342 }
343 }
344 else
345 {
346 // This is the case for raw, uncompressed image buffers
347 assert(*blockUri == "0");
348 AnswerDicomField(output, *element, transferSyntax);
349 }
350 }
351 }
352 catch (boost::bad_lexical_cast&)
353 {
354 // The URI entered by the user is not a number
355 }
356 catch (std::bad_cast&)
357 {
358 // This should never happen
359 }
360
361 return false;
362 }
363
364
365
366 static void SendPathValueForLeaf(RestApiOutput& output,
367 const std::string& tag,
368 DcmItem& dicom,
369 E_TransferSyntax transferSyntax)
370 {
371 DcmTagKey k;
372 ParseTagAndGroup(k, tag);
373
374 DcmSequenceOfItems* sequence = NULL;
375 if (dicom.findAndGetSequence(k, sequence).good() &&
376 sequence != NULL &&
377 sequence->getVR() == EVR_SQ)
378 {
379 SendSequence(output, *sequence);
380 return;
381 }
382
383 DcmElement* element = NULL;
384 if (dicom.findAndGetElement(k, element).good() &&
385 element != NULL &&
386 //element->getVR() != EVR_UNKNOWN && // This would forbid private tags
387 element->getVR() != EVR_SQ)
388 {
389 AnswerDicomField(output, *element, transferSyntax);
390 }
391 }
392
393 void ParsedDicomFile::SendPathValue(RestApiOutput& output,
394 const UriComponents& uri)
395 {
396 DcmItem* dicom = file_->getDataset();
397 E_TransferSyntax transferSyntax = file_->getDataset()->getOriginalXfer();
398
399 // Special case: Accessing the pixel data
400 if (uri.size() == 1 ||
401 uri.size() == 2)
402 {
403 DcmTagKey tag;
404 ParseTagAndGroup(tag, uri[0]);
405
406 if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() &&
407 tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement())
408 {
409 AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]);
410 return;
411 }
412 }
413
414 // Go down in the tag hierarchy according to the URI
415 for (size_t pos = 0; pos < uri.size() / 2; pos++)
416 {
417 size_t index;
418 try
419 {
420 index = boost::lexical_cast<size_t>(uri[2 * pos + 1]);
421 }
422 catch (boost::bad_lexical_cast&)
423 {
424 return;
425 }
426
427 DcmTagKey k;
428 DcmItem *child = NULL;
429 ParseTagAndGroup(k, uri[2 * pos]);
430 if (!dicom->findAndGetSequenceItem(k, child, index).good() ||
431 child == NULL)
432 {
433 return;
434 }
435
436 dicom = child;
437 }
438
439 // We have reached the end of the URI
440 if (uri.size() % 2 == 0)
441 {
442 SendPathValueForDictionary(output, *dicom);
443 }
444 else
445 {
446 SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax);
447 }
448 }
449
450
451
452
453
454 static DcmElement* CreateElementForTag(const DicomTag& tag)
455 {
456 DcmTag key(tag.GetGroup(), tag.GetElement());
457
458 switch (key.getEVR())
459 {
460 // http://support.dcmtk.org/docs/dcvr_8h-source.html
461
462 /**
463 * TODO.
464 **/
465
466 case EVR_OB: // other byte
467 case EVR_OF: // other float
468 case EVR_OW: // other word
469 case EVR_AT: // attribute tag
470 throw OrthancException(ErrorCode_NotImplemented);
471
472 case EVR_UN: // unknown value representation
473 throw OrthancException(ErrorCode_ParameterOutOfRange);
474
475
476 /**
477 * String types.
478 * http://support.dcmtk.org/docs/classDcmByteString.html
479 **/
480
481 case EVR_AS: // age string
482 return new DcmAgeString(key);
483
484 case EVR_AE: // application entity title
485 return new DcmApplicationEntity(key);
486
487 case EVR_CS: // code string
488 return new DcmCodeString(key);
489
490 case EVR_DA: // date string
491 return new DcmDate(key);
492
493 case EVR_DT: // date time string
494 return new DcmDateTime(key);
495
496 case EVR_DS: // decimal string
497 return new DcmDecimalString(key);
498
499 case EVR_IS: // integer string
500 return new DcmIntegerString(key);
501
502 case EVR_TM: // time string
503 return new DcmTime(key);
504
505 case EVR_UI: // unique identifier
506 return new DcmUniqueIdentifier(key);
507
508 case EVR_ST: // short text
509 return new DcmShortText(key);
510
511 case EVR_LO: // long string
512 return new DcmLongString(key);
513
514 case EVR_LT: // long text
515 return new DcmLongText(key);
516
517 case EVR_UT: // unlimited text
518 return new DcmUnlimitedText(key);
519
520 case EVR_SH: // short string
521 return new DcmShortString(key);
522
523 case EVR_PN: // person name
524 return new DcmPersonName(key);
525
526
527 /**
528 * Numerical types
529 **/
530
531 case EVR_SL: // signed long
532 return new DcmSignedLong(key);
533
534 case EVR_SS: // signed short
535 return new DcmSignedShort(key);
536
537 case EVR_UL: // unsigned long
538 return new DcmUnsignedLong(key);
539
540 case EVR_US: // unsigned short
541 return new DcmUnsignedShort(key);
542
543 case EVR_FL: // float single-precision
544 return new DcmFloatingPointSingle(key);
545
546 case EVR_FD: // float double-precision
547 return new DcmFloatingPointDouble(key);
548
549
550 /**
551 * Sequence types, should never occur at this point.
552 **/
553
554 case EVR_SQ: // sequence of items
555 throw OrthancException(ErrorCode_ParameterOutOfRange);
556
557
558 /**
559 * Internal to DCMTK.
560 **/
561
562 case EVR_ox: // OB or OW depending on context
563 case EVR_xs: // SS or US depending on context
564 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name)
565 case EVR_na: // na="not applicable", for data which has no VR
566 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor
567 case EVR_item: // used internally for items
568 case EVR_metainfo: // used internally for meta info datasets
569 case EVR_dataset: // used internally for datasets
570 case EVR_fileFormat: // used internally for DICOM files
571 case EVR_dicomDir: // used internally for DICOMDIR objects
572 case EVR_dirRecord: // used internally for DICOMDIR records
573 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image
574 case EVR_pixelItem: // used internally for pixel items in a compressed image
575 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
576 case EVR_PixelData: // used internally for uncompressed pixeld data
577 case EVR_OverlayData: // used internally for overlay data
578 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR
579 default:
580 break;
581 }
582
583 throw OrthancException(ErrorCode_InternalError);
584 }
585
586
587
588 static void FillElementWithString(DcmElement& element,
589 const DicomTag& tag,
590 const std::string& value)
591 {
592 DcmTag key(tag.GetGroup(), tag.GetElement());
593 bool ok = false;
594
595 try
596 {
597 switch (key.getEVR())
598 {
599 // http://support.dcmtk.org/docs/dcvr_8h-source.html
600
601 /**
602 * TODO.
603 **/
604
605 case EVR_OB: // other byte
606 case EVR_OF: // other float
607 case EVR_OW: // other word
608 case EVR_AT: // attribute tag
609 throw OrthancException(ErrorCode_NotImplemented);
610
611 case EVR_UN: // unknown value representation
612 throw OrthancException(ErrorCode_ParameterOutOfRange);
613
614
615 /**
616 * String types.
617 **/
618
619 case EVR_DS: // decimal string
620 case EVR_IS: // integer string
621 case EVR_AS: // age string
622 case EVR_DA: // date string
623 case EVR_DT: // date time string
624 case EVR_TM: // time string
625 case EVR_AE: // application entity title
626 case EVR_CS: // code string
627 case EVR_SH: // short string
628 case EVR_LO: // long string
629 case EVR_ST: // short text
630 case EVR_LT: // long text
631 case EVR_UT: // unlimited text
632 case EVR_PN: // person name
633 case EVR_UI: // unique identifier
634 {
635 ok = element.putString(value.c_str()).good();
636 break;
637 }
638
639
640 /**
641 * Numerical types
642 **/
643
644 case EVR_SL: // signed long
645 {
646 ok = element.putSint32(boost::lexical_cast<Sint32>(value)).good();
647 break;
648 }
649
650 case EVR_SS: // signed short
651 {
652 ok = element.putSint16(boost::lexical_cast<Sint16>(value)).good();
653 break;
654 }
655
656 case EVR_UL: // unsigned long
657 {
658 ok = element.putUint32(boost::lexical_cast<Uint32>(value)).good();
659 break;
660 }
661
662 case EVR_US: // unsigned short
663 {
664 ok = element.putUint16(boost::lexical_cast<Uint16>(value)).good();
665 break;
666 }
667
668 case EVR_FL: // float single-precision
669 {
670 ok = element.putFloat32(boost::lexical_cast<float>(value)).good();
671 break;
672 }
673
674 case EVR_FD: // float double-precision
675 {
676 ok = element.putFloat64(boost::lexical_cast<double>(value)).good();
677 break;
678 }
679
680
681 /**
682 * Sequence types, should never occur at this point.
683 **/
684
685 case EVR_SQ: // sequence of items
686 {
687 ok = false;
688 break;
689 }
690
691
692 /**
693 * Internal to DCMTK.
694 **/
695
696 case EVR_ox: // OB or OW depending on context
697 case EVR_xs: // SS or US depending on context
698 case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name)
699 case EVR_na: // na="not applicable", for data which has no VR
700 case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor
701 case EVR_item: // used internally for items
702 case EVR_metainfo: // used internally for meta info datasets
703 case EVR_dataset: // used internally for datasets
704 case EVR_fileFormat: // used internally for DICOM files
705 case EVR_dicomDir: // used internally for DICOMDIR objects
706 case EVR_dirRecord: // used internally for DICOMDIR records
707 case EVR_pixelSQ: // used internally for pixel sequences in a compressed image
708 case EVR_pixelItem: // used internally for pixel items in a compressed image
709 case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
710 case EVR_PixelData: // used internally for uncompressed pixeld data
711 case EVR_OverlayData: // used internally for overlay data
712 case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR
713 default:
714 break;
715 }
716 }
717 catch (boost::bad_lexical_cast&)
718 {
719 ok = false;
720 }
721
722 if (!ok)
723 {
724 throw OrthancException(ErrorCode_InternalError);
725 }
726 }
727
728
729 void ParsedDicomFile::Remove(const DicomTag& tag)
730 {
731 DcmTagKey key(tag.GetGroup(), tag.GetElement());
732 DcmElement* element = file_->getDataset()->remove(key);
733 if (element != NULL)
734 {
735 delete element;
736 }
737 }
738
739
740
741 void ParsedDicomFile::RemovePrivateTags()
742 {
743 typedef std::list<DcmElement*> Tags;
744
745 Tags privateTags;
746
747 DcmDataset& dataset = *file_->getDataset();
748 for (unsigned long i = 0; i < dataset.card(); i++)
749 {
750 DcmElement* element = dataset.getElement(i);
751 DcmTag tag(element->getTag());
752 if (!strcmp("PrivateCreator", tag.getTagName()) || // TODO - This may change with future versions of DCMTK
753 tag.getPrivateCreator() != NULL)
754 {
755 privateTags.push_back(element);
756 }
757 }
758
759 for (Tags::iterator it = privateTags.begin();
760 it != privateTags.end(); ++it)
761 {
762 DcmElement* tmp = dataset.remove(*it);
763 if (tmp != NULL)
764 {
765 delete tmp;
766 }
767 }
768 }
769
770
771
772 void ParsedDicomFile::Insert(const DicomTag& tag,
773 const std::string& value)
774 {
775 std::auto_ptr<DcmElement> element(CreateElementForTag(tag));
776 FillElementWithString(*element, tag, value);
777
778 if (!file_->getDataset()->insert(element.release(), false, false).good())
779 {
780 // This field already exists
781 throw OrthancException(ErrorCode_InternalError);
782 }
783 }
784
785
786 void ParsedDicomFile::Replace(const DicomTag& tag,
787 const std::string& value,
788 DicomReplaceMode mode)
789 {
790 DcmTagKey key(tag.GetGroup(), tag.GetElement());
791 DcmElement* element = NULL;
792
793 if (!file_->getDataset()->findAndGetElement(key, element).good() ||
794 element == NULL)
795 {
796 // This field does not exist, act wrt. the specified "mode"
797 switch (mode)
798 {
799 case DicomReplaceMode_InsertIfAbsent:
800 Insert(tag, value);
801 break;
802
803 case DicomReplaceMode_ThrowIfAbsent:
804 throw OrthancException(ErrorCode_InexistentItem);
805
806 case DicomReplaceMode_IgnoreIfAbsent:
807 return;
808 }
809 }
810 else
811 {
812 FillElementWithString(*element, tag, value);
813 }
814
815
816 /**
817 * dcmodify will automatically correct 'Media Storage SOP Class
818 * UID' and 'Media Storage SOP Instance UID' in the metaheader, if
819 * you make changes to the related tags in the dataset ('SOP Class
820 * UID' and 'SOP Instance UID') via insert or modify mode
821 * options. You can disable this behaviour by using the -nmu
822 * option.
823 **/
824
825 if (tag == DICOM_TAG_SOP_CLASS_UID)
826 {
827 Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent);
828 }
829
830 if (tag == DICOM_TAG_SOP_INSTANCE_UID)
831 {
832 Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent);
833 }
834 }
835
836
837 void ParsedDicomFile::Answer(RestApiOutput& output)
838 {
839 std::string serialized;
840 if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset()))
841 {
842 output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM);
843 }
844 }
845
846
847
848 bool ParsedDicomFile::GetTagValue(std::string& value,
849 const DicomTag& tag)
850 {
851 DcmTagKey k(tag.GetGroup(), tag.GetElement());
852 DcmDataset& dataset = *file_->getDataset();
853 DcmElement* element = NULL;
854 if (!dataset.findAndGetElement(k, element).good() ||
855 element == NULL)
856 {
857 return false;
858 }
859
860 std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element));
861
862 if (v.get() == NULL)
863 {
864 value = "";
865 }
866 else
867 {
868 value = v->AsString();
869 }
870
871 return true;
872 }
873
874
875
876 DicomInstanceHasher ParsedDicomFile::GetHasher()
877 {
878 std::string patientId, studyUid, seriesUid, instanceUid;
879
880 if (!GetTagValue(patientId, DICOM_TAG_PATIENT_ID) ||
881 !GetTagValue(studyUid, DICOM_TAG_STUDY_INSTANCE_UID) ||
882 !GetTagValue(seriesUid, DICOM_TAG_SERIES_INSTANCE_UID) ||
883 !GetTagValue(instanceUid, DICOM_TAG_SOP_INSTANCE_UID))
884 {
885 throw OrthancException(ErrorCode_BadFileFormat);
886 }
887
888 return DicomInstanceHasher(patientId, studyUid, seriesUid, instanceUid);
889 }
890
891
892 static void StoreElement(Json::Value& target,
893 DcmElement& element,
894 unsigned int maxStringLength);
895
896 static void StoreItem(Json::Value& target,
897 DcmItem& item,
898 unsigned int maxStringLength)
899 {
900 target = Json::Value(Json::objectValue);
901
902 for (unsigned long i = 0; i < item.card(); i++)
903 {
904 DcmElement* element = item.getElement(i);
905 StoreElement(target, *element, maxStringLength);
906 }
907 }
908
909
910 static void StoreElement(Json::Value& target,
911 DcmElement& element,
912 unsigned int maxStringLength)
913 {
914 assert(target.type() == Json::objectValue);
915
916 DicomTag tag(FromDcmtkBridge::GetTag(element));
917 const std::string formattedTag = tag.Format();
918
919 #if 0
920 const std::string tagName = FromDcmtkBridge::GetName(tag);
921 #else
922 // This version of the code gives access to the name of the private tags
923 DcmTag tagbis(element.getTag());
924 const std::string tagName(tagbis.getTagName());
925 #endif
926
927 if (element.isLeaf())
928 {
929 Json::Value value(Json::objectValue);
930 value["Name"] = tagName;
931
932 if (tagbis.getPrivateCreator() != NULL)
933 {
934 value["PrivateCreator"] = tagbis.getPrivateCreator();
935 }
936
937 std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element));
938 if (v->IsNull())
939 {
940 value["Type"] = "Null";
941 value["Value"] = Json::nullValue;
942 }
943 else
944 {
945 std::string s = v->AsString();
946 if (maxStringLength == 0 ||
947 s.size() <= maxStringLength)
948 {
949 value["Type"] = "String";
950 value["Value"] = s;
951 }
952 else
953 {
954 value["Type"] = "TooLong";
955 value["Value"] = Json::nullValue;
956 }
957 }
958
959 target[formattedTag] = value;
960 }
961 else
962 {
963 Json::Value children(Json::arrayValue);
964
965 // "All subclasses of DcmElement except for DcmSequenceOfItems
966 // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
967 // etc. are not." The following cast is thus OK.
968 DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
969
970 for (unsigned long i = 0; i < sequence.card(); i++)
971 {
972 DcmItem* child = sequence.getItem(i);
973 Json::Value& v = children.append(Json::objectValue);
974 StoreItem(v, *child, maxStringLength);
975 }
976
977 target[formattedTag]["Name"] = tagName;
978 target[formattedTag]["Type"] = "Sequence";
979 target[formattedTag]["Value"] = children;
980 }
981 }
982
983
984 template <typename T>
985 static void ExtractPngImageTruncate(std::string& result,
986 DicomIntegerPixelAccessor& accessor,
987 PixelFormat format)
988 {
989 assert(accessor.GetChannelCount() == 1);
990
991 PngWriter w;
992
993 std::vector<T> image(accessor.GetWidth() * accessor.GetHeight(), 0);
994 T* pixel = &image[0];
995 for (unsigned int y = 0; y < accessor.GetHeight(); y++)
996 {
997 for (unsigned int x = 0; x < accessor.GetWidth(); x++, pixel++)
998 {
999 int32_t v = accessor.GetValue(x, y);
1000 if (v < static_cast<int32_t>(std::numeric_limits<T>::min()))
1001 *pixel = std::numeric_limits<T>::min();
1002 else if (v > static_cast<int32_t>(std::numeric_limits<T>::max()))
1003 *pixel = std::numeric_limits<T>::max();
1004 else
1005 *pixel = static_cast<T>(v);
1006 }
1007 }
1008
1009 w.WriteToMemory(result, accessor.GetWidth(), accessor.GetHeight(),
1010 accessor.GetWidth() * sizeof(T), format, &image[0]);
1011 }
1012
1013
1014 void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer)
1015 {
1016 FromDcmtkBridge::SaveToMemoryBuffer(buffer, file_->getDataset());
1017 }
1018
1019
1020 void ParsedDicomFile::SaveToFile(const std::string& path)
1021 {
1022 // TODO Avoid using a temporary memory buffer, write directly on disk
1023 std::string content;
1024 SaveToMemoryBuffer(content);
1025 Toolbox::WriteFile(content, path);
1026 }
1027
1028
1029 ParsedDicomFile::ParsedDicomFile()
1030 {
1031 file_.reset(new DcmFileFormat);
1032 Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
1033 Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
1034 Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
1035 Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
1036 }
1037
1038 }