Mercurial > hg > orthanc
comparison OrthancServer/FromDcmtkBridge.cpp @ 759:8cfc6119a5bd dicom-rt
integration mainline -> dicom-rt
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 16 Apr 2014 16:04:55 +0200 |
parents | da8e064d0d49 2d0a347e8cfc |
children | e57e08ed510f |
comparison
equal
deleted
inserted
replaced
605:b82292ba2083 | 759:8cfc6119a5bd |
---|---|
1 /** | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | 2 * Orthanc - A Lightweight, RESTful DICOM Store |
3 * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege, | 3 * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, |
4 * Belgium | 4 * Belgium |
5 * | 5 * |
6 * This program is free software: you can redistribute it and/or | 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 | 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 | 8 * published by the Free Software Foundation, either version 3 of the |
96 #include <dcmtk/dcmdata/dcdicent.h> | 96 #include <dcmtk/dcmdata/dcdicent.h> |
97 #include <dcmtk/dcmdata/dcdict.h> | 97 #include <dcmtk/dcmdata/dcdict.h> |
98 #include <dcmtk/dcmdata/dcfilefo.h> | 98 #include <dcmtk/dcmdata/dcfilefo.h> |
99 #include <dcmtk/dcmdata/dcistrmb.h> | 99 #include <dcmtk/dcmdata/dcistrmb.h> |
100 #include <dcmtk/dcmdata/dcuid.h> | 100 #include <dcmtk/dcmdata/dcuid.h> |
101 #include <dcmtk/dcmdata/dcmetinf.h> | |
101 | 102 |
102 #include <dcmtk/dcmdata/dcvrae.h> | 103 #include <dcmtk/dcmdata/dcvrae.h> |
103 #include <dcmtk/dcmdata/dcvras.h> | 104 #include <dcmtk/dcmdata/dcvras.h> |
104 #include <dcmtk/dcmdata/dcvrcs.h> | 105 #include <dcmtk/dcmdata/dcvrcs.h> |
105 #include <dcmtk/dcmdata/dcvrda.h> | 106 #include <dcmtk/dcmdata/dcvrda.h> |
118 #include <dcmtk/dcmdata/dcvrtm.h> | 119 #include <dcmtk/dcmdata/dcvrtm.h> |
119 #include <dcmtk/dcmdata/dcvrui.h> | 120 #include <dcmtk/dcmdata/dcvrui.h> |
120 #include <dcmtk/dcmdata/dcvrul.h> | 121 #include <dcmtk/dcmdata/dcvrul.h> |
121 #include <dcmtk/dcmdata/dcvrus.h> | 122 #include <dcmtk/dcmdata/dcvrus.h> |
122 #include <dcmtk/dcmdata/dcvrut.h> | 123 #include <dcmtk/dcmdata/dcvrut.h> |
124 #include <dcmtk/dcmdata/dcpixel.h> | |
125 #include <dcmtk/dcmdata/dcpixseq.h> | |
126 #include <dcmtk/dcmdata/dcpxitem.h> | |
127 | |
123 | 128 |
124 #include <boost/math/special_functions/round.hpp> | 129 #include <boost/math/special_functions/round.hpp> |
125 #include <glog/logging.h> | 130 #include <glog/logging.h> |
126 #include <dcmtk/dcmdata/dcostrmb.h> | 131 #include <dcmtk/dcmdata/dcostrmb.h> |
132 | |
133 | |
134 static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; | |
135 | |
136 | |
127 | 137 |
128 namespace Orthanc | 138 namespace Orthanc |
129 { | 139 { |
130 void ParsedDicomFile::Setup(const char* buffer, size_t size) | 140 void ParsedDicomFile::Setup(const char* buffer, size_t size) |
131 { | 141 { |
206 } | 216 } |
207 | 217 |
208 output.AnswerJson(v); | 218 output.AnswerJson(v); |
209 } | 219 } |
210 | 220 |
221 | |
222 static unsigned int GetPixelDataBlockCount(DcmPixelData& pixelData, | |
223 E_TransferSyntax transferSyntax) | |
224 { | |
225 DcmPixelSequence* pixelSequence = NULL; | |
226 if (pixelData.getEncapsulatedRepresentation | |
227 (transferSyntax, NULL, pixelSequence).good() && pixelSequence) | |
228 { | |
229 return pixelSequence->card(); | |
230 } | |
231 else | |
232 { | |
233 return 1; | |
234 } | |
235 } | |
236 | |
237 | |
211 static void AnswerDicomField(RestApiOutput& output, | 238 static void AnswerDicomField(RestApiOutput& output, |
212 DcmElement& element) | 239 DcmElement& element, |
213 { | 240 E_TransferSyntax transferSyntax) |
214 // This element is not a sequence | 241 { |
242 // This element is nor a sequence, neither a pixel-data | |
215 std::string buffer; | 243 std::string buffer; |
216 buffer.resize(65536); | 244 buffer.resize(65536); |
217 Uint32 length = element.getLength(); | 245 Uint32 length = element.getLength(transferSyntax); |
218 Uint32 offset = 0; | 246 Uint32 offset = 0; |
219 | 247 |
220 output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL); | 248 output.GetLowLevelOutput().SendOkHeader(CONTENT_TYPE_OCTET_STREAM, true, length, NULL); |
221 | 249 |
222 while (offset < length) | 250 while (offset < length) |
223 { | 251 { |
224 Uint32 nbytes; | 252 Uint32 nbytes; |
225 if (length - offset < buffer.size()) | 253 if (length - offset < buffer.size()) |
229 else | 257 else |
230 { | 258 { |
231 nbytes = buffer.size(); | 259 nbytes = buffer.size(); |
232 } | 260 } |
233 | 261 |
234 if (element.getPartialValue(&buffer[0], offset, nbytes).good()) | 262 OFCondition cond = element.getPartialValue(&buffer[0], offset, nbytes); |
263 | |
264 if (cond.good()) | |
235 { | 265 { |
236 output.GetLowLevelOutput().Send(&buffer[0], nbytes); | 266 output.GetLowLevelOutput().Send(&buffer[0], nbytes); |
237 offset += nbytes; | 267 offset += nbytes; |
238 } | 268 } |
239 else | 269 else |
240 { | 270 { |
241 LOG(ERROR) << "Error while sending a DICOM field"; | 271 LOG(ERROR) << "Error while sending a DICOM field: " << cond.text(); |
242 return; | 272 return; |
243 } | 273 } |
244 } | 274 } |
245 | 275 |
246 output.MarkLowLevelOutputDone(); | 276 output.MarkLowLevelOutputDone(); |
247 } | 277 } |
278 | |
279 | |
280 static bool AnswerPixelData(RestApiOutput& output, | |
281 DcmItem& dicom, | |
282 E_TransferSyntax transferSyntax, | |
283 const std::string* blockUri) | |
284 { | |
285 DcmTag k(DICOM_TAG_PIXEL_DATA.GetGroup(), | |
286 DICOM_TAG_PIXEL_DATA.GetElement()); | |
287 | |
288 DcmElement *element = NULL; | |
289 if (!dicom.findAndGetElement(k, element).good() || | |
290 element == NULL) | |
291 { | |
292 return false; | |
293 } | |
294 | |
295 try | |
296 { | |
297 DcmPixelData& pixelData = dynamic_cast<DcmPixelData&>(*element); | |
298 if (blockUri == NULL) | |
299 { | |
300 // The user asks how many blocks are presents in this pixel data | |
301 unsigned int blocks = GetPixelDataBlockCount(pixelData, transferSyntax); | |
302 | |
303 Json::Value result(Json::arrayValue); | |
304 for (unsigned int i = 0; i < blocks; i++) | |
305 { | |
306 result.append(boost::lexical_cast<std::string>(i)); | |
307 } | |
308 | |
309 output.AnswerJson(result); | |
310 return true; | |
311 } | |
312 | |
313 | |
314 unsigned int block = boost::lexical_cast<unsigned int>(*blockUri); | |
315 | |
316 if (block < GetPixelDataBlockCount(pixelData, transferSyntax)) | |
317 { | |
318 DcmPixelSequence* pixelSequence = NULL; | |
319 if (pixelData.getEncapsulatedRepresentation | |
320 (transferSyntax, NULL, pixelSequence).good() && pixelSequence) | |
321 { | |
322 // This is the case for JPEG transfer syntaxes | |
323 if (block < pixelSequence->card()) | |
324 { | |
325 DcmPixelItem* pixelItem = NULL; | |
326 if (pixelSequence->getItem(pixelItem, block).good() && pixelItem) | |
327 { | |
328 if (pixelItem->getLength() == 0) | |
329 { | |
330 output.AnswerBuffer(NULL, 0, CONTENT_TYPE_OCTET_STREAM); | |
331 return true; | |
332 } | |
333 | |
334 Uint8* buffer = NULL; | |
335 if (pixelItem->getUint8Array(buffer).good() && buffer) | |
336 { | |
337 output.AnswerBuffer(buffer, pixelItem->getLength(), CONTENT_TYPE_OCTET_STREAM); | |
338 return true; | |
339 } | |
340 } | |
341 } | |
342 } | |
343 else | |
344 { | |
345 // This is the case for raw, uncompressed image buffers | |
346 assert(*blockUri == "0"); | |
347 AnswerDicomField(output, *element, transferSyntax); | |
348 } | |
349 } | |
350 } | |
351 catch (boost::bad_lexical_cast&) | |
352 { | |
353 // The URI entered by the user is not a number | |
354 } | |
355 catch (std::bad_cast&) | |
356 { | |
357 // This should never happen | |
358 } | |
359 | |
360 return false; | |
361 } | |
362 | |
363 | |
248 | 364 |
249 static void SendPathValueForLeaf(RestApiOutput& output, | 365 static void SendPathValueForLeaf(RestApiOutput& output, |
250 const std::string& tag, | 366 const std::string& tag, |
251 DcmItem& dicom) | 367 DcmItem& dicom, |
368 E_TransferSyntax transferSyntax) | |
252 { | 369 { |
253 DcmTagKey k; | 370 DcmTagKey k; |
254 ParseTagAndGroup(k, tag); | 371 ParseTagAndGroup(k, tag); |
255 | 372 |
256 DcmSequenceOfItems* sequence = NULL; | 373 DcmSequenceOfItems* sequence = NULL; |
266 if (dicom.findAndGetElement(k, element).good() && | 383 if (dicom.findAndGetElement(k, element).good() && |
267 element != NULL && | 384 element != NULL && |
268 //element->getVR() != EVR_UNKNOWN && // This would forbid private tags | 385 //element->getVR() != EVR_UNKNOWN && // This would forbid private tags |
269 element->getVR() != EVR_SQ) | 386 element->getVR() != EVR_SQ) |
270 { | 387 { |
271 AnswerDicomField(output, *element); | 388 AnswerDicomField(output, *element, transferSyntax); |
272 } | 389 } |
273 } | 390 } |
274 | 391 |
275 void ParsedDicomFile::SendPathValue(RestApiOutput& output, | 392 void ParsedDicomFile::SendPathValue(RestApiOutput& output, |
276 const UriComponents& uri) | 393 const UriComponents& uri) |
277 { | 394 { |
278 DcmItem* dicom = file_->getDataset(); | 395 DcmItem* dicom = file_->getDataset(); |
396 E_TransferSyntax transferSyntax = file_->getDataset()->getOriginalXfer(); | |
397 | |
398 // Special case: Accessing the pixel data | |
399 if (uri.size() == 1 || | |
400 uri.size() == 2) | |
401 { | |
402 DcmTagKey tag; | |
403 ParseTagAndGroup(tag, uri[0]); | |
404 | |
405 if (tag.getGroup() == DICOM_TAG_PIXEL_DATA.GetGroup() && | |
406 tag.getElement() == DICOM_TAG_PIXEL_DATA.GetElement()) | |
407 { | |
408 AnswerPixelData(output, *dicom, transferSyntax, uri.size() == 1 ? NULL : &uri[1]); | |
409 return; | |
410 } | |
411 } | |
279 | 412 |
280 // Go down in the tag hierarchy according to the URI | 413 // Go down in the tag hierarchy according to the URI |
281 for (size_t pos = 0; pos < uri.size() / 2; pos++) | 414 for (size_t pos = 0; pos < uri.size() / 2; pos++) |
282 { | 415 { |
283 size_t index; | 416 size_t index; |
307 { | 440 { |
308 SendPathValueForDictionary(output, *dicom); | 441 SendPathValueForDictionary(output, *dicom); |
309 } | 442 } |
310 else | 443 else |
311 { | 444 { |
312 SendPathValueForLeaf(output, uri.back(), *dicom); | 445 SendPathValueForLeaf(output, uri.back(), *dicom, transferSyntax); |
313 } | 446 } |
314 } | 447 } |
315 | 448 |
316 | 449 |
317 | 450 |
621 privateTags.push_back(element); | 754 privateTags.push_back(element); |
622 } | 755 } |
623 } | 756 } |
624 | 757 |
625 for (Tags::iterator it = privateTags.begin(); | 758 for (Tags::iterator it = privateTags.begin(); |
626 it != privateTags.end(); it++) | 759 it != privateTags.end(); ++it) |
627 { | 760 { |
628 DcmElement* tmp = dataset.remove(*it); | 761 DcmElement* tmp = dataset.remove(*it); |
629 if (tmp != NULL) | 762 if (tmp != NULL) |
630 { | 763 { |
631 delete tmp; | 764 delete tmp; |
698 void ParsedDicomFile::Answer(RestApiOutput& output) | 831 void ParsedDicomFile::Answer(RestApiOutput& output) |
699 { | 832 { |
700 std::string serialized; | 833 std::string serialized; |
701 if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset())) | 834 if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset())) |
702 { | 835 { |
703 output.AnswerBuffer(serialized, "application/octet-stream"); | 836 output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM); |
704 } | 837 } |
705 } | 838 } |
706 | 839 |
707 | 840 |
708 | 841 |
1541 | 1674 |
1542 | 1675 |
1543 void FromDcmtkBridge::Print(FILE* fp, const DicomMap& m) | 1676 void FromDcmtkBridge::Print(FILE* fp, const DicomMap& m) |
1544 { | 1677 { |
1545 for (DicomMap::Map::const_iterator | 1678 for (DicomMap::Map::const_iterator |
1546 it = m.map_.begin(); it != m.map_.end(); it++) | 1679 it = m.map_.begin(); it != m.map_.end(); ++it) |
1547 { | 1680 { |
1548 DicomTag t = it->first; | 1681 DicomTag t = it->first; |
1549 std::string s = it->second->AsString(); | 1682 std::string s = it->second->AsString(); |
1550 fprintf(fp, "0x%04x 0x%04x (%s) [%s]\n", t.GetGroup(), t.GetElement(), GetName(t).c_str(), s.c_str()); | 1683 fprintf(fp, "0x%04x 0x%04x (%s) [%s]\n", t.GetGroup(), t.GetElement(), GetName(t).c_str(), s.c_str()); |
1551 } | 1684 } |
1561 } | 1694 } |
1562 | 1695 |
1563 result.clear(); | 1696 result.clear(); |
1564 | 1697 |
1565 for (DicomMap::Map::const_iterator | 1698 for (DicomMap::Map::const_iterator |
1566 it = values.map_.begin(); it != values.map_.end(); it++) | 1699 it = values.map_.begin(); it != values.map_.end(); ++it) |
1567 { | 1700 { |
1568 result[GetName(it->first)] = it->second->AsString(); | 1701 result[GetName(it->first)] = it->second->AsString(); |
1569 } | 1702 } |
1570 } | 1703 } |
1571 | 1704 |
1602 // Determine the transfer syntax which shall be used to write the | 1735 // Determine the transfer syntax which shall be used to write the |
1603 // information to the file. We always switch to the Little Endian | 1736 // information to the file. We always switch to the Little Endian |
1604 // syntax, with explicit length. | 1737 // syntax, with explicit length. |
1605 | 1738 |
1606 // http://support.dcmtk.org/docs/dcxfer_8h-source.html | 1739 // http://support.dcmtk.org/docs/dcxfer_8h-source.html |
1607 E_TransferSyntax xfer = EXS_LittleEndianExplicit; | 1740 |
1741 | |
1742 /** | |
1743 * Note that up to Orthanc 0.7.1 (inclusive), the | |
1744 * "EXS_LittleEndianExplicit" was always used to save the DICOM | |
1745 * dataset into memory. We now keep the original transfer syntax | |
1746 * (if available). | |
1747 **/ | |
1748 E_TransferSyntax xfer = dataSet->getOriginalXfer(); | |
1749 if (xfer == EXS_Unknown) | |
1750 { | |
1751 // No information about the original transfer syntax: This is | |
1752 // most probably a DICOM dataset that was read from memory. | |
1753 xfer = EXS_LittleEndianExplicit; | |
1754 } | |
1755 | |
1608 E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; | 1756 E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength; |
1609 | 1757 |
1610 uint32_t s = dataSet->getLength(xfer, encodingType); | 1758 // Create the meta-header information |
1611 | 1759 DcmFileFormat ff(dataSet); |
1760 ff.validateMetaInfo(xfer); | |
1761 | |
1762 // Create a memory buffer with the proper size | |
1763 uint32_t s = ff.calcElementLength(xfer, encodingType); | |
1612 buffer.resize(s); | 1764 buffer.resize(s); |
1613 DcmOutputBufferStream ob(&buffer[0], s); | 1765 DcmOutputBufferStream ob(&buffer[0], s); |
1614 | 1766 |
1615 dataSet->transferInit(); | 1767 // Fill the memory buffer with the meta-header and the dataset |
1616 | 1768 ff.transferInit(); |
1617 #if DCMTK_VERSION_NUMBER >= 360 | 1769 OFCondition c = ff.write(ob, xfer, encodingType, NULL, |
1618 OFCondition c = dataSet->write(ob, xfer, encodingType, NULL, | 1770 /*opt_groupLength*/ EGL_recalcGL, |
1619 /*opt_groupLength*/ EGL_recalcGL, | 1771 /*opt_paddingType*/ EPD_withoutPadding); |
1620 /*opt_paddingType*/ EPD_withoutPadding); | 1772 ff.transferEnd(); |
1621 #else | 1773 |
1622 OFCondition c = dataSet->write(ob, xfer, encodingType, NULL); | 1774 // Handle errors |
1623 #endif | |
1624 | |
1625 dataSet->transferEnd(); | |
1626 if (c.good()) | 1775 if (c.good()) |
1627 { | 1776 { |
1628 return true; | 1777 return true; |
1629 } | 1778 } |
1630 else | 1779 else |
1631 { | 1780 { |
1632 buffer.clear(); | 1781 buffer.clear(); |
1633 return false; | 1782 return false; |
1634 } | 1783 } |
1635 | |
1636 #if 0 | |
1637 OFCondition cond = cbdata->dcmff->saveFile(fileName.c_str(), xfer, | |
1638 encodingType, | |
1639 /*opt_groupLength*/ EGL_recalcGL, | |
1640 /*opt_paddingType*/ EPD_withoutPadding, | |
1641 OFstatic_cast(Uint32, /*opt_filepad*/ 0), | |
1642 OFstatic_cast(Uint32, /*opt_itempad*/ 0), | |
1643 (opt_useMetaheader) ? EWM_fileformat : EWM_dataset); | |
1644 #endif | |
1645 } | 1784 } |
1646 } | 1785 } |