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 }