comparison OrthancServer/Internals/DicomFrameIndex.cpp @ 1924:6c73df12ca51

New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding)
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 07 Mar 2016 17:43:20 +0100
parents
children 84c7eaeb5244
comparison
equal deleted inserted replaced
1923:6ac7f31fc543 1924:6c73df12ca51
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 *
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 #include "../PrecompiledHeadersServer.h"
34 #include "DicomFrameIndex.h"
35
36 #include "../../Core/OrthancException.h"
37 #include "../../Core/DicomFormat/DicomImageInformation.h"
38 #include "../FromDcmtkBridge.h"
39 #include "DicomImageDecoder.h"
40
41 #include <boost/lexical_cast.hpp>
42
43 #include <dcmtk/dcmdata/dcdeftag.h>
44 #include <dcmtk/dcmdata/dcpxitem.h>
45 #include <dcmtk/dcmdata/dcpixseq.h>
46
47 #if defined(_WIN32) // Windows machines are always little-endian
48 # define le32toh(x) x
49 #elif defined(__APPLE__)
50 # include <libkern/OSByteOrder.h>
51 # define le32toh(x) OSSwapLittleToHostInt32(x)
52 #else
53 # include <endian.h>
54 #endif
55
56
57 namespace Orthanc
58 {
59 class DicomFrameIndex::FragmentIndex : public DicomFrameIndex::IIndex
60 {
61 private:
62 DcmPixelSequence* pixelSequence_;
63 std::vector<DcmPixelItem*> startFragment_;
64 std::vector<unsigned int> countFragments_;
65 std::vector<unsigned int> frameSize_;
66
67 void GetOffsetTable(std::vector<uint32_t>& table)
68 {
69 DcmPixelItem* item = NULL;
70 if (!pixelSequence_->getItem(item, 0).good() ||
71 item == NULL)
72 {
73 throw OrthancException(ErrorCode_BadFileFormat);
74 }
75
76 uint32_t length = item->getLength();
77 if (length == 0)
78 {
79 table.clear();
80 return;
81 }
82
83 if (length % 4 != 0)
84 {
85 // Error: Each fragment is index with 4 bytes (uint32_t)
86 throw OrthancException(ErrorCode_BadFileFormat);
87 }
88
89 uint8_t* content = NULL;
90 if (!item->getUint8Array(content).good() ||
91 content == NULL)
92 {
93 throw OrthancException(ErrorCode_InternalError);
94 }
95
96 table.resize(length / 4);
97
98 // The offset table is always in little endian in the DICOM
99 // file. Swap it to host endianness if needed.
100 const uint32_t* offset = reinterpret_cast<const uint32_t*>(content);
101 for (size_t i = 0; i < table.size(); i++, offset++)
102 {
103 table[i] = le32toh(*offset);
104 }
105 }
106
107
108 public:
109 FragmentIndex(DcmPixelSequence* pixelSequence,
110 unsigned int countFrames) :
111 pixelSequence_(pixelSequence)
112 {
113 assert(pixelSequence != NULL);
114
115 startFragment_.resize(countFrames);
116 countFragments_.resize(countFrames);
117 frameSize_.resize(countFrames);
118
119 // The first fragment corresponds to the offset table
120 unsigned int countFragments = static_cast<unsigned int>(pixelSequence_->card());
121 if (countFragments < countFrames + 1)
122 {
123 throw OrthancException(ErrorCode_BadFileFormat);
124 }
125
126 if (countFragments == countFrames + 1)
127 {
128 // Simple case: There is one fragment per frame.
129
130 DcmObject* fragment = pixelSequence_->nextInContainer(NULL); // Skip the offset table
131 if (fragment == NULL)
132 {
133 throw OrthancException(ErrorCode_InternalError);
134 }
135
136 for (unsigned int i = 0; i < countFrames; i++)
137 {
138 fragment = pixelSequence_->nextInContainer(fragment);
139 startFragment_[i] = dynamic_cast<DcmPixelItem*>(fragment);
140 frameSize_[i] = fragment->getLength();
141 countFragments_[i] = 1;
142 }
143
144 return;
145 }
146
147 // Parse the offset table
148 std::vector<uint32_t> offsetOfFrame;
149 GetOffsetTable(offsetOfFrame);
150
151 if (offsetOfFrame.size() != countFrames ||
152 offsetOfFrame[0] != 0)
153 {
154 throw OrthancException(ErrorCode_BadFileFormat);
155 }
156
157
158 // Loop over the fragments (ignoring the offset table). This is
159 // an alternative, faster implementation to DCMTK's
160 // "DcmCodec::determineStartFragment()".
161 DcmObject* fragment = pixelSequence_->nextInContainer(NULL);
162 if (fragment == NULL)
163 {
164 throw OrthancException(ErrorCode_InternalError);
165 }
166
167 fragment = pixelSequence_->nextInContainer(fragment); // Skip the offset table
168 if (fragment == NULL)
169 {
170 throw OrthancException(ErrorCode_InternalError);
171 }
172
173 uint32_t offset = 0;
174 unsigned int currentFrame = 0;
175 startFragment_[0] = dynamic_cast<DcmPixelItem*>(fragment);
176
177 unsigned int currentFragment = 1;
178 while (fragment != NULL)
179 {
180 if (currentFrame + 1 < countFrames &&
181 offset == offsetOfFrame[currentFrame + 1])
182 {
183 currentFrame += 1;
184 startFragment_[currentFrame] = dynamic_cast<DcmPixelItem*>(fragment);
185 }
186
187 frameSize_[currentFrame] += fragment->getLength();
188 countFragments_[currentFrame]++;
189
190 // 8 bytes = overhead for the item tag and length field
191 offset += fragment->getLength() + 8;
192
193 currentFragment++;
194 fragment = pixelSequence_->nextInContainer(fragment);
195 }
196
197 if (currentFragment != countFragments ||
198 currentFrame + 1 != countFrames ||
199 fragment != NULL)
200 {
201 throw OrthancException(ErrorCode_BadFileFormat);
202 }
203
204 assert(startFragment_.size() == countFragments_.size() &&
205 startFragment_.size() == frameSize_.size());
206 }
207
208
209 virtual void GetRawFrame(std::string& frame,
210 unsigned int index) const
211 {
212 if (index >= startFragment_.size())
213 {
214 throw OrthancException(ErrorCode_ParameterOutOfRange);
215 }
216
217 frame.resize(frameSize_[index]);
218 if (frame.size() == 0)
219 {
220 return;
221 }
222
223 uint8_t* target = reinterpret_cast<uint8_t*>(&frame[0]);
224
225 size_t offset = 0;
226 DcmPixelItem* fragment = startFragment_[index];
227 for (unsigned int i = 0; i < countFragments_[index]; i++)
228 {
229 uint8_t* content = NULL;
230 if (!fragment->getUint8Array(content).good() ||
231 content == NULL)
232 {
233 throw OrthancException(ErrorCode_InternalError);
234 }
235
236 assert(offset + fragment->getLength() <= frame.size());
237
238 memcpy(target + offset, content, fragment->getLength());
239 offset += fragment->getLength();
240
241 fragment = dynamic_cast<DcmPixelItem*>(pixelSequence_->nextInContainer(fragment));
242 }
243 }
244 };
245
246
247
248 class DicomFrameIndex::UncompressedIndex : public DicomFrameIndex::IIndex
249 {
250 private:
251 uint8_t* pixelData_;
252 size_t frameSize_;
253
254 public:
255 UncompressedIndex(DcmDataset& dataset,
256 unsigned int countFrames,
257 size_t frameSize) :
258 pixelData_(NULL),
259 frameSize_(frameSize)
260 {
261 size_t size = 0;
262
263 DcmElement* e;
264 if (dataset.findAndGetElement(DCM_PixelData, e).good() &&
265 e != NULL)
266 {
267 size = e->getLength();
268
269 if (size > 0)
270 {
271 pixelData_ = NULL;
272 if (!e->getUint8Array(pixelData_).good() ||
273 pixelData_ == NULL)
274 {
275 throw OrthancException(ErrorCode_BadFileFormat);
276 }
277 }
278 }
279
280 if (size < frameSize_ * countFrames)
281 {
282 throw OrthancException(ErrorCode_BadFileFormat);
283 }
284 }
285
286 virtual void GetRawFrame(std::string& frame,
287 unsigned int index) const
288 {
289 frame.resize(frameSize_);
290 if (frameSize_ > 0)
291 {
292 memcpy(&frame[0], pixelData_ + index * frameSize_, frameSize_);
293 }
294 }
295 };
296
297
298 class DicomFrameIndex::PsmctRle1Index : public DicomFrameIndex::IIndex
299 {
300 private:
301 std::string pixelData_;
302 size_t frameSize_;
303
304 public:
305 PsmctRle1Index(DcmDataset& dataset,
306 unsigned int countFrames,
307 size_t frameSize) :
308 frameSize_(frameSize)
309 {
310 if (!DicomImageDecoder::DecodePsmctRle1(pixelData_, dataset) ||
311 pixelData_.size() < frameSize * countFrames)
312 {
313 throw OrthancException(ErrorCode_BadFileFormat);
314 }
315 }
316
317 virtual void GetRawFrame(std::string& frame,
318 unsigned int index) const
319 {
320 frame.resize(frameSize_);
321 if (frameSize_ > 0)
322 {
323 memcpy(&frame[0], reinterpret_cast<const uint8_t*>(&pixelData_[0]) + index * frameSize_, frameSize_);
324 }
325 }
326 };
327
328
329
330
331 unsigned int DicomFrameIndex::GetFramesCount(DcmDataset& dataset)
332 {
333 const char* tmp = NULL;
334 if (!dataset.findAndGetString(DCM_NumberOfFrames, tmp).good() ||
335 tmp == NULL)
336 {
337 return 1;
338 }
339
340 int count = -1;
341 try
342 {
343 count = boost::lexical_cast<int>(tmp);
344 }
345 catch (boost::bad_lexical_cast&)
346 {
347 }
348
349 if (count < 0)
350 {
351 throw OrthancException(ErrorCode_BadFileFormat);
352 }
353 else
354 {
355 return count;
356 }
357 }
358
359
360 DicomFrameIndex::DicomFrameIndex(DcmDataset& dataset)
361 {
362 countFrames_ = GetFramesCount(dataset);
363 if (countFrames_ == 0)
364 {
365 // The image has no frame. No index is to be built.
366 return;
367 }
368
369 // Test whether this image is composed of a sequence of fragments
370 DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
371 if (pixelSequence != NULL)
372 {
373 index_.reset(new FragmentIndex(pixelSequence, countFrames_));
374 return;
375 }
376
377 // Extract information about the image structure
378 DicomMap tags;
379 FromDcmtkBridge::Convert(tags, dataset);
380
381 DicomImageInformation information(tags);
382
383 // Access to the raw pixel data
384 if (DicomImageDecoder::IsPsmctRle1(dataset))
385 {
386 index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize()));
387 }
388 else
389 {
390 index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize()));
391 }
392 }
393
394
395 void DicomFrameIndex::GetRawFrame(std::string& frame,
396 unsigned int index) const
397 {
398 if (index >= countFrames_)
399 {
400 throw OrthancException(ErrorCode_ParameterOutOfRange);
401 }
402 else if (index_.get() != NULL)
403 {
404 return index_->GetRawFrame(frame, index);
405 }
406 else
407 {
408 frame.clear();
409 }
410 }
411 }