comparison Framework/Toolbox/OrthancSlicesLoader.cpp @ 93:5945e81734a3 wasm

decoding of JPEG images
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 29 May 2017 17:28:31 +0200
parents f5f54ed8d307
children 7b14c12a3be5
comparison
equal deleted inserted replaced
92:961ee171d933 93:5945e81734a3
21 21
22 #include "OrthancSlicesLoader.h" 22 #include "OrthancSlicesLoader.h"
23 23
24 #include "MessagingToolbox.h" 24 #include "MessagingToolbox.h"
25 25
26 #include "../../Resources/Orthanc/Core/Images/Image.h"
27 #include "../../Resources/Orthanc/Core/Images/ImageProcessing.h"
28 #include "../../Resources/Orthanc/Core/Images/JpegReader.h"
26 #include "../../Resources/Orthanc/Core/Images/PngReader.h" 29 #include "../../Resources/Orthanc/Core/Images/PngReader.h"
27 #include "../../Resources/Orthanc/Core/Logging.h" 30 #include "../../Resources/Orthanc/Core/Logging.h"
28 #include "../../Resources/Orthanc/Core/OrthancException.h" 31 #include "../../Resources/Orthanc/Core/OrthancException.h"
32 #include "../../Resources/Orthanc/Core/Toolbox.h"
29 #include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h" 33 #include "../../Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h"
30 #include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h" 34 #include "../../Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h"
31 35
32 #include <boost/lexical_cast.hpp> 36 #include <boost/lexical_cast.hpp>
33 37
34 namespace OrthancStone 38 namespace OrthancStone
35 { 39 {
36 class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject 40 class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject
37 { 41 {
38 private: 42 private:
39 Mode mode_; 43 Mode mode_;
40 unsigned int frame_; 44 unsigned int frame_;
41 unsigned int sliceIndex_; 45 unsigned int sliceIndex_;
42 const Slice* slice_; 46 const Slice* slice_;
43 std::string instanceId_; 47 std::string instanceId_;
48 SliceImageQuality quality_;
44 49
45 Operation(Mode mode) : 50 Operation(Mode mode) :
46 mode_(mode) 51 mode_(mode)
47 { 52 {
48 } 53 }
49 54
50 public: 55 public:
51 Mode GetMode() const 56 Mode GetMode() const
52 { 57 {
53 return mode_; 58 return mode_;
59 }
60
61 SliceImageQuality GetQuality() const
62 {
63 assert(mode_ == Mode_LoadImage);
64 return quality_;
54 } 65 }
55 66
56 unsigned int GetSliceIndex() const 67 unsigned int GetSliceIndex() const
57 { 68 {
58 assert(mode_ == Mode_LoadImage); 69 assert(mode_ == Mode_LoadImage);
90 operation->frame_ = frame; 101 operation->frame_ = frame;
91 return operation.release(); 102 return operation.release();
92 } 103 }
93 104
94 static Operation* DownloadSliceImage(unsigned int sliceIndex, 105 static Operation* DownloadSliceImage(unsigned int sliceIndex,
95 const Slice& slice) 106 const Slice& slice,
107 SliceImageQuality quality)
96 { 108 {
97 std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage)); 109 std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage));
98 tmp->sliceIndex_ = sliceIndex; 110 tmp->sliceIndex_ = sliceIndex;
99 tmp->slice_ = &slice; 111 tmp->slice_ = &slice;
112 tmp->quality_ = quality;
100 return tmp.release(); 113 return tmp.release();
101 } 114 }
102 }; 115 };
103 116
104 117
130 that_.ParseInstanceGeometry(operation->GetInstanceId(), 143 that_.ParseInstanceGeometry(operation->GetInstanceId(),
131 operation->GetFrame(), answer, answerSize); 144 operation->GetFrame(), answer, answerSize);
132 break; 145 break;
133 146
134 case Mode_LoadImage: 147 case Mode_LoadImage:
135 that_.ParseSliceImage(*operation, answer, answerSize); 148 switch (operation->GetQuality())
149 {
150 case SliceImageQuality_Full:
151 that_.ParseSliceImagePng(*operation, answer, answerSize);
152 break;
153
154 case SliceImageQuality_Jpeg50:
155 case SliceImageQuality_Jpeg90:
156 case SliceImageQuality_Jpeg95:
157 that_.ParseSliceImageJpeg(*operation, answer, answerSize);
158 break;
159
160 default:
161 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
162 }
163
136 break; 164 break;
137 165
138 default: 166 default:
139 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 167 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
140 } 168 }
153 that_.state_ = State_Error; 181 that_.state_ = State_Error;
154 break; 182 break;
155 183
156 case Mode_LoadImage: 184 case Mode_LoadImage:
157 that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(), 185 that_.userCallback_.NotifySliceImageError(that_, operation->GetSliceIndex(),
158 operation->GetSlice()); 186 operation->GetSlice(),
187 operation->GetQuality());
159 break; 188 break;
160 189
161 default: 190 default:
162 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); 191 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
163 } 192 }
164 } 193 }
165 }; 194 };
166 195
196
197
198 void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
199 Orthanc::ImageAccessor* image) const
200 {
201 if (image == NULL)
202 {
203 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer);
204 }
205 else
206 {
207 userCallback_.NotifySliceImageReady
208 (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
209 }
210 }
211
212
213 void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation) const
214 {
215 userCallback_.NotifySliceImageError
216 (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
217 }
218
167 219
168 void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer, 220 void OrthancSlicesLoader::ParseSeriesGeometry(const void* answer,
169 size_t size) 221 size_t size)
170 { 222 {
171 Json::Value series; 223 Json::Value series;
254 userCallback_.NotifyGeometryError(*this); 306 userCallback_.NotifyGeometryError(*this);
255 } 307 }
256 } 308 }
257 309
258 310
259 void OrthancSlicesLoader::ParseSliceImage(const Operation& operation, 311 void OrthancSlicesLoader::ParseSliceImagePng(const Operation& operation,
260 const void* answer, 312 const void* answer,
261 size_t size) 313 size_t size)
262 { 314 {
263 std::auto_ptr<Orthanc::PngReader> image(new Orthanc::PngReader); 315 std::auto_ptr<Orthanc::PngReader> image(new Orthanc::PngReader);
264 image->ReadFromMemory(answer, size); 316
265 317 bool ok = false;
266 bool ok = (image->GetWidth() == operation.GetSlice().GetWidth() || 318
267 image->GetHeight() == operation.GetSlice().GetHeight()); 319 try
320 {
321 image->ReadFromMemory(answer, size);
322 }
323 catch (Orthanc::OrthancException&)
324 {
325 NotifySliceImageError(operation);
326 return;
327 }
328
329 if (image->GetWidth() != operation.GetSlice().GetWidth() ||
330 image->GetHeight() != operation.GetSlice().GetHeight())
331 {
332 NotifySliceImageError(operation);
333 return;
334 }
268 335
269 if (ok && 336 if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
270 operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
271 Orthanc::PixelFormat_SignedGrayscale16) 337 Orthanc::PixelFormat_SignedGrayscale16)
272 { 338 {
273 if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16) 339 if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
274 { 340 {
275 image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); 341 image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
276 } 342 }
277 else 343 else
278 { 344 {
279 ok = false; 345 NotifySliceImageError(operation);
280 } 346 return;
281 } 347 }
282 348 }
283 if (ok) 349
284 { 350 NotifySliceImageSuccess(operation, image.release());
285 userCallback_.NotifySliceImageReady(*this, operation.GetSliceIndex(), 351 }
286 operation.GetSlice(), image.release()); 352
287 } 353
288 else 354 void OrthancSlicesLoader::ParseSliceImageJpeg(const Operation& operation,
289 { 355 const void* answer,
290 userCallback_.NotifySliceImageError(*this, operation.GetSliceIndex(), 356 size_t size)
291 operation.GetSlice()); 357 {
292 } 358 Json::Value encoded;
359 if (!MessagingToolbox::ParseJson(encoded, answer, size) ||
360 encoded.type() != Json::objectValue ||
361 !encoded.isMember("Orthanc") ||
362 encoded["Orthanc"].type() != Json::objectValue)
363 {
364 NotifySliceImageError(operation);
365 return;
366 }
367
368 Json::Value& info = encoded["Orthanc"];
369 if (!info.isMember("PixelData") ||
370 !info.isMember("Stretched") ||
371 !info.isMember("Compression") ||
372 info["Compression"].type() != Json::stringValue ||
373 info["PixelData"].type() != Json::stringValue ||
374 info["Stretched"].type() != Json::booleanValue ||
375 info["Compression"].asString() != "Jpeg")
376 {
377 NotifySliceImageError(operation);
378 return;
379 }
380
381 bool isSigned = false;
382 bool isStretched = info["Stretched"].asBool();
383
384 if (info.isMember("IsSigned"))
385 {
386 if (info["IsSigned"].type() != Json::booleanValue)
387 {
388 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
389 }
390 else
391 {
392 isSigned = info["IsSigned"].asBool();
393 }
394 }
395
396 std::string jpeg;
397 Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
398
399 std::auto_ptr<Orthanc::JpegReader> reader(new Orthanc::JpegReader);
400 try
401 {
402 reader->ReadFromMemory(jpeg);
403 }
404 catch (Orthanc::OrthancException&)
405 {
406 NotifySliceImageError(operation);
407 return;
408 }
409
410 Orthanc::PixelFormat expectedFormat =
411 operation.GetSlice().GetConverter().GetExpectedPixelFormat();
412
413 if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image
414 {
415 if (expectedFormat != Orthanc::PixelFormat_RGB24)
416 {
417 NotifySliceImageError(operation);
418 return;
419 }
420
421 if (isSigned || isStretched)
422 {
423 NotifySliceImageError(operation);
424 return;
425 }
426 else
427 {
428 NotifySliceImageSuccess(operation, reader.release());
429 return;
430 }
431 }
432
433 if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
434 {
435 NotifySliceImageError(operation);
436 return;
437 }
438
439 if (!isStretched)
440 {
441 if (expectedFormat != reader->GetFormat())
442 {
443 NotifySliceImageError(operation);
444 return;
445 }
446 else
447 {
448 NotifySliceImageSuccess(operation, reader.release());
449 return;
450 }
451 }
452
453 int32_t stretchLow = 0;
454 int32_t stretchHigh = 0;
455
456 if (!info.isMember("StretchLow") ||
457 !info.isMember("StretchHigh") ||
458 info["StretchLow"].type() != Json::intValue ||
459 info["StretchHigh"].type() != Json::intValue)
460 {
461 NotifySliceImageError(operation);
462 return;
463 }
464
465 stretchLow = info["StretchLow"].asInt();
466 stretchHigh = info["StretchHigh"].asInt();
467
468 if (stretchLow < -32768 ||
469 stretchHigh > 65535 ||
470 (stretchLow < 0 && stretchHigh > 32767))
471 {
472 // This range cannot be represented with a uint16_t or an int16_t
473 NotifySliceImageError(operation);
474 return;
475 }
476
477 // Decode a grayscale JPEG 8bpp image coming from the Web viewer
478 std::auto_ptr<Orthanc::ImageAccessor> image
479 (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
480
481 float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
482 float offset = static_cast<float>(stretchLow) / scaling;
483
484 Orthanc::ImageProcessing::Convert(*image, *reader);
485 Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling);
486
487 NotifySliceImageSuccess(operation, image.release());
293 } 488 }
294 489
295 490
296 OrthancSlicesLoader::OrthancSlicesLoader(ICallback& callback, 491 OrthancSlicesLoader::OrthancSlicesLoader(ICallback& callback,
297 IWebService& orthanc) : 492 IWebService& orthanc) :
373 568
374 return slices_.LookupSlice(index, plane); 569 return slices_.LookupSlice(index, plane);
375 } 570 }
376 571
377 572
378 void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index) 573 void OrthancSlicesLoader::ScheduleSliceImagePng(size_t index)
574 {
575 const Slice& slice = GetSlice(index);
576
577 std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
578 boost::lexical_cast<std::string>(slice.GetFrame()));
579
580 switch (slice.GetConverter().GetExpectedPixelFormat())
581 {
582 case Orthanc::PixelFormat_RGB24:
583 uri += "/preview";
584 break;
585
586 case Orthanc::PixelFormat_Grayscale16:
587 uri += "/image-uint16";
588 break;
589
590 case Orthanc::PixelFormat_SignedGrayscale16:
591 uri += "/image-int16";
592 break;
593
594 default:
595 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
596 }
597
598 orthanc_.ScheduleGetRequest(*webCallback_, uri,
599 Operation::DownloadSliceImage(index, slice, SliceImageQuality_Full));
600 }
601
602
603 void OrthancSlicesLoader::ScheduleSliceImageJpeg(size_t index,
604 SliceImageQuality quality)
605 {
606 unsigned int value;
607
608 switch (quality)
609 {
610 case SliceImageQuality_Jpeg50:
611 value = 50;
612 break;
613
614 case SliceImageQuality_Jpeg90:
615 value = 90;
616 break;
617
618 case SliceImageQuality_Jpeg95:
619 value = 95;
620 break;
621
622 default:
623 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
624 }
625
626 // This requires the official Web viewer plugin to be installed!
627 const Slice& slice = GetSlice(index);
628 std::string uri = ("web-viewer/instances/jpeg" +
629 boost::lexical_cast<std::string>(value) +
630 "-" + slice.GetOrthancInstanceId() + "_" +
631 boost::lexical_cast<std::string>(slice.GetFrame()));
632
633 orthanc_.ScheduleGetRequest(*webCallback_, uri,
634 Operation::DownloadSliceImage(index, slice, quality));
635 }
636
637
638 void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index,
639 SliceImageQuality quality)
379 { 640 {
380 if (state_ != State_GeometryReady) 641 if (state_ != State_GeometryReady)
381 { 642 {
382 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); 643 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
383 } 644 }
645
646 if (quality == SliceImageQuality_Full)
647 {
648 ScheduleSliceImagePng(index);
649 }
384 else 650 else
385 { 651 {
386 const Slice& slice = GetSlice(index); 652 ScheduleSliceImageJpeg(index, quality);
387
388 std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
389 boost::lexical_cast<std::string>(slice.GetFrame()));
390
391 switch (slice.GetConverter().GetExpectedPixelFormat())
392 {
393 case Orthanc::PixelFormat_RGB24:
394 uri += "/preview";
395 break;
396
397 case Orthanc::PixelFormat_Grayscale16:
398 uri += "/image-uint16";
399 break;
400
401 case Orthanc::PixelFormat_SignedGrayscale16:
402 uri += "/image-int16";
403 break;
404
405 default:
406 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
407 }
408
409 orthanc_.ScheduleGetRequest(*webCallback_, uri, Operation::DownloadSliceImage(index, slice));
410 } 653 }
411 } 654 }
412 } 655 }