Mercurial > hg > orthanc-webviewer
annotate Plugin/DecodedImageAdapter.cpp @ 99:46ec13a1177c refactoring
use of ordered-slices
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 27 Nov 2015 21:39:41 +0100 |
parents | ef1b27ba7dfc |
children | 2932473a9b19 |
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 printf("[%s] [%s] [%s]\n", what[1].str().c_str(), what[2].str().c_str(), what[3].str().c_str()); |
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 | |
99
46ec13a1177c
use of ordered-slices
Sebastien Jodogne <s.jodogne@gmail.com>
parents:
97
diff
changeset
|
109 std::auto_ptr<OrthancImageWrapper> image(decoderCache_.Decode(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 | |
140 static bool GetTagValue(std::string& value, | |
141 const Json::Value& tags, | |
142 const std::string& tag) | |
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 | |
163 | |
164 bool DecodedImageAdapter::GetCornerstoneMetadata(Json::Value& result, | |
165 const Json::Value& tags, | |
166 OrthancImageWrapper& image) | |
167 { | |
168 using namespace Orthanc; | |
169 | |
170 float windowCenter, windowWidth; | |
171 | |
172 Orthanc::ImageAccessor accessor; | |
173 accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetFormat()), image.GetWidth(), | |
174 image.GetHeight(), image.GetPitch(), image.GetBuffer()); | |
175 | |
176 switch (accessor.GetFormat()) | |
177 { | |
178 case PixelFormat_Grayscale8: | |
179 case PixelFormat_Grayscale16: | |
180 case PixelFormat_SignedGrayscale16: | |
181 { | |
182 int64_t a, b; | |
183 Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor); | |
184 result["minPixelValue"] = (a < 0 ? static_cast<int32_t>(a) : 0); | |
185 result["maxPixelValue"] = (b > 0 ? static_cast<int32_t>(b) : 1); | |
186 result["color"] = false; | |
187 | |
188 windowCenter = static_cast<float>(a + b) / 2.0f; | |
189 | |
190 if (a == b) | |
191 { | |
192 windowWidth = 256.0f; // Arbitrary value | |
193 } | |
194 else | |
195 { | |
196 windowWidth = static_cast<float>(b - a) / 2.0f; | |
197 } | |
198 | |
199 break; | |
200 } | |
201 | |
202 case PixelFormat_RGB24: | |
203 result["minPixelValue"] = 0; | |
204 result["maxPixelValue"] = 255; | |
205 result["color"] = true; | |
206 windowCenter = 127.5f; | |
207 windowWidth = 256.0f; | |
208 break; | |
209 | |
210 default: | |
211 return false; | |
212 } | |
213 | |
214 result["slope"] = image.GetSlope(); | |
215 result["intercept"] = image.GetIntercept(); | |
216 result["rows"] = image.GetHeight(); | |
217 result["columns"] = image.GetWidth(); | |
218 result["height"] = image.GetHeight(); | |
219 result["width"] = image.GetWidth(); | |
220 result["columnPixelSpacing"] = image.GetColumnPixelSpacing(); | |
221 result["rowPixelSpacing"] = image.GetRowPixelSpacing(); | |
222 | |
223 result["windowCenter"] = windowCenter * image.GetSlope() + image.GetIntercept(); | |
224 result["windowWidth"] = windowWidth * image.GetSlope(); | |
225 | |
226 try | |
227 { | |
228 std::string width, center; | |
229 if (GetTagValue(center, tags, "0028,1050" /*DICOM_TAG_WINDOW_CENTER*/) && | |
230 GetTagValue(width, tags, "0028,1051" /*DICOM_TAG_WINDOW_WIDTH*/)) | |
231 { | |
232 float a = boost::lexical_cast<float>(width); | |
233 float b = boost::lexical_cast<float>(center); | |
234 result["windowWidth"] = a; | |
235 result["windowCenter"] = b; | |
236 } | |
237 } | |
238 catch (boost::bad_lexical_cast&) | |
239 { | |
240 } | |
241 | |
242 return true; | |
243 } | |
244 | |
245 | |
246 | |
247 bool DecodedImageAdapter::EncodeUsingDeflate(Json::Value& result, | |
248 OrthancImageWrapper& image, | |
249 uint8_t compressionLevel /* between 0 and 9 */) | |
250 { | |
251 Orthanc::ImageAccessor accessor; | |
252 accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetFormat()), image.GetWidth(), | |
253 image.GetHeight(), image.GetPitch(), image.GetBuffer()); | |
254 | |
255 Orthanc::ImageBuffer buffer; | |
256 buffer.SetMinimalPitchForced(true); | |
257 | |
258 Orthanc::ImageAccessor converted; | |
259 | |
260 switch (accessor.GetFormat()) | |
261 { | |
262 case Orthanc::PixelFormat_RGB24: | |
263 converted = accessor; | |
264 break; | |
265 | |
266 case Orthanc::PixelFormat_Grayscale8: | |
267 case Orthanc::PixelFormat_Grayscale16: | |
268 buffer.SetFormat(Orthanc::PixelFormat_SignedGrayscale16); | |
269 buffer.SetWidth(accessor.GetWidth()); | |
270 buffer.SetHeight(accessor.GetHeight()); | |
271 converted = buffer.GetAccessor(); | |
272 Orthanc::ImageProcessing::Convert(converted, accessor); | |
273 break; | |
274 | |
275 case Orthanc::PixelFormat_SignedGrayscale16: | |
276 converted = accessor; | |
277 break; | |
278 | |
279 default: | |
280 // Unsupported pixel format | |
281 return false; | |
282 } | |
283 | |
284 // Sanity check: The pitch must be minimal | |
285 assert(converted.GetSize() == converted.GetWidth() * converted.GetHeight() * | |
286 GetBytesPerPixel(converted.GetFormat())); | |
287 result["Orthanc"]["Compression"] = "Deflate"; | |
288 result["sizeInBytes"] = converted.GetSize(); | |
289 | |
290 std::string z; | |
291 CompressUsingDeflate(z, image.GetContext(), converted.GetConstBuffer(), converted.GetSize()); | |
292 | |
293 result["Orthanc"]["PixelData"] = base64_encode(z); | |
294 | |
295 return true; | |
296 } | |
297 | |
298 | |
299 | |
300 template <typename TargetType, typename SourceType> | |
301 static void ChangeDynamics(Orthanc::ImageAccessor& target, | |
302 const Orthanc::ImageAccessor& source, | |
303 SourceType source1, TargetType target1, | |
304 SourceType source2, TargetType target2) | |
305 { | |
306 if (source.GetWidth() != target.GetWidth() || | |
307 source.GetHeight() != target.GetHeight()) | |
308 { | |
309 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize); | |
310 } | |
311 | |
312 float scale = static_cast<float>(target2 - target1) / static_cast<float>(source2 - source1); | |
313 float offset = static_cast<float>(target1) - scale * static_cast<float>(source1); | |
314 | |
315 const float minValue = static_cast<float>(std::numeric_limits<TargetType>::min()); | |
316 const float maxValue = static_cast<float>(std::numeric_limits<TargetType>::max()); | |
317 | |
318 for (unsigned int y = 0; y < source.GetHeight(); y++) | |
319 { | |
320 const SourceType* p = reinterpret_cast<const SourceType*>(source.GetConstRow(y)); | |
321 TargetType* q = reinterpret_cast<TargetType*>(target.GetRow(y)); | |
322 | |
323 for (unsigned int x = 0; x < source.GetWidth(); x++, p++, q++) | |
324 { | |
325 float v = (scale * static_cast<float>(*p)) + offset; | |
326 | |
327 if (v > maxValue) | |
328 { | |
329 *q = std::numeric_limits<TargetType>::max(); | |
330 } | |
331 else if (v < minValue) | |
332 { | |
333 *q = std::numeric_limits<TargetType>::min(); | |
334 } | |
335 else | |
336 { | |
337 *q = static_cast<TargetType>(boost::math::iround(v)); | |
338 } | |
339 } | |
340 } | |
341 } | |
342 | |
343 | |
344 bool DecodedImageAdapter::EncodeUsingJpeg(Json::Value& result, | |
345 OrthancImageWrapper& image, | |
346 uint8_t quality /* between 0 and 100 */) | |
347 { | |
348 Orthanc::ImageAccessor accessor; | |
349 accessor.AssignReadOnly(OrthancPlugins::Convert(image.GetFormat()), image.GetWidth(), | |
350 image.GetHeight(), image.GetPitch(), image.GetBuffer()); | |
351 | |
352 Orthanc::ImageBuffer buffer; | |
353 buffer.SetMinimalPitchForced(true); | |
354 | |
355 Orthanc::ImageAccessor converted; | |
356 | |
357 if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale8 || | |
358 accessor.GetFormat() == Orthanc::PixelFormat_RGB24) | |
359 { | |
360 result["Orthanc"]["Stretched"] = false; | |
361 converted = accessor; | |
362 } | |
363 else if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16 || | |
364 accessor.GetFormat() == Orthanc::PixelFormat_SignedGrayscale16) | |
365 { | |
366 result["Orthanc"]["Stretched"] = true; | |
367 buffer.SetFormat(Orthanc::PixelFormat_Grayscale8); | |
368 buffer.SetWidth(accessor.GetWidth()); | |
369 buffer.SetHeight(accessor.GetHeight()); | |
370 converted = buffer.GetAccessor(); | |
371 | |
372 int64_t a, b; | |
373 Orthanc::ImageProcessing::GetMinMaxValue(a, b, accessor); | |
374 result["Orthanc"]["StretchLow"] = static_cast<int32_t>(a); | |
375 result["Orthanc"]["StretchHigh"] = static_cast<int32_t>(b); | |
376 | |
377 if (accessor.GetFormat() == Orthanc::PixelFormat_Grayscale16) | |
378 { | |
379 ChangeDynamics<uint8_t, uint16_t>(converted, accessor, a, 0, b, 255); | |
380 } | |
381 else | |
382 { | |
383 ChangeDynamics<uint8_t, int16_t>(converted, accessor, a, 0, b, 255); | |
384 } | |
385 } | |
386 else | |
387 { | |
388 return false; | |
389 } | |
390 | |
391 result["Orthanc"]["Compression"] = "Jpeg"; | |
392 result["sizeInBytes"] = converted.GetSize(); | |
393 | |
394 std::string jpeg; | |
395 WriteJpegToMemory(jpeg, image.GetContext(), converted, quality); | |
396 | |
397 result["Orthanc"]["PixelData"] = base64_encode(jpeg); | |
398 return true; | |
399 } | |
0 | 400 } |