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 }