Mercurial > hg > orthanc
comparison Core/DicomParsing/Internals/DicomFrameIndex.cpp @ 2382:7284093111b0
big reorganization to cleanly separate framework vs. server
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 29 Aug 2017 21:17:35 +0200 |
parents | OrthancServer/Internals/DicomFrameIndex.cpp@b8969010b534 |
children | 878b59270859 |
comparison
equal
deleted
inserted
replaced
2381:b8969010b534 | 2382:7284093111b0 |
---|---|
1 /** | |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics | |
4 * Department, University Hospital of Liege, Belgium | |
5 * Copyright (C) 2017 Osimis, Belgium | |
6 * | |
7 * This program is free software: you can redistribute it and/or | |
8 * modify it under the terms of the GNU General Public License as | |
9 * published by the Free Software Foundation, either version 3 of the | |
10 * License, or (at your option) any later version. | |
11 * | |
12 * In addition, as a special exception, the copyright holders of this | |
13 * program give permission to link the code of its release with the | |
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
15 * that use the same license as the "OpenSSL" library), and distribute | |
16 * the linked executables. You must obey the GNU General Public License | |
17 * in all respects for all of the code used other than "OpenSSL". If you | |
18 * modify file(s) with this exception, you may extend this exception to | |
19 * your version of the file(s), but you are not obligated to do so. If | |
20 * you do not wish to do so, delete this exception statement from your | |
21 * version. If you delete this exception statement from all source files | |
22 * in the program, then also delete it here. | |
23 * | |
24 * This program is distributed in the hope that it will be useful, but | |
25 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
27 * General Public License for more details. | |
28 * | |
29 * You should have received a copy of the GNU General Public License | |
30 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
31 **/ | |
32 | |
33 | |
34 #include "../../PrecompiledHeaders.h" | |
35 #include "DicomFrameIndex.h" | |
36 | |
37 #include "../../OrthancException.h" | |
38 #include "../../DicomFormat/DicomImageInformation.h" | |
39 #include "../FromDcmtkBridge.h" | |
40 #include "../../Endianness.h" | |
41 #include "DicomImageDecoder.h" | |
42 | |
43 #include <boost/lexical_cast.hpp> | |
44 | |
45 #include <dcmtk/dcmdata/dcdeftag.h> | |
46 #include <dcmtk/dcmdata/dcpxitem.h> | |
47 #include <dcmtk/dcmdata/dcpixseq.h> | |
48 | |
49 namespace Orthanc | |
50 { | |
51 class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex | |
52 { | |
53 private: | |
54 DcmPixelSequence* pixelSequence_; | |
55 std::vector<DcmPixelItem*> startFragment_; | |
56 std::vector<unsigned int> countFragments_; | |
57 std::vector<unsigned int> frameSize_; | |
58 | |
59 void GetOffsetTable(std::vector<uint32_t>& table) | |
60 { | |
61 DcmPixelItem* item = NULL; | |
62 if (!pixelSequence_->getItem(item, 0).good() || | |
63 item == NULL) | |
64 { | |
65 throw OrthancException(ErrorCode_BadFileFormat); | |
66 } | |
67 | |
68 uint32_t length = item->getLength(); | |
69 if (length == 0) | |
70 { | |
71 table.clear(); | |
72 return; | |
73 } | |
74 | |
75 if (length % 4 != 0) | |
76 { | |
77 // Error: Each fragment is index with 4 bytes (uint32_t) | |
78 throw OrthancException(ErrorCode_BadFileFormat); | |
79 } | |
80 | |
81 uint8_t* content = NULL; | |
82 if (!item->getUint8Array(content).good() || | |
83 content == NULL) | |
84 { | |
85 throw OrthancException(ErrorCode_InternalError); | |
86 } | |
87 | |
88 table.resize(length / 4); | |
89 | |
90 // The offset table is always in little endian in the DICOM | |
91 // file. Swap it to host endianness if needed. | |
92 const uint32_t* offset = reinterpret_cast<const uint32_t*>(content); | |
93 for (size_t i = 0; i < table.size(); i++, offset++) | |
94 { | |
95 table[i] = le32toh(*offset); | |
96 } | |
97 } | |
98 | |
99 | |
100 public: | |
101 FragmentIndex(DcmPixelSequence* pixelSequence, | |
102 unsigned int countFrames) : | |
103 pixelSequence_(pixelSequence) | |
104 { | |
105 assert(pixelSequence != NULL); | |
106 | |
107 startFragment_.resize(countFrames); | |
108 countFragments_.resize(countFrames); | |
109 frameSize_.resize(countFrames); | |
110 | |
111 // The first fragment corresponds to the offset table | |
112 unsigned int countFragments = static_cast<unsigned int>(pixelSequence_->card()); | |
113 if (countFragments < countFrames + 1) | |
114 { | |
115 throw OrthancException(ErrorCode_BadFileFormat); | |
116 } | |
117 | |
118 if (countFragments == countFrames + 1) | |
119 { | |
120 // Simple case: There is one fragment per frame. | |
121 | |
122 DcmObject* fragment = pixelSequence_->nextInContainer(NULL); // Skip the offset table | |
123 if (fragment == NULL) | |
124 { | |
125 throw OrthancException(ErrorCode_InternalError); | |
126 } | |
127 | |
128 for (unsigned int i = 0; i < countFrames; i++) | |
129 { | |
130 fragment = pixelSequence_->nextInContainer(fragment); | |
131 startFragment_[i] = dynamic_cast<DcmPixelItem*>(fragment); | |
132 frameSize_[i] = fragment->getLength(); | |
133 countFragments_[i] = 1; | |
134 } | |
135 | |
136 return; | |
137 } | |
138 | |
139 // Parse the offset table | |
140 std::vector<uint32_t> offsetOfFrame; | |
141 GetOffsetTable(offsetOfFrame); | |
142 | |
143 if (offsetOfFrame.size() != countFrames || | |
144 offsetOfFrame[0] != 0) | |
145 { | |
146 throw OrthancException(ErrorCode_BadFileFormat); | |
147 } | |
148 | |
149 | |
150 // Loop over the fragments (ignoring the offset table). This is | |
151 // an alternative, faster implementation to DCMTK's | |
152 // "DcmCodec::determineStartFragment()". | |
153 DcmObject* fragment = pixelSequence_->nextInContainer(NULL); | |
154 if (fragment == NULL) | |
155 { | |
156 throw OrthancException(ErrorCode_InternalError); | |
157 } | |
158 | |
159 fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table | |
160 if (fragment == NULL) | |
161 { | |
162 throw OrthancException(ErrorCode_InternalError); | |
163 } | |
164 | |
165 uint32_t offset = 0; | |
166 unsigned int currentFrame = 0; | |
167 startFragment_[0] = dynamic_cast<DcmPixelItem*>(fragment); | |
168 | |
169 unsigned int currentFragment = 1; | |
170 while (fragment != NULL) | |
171 { | |
172 if (currentFrame + 1 < countFrames && | |
173 offset == offsetOfFrame[currentFrame + 1]) | |
174 { | |
175 currentFrame += 1; | |
176 startFragment_[currentFrame] = dynamic_cast<DcmPixelItem*>(fragment); | |
177 } | |
178 | |
179 frameSize_[currentFrame] += fragment->getLength(); | |
180 countFragments_[currentFrame]++; | |
181 | |
182 // 8 bytes = overhead for the item tag and length field | |
183 offset += fragment->getLength() + 8; | |
184 | |
185 currentFragment++; | |
186 fragment = pixelSequence_->nextInContainer(fragment); | |
187 } | |
188 | |
189 if (currentFragment != countFragments || | |
190 currentFrame + 1 != countFrames || | |
191 fragment != NULL) | |
192 { | |
193 throw OrthancException(ErrorCode_BadFileFormat); | |
194 } | |
195 | |
196 assert(startFragment_.size() == countFragments_.size() && | |
197 startFragment_.size() == frameSize_.size()); | |
198 } | |
199 | |
200 | |
201 virtual void GetRawFrame(std::string& frame, | |
202 unsigned int index) const | |
203 { | |
204 if (index >= startFragment_.size()) | |
205 { | |
206 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
207 } | |
208 | |
209 frame.resize(frameSize_[index]); | |
210 if (frame.size() == 0) | |
211 { | |
212 return; | |
213 } | |
214 | |
215 uint8_t* target = reinterpret_cast<uint8_t*>(&frame[0]); | |
216 | |
217 size_t offset = 0; | |
218 DcmPixelItem* fragment = startFragment_[index]; | |
219 for (unsigned int i = 0; i < countFragments_[index]; i++) | |
220 { | |
221 uint8_t* content = NULL; | |
222 if (!fragment->getUint8Array(content).good() || | |
223 content == NULL) | |
224 { | |
225 throw OrthancException(ErrorCode_InternalError); | |
226 } | |
227 | |
228 assert(offset + fragment->getLength() <= frame.size()); | |
229 | |
230 memcpy(target + offset, content, fragment->getLength()); | |
231 offset += fragment->getLength(); | |
232 | |
233 fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment)); | |
234 } | |
235 } | |
236 }; | |
237 | |
238 | |
239 | |
240 class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex | |
241 { | |
242 private: | |
243 uint8_t* pixelData_; | |
244 size_t frameSize_; | |
245 | |
246 public: | |
247 UncompressedIndex(DcmDataset& dataset, | |
248 unsigned int countFrames, | |
249 size_t frameSize) : | |
250 pixelData_(NULL), | |
251 frameSize_(frameSize) | |
252 { | |
253 size_t size = 0; | |
254 | |
255 DcmElement* e; | |
256 if (dataset.findAndGetElement(DCM_PixelData, e).good() && | |
257 e != NULL) | |
258 { | |
259 size = e->getLength(); | |
260 | |
261 if (size > 0) | |
262 { | |
263 pixelData_ = NULL; | |
264 if (!e->getUint8Array(pixelData_).good() || | |
265 pixelData_ == NULL) | |
266 { | |
267 throw OrthancException(ErrorCode_BadFileFormat); | |
268 } | |
269 } | |
270 } | |
271 | |
272 if (size < frameSize_ * countFrames) | |
273 { | |
274 throw OrthancException(ErrorCode_BadFileFormat); | |
275 } | |
276 } | |
277 | |
278 virtual void GetRawFrame(std::string& frame, | |
279 unsigned int index) const | |
280 { | |
281 frame.resize(frameSize_); | |
282 if (frameSize_ > 0) | |
283 { | |
284 memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_); | |
285 } | |
286 } | |
287 }; | |
288 | |
289 | |
290 class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex | |
291 { | |
292 private: | |
293 std::string pixelData_; | |
294 size_t frameSize_; | |
295 | |
296 public: | |
297 PsmctRle1Index(DcmDataset& dataset, | |
298 unsigned int countFrames, | |
299 size_t frameSize) : | |
300 frameSize_(frameSize) | |
301 { | |
302 if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) || | |
303 pixelData_.size() < frameSize * countFrames) | |
304 { | |
305 throw OrthancException(ErrorCode_BadFileFormat); | |
306 } | |
307 } | |
308 | |
309 virtual void GetRawFrame(std::string& frame, | |
310 unsigned int index) const | |
311 { | |
312 frame.resize(frameSize_); | |
313 if (frameSize_ > 0) | |
314 { | |
315 memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_); | |
316 } | |
317 } | |
318 }; | |
319 | |
320 | |
321 | |
322 bool DicomFrameIndex::IsVideo(DcmFileFormat& dicom) | |
323 { | |
324 // Retrieve the transfer syntax from the DICOM header | |
325 const char* value = NULL; | |
326 if (!dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() || | |
327 value == NULL) | |
328 { | |
329 return false; | |
330 } | |
331 | |
332 const std::string transferSyntax(value); | |
333 | |
334 // Video standards supported in DICOM 2016a | |
335 // http://dicom.nema.org/medical/dicom/2016a/output/html/part05.html | |
336 if (transferSyntax == "1.2.840.10008.1.2.4.100" || // MPEG2 MP@ML option of ISO/IEC MPEG2 | |
337 transferSyntax == "1.2.840.10008.1.2.4.101" || // MPEG2 MP@HL option of ISO/IEC MPEG2 | |
338 transferSyntax == "1.2.840.10008.1.2.4.102" || // MPEG-4 AVC/H.264 High Profile / Level 4.1 of ITU-T H.264 | |
339 transferSyntax == "1.2.840.10008.1.2.4.103" || // MPEG-4 AVC/H.264 BD-compat High Profile / Level 4.1 of ITU-T H.264 | |
340 transferSyntax == "1.2.840.10008.1.2.4.104" || // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264 | |
341 transferSyntax == "1.2.840.10008.1.2.4.105" || // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264 | |
342 transferSyntax == "1.2.840.10008.1.2.4.106") // MPEG-4 AVC/H.264 Stereo High Profile / Level 4.2 of the ITU-T H.264 | |
343 { | |
344 return true; | |
345 } | |
346 | |
347 return false; | |
348 } | |
349 | |
350 | |
351 unsigned int DicomFrameIndex::GetFramesCount(DcmFileFormat& dicom) | |
352 { | |
353 // Assume 1 frame for video transfer syntaxes | |
354 if (IsVideo(dicom)) | |
355 { | |
356 return 1; | |
357 } | |
358 | |
359 const char* tmp = NULL; | |
360 if (!dicom.getDataset()->findAndGetString(DCM_NumberOfFrames, tmp).good() || | |
361 tmp == NULL) | |
362 { | |
363 return 1; | |
364 } | |
365 | |
366 int count = -1; | |
367 try | |
368 { | |
369 count = boost::lexical_cast<int>(tmp); | |
370 } | |
371 catch (boost::bad_lexical_cast&) | |
372 { | |
373 } | |
374 | |
375 if (count < 0) | |
376 { | |
377 throw OrthancException(ErrorCode_BadFileFormat); | |
378 } | |
379 else | |
380 { | |
381 return count; | |
382 } | |
383 } | |
384 | |
385 | |
386 DicomFrameIndex::DicomFrameIndex(DcmFileFormat& dicom) | |
387 { | |
388 countFrames_ = GetFramesCount(dicom); | |
389 if (countFrames_ == 0) | |
390 { | |
391 // The image has no frame. No index is to be built. | |
392 return; | |
393 } | |
394 | |
395 DcmDataset& dataset = *dicom.getDataset(); | |
396 | |
397 // Test whether this image is composed of a sequence of fragments | |
398 DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset); | |
399 if (pixelSequence != NULL) | |
400 { | |
401 index_.reset(new FragmentIndex(pixelSequence, countFrames_)); | |
402 return; | |
403 } | |
404 | |
405 // Extract information about the image structure | |
406 DicomMap tags; | |
407 FromDcmtkBridge::ExtractDicomSummary(tags, dataset); | |
408 | |
409 DicomImageInformation information(tags); | |
410 | |
411 // Access to the raw pixel data | |
412 if (DicomImageDecoder::IsPsmctRle1(dataset)) | |
413 { | |
414 index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize())); | |
415 } | |
416 else | |
417 { | |
418 index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize())); | |
419 } | |
420 } | |
421 | |
422 | |
423 void DicomFrameIndex::GetRawFrame(std::string& frame, | |
424 unsigned int index) const | |
425 { | |
426 if (index >= countFrames_) | |
427 { | |
428 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
429 } | |
430 else if (index_.get() != NULL) | |
431 { | |
432 return index_->GetRawFrame(frame, index); | |
433 } | |
434 else | |
435 { | |
436 frame.clear(); | |
437 } | |
438 } | |
439 } |