comparison WebApplication/viewer.js @ 0:02f7a0400a91

initial commit
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 25 Feb 2015 13:45:35 +0100
parents
children c919d488471f
comparison
equal deleted inserted replaced
-1:000000000000 0:02f7a0400a91
1 /**
2 * Orthanc - A Lightweight, RESTful DICOM Store
3 * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
4 * Department, University Hospital of Liege, Belgium
5 *
6 * This program is free software: you can redistribute it and/or
7 * modify it under the terms of the GNU Affero General Public License
8 * as published by the Free Software Foundation, either version 3 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 **/
19
20
21 var compression = 'jpeg95';
22
23
24
25 // Prevent the access to IE
26 if(navigator.appVersion.indexOf("MSIE ") != -1)
27 {
28 alert("Please use Mozilla Firefox or Google Chrome. Microsoft Internet Explorer is not supported.");
29 }
30
31
32 function ResizeCornerstone()
33 {
34 $('#dicomImage').height($(window).height() - $('#slider').parent().height());
35 var element = $('#dicomImage').get(0);
36 cornerstone.resize(element, true);
37 }
38
39
40 function SetWindowing(center, width)
41 {
42 var element = $('#dicomImage').get(0);
43 var viewport = cornerstone.getViewport(element);
44 viewport.voi.windowCenter = center;
45 viewport.voi.windowWidth = width;
46 cornerstone.setViewport(element, viewport);
47 UpdateViewportInformation();
48 }
49
50
51 function SetFullWindowing()
52 {
53 var element = $('#dicomImage').get(0);
54 var viewport = cornerstone.getViewport(element);
55 var image = cornerstone.getEnabledElement(element).image;
56
57 if (image.color) {
58 // Ignore color images
59 return;
60 }
61
62 var minValue = image.minPixelValue;
63 var maxValue = image.maxPixelValue;
64 if (minValue == undefined ||
65 maxValue == undefined ||
66 minValue == maxValue) {
67 return;
68 }
69
70 if (image.slope != undefined &&
71 image.intercept != undefined) {
72 minValue = minValue * image.slope + image.intercept;
73 maxValue = maxValue * image.slope + image.intercept;
74 }
75
76 viewport.voi.windowCenter = (minValue + maxValue) / 2.0;
77 viewport.voi.windowWidth = (maxValue - minValue) / 2.0;
78 cornerstone.setViewport(element, viewport);
79 UpdateViewportInformation();
80 }
81
82
83 function SetDefaultWindowing()
84 {
85 var element = $('#dicomImage').get(0);
86 var viewport = cornerstone.getViewport(element);
87 var image = cornerstone.getEnabledElement(element).image;
88
89 viewport.voi.windowCenter = image.windowCenter;
90 viewport.voi.windowWidth = image.windowWidth;
91 cornerstone.setViewport(element, viewport);
92 UpdateViewportInformation();
93 }
94
95
96 function SetBoneWindowing()
97 {
98 SetWindowing(300, 2000);
99 }
100
101
102 function SetLungWindowing()
103 {
104 SetWindowing(-600, 1600);
105 }
106
107
108 function UpdateViewportInformation()
109 {
110 var element = $('#dicomImage').get(0);
111 var viewport = cornerstone.getViewport(element);
112
113 $('#bottomleft').text('WW/WL:' + Math.round(viewport.voi.windowWidth) + '/' + Math.round(viewport.voi.windowCenter));
114 $('#bottomright').text('Zoom: ' + viewport.scale.toFixed(2) + 'x');
115 }
116
117
118 function ToggleSeriesInformation()
119 {
120 $('#topright').toggle();
121 }
122
123
124 function ToggleInterpolation()
125 {
126 var element = $('#dicomImage').get(0);
127 var viewport = cornerstone.getViewport(element);
128 if (viewport.pixelReplication === true) {
129 viewport.pixelReplication = false;
130 } else {
131 viewport.pixelReplication = true;
132 }
133 cornerstone.setViewport(element, viewport);
134 }
135
136
137 function ToggleInversion()
138 {
139 var element = $('#dicomImage').get(0);
140 var viewport = cornerstone.getViewport(element);
141 if (viewport.invert === true) {
142 viewport.invert = false;
143 } else {
144 viewport.invert = true;
145 }
146 cornerstone.setViewport(element, viewport);
147 }
148
149
150 function DownloadInstance(instance)
151 {
152 // http://stackoverflow.com/a/3749395/881731
153 var hiddenIFrameID = 'hiddenDownloader',
154 iframe = document.getElementById(hiddenIFrameID);
155 if (iframe === null) {
156 iframe = document.createElement('iframe');
157 iframe.id = hiddenIFrameID;
158 iframe.style.display = 'none';
159 document.body.appendChild(iframe);
160 }
161 iframe.src = '../../instances/' + instance + '/file';
162 }
163
164
165 function AdjustZoom()
166 {
167 var element = $('#dicomImage').get(0);
168 cornerstone.fitToWindow(element);
169 }
170
171
172 function ZoomIn()
173 {
174 var element = $('#dicomImage').get(0);
175 var viewport = cornerstone.getViewport(element);
176 viewport.scale /= 0.5;
177 cornerstone.setViewport(element, viewport);
178 UpdateViewportInformation();
179 }
180
181
182 function ZoomOut()
183 {
184 var element = $('#dicomImage').get(0);
185 var viewport = cornerstone.getViewport(element);
186 viewport.scale *= 0.5;
187 cornerstone.setViewport(element, viewport);
188 UpdateViewportInformation();
189 }
190
191
192
193 (function (cornerstone) {
194 'use strict';
195
196 function PrintRange(pixels)
197 {
198 var a = Infinity;
199 var b = -Infinity;
200
201 for (var i = 0, length = pixels.length; i < length; i++) {
202 if (pixels[i] < a)
203 a = pixels[i];
204 if (pixels[i] > b)
205 b = pixels[i];
206 }
207
208 console.log(a + ' ' + b);
209 }
210
211 function ChangeDynamics(pixels, source1, target1, source2, target2)
212 {
213 var scale = (target2 - target1) / (source2 - source1);
214 var offset = (target1) - scale * source1;
215
216 for (var i = 0, length = pixels.length; i < length; i++) {
217 pixels[i] = scale * pixels[i] + offset;
218 }
219 }
220
221
222 function getPixelDataDeflate(image) {
223 // Decompresses the base64 buffer that was compressed with Deflate
224 var s = pako.inflate(window.atob(image.Orthanc.PixelData));
225 var pixels = null;
226
227 if (image.color) {
228 var buf = new ArrayBuffer(s.length / 3 * 4); // RGB32
229 pixels = new Uint8Array(buf);
230 var index = 0;
231 for (var i = 0, length = s.length; i < length; i += 3) {
232 pixels[index++] = s[i];
233 pixels[index++] = s[i + 1];
234 pixels[index++] = s[i + 2];
235 pixels[index++] = 255; // Alpha channel
236 }
237 } else {
238 var buf = new ArrayBuffer(s.length * 2); // int16_t
239 pixels = new Int16Array(buf);
240 var index = 0;
241 for (var i = 0, length = s.length; i < length; i += 2) {
242 var lower = s[i];
243 var upper = s[i + 1];
244 pixels[index] = lower + upper * 256;
245 index++;
246 }
247 }
248
249 return pixels;
250 }
251
252
253 // http://stackoverflow.com/a/11058858/881731
254 function str2ab(str) {
255 var buf = new ArrayBuffer(str.length);
256 var pixels = new Uint8Array(buf);
257 for (var i = 0, strLen=str.length; i<strLen; i++) {
258 pixels[i] = str.charCodeAt(i);
259 }
260 return pixels;
261 }
262
263 function getPixelDataJpeg(image) {
264 var jpegReader = new JpegImage();
265 var jpeg = str2ab(window.atob(image.Orthanc.PixelData));
266 jpegReader.parse(jpeg);
267 var s = jpegReader.getData(image.width, image.height);
268 var pixels = null;
269
270 if (image.color) {
271 var buf = new ArrayBuffer(s.length / 3 * 4); // RGB32
272 pixels = new Uint8Array(buf);
273 var index = 0;
274 for (var i = 0, length = s.length; i < length; i += 3) {
275 pixels[index++] = s[i];
276 pixels[index++] = s[i + 1];
277 pixels[index++] = s[i + 2];
278 pixels[index++] = 255; // Alpha channel
279 }
280 } else {
281 var buf = new ArrayBuffer(s.length * 2); // uint8_t
282 pixels = new Int16Array(buf);
283 var index = 0;
284 for (var i = 0, length = s.length; i < length; i++) {
285 pixels[index] = s[i];
286 index++;
287 }
288
289 if (image.Orthanc.Stretched) {
290 ChangeDynamics(pixels, 0, image.Orthanc.StretchLow, 255, image.Orthanc.StretchHigh);
291 }
292 }
293
294 return pixels;
295 }
296
297
298 function getOrthancImage(imageId) {
299 var result = null;
300
301 $.ajax({
302 type: 'GET',
303 url: '../instances/' + compression + '-' + imageId,
304 dataType: 'json',
305 cache: true,
306 async: false,
307 success: function(image) {
308 image.imageId = imageId;
309 if (image.color)
310 image.render = cornerstone.renderColorImage;
311 else
312 image.render = cornerstone.renderGrayscaleImage;
313
314 image.getPixelData = function() {
315 if (image.Orthanc.Compression == 'Deflate')
316 return getPixelDataDeflate(this);
317
318 if (image.Orthanc.Compression == 'Jpeg')
319 return getPixelDataJpeg(this);
320
321 // Unknown compression
322 return null;
323 }
324
325 result = image;
326 },
327 error: function() {
328 return null;
329 }
330 });
331
332 var deferred = $.Deferred();
333 deferred.resolve(result);
334 return deferred;
335 }
336
337 // register our imageLoader plugin with cornerstone
338 cornerstone.registerImageLoader('', getOrthancImage);
339
340 }(cornerstone));
341
342
343 $(document).ready(function() {
344 $('#open-toolbar').button({
345 icons: { primary: 'ui-icon-custom-orthanc' },
346 text: false
347 });
348
349 var series = window.url('?series', window.location.search);
350 if (series == null)
351 return;
352
353 console.log('Displaying series: ' + series);
354 var instances = [ ];
355
356 $.ajax({
357 type: 'GET',
358 url: '../series/' + series,
359 dataType: 'json',
360 cache: false,
361 async: false,
362 success: function(volume) {
363 if (volume.SortedInstances.length != 0) {
364 instances = volume.SortedInstances;
365 $('#topright').html([
366 $('<span>').text(volume.PatientID),
367 $('<br>'),
368 $('<span>').text(volume.PatientName),
369 $('<br>'),
370 $('<span>').text(volume.StudyDescription),
371 $('<br>'),
372 $('<span>').text(volume.SeriesDescription)
373 ]);
374 }
375 }
376 });
377
378 if (instances.length == 0)
379 {
380 console.log('No image in this series');
381 return;
382 }
383
384
385 var currentImageIndex = 0;
386
387 // updates the image display
388 function updateTheImage(imageIndex) {
389 return cornerstone.loadAndCacheImage(instances[imageIndex]).then(function(image) {
390 currentImageIndex = imageIndex;
391 var viewport = cornerstone.getViewport(element);
392 cornerstone.displayImage(element, image, viewport);
393 });
394 }
395
396 // image enable the element
397 var element = $('#dicomImage').get(0);
398 cornerstone.enable(element);
399
400 // set event handlers
401 /*function onImageRendered(e, eventData) {
402 $('#topright').text('Render Time:' + eventData.renderTimeInMs + ' ms');
403 }
404 $(element).on('CornerstoneImageRendered', onImageRendered);*/
405
406 // load and display the image
407 var imagePromise = updateTheImage(0);
408
409 // add handlers for mouse events once the image is loaded.
410 imagePromise.then(function() {
411 viewport = cornerstone.getViewport(element);
412 UpdateViewportInformation();
413
414 // add event handlers to pan image on mouse move
415 $('#dicomImage').mousedown(function (e) {
416 var lastX = e.pageX;
417 var lastY = e.pageY;
418 var mouseButton = e.which;
419
420 $(toolbar).hide();
421
422 $(document).mousemove(function (e) {
423 var deltaX = e.pageX - lastX,
424 deltaY = e.pageY - lastY;
425 lastX = e.pageX;
426 lastY = e.pageY;
427
428 if (mouseButton == 1) {
429 var viewport = cornerstone.getViewport(element);
430 viewport.voi.windowWidth += (deltaX / viewport.scale);
431 viewport.voi.windowCenter += (deltaY / viewport.scale);
432 cornerstone.setViewport(element, viewport);
433 UpdateViewportInformation();
434 }
435 else if (mouseButton == 2) {
436 var viewport = cornerstone.getViewport(element);
437 viewport.translation.x += (deltaX / viewport.scale);
438 viewport.translation.y += (deltaY / viewport.scale);
439 cornerstone.setViewport(element, viewport);
440 }
441 else if (mouseButton == 3) {
442 var viewport = cornerstone.getViewport(element);
443 viewport.scale += (deltaY / 100);
444 cornerstone.setViewport(element, viewport);
445 UpdateViewportInformation();
446 }
447 });
448
449 $(document).mouseup(function (e) {
450 $(document).unbind('mousemove');
451 $(document).unbind('mouseup');
452 });
453 });
454
455 $('#dicomImage').on('mousewheel DOMMouseScroll', function (e) {
456 // Firefox e.originalEvent.detail > 0 scroll back, < 0 scroll forward
457 // chrome/safari e.originalEvent.wheelDelta < 0 scroll back, > 0 scroll forward
458 if (e.originalEvent.wheelDelta < 0 || e.originalEvent.detail > 0) {
459 currentImageIndex ++;
460 if (currentImageIndex >= instances.length) {
461 currentImageIndex = instances.length - 1;
462 }
463 } else {
464 currentImageIndex --;
465 if (currentImageIndex < 0) {
466 currentImageIndex = 0;
467 }
468 }
469
470 updateTheImage(currentImageIndex);
471 $('#slider').slider("option", "value", currentImageIndex);
472
473 //prevent page fom scrolling
474 return false;
475 });
476 });
477
478
479 $('#slider').slider({
480 min: 0,
481 max: instances.length - 1,
482 slide: function(event, ui) {
483 updateTheImage(ui.value);
484 }
485 });
486
487 var toolbar = $.jsPanel({
488 position: { top: 50, left: 10 },
489 size: { width: 155, height: 200 },
490 content: $('#toolbar-content').clone().show(),
491 controls: { buttons: 'none' },
492 title: '<a target="_blank" href="http://www.orthanc-server.com/"><img src="images/orthanc-logo.png" /></a>'
493 });
494
495 $('#open-toolbar').click(function() {
496 toolbar.toggle();
497 });
498
499 $(toolbar).hide();
500
501 $('.toolbar-view', toolbar).buttonset()
502 .children().first().button({
503 icons: { primary: 'ui-icon-info' },
504 text: false
505 }).click(ToggleSeriesInformation).next().button({
506 icons: { primary: 'ui-icon-custom-inversion' },
507 text: false
508 }).click(ToggleInversion).next().button({
509 icons: { primary: 'ui-icon-custom-interpolation' },
510 text: false
511 }).click(ToggleInterpolation).next().button({
512 icons: { primary: 'ui-icon-circle-triangle-s' },
513 text: false
514 }).click(function() {
515 DownloadInstance(instances[currentImageIndex]);
516 });
517
518 $('.toolbar-zoom', toolbar).buttonset()
519 .children().first().button({
520 icons: { primary: 'ui-icon-image' },
521 text: false
522 }).click(AdjustZoom).next().button({
523 icons: { primary: 'ui-icon-zoomin' },
524 text: false
525 }).click(ZoomIn).next().button({
526 icons: { primary: 'ui-icon-zoomout' },
527 text: false
528 }).click(ZoomOut);
529
530 $('.toolbar-windowing', toolbar).buttonset()
531 .children().first().button({
532 icons: { primary: 'ui-icon-custom-default' },
533 text: false
534 }).click(SetDefaultWindowing).next().button({
535 icons: { primary: 'ui-icon-custom-stretch' },
536 text: false
537 }).click(SetFullWindowing).next().button({
538 icons: { primary: 'ui-icon-custom-lung' },
539 text: false
540 }).click(SetLungWindowing).next().button({
541 icons: { primary: 'ui-icon-custom-bone' },
542 text: false
543 }).click(SetBoneWindowing);
544
545
546 function SetCompression(c)
547 {
548 compression = c;
549 cornerstone.imageCache.purgeCache();
550 updateTheImage(currentImageIndex);
551 cornerstone.invalidateImageId(instances[currentImageIndex]);
552 }
553
554 $('.toolbar-quality', toolbar).buttonset()
555 .children().first().button({
556 label: 'L'
557 }).click(function() {
558 SetCompression('jpeg80');
559 }).next().button({
560 label: 'M'
561 }).click(function() {
562 SetCompression('jpeg95');
563 }).next().button({
564 label: 'H'
565 }).click(function() {
566 SetCompression('deflate');
567 });
568
569
570 ResizeCornerstone();
571 $(window).resize(function(e) {
572 if (!$(e.target).hasClass('jsPanel')) // Ignore toolbar resizing
573 {
574 ResizeCornerstone();
575 }
576 });
577
578 });