/**** Multiresolution Computed Tomography Viewer (MCTV) ****
*Contributions:
* Further enhancements and cleaning of code by Lasse Wollatz; October, 2016; L.Wollatz@soton.ac.uk
* - improved error handling
* - error bar added for feedback
* - automatic adjustment of units added
* - outsourced styles into css
* - improved styles and backward compatibility
* Bug fixes, 3D mods, threshold and enhancements written by Lasse Wollatz; February, 2015; L.Wollatz@soton.ac.uk
* Publication: L. Wollatz, S. J. Cox, and S. J. Johnston, (2015) Web-Based Manipulation of Multiresolution Micro-CT Images. e-Science 2015.
* - added tile loading
* - corrected placement of tiles
* - optimized tile loading to reduce delay
* - threshold modifications functionality added
* - zoom "underlay" to improve user experience
* - non-tiled zoom added
* - measuring functionality added
* - resolution and CT data loading from JSON
* - improved styles
* - added animations
* - sliders added for more feedback and improved navigation
* - cross-sectional thumbnail added
* - display and event handling of thumbnails corrected and enhanced
* iPhone/iPad modifications written by Matthew K. Lindley; August 25, 2010
* base code (MIV) by Shawn Mikula; 2007; brainmaps@gmail.com.
* Publication: S. Mikula, I. Trotts, J. M. Stone, and E. G. Jones, (2007) Internet-Enabled High-Resolution Brain Mapping and Virtual Microscopy. NeuroImage 35 (1): 9-15.
* (Updated version of his script available at http://www.connectomes.org.)
* - main html structure
* - tile placement
* - basic zoom
* - basic navigation
*
*You are free to use this software for non-commercial
*use only, and only if proper credit is clearly visibly
*given wherever the code is used.
************************************************************/
var imgtype = ".jpg"; //filetype of image files, can be jpg,png,bmp should also work with gif NOT tif or dicom!
var tileSize = 256; //size of a tile in pixel
/*variables that can be set through the url*/
//root //path to infoJson.txt -> requires either root or JSON!
//JSON //or directly specify the full path -> requires either root or JSON!
//start //number of first slice to display -> defaults to 1/3 of image stack
//vX //X position to center as fraction of the image width -> defaults to 0.5
//vY //Y position to center as fraction of the image height -> defaults to 0.5
//vT //initial zoom level -> defaults to maximum normal zoom
//coords //0 if no position information should be displayed -> defaults to 1
//width //declare image width if not defined in JSON
//height //declare image height if not defined in JSON
//res //declare image resolution if not defined in JSON
//zres //declare image z-resolution if not defined in JSON
//resunits //declare image resolution units if not defined in JSON
/*JSON*/
var JSONout; //content of JSON file
var getJSON = false; //has JSON been requested?
var loadedJSON = false; //has JSON file been loaded?
var JSON; //full path of JSON file including filename
var rootpath; //path where to check for infoJSON.txt
var labelspath; //full path for labels JSON file for current tile
/*positioning and sizing*/
var viewportWidth = 2000; //width of viewable area in browser
var viewportHeight = 1000; //height of viewable area in browser
var innerDiv; //innerDiv html element
var mTop; //current top position of image relative to viewport
var mLeft; //current left position of image relative to viewport
var gImageWidth, gImageHeight; //width and height of image
var width, height; //width and height of specific slice images
/*resolution/dimension*/
var resunits = "μm"; //units of the resolution factors
var res = 1.0; //size of 1 pixel (resunits/px)
var zres = 1.0; //thickness of 1 slice (resunits/slice)
var JSONnum; //number of images (z dimension size)
/*zoom*/
var xtrazoomMax = 5; //maximal additional zoom beyond actual size of the image
var gTierCount; //number of zoom levels due to tiling
var zoom = 0; //current zoom level
var xtrazoom = 1; //current extra zoom beyond actual size of image
/*threshold*/
var thresLower; //Lower Threshold
var thresUpper; //Upper Threshold
var densmin = -1000; //HU of minimum value (0)
var densmax = 1000; //HU of maximum value (255)
/*mouse movement*/
var clickmode = 0; //mode for mouse click/ drag
var lasteventX = 0; //X coordiante from last known mouse movement
var lasteventY = 0; //Y coordiante from last known mouse movement
var dragStartTop; //Y coordiante from mouse down event
var dragStartLeft; //X coordiante from mouse down event
var dragging = false; //if mouse-drag and mouse-click set to pan
var measuring = false; //if mouse-drag and mouse-click set to measure
/*mouse wheel*/
var wheelmode = 0; //mode for mousewheel movement
var wheelobs = 0; //count of mousewheel operations
/*touch*/
var touchIdentifier;
var gestureScale = 1;
/*code for checking performance*/
// try {
// var Tstart = performance.now();
// } catch (err) {
// var Tstart = new Date().getTime();
// }
///try {
// var Tend = performance.now();
// } catch (err) {
// var Tend = new Date().getTime();
// }
// try {
// var TendL = performance.now();
// } catch (err) {
// var TendL = new Date().getTime();
// }
// try {
// var TendR = performance.now();
// } catch (err) {
// var TendR = new Date().getTime();
// }
/*others*/
var path = '/'; //path to specific slice images
var imgpath = ''; //path to specific tile
var slidePointer = 0; //current image number (z dimension position)
var start; //first slice to load
var coords = 1; //if coordinates are displayed
var ActiveTask; //taskid of currently active task
var isControl = false; //boolean if key controls are being displayed
var spaddingX = 20; //horizontal artificial padding for slider tables
/*** BASICS ***
* logError(errstr)
* removeError(errstr)
* isNumeric(n)
* engUnit(number,unit)
* stripPx(value)
* getVar(name)
*/
function logError(errstr) {
/*adds an error to the list of errors
*if Error already exists, a counter is used to save space
*/
var a, b, c;
var remStr ="";
var ecntr = 2;
try {
var allErrStr = document.getElementById('error').innerHTML;
a = allErrStr.toLowerCase().indexOf(errstr.toLowerCase());
if (a < 0) {
document.getElementById('error').innerHTML += errstr + "
";
} else {
b = allErrStr.toLowerCase().indexOf("
0 && c < b) {
ecntr = parseFloat(allErrStr.substring(c + 2, b));
ecntr += 1;
}
allErrStr = allErrStr.replace(remStr, errstr + " x" + ecntr + "
");
document.getElementById('error').innerHTML = allErrStr;
}
} catch (err) {}
}
function removeError(errstr) {
/*removes an error from the list of errors
*if Error existed multiple times, all entrys are removed
*only the first part of the error message needs to be provided,
* useful if part of the error message is variable
*/
try {
var allErrStr = document.getElementById('error').innerHTML;
if(allErrStr != ""){
var a = allErrStr.indexOf(errstr);
if (a >= 0) {
var b = allErrStr.indexOf("
= 0.1 && Math.abs(value) < 100){
return Math.round(value*100)/100 + " " + unit;
}
//detect zeros of unit
if (unit === "nm"){
unitlevel = -9;
}else if (unit === "μm"){
unitlevel = -6;
}else if (unit === "mm"){
unitlevel = -3;
}else if (unit === "cm"){
unitlevel = -2;
}else if (unit === "dm"){
unitlevel = -1;
}else if (unit === "km"){
unitlevel = 3;
}
//get to next best unit
while(Math.abs(value) < 0.1 && unitlevel <= 3){
value = value*10;
unitlevel -= 1;
}
while(Math.abs(value) >= 100 && unitlevel >= -9){
value = value/10;
unitlevel += 1;
}
if (unitlevel == 2 || unitlevel == -4 || unitlevel == -7){
value = value/10;
unitlevel += 1;
}else if (unitlevel == 1 || unitlevel == -5 || unitlevel == -8){
value = value*10;
unitlevel -= 1;
}
//return string representation
if (unitlevel == -9){
unit = "nm";
}else if (unitlevel == -6){
unit = "μm";
}else if (unitlevel == -3){
unit = "mm";
}else if (unitlevel == -2){
unit = "cm";
}else if (unitlevel == -1){
unit = "dm";
}else if (unitlevel == 0){
unit = "m";
}else if (unitlevel == 3){
unit = "km";
}
return Math.round(value*100)/100 + " " + unit;
}
function stripPx(value) {
/*converts pixel string into float
* therefore '128px' -> 128.0
*/
if (value === "") {
return 0;
}
return parseFloat(value.substring(0, value.length - 2));
}
function getVar(name) {
/*reads a variable from the URL
*/
var get_string = document.location.search;
var return_value = '';
var name_index = 0;
var end_of_value = -1;
var value = '';
do {
name_index = get_string.indexOf(name + '=');
if (name_index !== -1) {
get_string = get_string.substr(name_index + name.length + 1, get_string.length - name_index);
end_of_value = get_string.indexOf('&');
if (end_of_value !== -1) {
value = get_string.substr(0, end_of_value);
} else {
value = get_string;
}
if (return_value === '' || value === '') {
return_value += value;
} else {
return_value += ', ' + value;
}
}
} while (name_index !== -1);
var space = return_value.indexOf('+');
while (space !== -1) {
return_value = return_value.substr(0, space) + ' ' + return_value.substr(space + 1, return_value.length);
space = return_value.indexOf('+');
}
return (return_value);
}
/*** END BASICS ***/
/*** TILE HANDLER ***
* getVisibleTiles()
* createTile(pCol, pRow, tileSize, tileName, staticPath)
* displayTile(pCol, pRow, tileName)
* checkTiles(isForced)
* refreshTiles()
* refreshUnderlay()
*/
function getVisibleTiles() {
/*get x and y of all tiles that are in the visible area
*units of x and y are in tiles (starting at 0)
*Note: upper bound is not defined here but only within the checkTiles function
* => wouldn't it be more sensible to check it here?
*/
innerDiv = document.getElementById("innerDiv");
//need to get an absolute function here!
var mapX = stripPx(innerDiv.style.left);
var mapY = stripPx(innerDiv.style.top);
//changed from abs function to -1*.
//this way only the tiles on screen +-neighbours tiles are loaded.
var neighbours = 1; //minimum required is 1!
var startX = -1 * Math.floor(mapX / (tileSize * xtrazoom)) - neighbours;
var startY = -1 * Math.floor(mapY / (tileSize * xtrazoom)) - neighbours;
//+neighbours and not +neighbours+1, as the start of the image is recorded...
var tilesX = Math.ceil(viewportWidth / (tileSize * xtrazoom)) + neighbours;
var tilesY = Math.ceil(viewportHeight / (tileSize * xtrazoom)) + neighbours;
var visibleTileArray = [];
var counter = 0;
var x, y;
for (x = startX; x < (tilesX + startX); x++) {
for (y = startY; y < (tilesY + startY); y++) {
//need to add upper bound here (number of available tiles in each direction is???)
if (x >= 0 && y >= 0) {
visibleTileArray[counter++] = [x, y];
}
}
}
return visibleTileArray;
}
function createTile(pCol, pRow, tileSize, tileName, staticPath) {
/*function called if tile is not yet loaded
*/
// console.timeStamp(tileName + " Start");
var image = document.createElement("img");
image.src = staticPath + tileName;
image.setAttribute("id", tileName);
image.style.opacity = 0;
image.style.zIndex = -2;
//start threshold
//needed to place in onload to avoid threshold images before they are loaded
image.onload = function () {
var i;
var imgstr;
var v = 0;
var imgid = "";
// try {
// TendL = performance.now();
// } catch (err) {
// TendL = new Date().getTime();
// }
// var time = TendL - Tstart;
// document.getElementById('debug2').innerHTML = 'LT: ' + time;
var c = document.createElement("canvas");
var thresLowerVal = parseInt(255 * (thresLower - densmin) / (densmax - densmin), 10);
var thresUpperVal = parseInt(255 * (thresUpper - densmin) / (densmax - densmin), 10);
c.width = image.width;
c.height = image.height;
try{
var ctx = c.getContext('2d');
ctx.drawImage(image, 0, 0);
var idata = ctx.getImageData(0, 0, c.width, c.height);
var d = idata.data;
for (i = 0; i < d.length; i += 4) {
var r = d[i];
v = parseInt(255 * (r - thresLowerVal) / (thresUpperVal - thresLowerVal), 10);
v = (v >= 255) ? 255 : v;
v = (v <= 0) ? 0 : v;
d[i] = d[i + 1] = d[i + 2] = v;
d[i + 3] = 255;
}
ctx.putImageData(idata, 0, 0);
imgstr = c.toDataURL("image/png");
removeError("Failed to apply threshold");
}catch(err){
logError("Failed to apply threshold: "+err);
imgstr = image.src;
}
//reset the onload function, to avoid recursive threshold
image.onload = function () {
image.style.zIndex = 0;
image.style.height = (tileSize * xtrazoom) + "px";
image.style.display = "block";
image.style.width = "auto";
imgid = tileName.replace(".", "\\.");
$("#" + imgid + "").finish();
$("#" + imgid + "").animate({
opacity : 1
}, 600);
// console.timeStamp(tileName + " End");
// try {
// Tend = performance.now();
// } catch (err) {
// Tend = new Date().getTime();
// }
// var time = Tend - Tstart;
// document.getElementById('debug').innerHTML = 'ET: ' + time;
};
image.src = imgstr;
};
//end threshold
var brighness = 50;
image.style.position = "absolute";
image.style.left = (pCol * tileSize * xtrazoom) + "px";
image.style.top = (pRow * tileSize * xtrazoom) + "px";
var imageTiles = document.getElementById("imageTiles");
imageTiles.appendChild(image);
}
function displayTile(pCol, pRow, tileName) {
/*called if tile is loaded already but needs to be updated
*/
var image = document.getElementById(tileName);
var imgid = tileName.replace(".", "\\.");
$("#" + imgid + "").finish();
$("#" + imgid + "").animate({
opacity : 0
}, 2);
image.style.zIndex = -2;
image.style.height = "";
image.src = imgpath + tileName;
//start threshold
image.onload = function () {
var i;
var r, v;
var imgstr;
// try {
// TendL = performance.now();
// } catch (err) {
// TendL = new Date().getTime();
// }
// var time = TendL - Tstart;
// document.getElementById('debug2').innerHTML = 'LT: ' + time;
var c = document.createElement("canvas");
c.width = image.width;
c.height = image.height;
var thresLowerVal = parseInt(255 * (thresLower - densmin) / (densmax - densmin), 10);
var thresUpperVal = parseInt(255 * (thresUpper - densmin) / (densmax - densmin), 10);
try{
var ctx = c.getContext('2d');
ctx.drawImage(image, 0, 0);
var idata = ctx.getImageData(0, 0, c.width, c.height);
var d = idata.data;
for (i = 0; i < d.length; i += 4) {
r = d[i];
v = parseInt(255 * (r - thresLowerVal) / (thresUpperVal - thresLowerVal), 10);
v = (v >= 255) ? 255 : v;
v = (v <= 0) ? 0 : v;
d[i] = d[i + 1] = d[i + 2] = v;
d[i + 3] = 255;
}
ctx.putImageData(idata, 0, 0);
imgstr = c.toDataURL("image/png");
removeError("Failed to apply threshold");
}catch(err){
logError("Failed to apply threshold: "+err);
imgstr = image.src;
}
//reset the onload function, to avoid recursive threshold
image.onload = function () {
image.style.height = (tileSize * xtrazoom) + "px";
image.style.display = "block";
image.style.width = "auto";
image.style.zIndex = 0;
imgid = tileName.replace(".", "\\.");
$("#" + imgid + "").stop();
$("#" + imgid + "").animate({
opacity : 1
}, 600);
// try {
// Tend = performance.now();
// } catch (err) {
// Tend = new Date().getTime();
// }
// var time = Tend - Tstart;
// document.getElementById('debug').innerHTML = 'ET: ' + time;
};
image.src = imgstr;
};
//end threshold
}
function checkTiles(isForced) {
/*goes through the tiles and updates them as necessary.
*Updates every tile, if isForced
*/
// console.timeStamp("checkTiles Start");
// document.getElementById('debug').innerHTML = "";
//static path stuff didn't work - but shouldn't be necessary any longer
var staticPath = imgpath.substring(0);
innerDiv = document.getElementById("innerDiv");
var imageTiles = document.getElementById("imageTiles");
var visibleTiles = getVisibleTiles();
var tileArray = visibleTiles[0];
var visibleTilesMap = {};
var gTileCountWidth = new Array();
var gTileCountHeight = new Array();
var tempWidth = gImageWidth;
var tempHeight = gImageHeight;
var divider = Math.pow(2, (gTierCount - zoom - 1)) / xtrazoom;
var j;
var i = 0;
while (i < visibleTiles.length) {
tileArray = visibleTiles[i];
gTileCountWidth = new Array();
gTileCountHeight = new Array();
tempWidth = gImageWidth;
tempHeight = gImageHeight;
divider = 2;
//do I need to compute this for all zoom levels here?
for (j = gTierCount - 1; j >= 0; j--) {
gTileCountWidth[j] = Math.floor(tempWidth / (tileSize));
if (tempWidth % (tileSize)) {
gTileCountWidth[j]++;
}
gTileCountHeight[j] = Math.floor(tempHeight / (tileSize));
if (tempHeight % (tileSize)) {
gTileCountHeight[j]++;
}
tempWidth = Math.floor(gImageWidth / divider);
tempHeight = Math.floor(gImageHeight / divider);
divider *= 2;
if (tempWidth % 2){tempWidth++;}
if (tempHeight % 2){tempHeight++;}
}
moveThumb2();
var pCol = tileArray[0];
var pRow = tileArray[1];
var tier = zoom;
//why do I check this here? else -> repeat image which is already loaded?
if (pCol < gTileCountWidth[zoom] && pRow < gTileCountHeight[zoom]) {
var tileName = zoom + "-" + pCol + "-" + pRow + imgtype;
visibleTilesMap[tileName] = true;
var img = document.getElementById(tileName);
if (!img) {
//try and catch should no longer be necessary
try {
createTile(pCol, pRow, tileSize, tileName, staticPath);
removeError("Failed to create tile " + tileName + ":");
} catch (err) {
var image = document.createElement("img");
image.src = staticPath + tileName;
image.setAttribute("id", tileName);
image.style.position = "absolute";
image.style.left = (pCol * tileSize * xtrazoom) + "px";
image.style.top = (pRow * tileSize * xtrazoom) + "px";
image.style.zIndex = 0;
imageTiles.appendChild(image);
logError("Failed to create tile " + tileName + ": " + err);
}
} else if (isForced) {
displayTile(pCol, pRow, tileName);
}
}
i++;
}
var imgs = imageTiles.getElementsByTagName("img");
for (i = 0; i < imgs.length; i++) {
var id = imgs[i].getAttribute("id");
if (!visibleTilesMap[id] && id !== "mainTile") {
imageTiles.removeChild(imgs[i]);
i--;
}
}
// try {
// TendR = performance.now();
// } catch (err) {
// TendR = new Date().getTime();
// }
// var time = TendR - Tstart;
// document.getElementById('debug3').innerHTML = 'TTR: ' + time;
}
function refreshTiles() {
/*remove all tiles so that a reload is forced.
*/
//refresh main tile
var mainTile = document.getElementById("mainTile");
var divider = Math.pow(2, (gTierCount - zoom - 1)) / xtrazoom;
var MTwidth = (gImageWidth / divider) + "px";
$("#mainTile").finish();
$("#mainTile").animate({
width : MTwidth
}, 300, "swing");
//remove other tiles
var imageTiles = document.getElementById("imageTiles");
var imgs = imageTiles.getElementsByTagName("img");
while (imgs.length > 1) {
if (imgs[0].id != "mainTile") {
imageTiles.removeChild(imgs[0]);
} else {
imageTiles.removeChild(imgs[1]);
}
}
}
function refreshUnderlay() {
/*update the underlying (bad-resolution) image
*/
var temp = document.createElement("img");
var mainTile = document.getElementById("mainTile");
var timg = document.getElementById('timg');
//start threshold
temp.onload = function () {
var i;
var r, v;
try{
var c = document.createElement("canvas");
c.width = timg.width;
c.height = timg.height;
var thresLowerVal = parseInt(255 * (thresLower - densmin) / (densmax - densmin), 10);
var thresUpperVal = parseInt(255 * (thresUpper - densmin) / (densmax - densmin), 10);
var ctx = c.getContext('2d');
ctx.drawImage(timg, 0, 0);
var idata = ctx.getImageData(0, 0, c.width, c.height);
var d = idata.data;
for (i = 0; i < d.length; i += 4) {
r = d[i];
v = parseInt(255 * (r - thresLowerVal) / (thresUpperVal - thresLowerVal), 10);
v = (v >= 255) ? 255 : v;
v = (v <= 0) ? 0 : v;
d[i] = d[i + 1] = d[i + 2] = v;
d[i + 3] = 255;
}
ctx.putImageData(idata, 0, 0);
var imgstr = c.toDataURL("image/png");
mainTile.onload = function () {
var divider = Math.pow(2, (gTierCount - zoom - 1)) / xtrazoom;
mainTile.style.width = gImageWidth / divider + "px";
};
mainTile.src = imgstr;
removeError("Failed to apply threshold:");
}catch(err){
//failed to apply filter to image
logError("Failed to apply threshold: "+err);
mainTile.src = timg.src;
}
};
//end threshold
temp.src = imgpath + '0-0-0' + imgtype;
}
/*** END TILE HANDLER ***/
/*** ZOOM ***
* zoomIn()
* zoomOut()
* updateZoom()
* clickZoom(event)
*/
function zoomIn() {
/*zooms in 1 level
*/
var IDtop, IDleft;
if (zoom !== gTierCount - 1) {
//normal zoom through tiles
$("#innerDiv").finish();
innerDiv = document.getElementById("innerDiv");
mTop = stripPx(innerDiv.style.top);
mLeft = stripPx(innerDiv.style.left);
IDtop = 2 * mTop - viewportHeight / 2 + 'px';
IDleft = 2 * mLeft - viewportWidth / 2 + 'px';
zoom = zoom + 1;
$("#innerDiv").animate({
top : IDtop,
left : IDleft
}, {
duration : 300,
complete : function () {
checkTiles(1);
}
});
refreshTiles();
var imageLabels = document.getElementById("imageLabels");
var divs = imageLabels.getElementsByTagName("div");
for (var $i = 0; $i < divs.length; $i++) {
var Ltemp = "L" + $i;
$("#" + Ltemp + "").finish();
IDtop = 2 * stripPx(document.getElementById(Ltemp).style.top) + 'px';
IDleft = 2 * stripPx(document.getElementById(Ltemp).style.left) + 'px';
$("#" + Ltemp + "").animate({
top : IDtop,
left : IDleft
}, 300, "swing");
}
clickMode1()
} else if (xtrazoom < xtrazoomMax) {
//extra zoom beyond image resolution
zoomdif = xtrazoom;
xtrazoom = xtrazoom + 1;
zoomdif = xtrazoom / zoomdif;
$("#innerDiv").finish();
innerDiv = document.getElementById("innerDiv");
mTop = stripPx(innerDiv.style.top);
mLeft = stripPx(innerDiv.style.left);
IDtop = 0.5 * (1 - zoomdif) * viewportHeight + zoomdif * mTop + 'px';
IDleft = 0.5 * (1 - zoomdif) * viewportWidth + zoomdif * mLeft + 'px';
$("#innerDiv").animate({
top : IDtop,
left : IDleft
}, {
duration : 300,
complete : function () {
checkTiles(0);
}
});
var imageLabels = document.getElementById("imageLabels");
var divs = imageLabels.getElementsByTagName("div");
for (var $i = 0; $i < divs.length; $i++) {
var Ltemp = "L" + $i;
$("#" + Ltemp + "").finish();
IDtop = zoomdif * stripPx(document.getElementById(Ltemp).style.top) + 'px';
IDleft = zoomdif * stripPx(document.getElementById(Ltemp).style.left) + 'px';
$("#" + Ltemp + "").animate({
top : IDtop,
left : IDleft
}, 300, "swing");
}
clickMode1()
refreshTiles();
//checkTiles(0);
}
updateZoom();
}
function zoomOut() {
/*zooms out 1 level
*/
if (xtrazoom > 1) {
zoomdif = xtrazoom;
xtrazoom = xtrazoom - 1;
zoomdif = xtrazoom / zoomdif;
$("#innerDiv").finish();
innerDiv = document.getElementById("innerDiv");
mTop = stripPx(innerDiv.style.top);
mLeft = stripPx(innerDiv.style.left);
var IDtop = 0.5 * (1 - zoomdif) * viewportHeight + zoomdif * mTop + 'px';
var IDleft = 0.5 * (1 - zoomdif) * viewportWidth + zoomdif * mLeft + 'px';
$("#innerDiv").animate({
top : IDtop,
left : IDleft
}, {
duration : 300,
complete : function () {
checkTiles(0);
}
});
var imageLabels = document.getElementById("imageLabels");
var divs = imageLabels.getElementsByTagName("div");
for (var $i = 0; $i < divs.length; $i++) {
var Ltemp = "L" + $i;
$("#" + Ltemp + "").finish();
IDtop = zoomdif * stripPx(document.getElementById(Ltemp).style.top) + 'px';
IDleft = zoomdif * stripPx(document.getElementById(Ltemp).style.left) + 'px';
$("#" + Ltemp + "").animate({
top : IDtop,
left : IDleft
}, 300, "swing");
}
clickMode1()
refreshTiles();
//checkTiles(0);
} else if (zoom != 0) {
$("#innerDiv").finish();
var innerDiv = document.getElementById("innerDiv");
mTop = stripPx(innerDiv.style.top);
mLeft = stripPx(innerDiv.style.left);
var IDtop = mTop / 2 + viewportHeight / 4 + 'px';
var IDleft = mLeft / 2 + viewportWidth / 4 + 'px';
$("#innerDiv").animate({
top : IDtop,
left : IDleft
}, {
duration : 300,
complete : function () {
checkTiles(1);
}
});
zoom = zoom - 1;
refreshTiles();
var imageLabels = document.getElementById('imageLabels');
var divs = imageLabels.getElementsByTagName("div");
for (var $i = 0; $i < divs.length; $i++) {
var Ltemp = "L" + $i;
$("#" + Ltemp + "").finish();
IDtop = .5 * stripPx(document.getElementById(Ltemp).style.top) + 'px';
IDleft = .5 * stripPx(document.getElementById(Ltemp).style.left) + 'px';
$("#" + Ltemp + "").animate({
top : IDtop,
left : IDleft
}, 300, "swing");
}
clickMode1()
//checkTiles(1);
}
updateZoom();
}
function updateZoom() {
/*updates the zoom slider
*/
var zoomsliderdiv;
var zwidth = 150;
var zheight = 10;
var zpaddingY = 5;
var zpaddingX = 20;
var activewidth = Math.max(Math.round((zwidth * (zoom) / (gTierCount + xtrazoomMax - 2)) - 5), 0);
var active2width = Math.max(Math.round((zwidth * (xtrazoom - 1) / (gTierCount + xtrazoomMax - 2))), 0);
var inactivewidth = Math.max(Math.round((zwidth - zwidth * (zoom + xtrazoom - 1) / (gTierCount + xtrazoomMax - 2)) - 5), 0);
//Create table for zoom slider
var zoomstr = '
';
zoomstr += '';
zoomstr += ' | ';
if (activewidth) {
zoomstr += '';
zoomstr += ' | ';
} else {
inactivewidth -= 5;
}
if (active2width) {
zoomstr += '';
zoomstr += ' | ';
}
zoomstr += ' | ';
if (inactivewidth) {
zoomstr += '';
zoomstr += ' | ';
}
zoomstr += ' |
';
zoomsliderdiv = document.getElementById('zoomslider');
zoomsliderdiv.innerHTML = zoomstr
zoomsliderdiv.onmouseup = clickZoom;
//update the small ruler shown at the bottom left
document.getElementById('theScale').innerHTML = engUnit((Math.pow(2, gTierCount - zoom - 1) / (xtrazoom)) * res * 50,resunits);
}
function clickZoom(event) {
/*move to position clicked on zoom slider
*/
if (event) {
xThumb = event.clientX;
yThumb = event.clientY;
var sliderLeft = document.getElementById("zoomslider").getBoundingClientRect().left;
var sliderWidth = document.getElementById("zoomslider").getBoundingClientRect().right - sliderLeft;
var perc = Math.min(Math.abs(xThumb - sliderLeft - spaddingX) / (sliderWidth - 2 * spaddingX), 1);
totzoom = Math.round(0 + (gTierCount + xtrazoomMax - 2) * perc);
newzoom = Math.max(Math.min(gTierCount - 1, totzoom), 0);
newxtrazoom = Math.min(Math.max(1, totzoom + 1 - newzoom), xtrazoomMax);
totzoom = newzoom + newxtrazoom - 1;
oldzoom = zoom + xtrazoom - 1;
if (oldzoom < totzoom) {
for (j = oldzoom; j < totzoom; j++) {
zoomIn();
}
} else if (oldzoom > totzoom) {
for (j = oldzoom; j > totzoom; j--) {
zoomOut();
}
}
}
}
/*** END ZOOM ***/
/*** DENSITY RANGE (THRESHOLD) ***
* updateThreshold()
* clickThreshold(event)
*/
function updateThreshold() {
/*refreshes the Threshold slider
*/
var thressliderdiv;
var totmin = Math.round(densmin);
var totmax = Math.round(densmax);
var swidth = 145;
var inactive1width = Math.max(Math.round((swidth * (thresLower - totmin) / (totmax - totmin)) - 5), 0);
var inactive2width = Math.max(Math.round((swidth * (totmax - thresUpper) / (totmax - totmin)) - 5), 0);
var activewidth = Math.max(Math.round((swidth - swidth * (thresLower - totmin + totmax - thresUpper) / (totmax - totmin)) - 10), 0);
//Creating table for threshold double slider
var thresstr = ' | ';
if (inactive1width) {
thresstr += '';
thresstr += ' | ';
}
thresstr += ' | ';
if (activewidth) {
thresstr += '';
thresstr += ' | ';
}
thresstr += ' | ';
if (inactive2width) {
thresstr += '';
thresstr += ' | ';
}
thresstr += ' | ';
thresstr += '
';
thressliderdiv = document.getElementById('thresslider');
thressliderdiv.innerHTML = thresstr;
thressliderdiv.onmouseup = clickThreshold;
}
function clickThreshold(event) {
/*apply clicked position on threshold slider
*/
if (event) {
xThumb = event.clientX;
yThumb = event.clientY;
var sliderLeft = document.getElementById("thresslider").getBoundingClientRect().left;
var sliderWidth = document.getElementById("thresslider").getBoundingClientRect().right - sliderLeft;
var totmin = Math.round(densmin);
var totmax = Math.round(densmax);
var perc = Math.min(Math.abs(xThumb - sliderLeft - spaddingX) / (sliderWidth - 2 * spaddingX), 1);
perc = Math.max(perc, 0);
var newthres = Math.round(totmin + (totmax - totmin) * perc);
deltamin = Math.abs(newthres - thresLower);
deltamax = Math.abs(newthres - thresUpper);
if (deltamin < deltamax) {
thresLower = newthres
} else {
thresUpper = newthres
}
refreshUnderlay()
updateThreshold()
checkTiles(1)
}
}
/*** END DENSITY RANGE (THRESHOLD) ***/
/*** SLICES ***
* updateSlice()
* clickSlice(event)
* sliceNext(delta,scaled)
* slicePrev(delta,scaled)
* sliceNextDef()
* slicePrevDef()
*/
function updateSlice() {
/*update Slice slider
*/
var slicesliderdiv;
var swidth = 145;
var activewidth = Math.max(Math.round((swidth * slidePointer / JSONnum) - 5), 0);
var inactivewidth = Math.max(Math.round((swidth - swidth * slidePointer / JSONnum) - 5), 0);
var sliderstr = '';
sliderstr += ' | ';
if (activewidth) {
sliderstr += '';
sliderstr += ' | ';
} else {
inactivewidth -= 5;
}
sliderstr += ' | ';
if (inactivewidth) {
sliderstr += '';
sliderstr += ' | ';
}
sliderstr += ' | ';
sliderstr += '
';
slicesliderdiv = document.getElementById('sliceslider');
slicesliderdiv.innerHTML = sliderstr;
slicesliderdiv.onmouseup = clickSlice;
slicesliderdiv.ondragstart = function () {
return false;
}
}
function clickSlice(event) {
/*move to position clicked on slice slider
*/
if (event) {
xThumb = event.clientX;
yThumb = event.clientY;
var sliderLeft = document.getElementById("sliceslider").getBoundingClientRect().left;
var sliderWidth = document.getElementById("sliceslider").getBoundingClientRect().right - sliderLeft;
var perc = Math.min(Math.abs(xThumb - sliderLeft - spaddingX) / (sliderWidth - 2 * spaddingX), 1);
perc = Math.max(perc, 0);
newslice = Math.round(JSONnum * perc);
delta = newslice - slidePointer;
if (delta > 0) {
sliceNext(delta, false)
} else if (delta < 0) {
slicePrev(delta, false)
}
}
}
function sliceNext(delta, scaled) {
/*display next image in stack
*/
// try {
// Tstart = performance.now();
// } catch (err) {
// Tstart = new Date().getTime();
// }
try {
clearTimeout(ActiveTask);
} catch (err) {}
if (scaled) {
slidePointer += Math.floor(gTierCount * Math.abs(delta) / (zoom + xtrazoom)) + 1;
} else {
slidePointer += Math.floor(Math.abs(delta));
}
while (slidePointer >= JSONnum) {
slidePointer -= JSONnum;
}
path = JSONout.slides[slidePointer].path;
width = JSONout.slides[slidePointer].width;
height = JSONout.slides[slidePointer].height;
if (JSONout.slides[slidePointer].labelspath != undefined) {
labelspath = JSONout.slides[slidePointer].labelspath;
loadLabels();
} else {
labelspath = "";
}
updatePosition();
init();
}
function slicePrev(delta, scaled) {
/*display previous image in stack
*/
// try {
// Tstart = performance.now();
// } catch (err) {
// Tstart = new Date().getTime();
// }
try {
clearTimeout(ActiveTask);
} catch (err) {}
if (scaled) {
slidePointer += -1 * Math.floor(gTierCount * Math.abs(delta) / (zoom + xtrazoom)) - 1;
} else {
slidePointer += -1 * Math.floor(Math.abs(delta));
}
while (slidePointer < 0) {
slidePointer += JSONnum;
}
path = JSONout.slides[slidePointer].path;
width = JSONout.slides[slidePointer].width;
height = JSONout.slides[slidePointer].height;
if (JSONout.slides[slidePointer].labelspath != undefined) {
labelspath = JSONout.slides[slidePointer].labelspath;
loadLabels();
} else {
labelspath = "";
}
updatePosition();
init();
}
function sliceNextDef(){
/*default function for moving to next slice
*used for click events as no argument required
*/
sliceNext(1,true);
}
function slicePrevDef(){
/*default function for moving to previous slice
* used for click events as no argument required
*/
slicePrev(1,true);
}
/*** END SLICES ***/
/*** OTHER UPDATES ***/
function updatePosition() {
/*updates position displayed
*/
var innerDiv = document.getElementById("innerDiv");
var clientX, clientY;
var event = window.event;
if (!event) {
clientX = lasteventX;
clientY = lasteventY;
}else{
clientX = event.clientX;
clientY = event.clientY;
}
if (coords) {
var errstr = "Unable to resolve position."
try {
document.getElementById('coords').innerHTML = " Position (in " + resunits + "):
" + Math.round((res * (-stripPx(innerDiv.style.left) + clientX - 0) / (1 / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom)) * 10)) / 10 + ", " + Math.round((res * (-stripPx(innerDiv.style.top) + clientY - 16) / (1 / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom)) * 10)) / 10 + ", " + Math.round(zres * slidePointer * 10) / 10;
removeError(errstr)
} catch (err) {
document.getElementById('coords').innerHTML = " Position (in " + resunits + "):
unknown";
logError(errstr+" "+err)
}
}
}
function updateInfo() {
/*updates all sliders
*/
updateZoom();
updateThreshold();
if (JSONnum) {
updateSlice();
}
showThumb(); //should be in if statement
updatePosition();
}
function centreView() {
/*center image with respect to the display
*/
var innerDiv = document.getElementById("innerDiv");
if ((getVar('vX').length) > 0) {
innerDiv.style.left = -getVar('vX') * width / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom) + viewportWidth / 2 + "px";
} else {
innerDiv.style.left = - (width / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom)) / 2 + viewportWidth / 2 + 'px';
}
if ((getVar('vY').length) > 0) {
innerDiv.style.top = -getVar('vY') * height / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom) + viewportHeight / 2 + "px";
} else {
innerDiv.style.top = - (height / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom)) / 2 + viewportHeight / 2 + 'px';
}
}
/*** THUMBNAIL ***
* showThumb()
* hideThumb()
* moveThumb2()
* clickThumb(event)
* clickThumbC(event)
************************************************************
* Note:
* there are two thumbnails:
* normal one and
* crossectional one (denoted with 'C')
* each thumbnail is a div (denoted by '0') consisting of
* an "image+label"-div (denoted by '') and
* a "position rectangle"-div (denoted by '2')
************************************************************/
function showThumb() {
/*display thumbnail
*/
//thumbnail of planar view
var Thumb = document.getElementById('Thumb');
var timg = document.getElementById('timg');
timg.src = imgpath + '0-0-0' + imgtype;
refreshUnderlay()
var Thumb0 = document.getElementById('Thumb0');
Thumb0.style.height = gImageHeight / (Math.pow(2, gTierCount - 1)) + 'px';
Thumb0.style.width = gImageWidth / (Math.pow(2, gTierCount - 1)) + 'px';
Thumb0.style.display = "block";
Thumb.style.display = "block";
var Thumb2 = document.getElementById('Thumb2');
Thumb2.style.display = "block";
Thumb2.onmouseup = clickThumb;
Thumb.onmouseup = clickThumb;
Thumb.ondragstart = function () {
return false;
}
//thumbnail of crossectional view
var ThumbC = document.getElementById('ThumbC');
ThumbC.innerHTML = ' cross-sectional view
';
ThumbC.innerHTML += '';
var Thumb0C = document.getElementById('Thumb0C');
Thumb0C.style.height = JSONnum / (Math.pow(2, gTierCount - 1)) + 1 + 'px';
Thumb0C.style.width = gImageWidth / (Math.pow(2, gTierCount - 1)) + 'px';
Thumb0C.style.display = "block";
ThumbC.style.display = "block";
var Thumb2C = document.getElementById('Thumb2C');
Thumb2C.style.display = "block";
Thumb2C.onmouseup = clickThumbC;
ThumbC.onmouseup = clickThumbC;
ThumbC.ondragstart = function () {
return false;
}
}
function hideThumb() {
/*hide thumbnail
*/
document.getElementById('Thumb0').style.display = "none";
document.getElementById('Thumb0C').style.display = "none";
}
function moveThumb2() {
/*adjust thumbnail position indicator
*/
//display rectangle of current view
var innerDiv = document.getElementById("innerDiv");
//rectangle for cross-sectional view
var Thumb2C = document.getElementById("Thumb2C");
topT = stripPx(innerDiv.style.top);
leftT = stripPx(innerDiv.style.left);
Thumb2C.style.width = viewportWidth / (Math.pow(2, zoom) * xtrazoom) + 'px';
Thumb2C.style.height = '0px';
Thumb2C.style.left = -leftT / (Math.pow(2, zoom) * xtrazoom) + 'px';
Thumb2C.style.top = slidePointer / (Math.pow(2, gTierCount - 1)) + 'px';
//rectangle for planar view
var Thumb2 = document.getElementById("Thumb2");
topT = stripPx(innerDiv.style.top);
leftT = stripPx(innerDiv.style.left);
Thumb2.style.width = viewportWidth / (Math.pow(2, zoom) * xtrazoom) + 'px';
Thumb2.style.height = viewportHeight / (Math.pow(2, zoom) * xtrazoom) + 'px';
Thumb2.style.left = -leftT / (Math.pow(2, zoom) * xtrazoom) + 'px';
Thumb2.style.top = -topT / (Math.pow(2, zoom) * xtrazoom) + 'px';
}
function clickThumb(event) {
/*move to position clicked in thumbnail
*/
if (event) {
xThumb = event.clientX; //X position of mouse on click with respect to viewport
yThumb = event.clientY;
var innerDiv = document.getElementById("innerDiv");
//calculate new left position
var ThumbWidth = stripPx(document.getElementById("Thumb0").style.width); //width of thumbnail element
var ThumbLeft = viewportWidth - stripPx(document.getElementById("Thumb0").style.right) - stripPx(document.getElementById("Thumb0").style.border) - ThumbWidth; //left position of thumbnail element
var xThumbRel = xThumb - ThumbLeft; //left position of click with respect to thumbnail
var ImageZoomedWidth = width / (Math.pow(2, gTierCount - zoom - 1) / xtrazoom);
var ImageZoomedLeft = ImageZoomedWidth * xThumbRel / ThumbWidth;
innerDiv.style.left = viewportWidth / 2 - ImageZoomedLeft + 'px';
//calculate new top position
var ThumbHeight = stripPx(document.getElementById("Thumb0").style.height); //height of thumbnail element
var ThumbTop = viewportHeight - stripPx(document.getElementById("Thumb0").style.bottom) - stripPx(document.getElementById("Thumb0").style.border) - ThumbHeight; //top position of thumbnail element
var yThumbRel = yThumb - ThumbTop; //top position of click with respect to thumbnail
var ImageZoomedHeight = height / (Math.pow(2, gTierCount - zoom - 1) / xtrazoom);
var ImageZoomedTop = ImageZoomedHeight * yThumbRel / ThumbHeight;
innerDiv.style.top = viewportHeight / 2 - ImageZoomedTop + 'px';
//apply changes
checkTiles(0);
moveThumb2();
}
}
function clickThumbC(event) {
/*move to position clicked in cross-section thumbnail
*/
if (event) {
xThumb = event.clientX;
yThumb = event.clientY;
var innerDiv = document.getElementById("innerDiv");
//calculate new left position
var ThumbWidth = stripPx(document.getElementById("Thumb0C").style.width); //width of thumbnail element
var ThumbLeft = viewportWidth - stripPx(document.getElementById("Thumb0C").style.right) - stripPx(document.getElementById("Thumb0C").style.border) - ThumbWidth; //left position of thumbnail element
var xThumbRel = xThumb - ThumbLeft; //left position of click with respect to thumbnail
var ImageZoomedWidth = width / (Math.pow(2, gTierCount - zoom - 1) / xtrazoom);
var ImageZoomedLeft = ImageZoomedWidth * xThumbRel / ThumbWidth;
innerDiv.style.left = viewportWidth / 2 - ImageZoomedLeft + 'px';
//calculate new slice number
slidePointer = Math.round(JSONnum * (Math.abs(yThumb - viewportHeight + stripPx(document.getElementById("Thumb0C").style.bottom) + stripPx(document.getElementById("Thumb0C").style.height)) + 2) / stripPx(document.getElementById("Thumb0C").style.height));
//apply changes
sliceNext(1);
moveThumb2();
}
}
/*** END THUMBNAIL ***/
/*** CONTROLS-INFO ***
* clickControls(event)
* showControlsBtn()
* hideControlsBtn()
* showControls()
* hideControls()
*/
function clickControls(event) {
/*handles event when "Controls" button is clicked
*/
if(isControl){
hideControls();
}else{
showControls();
}
}
function showControlsBtn() {
/*displays "Controls" button
*/
document.getElementById("cntrlButton").style.display = "block";
}
function hideControlsBtn() {
/*hides "Controls" button
*/
document.getElementById("cntrlButton").style.display = "none";
if(isControl){
hideControls();
}
}
function showControls() {
/*displays div with image of controls
*/
document.getElementById("controls").style.display = "block";
$("#controls").animate({
opacity : 0.75
}, 500);
isControl = true;
}
function hideControls() {
/*hides div with image of controls
*/
$("#controls").animate({
opacity : 0
}, 500);
setTimeout(function () {
document.getElementById("controls").style.display = "none";
isControl = false;
}, 500);
}
/*** END OF CONTROLS-INFO ***/
/*** AJAX/FILE LOADING ***
* getHTTPObject()
* getJSONAttribute(attribute, JSONdic, defval)
* labelsHandler()
* loadLabels()
* JSONread()
* loadJSON()
*/
function getHTTPObject() {
/*requests an object from the server and returns it
*/
var xhr;
try {
xhr = new ActiveXObject("Msxml2.XMLHTTP");
} catch (err) {
try {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
} catch (Err2) {
xhr = false;
}
}
if (!xhr && typeof XMLHttpRequest !== ' undefined') {
xhr = new XMLHttpRequest();
}
return xhr;
}
function getJSONAttribute(attribute, JSONdic, defval) {
/*attempts to get and return requested attribute from a JSON dictionary,
*returns defval if not found
*/
if (attribute in JSONdic) {
var ans = JSONdic[attribute];
return ans;
}
return defval;
}
labels = getHTTPObject();
function labelsHandler() {
/*places labels in the labels div
*/
if (labels.readyState == 4) {
var labels2 = eval('(' + labels.responseText + ')');
var lab = labels2.labels.length;
for (var $i = 0; $i < lab; $i++) {
var label = labels2.labels[$i].label;
var name = label;
var nX = labels2.labels[$i].x;
var nY = labels2.labels[$i].y;
if (labels2.labels[$i].name != undefined) {
name = labels2.labels[$i].name;
}
if (labels2.labels[$i].url != undefined) {
label = '' + label + '';
}
pinImage = document.createElement("div");
pinImage.style.position = "absolute";
pinImage.style.left = (nX * gImageWidth / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom)) + "px";
pinImage.style.top = (nY * gImageHeight / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom)) + "px";
pinImage.style.width = 8 * label.length + "px";
pinImage.style.height = "2px";
pinImage.style.zIndex = 1;
pinImage.setAttribute("id", "L" + $i);
pinImage.innerHTML = label;
document.getElementById("imageLabels").appendChild(pinImage);
}
}
}
function loadLabels() {
/*requests labels to be loaded
*/
var urlLabels = labelspath;
var pinImage = document.getElementById("L0");
if (pinImage) {
imageLabels = document.getElementById("imageLabels");
var divs = imageLabels.getElementsByTagName("div");
while (divs.length > 0)
imageLabels.removeChild(divs[0]);
} else {
labels.open("GET", urlLabels, true);
labels.onreadystatechange = labelsHandler;
labels.send(null);
}
}
function JSONread() {
/*reads in the information about the stack of images and
*sets global variables according to its content
*/
var isError = false;
if (JSONrequest.readyState == 4 && JSONrequest.status != 404) {
removeError("JSON not ready.");
removeError("Couldn't load JSON. Status:");
isError = false;
try {
JSONout = eval('(' + JSONrequest.responseText + ')');
removeError("Failed to read JSON: ");
} catch (err) {
isError = true;
document.getElementById('debug').innerHTML = "Failed to read JSON: " + err;
logError("Failed to read JSON: " + err);
}
if (!isError) {
JSONnum = JSONout.slides.length;
/***useful DICOM tags
* (0018,0050) Slice Thickness ------- slices can have overlap so don't use this!
* (0018,0088) Spacing Between Slices <------ This is the z-Resolution
* (0018,1050) Spatial Resolution
* (0018,1164) Imager Pixel Spacing
* (0018,6048) Pixel Component Range Start
* (0018,604A) Pixel Component Range Stop
* (0018,604C) Pixel Component Physical Units
* (0018,1240) Upper/Lower Pixel Values
* (0018,6024) Physical Units X Direction
* (0018,6026) Physical Units Y Direction
* (0018,9322) Reconstruction Pixel Spacing
* (0028,0030) Pixel Spacing <------ This is x and y resolution as a string seperated by a "\"
* (0028,0034) Pixel Aspect Ratio ------- for CT should always be = 1
* (0028,0108) Smallest Pixel Value in Series
* (0028,0109) Largest Pixel Value in Series
* (0054,1001) Units <------ Does not seem to be commonly defined, but standart is mm/voxel
*/
/*get extra image info from JSON file if available*/
height = getJSONAttribute("height", JSONout, height);
width = getJSONAttribute("width", JSONout, width);
resunits = getJSONAttribute("Units", JSONout, resunits);
resunits = getJSONAttribute("resunits", JSONout, resunits);
res = getJSONAttribute("PixelSpacing", JSONout, res);
res = getJSONAttribute("0028,0030", JSONout, res); //need to check format
res = getJSONAttribute("res", JSONout, res);
zres = getJSONAttribute("SpacingBetweenSlices", JSONout, zres);
zres = getJSONAttribute("0018,0088", JSONout, zres); //need to check format
zres = getJSONAttribute("zres", JSONout, zres);
/*try{ densM = JSONout.RescaleSlope; } catch(err) {} //need to convert
try{ densM = JSONout["0028,1052"]; } catch(err) {} //need to convert
try{ densB = JSONout.RescaleIntercept; } catch(err) {} //need to convert
try{ densB = JSONout["0028,1053"]; } catch(err) {} //need to convert
*/
densmin = getJSONAttribute("densmin", JSONout, densmin);
densmax = getJSONAttribute("densmax", JSONout, densmax);
thresLower = Math.max(densmin, thresLower);
thresUpper = Math.min(densmax, thresUpper);
imgtype = getJSONAttribute("filetype", JSONout, ".jpg");
start = getVar('start');
if (start.length > 0) {
slidePointer = parseInt(start, 10);
} else {
slidePointer = parseInt(JSONnum / 3, 10);
}
path = JSONout.slides[slidePointer].path; //path to specific slice images
height = getJSONAttribute("height", JSONout.slides[slidePointer], height); //height of specific slice
width = getJSONAttribute("width", JSONout.slides[slidePointer], width); //width of specific slice
if (JSONout.slides[slidePointer].labelspath != undefined) {
labelspath = JSONout.slides[slidePointer].labelspath;
loadLabels();
}
loadedJSON = true;
startup();
}
} else {
if (JSONrequest.readyState != 4) {
logError("JSON not ready.");
} else {
logError("Couldn't load JSON. Status:" + JSONrequest.status);
}
}
}
function loadJSON() {
/*requests content of file describing the image stack
*/
JSONrequest = getHTTPObject();
JSONrequest.onreadystatechange = JSONread;
JSONrequest.open("GET", JSON, true);
JSONrequest.send(null);
}
/*** END AJAX/FILE LOADING ***/
/*** MOUSE WHEEL HANDLES ***
*Mouse wheel settings (radio buttons):
* wheelMode1()
* wheelMode2()
*Generic handlers:
* handle(delta)
* wheel(event)
*/
function wheelMode1() {
/*Mouse wheel used for zoom
*/
document.getElementById('chkMW1').checked = true;
document.getElementById('chkMW1fa').className = "fa fa-square";
document.getElementById('chkMW2fa').className = "fa fa-square-o";
wheelmode = 0;
}
function wheelMode2() {
/*Mouse wheel used for slices
*/
document.getElementById('chkMW2').checked = true;
document.getElementById('chkMW1fa').className = "fa fa-square-o";
document.getElementById('chkMW2fa').className = "fa fa-square";
wheelmode = 1;
}
function handle(delta) {
/*handles mouse-wheel rotation
*/
//calls the right function with the rotation argument from the mouse-wheel.
try {
clearTimeout(ActiveTask);
} catch (err) {}
wheelobs += delta;
if (wheelobs <= -2) {
wheelobs = 0;
if (wheelmode == 0) {
zoomIn();
} else {
slicePrev(delta, true);
}
} else if (wheelobs >= 2) {
wheelobs = 0;
if (wheelmode == 0) {
zoomOut();
} else {
sliceNext(delta, true);
}
}
}
function wheel(event) {
/*called after mouse wheel is moved
*/
//called after mouse wheel is moved
var delta = 0;
if (!event) {
event = window.event;
}
if (event.wheelDelta) {
delta = event.wheelDelta / 120;
} else if (event.detail) {
delta = -event.detail / 3;
}
if (delta) {
//alert("wheel");
handle(delta);
}
if (event.preventDefault) {
event.preventDefault();
}
event.returnValue = false;
}
function addWheelEvent() {
/*adds event listener for mousewheel
*/
if (window.addEventListener) {
window.addEventListener('DOMMouseScroll', wheel, false); //FF
window.addEventListener('mousewheel', wheel, false); // Opera, Chrome,Safari
//window.addEventListener('wheel', wheel, false); //IE9+
}else{
try{
if(elem.attachEvent) {
elem.attachEvent ("onmousewheel", wheel); // IE8-? IE7 emulator doesn't know this
}
}catch(err){}
}
window.onmousewheel = document.onmousewheel = wheel;
}
/*** END MOUSE WHEEL HANDLES ***/
/*** KEYBOARD HANDLES ***
* capturekey(e)
*/
function capturekey(e) {
/*calls appropriate functions in case of key being pressed
*/
var k = (typeof event != 'undefined') ? window.event.keyCode : e.keyCode;
try {
clearTimeout(ActiveTask);
} catch (err) {}
if (k == 187 || k == 61 || k == 107) {
/* =/+ (in FF 61, else 187) or Numpad+ (107) -> zoom in */
zoomIn();
} else if (k == 189 || k == 173 || k == 109) {
/* -/_ (in FF 173, else 189) or Numpad- (109) -> zoom out */
zoomOut();
} else if (k == 39 || k == 40 || k == 34) {
/* RightArrow (39), DownArrow (40) or PageDown (34) -> next slice */
sliceNext(1);
} else if (k == 37 || k == 38 || k == 33) {
/* LeftArrow (37), UpArrow (38) or PageUp (33) -> previous slice */
slicePrev(-1);
} else if (k == 82) {
/* r (82) -> increase minimum threshold */
thresLower = Math.min(1 * thresLower + 1,thresUpper);
updateThreshold();
checkTiles(1);
} else if (k == 70) {
/* f (70) -> dencrease minimum threshold */
thresLower = Math.max(1 * thresLower - 1,densmin);
updateThreshold();
checkTiles(1);
} else if (k == 84) {
/* t (84) -> increase maximum threshold */
thresUpper = Math.min(1 * thresUpper + 1,densmax);
checkTiles(1);
} else if (k == 71) {
/* g (71) -> decrease maximum threshold */
thresUpper = Math.max(1 * thresUpper - 1,thresLower);
checkTiles(1);
} else if (k == 81) {
/* q (81) -> zoom in */
zoomIn();
} else if (k == 69) {
/* e (69) -> zoom out */
zoomOut();
// } else if (k == 87) {
// /* w (87) -> pan */
// pan(-1,0);
// } else if (k == 83) {
// /* s (83) -> pan */
// pan(1,0);
// } else if (k == 65) {
// /* a (65) -> pan */
// pan(0,-1);
// } else if (k == 68) {
// /* d (68) -> pan */
// pan(0,1);
} else if (k == 88) {
/* x (88) -> previous slice */
slicePrev(-1);
} else if (k == 67) {
/* c (67) -> next slice */
sliceNext(1);
}
}
if (navigator.appName != "Mozilla") {
document.onkeyup = capturekey;
} else {
document.addEventListener("keypress", capturekey, true);
}
/*** END KEYBOARD HANDLES ***/
/*** MOUSE HANDLES ***
*Generic:
* is_touch_device()
* adjustRuler()
*Mouse Click Settings (Radio Buttons):
* clickMode1()
* clickMode2()
*Mouse Drag Event:
* startMove(event)
* processMove(event)
* stopMove()
*Apple Specific Events:
* appleStartTouch(event)
* appleMoveEnd(event)
* appleMove(event)
* appleMoving(event)
*/
/**Generic**/
function is_touch_device() {
/*checks if client device supports touch commands
*/
try {
document.createEvent("TouchEvent");
return true;
} catch (err) {
return false;
}
}
function adjustRuler(){
/*calculates and sets style of the ruler div
*/
var innerDiv = document.getElementById("innerDiv");
var clientX, clientY;
var event = window.event;
if (!event) {
clientX = lasteventX;
clientY = lasteventY;
}else{
clientX = event.clientX;
clientY = event.clientY;
}
Mtop = res * (clientY - dragStartTop) / (1 / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom));
Mleft = res * (clientX - dragStartLeft) / (1 / (Math.pow(2, gTierCount - 1 - zoom) / xtrazoom));
Mdist = Math.round(Math.sqrt(Math.pow(Mtop, 2) + Math.pow(Mleft, 2)) * 10) / 10;
rulerdiv = document.getElementById('ruler');
rulerdiv.alt = Mdist + resunits;
rulerwidth = Math.sqrt(Math.pow((clientY - dragStartTop), 2) + Math.pow((clientX - dragStartLeft), 2));
rulerangle = Math.atan2(Mtop, Mleft) * 180 / Math.PI;
rulerdiv.style.top = ((dragStartTop + clientY) / 2) + 'px';
rulerdiv.style.left = ((dragStartLeft + clientX - rulerwidth) / 2) + 'px';
rulerdiv.style.height = '0px';
rulerdiv.style.width = Math.round(rulerwidth) + 'px';
if (rulerangle < -90 || rulerangle > 90) {
rulerangle = rulerangle + 180;
}
//rotation might be an issue on Safari and on IE9-
rulerdiv.style.transform = "rotate(" + rulerangle + "deg)"; //;-ms-transform: rotate(30deg);-webkit-transform: rotate(30deg)
rulerdiv.style.webkitTransform = "rotate(" + rulerangle + "deg)";
rulerdiv.innerHTML = engUnit(Mdist,resunits);
rulerdiv.style.zIndex = "2";
rulerdiv.style.display = "block";
}
/**Mouse Click Settings (Radio Buttons)**/
function clickMode1() {
/*on Mouse Click/Drag: pan/drag the image
*/
var ruler = document.getElementById('ruler');
ruler.style.display = "none";
if (is_touch_device()) {
document.getElementById("cmlbl").innerHTML = 'Touch Mode:';
}
document.getElementById('chkMC1').checked = true;
document.getElementById('chkMC1fa').className = "fa fa-square";
document.getElementById('chkMC2fa').className = "fa fa-square-o";
document.getElementById('sleepRuler').style.display = "block";
document.getElementById('outerDiv').style.cursor = "move";
clickmode = 0;
}
function clickMode2() {
/*on Mouse Click/Drag: measure distance
*/
if (is_touch_device()) {
document.getElementById("cmlbl").innerHTML = 'Touch Mode:';
}
document.getElementById('chkMC2').checked = true;
document.getElementById('chkMC1fa').className = "fa fa-square-o";
document.getElementById('chkMC2fa').className = "fa fa-square";
document.getElementById('sleepRuler').style.display = "none";
document.getElementById('outerDiv').style.cursor = "default";
clickmode = 1;
}
/**Mouse Drag Event**/
function startMove(event) {
/*called on mousedown -
*saves initial position and defines mousemove event
*/
innerDiv = document.getElementById("innerDiv");
if (!event) {
event = window.event;
}
dragStartLeft = event.clientX;
dragStartTop = event.clientY;
mTop = stripPx(innerDiv.style.top);
mLeft = stripPx(innerDiv.style.left);
if (clickmode == 0) {
dragging = true;
} else {
measuring = true;
}
return false;
}
function processMove(event) {
/*updates the coordinates displayed and
*executes the appropriate function in case of panning or measuring active
*/
var Mtop, Mleft, Mdist;
var ruler;
var rulerwidth, rulerangle;
innerDiv = document.getElementById("innerDiv");
if (!event) {
event = window.event;
}
if (event) {
lasteventX = event.clientX
lasteventY = event.clientY
}
updatePosition();
if (dragging) {
innerDiv.style.top = mTop + (lasteventY - dragStartTop) + 'px';
innerDiv.style.left = mLeft + (lasteventX- dragStartLeft) + 'px';
} else if (measuring) {
adjustRuler();
}
}
function stopMove() {
/*called on mouseup -
*resets the mousemove events and updates tiles in case image was panned
*/
if (dragging) {
dragging = false;
//only load new tiles once moving has stopped
checkTiles(0);
}
measuring = false;
}
/**Apple Device Event Handlers Block**/
function appleStartTouch(event) {
/*Touch event started
*/
innerDiv = document.getElementById("innerDiv");
if (event.touches.length == 1) {
touchIdentifier = event.touches[0].identifier;
dragStartLeft = event.touches[0].clientX;
dragStartTop = event.touches[0].clientY;
mTop = stripPx(innerDiv.style.top);
mLeft = stripPx(innerDiv.style.left);
if (clickmode == 0) {
dragging = true;
} else {
measuring = true;
}
return true;
}
}
function appleMoveEnd(event) {
/*Touch event ended
*/
dragging = false;
measuring = false;
appleMove(event);
}
function appleMove(event) {
/*Touch event ongoing
*/
var Mtop, Mleft, Mdist;
var ruler;
var rulerwidth, rulerangle;
innerDiv = document.getElementById("innerDiv");
if (event) {
lasteventX = event.changedTouches[0].clientX
lasteventY = event.changedTouches[0].clientY
}
updatePosition();
if ((event.changedTouches.length == 1) && (dragging == true) && (touchIdentifier == event.changedTouches[0].identifier)) {
innerDiv.style.top = mTop + (lasteventY - dragStartTop) + 'px';
innerDiv.style.left = mLeft + (lasteventX - dragStartLeft) + 'px';
} else if ((event.changedTouches.length == 1) && (measuring == true) && (touchIdentifier == event.changedTouches[0].identifier)) {
adjustRuler();
}
event.preventDefault();
checkTiles(0);
}
function appleMoving(event) {
/*handles apple touch moving event
*/
event.preventDefault();
appleMove(event);
}
/*** END MOUSE HANDLES ***/
/*** INITIATION ***
* startup()
* init()
* initOnclicks()
* winsize()
* $(document).ready
*/
function startup() {
/*initialises all the variables, loads JSON, etc.
*/
// console.timeStamp("startup")
//Threshold sliders
thresLower = Math.max(densmin, -1000);
thresUpper = Math.min(densmax, 1000);
if (typeof jQuery === 'undefined') {
// no jQuery
logError("Please make sure your browser supports jQuery.");
}
winsize();
document.getElementById('error').innerHTML = ""; //error div displays overlay at top of screen
//one can use logError and removeError to access it
var divs = document.getElementById("imageLabels").getElementsByTagName("div");
while (divs.length > 0){
imageLabels.removeChild(divs[0]);
}
var width2 = getVar('width');
var height2 = getVar('height');
var coords2 = getVar('coords');
if (width2.length > 0) width = width2;
if (height2.length > 0) height = height2;
if (coords2.length > 0) coords = coords2;
var start = getVar('start');
if (start.length > 0) {
slidePointer = parseInt(start, 10);
}
//try to load JSON. the JSON file can either be specified directly, or a root directory can be given.
var rootpath2 = getVar('root');
if (rootpath2.length > 1) rootpath = rootpath2;
var JSON2 = getVar('JSON');
if (JSON2.length > 1) { //if specified directly, then load JSON file
JSON = JSON2;
} else {
JSON = rootpath + "/infoJSON.txt"; //otherwise load the file with name "infoJSON.txt" from rootpath specified
}
if (JSON.length > 1 && !loadedJSON) { //for 3D images display navigation elements for moving through the slices
getJSON = true;
document.getElementById("slices").style.display = "block";
loadJSON();
document.getElementById('wheelMode').style.display = "block";
return;
}
/* resolution (in plane) in px/resunits */
var res2 = getVar('res');
if (res2.length > 1) {
try {
res = parseFloat(res2);
} catch (err) {}
}
if (isNumeric(res)) {
try {
res = parseFloat(res);
} catch (err) {
res = 1.0;
}
} else {
res = 1.0;
}
/* z-resolution (between slides) in px/resunits */
zres2 = getVar('zres');
if (zres2.length > 1) {
try {
zres = parseFloat(zres2);
} catch (err) {}
}
if (isNumeric(zres)) {
try {
zres = parseFloat(zres);
} catch (err) {
zres = 1.0;
}
} else {
zres = 1.0;
}
/* unit for the resolution */
resunits2 = getVar('resunits');
if (resunits2.length > 0)
resunits = resunits2;
if (resunits.length <= 0) {
resunits = "px";
}
imgpath = path;
//calculate number of zoom levels
gImageWidth = width;
gImageHeight = height;
tempWidth = gImageWidth;
tempHeight = gImageHeight;
divider = 2;
gTierCount = 1;
while (tempWidth > tileSize || tempHeight > tileSize) {
tempWidth = Math.floor(gImageWidth / divider)
tempHeight = Math.floor(gImageHeight / divider);
divider *= 2;
if (tempWidth % 2)
tempWidth++;
if (tempHeight % 2)
tempHeight++;
gTierCount++;
}
vTier2 = getVar('vT');
if (vTier2.length > 0) {
zoom = Math.max(0,vTier2);
if (zoom > gTierCount - 1){
xtrazoom = Math.min(zoom - gTierCount + 1,xtrazoomMax);
zoom = gTierCount - 1;
}
} else {
zoom = gTierCount - 1;
}
//position innerDiv containing the image relative to viewport
centreView();
/*attach functions to outerDiv which contains the innerDiv
* this is important as the user may click in an area outside the
* actual image and this way can still interact with the image,
* e.g. if the image is out of the viewing area
*/
var outerDiv = document.getElementById("outerDiv");
outerDiv.onmousedown = startMove;
outerDiv.onmousemove = processMove;
outerDiv.onmouseup = stopMove;
outerDiv.ondragstart = function () {
return false;
}
/*Capture Mobile Device Events*/
outerDiv.ontouchstart = appleStartTouch;
outerDiv.ontouchend = appleMoveEnd;
outerDiv.ontouchmove = appleMoving;
outerDiv.ongesturestart = function (event) {
event.preventDefault();
gestureScale = event.scale;
parent.document.ontouchmove = function (event) {
event.preventDefault();
};
}
outerDiv.ongestureend = function (event) {
event.preventDefault();
if (event.scale > gestureScale) {
zoomIn();
} else {
zoomOut();
}
parent.document.ontouchmove = null;
};
/*now call original initialisation function for the rest.*/
winsize();
init();
/*Capture touch device events*/
if (is_touch_device()) {
/*for touch devices no mouse wheel support, but touching = zooming*/
document.getElementById("wheelMode").style.display = 'none';
document.getElementById("cmlbl").innerHTML = 'Touch Mode:';
} else {
/*for normal devices mousewheel support includes different modes*/
document.getElementById("wheelMode").style.display = 'block';
}
/*entry animation
* moves in toolbox from the side, once ready
* this is not onlz a nice effect, but prevents
* users from trying to access functions before
* they have been loaded. Unfortunately on older
* browsers animations aren't supported so the
* panel will be off screen and never appear
*/
setTimeout(function () {
$("#overlay").animate({
left : "0px"
}, 1500, "swing");
}, 1500);
// console.timeStamp("startup end");
initOnclicks();
addWheelEvent();
}
function init() {
/*initiate, but also called for new slides...
*/
var imageLabels = document.getElementById("imageLabels");
var divs = imageLabels.getElementsByTagName("div");
// try {
// Tstart = performance.now();
// } catch (err) {
// Tstart = new Date().getTime();
// }
while (divs.length > 0) {
imageLabels.removeChild(divs[0]);
}
imgpath = path;
ActiveTask = setTimeout(function () {
checkTiles(1);
}, 0);
updateInfo(); //check if all necessary
} //*** End of Init()
function initOnclicks() {
/*initiates html elements
*/
/*hide elements*/
document.getElementById("controls").style.display = "none";
/*create onclick events*/
document.getElementById('chkMW1').onclick = wheelMode1;
document.getElementById('chkMW2').onclick = wheelMode2;
document.getElementById('chkMW1div').onmouseup = wheelMode1;
document.getElementById('chkMW2div').onmouseup = wheelMode2;
document.getElementById('chkMC2').onclick = clickMode2;
document.getElementById('chkMC1').onclick = clickMode1;
document.getElementById('chkMC2div').onmouseup = clickMode2;
document.getElementById('chkMC1div').onmouseup = clickMode1;
document.getElementById("cntrlButton").onmouseup = clickControls;
document.getElementById("closeControls").onmouseup = hideControls;
document.getElementById("zoomouticon").onmouseup = zoomOut;
document.getElementById("zoominicon").onmouseup = zoomIn;
document.getElementById("sliceprevicon").onmouseup = slicePrevDef;
document.getElementById("slicenexticon").onmouseup = sliceNextDef;
document.getElementById("zoomouticonfa").onmouseup = zoomOut;
document.getElementById("zoominiconfa").onmouseup = zoomIn;
document.getElementById("slicepreviconfa").onmouseup = slicePrevDef;
document.getElementById("slicenexticonfa").onmouseup = sliceNextDef;
}
function winsize() {
/*repositions the image and deals with variable changes in case of the window being rescaled
*this always happens on page load!
*/
var contentbox, overlayTop;
var errmsg = "Failed on resize: ";
tviewportWidth = viewportWidth;
tviewportHeight = viewportHeight;
if (typeof(window.innerWidth) == 'number') {
viewportWidth = window.innerWidth;
viewportHeight = window.innerHeight;
} else if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
viewportWidth = document.documentElement.clientWidth;
viewportHeight = document.documentElement.clientHeight;
} else if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
viewportWidth = document.body.clientWidth;
viewportHeight = document.body.clientHeight;
}
try {
contentbox = document.getElementById('contentbox');
viewportWidth = contentbox.clientWidth;
viewportHeight = contentbox.clientHeight;
} catch (err) {
//setTimeout("winsize();", 5000);
}
if (viewportHeight != tviewportHeight || viewportWidth != tviewportWidth) {
if (viewportHeight != tviewportHeight){
try {
overlayTop = Math.max(Math.min((viewportHeight - 500), 145), 0);
document.getElementById("overlay").style.top = overlayTop + 'px';
document.getElementById("overlay").style.height = (viewportHeight - overlayTop) + 'px';
} catch (err) {
logError(errmsg + err);
//Fails on load as element not yet created
}
}
try {
if (viewportWidth >= 700) {
if(loadedJSON){
showThumb();
}
showControlsBtn();
} else if (viewportWidth <= 600) {
hideThumb();
hideControlsBtn();
}
removeError(errmsg);
//updateInfo();
} catch (err) {
logError(errmsg + err);
//Fails on load as element not yet created
}
}
}
window.onresize = winsize;
/*execute once page is loaded*/
$(document).ready(
function(){
winsize();
startup();
}
);
/*** END INITIATION ***/