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