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