Mercurial > hg > orthanc-stone
comparison Samples/Sdl/Loader.cpp @ 860:238693c3bc51 am-dev
merge default -> am-dev
author | Alain Mazy <alain@mazy.be> |
---|---|
date | Mon, 24 Jun 2019 14:35:00 +0200 |
parents | 47fc7919977d |
children | 2d8ab34c8c91 |
comparison
equal
deleted
inserted
replaced
856:a6e17a5a39e7 | 860:238693c3bc51 |
---|---|
16 * | 16 * |
17 * You should have received a copy of the GNU Affero General Public License | 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/>. | 18 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 **/ | 19 **/ |
20 | 20 |
21 // From Stone | 21 |
22 #include "../../Framework/Loaders/BasicFetchingItemsSorter.h" | 22 #include "../../Framework/Loaders/DicomStructureSetLoader.h" |
23 #include "../../Framework/Loaders/BasicFetchingStrategy.h" | 23 #include "../../Framework/Loaders/OrthancMultiframeVolumeLoader.h" |
24 #include "../../Framework/Messages/ICallable.h" | 24 #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" |
25 #include "../../Framework/Messages/IMessage.h" | 25 #include "../../Framework/Oracle/SleepOracleCommand.h" |
26 #include "../../Framework/Messages/IObservable.h" | 26 #include "../../Framework/Oracle/ThreadedOracle.h" |
27 #include "../../Framework/Messages/MessageBroker.h" | 27 #include "../../Framework/Scene2D/CairoCompositor.h" |
28 #include "../../Framework/Scene2D/ColorTextureSceneLayer.h" | 28 #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" |
29 #include "../../Framework/Scene2D/FloatTextureSceneLayer.h" | 29 #include "../../Framework/Scene2D/LookupTableStyleConfigurator.h" |
30 #include "../../Framework/Scene2D/Scene2D.h" | |
31 #include "../../Framework/StoneInitialization.h" | 30 #include "../../Framework/StoneInitialization.h" |
32 #include "../../Framework/Toolbox/GeometryToolbox.h" | 31 #include "../../Framework/Volumes/VolumeSceneLayerSource.h" |
33 #include "../../Framework/Toolbox/SlicesSorter.h" | 32 #include "../../Framework/Volumes/DicomVolumeImageMPRSlicer.h" |
34 #include "../../Framework/Volumes/ImageBuffer3D.h" | 33 #include "../../Framework/Volumes/DicomVolumeImageReslicer.h" |
35 | 34 |
36 // From Orthanc framework | 35 // From Orthanc framework |
37 #include <Core/Compression/GzipCompressor.h> | |
38 #include <Core/Compression/ZlibCompressor.h> | |
39 #include <Core/DicomFormat/DicomArray.h> | |
40 #include <Core/DicomFormat/DicomImageInformation.h> | |
41 #include <Core/HttpClient.h> | |
42 #include <Core/IDynamicObject.h> | |
43 #include <Core/Images/Image.h> | |
44 #include <Core/Images/ImageProcessing.h> | 36 #include <Core/Images/ImageProcessing.h> |
45 #include <Core/Images/JpegReader.h> | |
46 #include <Core/Images/PamReader.h> | |
47 #include <Core/Images/PngReader.h> | |
48 #include <Core/Images/PngWriter.h> | 37 #include <Core/Images/PngWriter.h> |
49 #include <Core/Logging.h> | 38 #include <Core/Logging.h> |
50 #include <Core/MultiThreading/SharedMessageQueue.h> | |
51 #include <Core/OrthancException.h> | 39 #include <Core/OrthancException.h> |
52 #include <Core/SystemToolbox.h> | 40 #include <Core/SystemToolbox.h> |
53 #include <Core/Toolbox.h> | 41 |
54 | 42 |
55 #include <json/reader.h> | 43 namespace OrthancStone |
56 #include <json/value.h> | |
57 #include <json/writer.h> | |
58 | |
59 #include <list> | |
60 #include <stdio.h> | |
61 | |
62 | |
63 | |
64 namespace Refactoring | |
65 { | 44 { |
66 class IOracleCommand : public boost::noncopyable | 45 class NativeApplicationContext : public IMessageEmitter |
67 { | |
68 public: | |
69 enum Type | |
70 { | |
71 Type_OrthancRestApi, | |
72 Type_GetOrthancImage, | |
73 Type_GetOrthancWebViewerJpeg | |
74 }; | |
75 | |
76 virtual ~IOracleCommand() | |
77 { | |
78 } | |
79 | |
80 virtual Type GetType() const = 0; | |
81 }; | |
82 | |
83 | |
84 class IMessageEmitter : public boost::noncopyable | |
85 { | |
86 public: | |
87 virtual ~IMessageEmitter() | |
88 { | |
89 } | |
90 | |
91 virtual void EmitMessage(const OrthancStone::IObserver& observer, | |
92 const OrthancStone::IMessage& message) = 0; | |
93 }; | |
94 | |
95 | |
96 class IOracle : public boost::noncopyable | |
97 { | |
98 public: | |
99 virtual ~IOracle() | |
100 { | |
101 } | |
102 | |
103 virtual void Schedule(const OrthancStone::IObserver& receiver, | |
104 IOracleCommand* command) = 0; // Takes ownership | |
105 }; | |
106 | |
107 | |
108 | |
109 class IVolumeSlicer : public boost::noncopyable | |
110 { | |
111 public: | |
112 virtual ~IVolumeSlicer() | |
113 { | |
114 } | |
115 | |
116 virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) = 0; | |
117 }; | |
118 | |
119 | |
120 | |
121 class OracleCommandWithPayload : public IOracleCommand | |
122 { | 46 { |
123 private: | 47 private: |
124 std::auto_ptr<Orthanc::IDynamicObject> payload_; | 48 boost::shared_mutex mutex_; |
125 | 49 MessageBroker broker_; |
126 public: | 50 IObservable oracleObservable_; |
127 void SetPayload(Orthanc::IDynamicObject* payload) | |
128 { | |
129 if (payload == NULL) | |
130 { | |
131 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
132 } | |
133 else | |
134 { | |
135 payload_.reset(payload); | |
136 } | |
137 } | |
138 | |
139 bool HasPayload() const | |
140 { | |
141 return (payload_.get() != NULL); | |
142 } | |
143 | |
144 const Orthanc::IDynamicObject& GetPayload() const | |
145 { | |
146 if (HasPayload()) | |
147 { | |
148 return *payload_; | |
149 } | |
150 else | |
151 { | |
152 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
153 } | |
154 } | |
155 }; | |
156 | |
157 | |
158 | |
159 class OracleCommandExceptionMessage : public OrthancStone::IMessage | |
160 { | |
161 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); | |
162 | |
163 private: | |
164 const IOracleCommand& command_; | |
165 Orthanc::OrthancException exception_; | |
166 | |
167 public: | |
168 OracleCommandExceptionMessage(const IOracleCommand& command, | |
169 const Orthanc::OrthancException& exception) : | |
170 command_(command), | |
171 exception_(exception) | |
172 { | |
173 } | |
174 | |
175 OracleCommandExceptionMessage(const IOracleCommand& command, | |
176 const Orthanc::ErrorCode& error) : | |
177 command_(command), | |
178 exception_(error) | |
179 { | |
180 } | |
181 | |
182 const IOracleCommand& GetCommand() const | |
183 { | |
184 return command_; | |
185 } | |
186 | |
187 const Orthanc::OrthancException& GetException() const | |
188 { | |
189 return exception_; | |
190 } | |
191 }; | |
192 | |
193 | |
194 typedef std::map<std::string, std::string> HttpHeaders; | |
195 | |
196 class OrthancRestApiCommand : public OracleCommandWithPayload | |
197 { | |
198 public: | |
199 class SuccessMessage : public OrthancStone::OriginMessage<OrthancRestApiCommand> | |
200 { | |
201 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); | |
202 | |
203 private: | |
204 HttpHeaders headers_; | |
205 std::string answer_; | |
206 | |
207 public: | |
208 SuccessMessage(const OrthancRestApiCommand& command, | |
209 const HttpHeaders& answerHeaders, | |
210 std::string& answer /* will be swapped to avoid a memcpy() */) : | |
211 OriginMessage(command), | |
212 headers_(answerHeaders), | |
213 answer_(answer) | |
214 { | |
215 } | |
216 | |
217 const std::string& GetAnswer() const | |
218 { | |
219 return answer_; | |
220 } | |
221 | |
222 void ParseJsonBody(Json::Value& target) const | |
223 { | |
224 Json::Reader reader; | |
225 if (!reader.parse(answer_, target)) | |
226 { | |
227 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
228 } | |
229 } | |
230 | |
231 const HttpHeaders& GetAnswerHeaders() const | |
232 { | |
233 return headers_; | |
234 } | |
235 }; | |
236 | |
237 | |
238 private: | |
239 Orthanc::HttpMethod method_; | |
240 std::string uri_; | |
241 std::string body_; | |
242 HttpHeaders headers_; | |
243 unsigned int timeout_; | |
244 | |
245 std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_; | |
246 std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> > failureCallback_; | |
247 | |
248 public: | |
249 OrthancRestApiCommand() : | |
250 method_(Orthanc::HttpMethod_Get), | |
251 uri_("/"), | |
252 timeout_(10) | |
253 { | |
254 } | |
255 | |
256 virtual Type GetType() const | |
257 { | |
258 return Type_OrthancRestApi; | |
259 } | |
260 | |
261 void SetMethod(Orthanc::HttpMethod method) | |
262 { | |
263 method_ = method; | |
264 } | |
265 | |
266 void SetUri(const std::string& uri) | |
267 { | |
268 uri_ = uri; | |
269 } | |
270 | |
271 void SetBody(const std::string& body) | |
272 { | |
273 body_ = body; | |
274 } | |
275 | |
276 void SetBody(const Json::Value& json) | |
277 { | |
278 Json::FastWriter writer; | |
279 body_ = writer.write(json); | |
280 } | |
281 | |
282 void SetHttpHeaders(const HttpHeaders& headers) | |
283 { | |
284 headers_ = headers; | |
285 } | |
286 | |
287 void SetHttpHeader(const std::string& key, | |
288 const std::string& value) | |
289 { | |
290 headers_[key] = value; | |
291 } | |
292 | |
293 Orthanc::HttpMethod GetMethod() const | |
294 { | |
295 return method_; | |
296 } | |
297 | |
298 const std::string& GetUri() const | |
299 { | |
300 return uri_; | |
301 } | |
302 | |
303 const std::string& GetBody() const | |
304 { | |
305 if (method_ == Orthanc::HttpMethod_Post || | |
306 method_ == Orthanc::HttpMethod_Put) | |
307 { | |
308 return body_; | |
309 } | |
310 else | |
311 { | |
312 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
313 } | |
314 } | |
315 | |
316 const HttpHeaders& GetHttpHeaders() const | |
317 { | |
318 return headers_; | |
319 } | |
320 | |
321 void SetTimeout(unsigned int seconds) | |
322 { | |
323 timeout_ = seconds; | |
324 } | |
325 | |
326 unsigned int GetTimeout() const | |
327 { | |
328 return timeout_; | |
329 } | |
330 }; | |
331 | |
332 | |
333 | |
334 | |
335 class GetOrthancImageCommand : public OracleCommandWithPayload | |
336 { | |
337 public: | |
338 class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancImageCommand> | |
339 { | |
340 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); | |
341 | |
342 private: | |
343 std::auto_ptr<Orthanc::ImageAccessor> image_; | |
344 Orthanc::MimeType mime_; | |
345 | |
346 public: | |
347 SuccessMessage(const GetOrthancImageCommand& command, | |
348 Orthanc::ImageAccessor* image, // Takes ownership | |
349 Orthanc::MimeType mime) : | |
350 OriginMessage(command), | |
351 image_(image), | |
352 mime_(mime) | |
353 { | |
354 if (image == NULL) | |
355 { | |
356 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
357 } | |
358 } | |
359 | |
360 const Orthanc::ImageAccessor& GetImage() const | |
361 { | |
362 return *image_; | |
363 } | |
364 | |
365 Orthanc::MimeType GetMimeType() const | |
366 { | |
367 return mime_; | |
368 } | |
369 }; | |
370 | |
371 | |
372 private: | |
373 std::string uri_; | |
374 HttpHeaders headers_; | |
375 unsigned int timeout_; | |
376 bool hasExpectedFormat_; | |
377 Orthanc::PixelFormat expectedFormat_; | |
378 | |
379 std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_; | |
380 std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> > failureCallback_; | |
381 | |
382 public: | |
383 GetOrthancImageCommand() : | |
384 uri_("/"), | |
385 timeout_(10), | |
386 hasExpectedFormat_(false) | |
387 { | |
388 } | |
389 | |
390 virtual Type GetType() const | |
391 { | |
392 return Type_GetOrthancImage; | |
393 } | |
394 | |
395 void SetExpectedPixelFormat(Orthanc::PixelFormat format) | |
396 { | |
397 hasExpectedFormat_ = true; | |
398 expectedFormat_ = format; | |
399 } | |
400 | |
401 void SetUri(const std::string& uri) | |
402 { | |
403 uri_ = uri; | |
404 } | |
405 | |
406 void SetInstanceUri(const std::string& instance, | |
407 Orthanc::PixelFormat pixelFormat) | |
408 { | |
409 uri_ = "/instances/" + instance; | |
410 | |
411 switch (pixelFormat) | |
412 { | |
413 case Orthanc::PixelFormat_RGB24: | |
414 uri_ += "/preview"; | |
415 break; | |
416 | |
417 case Orthanc::PixelFormat_Grayscale16: | |
418 uri_ += "/image-uint16"; | |
419 break; | |
420 | |
421 case Orthanc::PixelFormat_SignedGrayscale16: | |
422 uri_ += "/image-int16"; | |
423 break; | |
424 | |
425 default: | |
426 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
427 } | |
428 } | |
429 | |
430 void SetHttpHeader(const std::string& key, | |
431 const std::string& value) | |
432 { | |
433 headers_[key] = value; | |
434 } | |
435 | |
436 const std::string& GetUri() const | |
437 { | |
438 return uri_; | |
439 } | |
440 | |
441 const HttpHeaders& GetHttpHeaders() const | |
442 { | |
443 return headers_; | |
444 } | |
445 | |
446 void SetTimeout(unsigned int seconds) | |
447 { | |
448 timeout_ = seconds; | |
449 } | |
450 | |
451 unsigned int GetTimeout() const | |
452 { | |
453 return timeout_; | |
454 } | |
455 | |
456 void ProcessHttpAnswer(IMessageEmitter& emitter, | |
457 const OrthancStone::IObserver& receiver, | |
458 const std::string& answer, | |
459 const HttpHeaders& answerHeaders) const | |
460 { | |
461 Orthanc::MimeType contentType = Orthanc::MimeType_Binary; | |
462 | |
463 for (HttpHeaders::const_iterator it = answerHeaders.begin(); | |
464 it != answerHeaders.end(); ++it) | |
465 { | |
466 std::string s; | |
467 Orthanc::Toolbox::ToLowerCase(s, it->first); | |
468 | |
469 if (s == "content-type") | |
470 { | |
471 contentType = Orthanc::StringToMimeType(it->second); | |
472 break; | |
473 } | |
474 } | |
475 | |
476 std::auto_ptr<Orthanc::ImageAccessor> image; | |
477 | |
478 switch (contentType) | |
479 { | |
480 case Orthanc::MimeType_Png: | |
481 { | |
482 image.reset(new Orthanc::PngReader); | |
483 dynamic_cast<Orthanc::PngReader&>(*image).ReadFromMemory(answer); | |
484 break; | |
485 } | |
486 | |
487 case Orthanc::MimeType_Pam: | |
488 { | |
489 image.reset(new Orthanc::PamReader); | |
490 dynamic_cast<Orthanc::PamReader&>(*image).ReadFromMemory(answer); | |
491 break; | |
492 } | |
493 | |
494 case Orthanc::MimeType_Jpeg: | |
495 { | |
496 image.reset(new Orthanc::JpegReader); | |
497 dynamic_cast<Orthanc::JpegReader&>(*image).ReadFromMemory(answer); | |
498 break; | |
499 } | |
500 | |
501 default: | |
502 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, | |
503 "Unsupported HTTP Content-Type for an image: " + | |
504 std::string(Orthanc::EnumerationToString(contentType))); | |
505 } | |
506 | |
507 if (hasExpectedFormat_) | |
508 { | |
509 if (expectedFormat_ == Orthanc::PixelFormat_SignedGrayscale16 && | |
510 image->GetFormat() == Orthanc::PixelFormat_Grayscale16) | |
511 { | |
512 image->SetFormat(Orthanc::PixelFormat_SignedGrayscale16); | |
513 } | |
514 | |
515 if (expectedFormat_ != image->GetFormat()) | |
516 { | |
517 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
518 } | |
519 } | |
520 | |
521 SuccessMessage message(*this, image.release(), contentType); | |
522 emitter.EmitMessage(receiver, message); | |
523 } | |
524 }; | |
525 | |
526 | |
527 | |
528 class GetOrthancWebViewerJpegCommand : public OracleCommandWithPayload | |
529 { | |
530 public: | |
531 class SuccessMessage : public OrthancStone::OriginMessage<GetOrthancWebViewerJpegCommand> | |
532 { | |
533 ORTHANC_STONE_MESSAGE(__FILE__, __LINE__); | |
534 | |
535 private: | |
536 std::auto_ptr<Orthanc::ImageAccessor> image_; | |
537 | |
538 public: | |
539 SuccessMessage(const GetOrthancWebViewerJpegCommand& command, | |
540 Orthanc::ImageAccessor* image) : // Takes ownership | |
541 OriginMessage(command), | |
542 image_(image) | |
543 { | |
544 if (image == NULL) | |
545 { | |
546 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
547 } | |
548 } | |
549 | |
550 const Orthanc::ImageAccessor& GetImage() const | |
551 { | |
552 return *image_; | |
553 } | |
554 }; | |
555 | |
556 private: | |
557 std::string instanceId_; | |
558 unsigned int frame_; | |
559 unsigned int quality_; | |
560 HttpHeaders headers_; | |
561 unsigned int timeout_; | |
562 Orthanc::PixelFormat expectedFormat_; | |
563 | |
564 std::auto_ptr< OrthancStone::MessageHandler<SuccessMessage> > successCallback_; | |
565 std::auto_ptr< OrthancStone::MessageHandler<OracleCommandExceptionMessage> > failureCallback_; | |
566 | |
567 public: | |
568 GetOrthancWebViewerJpegCommand() : | |
569 frame_(0), | |
570 quality_(95), | |
571 timeout_(10), | |
572 expectedFormat_(Orthanc::PixelFormat_Grayscale8) | |
573 { | |
574 } | |
575 | |
576 virtual Type GetType() const | |
577 { | |
578 return Type_GetOrthancWebViewerJpeg; | |
579 } | |
580 | |
581 void SetExpectedPixelFormat(Orthanc::PixelFormat format) | |
582 { | |
583 expectedFormat_ = format; | |
584 } | |
585 | |
586 void SetInstance(const std::string& instanceId) | |
587 { | |
588 instanceId_ = instanceId; | |
589 } | |
590 | |
591 void SetFrame(unsigned int frame) | |
592 { | |
593 frame_ = frame; | |
594 } | |
595 | |
596 void SetQuality(unsigned int quality) | |
597 { | |
598 if (quality <= 0 || | |
599 quality > 100) | |
600 { | |
601 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
602 } | |
603 else | |
604 { | |
605 quality_ = quality; | |
606 } | |
607 } | |
608 | |
609 void SetHttpHeader(const std::string& key, | |
610 const std::string& value) | |
611 { | |
612 headers_[key] = value; | |
613 } | |
614 | |
615 Orthanc::PixelFormat GetExpectedPixelFormat() const | |
616 { | |
617 return expectedFormat_; | |
618 } | |
619 | |
620 const std::string& GetInstanceId() const | |
621 { | |
622 return instanceId_; | |
623 } | |
624 | |
625 unsigned int GetFrame() const | |
626 { | |
627 return frame_; | |
628 } | |
629 | |
630 unsigned int GetQuality() const | |
631 { | |
632 return quality_; | |
633 } | |
634 | |
635 const HttpHeaders& GetHttpHeaders() const | |
636 { | |
637 return headers_; | |
638 } | |
639 | |
640 void SetTimeout(unsigned int seconds) | |
641 { | |
642 timeout_ = seconds; | |
643 } | |
644 | |
645 unsigned int GetTimeout() const | |
646 { | |
647 return timeout_; | |
648 } | |
649 | |
650 std::string GetUri() const | |
651 { | |
652 return ("/web-viewer/instances/jpeg" + boost::lexical_cast<std::string>(quality_) + | |
653 "-" + instanceId_ + "_" + boost::lexical_cast<std::string>(frame_)); | |
654 } | |
655 | |
656 void ProcessHttpAnswer(IMessageEmitter& emitter, | |
657 const OrthancStone::IObserver& receiver, | |
658 const std::string& answer) const | |
659 { | |
660 // This code comes from older "OrthancSlicesLoader::ParseSliceImageJpeg()" | |
661 | |
662 Json::Value encoded; | |
663 | |
664 { | |
665 Json::Reader reader; | |
666 if (!reader.parse(answer, encoded)) | |
667 { | |
668 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
669 } | |
670 } | |
671 | |
672 if (encoded.type() != Json::objectValue || | |
673 !encoded.isMember("Orthanc") || | |
674 encoded["Orthanc"].type() != Json::objectValue) | |
675 { | |
676 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
677 } | |
678 | |
679 const Json::Value& info = encoded["Orthanc"]; | |
680 if (!info.isMember("PixelData") || | |
681 !info.isMember("Stretched") || | |
682 !info.isMember("Compression") || | |
683 info["Compression"].type() != Json::stringValue || | |
684 info["PixelData"].type() != Json::stringValue || | |
685 info["Stretched"].type() != Json::booleanValue || | |
686 info["Compression"].asString() != "Jpeg") | |
687 { | |
688 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
689 } | |
690 | |
691 bool isSigned = false; | |
692 bool isStretched = info["Stretched"].asBool(); | |
693 | |
694 if (info.isMember("IsSigned")) | |
695 { | |
696 if (info["IsSigned"].type() != Json::booleanValue) | |
697 { | |
698 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
699 } | |
700 else | |
701 { | |
702 isSigned = info["IsSigned"].asBool(); | |
703 } | |
704 } | |
705 | |
706 std::auto_ptr<Orthanc::ImageAccessor> reader; | |
707 | |
708 { | |
709 std::string jpeg; | |
710 Orthanc::Toolbox::DecodeBase64(jpeg, info["PixelData"].asString()); | |
711 | |
712 reader.reset(new Orthanc::JpegReader); | |
713 dynamic_cast<Orthanc::JpegReader&>(*reader).ReadFromMemory(jpeg); | |
714 } | |
715 | |
716 if (reader->GetFormat() == Orthanc::PixelFormat_RGB24) // This is a color image | |
717 { | |
718 if (expectedFormat_ != Orthanc::PixelFormat_RGB24) | |
719 { | |
720 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
721 } | |
722 | |
723 if (isSigned || isStretched) | |
724 { | |
725 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
726 } | |
727 else | |
728 { | |
729 SuccessMessage message(*this, reader.release()); | |
730 emitter.EmitMessage(receiver, message); | |
731 return; | |
732 } | |
733 } | |
734 | |
735 if (reader->GetFormat() != Orthanc::PixelFormat_Grayscale8) | |
736 { | |
737 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
738 } | |
739 | |
740 if (!isStretched) | |
741 { | |
742 if (expectedFormat_ != reader->GetFormat()) | |
743 { | |
744 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
745 } | |
746 else | |
747 { | |
748 SuccessMessage message(*this, reader.release()); | |
749 emitter.EmitMessage(receiver, message); | |
750 return; | |
751 } | |
752 } | |
753 | |
754 int32_t stretchLow = 0; | |
755 int32_t stretchHigh = 0; | |
756 | |
757 if (!info.isMember("StretchLow") || | |
758 !info.isMember("StretchHigh") || | |
759 info["StretchLow"].type() != Json::intValue || | |
760 info["StretchHigh"].type() != Json::intValue) | |
761 { | |
762 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
763 } | |
764 | |
765 stretchLow = info["StretchLow"].asInt(); | |
766 stretchHigh = info["StretchHigh"].asInt(); | |
767 | |
768 if (stretchLow < -32768 || | |
769 stretchHigh > 65535 || | |
770 (stretchLow < 0 && stretchHigh > 32767)) | |
771 { | |
772 // This range cannot be represented with a uint16_t or an int16_t | |
773 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
774 } | |
775 | |
776 // Decode a grayscale JPEG 8bpp image coming from the Web viewer | |
777 std::auto_ptr<Orthanc::ImageAccessor> image | |
778 (new Orthanc::Image(expectedFormat_, reader->GetWidth(), reader->GetHeight(), false)); | |
779 | |
780 Orthanc::ImageProcessing::Convert(*image, *reader); | |
781 reader.reset(); | |
782 | |
783 float scaling = static_cast<float>(stretchHigh - stretchLow) / 255.0f; | |
784 | |
785 if (!OrthancStone::LinearAlgebra::IsCloseToZero(scaling)) | |
786 { | |
787 float offset = static_cast<float>(stretchLow) / scaling; | |
788 Orthanc::ImageProcessing::ShiftScale(*image, offset, scaling, true); | |
789 } | |
790 | |
791 SuccessMessage message(*this, image.release()); | |
792 emitter.EmitMessage(receiver, message); | |
793 } | |
794 }; | |
795 | |
796 | |
797 | |
798 | |
799 | |
800 class DicomInstanceParameters : | |
801 public Orthanc::IDynamicObject /* to be used as a payload of SlicesSorter */ | |
802 { | |
803 private: | |
804 struct Data // Struct to ease the copy constructor | |
805 { | |
806 std::string orthancInstanceId_; | |
807 std::string studyInstanceUid_; | |
808 std::string seriesInstanceUid_; | |
809 std::string sopInstanceUid_; | |
810 Orthanc::DicomImageInformation imageInformation_; | |
811 OrthancStone::SopClassUid sopClassUid_; | |
812 double thickness_; | |
813 double pixelSpacingX_; | |
814 double pixelSpacingY_; | |
815 OrthancStone::CoordinateSystem3D geometry_; | |
816 OrthancStone::Vector frameOffsets_; | |
817 bool isColor_; | |
818 bool hasRescale_; | |
819 double rescaleIntercept_; | |
820 double rescaleSlope_; | |
821 bool hasDefaultWindowing_; | |
822 float defaultWindowingCenter_; | |
823 float defaultWindowingWidth_; | |
824 Orthanc::PixelFormat expectedPixelFormat_; | |
825 | |
826 void ComputeDoseOffsets(const Orthanc::DicomMap& dicom) | |
827 { | |
828 // http://dicom.nema.org/medical/Dicom/2016a/output/chtml/part03/sect_C.8.8.3.2.html | |
829 | |
830 { | |
831 std::string increment; | |
832 | |
833 if (dicom.CopyToString(increment, Orthanc::DICOM_TAG_FRAME_INCREMENT_POINTER, false)) | |
834 { | |
835 Orthanc::Toolbox::ToUpperCase(increment); | |
836 if (increment != "3004,000C") // This is the "Grid Frame Offset Vector" tag | |
837 { | |
838 LOG(ERROR) << "RT-DOSE: Bad value for the \"FrameIncrementPointer\" tag"; | |
839 return; | |
840 } | |
841 } | |
842 } | |
843 | |
844 if (!OrthancStone::LinearAlgebra::ParseVector(frameOffsets_, dicom, Orthanc::DICOM_TAG_GRID_FRAME_OFFSET_VECTOR) || | |
845 frameOffsets_.size() < imageInformation_.GetNumberOfFrames()) | |
846 { | |
847 LOG(ERROR) << "RT-DOSE: No information about the 3D location of some slice(s)"; | |
848 frameOffsets_.clear(); | |
849 } | |
850 else | |
851 { | |
852 if (frameOffsets_.size() >= 2) | |
853 { | |
854 thickness_ = frameOffsets_[1] - frameOffsets_[0]; | |
855 | |
856 if (thickness_ < 0) | |
857 { | |
858 thickness_ = -thickness_; | |
859 } | |
860 } | |
861 } | |
862 } | |
863 | |
864 Data(const Orthanc::DicomMap& dicom) : | |
865 imageInformation_(dicom) | |
866 { | |
867 if (imageInformation_.GetNumberOfFrames() <= 0) | |
868 { | |
869 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
870 } | |
871 | |
872 if (!dicom.CopyToString(studyInstanceUid_, Orthanc::DICOM_TAG_STUDY_INSTANCE_UID, false) || | |
873 !dicom.CopyToString(seriesInstanceUid_, Orthanc::DICOM_TAG_SERIES_INSTANCE_UID, false) || | |
874 !dicom.CopyToString(sopInstanceUid_, Orthanc::DICOM_TAG_SOP_INSTANCE_UID, false)) | |
875 { | |
876 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
877 } | |
878 | |
879 std::string s; | |
880 if (!dicom.CopyToString(s, Orthanc::DICOM_TAG_SOP_CLASS_UID, false)) | |
881 { | |
882 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); | |
883 } | |
884 else | |
885 { | |
886 sopClassUid_ = OrthancStone::StringToSopClassUid(s); | |
887 } | |
888 | |
889 if (!dicom.ParseDouble(thickness_, Orthanc::DICOM_TAG_SLICE_THICKNESS)) | |
890 { | |
891 thickness_ = 100.0 * std::numeric_limits<double>::epsilon(); | |
892 } | |
893 | |
894 OrthancStone::GeometryToolbox::GetPixelSpacing(pixelSpacingX_, pixelSpacingY_, dicom); | |
895 | |
896 std::string position, orientation; | |
897 if (dicom.CopyToString(position, Orthanc::DICOM_TAG_IMAGE_POSITION_PATIENT, false) && | |
898 dicom.CopyToString(orientation, Orthanc::DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false)) | |
899 { | |
900 geometry_ = OrthancStone::CoordinateSystem3D(position, orientation); | |
901 } | |
902 | |
903 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) | |
904 { | |
905 ComputeDoseOffsets(dicom); | |
906 } | |
907 | |
908 isColor_ = (imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome1 && | |
909 imageInformation_.GetPhotometricInterpretation() != Orthanc::PhotometricInterpretation_Monochrome2); | |
910 | |
911 double doseGridScaling; | |
912 | |
913 if (dicom.ParseDouble(rescaleIntercept_, Orthanc::DICOM_TAG_RESCALE_INTERCEPT) && | |
914 dicom.ParseDouble(rescaleSlope_, Orthanc::DICOM_TAG_RESCALE_SLOPE)) | |
915 { | |
916 hasRescale_ = true; | |
917 } | |
918 else if (dicom.ParseDouble(doseGridScaling, Orthanc::DICOM_TAG_DOSE_GRID_SCALING)) | |
919 { | |
920 hasRescale_ = true; | |
921 rescaleIntercept_ = 0; | |
922 rescaleSlope_ = doseGridScaling; | |
923 } | |
924 else | |
925 { | |
926 hasRescale_ = false; | |
927 } | |
928 | |
929 OrthancStone::Vector c, w; | |
930 if (OrthancStone::LinearAlgebra::ParseVector(c, dicom, Orthanc::DICOM_TAG_WINDOW_CENTER) && | |
931 OrthancStone::LinearAlgebra::ParseVector(w, dicom, Orthanc::DICOM_TAG_WINDOW_WIDTH) && | |
932 c.size() > 0 && | |
933 w.size() > 0) | |
934 { | |
935 hasDefaultWindowing_ = true; | |
936 defaultWindowingCenter_ = static_cast<float>(c[0]); | |
937 defaultWindowingWidth_ = static_cast<float>(w[0]); | |
938 } | |
939 else | |
940 { | |
941 hasDefaultWindowing_ = false; | |
942 } | |
943 | |
944 if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) | |
945 { | |
946 switch (imageInformation_.GetBitsStored()) | |
947 { | |
948 case 16: | |
949 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; | |
950 break; | |
951 | |
952 case 32: | |
953 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale32; | |
954 break; | |
955 | |
956 default: | |
957 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
958 } | |
959 } | |
960 else if (isColor_) | |
961 { | |
962 expectedPixelFormat_ = Orthanc::PixelFormat_RGB24; | |
963 } | |
964 else if (imageInformation_.IsSigned()) | |
965 { | |
966 expectedPixelFormat_ = Orthanc::PixelFormat_SignedGrayscale16; | |
967 } | |
968 else | |
969 { | |
970 expectedPixelFormat_ = Orthanc::PixelFormat_Grayscale16; | |
971 } | |
972 } | |
973 | |
974 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const | |
975 { | |
976 if (frame == 0) | |
977 { | |
978 return geometry_; | |
979 } | |
980 else if (frame >= imageInformation_.GetNumberOfFrames()) | |
981 { | |
982 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
983 } | |
984 else if (sopClassUid_ == OrthancStone::SopClassUid_RTDose) | |
985 { | |
986 if (frame >= frameOffsets_.size()) | |
987 { | |
988 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
989 } | |
990 | |
991 return OrthancStone::CoordinateSystem3D( | |
992 geometry_.GetOrigin() + frameOffsets_[frame] * geometry_.GetNormal(), | |
993 geometry_.GetAxisX(), | |
994 geometry_.GetAxisY()); | |
995 } | |
996 else | |
997 { | |
998 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
999 } | |
1000 } | |
1001 | |
1002 // TODO - Is this necessary? | |
1003 bool FrameContainsPlane(unsigned int frame, | |
1004 const OrthancStone::CoordinateSystem3D& plane) const | |
1005 { | |
1006 if (frame >= imageInformation_.GetNumberOfFrames()) | |
1007 { | |
1008 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
1009 } | |
1010 | |
1011 OrthancStone::CoordinateSystem3D tmp = geometry_; | |
1012 | |
1013 if (frame != 0) | |
1014 { | |
1015 tmp = GetFrameGeometry(frame); | |
1016 } | |
1017 | |
1018 double distance; | |
1019 | |
1020 return (OrthancStone::CoordinateSystem3D::GetDistance(distance, tmp, plane) && | |
1021 distance <= thickness_ / 2.0); | |
1022 } | |
1023 | |
1024 | |
1025 void ApplyRescale(Orthanc::ImageAccessor& image, | |
1026 bool useDouble) const | |
1027 { | |
1028 if (image.GetFormat() != Orthanc::PixelFormat_Float32) | |
1029 { | |
1030 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
1031 } | |
1032 | |
1033 if (hasRescale_) | |
1034 { | |
1035 const unsigned int width = image.GetWidth(); | |
1036 const unsigned int height = image.GetHeight(); | |
1037 | |
1038 for (unsigned int y = 0; y < height; y++) | |
1039 { | |
1040 float* p = reinterpret_cast<float*>(image.GetRow(y)); | |
1041 | |
1042 if (useDouble) | |
1043 { | |
1044 // Slower, accurate implementation using double | |
1045 for (unsigned int x = 0; x < width; x++, p++) | |
1046 { | |
1047 double value = static_cast<double>(*p); | |
1048 *p = static_cast<float>(value * rescaleSlope_ + rescaleIntercept_); | |
1049 } | |
1050 } | |
1051 else | |
1052 { | |
1053 // Fast, approximate implementation using float | |
1054 for (unsigned int x = 0; x < width; x++, p++) | |
1055 { | |
1056 *p = (*p) * static_cast<float>(rescaleSlope_) + static_cast<float>(rescaleIntercept_); | |
1057 } | |
1058 } | |
1059 } | |
1060 } | |
1061 } | |
1062 }; | |
1063 | |
1064 | |
1065 Data data_; | |
1066 | |
1067 | |
1068 public: | |
1069 DicomInstanceParameters(const DicomInstanceParameters& other) : | |
1070 data_(other.data_) | |
1071 { | |
1072 } | |
1073 | |
1074 DicomInstanceParameters(const Orthanc::DicomMap& dicom) : | |
1075 data_(dicom) | |
1076 { | |
1077 } | |
1078 | |
1079 void SetOrthancInstanceIdentifier(const std::string& id) | |
1080 { | |
1081 data_.orthancInstanceId_ = id; | |
1082 } | |
1083 | |
1084 const std::string& GetOrthancInstanceIdentifier() const | |
1085 { | |
1086 return data_.orthancInstanceId_; | |
1087 } | |
1088 | |
1089 const Orthanc::DicomImageInformation& GetImageInformation() const | |
1090 { | |
1091 return data_.imageInformation_; | |
1092 } | |
1093 | |
1094 const std::string& GetStudyInstanceUid() const | |
1095 { | |
1096 return data_.studyInstanceUid_; | |
1097 } | |
1098 | |
1099 const std::string& GetSeriesInstanceUid() const | |
1100 { | |
1101 return data_.seriesInstanceUid_; | |
1102 } | |
1103 | |
1104 const std::string& GetSopInstanceUid() const | |
1105 { | |
1106 return data_.sopInstanceUid_; | |
1107 } | |
1108 | |
1109 OrthancStone::SopClassUid GetSopClassUid() const | |
1110 { | |
1111 return data_.sopClassUid_; | |
1112 } | |
1113 | |
1114 double GetThickness() const | |
1115 { | |
1116 return data_.thickness_; | |
1117 } | |
1118 | |
1119 double GetPixelSpacingX() const | |
1120 { | |
1121 return data_.pixelSpacingX_; | |
1122 } | |
1123 | |
1124 double GetPixelSpacingY() const | |
1125 { | |
1126 return data_.pixelSpacingY_; | |
1127 } | |
1128 | |
1129 const OrthancStone::CoordinateSystem3D& GetGeometry() const | |
1130 { | |
1131 return data_.geometry_; | |
1132 } | |
1133 | |
1134 OrthancStone::CoordinateSystem3D GetFrameGeometry(unsigned int frame) const | |
1135 { | |
1136 return data_.GetFrameGeometry(frame); | |
1137 } | |
1138 | |
1139 // TODO - Is this necessary? | |
1140 bool FrameContainsPlane(unsigned int frame, | |
1141 const OrthancStone::CoordinateSystem3D& plane) const | |
1142 { | |
1143 return data_.FrameContainsPlane(frame, plane); | |
1144 } | |
1145 | |
1146 bool IsColor() const | |
1147 { | |
1148 return data_.isColor_; | |
1149 } | |
1150 | |
1151 bool HasRescale() const | |
1152 { | |
1153 return data_.hasRescale_; | |
1154 } | |
1155 | |
1156 double GetRescaleIntercept() const | |
1157 { | |
1158 if (data_.hasRescale_) | |
1159 { | |
1160 return data_.rescaleIntercept_; | |
1161 } | |
1162 else | |
1163 { | |
1164 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1165 } | |
1166 } | |
1167 | |
1168 double GetRescaleSlope() const | |
1169 { | |
1170 if (data_.hasRescale_) | |
1171 { | |
1172 return data_.rescaleSlope_; | |
1173 } | |
1174 else | |
1175 { | |
1176 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1177 } | |
1178 } | |
1179 | |
1180 bool HasDefaultWindowing() const | |
1181 { | |
1182 return data_.hasDefaultWindowing_; | |
1183 } | |
1184 | |
1185 float GetDefaultWindowingCenter() const | |
1186 { | |
1187 if (data_.hasDefaultWindowing_) | |
1188 { | |
1189 return data_.defaultWindowingCenter_; | |
1190 } | |
1191 else | |
1192 { | |
1193 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1194 } | |
1195 } | |
1196 | |
1197 float GetDefaultWindowingWidth() const | |
1198 { | |
1199 if (data_.hasDefaultWindowing_) | |
1200 { | |
1201 return data_.defaultWindowingWidth_; | |
1202 } | |
1203 else | |
1204 { | |
1205 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1206 } | |
1207 } | |
1208 | |
1209 Orthanc::PixelFormat GetExpectedPixelFormat() const | |
1210 { | |
1211 return data_.expectedPixelFormat_; | |
1212 } | |
1213 | |
1214 | |
1215 OrthancStone::TextureBaseSceneLayer* CreateTexture(const Orthanc::ImageAccessor& source) const | |
1216 { | |
1217 assert(sizeof(float) == 4); | |
1218 | |
1219 Orthanc::PixelFormat sourceFormat = source.GetFormat(); | |
1220 | |
1221 if (sourceFormat != GetExpectedPixelFormat()) | |
1222 { | |
1223 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat); | |
1224 } | |
1225 | |
1226 if (sourceFormat == Orthanc::PixelFormat_RGB24) | |
1227 { | |
1228 // This is the case of a color image. No conversion has to be done. | |
1229 return new OrthancStone::ColorTextureSceneLayer(source); | |
1230 } | |
1231 else | |
1232 { | |
1233 if (sourceFormat != Orthanc::PixelFormat_Grayscale16 && | |
1234 sourceFormat != Orthanc::PixelFormat_Grayscale32 && | |
1235 sourceFormat != Orthanc::PixelFormat_SignedGrayscale16) | |
1236 { | |
1237 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
1238 } | |
1239 | |
1240 std::auto_ptr<OrthancStone::FloatTextureSceneLayer> texture; | |
1241 | |
1242 { | |
1243 // This is the case of a grayscale frame. Convert it to Float32. | |
1244 std::auto_ptr<Orthanc::Image> converted(new Orthanc::Image(Orthanc::PixelFormat_Float32, | |
1245 source.GetWidth(), | |
1246 source.GetHeight(), | |
1247 false)); | |
1248 Orthanc::ImageProcessing::Convert(*converted, source); | |
1249 | |
1250 // Correct rescale slope/intercept if need be | |
1251 data_.ApplyRescale(*converted, (sourceFormat == Orthanc::PixelFormat_Grayscale32)); | |
1252 | |
1253 texture.reset(new OrthancStone::FloatTextureSceneLayer(*converted)); | |
1254 } | |
1255 | |
1256 if (data_.hasDefaultWindowing_) | |
1257 { | |
1258 texture->SetCustomWindowing(data_.defaultWindowingCenter_, | |
1259 data_.defaultWindowingWidth_); | |
1260 } | |
1261 | |
1262 return texture.release(); | |
1263 } | |
1264 } | |
1265 }; | |
1266 | |
1267 | |
1268 class DicomVolumeImage : public boost::noncopyable | |
1269 { | |
1270 private: | |
1271 std::auto_ptr<OrthancStone::ImageBuffer3D> image_; | |
1272 std::vector<DicomInstanceParameters*> slices_; | |
1273 uint64_t revision_; | |
1274 std::vector<uint64_t> slicesRevision_; | |
1275 std::vector<unsigned int> slicesQuality_; | |
1276 | |
1277 void CheckSlice(size_t index, | |
1278 const DicomInstanceParameters& reference) const | |
1279 { | |
1280 const DicomInstanceParameters& slice = *slices_[index]; | |
1281 | |
1282 if (!OrthancStone::GeometryToolbox::IsParallel( | |
1283 reference.GetGeometry().GetNormal(), | |
1284 slice.GetGeometry().GetNormal())) | |
1285 { | |
1286 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, | |
1287 "A slice in the volume image is not parallel to the others"); | |
1288 } | |
1289 | |
1290 if (reference.GetExpectedPixelFormat() != slice.GetExpectedPixelFormat()) | |
1291 { | |
1292 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat, | |
1293 "The pixel format changes across the slices of the volume image"); | |
1294 } | |
1295 | |
1296 if (reference.GetImageInformation().GetWidth() != slice.GetImageInformation().GetWidth() || | |
1297 reference.GetImageInformation().GetHeight() != slice.GetImageInformation().GetHeight()) | |
1298 { | |
1299 throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageSize, | |
1300 "The width/height of slices are not constant in the volume image"); | |
1301 } | |
1302 | |
1303 if (!OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingX(), slice.GetPixelSpacingX()) || | |
1304 !OrthancStone::LinearAlgebra::IsNear(reference.GetPixelSpacingY(), slice.GetPixelSpacingY())) | |
1305 { | |
1306 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, | |
1307 "The pixel spacing of the slices change across the volume image"); | |
1308 } | |
1309 } | |
1310 | |
1311 | |
1312 void CheckVolume() const | |
1313 { | |
1314 for (size_t i = 0; i < slices_.size(); i++) | |
1315 { | |
1316 assert(slices_[i] != NULL); | |
1317 if (slices_[i]->GetImageInformation().GetNumberOfFrames() != 1) | |
1318 { | |
1319 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadGeometry, | |
1320 "This class does not support multi-frame images"); | |
1321 } | |
1322 } | |
1323 | |
1324 if (slices_.size() != 0) | |
1325 { | |
1326 const DicomInstanceParameters& reference = *slices_[0]; | |
1327 | |
1328 for (size_t i = 1; i < slices_.size(); i++) | |
1329 { | |
1330 CheckSlice(i, reference); | |
1331 } | |
1332 } | |
1333 } | |
1334 | |
1335 | |
1336 void Clear() | |
1337 { | |
1338 image_.reset(); | |
1339 | |
1340 for (size_t i = 0; i < slices_.size(); i++) | |
1341 { | |
1342 assert(slices_[i] != NULL); | |
1343 delete slices_[i]; | |
1344 } | |
1345 | |
1346 slices_.clear(); | |
1347 slicesRevision_.clear(); | |
1348 slicesQuality_.clear(); | |
1349 } | |
1350 | |
1351 | |
1352 void CheckSliceIndex(size_t index) const | |
1353 { | |
1354 assert(slices_.size() == image_->GetDepth() && | |
1355 slices_.size() == slicesRevision_.size()); | |
1356 | |
1357 if (!HasGeometry()) | |
1358 { | |
1359 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1360 } | |
1361 else if (index >= slices_.size()) | |
1362 { | |
1363 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
1364 } | |
1365 } | |
1366 | |
1367 | |
1368 public: | |
1369 DicomVolumeImage() | |
1370 { | |
1371 } | |
1372 | |
1373 ~DicomVolumeImage() | |
1374 { | |
1375 Clear(); | |
1376 } | |
1377 | |
1378 // WARNING: The payload of "slices" must be of class "DicomInstanceParameters" | |
1379 void SetGeometry(OrthancStone::SlicesSorter& slices) | |
1380 { | |
1381 Clear(); | |
1382 | |
1383 if (!slices.Sort()) | |
1384 { | |
1385 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, | |
1386 "Cannot sort the 3D slices of a DICOM series"); | |
1387 } | |
1388 | |
1389 if (slices.GetSlicesCount() == 0) | |
1390 { | |
1391 // Empty volume | |
1392 image_.reset(new OrthancStone::ImageBuffer3D(Orthanc::PixelFormat_Grayscale8, 0, 0, 0, | |
1393 false /* don't compute range */)); | |
1394 } | |
1395 else | |
1396 { | |
1397 slices_.reserve(slices.GetSlicesCount()); | |
1398 slicesRevision_.resize(slices.GetSlicesCount(), 0); | |
1399 slicesQuality_.resize(slices.GetSlicesCount(), 0); | |
1400 | |
1401 for (size_t i = 0; i < slices.GetSlicesCount(); i++) | |
1402 { | |
1403 const DicomInstanceParameters& slice = | |
1404 dynamic_cast<const DicomInstanceParameters&>(slices.GetSlicePayload(i)); | |
1405 slices_.push_back(new DicomInstanceParameters(slice)); | |
1406 } | |
1407 | |
1408 CheckVolume(); | |
1409 | |
1410 const double spacingZ = slices.ComputeSpacingBetweenSlices(); | |
1411 LOG(INFO) << "Computed spacing between slices: " << spacingZ << "mm"; | |
1412 | |
1413 const DicomInstanceParameters& parameters = *slices_[0]; | |
1414 | |
1415 image_.reset(new OrthancStone::ImageBuffer3D(parameters.GetExpectedPixelFormat(), | |
1416 parameters.GetImageInformation().GetWidth(), | |
1417 parameters.GetImageInformation().GetHeight(), | |
1418 slices.GetSlicesCount(), false /* don't compute range */)); | |
1419 | |
1420 image_->GetGeometry().SetAxialGeometry(slices.GetSliceGeometry(0)); | |
1421 image_->GetGeometry().SetVoxelDimensions(parameters.GetPixelSpacingX(), | |
1422 parameters.GetPixelSpacingY(), spacingZ); | |
1423 } | |
1424 | |
1425 image_->Clear(); | |
1426 | |
1427 revision_++; | |
1428 } | |
1429 | |
1430 uint64_t GetRevision() const | |
1431 { | |
1432 return revision_; | |
1433 } | |
1434 | |
1435 bool HasGeometry() const | |
1436 { | |
1437 return (image_.get() != NULL); | |
1438 } | |
1439 | |
1440 const OrthancStone::ImageBuffer3D& GetImage() const | |
1441 { | |
1442 if (!HasGeometry()) | |
1443 { | |
1444 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1445 } | |
1446 else | |
1447 { | |
1448 return *image_; | |
1449 } | |
1450 } | |
1451 | |
1452 size_t GetSlicesCount() const | |
1453 { | |
1454 if (!HasGeometry()) | |
1455 { | |
1456 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1457 } | |
1458 else | |
1459 { | |
1460 return slices_.size(); | |
1461 } | |
1462 } | |
1463 | |
1464 const DicomInstanceParameters& GetSliceParameters(size_t index) const | |
1465 { | |
1466 CheckSliceIndex(index); | |
1467 return *slices_[index]; | |
1468 } | |
1469 | |
1470 uint64_t GetSliceRevision(size_t index) const | |
1471 { | |
1472 CheckSliceIndex(index); | |
1473 return slicesRevision_[index]; | |
1474 } | |
1475 | |
1476 void SetSliceContent(size_t index, | |
1477 const Orthanc::ImageAccessor& image, | |
1478 unsigned int quality) | |
1479 { | |
1480 CheckSliceIndex(index); | |
1481 | |
1482 // If a better image quality is already available, don't update the content | |
1483 if (quality >= slicesQuality_[index]) | |
1484 { | |
1485 { | |
1486 OrthancStone::ImageBuffer3D::SliceWriter writer | |
1487 (*image_, OrthancStone::VolumeProjection_Axial, index); | |
1488 Orthanc::ImageProcessing::Copy(writer.GetAccessor(), image); | |
1489 } | |
1490 | |
1491 revision_ ++; | |
1492 slicesRevision_[index] += 1; | |
1493 } | |
1494 } | |
1495 }; | |
1496 | |
1497 | |
1498 | |
1499 class IDicomVolumeSource : public boost::noncopyable | |
1500 { | |
1501 public: | |
1502 virtual ~IDicomVolumeSource() | |
1503 { | |
1504 } | |
1505 | |
1506 virtual const DicomVolumeImage& GetVolume() const = 0; | |
1507 | |
1508 virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) = 0; | |
1509 }; | |
1510 | |
1511 | |
1512 | |
1513 class VolumeSeriesOrthancLoader : | |
1514 public OrthancStone::IObserver, | |
1515 public IDicomVolumeSource | |
1516 { | |
1517 private: | |
1518 static const unsigned int LOW_QUALITY = 0; | |
1519 static const unsigned int MIDDLE_QUALITY = 1; | |
1520 static const unsigned int BEST_QUALITY = 2; | |
1521 | |
1522 | |
1523 static unsigned int GetSliceIndexPayload(const OracleCommandWithPayload& command) | |
1524 { | |
1525 return dynamic_cast< const Orthanc::SingleValueObject<unsigned int>& >(command.GetPayload()).GetValue(); | |
1526 } | |
1527 | |
1528 | |
1529 void ScheduleNextSliceDownload() | |
1530 { | |
1531 assert(strategy_.get() != NULL); | |
1532 | |
1533 unsigned int sliceIndex, quality; | |
1534 | |
1535 if (strategy_->GetNext(sliceIndex, quality)) | |
1536 { | |
1537 assert(quality <= BEST_QUALITY); | |
1538 | |
1539 const DicomInstanceParameters& slice = volume_.GetSliceParameters(sliceIndex); | |
1540 | |
1541 const std::string& instance = slice.GetOrthancInstanceIdentifier(); | |
1542 if (instance.empty()) | |
1543 { | |
1544 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
1545 } | |
1546 | |
1547 std::auto_ptr<Refactoring::OracleCommandWithPayload> command; | |
1548 | |
1549 if (quality == BEST_QUALITY) | |
1550 { | |
1551 std::auto_ptr<Refactoring::GetOrthancImageCommand> tmp( | |
1552 new Refactoring::GetOrthancImageCommand); | |
1553 tmp->SetHttpHeader("Accept-Encoding", "gzip"); | |
1554 tmp->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); | |
1555 tmp->SetInstanceUri(instance, slice.GetExpectedPixelFormat()); | |
1556 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); | |
1557 command.reset(tmp.release()); | |
1558 } | |
1559 else | |
1560 { | |
1561 std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> tmp( | |
1562 new Refactoring::GetOrthancWebViewerJpegCommand); | |
1563 tmp->SetHttpHeader("Accept-Encoding", "gzip"); | |
1564 tmp->SetInstance(instance); | |
1565 tmp->SetQuality((quality == 0 ? 50 : 90)); | |
1566 tmp->SetExpectedPixelFormat(slice.GetExpectedPixelFormat()); | |
1567 command.reset(tmp.release()); | |
1568 } | |
1569 | |
1570 command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(sliceIndex)); | |
1571 oracle_.Schedule(*this, command.release()); | |
1572 } | |
1573 } | |
1574 | |
1575 | |
1576 void LoadGeometry(const OrthancRestApiCommand::SuccessMessage& message) | |
1577 { | |
1578 Json::Value body; | |
1579 message.ParseJsonBody(body); | |
1580 | |
1581 if (body.type() != Json::objectValue) | |
1582 { | |
1583 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); | |
1584 } | |
1585 | |
1586 { | |
1587 Json::Value::Members instances = body.getMemberNames(); | |
1588 | |
1589 OrthancStone::SlicesSorter slices; | |
1590 | |
1591 for (size_t i = 0; i < instances.size(); i++) | |
1592 { | |
1593 Orthanc::DicomMap dicom; | |
1594 dicom.FromDicomAsJson(body[instances[i]]); | |
1595 | |
1596 std::auto_ptr<DicomInstanceParameters> instance(new DicomInstanceParameters(dicom)); | |
1597 instance->SetOrthancInstanceIdentifier(instances[i]); | |
1598 | |
1599 OrthancStone::CoordinateSystem3D geometry = instance->GetGeometry(); | |
1600 slices.AddSlice(geometry, instance.release()); | |
1601 } | |
1602 | |
1603 volume_.SetGeometry(slices); | |
1604 } | |
1605 | |
1606 if (volume_.GetSlicesCount() != 0) | |
1607 { | |
1608 strategy_.reset(new OrthancStone::BasicFetchingStrategy( | |
1609 new OrthancStone::BasicFetchingItemsSorter(volume_.GetSlicesCount()), BEST_QUALITY)); | |
1610 | |
1611 for (unsigned int i = 0; i < 4; i++) // Schedule up to 4 simultaneous downloads (TODO - parameter) | |
1612 { | |
1613 ScheduleNextSliceDownload(); | |
1614 } | |
1615 } | |
1616 } | |
1617 | |
1618 | |
1619 void LoadBestQualitySliceContent(const Refactoring::GetOrthancImageCommand::SuccessMessage& message) | |
1620 { | |
1621 volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), | |
1622 message.GetImage(), BEST_QUALITY); | |
1623 | |
1624 ScheduleNextSliceDownload(); | |
1625 } | |
1626 | |
1627 | |
1628 void LoadJpegSliceContent(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message) | |
1629 { | |
1630 unsigned int quality; | |
1631 | |
1632 switch (message.GetOrigin().GetQuality()) | |
1633 { | |
1634 case 50: | |
1635 quality = LOW_QUALITY; | |
1636 break; | |
1637 | |
1638 case 90: | |
1639 quality = MIDDLE_QUALITY; | |
1640 break; | |
1641 | |
1642 default: | |
1643 throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); | |
1644 } | |
1645 | |
1646 volume_.SetSliceContent(GetSliceIndexPayload(message.GetOrigin()), message.GetImage(), quality); | |
1647 | |
1648 ScheduleNextSliceDownload(); | |
1649 } | |
1650 | |
1651 | |
1652 IOracle& oracle_; | |
1653 bool active_; | |
1654 DicomVolumeImage volume_; | |
1655 | |
1656 std::auto_ptr<OrthancStone::IFetchingStrategy> strategy_; | |
1657 | |
1658 public: | |
1659 VolumeSeriesOrthancLoader(IOracle& oracle, | |
1660 OrthancStone::IObservable& oracleObservable) : | |
1661 IObserver(oracleObservable.GetBroker()), | |
1662 oracle_(oracle), | |
1663 active_(false) | |
1664 { | |
1665 oracleObservable.RegisterObserverCallback( | |
1666 new OrthancStone::Callable<VolumeSeriesOrthancLoader, OrthancRestApiCommand::SuccessMessage> | |
1667 (*this, &VolumeSeriesOrthancLoader::LoadGeometry)); | |
1668 | |
1669 oracleObservable.RegisterObserverCallback( | |
1670 new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancImageCommand::SuccessMessage> | |
1671 (*this, &VolumeSeriesOrthancLoader::LoadBestQualitySliceContent)); | |
1672 | |
1673 oracleObservable.RegisterObserverCallback( | |
1674 new OrthancStone::Callable<VolumeSeriesOrthancLoader, GetOrthancWebViewerJpegCommand::SuccessMessage> | |
1675 (*this, &VolumeSeriesOrthancLoader::LoadJpegSliceContent)); | |
1676 } | |
1677 | |
1678 void LoadSeries(const std::string& seriesId) | |
1679 { | |
1680 if (active_) | |
1681 { | |
1682 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1683 } | |
1684 | |
1685 active_ = true; | |
1686 | |
1687 std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); | |
1688 command->SetUri("/series/" + seriesId + "/instances-tags"); | |
1689 | |
1690 oracle_.Schedule(*this, command.release()); | |
1691 } | |
1692 | |
1693 | |
1694 virtual const DicomVolumeImage& GetVolume() const | |
1695 { | |
1696 return volume_; | |
1697 } | |
1698 | |
1699 | |
1700 virtual void NotifyAxialSliceAccessed(unsigned int sliceIndex) | |
1701 { | |
1702 if (strategy_.get() == NULL) | |
1703 { | |
1704 // Should have called GetVolume().HasGeometry() before | |
1705 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1706 } | |
1707 else | |
1708 { | |
1709 strategy_->SetCurrent(sliceIndex); | |
1710 } | |
1711 } | |
1712 }; | |
1713 | |
1714 | |
1715 | |
1716 #if 0 | |
1717 void LoadInstance(const std::string& instanceId) | |
1718 { | |
1719 if (active_) | |
1720 { | |
1721 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
1722 } | |
1723 | |
1724 active_ = true; | |
1725 | |
1726 // Tag "3004-000c" is "Grid Frame Offset Vector", which is | |
1727 // mandatory to read RT DOSE, but is too long to be returned by default | |
1728 | |
1729 // TODO => Should be part of a second call if needed | |
1730 | |
1731 std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); | |
1732 command->SetUri("/instances/" + instanceId + "/tags?ignore-length=3004-000c"); | |
1733 command->SetPayload(new LoadInstanceGeometryHandler(*this)); | |
1734 | |
1735 oracle_.Schedule(*this, command.release()); | |
1736 } | |
1737 #endif | |
1738 | |
1739 | |
1740 /* class VolumeSlicerBase : public IVolumeSlicer | |
1741 { | |
1742 private: | |
1743 OrthancStone::Scene2D& scene_; | |
1744 int layerDepth_; | |
1745 bool first_; | |
1746 OrthancStone::CoordinateSystem3D lastPlane_; | |
1747 | |
1748 protected: | |
1749 bool HasViewportPlaneChanged(const OrthancStone::CoordinateSystem3D& plane) const | |
1750 { | |
1751 if (first_ || | |
1752 !OrthancStone::LinearAlgebra::IsCloseToZero( | |
1753 boost::numeric::ublas::norm_2(lastPlane_.GetNormal() - plane.GetNormal()))) | |
1754 { | |
1755 // This is the first rendering, or the plane has not the same orientation | |
1756 return false; | |
1757 } | |
1758 else | |
1759 { | |
1760 double offset1 = lastPlane_.ProjectAlongNormal(plane.GetOrigin()); | |
1761 double offset2 = lastPlane_.ProjectAlongNormal(lastPlane_.GetOrigin()); | |
1762 return OrthancStone::LinearAlgebra::IsCloseToZero(offset2 - offset1); | |
1763 } | |
1764 } | |
1765 | |
1766 void SetLastViewportPlane(const OrthancStone::CoordinateSystem3D& plane) | |
1767 { | |
1768 first_ = false; | |
1769 lastPlane_ = plane; | |
1770 } | |
1771 | |
1772 void SetLayer(OrthancStone::ISceneLayer* layer) | |
1773 { | |
1774 scene_.SetLayer(layerDepth_, layer); | |
1775 } | |
1776 | |
1777 void DeleteLayer() | |
1778 { | |
1779 scene_.DeleteLayer(layerDepth_); | |
1780 } | |
1781 | |
1782 public: | |
1783 VolumeSlicerBase(OrthancStone::Scene2D& scene, | |
1784 int layerDepth) : | |
1785 scene_(scene), | |
1786 layerDepth_(layerDepth), | |
1787 first_(true) | |
1788 { | |
1789 } | |
1790 };*/ | |
1791 | |
1792 | |
1793 | |
1794 class DicomVolumeMPRSlicer : public IVolumeSlicer | |
1795 { | |
1796 private: | |
1797 bool linearInterpolation_; | |
1798 OrthancStone::Scene2D& scene_; | |
1799 int layerDepth_; | |
1800 IDicomVolumeSource& source_; | |
1801 bool first_; | |
1802 OrthancStone::VolumeProjection lastProjection_; | |
1803 unsigned int lastSliceIndex_; | |
1804 uint64_t lastSliceRevision_; | |
1805 | |
1806 public: | |
1807 DicomVolumeMPRSlicer(OrthancStone::Scene2D& scene, | |
1808 int layerDepth, | |
1809 IDicomVolumeSource& source) : | |
1810 linearInterpolation_(false), | |
1811 scene_(scene), | |
1812 layerDepth_(layerDepth), | |
1813 source_(source), | |
1814 first_(true) | |
1815 { | |
1816 } | |
1817 | |
1818 void SetLinearInterpolation(bool enabled) | |
1819 { | |
1820 linearInterpolation_ = enabled; | |
1821 } | |
1822 | |
1823 bool IsLinearInterpolation() const | |
1824 { | |
1825 return linearInterpolation_; | |
1826 } | |
1827 | |
1828 virtual void SetViewportPlane(const OrthancStone::CoordinateSystem3D& plane) | |
1829 { | |
1830 if (!source_.GetVolume().HasGeometry() || | |
1831 source_.GetVolume().GetSlicesCount() == 0) | |
1832 { | |
1833 scene_.DeleteLayer(layerDepth_); | |
1834 return; | |
1835 } | |
1836 | |
1837 const OrthancStone::VolumeImageGeometry& geometry = source_.GetVolume().GetImage().GetGeometry(); | |
1838 | |
1839 OrthancStone::VolumeProjection projection; | |
1840 unsigned int sliceIndex; | |
1841 if (!geometry.DetectSlice(projection, sliceIndex, plane)) | |
1842 { | |
1843 // The cutting plane is neither axial, nor coronal, nor | |
1844 // sagittal. Could use "VolumeReslicer" here. | |
1845 scene_.DeleteLayer(layerDepth_); | |
1846 return; | |
1847 } | |
1848 | |
1849 uint64_t sliceRevision; | |
1850 if (projection == OrthancStone::VolumeProjection_Axial) | |
1851 { | |
1852 sliceRevision = source_.GetVolume().GetSliceRevision(sliceIndex); | |
1853 | |
1854 if (first_ || | |
1855 lastSliceIndex_ != sliceIndex) | |
1856 { | |
1857 // Reorder the prefetching queue | |
1858 source_.NotifyAxialSliceAccessed(sliceIndex); | |
1859 } | |
1860 } | |
1861 else | |
1862 { | |
1863 // For coronal and sagittal projections, we take the global | |
1864 // revision of the volume | |
1865 sliceRevision = source_.GetVolume().GetRevision(); | |
1866 } | |
1867 | |
1868 if (first_ || | |
1869 lastProjection_ != projection || | |
1870 lastSliceIndex_ != sliceIndex || | |
1871 lastSliceRevision_ != sliceRevision) | |
1872 { | |
1873 // Either the viewport plane, or the content of the slice have not | |
1874 // changed since the last time the layer was set: Update is needed | |
1875 | |
1876 first_ = false; | |
1877 lastProjection_ = projection; | |
1878 lastSliceIndex_ = sliceIndex; | |
1879 lastSliceRevision_ = sliceRevision; | |
1880 | |
1881 std::auto_ptr<OrthancStone::TextureBaseSceneLayer> texture; | |
1882 | |
1883 { | |
1884 const DicomInstanceParameters& parameters = source_.GetVolume().GetSliceParameters | |
1885 (projection == OrthancStone::VolumeProjection_Axial ? sliceIndex : 0); | |
1886 | |
1887 OrthancStone::ImageBuffer3D::SliceReader reader(source_.GetVolume().GetImage(), projection, sliceIndex); | |
1888 texture.reset(parameters.CreateTexture(reader.GetAccessor())); | |
1889 } | |
1890 | |
1891 const OrthancStone::CoordinateSystem3D& system = geometry.GetProjectionGeometry(projection); | |
1892 | |
1893 double x0, y0, x1, y1; | |
1894 system.ProjectPoint(x0, y0, system.GetOrigin()); | |
1895 system.ProjectPoint(x0, y0, system.GetOrigin() + system.GetAxisX()); | |
1896 texture->SetOrigin(x0, y0); | |
1897 | |
1898 double dx = x1 - x0; | |
1899 double dy = y1 - y0; | |
1900 if (!OrthancStone::LinearAlgebra::IsCloseToZero(dx) || | |
1901 !OrthancStone::LinearAlgebra::IsCloseToZero(dy)) | |
1902 { | |
1903 texture->SetAngle(atan2(dy, dx)); | |
1904 } | |
1905 | |
1906 OrthancStone::Vector tmp; | |
1907 geometry.GetVoxelDimensions(projection); | |
1908 texture->SetPixelSpacing(tmp[0], tmp[1]); | |
1909 | |
1910 texture->SetLinearInterpolation(linearInterpolation_); | |
1911 | |
1912 scene_.SetLayer(layerDepth_, texture.release()); | |
1913 } | |
1914 } | |
1915 }; | |
1916 | |
1917 | |
1918 | |
1919 | |
1920 | |
1921 class NativeOracle : public IOracle | |
1922 { | |
1923 private: | |
1924 class Item : public Orthanc::IDynamicObject | |
1925 { | |
1926 private: | |
1927 const OrthancStone::IObserver& receiver_; | |
1928 std::auto_ptr<IOracleCommand> command_; | |
1929 | |
1930 public: | |
1931 Item(const OrthancStone::IObserver& receiver, | |
1932 IOracleCommand* command) : | |
1933 receiver_(receiver), | |
1934 command_(command) | |
1935 { | |
1936 if (command == NULL) | |
1937 { | |
1938 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
1939 } | |
1940 } | |
1941 | |
1942 const OrthancStone::IObserver& GetReceiver() const | |
1943 { | |
1944 return receiver_; | |
1945 } | |
1946 | |
1947 const IOracleCommand& GetCommand() const | |
1948 { | |
1949 assert(command_.get() != NULL); | |
1950 return *command_; | |
1951 } | |
1952 }; | |
1953 | |
1954 | |
1955 enum State | |
1956 { | |
1957 State_Setup, | |
1958 State_Running, | |
1959 State_Stopped | |
1960 }; | |
1961 | |
1962 | |
1963 IMessageEmitter& emitter_; | |
1964 Orthanc::WebServiceParameters orthanc_; | |
1965 Orthanc::SharedMessageQueue queue_; | |
1966 State state_; | |
1967 boost::mutex mutex_; | |
1968 std::vector<boost::thread*> workers_; | |
1969 | |
1970 | |
1971 void CopyHttpHeaders(Orthanc::HttpClient& client, | |
1972 const HttpHeaders& headers) | |
1973 { | |
1974 for (HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); it++ ) | |
1975 { | |
1976 client.AddHeader(it->first, it->second); | |
1977 } | |
1978 } | |
1979 | |
1980 | |
1981 void DecodeAnswer(std::string& answer, | |
1982 const HttpHeaders& headers) | |
1983 { | |
1984 Orthanc::HttpCompression contentEncoding = Orthanc::HttpCompression_None; | |
1985 | |
1986 for (HttpHeaders::const_iterator it = headers.begin(); | |
1987 it != headers.end(); ++it) | |
1988 { | |
1989 std::string s; | |
1990 Orthanc::Toolbox::ToLowerCase(s, it->first); | |
1991 | |
1992 if (s == "content-encoding") | |
1993 { | |
1994 if (it->second == "gzip") | |
1995 { | |
1996 contentEncoding = Orthanc::HttpCompression_Gzip; | |
1997 } | |
1998 else | |
1999 { | |
2000 throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol, | |
2001 "Unsupported HTTP Content-Encoding: " + it->second); | |
2002 } | |
2003 | |
2004 break; | |
2005 } | |
2006 } | |
2007 | |
2008 if (contentEncoding == Orthanc::HttpCompression_Gzip) | |
2009 { | |
2010 std::string compressed; | |
2011 answer.swap(compressed); | |
2012 | |
2013 Orthanc::GzipCompressor compressor; | |
2014 compressor.Uncompress(answer, compressed.c_str(), compressed.size()); | |
2015 } | |
2016 } | |
2017 | |
2018 | |
2019 void Execute(const OrthancStone::IObserver& receiver, | |
2020 const OrthancRestApiCommand& command) | |
2021 { | |
2022 Orthanc::HttpClient client(orthanc_, command.GetUri()); | |
2023 client.SetMethod(command.GetMethod()); | |
2024 client.SetTimeout(command.GetTimeout()); | |
2025 | |
2026 CopyHttpHeaders(client, command.GetHttpHeaders()); | |
2027 | |
2028 if (command.GetMethod() == Orthanc::HttpMethod_Post || | |
2029 command.GetMethod() == Orthanc::HttpMethod_Put) | |
2030 { | |
2031 client.SetBody(command.GetBody()); | |
2032 } | |
2033 | |
2034 std::string answer; | |
2035 HttpHeaders answerHeaders; | |
2036 client.ApplyAndThrowException(answer, answerHeaders); | |
2037 | |
2038 DecodeAnswer(answer, answerHeaders); | |
2039 | |
2040 OrthancRestApiCommand::SuccessMessage message(command, answerHeaders, answer); | |
2041 emitter_.EmitMessage(receiver, message); | |
2042 } | |
2043 | |
2044 | |
2045 void Execute(const OrthancStone::IObserver& receiver, | |
2046 const GetOrthancImageCommand& command) | |
2047 { | |
2048 Orthanc::HttpClient client(orthanc_, command.GetUri()); | |
2049 client.SetTimeout(command.GetTimeout()); | |
2050 | |
2051 CopyHttpHeaders(client, command.GetHttpHeaders()); | |
2052 | |
2053 std::string answer; | |
2054 HttpHeaders answerHeaders; | |
2055 client.ApplyAndThrowException(answer, answerHeaders); | |
2056 | |
2057 DecodeAnswer(answer, answerHeaders); | |
2058 | |
2059 command.ProcessHttpAnswer(emitter_, receiver, answer, answerHeaders); | |
2060 } | |
2061 | |
2062 | |
2063 void Execute(const OrthancStone::IObserver& receiver, | |
2064 const GetOrthancWebViewerJpegCommand& command) | |
2065 { | |
2066 Orthanc::HttpClient client(orthanc_, command.GetUri()); | |
2067 client.SetTimeout(command.GetTimeout()); | |
2068 | |
2069 CopyHttpHeaders(client, command.GetHttpHeaders()); | |
2070 | |
2071 std::string answer; | |
2072 HttpHeaders answerHeaders; | |
2073 client.ApplyAndThrowException(answer, answerHeaders); | |
2074 | |
2075 DecodeAnswer(answer, answerHeaders); | |
2076 | |
2077 command.ProcessHttpAnswer(emitter_, receiver, answer); | |
2078 } | |
2079 | |
2080 | |
2081 void Step() | |
2082 { | |
2083 std::auto_ptr<Orthanc::IDynamicObject> object(queue_.Dequeue(100)); | |
2084 | |
2085 if (object.get() != NULL) | |
2086 { | |
2087 const Item& item = dynamic_cast<Item&>(*object); | |
2088 | |
2089 try | |
2090 { | |
2091 switch (item.GetCommand().GetType()) | |
2092 { | |
2093 case IOracleCommand::Type_OrthancRestApi: | |
2094 Execute(item.GetReceiver(), | |
2095 dynamic_cast<const OrthancRestApiCommand&>(item.GetCommand())); | |
2096 break; | |
2097 | |
2098 case IOracleCommand::Type_GetOrthancImage: | |
2099 Execute(item.GetReceiver(), | |
2100 dynamic_cast<const GetOrthancImageCommand&>(item.GetCommand())); | |
2101 break; | |
2102 | |
2103 case IOracleCommand::Type_GetOrthancWebViewerJpeg: | |
2104 Execute(item.GetReceiver(), | |
2105 dynamic_cast<const GetOrthancWebViewerJpegCommand&>(item.GetCommand())); | |
2106 break; | |
2107 | |
2108 default: | |
2109 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
2110 } | |
2111 } | |
2112 catch (Orthanc::OrthancException& e) | |
2113 { | |
2114 LOG(ERROR) << "Exception within the oracle: " << e.What(); | |
2115 emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage(item.GetCommand(), e)); | |
2116 } | |
2117 catch (...) | |
2118 { | |
2119 LOG(ERROR) << "Native exception within the oracle"; | |
2120 emitter_.EmitMessage(item.GetReceiver(), OracleCommandExceptionMessage | |
2121 (item.GetCommand(), Orthanc::ErrorCode_InternalError)); | |
2122 } | |
2123 } | |
2124 } | |
2125 | |
2126 | |
2127 static void Worker(NativeOracle* that) | |
2128 { | |
2129 assert(that != NULL); | |
2130 | |
2131 for (;;) | |
2132 { | |
2133 { | |
2134 boost::mutex::scoped_lock lock(that->mutex_); | |
2135 if (that->state_ != State_Running) | |
2136 { | |
2137 return; | |
2138 } | |
2139 } | |
2140 | |
2141 that->Step(); | |
2142 } | |
2143 } | |
2144 | |
2145 | |
2146 void StopInternal() | |
2147 { | |
2148 { | |
2149 boost::mutex::scoped_lock lock(mutex_); | |
2150 | |
2151 if (state_ == State_Setup || | |
2152 state_ == State_Stopped) | |
2153 { | |
2154 return; | |
2155 } | |
2156 else | |
2157 { | |
2158 state_ = State_Stopped; | |
2159 } | |
2160 } | |
2161 | |
2162 for (size_t i = 0; i < workers_.size(); i++) | |
2163 { | |
2164 if (workers_[i] != NULL) | |
2165 { | |
2166 if (workers_[i]->joinable()) | |
2167 { | |
2168 workers_[i]->join(); | |
2169 } | |
2170 | |
2171 delete workers_[i]; | |
2172 } | |
2173 } | |
2174 } | |
2175 | |
2176 | |
2177 public: | |
2178 NativeOracle(IMessageEmitter& emitter) : | |
2179 emitter_(emitter), | |
2180 state_(State_Setup), | |
2181 workers_(4) | |
2182 { | |
2183 } | |
2184 | |
2185 virtual ~NativeOracle() | |
2186 { | |
2187 StopInternal(); | |
2188 } | |
2189 | |
2190 void SetOrthancParameters(const Orthanc::WebServiceParameters& orthanc) | |
2191 { | |
2192 boost::mutex::scoped_lock lock(mutex_); | |
2193 | |
2194 if (state_ != State_Setup) | |
2195 { | |
2196 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
2197 } | |
2198 else | |
2199 { | |
2200 orthanc_ = orthanc; | |
2201 } | |
2202 } | |
2203 | |
2204 void SetWorkersCount(unsigned int count) | |
2205 { | |
2206 boost::mutex::scoped_lock lock(mutex_); | |
2207 | |
2208 if (count <= 0) | |
2209 { | |
2210 throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); | |
2211 } | |
2212 else if (state_ != State_Setup) | |
2213 { | |
2214 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
2215 } | |
2216 else | |
2217 { | |
2218 workers_.resize(count); | |
2219 } | |
2220 } | |
2221 | |
2222 void Start() | |
2223 { | |
2224 boost::mutex::scoped_lock lock(mutex_); | |
2225 | |
2226 if (state_ != State_Setup) | |
2227 { | |
2228 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
2229 } | |
2230 else | |
2231 { | |
2232 state_ = State_Running; | |
2233 | |
2234 for (unsigned int i = 0; i < workers_.size(); i++) | |
2235 { | |
2236 workers_[i] = new boost::thread(Worker, this); | |
2237 } | |
2238 } | |
2239 } | |
2240 | |
2241 void Stop() | |
2242 { | |
2243 StopInternal(); | |
2244 } | |
2245 | |
2246 virtual void Schedule(const OrthancStone::IObserver& receiver, | |
2247 IOracleCommand* command) | |
2248 { | |
2249 queue_.Enqueue(new Item(receiver, command)); | |
2250 } | |
2251 }; | |
2252 | |
2253 | |
2254 class NativeApplicationContext : public IMessageEmitter | |
2255 { | |
2256 private: | |
2257 boost::shared_mutex mutex_; | |
2258 OrthancStone::MessageBroker broker_; | |
2259 OrthancStone::IObservable oracleObservable_; | |
2260 | 51 |
2261 public: | 52 public: |
2262 NativeApplicationContext() : | 53 NativeApplicationContext() : |
2263 oracleObservable_(broker_) | 54 oracleObservable_(broker_) |
2264 { | 55 { |
2265 } | 56 } |
2266 | 57 |
2267 | 58 |
2268 virtual void EmitMessage(const OrthancStone::IObserver& observer, | 59 virtual void EmitMessage(const IObserver& observer, |
2269 const OrthancStone::IMessage& message) | 60 const IMessage& message) ORTHANC_OVERRIDE |
2270 { | 61 { |
2271 try | 62 try |
2272 { | 63 { |
2273 boost::unique_lock<boost::shared_mutex> lock(mutex_); | 64 boost::unique_lock<boost::shared_mutex> lock(mutex_); |
2274 oracleObservable_.EmitMessage(observer, message); | 65 oracleObservable_.EmitMessage(observer, message); |
2306 that_(that), | 97 that_(that), |
2307 lock_(that.mutex_) | 98 lock_(that.mutex_) |
2308 { | 99 { |
2309 } | 100 } |
2310 | 101 |
2311 OrthancStone::MessageBroker& GetBroker() | 102 MessageBroker& GetBroker() |
2312 { | 103 { |
2313 return that_.broker_; | 104 return that_.broker_; |
2314 } | 105 } |
2315 | 106 |
2316 OrthancStone::IObservable& GetOracleObservable() | 107 IObservable& GetOracleObservable() |
2317 { | 108 { |
2318 return that_.oracleObservable_; | 109 return that_.oracleObservable_; |
2319 } | 110 } |
2320 }; | 111 }; |
2321 }; | 112 }; |
2324 | 115 |
2325 | 116 |
2326 class Toto : public OrthancStone::IObserver | 117 class Toto : public OrthancStone::IObserver |
2327 { | 118 { |
2328 private: | 119 private: |
2329 void Handle(const Refactoring::OrthancRestApiCommand::SuccessMessage& message) | 120 OrthancStone::CoordinateSystem3D plane_; |
121 OrthancStone::IOracle& oracle_; | |
122 OrthancStone::Scene2D scene_; | |
123 std::auto_ptr<OrthancStone::VolumeSceneLayerSource> source1_, source2_, source3_; | |
124 | |
125 | |
126 void Refresh() | |
127 { | |
128 if (source1_.get() != NULL) | |
129 { | |
130 source1_->Update(plane_); | |
131 } | |
132 | |
133 if (source2_.get() != NULL) | |
134 { | |
135 source2_->Update(plane_); | |
136 } | |
137 | |
138 if (source3_.get() != NULL) | |
139 { | |
140 source3_->Update(plane_); | |
141 } | |
142 | |
143 scene_.FitContent(1024, 768); | |
144 | |
145 { | |
146 OrthancStone::CairoCompositor compositor(scene_, 1024, 768); | |
147 compositor.Refresh(); | |
148 | |
149 Orthanc::ImageAccessor accessor; | |
150 compositor.GetCanvas().GetReadOnlyAccessor(accessor); | |
151 | |
152 Orthanc::Image tmp(Orthanc::PixelFormat_RGB24, accessor.GetWidth(), accessor.GetHeight(), false); | |
153 Orthanc::ImageProcessing::Convert(tmp, accessor); | |
154 | |
155 static unsigned int count = 0; | |
156 char buf[64]; | |
157 sprintf(buf, "scene-%06d.png", count++); | |
158 | |
159 Orthanc::PngWriter writer; | |
160 writer.WriteToFile(buf, tmp); | |
161 } | |
162 } | |
163 | |
164 | |
165 void Handle(const OrthancStone::DicomVolumeImage::GeometryReadyMessage& message) | |
166 { | |
167 printf("Geometry ready\n"); | |
168 | |
169 plane_ = message.GetOrigin().GetGeometry().GetSagittalGeometry(); | |
170 //plane_ = message.GetOrigin().GetGeometry().GetAxialGeometry(); | |
171 //plane_ = message.GetOrigin().GetGeometry().GetCoronalGeometry(); | |
172 plane_.SetOrigin(message.GetOrigin().GetGeometry().GetCoordinates(0.5f, 0.5f, 0.5f)); | |
173 | |
174 Refresh(); | |
175 } | |
176 | |
177 | |
178 void Handle(const OrthancStone::SleepOracleCommand::TimeoutMessage& message) | |
179 { | |
180 if (message.GetOrigin().HasPayload()) | |
181 { | |
182 printf("TIMEOUT! %d\n", dynamic_cast<const Orthanc::SingleValueObject<unsigned int>& >(message.GetOrigin().GetPayload()).GetValue()); | |
183 } | |
184 else | |
185 { | |
186 printf("TIMEOUT\n"); | |
187 | |
188 Refresh(); | |
189 | |
190 /** | |
191 * The sleep() leads to a crash if the oracle is still running, | |
192 * while this object is destroyed. Always stop the oracle before | |
193 * destroying active objects. (*) | |
194 **/ | |
195 // boost::this_thread::sleep(boost::posix_time::seconds(2)); | |
196 | |
197 oracle_.Schedule(*this, new OrthancStone::SleepOracleCommand(message.GetOrigin().GetDelay())); | |
198 } | |
199 } | |
200 | |
201 void Handle(const OrthancStone::OrthancRestApiCommand::SuccessMessage& message) | |
2330 { | 202 { |
2331 Json::Value v; | 203 Json::Value v; |
2332 message.ParseJsonBody(v); | 204 message.ParseJsonBody(v); |
2333 | 205 |
2334 printf("ICI [%s]\n", v.toStyledString().c_str()); | 206 printf("ICI [%s]\n", v.toStyledString().c_str()); |
2335 } | 207 } |
2336 | 208 |
2337 void Handle(const Refactoring::GetOrthancImageCommand::SuccessMessage& message) | 209 void Handle(const OrthancStone::GetOrthancImageCommand::SuccessMessage& message) |
2338 { | 210 { |
2339 printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); | 211 printf("IMAGE %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); |
2340 } | 212 } |
2341 | 213 |
2342 void Handle(const Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage& message) | 214 void Handle(const OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage& message) |
2343 { | 215 { |
2344 printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); | 216 printf("WebViewer %dx%d\n", message.GetImage().GetWidth(), message.GetImage().GetHeight()); |
2345 } | 217 } |
2346 | 218 |
2347 void Handle(const Refactoring::OracleCommandExceptionMessage& message) | 219 void Handle(const OrthancStone::OracleCommandExceptionMessage& message) |
2348 { | 220 { |
2349 printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType()); | 221 printf("EXCEPTION: [%s] on command type %d\n", message.GetException().What(), message.GetCommand().GetType()); |
2350 | 222 |
2351 switch (message.GetCommand().GetType()) | 223 switch (message.GetCommand().GetType()) |
2352 { | 224 { |
2353 case Refactoring::IOracleCommand::Type_GetOrthancWebViewerJpeg: | 225 case OrthancStone::IOracleCommand::Type_GetOrthancWebViewerJpeg: |
2354 printf("URI: [%s]\n", dynamic_cast<const Refactoring::GetOrthancWebViewerJpegCommand&> | 226 printf("URI: [%s]\n", dynamic_cast<const OrthancStone::GetOrthancWebViewerJpegCommand&> |
2355 (message.GetCommand()).GetUri().c_str()); | 227 (message.GetCommand()).GetUri().c_str()); |
2356 break; | 228 break; |
2357 | 229 |
2358 default: | 230 default: |
2359 break; | 231 break; |
2360 } | 232 } |
2361 } | 233 } |
2362 | 234 |
2363 public: | 235 public: |
2364 Toto(OrthancStone::IObservable& oracle) : | 236 Toto(OrthancStone::IOracle& oracle, |
2365 IObserver(oracle.GetBroker()) | 237 OrthancStone::IObservable& oracleObservable) : |
2366 { | 238 IObserver(oracleObservable.GetBroker()), |
2367 oracle.RegisterObserverCallback | 239 oracle_(oracle) |
240 { | |
241 oracleObservable.RegisterObserverCallback | |
2368 (new OrthancStone::Callable | 242 (new OrthancStone::Callable |
2369 <Toto, Refactoring::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle)); | 243 <Toto, OrthancStone::SleepOracleCommand::TimeoutMessage>(*this, &Toto::Handle)); |
2370 | 244 |
2371 oracle.RegisterObserverCallback | 245 oracleObservable.RegisterObserverCallback |
2372 (new OrthancStone::Callable | 246 (new OrthancStone::Callable |
2373 <Toto, Refactoring::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle)); | 247 <Toto, OrthancStone::OrthancRestApiCommand::SuccessMessage>(*this, &Toto::Handle)); |
2374 | 248 |
2375 oracle.RegisterObserverCallback | 249 oracleObservable.RegisterObserverCallback |
2376 (new OrthancStone::Callable | 250 (new OrthancStone::Callable |
2377 <Toto, Refactoring::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle)); | 251 <Toto, OrthancStone::GetOrthancImageCommand::SuccessMessage>(*this, &Toto::Handle)); |
2378 | 252 |
2379 oracle.RegisterObserverCallback | 253 oracleObservable.RegisterObserverCallback |
2380 (new OrthancStone::Callable | 254 (new OrthancStone::Callable |
2381 <Toto, Refactoring::OracleCommandExceptionMessage>(*this, &Toto::Handle)); | 255 <Toto, OrthancStone::GetOrthancWebViewerJpegCommand::SuccessMessage>(*this, &Toto::Handle)); |
2382 } | 256 |
257 oracleObservable.RegisterObserverCallback | |
258 (new OrthancStone::Callable | |
259 <Toto, OrthancStone::OracleCommandExceptionMessage>(*this, &Toto::Handle)); | |
260 } | |
261 | |
262 void SetReferenceLoader(OrthancStone::IObservable& loader) | |
263 { | |
264 loader.RegisterObserverCallback | |
265 (new OrthancStone::Callable | |
266 <Toto, OrthancStone::DicomVolumeImage::GeometryReadyMessage>(*this, &Toto::Handle)); | |
267 } | |
268 | |
269 void SetVolume1(int depth, | |
270 const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, | |
271 OrthancStone::ILayerStyleConfigurator* style) | |
272 { | |
273 source1_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); | |
274 | |
275 if (style != NULL) | |
276 { | |
277 source1_->SetConfigurator(style); | |
278 } | |
279 } | |
280 | |
281 void SetVolume2(int depth, | |
282 const boost::shared_ptr<OrthancStone::IVolumeSlicer>& volume, | |
283 OrthancStone::ILayerStyleConfigurator* style) | |
284 { | |
285 source2_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); | |
286 | |
287 if (style != NULL) | |
288 { | |
289 source2_->SetConfigurator(style); | |
290 } | |
291 } | |
292 | |
293 void SetStructureSet(int depth, | |
294 const boost::shared_ptr<OrthancStone::DicomStructureSetLoader>& volume) | |
295 { | |
296 source3_.reset(new OrthancStone::VolumeSceneLayerSource(scene_, depth, volume)); | |
297 } | |
298 | |
2383 }; | 299 }; |
2384 | 300 |
2385 | 301 |
2386 void Run(Refactoring::NativeApplicationContext& context, | 302 void Run(OrthancStone::NativeApplicationContext& context, |
2387 Refactoring::IOracle& oracle) | 303 OrthancStone::ThreadedOracle& oracle) |
2388 { | 304 { |
2389 std::auto_ptr<Toto> toto; | 305 // the oracle has been supplied with the context (as an IEmitter) upon |
2390 std::auto_ptr<Refactoring::VolumeSeriesOrthancLoader> loader1, loader2; | 306 // creation |
2391 | 307 boost::shared_ptr<OrthancStone::DicomVolumeImage> ct(new OrthancStone::DicomVolumeImage); |
2392 { | 308 boost::shared_ptr<OrthancStone::DicomVolumeImage> dose(new OrthancStone::DicomVolumeImage); |
2393 Refactoring::NativeApplicationContext::WriterLock lock(context); | 309 |
2394 toto.reset(new Toto(lock.GetOracleObservable())); | 310 |
2395 loader1.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable())); | 311 boost::shared_ptr<Toto> toto; |
2396 loader2.reset(new Refactoring::VolumeSeriesOrthancLoader(oracle, lock.GetOracleObservable())); | 312 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> ctLoader; |
2397 } | 313 boost::shared_ptr<OrthancStone::OrthancMultiframeVolumeLoader> doseLoader; |
2398 | 314 boost::shared_ptr<OrthancStone::DicomStructureSetLoader> rtstructLoader; |
2399 if (1) | 315 |
316 { | |
317 OrthancStone::NativeApplicationContext::WriterLock lock(context); | |
318 toto.reset(new Toto(oracle, lock.GetOracleObservable())); | |
319 | |
320 // the oracle is used to schedule commands | |
321 // the oracleObservable is used by the loaders to: | |
322 // - request the broker (lifetime mgmt) | |
323 // - register the loader callbacks (called indirectly by the oracle) | |
324 ctLoader.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct, oracle, lock.GetOracleObservable())); | |
325 doseLoader.reset(new OrthancStone::OrthancMultiframeVolumeLoader(dose, oracle, lock.GetOracleObservable())); | |
326 rtstructLoader.reset(new OrthancStone::DicomStructureSetLoader(oracle, lock.GetOracleObservable())); | |
327 } | |
328 | |
329 | |
330 //toto->SetReferenceLoader(*ctLoader); | |
331 toto->SetReferenceLoader(*doseLoader); | |
332 | |
333 | |
334 #if 1 | |
335 toto->SetVolume1(0, ctLoader, new OrthancStone::GrayscaleStyleConfigurator); | |
336 #else | |
337 { | |
338 boost::shared_ptr<OrthancStone::IVolumeSlicer> reslicer(new OrthancStone::DicomVolumeImageReslicer(ct)); | |
339 toto->SetVolume1(0, reslicer, new OrthancStone::GrayscaleStyleConfigurator); | |
340 } | |
341 #endif | |
342 | |
343 | |
344 { | |
345 std::auto_ptr<OrthancStone::LookupTableStyleConfigurator> config(new OrthancStone::LookupTableStyleConfigurator); | |
346 config->SetLookupTable(Orthanc::EmbeddedResources::COLORMAP_HOT); | |
347 | |
348 boost::shared_ptr<OrthancStone::DicomVolumeImageMPRSlicer> tmp(new OrthancStone::DicomVolumeImageMPRSlicer(dose)); | |
349 toto->SetVolume2(1, tmp, config.release()); | |
350 } | |
351 | |
352 toto->SetStructureSet(2, rtstructLoader); | |
353 | |
354 oracle.Schedule(*toto, new OrthancStone::SleepOracleCommand(100)); | |
355 | |
356 if (0) | |
2400 { | 357 { |
2401 Json::Value v = Json::objectValue; | 358 Json::Value v = Json::objectValue; |
2402 v["Level"] = "Series"; | 359 v["Level"] = "Series"; |
2403 v["Query"] = Json::objectValue; | 360 v["Query"] = Json::objectValue; |
2404 | 361 |
2405 std::auto_ptr<Refactoring::OrthancRestApiCommand> command(new Refactoring::OrthancRestApiCommand); | 362 std::auto_ptr<OrthancStone::OrthancRestApiCommand> command(new OrthancStone::OrthancRestApiCommand); |
2406 command->SetMethod(Orthanc::HttpMethod_Post); | 363 command->SetMethod(Orthanc::HttpMethod_Post); |
2407 command->SetUri("/tools/find"); | 364 command->SetUri("/tools/find"); |
2408 command->SetBody(v); | 365 command->SetBody(v); |
2409 | 366 |
2410 oracle.Schedule(*toto, command.release()); | 367 oracle.Schedule(*toto, command.release()); |
2411 } | 368 } |
2412 | 369 |
2413 if (1) | 370 if(0) |
2414 { | 371 { |
2415 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); | 372 if (0) |
2416 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg))); | 373 { |
2417 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); | 374 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); |
2418 oracle.Schedule(*toto, command.release()); | 375 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Jpeg))); |
2419 } | 376 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); |
2420 | 377 oracle.Schedule(*toto, command.release()); |
2421 if (1) | 378 } |
2422 { | 379 |
2423 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); | 380 if (0) |
2424 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); | 381 { |
2425 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); | 382 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); |
2426 oracle.Schedule(*toto, command.release()); | 383 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); |
2427 } | 384 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/preview"); |
2428 | 385 oracle.Schedule(*toto, command.release()); |
2429 if (1) | 386 } |
2430 { | 387 |
2431 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); | 388 if (0) |
2432 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); | 389 { |
2433 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); | 390 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); |
2434 oracle.Schedule(*toto, command.release()); | 391 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Png))); |
2435 } | 392 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); |
2436 | 393 oracle.Schedule(*toto, command.release()); |
2437 if (1) | 394 } |
2438 { | 395 |
2439 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); | 396 if (0) |
2440 command->SetHttpHeader("Accept-Encoding", "gzip"); | 397 { |
2441 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); | 398 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); |
2442 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); | 399 command->SetHttpHeader("Accept-Encoding", "gzip"); |
2443 oracle.Schedule(*toto, command.release()); | 400 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); |
2444 } | 401 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); |
2445 | 402 oracle.Schedule(*toto, command.release()); |
2446 if (1) | 403 } |
2447 { | 404 |
2448 std::auto_ptr<Refactoring::GetOrthancImageCommand> command(new Refactoring::GetOrthancImageCommand); | 405 if (0) |
2449 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); | 406 { |
2450 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); | 407 std::auto_ptr<OrthancStone::GetOrthancImageCommand> command(new OrthancStone::GetOrthancImageCommand); |
2451 oracle.Schedule(*toto, command.release()); | 408 command->SetHttpHeader("Accept", std::string(Orthanc::EnumerationToString(Orthanc::MimeType_Pam))); |
2452 } | 409 command->SetUri("/instances/6687cc73-07cae193-52ff29c8-f646cb16-0753ed92/image-uint16"); |
2453 | 410 oracle.Schedule(*toto, command.release()); |
2454 if (1) | 411 } |
2455 { | 412 |
2456 std::auto_ptr<Refactoring::GetOrthancWebViewerJpegCommand> command(new Refactoring::GetOrthancWebViewerJpegCommand); | 413 if (0) |
2457 command->SetHttpHeader("Accept-Encoding", "gzip"); | 414 { |
2458 command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e"); | 415 std::auto_ptr<OrthancStone::GetOrthancWebViewerJpegCommand> command(new OrthancStone::GetOrthancWebViewerJpegCommand); |
2459 command->SetQuality(90); | 416 command->SetHttpHeader("Accept-Encoding", "gzip"); |
2460 oracle.Schedule(*toto, command.release()); | 417 command->SetInstance("e6c7c20b-c9f65d7e-0d76f2e2-830186f2-3e3c600e"); |
2461 } | 418 command->SetQuality(90); |
2462 | 419 oracle.Schedule(*toto, command.release()); |
2463 | 420 } |
421 | |
422 | |
423 if (0) | |
424 { | |
425 for (unsigned int i = 0; i < 10; i++) | |
426 { | |
427 std::auto_ptr<OrthancStone::SleepOracleCommand> command(new OrthancStone::SleepOracleCommand(i * 1000)); | |
428 command->SetPayload(new Orthanc::SingleValueObject<unsigned int>(42 * i)); | |
429 oracle.Schedule(*toto, command.release()); | |
430 } | |
431 } | |
432 } | |
433 | |
2464 // 2017-11-17-Anonymized | 434 // 2017-11-17-Anonymized |
2465 //loader1->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT | 435 #if 0 |
2466 //loader2->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE | 436 // BGO data |
2467 | 437 ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT |
438 doseLoader->LoadInstance("830a69ff-8e4b5ee3-b7f966c8-bccc20fb-d322dceb"); // RT-DOSE | |
439 //rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT | |
440 #else | |
441 //ctLoader->LoadSeries("cb3ea4d1-d08f3856-ad7b6314-74d88d77-60b05618"); // CT | |
442 //doseLoader->LoadInstance("41029085-71718346-811efac4-420e2c15-d39f99b6"); // RT-DOSE | |
443 //rtstructLoader->LoadInstance("83d9c0c3-913a7fee-610097d7-cbf0522d-fd75bee6"); // RT-STRUCT | |
444 | |
445 // 2017-05-16 | |
446 ctLoader->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); // CT | |
447 doseLoader->LoadInstance("eac822ef-a395f94e-e8121fe0-8411fef8-1f7bffad"); // RT-DOSE | |
448 rtstructLoader->LoadInstance("54460695-ba3885ee-ddf61ac0-f028e31d-a6e474d9"); // RT-STRUCT | |
449 #endif | |
450 // 2015-01-28-Multiframe | |
451 //doseLoader->LoadInstance("88f71e2a-5fad1c61-96ed14d6-5b3d3cf7-a5825279"); // Multiframe CT | |
452 | |
2468 // Delphine | 453 // Delphine |
2469 loader1->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT | 454 //ctLoader->LoadSeries("5990e39c-51e5f201-fe87a54c-31a55943-e59ef80e"); // CT |
2470 | 455 //ctLoader->LoadSeries("67f1b334-02c16752-45026e40-a5b60b6b-030ecab5"); // Lung 1/10mm |
2471 LOG(WARNING) << "...Waiting for Ctrl-C..."; | 456 |
2472 Orthanc::SystemToolbox::ServerBarrier(); | 457 |
2473 //boost::this_thread::sleep(boost::posix_time::seconds(1)); | 458 { |
459 LOG(WARNING) << "...Waiting for Ctrl-C..."; | |
460 | |
461 oracle.Start(); | |
462 | |
463 Orthanc::SystemToolbox::ServerBarrier(); | |
464 | |
465 /** | |
466 * WARNING => The oracle must be stopped BEFORE the objects using | |
467 * it are destroyed!!! This forces to wait for the completion of | |
468 * the running callback methods. Otherwise, the callbacks methods | |
469 * might still be running while their parent object is destroyed, | |
470 * resulting in crashes. This is very visible if adding a sleep(), | |
471 * as in (*). | |
472 **/ | |
473 | |
474 oracle.Stop(); | |
475 } | |
2474 } | 476 } |
2475 | 477 |
2476 | 478 |
2477 | 479 |
2478 /** | 480 /** |
2481 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows | 483 * to `SDL_main'". https://wiki.libsdl.org/FAQWindows |
2482 **/ | 484 **/ |
2483 int main(int argc, char* argv[]) | 485 int main(int argc, char* argv[]) |
2484 { | 486 { |
2485 OrthancStone::StoneInitialize(); | 487 OrthancStone::StoneInitialize(); |
2486 Orthanc::Logging::EnableInfoLevel(true); | 488 //Orthanc::Logging::EnableInfoLevel(true); |
2487 | 489 |
2488 try | 490 try |
2489 { | 491 { |
2490 Refactoring::NativeApplicationContext context; | 492 OrthancStone::NativeApplicationContext context; |
2491 | 493 |
2492 Refactoring::NativeOracle oracle(context); | 494 OrthancStone::ThreadedOracle oracle(context); |
495 //oracle.SetThreadsCount(1); | |
2493 | 496 |
2494 { | 497 { |
2495 Orthanc::WebServiceParameters p; | 498 Orthanc::WebServiceParameters p; |
2496 //p.SetUrl("http://localhost:8043/"); | 499 //p.SetUrl("http://localhost:8043/"); |
2497 p.SetCredentials("orthanc", "orthanc"); | 500 p.SetCredentials("orthanc", "orthanc"); |
2498 oracle.SetOrthancParameters(p); | 501 oracle.SetOrthancParameters(p); |
2499 } | 502 } |
2500 | 503 |
2501 oracle.Start(); | 504 //oracle.Start(); |
2502 | 505 |
2503 Run(context, oracle); | 506 Run(context, oracle); |
2504 | 507 |
2505 oracle.Stop(); | 508 //oracle.Stop(); |
2506 } | 509 } |
2507 catch (Orthanc::OrthancException& e) | 510 catch (Orthanc::OrthancException& e) |
2508 { | 511 { |
2509 LOG(ERROR) << "EXCEPTION: " << e.What(); | 512 LOG(ERROR) << "EXCEPTION: " << e.What(); |
2510 } | 513 } |