0
|
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 });
|