Mercurial > hg > orthanc-stone
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 } |