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