Mercurial > hg > orthanc-webviewer
annotate Plugin/DecodedImageAdapter.cpp @ 101:2932473a9b19 refactoring
fix prefetching
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 27 Nov 2015 22:05:51 +0100 |
parents | 46ec13a1177c |
children | 21123729ac71 |
rev | line source |
---|---|
0 | 1 /** |
2 * Orthanc - A Lightweight, RESTful DICOM Store | |
3 * Copyright (C) 2012-2015 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 "DecodedImageAdapter.h" | |
22 | |
96 | 23 #include "../Orthanc/Core/Images/ImageBuffer.h" |
24 #include "../Orthanc/Core/Images/ImageProcessing.h" | |
25 #include "../Orthanc/Core/OrthancException.h" | |
26 #include "../Orthanc/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h" | |
27 #include "../Orthanc/Resources/ThirdParty/base64/base64.h" | |
0 | 28 #include "ViewerToolbox.h" |
29 | |
30 #include <boost/lexical_cast.hpp> | |
31 #include <boost/algorithm/string/predicate.hpp> | |
32 #include <json/writer.h> | |
99
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
33 #include <boost/regex.hpp> |
0 | 34 |
35 namespace OrthancPlugins | |
36 { | |
37 bool DecodedImageAdapter::ParseUri(CompressionType& type, | |
38 uint8_t& compressionLevel, | |
39 std::string& instanceId, | |
99
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
40 unsigned int& frameIndex, |
0 | 41 const std::string& uri) |
42 { | |
99
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
43 boost::regex pattern("^([a-z0-9]+)-([a-z0-9-]+)_([0-9]+)$"); |
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
44 |
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
45 boost::cmatch what; |
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
46 if (!regex_match(uri.c_str(), what, pattern)) |
0 | 47 { |
48 return false; | |
49 } | |
99
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
50 |
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
51 std::string compression(what[1]); |
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
52 instanceId = what[2]; |
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
53 frameIndex = boost::lexical_cast<unsigned int>(what[3]); |
0 | 54 |
55 if (compression == "deflate") | |
56 { | |
57 type = CompressionType_Deflate; | |
58 } | |
59 else if (boost::starts_with(compression, "jpeg")) | |
60 { | |
61 type = CompressionType_Jpeg; | |
62 int level = boost::lexical_cast<int>(compression.substr(4)); | |
63 if (level <= 0 || level > 100) | |
64 { | |
65 return false; | |
66 } | |
67 | |
68 compressionLevel = static_cast<uint8_t>(level); | |
69 } | |
70 else | |
71 { | |
72 return false; | |
73 } | |
74 | |
75 return true; | |
76 } | |
77 | |
78 | |
79 | |
80 bool DecodedImageAdapter::Create(std::string& content, | |
81 const std::string& uri) | |
82 { | |
83 std::string message = "Decoding DICOM instance: " + uri; | |
84 OrthancPluginLogInfo(context_, message.c_str()); | |
85 | |
86 CompressionType type; | |
87 uint8_t level; | |
88 std::string instanceId; | |
99
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
89 unsigned int frameIndex; |
0 | 90 |
99
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
91 if (!ParseUri(type, level, instanceId, frameIndex, uri)) |
0 | 92 { |
93 return false; | |
94 } | |
95 | |
96 | 96 |
97 bool ok = false; | |
98 | |
99 Json::Value tags; | |
100 std::string dicom; | |
101 if (!GetStringFromOrthanc(dicom, context_, "/instances/" + instanceId + "/file") || | |
102 !GetJsonFromOrthanc(tags, context_, "/instances/" + instanceId + "/tags")) | |
103 { | |
104 throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource); | |
105 } | |
106 | |
99
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
107 std::auto_ptr<OrthancImageWrapper> image(decoderCache_.Decode(context_, dicom.c_str(), dicom.size(), frameIndex)); |
96 | 108 |
109 Json::Value json; | |
110 if (GetCornerstoneMetadata(json, tags, *image)) | |
111 { | |
112 if (type == CompressionType_Deflate) | |
113 { | |
114 ok = EncodeUsingDeflate(json, *image, 9); | |
115 } | |
116 else if (type == CompressionType_Jpeg) | |
117 { | |
118 ok = EncodeUsingJpeg(json, *image, level); | |
119 } | |
120 } | |
121 | |
31 | 122 if (ok) |
123 { | |
124 Json::FastWriter writer; | |
125 content = writer.write(json); | |
126 return true; | |
0 | 127 } |
128 else | |
129 { | |
31 | 130 char msg[1024]; |
131 sprintf(msg, "Unable to decode the following instance: %s", uri.c_str()); | |
132 OrthancPluginLogWarning(context_, msg); | |
0 | 133 return false; |
134 } | |
135 } | |
96 | 136 |
137 | |
138 static bool GetTagValue(std::string& value, | |
139 const Json::Value& tags, | |
140 const std::string& tag) | |
141 { | |
142 if (tags.type() == Json::objectValue && | |
143 tags.isMember(tag) && | |
144 tags[tag].type() == Json::objectValue && | |
145 tags[tag].isMember("Type") && | |
146 tags[tag].isMember("Value") && | |
147 tags[tag]["Type"].type() == Json::stringValue && | |
148 tags[tag]["Value"].type() == Json::stringValue && | |
149 tags[tag]["Type"].asString() == "String") | |
150 { | |
151 value = tags[tag]["Value"].asString(); | |
152 return true; | |
153 } | |
154 else | |
155 { | |
156 return false; | |
157 } | |
158 } | |
159 | |
160 | |
161 | |
162 bool DecodedImageAdapter::GetCornerstoneMetadata(Json::Value& result, | |
163 const Json::Value& tags, | |
164 OrthancImageWrapper& image) | |
165 { | |
166 using namespace Orthanc; | |
167 | |
168 float windowCenter, windowWidth; | |
169 | |
170 Orthanc::ImageAccessor accessor; | |
171 accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetFormat()), image.GetWidth(), | |
172 image.GetHeight(), image.GetPitch(), image.GetBuffer()); | |
173 | |
174 switch (accessor.GetFormat()) | |
175 { | |
176 case PixelFormat_Grayscale8: | |
177 case PixelFormat_Grayscale16: | |
178 case PixelFormat_SignedGrayscale16: | |
179 { | |
180 int64_t a, b; | |
181 Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor); | |
182 result["minPixelValue"] = (a < 0 ? static_cast<int32_t>(a) : 0); | |
183 result["maxPixelValue"] = (b > 0 ? static_cast<int32_t>(b) : 1); | |
184 result["color"] = false; | |
185 | |
186 windowCenter = static_cast<float>(a + b) / 2.0f; | |
187 | |
188 if (a == b) | |
189 { | |
190 windowWidth = 256.0f; // Arbitrary value | |
191 } | |
192 else | |
193 { | |
194 windowWidth = static_cast<float>(b - a) / 2.0f; | |
195 } | |
196 | |
197 break; | |
198 } | |
199 | |
200 case PixelFormat_RGB24: | |
201 result["minPixelValue"] = 0; | |
202 result["maxPixelValue"] = 255; | |
203 result["color"] = true; | |
204 windowCenter = 127.5f; | |
205 windowWidth = 256.0f; | |
206 break; | |
207 | |
208 default: | |
209 return false; | |
210 } | |
211 | |
212 result["slope"] = image.GetSlope(); | |
213 result["intercept"] = image.GetIntercept(); | |
214 result["rows"] = image.GetHeight(); | |
215 result["columns"] = image.GetWidth(); | |
216 result["height"] = image.GetHeight(); | |
217 result["width"] = image.GetWidth(); | |
218 result["columnPixelSpacing"] = image.GetColumnPixelSpacing(); | |
219 result["rowPixelSpacing"] = image.GetRowPixelSpacing(); | |
220 | |
221 result["windowCenter"] = windowCenter * image.GetSlope() + image.GetIntercept(); | |
222 result["windowWidth"] = windowWidth * image.GetSlope(); | |
223 | |
224 try | |
225 { | |
226 std::string width, center; | |
227 if (GetTagValue(center, tags, "0028,1050" /*DICOM_TAG_WINDOW_CENTER*/) && | |
228 GetTagValue(width, tags, "0028,1051" /*DICOM_TAG_WINDOW_WIDTH*/)) | |
229 { | |
230 float a = boost::lexical_cast<float>(width); | |
231 float b = boost::lexical_cast<float>(center); | |
232 result["windowWidth"] = a; | |
233 result["windowCenter"] = b; | |
234 } | |
235 } | |
236 catch (boost::bad_lexical_cast&) | |
237 { | |
238 } | |
239 | |
240 return true; | |
241 } | |
242 | |
243 | |
244 | |
245 bool DecodedImageAdapter::EncodeUsingDeflate(Json::Value& result, | |
246 OrthancImageWrapper& image, | |
247 uint8_t compressionLevel /* between 0 and 9 */) | |
248 { | |
249 Orthanc::ImageAccessor accessor; | |
250 accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetFormat()), image.GetWidth(), | |
251 image.GetHeight(), image.GetPitch(), image.GetBuffer()); | |
252 | |
253 Orthanc::ImageBuffer buffer; | |
254 buffer.SetMinimalPitchForced(true); | |
255 | |
256 Orthanc::ImageAccessor converted; | |
257 | |
258 switch (accessor.GetFormat()) | |
259 { | |
260 case Orthanc::PixelFormat_RGB24: | |
261 converted = accessor; | |
262 break; | |
263 | |
264 case Orthanc::PixelFormat_Grayscale8: | |
265 case Orthanc::PixelFormat_Grayscale16: | |
266 buffer.SetFormat(Orthanc::PixelFormat_SignedGrayscale16); | |
267 buffer.SetWidth(accessor.GetWidth()); | |
268 buffer.SetHeight(accessor.GetHeight()); | |
269 converted = buffer.GetAccessor(); | |
270 Orthanc::ImageProcessing::Convert(converted, accessor); | |
271 break; | |
272 | |
273 case Orthanc::PixelFormat_SignedGrayscale16: | |
274 converted = accessor; | |
275 break; | |
276 | |
277 default: | |
278 // Unsupported pixel format | |
279 return false; | |
280 } | |
281 | |
282 // Sanity check: The pitch must be minimal | |
283 assert(converted.GetSize() == converted.GetWidth() * converted.GetHeight() * | |
284 GetBytesPerPixel(converted.GetFormat())); | |
285 result["Orthanc"]["Compression"] = "Deflate"; | |
286 result["sizeInBytes"] = converted.GetSize(); | |
287 | |
288 std::string z; | |
289 CompressUsingDeflate(z, image.GetContext(), converted.GetConstBuffer(), converted.GetSize()); | |
290 | |
291 result["Orthanc"]["PixelData"] = base64_encode(z); | |
292 | |
293 return true; | |
294 } | |
295 | |
296 | |
297 | |
298 template <typename TargetType, typename SourceType> | |
299 static void ChangeDynamics(Orthanc::ImageAccessor& target, | |
300 const Orthanc::ImageAccessor& source, | |
301 SourceType source1, TargetType target1, | |
302 SourceType source2, TargetType target2) | |
303 { | |
304 if (source.GetWidth() != target.GetWidth() || | |
305 source.GetHeight() != target.GetHeight()) | |
306 { | |
307 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); | |
308 } | |
309 | |
310 float scale = static_cast<float>(target2 - target1) / static_cast<float>(source2 - source1); | |
311 float offset = static_cast<float>(target1) - scale * static_cast<float>(source1); | |
312 | |
313 const float minValue = static_cast<float>(std::numeric_limits<TargetType>::min()); | |
314 const float maxValue = static_cast<float>(std::numeric_limits<TargetType>::max()); | |
315 | |
316 for (unsigned int y = 0; y < source.GetHeight(); y++) | |
317 { | |
318 const SourceType* p = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); | |
319 TargetType* q = reinterpret_cast<TargetType*>(target.GetRow(y)); | |
320 | |
321 for (unsigned int x = 0; x < source.GetWidth(); x++, p++, q++) | |
322 { | |
323 float v = (scale * static_cast<float>(*p)) + offset; | |
324 | |
325 if (v > maxValue) | |
326 { | |
327 *q = std::numeric_limits<TargetType>::max(); | |
328 } | |
329 else if (v < minValue) | |
330 { | |
331 *q = std::numeric_limits<TargetType>::min(); | |
332 } | |
333 else | |
334 { | |
101 | 335 //*q = static_cast<TargetType>(boost::math::iround(v)); |
336 | |
337 // http://stackoverflow.com/a/485546/881731 | |
338 *q = static_cast<TargetType>(floor(v + 0.5f)); | |
96 | 339 } |
340 } | |
341 } | |
342 } | |
343 | |
344 | |
345 bool DecodedImageAdapter::EncodeUsingJpeg(Json::Value& result, | |
346 OrthancImageWrapper& image, | |
347 uint8_t quality /* between 0 and 100 */) | |
348 { | |
349 Orthanc::ImageAccessor accessor; | |
350 accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetFormat()), image.GetWidth(), | |
351 image.GetHeight(), image.GetPitch(), image.GetBuffer()); | |
352 | |
353 Orthanc::ImageBuffer buffer; | |
354 buffer.SetMinimalPitchForced(true); | |
355 | |
356 Orthanc::ImageAccessor converted; | |
357 | |
358 if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale8 || | |
359 accessor.GetFormat() == Orthanc::PixelFormat_RGB24) | |
360 { | |
361 result["Orthanc"]["Stretched"] = false; | |
362 converted = accessor; | |
363 } | |
364 else if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16 || | |
365 accessor.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16) | |
366 { | |
367 result["Orthanc"]["Stretched"] = true; | |
368 buffer.SetFormat(Orthanc::PixelFormat_Grayscale8); | |
369 buffer.SetWidth(accessor.GetWidth()); | |
370 buffer.SetHeight(accessor.GetHeight()); | |
371 converted = buffer.GetAccessor(); | |
372 | |
373 int64_t a, b; | |
374 Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor); | |
375 result["Orthanc"]["StretchLow"] = static_cast<int32_t>(a); | |
376 result["Orthanc"]["StretchHigh"] = static_cast<int32_t>(b); | |
377 | |
378 if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16) | |
379 { | |
380 ChangeDynamics<uint8_t, uint16_t>(converted, accessor, a, 0, b, 255); | |
381 } | |
382 else | |
383 { | |
384 ChangeDynamics<uint8_t, int16_t>(converted, accessor, a, 0, b, 255); | |
385 } | |
386 } | |
387 else | |
388 { | |
389 return false; | |
390 } | |
391 | |
392 result["Orthanc"]["Compression"] = "Jpeg"; | |
393 result["sizeInBytes"] = converted.GetSize(); | |
394 | |
395 std::string jpeg; | |
396 WriteJpegToMemory(jpeg, image.GetContext(), converted, quality); | |
397 | |
398 result["Orthanc"]["PixelData"] = base64_encode(jpeg); | |
399 return true; | |
400 } | |
0 | 401 } |