Mercurial > hg > orthanc-stone
comparison Framework/Deprecated/Toolbox/OrthancSlicesLoader.cpp @ 754:92c400a09f1b
Merge from default
author | Benjamin Golinvaux <bgo@osimis.io> |
---|---|
date | Wed, 22 May 2019 16:13:46 +0200 |
parents | be9c1530d40a |
children | 4fe4b221a31f |
comparison
equal
deleted
inserted
replaced
753:a386bbc955dc | 754:92c400a09f1b |
---|---|
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 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 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 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_ = 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, 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, SliceImageQuality_FullPam)); | |
818 } | |
819 | |
820 | |
821 | |
822 void OrthancSlicesLoader::ScheduleSliceImageJpeg(const Slice& slice, | |
823 size_t index, | |
824 SliceImageQuality quality) | |
825 { | |
826 unsigned int value; | |
827 | |
828 switch (quality) | |
829 { | |
830 case SliceImageQuality_Jpeg50: | |
831 value = 50; | |
832 break; | |
833 | |
834 case SliceImageQuality_Jpeg90: | |
835 value = 90; | |
836 break; | |
837 | |
838 case 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 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 SliceImageQuality_FullPng: | |
880 ScheduleSliceImagePng(slice, index); | |
881 break; | |
882 case 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 } |