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