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