Mercurial > hg > orthanc-stone
comparison StoneWebViewer/WebApplication/app.js @ 1495:fb74ed5d8c22
initial commit of the Stone Web viewer
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 25 Jun 2020 16:51:10 +0200 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
1494:5a3ef478deb6 | 1495:fb74ed5d8c22 |
---|---|
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-2020 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 var COLORS = [ 'blue', 'red', 'green', 'yellow', 'violet' ]; | |
23 var SERIES_INSTANCE_UID = '0020,000e'; | |
24 var STUDY_INSTANCE_UID = '0020,000d'; | |
25 var STUDY_DESCRIPTION = '0008,1030'; | |
26 var STUDY_DATE = '0008,0020'; | |
27 | |
28 | |
29 function getParameterFromUrl(key) { | |
30 var url = window.location.search.substring(1); | |
31 var args = url.split('&'); | |
32 for (var i = 0; i < args.length; i++) { | |
33 var arg = args[i].split('='); | |
34 if (arg[0] == key) { | |
35 return arg[1]; | |
36 } | |
37 } | |
38 } | |
39 | |
40 | |
41 Vue.component('viewport', { | |
42 props: [ 'left', 'top', 'width', 'height', 'canvasId', 'active', 'series', 'viewportIndex', | |
43 'quality', 'framesCount', 'currentFrame', 'showInfo' ], | |
44 template: '#viewport-template', | |
45 data: function () { | |
46 return { | |
47 stone: stone, // To access global object "stone" from "index.html" | |
48 status: 'waiting' | |
49 } | |
50 }, | |
51 watch: { | |
52 series: function(newVal, oldVal) { | |
53 this.status = 'loading'; | |
54 | |
55 var studyInstanceUid = newVal.tags[STUDY_INSTANCE_UID]; | |
56 var seriesInstanceUid = newVal.tags[SERIES_INSTANCE_UID]; | |
57 stone.SpeedUpFetchSeriesMetadata(studyInstanceUid, seriesInstanceUid); | |
58 | |
59 if ((newVal.type == stone.ThumbnailType.IMAGE || | |
60 newVal.type == stone.ThumbnailType.NO_PREVIEW) && | |
61 newVal.complete) { | |
62 this.status = 'ready'; | |
63 | |
64 var that = this; | |
65 Vue.nextTick(function() { | |
66 stone.LoadSeriesInViewport(that.canvasId, seriesInstanceUid); | |
67 }); | |
68 } | |
69 else if (newVal.type == stone.ThumbnailType.PDF || | |
70 newVal.type == stone.ThumbnailType.VIDEO) { | |
71 // TODO | |
72 } | |
73 } | |
74 }, | |
75 methods: { | |
76 SeriesDragAccept: function(event) { | |
77 event.preventDefault(); | |
78 }, | |
79 SeriesDragDrop: function(event) { | |
80 event.preventDefault(); | |
81 this.$emit('updated-series', event.dataTransfer.getData('seriesIndex')); | |
82 }, | |
83 MakeActive: function() { | |
84 this.$emit('selected-viewport'); | |
85 }, | |
86 DecrementFrame: function() { | |
87 stone.DecrementFrame(this.canvasId); | |
88 }, | |
89 IncrementFrame: function() { | |
90 stone.IncrementFrame(this.canvasId); | |
91 } | |
92 } | |
93 }) | |
94 | |
95 | |
96 var app = new Vue({ | |
97 el: '#wv', | |
98 data: function() { | |
99 return { | |
100 stone: stone, // To access global object "stone" from "index.html" | |
101 ready: false, | |
102 leftMode: 'grid', // Can be 'small', 'grid' or 'full' | |
103 leftVisible: true, | |
104 showWarning: false, | |
105 viewportLayoutButtonsVisible: false, | |
106 activeViewport: 0, | |
107 showInfo: true, | |
108 showReferenceLines: true, | |
109 | |
110 viewport1Width: '100%', | |
111 viewport1Height: '100%', | |
112 viewport1Left: '0%', | |
113 viewport1Top: '0%', | |
114 viewport1Visible: true, | |
115 viewport1Series: {}, | |
116 viewport1Quality: '', | |
117 viewport1FramesCount: 0, | |
118 viewport1CurrentFrame: 0, | |
119 | |
120 viewport2Width: '100%', | |
121 viewport2Height: '100%', | |
122 viewport2Left: '0%', | |
123 viewport2Top: '0%', | |
124 viewport2Visible: false, | |
125 viewport2Series: {}, | |
126 viewport2Quality: '', | |
127 viewport2FramesCount: 0, | |
128 viewport2CurrentFrame: 0, | |
129 | |
130 viewport3Width: '100%', | |
131 viewport3Height: '100%', | |
132 viewport3Left: '0%', | |
133 viewport3Top: '0%', | |
134 viewport3Visible: false, | |
135 viewport3Series: {}, | |
136 viewport3Quality: '', | |
137 viewport3FramesCount: 0, | |
138 viewport3CurrentFrame: 0, | |
139 | |
140 viewport4Width: '100%', | |
141 viewport4Height: '100%', | |
142 viewport4Left: '0%', | |
143 viewport4Top: '0%', | |
144 viewport4Visible: false, | |
145 viewport4Series: {}, | |
146 viewport4Quality: '', | |
147 viewport4FramesCount: 0, | |
148 viewport4CurrentFrame: 0, | |
149 | |
150 series: [], | |
151 studies: [], | |
152 seriesIndex: {} // Maps "SeriesInstanceUID" to "index in this.series" | |
153 } | |
154 }, | |
155 computed: { | |
156 getSelectedStudies() { | |
157 var s = ''; | |
158 for (var i = 0; i < this.studies.length; i++) { | |
159 if (this.studies[i].selected) { | |
160 if (s.length > 0) | |
161 s += ', '; | |
162 s += (this.studies[i].tags[STUDY_DESCRIPTION] + ' [' + | |
163 this.studies[i].tags[STUDY_DATE] + ']'); | |
164 } | |
165 } | |
166 if (s.length == 0) | |
167 return '...'; | |
168 else | |
169 return s; | |
170 } | |
171 }, | |
172 watch: { | |
173 leftVisible: function(newVal, oldVal) { | |
174 this.FitContent(); | |
175 }, | |
176 showReferenceLines: function(newVal, oldVal) { | |
177 stone.ShowReferenceLines(newVal ? 1 : 0); | |
178 } | |
179 }, | |
180 methods: { | |
181 FitContent() { | |
182 // This function can be used even if WebAssembly is not initialized yet | |
183 if (typeof stone._AllViewportsUpdateSize !== 'undefined') { | |
184 this.$nextTick(function () { | |
185 stone.AllViewportsUpdateSize(true /* fit content */); | |
186 }); | |
187 } | |
188 }, | |
189 | |
190 GetActiveSeries() { | |
191 var s = []; | |
192 | |
193 if ('tags' in this.viewport1Series) | |
194 s.push(this.viewport1Series.tags[SERIES_INSTANCE_UID]); | |
195 | |
196 if ('tags' in this.viewport2Series) | |
197 s.push(this.viewport2Series.tags[SERIES_INSTANCE_UID]); | |
198 | |
199 if ('tags' in this.viewport3Series) | |
200 s.push(this.viewport3Series.tags[SERIES_INSTANCE_UID]); | |
201 | |
202 if ('tags' in this.viewport4Series) | |
203 s.push(this.viewport4Series.tags[SERIES_INSTANCE_UID]); | |
204 | |
205 return s; | |
206 }, | |
207 | |
208 GetActiveCanvas() { | |
209 if (this.activeViewport == 1) { | |
210 return 'canvas1'; | |
211 } | |
212 else if (this.activeViewport == 2) { | |
213 return 'canvas2'; | |
214 } | |
215 else if (this.activeViewport == 3) { | |
216 return 'canvas3'; | |
217 } | |
218 else if (this.activeViewport == 4) { | |
219 return 'canvas4'; | |
220 } | |
221 else { | |
222 return 'canvas1'; | |
223 } | |
224 }, | |
225 | |
226 SetResources: function(sourceStudies, sourceSeries) { | |
227 var indexStudies = {}; | |
228 | |
229 var studies = []; | |
230 var posColor = 0; | |
231 | |
232 for (var i = 0; i < sourceStudies.length; i++) { | |
233 var studyInstanceUid = sourceStudies[i][STUDY_INSTANCE_UID]; | |
234 if (studyInstanceUid !== undefined) { | |
235 if (studyInstanceUid in indexStudies) { | |
236 console.error('Twice the same study: ' + studyInstanceUid); | |
237 } else { | |
238 indexStudies[studyInstanceUid] = studies.length; | |
239 | |
240 studies.push({ | |
241 'studyInstanceUid' : studyInstanceUid, | |
242 'series' : [ ], | |
243 'color' : COLORS[posColor], | |
244 'selected' : true, | |
245 'tags' : sourceStudies[i] | |
246 }); | |
247 | |
248 posColor = (posColor + 1) % COLORS.length; | |
249 } | |
250 } | |
251 } | |
252 | |
253 var series = []; | |
254 var seriesIndex = {}; | |
255 | |
256 for (var i = 0; i < sourceSeries.length; i++) { | |
257 var studyInstanceUid = sourceSeries[i][STUDY_INSTANCE_UID]; | |
258 var seriesInstanceUid = sourceSeries[i][SERIES_INSTANCE_UID]; | |
259 if (studyInstanceUid !== undefined && | |
260 seriesInstanceUid !== undefined) { | |
261 if (studyInstanceUid in indexStudies) { | |
262 seriesIndex[seriesInstanceUid] = series.length; | |
263 var study = studies[indexStudies[studyInstanceUid]]; | |
264 study.series.push(i); | |
265 series.push({ | |
266 //'length' : 4, | |
267 'complete' : false, | |
268 'type' : stone.ThumbnailType.LOADING, | |
269 'color': study.color, | |
270 'tags': sourceSeries[i] | |
271 }); | |
272 } | |
273 } | |
274 } | |
275 | |
276 this.studies = studies; | |
277 this.series = series; | |
278 this.seriesIndex = seriesIndex; | |
279 this.ready = true; | |
280 }, | |
281 | |
282 SeriesDragStart: function(event, seriesIndex) { | |
283 event.dataTransfer.setData('seriesIndex', seriesIndex); | |
284 }, | |
285 | |
286 SetViewportSeries: function(viewportIndex, seriesIndex) { | |
287 var series = this.series[seriesIndex]; | |
288 | |
289 if (viewportIndex == 1) { | |
290 this.viewport1Series = series; | |
291 } | |
292 else if (viewportIndex == 2) { | |
293 this.viewport2Series = series; | |
294 } | |
295 else if (viewportIndex == 3) { | |
296 this.viewport3Series = series; | |
297 } | |
298 else if (viewportIndex == 4) { | |
299 this.viewport4Series = series; | |
300 } | |
301 }, | |
302 | |
303 ClickSeries: function(seriesIndex) { | |
304 this.SetViewportSeries(this.activeViewport, seriesIndex); | |
305 }, | |
306 | |
307 HideViewport: function(index) { | |
308 if (index == 1) { | |
309 this.viewport1Visible = false; | |
310 } | |
311 else if (index == 2) { | |
312 this.viewport2Visible = false; | |
313 } | |
314 else if (index == 3) { | |
315 this.viewport3Visible = false; | |
316 } | |
317 else if (index == 4) { | |
318 this.viewport4Visible = false; | |
319 } | |
320 }, | |
321 | |
322 ShowViewport: function(index, left, top, width, height) { | |
323 if (index == 1) { | |
324 this.viewport1Visible = true; | |
325 this.viewport1Left = left; | |
326 this.viewport1Top = top; | |
327 this.viewport1Width = width; | |
328 this.viewport1Height = height; | |
329 } | |
330 else if (index == 2) { | |
331 this.viewport2Visible = true; | |
332 this.viewport2Left = left; | |
333 this.viewport2Top = top; | |
334 this.viewport2Width = width; | |
335 this.viewport2Height = height; | |
336 } | |
337 else if (index == 3) { | |
338 this.viewport3Visible = true; | |
339 this.viewport3Left = left; | |
340 this.viewport3Top = top; | |
341 this.viewport3Width = width; | |
342 this.viewport3Height = height; | |
343 } | |
344 else if (index == 4) { | |
345 this.viewport4Visible = true; | |
346 this.viewport4Left = left; | |
347 this.viewport4Top = top; | |
348 this.viewport4Width = width; | |
349 this.viewport4Height = height; | |
350 } | |
351 }, | |
352 | |
353 SetViewportLayout: function(layout) { | |
354 this.viewportLayoutButtonsVisible = false; | |
355 if (layout == '1x1') { | |
356 this.ShowViewport(1, '0%', '0%', '100%', '100%'); | |
357 this.HideViewport(2); | |
358 this.HideViewport(3); | |
359 this.HideViewport(4); | |
360 } | |
361 else if (layout == '2x2') { | |
362 this.ShowViewport(1, '0%', '0%', '50%', '50%'); | |
363 this.ShowViewport(2, '50%', '0%', '50%', '50%'); | |
364 this.ShowViewport(3, '0%', '50%', '50%', '50%'); | |
365 this.ShowViewport(4, '50%', '50%', '50%', '50%'); | |
366 } | |
367 else if (layout == '2x1') { | |
368 this.ShowViewport(1, '0%', '0%', '50%', '100%'); | |
369 this.ShowViewport(2, '50%', '0%', '50%', '100%'); | |
370 this.HideViewport(3); | |
371 this.HideViewport(4); | |
372 } | |
373 else if (layout == '1x2') { | |
374 this.ShowViewport(1, '0%', '0%', '100%', '50%'); | |
375 this.ShowViewport(2, '0%', '50%', '100%', '50%'); | |
376 this.HideViewport(3); | |
377 this.HideViewport(4); | |
378 } | |
379 | |
380 this.FitContent(); | |
381 }, | |
382 | |
383 UpdateSeriesThumbnail: function(seriesInstanceUid) { | |
384 if (seriesInstanceUid in this.seriesIndex) { | |
385 var index = this.seriesIndex[seriesInstanceUid]; | |
386 var series = this.series[index]; | |
387 | |
388 var type = stone.LoadSeriesThumbnail(seriesInstanceUid); | |
389 series.type = type; | |
390 | |
391 if (type == stone.ThumbnailType.IMAGE) { | |
392 series.thumbnail = stone.GetStringBuffer(); | |
393 } | |
394 | |
395 // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating | |
396 this.$set(this.series, index, series); | |
397 } | |
398 }, | |
399 | |
400 UpdateIsSeriesComplete: function(seriesInstanceUid) { | |
401 if (seriesInstanceUid in this.seriesIndex) { | |
402 var index = this.seriesIndex[seriesInstanceUid]; | |
403 var series = this.series[index]; | |
404 | |
405 series.complete = stone.IsSeriesComplete(seriesInstanceUid); | |
406 | |
407 // https://fr.vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating | |
408 this.$set(this.series, index, series); | |
409 | |
410 if ('tags' in this.viewport1Series && | |
411 this.viewport1Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { | |
412 this.$set(this.viewport1Series, series); | |
413 } | |
414 | |
415 if ('tags' in this.viewport2Series && | |
416 this.viewport2Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { | |
417 this.$set(this.viewport2Series, series); | |
418 } | |
419 | |
420 if ('tags' in this.viewport3Series && | |
421 this.viewport3Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { | |
422 this.$set(this.viewport3Series, series); | |
423 } | |
424 | |
425 if ('tags' in this.viewport4Series && | |
426 this.viewport4Series.tags[SERIES_INSTANCE_UID] == seriesInstanceUid) { | |
427 this.$set(this.viewport4Series, series); | |
428 } | |
429 } | |
430 }, | |
431 | |
432 SetWindowing(center, width) { | |
433 var canvas = this.GetActiveCanvas(); | |
434 if (canvas != '') { | |
435 stone.SetWindowing(canvas, center, width); | |
436 } | |
437 }, | |
438 | |
439 SetDefaultWindowing() { | |
440 var canvas = this.GetActiveCanvas(); | |
441 if (canvas != '') { | |
442 stone.SetDefaultWindowing(canvas); | |
443 } | |
444 }, | |
445 | |
446 InvertContrast() { | |
447 var canvas = this.GetActiveCanvas(); | |
448 if (canvas != '') { | |
449 stone.InvertContrast(canvas); | |
450 } | |
451 } | |
452 }, | |
453 | |
454 mounted: function() { | |
455 this.SetViewportLayout('1x1'); | |
456 } | |
457 }); | |
458 | |
459 | |
460 | |
461 window.addEventListener('StoneInitialized', function() { | |
462 stone.Setup(Module); | |
463 stone.SetOrthancRoot('..', true); | |
464 console.warn('Native Stone properly intialized'); | |
465 | |
466 var study = getParameterFromUrl('study'); | |
467 var series = getParameterFromUrl('series'); | |
468 | |
469 if (study === undefined) { | |
470 alert('No study was provided in the URL!'); | |
471 } else { | |
472 if (series === undefined) { | |
473 console.warn('Loading study: ' + study); | |
474 stone.FetchStudy(study); | |
475 } else { | |
476 console.warn('Loading series: ' + series + ' from study ' + study); | |
477 stone.FetchSeries(study, series); | |
478 app.leftMode = 'full'; | |
479 } | |
480 } | |
481 }); | |
482 | |
483 | |
484 window.addEventListener('ResourcesLoaded', function() { | |
485 console.log('resources loaded'); | |
486 | |
487 var studies = []; | |
488 for (var i = 0; i < stone.GetStudiesCount(); i++) { | |
489 stone.LoadStudyTags(i); | |
490 studies.push(JSON.parse(stone.GetStringBuffer())); | |
491 } | |
492 | |
493 var series = []; | |
494 for (var i = 0; i < stone.GetSeriesCount(); i++) { | |
495 stone.LoadSeriesTags(i); | |
496 series.push(JSON.parse(stone.GetStringBuffer())); | |
497 } | |
498 | |
499 app.SetResources(studies, series); | |
500 | |
501 for (var i = 0; i < app.series.length; i++) { | |
502 var seriesInstanceUid = app.series[i].tags[SERIES_INSTANCE_UID]; | |
503 app.UpdateSeriesThumbnail(seriesInstanceUid); | |
504 app.UpdateIsSeriesComplete(seriesInstanceUid); | |
505 } | |
506 }); | |
507 | |
508 | |
509 window.addEventListener('ThumbnailLoaded', function(args) { | |
510 //var studyInstanceUid = args.detail.studyInstanceUid; | |
511 var seriesInstanceUid = args.detail.seriesInstanceUid; | |
512 app.UpdateSeriesThumbnail(seriesInstanceUid); | |
513 }); | |
514 | |
515 | |
516 window.addEventListener('MetadataLoaded', function(args) { | |
517 //var studyInstanceUid = args.detail.studyInstanceUid; | |
518 var seriesInstanceUid = args.detail.seriesInstanceUid; | |
519 app.UpdateIsSeriesComplete(seriesInstanceUid); | |
520 }); | |
521 | |
522 | |
523 window.addEventListener('FrameUpdated', function(args) { | |
524 var canvasId = args.detail.canvasId; | |
525 var framesCount = args.detail.framesCount; | |
526 var currentFrame = (args.detail.currentFrame + 1); | |
527 var quality = args.detail.quality; | |
528 | |
529 if (canvasId == 'canvas1') { | |
530 app.viewport1CurrentFrame = currentFrame; | |
531 app.viewport1FramesCount = framesCount; | |
532 app.viewport1Quality = quality; | |
533 } | |
534 else if (canvasId == 'canvas2') { | |
535 app.viewport2CurrentFrame = currentFrame; | |
536 app.viewport2FramesCount = framesCount; | |
537 app.viewport2Quality = quality; | |
538 } | |
539 else if (canvasId == 'canvas3') { | |
540 app.viewport3CurrentFrame = currentFrame; | |
541 app.viewport3FramesCount = framesCount; | |
542 app.viewport3Quality = quality; | |
543 } | |
544 else if (canvasId == 'canvas4') { | |
545 app.viewport4CurrentFrame = currentFrame; | |
546 app.viewport4FramesCount = framesCount; | |
547 app.viewport4Quality = quality; | |
548 } | |
549 }); | |
550 | |
551 | |
552 window.addEventListener('StoneException', function() { | |
553 console.error('Exception catched in Stone'); | |
554 }); | |
555 | |
556 | |
557 | |
558 | |
559 | |
560 | |
561 $(document).ready(function() { | |
562 // Enable support for tooltips in Bootstrap | |
563 //$('[data-toggle="tooltip"]').tooltip(); | |
564 | |
565 //app.showWarning = true; | |
566 | |
567 | |
568 $('#windowing-popover').popover({ | |
569 container: 'body', | |
570 content: $('#windowing-content').html(), | |
571 template: '<div class="popover wvToolbar__windowingPresetConfigPopover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>', | |
572 placement: 'auto', | |
573 html: true, | |
574 sanitize: false, | |
575 trigger: 'focus' // Close on click | |
576 }); | |
577 | |
578 | |
579 var wasmSource = 'StoneWebViewer.js'; | |
580 | |
581 // Option 1: Loading script using plain HTML | |
582 | |
583 /* | |
584 var script = document.createElement('script'); | |
585 script.src = wasmSource; | |
586 script.type = 'text/javascript'; | |
587 document.body.appendChild(script); | |
588 */ | |
589 | |
590 // Option 2: Loading script using AJAX (gives the opportunity to | |
591 // report explicit errors) | |
592 | |
593 axios.get(wasmSource) | |
594 .then(function (response) { | |
595 var script = document.createElement('script'); | |
596 script.innerHTML = response.data; | |
597 script.type = 'text/javascript'; | |
598 document.body.appendChild(script); | |
599 }) | |
600 .catch(function (error) { | |
601 alert('Cannot load the WebAssembly framework'); | |
602 }); | |
603 }); | |
604 | |
605 | |
606 // "Prevent Bootstrap dropdown from closing on clicks" for the list of | |
607 // studies: https://stackoverflow.com/questions/26639346 | |
608 $('.dropdown-menu').click(function(e) { | |
609 e.stopPropagation(); | |
610 }); | |
611 | |
612 | |
613 // Disable the selection of text using the mouse | |
614 document.onselectstart = new Function ('return false'); |