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
|
|
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 }
|