0
|
1 /**
|
|
2 * Stone of Orthanc
|
|
3 * Copyright (C) 2012-2016 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 General Public License as
|
|
8 * published by the Free Software Foundation, either version 3 of the
|
|
9 * License, or (at your option) any later version.
|
|
10 *
|
|
11 * In addition, as a special exception, the copyright holders of this
|
|
12 * program give permission to link the code of its release with the
|
|
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it
|
|
14 * that use the same license as the "OpenSSL" library), and distribute
|
|
15 * the linked executables. You must obey the GNU General Public License
|
|
16 * in all respects for all of the code used other than "OpenSSL". If you
|
|
17 * modify file(s) with this exception, you may extend this exception to
|
|
18 * your version of the file(s), but you are not obligated to do so. If
|
|
19 * you do not wish to do so, delete this exception statement from your
|
|
20 * version. If you delete this exception statement from all source files
|
|
21 * in the program, then also delete it here.
|
|
22 *
|
|
23 * This program is distributed in the hope that it will be useful, but
|
|
24 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
26 * General Public License for more details.
|
|
27 *
|
|
28 * You should have received a copy of the GNU General Public License
|
|
29 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
30 **/
|
|
31
|
|
32
|
|
33 #include "MessagingToolbox.h"
|
|
34
|
|
35 #include "../Orthanc/Core/Images/Image.h"
|
|
36 #include "../Orthanc/Core/Images/ImageProcessing.h"
|
|
37 #include "../Orthanc/Core/Images/JpegReader.h"
|
|
38 #include "../Orthanc/Core/Images/PngReader.h"
|
|
39 #include "../Orthanc/Core/OrthancException.h"
|
|
40 #include "../Orthanc/Core/Toolbox.h"
|
|
41 #include "../Orthanc/Core/Logging.h"
|
|
42
|
|
43 #include <boost/lexical_cast.hpp>
|
|
44 #include <json/reader.h>
|
|
45
|
|
46 #if defined(__native_client__)
|
|
47 # include <boost/math/special_functions/round.hpp>
|
|
48 #else
|
|
49 # include <boost/date_time/microsec_time_clock.hpp>
|
|
50 #endif
|
|
51
|
|
52 namespace OrthancStone
|
|
53 {
|
|
54 namespace MessagingToolbox
|
|
55 {
|
|
56 #if defined(__native_client__)
|
|
57 static pp::Core* core_ = NULL;
|
|
58
|
|
59 void Timestamp::Initialize(pp::Core* core)
|
|
60 {
|
|
61 if (core == NULL)
|
|
62 {
|
|
63 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
64 }
|
|
65
|
|
66 core_ = core;
|
|
67 }
|
|
68
|
|
69 Timestamp::Timestamp()
|
|
70 {
|
|
71 if (core_ == NULL)
|
|
72 {
|
|
73 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
|
|
74 }
|
|
75
|
|
76 time_ = core_->GetTimeTicks();
|
|
77 }
|
|
78
|
|
79 int Timestamp::GetMillisecondsSince(const Timestamp& other)
|
|
80 {
|
|
81 double difference = time_ - other.time_;
|
|
82 return static_cast<int>(boost::math::iround(difference * 1000.0));
|
|
83 }
|
|
84 #else
|
|
85 Timestamp::Timestamp()
|
|
86 {
|
|
87 time_ = boost::posix_time::microsec_clock::local_time();
|
|
88 }
|
|
89
|
|
90 int Timestamp::GetMillisecondsSince(const Timestamp& other)
|
|
91 {
|
|
92 boost::posix_time::time_duration difference = time_ - other.time_;
|
|
93 return static_cast<int>(difference.total_milliseconds());
|
|
94 }
|
|
95 #endif
|
|
96
|
|
97 static bool ParseVersion(std::string& version,
|
|
98 unsigned int& major,
|
|
99 unsigned int& minor,
|
|
100 unsigned int& patch,
|
|
101 const Json::Value& info)
|
|
102 {
|
|
103 if (info.type() != Json::objectValue ||
|
|
104 !info.isMember("Version") ||
|
|
105 info["Version"].type() != Json::stringValue)
|
|
106 {
|
|
107 return false;
|
|
108 }
|
|
109
|
|
110 version = info["Version"].asString();
|
|
111 if (version == "mainline")
|
|
112 {
|
|
113 // Some arbitrary high values Orthanc versions will never reach ;)
|
|
114 major = 999;
|
|
115 minor = 999;
|
|
116 patch = 999;
|
|
117 return true;
|
|
118 }
|
|
119
|
|
120 std::vector<std::string> tokens;
|
|
121 Orthanc::Toolbox::TokenizeString(tokens, version, '.');
|
|
122
|
|
123 if (tokens.size() != 2 &&
|
|
124 tokens.size() != 3)
|
|
125 {
|
|
126 return false;
|
|
127 }
|
|
128
|
|
129 int a, b, c;
|
|
130 try
|
|
131 {
|
|
132 a = boost::lexical_cast<int>(tokens[0]);
|
|
133 b = boost::lexical_cast<int>(tokens[1]);
|
|
134
|
|
135 if (tokens.size() == 3)
|
|
136 {
|
|
137 c = boost::lexical_cast<int>(tokens[2]);
|
|
138 }
|
|
139 else
|
|
140 {
|
|
141 c = 0;
|
|
142 }
|
|
143 }
|
|
144 catch (boost::bad_lexical_cast&)
|
|
145 {
|
|
146 return false;
|
|
147 }
|
|
148
|
|
149 if (a < 0 ||
|
|
150 b < 0 ||
|
|
151 c < 0)
|
|
152 {
|
|
153 return false;
|
|
154 }
|
|
155 else
|
|
156 {
|
|
157 major = static_cast<unsigned int>(a);
|
|
158 minor = static_cast<unsigned int>(b);
|
|
159 patch = static_cast<unsigned int>(c);
|
|
160 return true;
|
|
161 }
|
|
162 }
|
|
163
|
|
164 void ParseJson(Json::Value& target,
|
|
165 const std::string& source)
|
|
166 {
|
|
167 Json::Reader reader;
|
|
168 if (!reader.parse(source, target))
|
|
169 {
|
|
170 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
|
|
171 }
|
|
172 }
|
|
173
|
|
174 void RestApiGet(Json::Value& target,
|
|
175 IOrthancConnection& orthanc,
|
|
176 const std::string& uri)
|
|
177 {
|
|
178 std::string tmp;
|
|
179 orthanc.RestApiGet(tmp, uri);
|
|
180 ParseJson(target, tmp);
|
|
181 }
|
|
182
|
|
183
|
|
184 bool HasWebViewerInstalled(IOrthancConnection& orthanc)
|
|
185 {
|
|
186 try
|
|
187 {
|
|
188 Json::Value json;
|
|
189 RestApiGet(json, orthanc, "/plugins/web-viewer");
|
|
190 return json.type() == Json::objectValue;
|
|
191 }
|
|
192 catch (Orthanc::OrthancException&)
|
|
193 {
|
|
194 return false;
|
|
195 }
|
|
196 }
|
|
197
|
|
198
|
|
199 bool CheckOrthancVersion(IOrthancConnection& orthanc)
|
|
200 {
|
|
201 Json::Value json;
|
|
202 std::string version;
|
|
203 unsigned int major, minor, patch;
|
|
204
|
|
205 try
|
|
206 {
|
|
207 RestApiGet(json, orthanc, "/system");
|
|
208 }
|
|
209 catch (Orthanc::OrthancException&)
|
|
210 {
|
|
211 LOG(ERROR) << "Cannot connect to your Orthanc server";
|
|
212 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
213 }
|
|
214
|
|
215 if (!ParseVersion(version, major, minor, patch, json))
|
|
216 {
|
|
217 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
218 }
|
|
219
|
|
220 LOG(WARNING) << "Version of the Orthanc core (must be above 1.1.0): " << version;
|
|
221
|
|
222 // Stone is only compatible with Orthanc >= 1.1.0, otherwise deadlocks might occur
|
|
223 if (major < 1 ||
|
|
224 (major == 1 && minor < 1))
|
|
225 {
|
|
226 return false;
|
|
227 }
|
|
228
|
|
229 try
|
|
230 {
|
|
231 RestApiGet(json, orthanc, "/plugins/web-viewer");
|
|
232 }
|
|
233 catch (Orthanc::OrthancException&)
|
|
234 {
|
|
235 // The Web viewer is not installed, this is OK
|
|
236 LOG(WARNING) << "The Web viewer plugin is not installed, progressive download is disabled";
|
|
237 return true;
|
|
238 }
|
|
239
|
|
240 if (!ParseVersion(version, major, minor, patch, json))
|
|
241 {
|
|
242 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
243 }
|
|
244
|
|
245 LOG(WARNING) << "Version of the Web viewer plugin (must be above 2.2): " << version;
|
|
246
|
|
247 return (major >= 3 ||
|
|
248 (major == 2 && minor >= 2));
|
|
249 }
|
|
250
|
|
251
|
|
252 Orthanc::ImageAccessor* DecodeFrame(IOrthancConnection& orthanc,
|
|
253 const std::string& instance,
|
|
254 unsigned int frame,
|
|
255 Orthanc::PixelFormat targetFormat)
|
|
256 {
|
|
257 std::string uri = ("instances/" + instance + "/frames/" +
|
|
258 boost::lexical_cast<std::string>(frame));
|
|
259
|
|
260 std::string compressed;
|
|
261
|
|
262 switch (targetFormat)
|
|
263 {
|
|
264 case Orthanc::PixelFormat_RGB24:
|
|
265 orthanc.RestApiGet(compressed, uri + "/preview");
|
|
266 break;
|
|
267
|
|
268 case Orthanc::PixelFormat_Grayscale16:
|
|
269 orthanc.RestApiGet(compressed, uri + "/image-uint16");
|
|
270 break;
|
|
271
|
|
272 case Orthanc::PixelFormat_SignedGrayscale16:
|
|
273 orthanc.RestApiGet(compressed, uri + "/image-int16");
|
|
274 break;
|
|
275
|
|
276 default:
|
|
277 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
|
|
278 }
|
|
279
|
|
280 std::auto_ptr<Orthanc::PngReader> result(new Orthanc::PngReader);
|
|
281 result->ReadFromMemory(compressed);
|
|
282
|
|
283 if (targetFormat == Orthanc::PixelFormat_SignedGrayscale16)
|
|
284 {
|
|
285 if (result->GetFormat() == Orthanc::PixelFormat_Grayscale16)
|
|
286 {
|
|
287 result->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
|
|
288 }
|
|
289 else
|
|
290 {
|
|
291 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
292 }
|
|
293 }
|
|
294
|
|
295 return result.release();
|
|
296 }
|
|
297
|
|
298
|
|
299 Orthanc::ImageAccessor* DecodeJpegFrame(IOrthancConnection& orthanc,
|
|
300 const std::string& instance,
|
|
301 unsigned int frame,
|
|
302 unsigned int quality,
|
|
303 Orthanc::PixelFormat targetFormat)
|
|
304 {
|
|
305 if (quality <= 0 ||
|
|
306 quality > 100)
|
|
307 {
|
|
308 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
|
|
309 }
|
|
310
|
|
311 // This requires the official Web viewer plugin to be installed!
|
|
312 std::string uri = ("web-viewer/instances/jpeg" +
|
|
313 boost::lexical_cast<std::string>(quality) +
|
|
314 "-" + instance + "_" +
|
|
315 boost::lexical_cast<std::string>(frame));
|
|
316
|
|
317 Json::Value encoded;
|
|
318 RestApiGet(encoded, orthanc, uri);
|
|
319
|
|
320 if (encoded.type() != Json::objectValue ||
|
|
321 !encoded.isMember("Orthanc") ||
|
|
322 encoded["Orthanc"].type() != Json::objectValue)
|
|
323 {
|
|
324 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
325 }
|
|
326
|
|
327 Json::Value& info = encoded["Orthanc"];
|
|
328 if (!info.isMember("PixelData") ||
|
|
329 !info.isMember("Stretched") ||
|
|
330 !info.isMember("Compression") ||
|
|
331 info["Compression"].type() != Json::stringValue ||
|
|
332 info["PixelData"].type() != Json::stringValue ||
|
|
333 info["Stretched"].type() != Json::booleanValue ||
|
|
334 info["Compression"].asString() != "Jpeg")
|
|
335 {
|
|
336 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
337 }
|
|
338
|
|
339 bool isSigned = false;
|
|
340 bool isStretched = info["Stretched"].asBool();
|
|
341
|
|
342 if (info.isMember("IsSigned"))
|
|
343 {
|
|
344 if (info["IsSigned"].type() != Json::booleanValue)
|
|
345 {
|
|
346 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
347 }
|
|
348 else
|
|
349 {
|
|
350 isSigned = info["IsSigned"].asBool();
|
|
351 }
|
|
352 }
|
|
353
|
|
354 std::string jpeg;
|
|
355 Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
|
|
356
|
|
357 std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
|
|
358 reader->ReadFromMemory(jpeg);
|
|
359
|
|
360 if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image
|
|
361 {
|
|
362 if (targetFormat != Orthanc::PixelFormat_RGB24)
|
|
363 {
|
|
364 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
365 }
|
|
366
|
|
367 if (isSigned || isStretched)
|
|
368 {
|
|
369 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
|
|
370 }
|
|
371 else
|
|
372 {
|
|
373 return reader.release();
|
|
374 }
|
|
375 }
|
|
376
|
|
377 if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
|
|
378 {
|
|
379 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
|
|
380 }
|
|
381
|
|
382 if (!isStretched)
|
|
383 {
|
|
384 if (targetFormat != reader->GetFormat())
|
|
385 {
|
|
386 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
387 }
|
|
388
|
|
389 return reader.release();
|
|
390 }
|
|
391
|
|
392 int32_t stretchLow = 0;
|
|
393 int32_t stretchHigh = 0;
|
|
394
|
|
395 if (!info.isMember("StretchLow") ||
|
|
396 !info.isMember("StretchHigh") ||
|
|
397 info["StretchLow"].type() != Json::intValue ||
|
|
398 info["StretchHigh"].type() != Json::intValue)
|
|
399 {
|
|
400 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
|
|
401 }
|
|
402
|
|
403 stretchLow = info["StretchLow"].asInt();
|
|
404 stretchHigh = info["StretchHigh"].asInt();
|
|
405
|
|
406 if (stretchLow < -32768 ||
|
|
407 stretchHigh > 65535 ||
|
|
408 (stretchLow < 0 && stretchHigh > 32767))
|
|
409 {
|
|
410 // This range cannot be represented with a uint16_t or an int16_t
|
|
411 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
|
|
412 }
|
|
413
|
|
414 // Decode a grayscale JPEG 8bpp image coming from the Web viewer
|
|
415 std::auto_ptr<Orthanc::ImageAccessor> image
|
|
416 (new Orthanc::Image(targetFormat, reader->GetWidth(), reader->GetHeight()));
|
|
417
|
|
418 float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
|
|
419 float offset = static_cast<float>(stretchLow) / scaling;
|
|
420
|
|
421 Orthanc::ImageProcessing::Convert(*image, *reader);
|
|
422 Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling);
|
|
423
|
|
424 #if 0
|
|
425 /*info.removeMember("PixelData");
|
|
426 std::cout << info.toStyledString();*/
|
|
427
|
|
428 int64_t a, b;
|
|
429 Orthanc::ImageProcessing::GetMinMaxValue(a, b, *image);
|
|
430 std::cout << stretchLow << "->" << stretchHigh << " = " << a << "->" << b << std::endl;
|
|
431 #endif
|
|
432
|
|
433 return image.release();
|
|
434 }
|
|
435 }
|
|
436 }
|