Mercurial > hg > orthanc-stone
comparison Samples/WebAssembly/BasicMPR.cpp @ 820:270c31978df1
BasicMPR sample
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 29 May 2019 13:40:07 +0200 |
parents | |
children | 76e8224bc300 |
comparison
equal
deleted
inserted
replaced
819:a68cd7ae8838 | 820:270c31978df1 |
---|---|
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 | |
23 #include <emscripten.h> | |
24 #include <emscripten/fetch.h> | |
25 #include <emscripten/html5.h> | |
26 | |
27 #include "../../Framework/Loaders/OrthancSeriesVolumeProgressiveLoader.h" | |
28 #include "../../Framework/OpenGL/WebAssemblyOpenGLContext.h" | |
29 #include "../../Framework/Scene2D/GrayscaleStyleConfigurator.h" | |
30 #include "../../Framework/Scene2D/OpenGLCompositor.h" | |
31 #include "../../Framework/Scene2D/PanSceneTracker.h" | |
32 #include "../../Framework/Scene2D/RotateSceneTracker.h" | |
33 #include "../../Framework/Scene2D/ZoomSceneTracker.h" | |
34 #include "../../Framework/Scene2DViewport/IFlexiblePointerTracker.h" | |
35 #include "../../Framework/Scene2DViewport/ViewportController.h" | |
36 #include "../../Framework/StoneInitialization.h" | |
37 #include "../../Framework/Volumes/VolumeSceneLayerSource.h" | |
38 | |
39 #include <Core/OrthancException.h> | |
40 #include <Core/Toolbox.h> | |
41 | |
42 | |
43 static const unsigned int FONT_SIZE = 32; | |
44 | |
45 | |
46 namespace OrthancStone | |
47 { | |
48 class WebAssemblyViewport : public boost::noncopyable | |
49 { | |
50 private: | |
51 // the construction order is important because compositor_ | |
52 // will hold a reference to the scene that belong to the | |
53 // controller_ object | |
54 OpenGL::WebAssemblyOpenGLContext context_; | |
55 boost::shared_ptr<ViewportController> controller_; | |
56 OpenGLCompositor compositor_; | |
57 | |
58 void SetupEvents(const std::string& canvas); | |
59 | |
60 public: | |
61 WebAssemblyViewport(MessageBroker& broker, | |
62 const std::string& canvas) : | |
63 context_(canvas), | |
64 controller_(new ViewportController(broker)), | |
65 compositor_(context_, *controller_->GetScene()) | |
66 { | |
67 compositor_.SetFont(0, Orthanc::EmbeddedResources::UBUNTU_FONT, | |
68 FONT_SIZE, Orthanc::Encoding_Latin1); | |
69 SetupEvents(canvas); | |
70 } | |
71 | |
72 Scene2D& GetScene() | |
73 { | |
74 return *controller_->GetScene(); | |
75 } | |
76 | |
77 const boost::shared_ptr<ViewportController>& GetController() | |
78 { | |
79 return controller_; | |
80 } | |
81 | |
82 void UpdateSize() | |
83 { | |
84 context_.UpdateSize(); | |
85 compositor_.UpdateSize(); | |
86 Refresh(); | |
87 } | |
88 | |
89 void Refresh() | |
90 { | |
91 compositor_.Refresh(); | |
92 } | |
93 | |
94 const std::string& GetCanvasIdentifier() const | |
95 { | |
96 return context_.GetCanvasIdentifier(); | |
97 } | |
98 | |
99 ScenePoint2D GetPixelCenterCoordinates(int x, int y) const | |
100 { | |
101 return compositor_.GetPixelCenterCoordinates(x, y); | |
102 } | |
103 | |
104 unsigned int GetCanvasWidth() const | |
105 { | |
106 return context_.GetCanvasWidth(); | |
107 } | |
108 | |
109 unsigned int GetCanvasHeight() const | |
110 { | |
111 return context_.GetCanvasHeight(); | |
112 } | |
113 }; | |
114 | |
115 class ActiveTracker : public boost::noncopyable | |
116 { | |
117 private: | |
118 boost::shared_ptr<IFlexiblePointerTracker> tracker_; | |
119 std::string canvasIdentifier_; | |
120 bool insideCanvas_; | |
121 | |
122 public: | |
123 ActiveTracker(const boost::shared_ptr<IFlexiblePointerTracker>& tracker, | |
124 const WebAssemblyViewport& viewport) : | |
125 tracker_(tracker), | |
126 canvasIdentifier_(viewport.GetCanvasIdentifier()), | |
127 insideCanvas_(true) | |
128 { | |
129 if (tracker_.get() == NULL) | |
130 { | |
131 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
132 } | |
133 } | |
134 | |
135 bool IsAlive() const | |
136 { | |
137 return tracker_->IsAlive(); | |
138 } | |
139 | |
140 void PointerMove(const PointerEvent& event) | |
141 { | |
142 tracker_->PointerMove(event); | |
143 } | |
144 | |
145 void PointerUp(const PointerEvent& event) | |
146 { | |
147 tracker_->PointerUp(event); | |
148 } | |
149 }; | |
150 } | |
151 | |
152 static OrthancStone::PointerEvent* ConvertMouseEvent( | |
153 const EmscriptenMouseEvent& source, | |
154 OrthancStone::WebAssemblyViewport& viewport) | |
155 { | |
156 std::auto_ptr<OrthancStone::PointerEvent> target( | |
157 new OrthancStone::PointerEvent); | |
158 | |
159 target->AddPosition(viewport.GetPixelCenterCoordinates( | |
160 source.targetX, source.targetY)); | |
161 target->SetAltModifier(source.altKey); | |
162 target->SetControlModifier(source.ctrlKey); | |
163 target->SetShiftModifier(source.shiftKey); | |
164 | |
165 return target.release(); | |
166 } | |
167 | |
168 std::auto_ptr<OrthancStone::ActiveTracker> tracker_; | |
169 | |
170 EM_BOOL OnMouseEvent(int eventType, | |
171 const EmscriptenMouseEvent *mouseEvent, | |
172 void *userData) | |
173 { | |
174 if (mouseEvent != NULL && | |
175 userData != NULL) | |
176 { | |
177 OrthancStone::WebAssemblyViewport& viewport = | |
178 *reinterpret_cast<OrthancStone::WebAssemblyViewport*>(userData); | |
179 | |
180 switch (eventType) | |
181 { | |
182 case EMSCRIPTEN_EVENT_CLICK: | |
183 { | |
184 static unsigned int count = 0; | |
185 char buf[64]; | |
186 sprintf(buf, "click %d", count++); | |
187 | |
188 std::auto_ptr<OrthancStone::TextSceneLayer> layer(new OrthancStone::TextSceneLayer); | |
189 layer->SetText(buf); | |
190 viewport.GetScene().SetLayer(100, layer.release()); | |
191 viewport.Refresh(); | |
192 break; | |
193 } | |
194 | |
195 case EMSCRIPTEN_EVENT_MOUSEDOWN: | |
196 { | |
197 boost::shared_ptr<OrthancStone::IFlexiblePointerTracker> t; | |
198 | |
199 { | |
200 std::auto_ptr<OrthancStone::PointerEvent> event( | |
201 ConvertMouseEvent(*mouseEvent, viewport)); | |
202 | |
203 switch (mouseEvent->button) | |
204 { | |
205 case 0: // Left button | |
206 emscripten_console_log("Creating RotateSceneTracker"); | |
207 t.reset(new OrthancStone::RotateSceneTracker( | |
208 viewport.GetController(), *event)); | |
209 break; | |
210 | |
211 case 1: // Middle button | |
212 emscripten_console_log("Creating PanSceneTracker"); | |
213 LOG(INFO) << "Creating PanSceneTracker" ; | |
214 t.reset(new OrthancStone::PanSceneTracker( | |
215 viewport.GetController(), *event)); | |
216 break; | |
217 | |
218 case 2: // Right button | |
219 emscripten_console_log("Creating ZoomSceneTracker"); | |
220 t.reset(new OrthancStone::ZoomSceneTracker( | |
221 viewport.GetController(), *event, viewport.GetCanvasWidth())); | |
222 break; | |
223 | |
224 default: | |
225 break; | |
226 } | |
227 } | |
228 | |
229 if (t.get() != NULL) | |
230 { | |
231 tracker_.reset( | |
232 new OrthancStone::ActiveTracker(t, viewport)); | |
233 viewport.Refresh(); | |
234 } | |
235 | |
236 break; | |
237 } | |
238 | |
239 case EMSCRIPTEN_EVENT_MOUSEMOVE: | |
240 if (tracker_.get() != NULL) | |
241 { | |
242 std::auto_ptr<OrthancStone::PointerEvent> event( | |
243 ConvertMouseEvent(*mouseEvent, viewport)); | |
244 tracker_->PointerMove(*event); | |
245 viewport.Refresh(); | |
246 } | |
247 break; | |
248 | |
249 case EMSCRIPTEN_EVENT_MOUSEUP: | |
250 if (tracker_.get() != NULL) | |
251 { | |
252 std::auto_ptr<OrthancStone::PointerEvent> event( | |
253 ConvertMouseEvent(*mouseEvent, viewport)); | |
254 tracker_->PointerUp(*event); | |
255 viewport.Refresh(); | |
256 if (!tracker_->IsAlive()) | |
257 tracker_.reset(); | |
258 } | |
259 break; | |
260 | |
261 default: | |
262 break; | |
263 } | |
264 } | |
265 | |
266 return true; | |
267 } | |
268 | |
269 | |
270 void OrthancStone::WebAssemblyViewport::SetupEvents(const std::string& canvas) | |
271 { | |
272 emscripten_set_mousedown_callback(canvas.c_str(), this, false, OnMouseEvent); | |
273 emscripten_set_mousemove_callback(canvas.c_str(), this, false, OnMouseEvent); | |
274 emscripten_set_mouseup_callback(canvas.c_str(), this, false, OnMouseEvent); | |
275 } | |
276 | |
277 | |
278 | |
279 | |
280 namespace OrthancStone | |
281 { | |
282 class VolumeSlicerViewport : public IObserver | |
283 { | |
284 private: | |
285 OrthancStone::WebAssemblyViewport viewport_; | |
286 std::auto_ptr<VolumeSceneLayerSource> source_; | |
287 VolumeProjection projection_; | |
288 std::vector<CoordinateSystem3D> planes_; | |
289 size_t currentPlane_; | |
290 | |
291 void Handle(const DicomVolumeImage::GeometryReadyMessage& message) | |
292 { | |
293 LOG(INFO) << "Geometry is available"; | |
294 | |
295 const VolumeImageGeometry& geometry = message.GetOrigin().GetGeometry(); | |
296 | |
297 const unsigned int depth = geometry.GetProjectionDepth(projection_); | |
298 currentPlane_ = depth / 2; | |
299 | |
300 planes_.resize(depth); | |
301 | |
302 for (unsigned int z = 0; z < depth; z++) | |
303 { | |
304 planes_[z] = geometry.GetProjectionSlice(projection_, z); | |
305 } | |
306 } | |
307 | |
308 public: | |
309 VolumeSlicerViewport(MessageBroker& broker, | |
310 const std::string& canvas, | |
311 VolumeProjection projection) : | |
312 IObserver(broker), | |
313 viewport_(broker, canvas), | |
314 projection_(projection), | |
315 currentPlane_(0) | |
316 { | |
317 } | |
318 | |
319 void UpdateSize() | |
320 { | |
321 viewport_.UpdateSize(); | |
322 } | |
323 | |
324 void SetSlicer(int layerDepth, | |
325 const boost::shared_ptr<IVolumeSlicer>& slicer, | |
326 IObservable& loader, | |
327 ILayerStyleConfigurator* configurator) | |
328 { | |
329 if (source_.get() != NULL) | |
330 { | |
331 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls, | |
332 "Only one slicer can be registered"); | |
333 } | |
334 | |
335 loader.RegisterObserverCallback( | |
336 new Callable<VolumeSlicerViewport, DicomVolumeImage::GeometryReadyMessage> | |
337 (*this, &VolumeSlicerViewport::Handle)); | |
338 | |
339 source_.reset(new VolumeSceneLayerSource(viewport_.GetScene(), layerDepth, slicer)); | |
340 | |
341 if (configurator != NULL) | |
342 { | |
343 source_->SetConfigurator(configurator); | |
344 } | |
345 } | |
346 | |
347 void Refresh() | |
348 { | |
349 if (source_.get() != NULL && | |
350 currentPlane_ < planes_.size()) | |
351 { | |
352 source_->Update(planes_[currentPlane_]); | |
353 } | |
354 } | |
355 }; | |
356 | |
357 | |
358 | |
359 | |
360 class WebAssemblyOracle : | |
361 public IOracle, | |
362 public IObservable | |
363 { | |
364 private: | |
365 typedef std::map<std::string, std::string> HttpHeaders; | |
366 | |
367 class FetchContext : public boost::noncopyable | |
368 { | |
369 private: | |
370 class Emitter : public IMessageEmitter | |
371 { | |
372 private: | |
373 WebAssemblyOracle& oracle_; | |
374 | |
375 public: | |
376 Emitter(WebAssemblyOracle& oracle) : | |
377 oracle_(oracle) | |
378 { | |
379 } | |
380 | |
381 virtual void EmitMessage(const IObserver& receiver, | |
382 const IMessage& message) | |
383 { | |
384 oracle_.EmitMessage(receiver, message); | |
385 } | |
386 }; | |
387 | |
388 Emitter emitter_; | |
389 const IObserver& receiver_; | |
390 std::auto_ptr<IOracleCommand> command_; | |
391 std::string expectedContentType_; | |
392 | |
393 public: | |
394 FetchContext(WebAssemblyOracle& oracle, | |
395 const IObserver& receiver, | |
396 IOracleCommand* command, | |
397 const std::string& expectedContentType) : | |
398 emitter_(oracle), | |
399 receiver_(receiver), | |
400 command_(command), | |
401 expectedContentType_(expectedContentType) | |
402 { | |
403 if (command == NULL) | |
404 { | |
405 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
406 } | |
407 } | |
408 | |
409 const std::string& GetExpectedContentType() const | |
410 { | |
411 return expectedContentType_; | |
412 } | |
413 | |
414 void EmitMessage(const IMessage& message) | |
415 { | |
416 emitter_.EmitMessage(receiver_, message); | |
417 } | |
418 | |
419 IMessageEmitter& GetEmitter() | |
420 { | |
421 return emitter_; | |
422 } | |
423 | |
424 const IObserver& GetReceiver() const | |
425 { | |
426 return receiver_; | |
427 } | |
428 | |
429 IOracleCommand& GetCommand() const | |
430 { | |
431 return *command_; | |
432 } | |
433 | |
434 template <typename T> | |
435 const T& GetTypedCommand() const | |
436 { | |
437 return dynamic_cast<T&>(*command_); | |
438 } | |
439 }; | |
440 | |
441 static void FetchSucceeded(emscripten_fetch_t *fetch) | |
442 { | |
443 /** | |
444 * Firstly, make a local copy of the fetched information, and | |
445 * free data associated with the fetch. | |
446 **/ | |
447 | |
448 std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); | |
449 | |
450 std::string answer; | |
451 if (fetch->numBytes > 0) | |
452 { | |
453 answer.assign(fetch->data, fetch->numBytes); | |
454 } | |
455 | |
456 /** | |
457 * TODO - HACK - As of emscripten-1.38.31, the fetch API does | |
458 * not contain a way to retrieve the HTTP headers of the | |
459 * answer. We make the assumption that the "Content-Type" header | |
460 * of the response is the same as the "Accept" header of the | |
461 * query. This should be fixed in future versions of emscripten. | |
462 * https://github.com/emscripten-core/emscripten/pull/8486 | |
463 **/ | |
464 | |
465 HttpHeaders headers; | |
466 if (!context->GetExpectedContentType().empty()) | |
467 { | |
468 headers["Content-Type"] = context->GetExpectedContentType(); | |
469 } | |
470 | |
471 | |
472 emscripten_fetch_close(fetch); | |
473 | |
474 | |
475 /** | |
476 * Secondly, use the retrieved data. | |
477 **/ | |
478 | |
479 try | |
480 { | |
481 if (context.get() == NULL) | |
482 { | |
483 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
484 } | |
485 else | |
486 { | |
487 switch (context->GetCommand().GetType()) | |
488 { | |
489 case IOracleCommand::Type_OrthancRestApi: | |
490 { | |
491 OrthancRestApiCommand::SuccessMessage message | |
492 (context->GetTypedCommand<OrthancRestApiCommand>(), headers, answer); | |
493 context->EmitMessage(message); | |
494 break; | |
495 } | |
496 | |
497 case IOracleCommand::Type_GetOrthancImage: | |
498 { | |
499 context->GetTypedCommand<GetOrthancImageCommand>().ProcessHttpAnswer | |
500 (context->GetEmitter(), context->GetReceiver(), answer, headers); | |
501 break; | |
502 } | |
503 | |
504 case IOracleCommand::Type_GetOrthancWebViewerJpeg: | |
505 { | |
506 context->GetTypedCommand<GetOrthancWebViewerJpegCommand>().ProcessHttpAnswer | |
507 (context->GetEmitter(), context->GetReceiver(), answer); | |
508 break; | |
509 } | |
510 | |
511 default: | |
512 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " | |
513 << context->GetCommand().GetType(); | |
514 } | |
515 } | |
516 } | |
517 catch (Orthanc::OrthancException& e) | |
518 { | |
519 LOG(ERROR) << "Error while processing a fetch answer in the oracle: " << e.What(); | |
520 } | |
521 } | |
522 | |
523 static void FetchFailed(emscripten_fetch_t *fetch) | |
524 { | |
525 std::auto_ptr<FetchContext> context(reinterpret_cast<FetchContext*>(fetch->userData)); | |
526 | |
527 LOG(ERROR) << "Fetching " << fetch->url << " failed, HTTP failure status code: " << fetch->status; | |
528 | |
529 /** | |
530 * TODO - The following code leads to an infinite recursion, at | |
531 * least with Firefox running on incognito mode => WHY? | |
532 **/ | |
533 //emscripten_fetch_close(fetch); // Also free data on failure. | |
534 } | |
535 | |
536 | |
537 class FetchCommand : public boost::noncopyable | |
538 { | |
539 private: | |
540 WebAssemblyOracle& oracle_; | |
541 const IObserver& receiver_; | |
542 std::auto_ptr<IOracleCommand> command_; | |
543 Orthanc::HttpMethod method_; | |
544 std::string uri_; | |
545 std::string body_; | |
546 HttpHeaders headers_; | |
547 unsigned int timeout_; | |
548 std::string expectedContentType_; | |
549 | |
550 public: | |
551 FetchCommand(WebAssemblyOracle& oracle, | |
552 const IObserver& receiver, | |
553 IOracleCommand* command) : | |
554 oracle_(oracle), | |
555 receiver_(receiver), | |
556 command_(command), | |
557 method_(Orthanc::HttpMethod_Get), | |
558 timeout_(0) | |
559 { | |
560 if (command == NULL) | |
561 { | |
562 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
563 } | |
564 } | |
565 | |
566 void SetMethod(Orthanc::HttpMethod method) | |
567 { | |
568 method_ = method; | |
569 } | |
570 | |
571 void SetUri(const std::string& uri) | |
572 { | |
573 uri_ = uri; | |
574 } | |
575 | |
576 void SetBody(std::string& body /* will be swapped */) | |
577 { | |
578 body_.swap(body); | |
579 } | |
580 | |
581 void SetHttpHeaders(const HttpHeaders& headers) | |
582 { | |
583 headers_ = headers; | |
584 } | |
585 | |
586 void SetTimeout(unsigned int timeout) | |
587 { | |
588 timeout_ = timeout; | |
589 } | |
590 | |
591 void Execute() | |
592 { | |
593 if (command_.get() == NULL) | |
594 { | |
595 // Cannot call Execute() twice | |
596 throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); | |
597 } | |
598 | |
599 emscripten_fetch_attr_t attr; | |
600 emscripten_fetch_attr_init(&attr); | |
601 | |
602 const char* method; | |
603 | |
604 switch (method_) | |
605 { | |
606 case Orthanc::HttpMethod_Get: | |
607 method = "GET"; | |
608 break; | |
609 | |
610 case Orthanc::HttpMethod_Post: | |
611 method = "POST"; | |
612 break; | |
613 | |
614 case Orthanc::HttpMethod_Delete: | |
615 method = "DELETE"; | |
616 break; | |
617 | |
618 case Orthanc::HttpMethod_Put: | |
619 method = "PUT"; | |
620 break; | |
621 | |
622 default: | |
623 throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); | |
624 } | |
625 | |
626 strcpy(attr.requestMethod, method); | |
627 | |
628 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; | |
629 attr.onsuccess = FetchSucceeded; | |
630 attr.onerror = FetchFailed; | |
631 attr.timeoutMSecs = timeout_ * 1000; | |
632 | |
633 std::vector<const char*> headers; | |
634 headers.reserve(2 * headers_.size() + 1); | |
635 | |
636 std::string expectedContentType; | |
637 | |
638 for (HttpHeaders::const_iterator it = headers_.begin(); it != headers_.end(); ++it) | |
639 { | |
640 std::string key; | |
641 Orthanc::Toolbox::ToLowerCase(key, it->first); | |
642 | |
643 if (key == "accept") | |
644 { | |
645 expectedContentType = it->second; | |
646 } | |
647 | |
648 if (key != "accept-encoding") // Web browsers forbid the modification of this HTTP header | |
649 { | |
650 headers.push_back(it->first.c_str()); | |
651 headers.push_back(it->second.c_str()); | |
652 } | |
653 } | |
654 | |
655 headers.push_back(NULL); // Termination of the array of HTTP headers | |
656 | |
657 attr.requestHeaders = &headers[0]; | |
658 | |
659 if (!body_.empty()) | |
660 { | |
661 attr.requestDataSize = body_.size(); | |
662 attr.requestData = body_.c_str(); | |
663 } | |
664 | |
665 // Must be the last call to prevent memory leak on error | |
666 attr.userData = new FetchContext(oracle_, receiver_, command_.release(), expectedContentType); | |
667 emscripten_fetch(&attr, uri_.c_str()); | |
668 } | |
669 }; | |
670 | |
671 | |
672 void Execute(const IObserver& receiver, | |
673 OrthancRestApiCommand* command) | |
674 { | |
675 FetchCommand fetch(*this, receiver, command); | |
676 | |
677 fetch.SetMethod(command->GetMethod()); | |
678 fetch.SetUri(command->GetUri()); | |
679 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
680 fetch.SetTimeout(command->GetTimeout()); | |
681 | |
682 if (command->GetMethod() == Orthanc::HttpMethod_Put || | |
683 command->GetMethod() == Orthanc::HttpMethod_Put) | |
684 { | |
685 std::string body; | |
686 command->SwapBody(body); | |
687 fetch.SetBody(body); | |
688 } | |
689 | |
690 fetch.Execute(); | |
691 } | |
692 | |
693 | |
694 void Execute(const IObserver& receiver, | |
695 GetOrthancImageCommand* command) | |
696 { | |
697 FetchCommand fetch(*this, receiver, command); | |
698 | |
699 fetch.SetUri(command->GetUri()); | |
700 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
701 fetch.SetTimeout(command->GetTimeout()); | |
702 | |
703 fetch.Execute(); | |
704 } | |
705 | |
706 | |
707 void Execute(const IObserver& receiver, | |
708 GetOrthancWebViewerJpegCommand* command) | |
709 { | |
710 FetchCommand fetch(*this, receiver, command); | |
711 | |
712 fetch.SetUri(command->GetUri()); | |
713 fetch.SetHttpHeaders(command->GetHttpHeaders()); | |
714 fetch.SetTimeout(command->GetTimeout()); | |
715 | |
716 fetch.Execute(); | |
717 } | |
718 | |
719 | |
720 public: | |
721 WebAssemblyOracle(MessageBroker& broker) : | |
722 IObservable(broker) | |
723 { | |
724 } | |
725 | |
726 virtual void Schedule(const IObserver& receiver, | |
727 IOracleCommand* command) | |
728 { | |
729 std::auto_ptr<IOracleCommand> protection(command); | |
730 | |
731 if (command == NULL) | |
732 { | |
733 throw Orthanc::OrthancException(Orthanc::ErrorCode_NullPointer); | |
734 } | |
735 | |
736 switch (command->GetType()) | |
737 { | |
738 case IOracleCommand::Type_OrthancRestApi: | |
739 Execute(receiver, dynamic_cast<OrthancRestApiCommand*>(protection.release())); | |
740 break; | |
741 | |
742 case IOracleCommand::Type_GetOrthancImage: | |
743 Execute(receiver, dynamic_cast<GetOrthancImageCommand*>(protection.release())); | |
744 break; | |
745 | |
746 case IOracleCommand::Type_GetOrthancWebViewerJpeg: | |
747 Execute(receiver, dynamic_cast<GetOrthancWebViewerJpegCommand*>(protection.release())); | |
748 break; | |
749 | |
750 default: | |
751 LOG(ERROR) << "Command type not implemented by the WebAssembly Oracle: " << command->GetType(); | |
752 } | |
753 } | |
754 | |
755 virtual void Start() | |
756 { | |
757 } | |
758 | |
759 virtual void Stop() | |
760 { | |
761 } | |
762 }; | |
763 } | |
764 | |
765 | |
766 | |
767 | |
768 boost::shared_ptr<OrthancStone::DicomVolumeImage> ct_(new OrthancStone::DicomVolumeImage); | |
769 | |
770 boost::shared_ptr<OrthancStone::OrthancSeriesVolumeProgressiveLoader> loader_; | |
771 | |
772 std::auto_ptr<OrthancStone::VolumeSlicerViewport> viewport1_; | |
773 std::auto_ptr<OrthancStone::VolumeSlicerViewport> viewport2_; | |
774 std::auto_ptr<OrthancStone::VolumeSlicerViewport> viewport3_; | |
775 | |
776 OrthancStone::MessageBroker broker_; | |
777 OrthancStone::WebAssemblyOracle oracle_(broker_); | |
778 | |
779 | |
780 EM_BOOL OnWindowResize(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) | |
781 { | |
782 try | |
783 { | |
784 if (viewport1_.get() != NULL) | |
785 { | |
786 viewport1_->UpdateSize(); | |
787 } | |
788 | |
789 if (viewport2_.get() != NULL) | |
790 { | |
791 viewport2_->UpdateSize(); | |
792 } | |
793 | |
794 if (viewport3_.get() != NULL) | |
795 { | |
796 viewport3_->UpdateSize(); | |
797 } | |
798 } | |
799 catch (Orthanc::OrthancException& e) | |
800 { | |
801 LOG(ERROR) << "Exception while updating canvas size: " << e.What(); | |
802 } | |
803 | |
804 return true; | |
805 } | |
806 | |
807 | |
808 | |
809 | |
810 EM_BOOL OnAnimationFrame(double time, void *userData) | |
811 { | |
812 try | |
813 { | |
814 if (viewport1_.get() != NULL) | |
815 { | |
816 viewport1_->Refresh(); | |
817 } | |
818 | |
819 if (viewport2_.get() != NULL) | |
820 { | |
821 viewport2_->Refresh(); | |
822 } | |
823 | |
824 if (viewport3_.get() != NULL) | |
825 { | |
826 viewport3_->Refresh(); | |
827 } | |
828 | |
829 return true; | |
830 } | |
831 catch (Orthanc::OrthancException& e) | |
832 { | |
833 LOG(ERROR) << "Exception in the animation loop, stopping now: " << e.What(); | |
834 return false; | |
835 } | |
836 } | |
837 | |
838 | |
839 extern "C" | |
840 { | |
841 int main(int argc, char const *argv[]) | |
842 { | |
843 OrthancStone::StoneInitialize(); | |
844 Orthanc::Logging::EnableInfoLevel(true); | |
845 // Orthanc::Logging::EnableTraceLevel(true); | |
846 EM_ASM(window.dispatchEvent(new CustomEvent("WebAssemblyLoaded"));); | |
847 } | |
848 | |
849 EMSCRIPTEN_KEEPALIVE | |
850 void Initialize() | |
851 { | |
852 try | |
853 { | |
854 loader_.reset(new OrthancStone::OrthancSeriesVolumeProgressiveLoader(ct_, oracle_, oracle_)); | |
855 | |
856 viewport1_.reset(new OrthancStone::VolumeSlicerViewport(broker_, "mycanvas1", OrthancStone::VolumeProjection_Axial)); | |
857 viewport1_->SetSlicer(0, loader_, *loader_, new OrthancStone::GrayscaleStyleConfigurator); | |
858 viewport1_->UpdateSize(); | |
859 | |
860 viewport2_.reset(new OrthancStone::VolumeSlicerViewport(broker_, "mycanvas2", OrthancStone::VolumeProjection_Coronal)); | |
861 viewport2_->SetSlicer(0, loader_, *loader_, new OrthancStone::GrayscaleStyleConfigurator); | |
862 viewport2_->UpdateSize(); | |
863 | |
864 viewport3_.reset(new OrthancStone::VolumeSlicerViewport(broker_, "mycanvas3", OrthancStone::VolumeProjection_Sagittal)); | |
865 viewport3_->SetSlicer(0, loader_, *loader_, new OrthancStone::GrayscaleStyleConfigurator); | |
866 viewport3_->UpdateSize(); | |
867 | |
868 emscripten_set_resize_callback("#window", NULL, false, OnWindowResize); | |
869 | |
870 emscripten_request_animation_frame_loop(OnAnimationFrame, NULL); | |
871 | |
872 oracle_.Start(); | |
873 loader_->LoadSeries("a04ecf01-79b2fc33-58239f7e-ad9db983-28e81afa"); | |
874 } | |
875 catch (Orthanc::OrthancException& e) | |
876 { | |
877 LOG(ERROR) << "Exception during Initialize(): " << e.What(); | |
878 } | |
879 } | |
880 } |