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 }