modules/visualize/public/js/paraview/paraview.volume.js
// Midas Server. Copyright Kitware SAS. Licensed under the Apache License 2.0.
/* global JavaScriptRenderer */
/* global json */
var paraview;
var midas = midas || {};
midas.visualize = midas.visualize || {};
midas.visualize.renderers = {};
midas.visualize.DISTANCE_FACTOR = 1.6; // factor to zoom the camera out by
midas.visualize.start = function () {
'use strict';
// Create a paraview proxy
if (typeof Paraview != 'function') {
alert('Paraview javascript was not fetched correctly from server.');
return;
}
$('#loadingStatus').html('Creating ParaView session on the server and loading plugins...');
paraview = new Paraview("/PWService");
paraview.errorListener = {
manageError: function (error) {
if (error) {
midas.createNotice('A ParaViewWeb error occurred.', 4000, 'error');
return false;
}
}
};
paraview.createSessionAsync('midas', 'volume render', 'default', function () {
$('#loadingStatus').html('Reading image data from files...');
paraview.callPluginMethod('midascommon', 'OpenData', {
filename: json.visualize.url,
otherMeshes: json.visualize.meshes
}, midas.visualize._dataOpened);
});
};
/**
* Helper callback for after the data file has been opened
*/
midas.visualize._dataOpened = function (view, retVal) {
'use strict';
midas.visualize.input = retVal.input;
midas.visualize.bounds = retVal.imageData.Bounds;
midas.visualize.meshes = retVal.meshes;
midas.visualize.maxDim = Math.max(midas.visualize.bounds[1] - midas.visualize.bounds[0],
midas.visualize.bounds[3] - midas.visualize.bounds[2],
midas.visualize.bounds[5] - midas.visualize.bounds[4]);
midas.visualize.minVal = retVal.imageData.PointData.Arrays[0].Ranges[0][0];
midas.visualize.maxVal = retVal.imageData.PointData.Arrays[0].Ranges[0][1];
midas.visualize.imageWindow = [midas.visualize.minVal, midas.visualize.maxVal];
midas.visualize.midI = (midas.visualize.bounds[0] + midas.visualize.bounds[1]) / 2.0;
midas.visualize.midJ = (midas.visualize.bounds[2] + midas.visualize.bounds[3]) / 2.0;
midas.visualize.midK = Math.floor((midas.visualize.bounds[4] + midas.visualize.bounds[5]) / 2.0);
if (midas.visualize.bounds.length != 6) {
return;
}
midas.visualize.defaultColorMap = [
midas.visualize.minVal, 0.0, 0.0, 0.0,
midas.visualize.maxVal, 1.0, 1.0, 1.0
];
midas.visualize.colorMap = midas.visualize.defaultColorMap;
var rw = $('#renderercontainer');
var params = {
viewSize: [rw.width(), rw.height()],
cameraFocalPoint: [midas.visualize.midI, midas.visualize.midJ, midas.visualize.midK],
cameraPosition: [midas.visualize.midI - midas.visualize.DISTANCE_FACTOR * midas.visualize.maxDim,
midas.visualize.midJ,
midas.visualize.midK
],
colorMap: midas.visualize.defaultColorMap,
sofPoints: [midas.visualize.minVal, 0.0, 0.5, 0.0,
midas.visualize.maxVal, 1.0, 0.5, 0.0
],
colorArrayName: json.visualize.colorArrayName
};
$('#loadingStatus').html('Initializing view state and renderer...');
paraview.callPluginMethod('midasvr', 'InitViewState', params, midas.visualize.initCallback);
};
/**
* Async callback from the plugin's Initialize function.
* Sets the return variables in the javascript global scope
* for use in other functions, and starts the render window
*/
midas.visualize.initCallback = function (view, retVal) {
'use strict';
midas.visualize.sof = retVal.sof;
midas.visualize.lookupTable = retVal.lookupTable;
midas.visualize.activeView = retVal.activeView;
midas.visualize.switchRenderer(true); // render in the div
$('img.visuLoading').hide();
$('#loadingStatus').html('').hide();
$('#renderercontainer').show();
midas.visualize.populateInfo();
midas.visualize.setupExtractSubgrid();
midas.visualize.setupScalarOpacity();
midas.visualize.setupColorMapping();
midas.visualize.setupOverlay();
midas.visualize.setupObjectList();
if (typeof midas.visualize.postInitCallback == 'function') {
midas.visualize.postInitCallback();
}
midas.visualize.renderers.current.updateServerSizeIfNeeded(); // force a view refresh
midas.visualize.forceRefreshView();
};
/**
* Initialize or re-initialize the renderer within the DOM
*/
midas.visualize.switchRenderer = function (first) {
'use strict';
if (midas.visualize.renderers.js === undefined) {
midas.visualize.renderers.js = new JavaScriptRenderer("jsRenderer", "/PWService");
midas.visualize.renderers.js.enableWebSocket(paraview, 'ws://' + json.visualize.hostname + ':' + json.visualize.wsport + '/PWService/Websocket');
midas.visualize.renderers.js.init(paraview.sessionId, midas.visualize.activeView.__selfid__);
$('img.toolButton').show();
}
if (!first) {
midas.visualize.renderers.current.unbindToElementId('renderercontainer');
}
midas.visualize.renderers.current = midas.visualize.renderers.js;
midas.visualize.renderers.current.bindToElementId('renderercontainer');
var el = $('#renderercontainer');
midas.visualize.renderers.current.setSize(el.width(), el.height());
midas.visualize.renderers.current.start();
};
/**
* Display the subset of the volume defined by the bounds list
* of the form [xMin, xMax, yMin, yMax, zMin, zMax]
*/
midas.visualize.renderSubgrid = function (bounds) {
'use strict';
var toHide = midas.visualize.subgrid ? midas.visualize.subgrid : null;
var container = $('div.MainDialog');
container.find('img.extractInProgress').show();
container.find('button.extractSubgridApply').attr('disabled', 'disabled');
paraview.callPluginMethod('midasvr', 'ExtractSubgrid', [
midas.visualize.input, bounds, midas.visualize.lookupTable,
midas.visualize.sof, json.visualize.colorArrayName, toHide
], function (view, subgrid) {
midas.visualize.subgrid = subgrid;
container.find('img.extractInProgress').hide();
container.find('button.extractSubgridApply').removeAttr('disabled');
midas.visualize.forceRefreshView();
});
};
/**
* Display information about the volume
*/
midas.visualize.populateInfo = function () {
'use strict';
$('#boundsXInfo').html(midas.visualize.bounds[0] + ' .. ' + midas.visualize.bounds[1]);
$('#boundsYInfo').html(midas.visualize.bounds[2] + ' .. ' + midas.visualize.bounds[3]);
$('#boundsZInfo').html(midas.visualize.bounds[4] + ' .. ' + midas.visualize.bounds[5]);
$('#scalarRangeInfo').html(midas.visualize.minVal + ' .. ' + midas.visualize.maxVal);
};
/**
* Get the plot data from the scalar opacity function
*/
midas.visualize.getSofCurve = function () {
'use strict';
var points = paraview.GetProperty(midas.visualize.sof, 'Points');
var curve = [];
for (var i = 0; i < points.length; i++) {
curve[i] = [points[4 * i], points[4 * i + 1]];
}
return curve;
};
/**
* Setup the object list widget
*/
midas.visualize.setupObjectList = function () {
'use strict';
var dialog = $('#objectListTemplate').clone();
dialog.removeAttr('id');
$('#objectListAction').click(function () {
midas.showDialogWithContent('Objects in the scene',
dialog.html(), false, {
modal: false,
width: 430
});
var container = $('div.MainDialog');
var objectList = container.find('table.objectList tbody');
var html = '<tr><td><input type="checkbox" vis="volume" element="' + json.visualize.item.item_id + '" ';
if (json.visualize.visible) {
html += 'checked="checked" ';
}
html += ' /></td><td>Volume</td><td>' + json.visualize.item.name + '</td></tr>';
objectList.append(html);
$.each(json.visualize.meshes, function (idx, mesh) {
var html = '<tr><td><input type="checkbox" vis="surface" element="' + mesh.item.item_id + '" ';
if (mesh.visible) {
html += 'checked="checked" ';
}
html += ' /></td><td>Surface</td><td>' + mesh.item.name + '</td></tr>';
objectList.append(html);
});
container.find('button.objectListClose').click(function () {
$('div.MainDialog').dialog('close');
});
container.find('button.objectListApply').click(function () {
$.each(objectList.find('input[type=checkbox]'), function (idx, checkbox) {
midas.visualize.toggleObjectVisibility($(checkbox));
});
});
});
};
/**
* Force the renderer image to refresh from the server
*/
midas.visualize.forceRefreshView = function () {
'use strict';
midas.visualize.renderers.js.forceRefresh();
};
midas.visualize.toggleObjectVisibility = function (checkbox) {
'use strict';
var type = checkbox.attr('vis');
var itemId = checkbox.attr('element');
var proxy;
if (type == 'volume') {
if (midas.visualize.subgrid) {
proxy = midas.visualize.subgrid;
}
else {
proxy = midas.visualize.input;
}
}
else if (type == 'surface') {
$.each(midas.visualize.meshes, function (k, mesh) {
if (mesh.item.item_id == itemId) {
proxy = mesh.source;
mesh.visible = checkbox.is(':checked');
}
});
}
if (checkbox.is(':checked')) {
paraview.Show({
proxy: proxy
});
}
else {
paraview.Hide({
proxy: proxy
});
}
midas.visualize.forceRefreshView();
};
/**
* Setup the color mapping controls
*/
midas.visualize.setupColorMapping = function () {
'use strict';
var dialog = $('#scmDialogTemplate').clone();
dialog.removeAttr('id');
$('#scmEditAction').click(function () {
midas.showDialogWithContent('Scalar color mapping',
dialog.html(), false, {
modal: false,
width: 360
});
var container = $('div.MainDialog');
var pointListDiv = container.find('div.rgbPointList');
function renderPointList(colorMap) {
for (var i = 0; i < colorMap.length; i += 4) {
var rgbPoint = $('#scmPointMapTemplate').clone();
var r = Math.round(255 * colorMap[i + 1]);
var g = Math.round(255 * colorMap[i + 2]);
var b = Math.round(255 * colorMap[i + 3]);
rgbPoint.removeAttr('id').appendTo(pointListDiv).show();
rgbPoint.find('input.scmScalarValue').val(colorMap[i]);
if (i < 8) { // first two values must be present (min and max)
rgbPoint.find('input.scmScalarValue').attr('disabled', 'disabled');
}
else {
rgbPoint.find('button.scmDeletePoint').show().click(function () {
$(this).parents('div.rgbPointContainer').remove();
});
}
rgbPoint.find('.scmColorPicker').ColorPicker({
color: {
r: r,
g: g,
b: b
},
onSubmit: function (hsb, hex, rgb, el) {
$(el).css('background-color', 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')');
}
}).css('background-color', 'rgb(' + r + ',' + g + ',' + b + ')');
}
}
renderPointList(midas.visualize.colorMap);
container.find('button.scmAddPoint').unbind('click').click(function () {
var rgbPoint = $('#scmPointMapTemplate').clone();
rgbPoint.removeAttr('id').appendTo(pointListDiv).show();
rgbPoint.find('input.scmScalarValue').val(midas.visualize.defaultColorMap[0]);
rgbPoint.removeAttr('id').appendTo(pointListDiv).show();
rgbPoint.find('button.scmDeletePoint').show().click(function () {
rgbPoint.remove();
});
rgbPoint.find('.scmColorPicker').ColorPicker({
color: {
r: 0,
g: 0,
b: 0
},
onSubmit: function (hsb, hex, rgb, el) {
$(el).css('background-color', 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')');
}
}).css('background-color', 'rgb(0, 0, 0)');
});
container.find('button.scmClose').unbind('click').click(function () {
container.dialog('close');
});
container.find('button.scmReset').unbind('click').click(function () {
pointListDiv.html('');
renderPointList(midas.visualize.defaultColorMap);
});
container.find('button.scmApply').unbind('click').click(function () {
var colorMap = [];
$.each(pointListDiv.find('div.rgbPointContainer'), function (idx, template) {
var scalar = parseFloat($(template).find('input.scmScalarValue').val());
var tokens = $(template).find('div.scmColorPicker')
.css('background-color').match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
colorMap.push(scalar, parseFloat(tokens[1]) / 255.0, parseFloat(tokens[2]) / 255.0,
parseFloat(tokens[3]) / 255.0);
});
midas.visualize.colorMap = colorMap;
paraview.callPluginMethod('midascommon', 'UpdateColorMap', {
colorArrayName: json.visualize.colorArrayName,
colorMap: colorMap
}, function () {
midas.visualize.forceRefreshView();
});
});
});
};
/**
* Setup the scalar opacity function controls
*/
midas.visualize.setupScalarOpacity = function () {
'use strict';
var dialog = $('#sofDialogTemplate').clone();
dialog.removeAttr('id');
$('#sofEditAction').click(function () {
midas.showDialogWithContent('Scalar opacity function',
dialog.html(), false, {
modal: false,
width: 500
});
var container = $('div.MainDialog');
container.find('div.sofPlot').attr('id', 'sofChartDiv');
midas.visualize.sofPlot = $.jqplot('sofChartDiv', [midas.visualize.getSofCurve()], {
series: [{
showMarker: true
}],
axes: {
xaxis: {
min: midas.visualize.minVal,
max: midas.visualize.maxVal,
label: 'Scalar value',
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
labelOptions: {
fontSize: '8pt'
}
},
yaxis: {
min: 0,
max: 1,
label: 'Opacity',
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
labelOptions: {
angle: 270,
fontSize: '8pt'
},
tickInterval: 1.0
}
},
grid: {
drawGridlines: false
},
cursor: {
show: true,
style: 'pointer',
tooltipLocation: 'se'
},
highlighter: {
show: true,
sizeAdjust: 7.5
}
});
container.find('button.sofClose').click(function () {
$('div.MainDialog').dialog('close');
});
container.find('button.sofApply').click(function () {
midas.visualize.applySofCurve();
});
container.find('button.sofReset').click(function () {
midas.visualize.sofPlot.series[0].data = [
[midas.visualize.minVal, 0],
[midas.visualize.maxVal, 1]
];
midas.visualize.sofPlot.replot();
midas.visualize.setupSofPlotBindings();
container.find('div.sofPointEdit').hide();
});
midas.visualize.setupSofPlotBindings();
});
};
/**
* Called when the "apply" button on the sof dialog is clicked;
* updates the sof in paraview based on the jqplot curve
*/
midas.visualize.applySofCurve = function () {
'use strict';
// Create the scalar opacity transfer function
var points = [];
var curve = midas.visualize.sofPlot.series[0].data;
for (var idx in curve) {
points.push(curve[idx][0], curve[idx][1], 0.5, 0.0);
}
midas.visualize.sof = paraview.CreatePiecewiseFunction({
Points: points
});
paraview.SetDisplayProperties({
ScalarOpacityFunction: midas.visualize.sof
});
midas.visualize.forceRefreshView();
};
/**
* Must call this anytime a redraw or replot is called on the sof plot
*/
midas.visualize.setupSofPlotBindings = function () {
'use strict';
// Clicking an existing point should let you change its values
$('#sofChartDiv').bind('jqplotDataClick', function (ev, seriesIndex, pointIndex, data) {
var container = $('div.MainDialog').find('div.sofPointEdit');
container.find('input.scalarValueEdit').val(data[0]);
container.find('input.opacityValueEdit').val(data[1]);
container.show();
container.find('button.pointUpdate').unbind('click').click(function () {
var s = parseFloat(container.find('input.scalarValueEdit').val());
var o = parseFloat(container.find('input.opacityValueEdit').val());
midas.visualize.sofPlot.series[0].data[pointIndex] = [s, o];
midas.visualize.sofPlot.replot();
midas.visualize.setupSofPlotBindings();
});
container.find('button.pointDelete').unbind('click').click(function () {
midas.visualize.sofPlot.series[0].data.splice(pointIndex, 1);
midas.visualize.sofPlot.replot();
midas.visualize.setupSofPlotBindings();
});
});
// Clicking on the plot (except on an existing point) should add a new one
$('#sofChartDiv').bind('jqplotClick', function (ev, seriesIndex, pointIndex, data) {
if (data) {
return; // we use the data click handler for this
}
$('div.MainDialog').find('div.sofPointEdit').hide();
// insert new data point in between closest x-axis values
var inserted = false;
var newData = [midas.visualize.sofPlot.series[0].data[0]];
for (var i = 1; i < midas.visualize.sofPlot.series[0].data.length; i++) {
if (!inserted && pointIndex.xaxis < midas.visualize.sofPlot.series[0].data[i][0]) {
inserted = true;
newData.push([pointIndex.xaxis, pointIndex.yaxis]);
}
newData.push(midas.visualize.sofPlot.series[0].data[i]);
}
if (!inserted) {
newData.push([pointIndex.xaxis, pointIndex.yaxis]);
}
midas.visualize.sofPlot.series[0].data = newData;
midas.visualize.sofPlot.replot();
midas.visualize.setupSofPlotBindings();
});
};
/**
* Setup the extract subgrid controls
*/
midas.visualize.setupExtractSubgrid = function () {
'use strict';
var dialog = $('#extractSubgridDialogTemplate').clone();
dialog.removeAttr('id');
$('#extractSubgridAction').click(function () {
midas.showDialogWithContent('Select subgrid bounds',
dialog.html(), false, {
modal: false,
width: 560
});
var container = $('div.MainDialog');
container.find('.sliderX').slider({
range: true,
min: midas.visualize.bounds[0],
max: midas.visualize.bounds[1],
values: [midas.visualize.bounds[0], midas.visualize.bounds[1]],
slide: function (event, ui) {
container.find('.extractSubgridMinX').val(ui.values[0]);
container.find('.extractSubgridMaxX').val(ui.values[1]);
}
});
container.find('.sliderY').slider({
range: true,
min: midas.visualize.bounds[2],
max: midas.visualize.bounds[3],
values: [midas.visualize.bounds[2], midas.visualize.bounds[3]],
slide: function (event, ui) {
container.find('.extractSubgridMinY').val(ui.values[0]);
container.find('.extractSubgridMaxY').val(ui.values[1]);
}
});
container.find('.sliderZ').slider({
range: true,
min: midas.visualize.bounds[4],
max: midas.visualize.bounds[5],
values: [midas.visualize.bounds[4], midas.visualize.bounds[5]],
slide: function (event, ui) {
container.find('.extractSubgridMinZ').val(ui.values[0]);
container.find('.extractSubgridMaxZ').val(ui.values[1]);
}
});
// setup spinboxes with feedback into the range sliders
container.find('.extractSubgridMinX').spinbox({
min: midas.visualize.bounds[0],
max: midas.visualize.bounds[1]
}).change(function () {
container.find('.sliderX').slider('option', 'values', [$(this).val(), container.find('.extractSubgridMaxX').val()]);
}).val(midas.visualize.bounds[0]);
container.find('.extractSubgridMaxX').spinbox({
min: midas.visualize.bounds[0],
max: midas.visualize.bounds[1]
}).change(function () {
container.find('.sliderX').slider('option', 'values', [container.find('.extractSubgridMinX').val(), $(this).val()]);
}).val(midas.visualize.bounds[1]);
container.find('.extractSubgridMinY').spinbox({
min: midas.visualize.bounds[2],
max: midas.visualize.bounds[3]
}).change(function () {
container.find('.sliderY').slider('option', 'values', [$(this).val(), container.find('.extractSubgridMaxY').val()]);
}).val(midas.visualize.bounds[2]);
container.find('.extractSubgridMaxY').spinbox({
min: midas.visualize.bounds[2],
max: midas.visualize.bounds[3]
}).change(function () {
container.find('.sliderY').slider('option', 'values', [container.find('.extractSubgridMinY').val(), $(this).val()]);
}).val(midas.visualize.bounds[3]);
container.find('.extractSubgridMinZ').spinbox({
min: midas.visualize.bounds[4],
max: midas.visualize.bounds[5]
}).change(function () {
container.find('.sliderZ').slider('option', 'values', [$(this).val(), container.find('.extractSubgridMaxZ').val()]);
}).val(midas.visualize.bounds[4]);
container.find('.extractSubgridMaxZ').spinbox({
min: midas.visualize.bounds[4],
max: midas.visualize.bounds[5]
}).change(function () {
container.find('.sliderZ').slider('option', 'values', [container.find('.extractSubgridMinZ').val(), $(this).val()]);
}).val(midas.visualize.bounds[5]);
// setup button actions
container.find('button.extractSubgridApply').click(function () {
midas.visualize.renderSubgrid([
parseInt(container.find('.extractSubgridMinX').val()),
parseInt(container.find('.extractSubgridMaxX').val()),
parseInt(container.find('.extractSubgridMinY').val()),
parseInt(container.find('.extractSubgridMaxY').val()),
parseInt(container.find('.extractSubgridMinZ').val()),
parseInt(container.find('.extractSubgridMaxZ').val())
]);
});
container.find('button.extractSubgridClose').click(function () {
$('div.MainDialog').dialog('close');
});
});
};
midas.visualize.setupOverlay = function () {
'use strict';
$('button.plusX').click(function () {
paraview.callPluginMethod('midascommon', 'SetCamera', {
cameraPosition: [
midas.visualize.midI - midas.visualize.DISTANCE_FACTOR * midas.visualize.maxDim,
midas.visualize.midJ,
midas.visualize.midK
],
cameraViewUp: [0.0, 0.0, 1.0]
}, function () {
midas.visualize.forceRefreshView();
});
});
$('button.minusX').click(function () {
paraview.callPluginMethod('midascommon', 'SetCamera', {
cameraPosition: [
midas.visualize.midI + midas.visualize.DISTANCE_FACTOR * midas.visualize.maxDim,
midas.visualize.midJ,
midas.visualize.midK
],
cameraViewUp: [0.0, 0.0, 1.0]
}, function () {
midas.visualize.forceRefreshView();
});
});
$('button.plusY').click(function () {
paraview.callPluginMethod('midascommon', 'SetCamera', {
cameraPosition: [
midas.visualize.midI,
midas.visualize.midJ - midas.visualize.DISTANCE_FACTOR * midas.visualize.maxDim,
midas.visualize.midK
],
cameraViewUp: [0.0, 0.0, 1.0]
}, function () {
midas.visualize.forceRefreshView();
});
});
$('button.minusY').click(function () {
paraview.callPluginMethod('midascommon', 'SetCamera', {
cameraPosition: [
midas.visualize.midI,
midas.visualize.midJ + midas.visualize.DISTANCE_FACTOR * midas.visualize.maxDim,
midas.visualize.midK
],
cameraViewUp: [0.0, 0.0, 1.0]
}, function () {
midas.visualize.forceRefreshView();
});
});
$('button.plusZ').click(function () {
paraview.callPluginMethod('midascommon', 'SetCamera', {
cameraPosition: [
midas.visualize.midI,
midas.visualize.midJ,
midas.visualize.midK - midas.visualize.DISTANCE_FACTOR * midas.visualize.maxDim
],
cameraViewUp: [0.0, 1.0, 0.0]
}, function () {
midas.visualize.forceRefreshView();
});
});
$('button.minusZ').click(function () {
paraview.callPluginMethod('midascommon', 'SetCamera', {
cameraPosition: [
midas.visualize.midI,
midas.visualize.midJ,
midas.visualize.midK + midas.visualize.DISTANCE_FACTOR * midas.visualize.maxDim
],
cameraViewUp: [0.0, 1.0, 0.0]
}, function () {
midas.visualize.forceRefreshView();
});
});
};
$(window).load(function () {
'use strict';
if (typeof midas.visualize.preInitCallback == 'function') {
midas.visualize.preInitCallback();
}
json = $.parseJSON($('div.jsonContent').html());
midas.visualize.start(); // warning: asynchronous. To add post logic, see initCallback
});
$(window).unload(function () {
'use strict';
paraview.disconnect();
});