comparison Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp @ 732:c35e98d22764

move Deprecated classes to a separate folder
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 21 May 2019 14:27:35 +0200
parents Framework/Toolbox/OrthancSlicesLoader.cpp@4f2416d519b4
children be9c1530d40a
comparison
equal deleted inserted replaced
729:529189f399ec 732:c35e98d22764
1 /**
2 * Stone of Orthanc
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 * Copyright (C) 2017-2019 Osimis S.A., Belgium
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 "OrthancSlicesLoader.h"
23
24 #include "../../Toolbox/MessagingToolbox.h"
25
26 #include <Core/Compression/GzipCompressor.h>
27 #include <Core/Endianness.h>
28 #include <Core/Images/Image.h>
29 #include <Core/Images/ImageProcessing.h>
30 #include <Core/Images/JpegReader.h>
31 #include <Core/Images/PngReader.h>
32 #include <Core/Images/PamReader.h>
33 #include <Core/Logging.h>
34 #include <Core/OrthancException.h>
35 #include <Core/Toolbox.h>
36 #include <Plugins/Samples/Common/FullOrthancDataset.h>
37
38 #include <boost/lexical_cast.hpp>
39
40
41
42 /**
43 * TODO This is a SLOW implementation of base64 decoding, because
44 * "Orthanc::Toolbox::DecodeBase64()" does not work properly with
45 * WASM. UNDERSTAND WHY.
46 * https://stackoverflow.com/a/34571089/881731
47 **/
48 static std::string base64_decode(const std::string &in)
49 {
50 std::string out;
51
52 std::vector<int> T(256,-1);
53 for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
54
55 int val=0, valb=-8;
56 for (size_t i = 0; i < in.size(); i++) {
57 unsigned char c = in[i];
58 if (T[c] == -1) break;
59 val = (val<<6) + T[c];
60 valb += 6;
61 if (valb>=0) {
62 out.push_back(char((val>>valb)&0xFF));
63 valb-=8;
64 }
65 }
66 return out;
67 }
68
69
70
71 namespace Deprecated
72 {
73 class OrthancSlicesLoader::Operation : public Orthanc::IDynamicObject
74 {
75 private:
76 Mode mode_;
77 unsigned int frame_;
78 unsigned int sliceIndex_;
79 const Slice* slice_;
80 std::string instanceId_;
81 OrthancStone::SliceImageQuality quality_;
82
83 Operation(Mode mode) :
84 mode_(mode)
85 {
86 }
87
88 public:
89 Mode GetMode() const
90 {
91 return mode_;
92 }
93
94 OrthancStone::SliceImageQuality GetQuality() const
95 {
96 assert(mode_ == Mode_LoadImage ||
97 mode_ == Mode_LoadRawImage);
98 return quality_;
99 }
100
101 unsigned int GetSliceIndex() const
102 {
103 assert(mode_ == Mode_LoadImage ||
104 mode_ == Mode_LoadRawImage);
105 return sliceIndex_;
106 }
107
108 const Slice& GetSlice() const
109 {
110 assert(mode_ == Mode_LoadImage ||
111 mode_ == Mode_LoadRawImage);
112 assert(slice_ != NULL);
113 return *slice_;
114 }
115
116 unsigned int GetFrame() const
117 {
118 assert(mode_ == Mode_FrameGeometry);
119 return frame_;
120 }
121
122 const std::string& GetInstanceId() const
123 {
124 assert(mode_ == Mode_FrameGeometry ||
125 mode_ == Mode_InstanceGeometry);
126 return instanceId_;
127 }
128
129 static Operation* DownloadInstanceGeometry(const std::string& instanceId)
130 {
131 std::auto_ptr<Operation> operation(new Operation(Mode_InstanceGeometry));
132 operation->instanceId_ = instanceId;
133 return operation.release();
134 }
135
136 static Operation* DownloadFrameGeometry(const std::string& instanceId,
137 unsigned int frame)
138 {
139 std::auto_ptr<Operation> operation(new Operation(Mode_FrameGeometry));
140 operation->instanceId_ = instanceId;
141 operation->frame_ = frame;
142 return operation.release();
143 }
144
145 static Operation* DownloadSliceImage(unsigned int sliceIndex,
146 const Slice& slice,
147 OrthancStone::SliceImageQuality quality)
148 {
149 std::auto_ptr<Operation> tmp(new Operation(Mode_LoadImage));
150 tmp->sliceIndex_ = sliceIndex;
151 tmp->slice_ = &slice;
152 tmp->quality_ = quality;
153 return tmp.release();
154 }
155
156 static Operation* DownloadSliceRawImage(unsigned int sliceIndex,
157 const Slice& slice)
158 {
159 std::auto_ptr<Operation> tmp(new Operation(Mode_LoadRawImage));
160 tmp->sliceIndex_ = sliceIndex;
161 tmp->slice_ = &slice;
162 tmp->quality_ = OrthancStone::SliceImageQuality_InternalRaw;
163 return tmp.release();
164 }
165
166 static Operation* DownloadDicomFile(const Slice& slice)
167 {
168 std::auto_ptr<Operation> tmp(new Operation(Mode_LoadDicomFile));
169 tmp->slice_ = &slice;
170 return tmp.release();
171 }
172
173 };
174
175 void OrthancSlicesLoader::NotifySliceImageSuccess(const Operation& operation,
176 const Orthanc::ImageAccessor& image)
177 {
178 OrthancSlicesLoader::SliceImageReadyMessage msg
179 (*this, operation.GetSliceIndex(), operation.GetSlice(), image, operation.GetQuality());
180 BroadcastMessage(msg);
181 }
182
183
184 void OrthancSlicesLoader::NotifySliceImageError(const Operation& operation)
185 {
186 OrthancSlicesLoader::SliceImageErrorMessage msg
187 (*this, operation.GetSliceIndex(), operation.GetSlice(), operation.GetQuality());
188 BroadcastMessage(msg);
189 }
190
191
192 void OrthancSlicesLoader::SortAndFinalizeSlices()
193 {
194 bool ok = slices_.Sort();
195
196 state_ = State_GeometryReady;
197
198 if (ok)
199 {
200 LOG(INFO) << "Loaded a series with " << slices_.GetSlicesCount() << " slice(s)";
201 BroadcastMessage(SliceGeometryReadyMessage(*this));
202 }
203 else
204 {
205 LOG(ERROR) << "This series is empty";
206 BroadcastMessage(SliceGeometryErrorMessage(*this));
207 }
208 }
209
210 void OrthancSlicesLoader::OnGeometryError(const IWebService::HttpRequestErrorMessage& message)
211 {
212 BroadcastMessage(SliceGeometryErrorMessage(*this));
213 state_ = State_Error;
214 }
215
216 void OrthancSlicesLoader::OnSliceImageError(const IWebService::HttpRequestErrorMessage& message)
217 {
218 NotifySliceImageError(dynamic_cast<const Operation&>(message.GetPayload()));
219 state_ = State_Error;
220 }
221
222 void OrthancSlicesLoader::ParseSeriesGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
223 {
224 const Json::Value& series = message.GetJson();
225 Json::Value::Members instances = series.getMemberNames();
226
227 slices_.Reserve(instances.size());
228
229 for (size_t i = 0; i < instances.size(); i++)
230 {
231 OrthancPlugins::FullOrthancDataset dataset(series[instances[i]]);
232
233 Orthanc::DicomMap dicom;
234 OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset);
235
236 unsigned int frames;
237 if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
238 {
239 frames = 1;
240 }
241
242 for (unsigned int frame = 0; frame < frames; frame++)
243 {
244 std::auto_ptr<Slice> slice(new Slice);
245 if (slice->ParseOrthancFrame(dicom, instances[i], frame))
246 {
247 OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry();
248 slices_.AddSlice(geometry, slice.release());
249 }
250 else
251 {
252 LOG(WARNING) << "Skipping invalid frame " << frame << " within instance " << instances[i];
253 }
254 }
255 }
256
257 SortAndFinalizeSlices();
258 }
259
260 void OrthancSlicesLoader::ParseInstanceGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
261 {
262 const Json::Value& tags = message.GetJson();
263 const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId();
264
265 OrthancPlugins::FullOrthancDataset dataset(tags);
266
267 Orthanc::DicomMap dicom;
268 OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset);
269
270 unsigned int frames;
271 if (!dicom.ParseUnsignedInteger32(frames, Orthanc::DICOM_TAG_NUMBER_OF_FRAMES))
272 {
273 frames = 1;
274 }
275
276 LOG(INFO) << "Instance " << instanceId << " contains " << frames << " frame(s)";
277
278 for (unsigned int frame = 0; frame < frames; frame++)
279 {
280 std::auto_ptr<Slice> slice(new Slice);
281 if (slice->ParseOrthancFrame(dicom, instanceId, frame))
282 {
283 OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry();
284 slices_.AddSlice(geometry, slice.release());
285 }
286 else
287 {
288 LOG(WARNING) << "Skipping invalid multi-frame instance " << instanceId;
289 BroadcastMessage(SliceGeometryErrorMessage(*this));
290 return;
291 }
292 }
293
294 SortAndFinalizeSlices();
295 }
296
297
298 void OrthancSlicesLoader::ParseFrameGeometry(const OrthancApiClient::JsonResponseReadyMessage& message)
299 {
300 const Json::Value& tags = message.GetJson();
301 const std::string& instanceId = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetInstanceId();
302 unsigned int frame = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload()).GetFrame();
303
304 OrthancPlugins::FullOrthancDataset dataset(tags);
305
306 state_ = State_GeometryReady;
307
308 Orthanc::DicomMap dicom;
309 OrthancStone::MessagingToolbox::ConvertDataset(dicom, dataset);
310
311 std::auto_ptr<Slice> slice(new Slice);
312 if (slice->ParseOrthancFrame(dicom, instanceId, frame))
313 {
314 LOG(INFO) << "Loaded instance geometry " << instanceId;
315
316 OrthancStone::CoordinateSystem3D geometry = slice->GetGeometry();
317 slices_.AddSlice(geometry, slice.release());
318
319 BroadcastMessage(SliceGeometryReadyMessage(*this));
320 }
321 else
322 {
323 LOG(WARNING) << "Skipping invalid instance " << instanceId;
324 BroadcastMessage(SliceGeometryErrorMessage(*this));
325 }
326 }
327
328
329 void OrthancSlicesLoader::ParseSliceImagePng(const OrthancApiClient::BinaryResponseReadyMessage& message)
330 {
331 const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
332 std::auto_ptr<Orthanc::ImageAccessor> image;
333
334 try
335 {
336 image.reset(new Orthanc::PngReader);
337 dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize());
338 }
339 catch (Orthanc::OrthancException&)
340 {
341 NotifySliceImageError(operation);
342 return;
343 }
344
345 if (image->GetWidth() != operation.GetSlice().GetWidth() ||
346 image->GetHeight() != operation.GetSlice().GetHeight())
347 {
348 NotifySliceImageError(operation);
349 return;
350 }
351
352 if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
353 Orthanc::PixelFormat_SignedGrayscale16)
354 {
355 if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
356 {
357 image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
358 }
359 else
360 {
361 NotifySliceImageError(operation);
362 return;
363 }
364 }
365
366 NotifySliceImageSuccess(operation, *image);
367 }
368
369 void OrthancSlicesLoader::ParseSliceImagePam(const OrthancApiClient::BinaryResponseReadyMessage& message)
370 {
371 const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
372 std::auto_ptr<Orthanc::ImageAccessor> image;
373
374 try
375 {
376 image.reset(new Orthanc::PamReader);
377 dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(message.GetAnswer(), message.GetAnswerSize());
378 }
379 catch (Orthanc::OrthancException&)
380 {
381 NotifySliceImageError(operation);
382 return;
383 }
384
385 if (image->GetWidth() != operation.GetSlice().GetWidth() ||
386 image->GetHeight() != operation.GetSlice().GetHeight())
387 {
388 NotifySliceImageError(operation);
389 return;
390 }
391
392 if (operation.GetSlice().GetConverter().GetExpectedPixelFormat() ==
393 Orthanc::PixelFormat_SignedGrayscale16)
394 {
395 if (image->GetFormat() == Orthanc::PixelFormat_Grayscale16)
396 {
397 image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
398 }
399 else
400 {
401 NotifySliceImageError(operation);
402 return;
403 }
404 }
405
406 NotifySliceImageSuccess(operation, *image);
407 }
408
409
410 void OrthancSlicesLoader::ParseSliceImageJpeg(const OrthancApiClient::JsonResponseReadyMessage& message)
411 {
412 const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
413
414 const Json::Value& encoded = message.GetJson();
415 if (encoded.type() != Json::objectValue ||
416 !encoded.isMember("Orthanc") ||
417 encoded["Orthanc"].type() != Json::objectValue)
418 {
419 NotifySliceImageError(operation);
420 return;
421 }
422
423 const Json::Value& info = encoded["Orthanc"];
424 if (!info.isMember("PixelData") ||
425 !info.isMember("Stretched") ||
426 !info.isMember("Compression") ||
427 info["Compression"].type() != Json::stringValue ||
428 info["PixelData"].type() != Json::stringValue ||
429 info["Stretched"].type() != Json::booleanValue ||
430 info["Compression"].asString() != "Jpeg")
431 {
432 NotifySliceImageError(operation);
433 return;
434 }
435
436 bool isSigned = false;
437 bool isStretched = info["Stretched"].asBool();
438
439 if (info.isMember("IsSigned"))
440 {
441 if (info["IsSigned"].type() != Json::booleanValue)
442 {
443 NotifySliceImageError(operation);
444 return;
445 }
446 else
447 {
448 isSigned = info["IsSigned"].asBool();
449 }
450 }
451
452 std::auto_ptr<Orthanc::ImageAccessor> reader;
453
454 {
455 std::string jpeg;
456 //Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString());
457 jpeg = base64_decode(info["PixelData"].asString());
458
459 try
460 {
461 reader.reset(new Orthanc::JpegReader);
462 dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg);
463 }
464 catch (Orthanc::OrthancException&)
465 {
466 NotifySliceImageError(operation);
467 return;
468 }
469 }
470
471 Orthanc::PixelFormat expectedFormat =
472 operation.GetSlice().GetConverter().GetExpectedPixelFormat();
473
474 if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image
475 {
476 if (expectedFormat != Orthanc::PixelFormat_RGB24)
477 {
478 NotifySliceImageError(operation);
479 return;
480 }
481
482 if (isSigned || isStretched)
483 {
484 NotifySliceImageError(operation);
485 return;
486 }
487 else
488 {
489 NotifySliceImageSuccess(operation, *reader);
490 return;
491 }
492 }
493
494 if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8)
495 {
496 NotifySliceImageError(operation);
497 return;
498 }
499
500 if (!isStretched)
501 {
502 if (expectedFormat != reader->GetFormat())
503 {
504 NotifySliceImageError(operation);
505 return;
506 }
507 else
508 {
509 NotifySliceImageSuccess(operation, *reader);
510 return;
511 }
512 }
513
514 int32_t stretchLow = 0;
515 int32_t stretchHigh = 0;
516
517 if (!info.isMember("StretchLow") ||
518 !info.isMember("StretchHigh") ||
519 info["StretchLow"].type() != Json::intValue ||
520 info["StretchHigh"].type() != Json::intValue)
521 {
522 NotifySliceImageError(operation);
523 return;
524 }
525
526 stretchLow = info["StretchLow"].asInt();
527 stretchHigh = info["StretchHigh"].asInt();
528
529 if (stretchLow < -32768 ||
530 stretchHigh > 65535 ||
531 (stretchLow < 0 && stretchHigh > 32767))
532 {
533 // This range cannot be represented with a uint16_t or an int16_t
534 NotifySliceImageError(operation);
535 return;
536 }
537
538 // Decode a grayscale JPEG 8bpp image coming from the Web viewer
539 std::auto_ptr<Orthanc::ImageAccessor> image
540 (new Orthanc::Image(expectedFormat, reader->GetWidth(), reader->GetHeight(), false));
541
542 Orthanc::ImageProcessing::Convert(*image, *reader);
543 reader.reset();
544
545 float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f;
546
547 if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling))
548 {
549 float offset = static_cast<float>(stretchLow) / scaling;
550 Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true);
551 }
552
553 NotifySliceImageSuccess(operation, *image);
554 }
555
556
557 class StringImage : public Orthanc::ImageAccessor
558 {
559 private:
560 std::string buffer_;
561
562 public:
563 StringImage(Orthanc::PixelFormat format,
564 unsigned int width,
565 unsigned int height,
566 std::string& buffer)
567 {
568 if (buffer.size() != Orthanc::GetBytesPerPixel(format) * width * height)
569 {
570 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
571 }
572
573 buffer_.swap(buffer); // The source buffer is now empty
574
575 void* data = (buffer_.empty() ? NULL : &buffer_[0]);
576
577 AssignWritable(format, width, height,
578 Orthanc::GetBytesPerPixel(format) * width, data);
579 }
580 };
581
582 void OrthancSlicesLoader::ParseSliceRawImage(const OrthancApiClient::BinaryResponseReadyMessage& message)
583 {
584 const Operation& operation = dynamic_cast<const OrthancSlicesLoader::Operation&>(message.GetPayload());
585 Orthanc::GzipCompressor compressor;
586
587 std::string raw;
588 compressor.Uncompress(raw, message.GetAnswer(), message.GetAnswerSize());
589
590 const Orthanc::DicomImageInformation& info = operation.GetSlice().GetImageInformation();
591
592 if (info.GetBitsAllocated() == 32 &&
593 info.GetBitsStored() == 32 &&
594 info.GetHighBit() == 31 &&
595 info.GetChannelCount() == 1 &&
596 !info.IsSigned() &&
597 info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 &&
598 raw.size() == info.GetWidth() * info.GetHeight() * 4)
599 {
600 // This is the case of RT-DOSE (uint32_t values)
601
602 std::auto_ptr<Orthanc::ImageAccessor> image
603 (new StringImage(Orthanc::PixelFormat_Grayscale32, info.GetWidth(),
604 info.GetHeight(), raw));
605
606 // TODO - Only for big endian
607 for (unsigned int y = 0; y < image->GetHeight(); y++)
608 {
609 uint32_t *p = reinterpret_cast<uint32_t*>(image->GetRow(y));
610 for (unsigned int x = 0; x < image->GetWidth(); x++, p++)
611 {
612 *p = le32toh(*p);
613 }
614 }
615
616 NotifySliceImageSuccess(operation, *image);
617 }
618 else if (info.GetBitsAllocated() == 16 &&
619 info.GetBitsStored() == 16 &&
620 info.GetHighBit() == 15 &&
621 info.GetChannelCount() == 1 &&
622 !info.IsSigned() &&
623 info.GetPhotometricInterpretation() == Orthanc::PhotometricInterpretation_Monochrome2 &&
624 raw.size() == info.GetWidth() * info.GetHeight() * 2)
625 {
626 std::auto_ptr<Orthanc::ImageAccessor> image
627 (new StringImage(Orthanc::PixelFormat_Grayscale16, info.GetWidth(),
628 info.GetHeight(), raw));
629
630 // TODO - Big endian ?
631
632 NotifySliceImageSuccess(operation, *image);
633 }
634 else
635 {
636 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
637 }
638
639 }
640
641
642 OrthancSlicesLoader::OrthancSlicesLoader(OrthancStone::MessageBroker& broker,
643 OrthancApiClient& orthanc) :
644 OrthancStone::IObservable(broker),
645 OrthancStone::IObserver(broker),
646 orthanc_(orthanc),
647 state_(State_Initialization)
648 {
649 }
650
651
652 void OrthancSlicesLoader::ScheduleLoadSeries(const std::string& seriesId)
653 {
654 if (state_ != State_Initialization)
655 {
656 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
657 }
658 else
659 {
660 state_ = State_LoadingGeometry;
661 orthanc_.GetJsonAsync("/series/" + seriesId + "/instances-tags",
662 new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseSeriesGeometry),
663 new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
664 NULL);
665 }
666 }
667
668 void OrthancSlicesLoader::ScheduleLoadInstance(const std::string& instanceId)
669 {
670 if (state_ != State_Initialization)
671 {
672 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
673 }
674 else
675 {
676 state_ = State_LoadingGeometry;
677
678 // Tag "3004-000c" is "Grid Frame Offset Vector", which is
679 // mandatory to read RT DOSE, but is too long to be returned by default
680 orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags?ignore-length=3004-000c",
681 new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseInstanceGeometry),
682 new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
683 Operation::DownloadInstanceGeometry(instanceId));
684 }
685 }
686
687
688 void OrthancSlicesLoader::ScheduleLoadFrame(const std::string& instanceId,
689 unsigned int frame)
690 {
691 if (state_ != State_Initialization)
692 {
693 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
694 }
695 else
696 {
697 state_ = State_LoadingGeometry;
698
699 orthanc_.GetJsonAsync("/instances/" + instanceId + "/tags",
700 new OrthancStone::Callable<OrthancSlicesLoader, OrthancApiClient::JsonResponseReadyMessage>(*this, &OrthancSlicesLoader::ParseFrameGeometry),
701 new OrthancStone::Callable<OrthancSlicesLoader, IWebService::HttpRequestErrorMessage>(*this, &OrthancSlicesLoader::OnGeometryError),
702 Operation::DownloadFrameGeometry(instanceId, frame));
703 }
704 }
705
706
707 bool OrthancSlicesLoader::IsGeometryReady() const
708 {
709 return state_ == State_GeometryReady;
710 }
711
712
713 size_t OrthancSlicesLoader::GetSlicesCount() const
714 {
715 if (state_ != State_GeometryReady)
716 {
717 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
718 }
719
720 return slices_.GetSlicesCount();
721 }
722
723
724 const Slice& OrthancSlicesLoader::GetSlice(size_t index) const
725 {
726 if (state_ != State_GeometryReady)
727 {
728 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
729 }
730
731 return dynamic_cast<const Slice&>(slices_.GetSlicePayload(index));
732 }
733
734
735 bool OrthancSlicesLoader::LookupSlice(size_t& index,
736 const OrthancStone::CoordinateSystem3D& plane) const
737 {
738 if (state_ != State_GeometryReady)
739 {
740 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
741 }
742
743 double distance;
744 return (slices_.LookupClosestSlice(index, distance, plane) &&
745 distance <= GetSlice(index).GetThickness() / 2.0);
746 }
747
748
749 void OrthancSlicesLoader::ScheduleSliceImagePng(const Slice& slice,
750 size_t index)
751 {
752 std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
753 boost::lexical_cast<std::string>(slice.GetFrame()));
754
755 switch (slice.GetConverter().GetExpectedPixelFormat())
756 {
757 case Orthanc::PixelFormat_RGB24:
758 uri += "/preview";
759 break;
760
761 case Orthanc::PixelFormat_Grayscale16:
762 uri += "/image-uint16";
763 break;
764
765 case Orthanc::PixelFormat_SignedGrayscale16:
766 uri += "/image-int16";
767 break;
768
769 default:
770 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
771 }
772
773 orthanc_.GetBinaryAsync(uri, "image/png",
774 new OrthancStone::Callable<OrthancSlicesLoader,
775 OrthancApiClient::BinaryResponseReadyMessage>
776 (*this, &OrthancSlicesLoader::ParseSliceImagePng),
777 new OrthancStone::Callable<OrthancSlicesLoader,
778 IWebService::HttpRequestErrorMessage>
779 (*this, &OrthancSlicesLoader::OnSliceImageError),
780 Operation::DownloadSliceImage(
781 static_cast<unsigned int>(index), slice, OrthancStone::SliceImageQuality_FullPng));
782 }
783
784 void OrthancSlicesLoader::ScheduleSliceImagePam(const Slice& slice,
785 size_t index)
786 {
787 std::string uri =
788 ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
789 boost::lexical_cast<std::string>(slice.GetFrame()));
790
791 switch (slice.GetConverter().GetExpectedPixelFormat())
792 {
793 case Orthanc::PixelFormat_RGB24:
794 uri += "/preview";
795 break;
796
797 case Orthanc::PixelFormat_Grayscale16:
798 uri += "/image-uint16";
799 break;
800
801 case Orthanc::PixelFormat_SignedGrayscale16:
802 uri += "/image-int16";
803 break;
804
805 default:
806 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
807 }
808
809 orthanc_.GetBinaryAsync(uri, "image/x-portable-arbitrarymap",
810 new OrthancStone::Callable<OrthancSlicesLoader,
811 OrthancApiClient::BinaryResponseReadyMessage>
812 (*this, &OrthancSlicesLoader::ParseSliceImagePam),
813 new OrthancStone::Callable<OrthancSlicesLoader,
814 IWebService::HttpRequestErrorMessage>
815 (*this, &OrthancSlicesLoader::OnSliceImageError),
816 Operation::DownloadSliceImage(static_cast<unsigned int>(index),
817 slice, OrthancStone::SliceImageQuality_FullPam));
818 }
819
820
821
822 void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice,
823 size_t index,
824 OrthancStone::SliceImageQuality quality)
825 {
826 unsigned int value;
827
828 switch (quality)
829 {
830 case OrthancStone::SliceImageQuality_Jpeg50:
831 value = 50;
832 break;
833
834 case OrthancStone::SliceImageQuality_Jpeg90:
835 value = 90;
836 break;
837
838 case OrthancStone::SliceImageQuality_Jpeg95:
839 value = 95;
840 break;
841
842 default:
843 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
844 }
845
846 // This requires the official Web viewer plugin to be installed!
847 std::string uri = ("/web-viewer/instances/jpeg" +
848 boost::lexical_cast<std::string>(value) +
849 "-" + slice.GetOrthancInstanceId() + "_" +
850 boost::lexical_cast<std::string>(slice.GetFrame()));
851
852 orthanc_.GetJsonAsync(uri,
853 new OrthancStone::Callable<OrthancSlicesLoader,
854 OrthancApiClient::JsonResponseReadyMessage>
855 (*this, &OrthancSlicesLoader::ParseSliceImageJpeg),
856 new OrthancStone::Callable<OrthancSlicesLoader,
857 IWebService::HttpRequestErrorMessage>
858 (*this, &OrthancSlicesLoader::OnSliceImageError),
859 Operation::DownloadSliceImage(
860 static_cast<unsigned int>(index), slice, quality));
861 }
862
863
864
865 void OrthancSlicesLoader::ScheduleLoadSliceImage(size_t index,
866 OrthancStone::SliceImageQuality quality)
867 {
868 if (state_ != State_GeometryReady)
869 {
870 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
871 }
872
873 const Slice& slice = GetSlice(index);
874
875 if (slice.HasOrthancDecoding())
876 {
877 switch (quality)
878 {
879 case OrthancStone::SliceImageQuality_FullPng:
880 ScheduleSliceImagePng(slice, index);
881 break;
882 case OrthancStone::SliceImageQuality_FullPam:
883 ScheduleSliceImagePam(slice, index);
884 break;
885 default:
886 ScheduleSliceImageJpeg(slice, index, quality);
887 }
888 }
889 else
890 {
891 std::string uri = ("/instances/" + slice.GetOrthancInstanceId() + "/frames/" +
892 boost::lexical_cast<std::string>(slice.GetFrame()) + "/raw.gz");
893 orthanc_.GetBinaryAsync(uri, IWebService::HttpHeaders(),
894 new OrthancStone::Callable<OrthancSlicesLoader,
895 OrthancApiClient::BinaryResponseReadyMessage>
896 (*this, &OrthancSlicesLoader::ParseSliceRawImage),
897 new OrthancStone::Callable<OrthancSlicesLoader,
898 IWebService::HttpRequestErrorMessage>
899 (*this, &OrthancSlicesLoader::OnSliceImageError),
900 Operation::DownloadSliceRawImage(
901 static_cast<unsigned int>(index), slice));
902 }
903 }
904 }