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