diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/WebApplication/viewer.js	Wed Feb 25 13:45:35 2015 +0100
@@ -0,0 +1,578 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+var compression = 'jpeg95';
+
+
+
+// Prevent the access to IE
+if(navigator.appVersion.indexOf("MSIE ") != -1)
+{
+  alert("Please use Mozilla Firefox or Google Chrome. Microsoft Internet Explorer is not supported.");
+}
+
+
+function ResizeCornerstone()
+{
+  $('#dicomImage').height($(window).height() - $('#slider').parent().height());
+  var element = $('#dicomImage').get(0);
+  cornerstone.resize(element, true);
+}
+
+
+function SetWindowing(center, width)
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  viewport.voi.windowCenter = center;
+  viewport.voi.windowWidth = width;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+function SetFullWindowing()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  var image = cornerstone.getEnabledElement(element).image;
+
+  if (image.color) {
+    // Ignore color images
+    return;
+  }
+
+  var minValue = image.minPixelValue;
+  var maxValue = image.maxPixelValue;
+  if (minValue == undefined ||
+      maxValue == undefined ||
+      minValue == maxValue) {
+    return; 
+  }
+
+  if (image.slope != undefined && 
+      image.intercept != undefined) {
+    minValue = minValue * image.slope + image.intercept;
+    maxValue = maxValue * image.slope + image.intercept;
+  }
+
+  viewport.voi.windowCenter = (minValue + maxValue) / 2.0;
+  viewport.voi.windowWidth = (maxValue - minValue) / 2.0;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+function SetDefaultWindowing()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  var image = cornerstone.getEnabledElement(element).image;
+
+  viewport.voi.windowCenter = image.windowCenter;
+  viewport.voi.windowWidth = image.windowWidth;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+function SetBoneWindowing()
+{
+  SetWindowing(300, 2000);
+}
+
+
+function SetLungWindowing()
+{
+  SetWindowing(-600, 1600);
+}
+
+
+function UpdateViewportInformation()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+
+  $('#bottomleft').text('WW/WL:' + Math.round(viewport.voi.windowWidth) + '/' + Math.round(viewport.voi.windowCenter));
+  $('#bottomright').text('Zoom: ' + viewport.scale.toFixed(2) + 'x');
+}
+
+
+function ToggleSeriesInformation()
+{
+  $('#topright').toggle();
+}
+
+
+function ToggleInterpolation()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  if (viewport.pixelReplication === true) {
+    viewport.pixelReplication = false;
+  } else {
+    viewport.pixelReplication = true;
+  }
+  cornerstone.setViewport(element, viewport);
+}
+
+
+function ToggleInversion()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  if (viewport.invert === true) {
+    viewport.invert = false;
+  } else {
+    viewport.invert = true;
+  }
+  cornerstone.setViewport(element, viewport);
+}
+
+
+function DownloadInstance(instance)
+{
+  // http://stackoverflow.com/a/3749395/881731
+  var hiddenIFrameID = 'hiddenDownloader',
+  iframe = document.getElementById(hiddenIFrameID);
+  if (iframe === null) {
+    iframe = document.createElement('iframe');
+    iframe.id = hiddenIFrameID;
+    iframe.style.display = 'none';
+    document.body.appendChild(iframe);
+  }
+  iframe.src = '../../instances/' + instance + '/file';
+}
+
+
+function AdjustZoom()
+{
+  var element = $('#dicomImage').get(0);
+  cornerstone.fitToWindow(element);
+}
+
+
+function ZoomIn()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  viewport.scale /= 0.5;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+function ZoomOut()
+{
+  var element = $('#dicomImage').get(0);
+  var viewport = cornerstone.getViewport(element);
+  viewport.scale *= 0.5;
+  cornerstone.setViewport(element, viewport);
+  UpdateViewportInformation();
+}
+
+
+
+(function (cornerstone) {
+  'use strict';
+
+  function PrintRange(pixels)
+  {
+    var a = Infinity;
+    var b = -Infinity;
+
+    for (var i = 0, length = pixels.length; i < length; i++) {
+      if (pixels[i] < a)
+        a = pixels[i];
+      if (pixels[i] > b)
+        b = pixels[i];
+    }    
+
+    console.log(a + ' ' + b);
+  }
+
+  function ChangeDynamics(pixels, source1, target1, source2, target2)
+  {
+    var scale = (target2 - target1) / (source2 - source1);
+    var offset = (target1) - scale * source1;
+
+    for (var i = 0, length = pixels.length; i < length; i++) {
+      pixels[i] = scale * pixels[i] + offset;
+    }    
+  }
+
+
+  function getPixelDataDeflate(image) {
+    // Decompresses the base64 buffer that was compressed with Deflate
+    var s = pako.inflate(window.atob(image.Orthanc.PixelData));
+    var pixels = null;
+
+    if (image.color) {
+      var buf = new ArrayBuffer(s.length / 3 * 4); // RGB32
+      pixels = new Uint8Array(buf);
+      var index = 0;
+      for (var i = 0, length = s.length; i < length; i += 3) {
+        pixels[index++] = s[i];
+        pixels[index++] = s[i + 1];
+        pixels[index++] = s[i + 2];
+        pixels[index++] = 255;  // Alpha channel
+      }
+    } else {
+      var buf = new ArrayBuffer(s.length * 2); // int16_t
+      pixels = new Int16Array(buf);
+      var index = 0;
+      for (var i = 0, length = s.length; i < length; i += 2) {
+        var lower = s[i];
+        var upper = s[i + 1];
+        pixels[index] = lower + upper * 256;
+        index++;
+      }
+    }
+
+    return pixels;
+  }
+
+
+  // http://stackoverflow.com/a/11058858/881731
+  function str2ab(str) {
+    var buf = new ArrayBuffer(str.length);
+    var pixels = new Uint8Array(buf);
+    for (var i = 0, strLen=str.length; i<strLen; i++) {
+      pixels[i] = str.charCodeAt(i);
+    }
+    return pixels;
+  }
+
+  function getPixelDataJpeg(image) {
+    var jpegReader = new JpegImage();
+    var jpeg = str2ab(window.atob(image.Orthanc.PixelData));
+    jpegReader.parse(jpeg);
+    var s = jpegReader.getData(image.width, image.height);
+    var pixels = null;
+
+    if (image.color) {
+      var buf = new ArrayBuffer(s.length / 3 * 4); // RGB32
+      pixels = new Uint8Array(buf);
+      var index = 0;
+      for (var i = 0, length = s.length; i < length; i += 3) {
+        pixels[index++] = s[i];
+        pixels[index++] = s[i + 1];
+        pixels[index++] = s[i + 2];
+        pixels[index++] = 255;  // Alpha channel
+      }
+    } else {
+      var buf = new ArrayBuffer(s.length * 2); // uint8_t
+      pixels = new Int16Array(buf);
+      var index = 0;
+      for (var i = 0, length = s.length; i < length; i++) {
+        pixels[index] = s[i];
+        index++;
+      }
+
+      if (image.Orthanc.Stretched) {
+        ChangeDynamics(pixels, 0, image.Orthanc.StretchLow, 255, image.Orthanc.StretchHigh);
+      }
+    }
+
+    return pixels;
+  }
+  
+
+  function getOrthancImage(imageId) {
+    var result = null;
+
+    $.ajax({
+      type: 'GET',
+      url: '../instances/' + compression + '-' + imageId,
+      dataType: 'json',
+      cache: true,
+      async: false,
+      success: function(image) {
+        image.imageId = imageId;
+        if (image.color)
+          image.render = cornerstone.renderColorImage;
+        else
+          image.render = cornerstone.renderGrayscaleImage;
+
+        image.getPixelData = function() {
+          if (image.Orthanc.Compression == 'Deflate')
+            return getPixelDataDeflate(this);
+
+          if (image.Orthanc.Compression == 'Jpeg')
+            return getPixelDataJpeg(this);
+
+          // Unknown compression
+          return null;
+        }
+
+        result = image;
+      },
+      error: function() {
+        return null;
+      }
+    });
+    
+    var deferred = $.Deferred();
+    deferred.resolve(result);
+    return deferred;
+  }
+
+  // register our imageLoader plugin with cornerstone
+  cornerstone.registerImageLoader('', getOrthancImage);
+
+}(cornerstone));
+
+
+$(document).ready(function() {
+  $('#open-toolbar').button({
+    icons: { primary: 'ui-icon-custom-orthanc' },
+    text: false
+  });
+
+  var series = window.url('?series', window.location.search);
+  if (series == null)
+    return;
+
+  console.log('Displaying series: ' + series);
+  var instances = [ ];
+
+  $.ajax({
+    type: 'GET',
+    url: '../series/' + series,
+    dataType: 'json',
+    cache: false,
+    async: false,
+    success: function(volume) {
+      if (volume.SortedInstances.length != 0) {
+        instances = volume.SortedInstances;
+        $('#topright').html([
+          $('<span>').text(volume.PatientID),
+          $('<br>'),
+          $('<span>').text(volume.PatientName),
+          $('<br>'),
+          $('<span>').text(volume.StudyDescription),
+          $('<br>'),
+          $('<span>').text(volume.SeriesDescription)
+        ]);
+      }
+    }
+  });
+  
+  if (instances.length == 0)
+  {
+    console.log('No image in this series');
+    return;
+  }
+
+
+  var currentImageIndex = 0;
+
+  // updates the image display
+  function updateTheImage(imageIndex) {
+    return cornerstone.loadAndCacheImage(instances[imageIndex]).then(function(image) {
+      currentImageIndex = imageIndex;
+      var viewport = cornerstone.getViewport(element);
+      cornerstone.displayImage(element, image, viewport);
+    });
+  }
+
+  // image enable the element
+  var element = $('#dicomImage').get(0);
+  cornerstone.enable(element);
+
+  // set event handlers
+  /*function onImageRendered(e, eventData) {
+    $('#topright').text('Render Time:' + eventData.renderTimeInMs + ' ms');
+  }
+  $(element).on('CornerstoneImageRendered', onImageRendered);*/
+
+  // load and display the image
+  var imagePromise = updateTheImage(0);
+
+  // add handlers for mouse events once the image is loaded.
+  imagePromise.then(function() {
+    viewport = cornerstone.getViewport(element);
+    UpdateViewportInformation();
+
+    // add event handlers to pan image on mouse move
+    $('#dicomImage').mousedown(function (e) {
+      var lastX = e.pageX;
+      var lastY = e.pageY;
+      var mouseButton = e.which;
+
+      $(toolbar).hide();
+
+      $(document).mousemove(function (e) {
+        var deltaX = e.pageX - lastX,
+        deltaY = e.pageY - lastY;
+        lastX = e.pageX;
+        lastY = e.pageY;
+
+        if (mouseButton == 1) {
+          var viewport = cornerstone.getViewport(element);
+          viewport.voi.windowWidth += (deltaX / viewport.scale);
+          viewport.voi.windowCenter += (deltaY / viewport.scale);
+          cornerstone.setViewport(element, viewport);
+          UpdateViewportInformation();
+        }
+        else if (mouseButton == 2) {
+          var viewport = cornerstone.getViewport(element);
+          viewport.translation.x += (deltaX / viewport.scale);
+          viewport.translation.y += (deltaY / viewport.scale);
+          cornerstone.setViewport(element, viewport);
+        }
+        else if (mouseButton == 3) {
+          var viewport = cornerstone.getViewport(element);
+          viewport.scale += (deltaY / 100);
+          cornerstone.setViewport(element, viewport);
+          UpdateViewportInformation();
+        }
+      });
+
+      $(document).mouseup(function (e) {
+        $(document).unbind('mousemove');
+        $(document).unbind('mouseup');
+      });
+    });
+
+    $('#dicomImage').on('mousewheel DOMMouseScroll', function (e) {
+      // Firefox e.originalEvent.detail > 0 scroll back, < 0 scroll forward
+      // chrome/safari e.originalEvent.wheelDelta < 0 scroll back, > 0 scroll forward
+      if (e.originalEvent.wheelDelta < 0 || e.originalEvent.detail > 0) {
+        currentImageIndex ++;
+        if (currentImageIndex >= instances.length) {
+          currentImageIndex = instances.length - 1; 
+        }         
+      } else {
+        currentImageIndex --;
+        if (currentImageIndex < 0) {
+          currentImageIndex = 0;
+        }         
+      }
+
+      updateTheImage(currentImageIndex);
+      $('#slider').slider("option", "value", currentImageIndex);
+
+      //prevent page fom scrolling
+      return false;
+    });
+  });
+
+
+  $('#slider').slider({
+    min: 0,
+    max: instances.length - 1,
+    slide: function(event, ui) {
+      updateTheImage(ui.value);
+    }
+  });
+
+  var toolbar = $.jsPanel({
+    position: { top: 50, left: 10 },
+    size: { width: 155, height: 200 },
+    content: $('#toolbar-content').clone().show(),
+    controls: { buttons: 'none' },
+    title: '<a target="_blank" href="http://www.orthanc-server.com/"><img src="images/orthanc-logo.png" /></a>'
+  });
+
+  $('#open-toolbar').click(function() {
+    toolbar.toggle();
+  });
+
+  $(toolbar).hide();
+
+  $('.toolbar-view', toolbar).buttonset()
+    .children().first().button({
+      icons: { primary: 'ui-icon-info' },
+      text: false
+    }).click(ToggleSeriesInformation).next().button({
+      icons: { primary: 'ui-icon-custom-inversion' },
+      text: false
+    }).click(ToggleInversion).next().button({
+      icons: { primary: 'ui-icon-custom-interpolation' },
+      text: false
+    }).click(ToggleInterpolation).next().button({
+      icons: { primary: 'ui-icon-circle-triangle-s' },
+      text: false
+    }).click(function() {
+      DownloadInstance(instances[currentImageIndex]);
+    });
+
+  $('.toolbar-zoom', toolbar).buttonset()
+    .children().first().button({
+      icons: { primary: 'ui-icon-image' },
+      text: false
+    }).click(AdjustZoom).next().button({
+      icons: { primary: 'ui-icon-zoomin' },
+      text: false
+    }).click(ZoomIn).next().button({
+      icons: { primary: 'ui-icon-zoomout' },
+      text: false
+    }).click(ZoomOut);
+
+  $('.toolbar-windowing', toolbar).buttonset()
+    .children().first().button({
+      icons: { primary: 'ui-icon-custom-default' },
+      text: false
+    }).click(SetDefaultWindowing).next().button({
+      icons: { primary: 'ui-icon-custom-stretch' },
+      text: false
+    }).click(SetFullWindowing).next().button({
+      icons: { primary: 'ui-icon-custom-lung' },
+      text: false
+    }).click(SetLungWindowing).next().button({
+      icons: { primary: 'ui-icon-custom-bone' },
+      text: false
+    }).click(SetBoneWindowing);
+
+
+  function SetCompression(c)
+  {
+    compression = c;
+    cornerstone.imageCache.purgeCache();
+    updateTheImage(currentImageIndex);
+    cornerstone.invalidateImageId(instances[currentImageIndex]);
+  }
+
+  $('.toolbar-quality', toolbar).buttonset()
+    .children().first().button({
+      label: 'L'
+    }).click(function() {
+      SetCompression('jpeg80');
+    }).next().button({
+      label: 'M'
+    }).click(function() {
+      SetCompression('jpeg95');
+    }).next().button({
+      label: 'H'
+    }).click(function() {
+      SetCompression('deflate');
+    });
+
+
+  ResizeCornerstone();
+  $(window).resize(function(e) {
+    if (!$(e.target).hasClass('jsPanel'))  // Ignore toolbar resizing
+    {
+      ResizeCornerstone();
+    }
+  });
+
+});