comparison Framework/Outputs/MultiframeDicomWriter.cpp @ 0:4a7a53257c7d

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Sat, 22 Oct 2016 21:48:33 +0200
parents
children d9c49ab82931
comparison
equal deleted inserted replaced
-1:000000000000 0:4a7a53257c7d
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 Affero General Public License
8 * as published by the Free Software Foundation, either version 3 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 **/
19
20
21 #include "MultiframeDicomWriter.h"
22
23 #include "../Orthanc/Core/OrthancException.h"
24 #include "../Orthanc/Core/Logging.h"
25 #include "../DicomToolbox.h"
26
27 #include <dcmtk/dcmdata/dcuid.h>
28 #include <dcmtk/dcmdata/dcdeftag.h>
29 #include <dcmtk/dcmdata/dcostrmb.h>
30 #include <dcmtk/dcmdata/dcpxitem.h>
31 #include <dcmtk/dcmdata/dcpixel.h>
32 #include <dcmtk/dcmdata/dcvrat.h>
33
34 #include <boost/lexical_cast.hpp>
35
36 namespace OrthancWSI
37 {
38 static void SaveDicomToMemory(std::string& target,
39 DcmFileFormat& dicom,
40 E_TransferSyntax transferSyntax)
41 {
42 dicom.validateMetaInfo(transferSyntax);
43 dicom.removeInvalidGroups();
44
45 E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
46
47 // Create a memory buffer with the proper size
48 {
49 const uint32_t estimatedSize = dicom.calcElementLength(transferSyntax, encodingType); // (*)
50 target.resize(estimatedSize);
51 }
52
53 DcmOutputBufferStream ob(&target[0], target.size());
54
55 // Fill the memory buffer with the meta-header and the dataset
56 dicom.transferInit();
57 OFCondition c = dicom.write(ob, transferSyntax, encodingType, NULL,
58 /*opt_groupLength*/ EGL_recalcGL,
59 /*opt_paddingType*/ EPD_withoutPadding);
60 dicom.transferEnd();
61
62 if (!c.good())
63 {
64 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
65 }
66
67 // The DICOM file is successfully written, truncate the target
68 // buffer if its size was overestimated by (*)
69 ob.flush();
70
71 size_t effectiveSize = static_cast<size_t>(ob.tell());
72 if (effectiveSize < target.size())
73 {
74 target.resize(effectiveSize);
75 }
76 }
77
78
79 void MultiframeDicomWriter::ResetImage()
80 {
81 perFrameFunctionalGroups_.reset(new DcmSequenceOfItems(DCM_PerFrameFunctionalGroupsSequence));
82
83 if (compression_ != ImageCompression_None)
84 {
85 compressedPixelSequence_.reset(new DcmPixelSequence(DcmTag(DCM_PixelData, EVR_OB)));
86
87 offsetTable_ = new DcmPixelItem(DCM_Item, EVR_OB);
88 compressedPixelSequence_->insert(offsetTable_);
89 offsetList_.reset(new DcmOffsetList);
90 }
91
92 writtenSize_ = 0;
93 framesCount_ = 0;
94 }
95
96
97 void MultiframeDicomWriter::InjectUncompressedPixelData(DcmFileFormat& dicom)
98 {
99 static const size_t GIGABYTE = 1024 * 1024 * 1024;
100
101 std::string pixelData;
102 uncompressedPixelData_.Flatten(pixelData);
103
104 // Prevent the creation of too large DICOM files
105 // (uncompressed DICOM files are limited to 2GB)
106 if (pixelData.size() > GIGABYTE)
107 {
108 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
109 }
110
111 std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(DCM_PixelData));
112
113 uint8_t* target = NULL;
114 if (!pixels->createUint8Array(pixelData.size(), target).good())
115 {
116 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
117 }
118
119 if (pixelData.size() > 0)
120 {
121 if (target == NULL)
122 {
123 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
124 }
125
126 // TODO Avoid this memcpy()
127 memcpy(target, pixelData.c_str(), pixelData.size());
128 }
129
130 if (!dicom.getDataset()->insert(pixels.release(), false, false).good())
131 {
132 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
133 }
134 }
135
136
137 MultiframeDicomWriter::MultiframeDicomWriter(const DcmDataset& dataset,
138 ImageCompression compression,
139 Orthanc::PixelFormat pixelFormat,
140 unsigned int width,
141 unsigned int height,
142 unsigned int tileWidth,
143 unsigned int tileHeight) :
144 compression_(compression),
145 width_(width),
146 height_(height)
147 {
148 switch (compression)
149 {
150 case ImageCompression_None:
151 transferSyntax_ = EXS_LittleEndianImplicit;
152 break;
153
154 case ImageCompression_Jpeg:
155 // Default transfer syntax for lossy JPEG 8bit compression
156 transferSyntax_ = EXS_JPEGProcess1TransferSyntax;
157 break;
158
159 case ImageCompression_Jpeg2000:
160 // JPEG2000 compression (lossless only)
161 transferSyntax_ = EXS_JPEG2000LosslessOnly;
162 break;
163
164 default:
165 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
166 }
167
168 if (!sharedTags_.copyFrom(dataset).good())
169 {
170 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
171 }
172
173 // We only take care of grayscale or RGB images in 8bpp
174 DicomToolbox::SetUint32Tag(sharedTags_, DCM_TotalPixelMatrixColumns, width);
175 DicomToolbox::SetUint32Tag(sharedTags_, DCM_TotalPixelMatrixRows, height);
176 DicomToolbox::SetUint16Tag(sharedTags_, DCM_PlanarConfiguration, 0); // Interleaved RGB values
177 DicomToolbox::SetUint16Tag(sharedTags_, DCM_Columns, tileWidth);
178 DicomToolbox::SetUint16Tag(sharedTags_, DCM_Rows, tileHeight);
179 DicomToolbox::SetUint16Tag(sharedTags_, DCM_BitsAllocated, 8);
180 DicomToolbox::SetUint16Tag(sharedTags_, DCM_BitsStored, 8);
181 DicomToolbox::SetUint16Tag(sharedTags_, DCM_HighBit, 7);
182 DicomToolbox::SetUint16Tag(sharedTags_, DCM_PixelRepresentation, 0); // Unsigned values
183
184 switch (pixelFormat)
185 {
186 case Orthanc::PixelFormat_RGB24:
187 uncompressedFrameSize_ = 3 * tileWidth * tileHeight;
188 DicomToolbox::SetUint16Tag(sharedTags_, DCM_SamplesPerPixel, 3);
189
190 if (compression_ == ImageCompression_Jpeg)
191 {
192 DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "YBR_FULL_422");
193 }
194 else
195 {
196 DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "RGB");
197 }
198
199 break;
200
201 case Orthanc::PixelFormat_Grayscale8:
202 uncompressedFrameSize_ = tileWidth * tileHeight;
203 DicomToolbox::SetUint16Tag(sharedTags_, DCM_SamplesPerPixel, 1);
204 DicomToolbox::SetStringTag(sharedTags_, DCM_PhotometricInterpretation, "MONOCHROME2");
205 break;
206
207 default:
208 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
209 }
210
211 ResetImage();
212 }
213
214
215 void MultiframeDicomWriter::AddFrame(const std::string& frame,
216 DcmItem* functionalGroup) // This takes the ownership
217 {
218 // Free the functional group on error
219 std::auto_ptr<DcmItem> functionalGroupRaii(functionalGroup);
220
221 if (compression_ == ImageCompression_None)
222 {
223 if (frame.size() != uncompressedFrameSize_)
224 {
225 LOG(ERROR) << "An uncompressed frame has not the proper size: "
226 << frame.size() << " instead of " << uncompressedFrameSize_;
227 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize);
228 }
229
230 uncompressedPixelData_.AddChunk(frame);
231 }
232 else
233 {
234 uint8_t* bytes = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(frame.c_str()));
235
236 compressedPixelSequence_->storeCompressedFrame(*offsetList_, bytes, frame.size(),
237 0 /* unlimited fragment size */);
238 }
239
240 if (functionalGroup != NULL)
241 {
242 perFrameFunctionalGroups_->insert(functionalGroupRaii.release());
243 }
244 else
245 {
246 perFrameFunctionalGroups_->insert(new DcmItem);
247 }
248
249 writtenSize_ += frame.size();
250 framesCount_ += 1;
251 }
252
253
254 void MultiframeDicomWriter::Flush(std::string& target,
255 unsigned int instanceNumber)
256 {
257 if (instanceNumber <= 0)
258 {
259 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
260 }
261
262 std::string tmp = boost::lexical_cast<std::string>(instanceNumber);
263
264 std::auto_ptr<DcmFileFormat> dicom(new DcmFileFormat);
265
266 char uid[100];
267 dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
268
269 if (!dicom->getDataset()->copyFrom(sharedTags_).good() ||
270 !dicom->getDataset()->insert(perFrameFunctionalGroups_.release(), false, false).good() ||
271 !dicom->getDataset()->putAndInsertString(DCM_SOPInstanceUID, uid).good() ||
272 !dicom->getDataset()->putAndInsertString(DCM_InstanceNumber, tmp.c_str()).good())
273 {
274 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
275 }
276
277 DicomToolbox::SetStringTag(*dicom->getDataset(), DCM_NumberOfFrames, boost::lexical_cast<std::string>(framesCount_));
278
279 switch (compression_)
280 {
281 case ImageCompression_None:
282 InjectUncompressedPixelData(*dicom);
283 break;
284
285 case ImageCompression_Jpeg:
286 case ImageCompression_Jpeg2000:
287 offsetTable_->createOffsetTable(*offsetList_);
288 dicom->getDataset()->insert(compressedPixelSequence_.release());
289 break;
290
291 default:
292 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
293 }
294
295 ResetImage();
296
297 SaveDicomToMemory(target, *dicom, transferSyntax_);
298 }
299 }