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