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