midasplatform/Midas

View on GitHub
modules/pvw/public/js/paraview/paraview.volume.js

Summary

Maintainability
F
2 wks
Test Coverage
// Midas Server. Copyright Kitware SAS. Licensed under the Apache License 2.0.

/* global json */

var pv;
var midas = midas || {};
midas.pvw = midas.pvw || {};

midas.pvw.PRESET_TRANSFER_RGBPOINTS = {
    'Grayscale': [0.0, 0, 0, 0,
        1.0, 1, 1, 1
    ],
    'X-Ray': [0.0, 1, 1, 1,
        1.0, 0, 0, 0
    ],
    'Rainbow': [0.0, 1.0, 0.0, 0.0,
        0.166667, 1.0, 0.0, 1.0,
        0.333333, 0.0, 0.0, 1.0,
        0.5, 0.0, 1.0, 1.0,
        0.666667, 0.0, 1.0, 0.0,
        0.833333, 1.0, 1.0, 0.0,
        1.0, 1.0, 0.0, 0.0
    ],
    'Rainbow (Desaturated)': [0.0, 0.2784313725490196, 0.2784313725490196, 0.8588235294117647,
        0.1428, 0.0, 0.0, 0.3607843137254902,
        0.2857, 0.0, 1.0, 1.0,
        0.4286, 0.0, 0.5019607843137255, 0.0,
        0.5714, 1.0, 1.0, 0.0,
        0.7143, 1.0, 0.3803921568627451, 0.0,
        0.8571, 0.4196078431372549, 0.0, 0.0,
        1.0, 0.8784313725490196, 0.30196078431372547, 0.30196078431372547
    ],
    'Yellow-Orange-Brown': [0, 1.0, 1.0, 0.8313725490196079,
        0.33333, 0.996078431372549, 0.8509803921568627, 0.5568627450980392,
        0.66667, 0.996078431372549, 0.6, 0.1607843137254902,
        1.0, 0.8, 0.2980392156862745, 0.00784313725490196
    ],
    'Qualitative Accent 1': [0, 0.4980392156862745, 0.788235294117647, 0.4980392156862745,
        0.1428, 0.7450980392156863, 0.6823529411764706, 0.8313725490196079,
        0.2857, 0.9921568627450981, 0.7529411764705882, 0.5254901960784314,
        0.4286, 1.0, 1.0, 0.6,
        0.5714, 0.2196078431372549, 0.4235294117647059, 0.6901960784313725,
        0.7143, 0.9411764705882353, 0.00784313725490196, 0.4980392156862745,
        0.8571, 0.7490196078431373, 0.3568627450980392, 0.09019607843137255,
        1.0, 0.4, 0.4, 0.4
    ],
    'Qualitative Accent 2': [0, 0.4, 0.7607843137254902, 0.6470588235294118,
        0.1428, 0.9882352941176471, 0.5529411764705883, 0.3843137254901961,
        0.2857, 0.5529411764705883, 0.6274509803921569, 0.796078431372549,
        0.4286, 0.9058823529411765, 0.5411764705882353, 0.7647058823529411,
        0.5714, 0.6509803921568628, 0.8470588235294118, 0.32941176470588235,
        0.7143, 1.0, 0.8509803921568627, 0.1843137254901961,
        0.8571, 0.8980392156862745, 0.7686274509803922, 0.5803921568627451,
        1.0, 0.7019607843137254, 0.7019607843137254, 0.7019607843137254
    ]
};

/**
 * Display the subset of the volume defined by the bounds list
 * of the form [xMin, xMax, yMin, yMax, zMin, zMax]
 */
midas.pvw.renderSubgrid = function (bounds) {
    'use strict';
    midas.pvw.subgridBounds = bounds;
    var container = $('div.MainDialog');
    container.find('img.extractInProgress').show();
    container.find('button.extractSubgridApply').attr('disabled', 'disabled');
    pv.connection.session.call('vtk:extractSubgrid', bounds)
        .then(function (resp) {
            pv.viewport.render();
            container.find('img.extractInProgress').hide();
            container.find('button.extractSubgridApply').removeAttr('disabled');
            $('div.MainDialog').dialog('close');
        })
        .otherwise(midas.pvw.rpcFailure);
};

/**
 * Display information about the volume
 */
midas.pvw.populateInfo = function () {
    'use strict';
    $('#boundsXInfo').html(midas.pvw.bounds[0] + ' .. ' + midas.pvw.bounds[1]);
    $('#boundsYInfo').html(midas.pvw.bounds[2] + ' .. ' + midas.pvw.bounds[3]);
    $('#boundsZInfo').html(midas.pvw.bounds[4] + ' .. ' + midas.pvw.bounds[5]);
    $('#scalarRangeInfo').html(midas.pvw.scalarRange[0] + ' .. ' + midas.pvw.scalarRange[1]);
};

/**
 * 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));
            });
        });
    });
};

midas.visualize.toggleObjectVisibility = function(checkbox) {
    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();
};*/

midas.pvw._setupColorPresets = function (container) {
    'use strict';
    var presetSelect = container.find('select.scmPresets');
    var html = '<option value="">Use preset...</option>';
    $.each(midas.pvw.PRESET_TRANSFER_RGBPOINTS, function (name, points) {
        html += '<option value="' + name + '">' + name + '</option>';
    });
    presetSelect.html(html);
    presetSelect.change(function () {
        midas.pvw.changeColorPreset(container, $(this));
    });
};

midas.pvw.changeColorPreset = function (container, select) {
    'use strict';
    var name = select.val();
    if (name == '') {
        return;
    }
    var colorList = midas.pvw.PRESET_TRANSFER_RGBPOINTS[name];
    if ((colorList.length % 4) !== 0) {
        alert('Invalid color list length: ' + name);
    }
    // Map points into the actual scalar range
    var modifiedColorList = [];
    var range = midas.pvw.scalarRange[1] - midas.pvw.scalarRange[0];
    for (var i = 0; i < colorList.length; i += 4) {
        var interpScalar = midas.pvw.scalarRange[0] + colorList[i] * range;
        modifiedColorList.push(interpScalar, colorList[i + 1], colorList[i + 2], colorList[i + 3]);
    }
    midas.pvw.renderPointList(modifiedColorList);
};

/**
 * Setup the color mapping controls
 */
midas.pvw.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');
        midas.pvw._setupColorPresets(container);

        midas.pvw.renderPointList = function (colorMap) {
            pointListDiv.html(''); // clear any existing points out
            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]);
                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 + ')');
                        $(el).ColorPickerHide();
                    }
                }).css('background-color', 'rgb(' + r + ',' + g + ',' + b + ')');
            }
        };
        midas.pvw.renderPointList(midas.pvw.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.pvw.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 + ')');
                    $(el).ColorPickerHide();
                }
            }).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('');
            midas.pvw.renderPointList(midas.pvw.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.pvw.colorMap = colorMap;
            pv.connection.session.call('vtk:updateColorMap', colorMap)
                .then(function () {
                    pv.viewport.render();
                })
                .otherwise(midas.pvw.rpcFailure);
        });
    });
};

/**
 * Setup the scalar opacity function controls
 */
midas.pvw.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.pvw.sofPlot = $.jqplot('sofChartDiv', [midas.pvw.getSofCurve(midas.pvw.sof)], {
            series: [{
                showMarker: true
            }],
            axes: {
                xaxis: {
                    min: midas.pvw.scalarRange[0],
                    max: midas.pvw.scalarRange[1],
                    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.pvw.applySofCurve();
        });
        container.find('button.sofReset').click(function () {
            midas.pvw.sofPlot.series[0].data = [
                [midas.pvw.scalarRange[0], 0],
                [midas.pvw.scalarRange[1], 1]
            ];
            midas.pvw.sofPlot.replot();
            midas.pvw.setupSofPlotBindings();
            container.find('div.sofPointEdit').hide();
        });
        midas.pvw.setupSofPlotBindings();
    });
};

/**
 * Get the plot data from the scalar opacity function
 */
midas.pvw.getSofCurve = function (points) {
    'use strict';
    var curve = [];
    for (var i = 0; i < points.length; i++) {
        curve[i] = [points[4 * i], points[4 * i + 1]];
    }
    return curve;
};

/**
 * Called when the "apply" button on the sof dialog is clicked;
 * updates the sof in paraview based on the jqplot curve
 */
midas.pvw.applySofCurve = function () {
    'use strict';
    // Create the scalar opacity transfer function
    var points = [];
    var curve = midas.pvw.sofPlot.series[0].data;
    for (var idx in curve) {
        points.push(curve[idx][0], curve[idx][1], 0.5, 0.0);
    }

    midas.pvw.sof = points;
    pv.connection.session.call('vtk:updateSof', points)
        .then(function () {
            pv.viewport.render();
        })
        .otherwise(midas.pvw.rpcFailure);
};

/**
 * Must call this anytime a redraw or replot is called on the sof plot
 */
midas.pvw.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.pvw.sofPlot.series[0].data[pointIndex] = [s, o];
            midas.pvw.sofPlot.replot();
            midas.pvw.setupSofPlotBindings();
        });
        container.find('button.pointDelete').unbind('click').click(function () {
            midas.pvw.sofPlot.series[0].data.splice(pointIndex, 1);
            midas.pvw.sofPlot.replot();
            midas.pvw.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.pvw.sofPlot.series[0].data[0]];

        for (var i = 1; i < midas.pvw.sofPlot.series[0].data.length; i++) {
            if (!inserted && pointIndex.xaxis < midas.pvw.sofPlot.series[0].data[i][0]) {
                inserted = true;
                newData.push([pointIndex.xaxis, pointIndex.yaxis]);
            }
            newData.push(midas.pvw.sofPlot.series[0].data[i]);
        }
        if (!inserted) {
            newData.push([pointIndex.xaxis, pointIndex.yaxis]);
        }
        midas.pvw.sofPlot.series[0].data = newData;
        midas.pvw.sofPlot.replot();
        midas.pvw.setupSofPlotBindings();
    });
};

/**
 * Setup the extract subgrid controls
 */
midas.pvw.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.pvw.extent[0],
            max: midas.pvw.extent[1],
            values: [midas.pvw.subgridBounds[0], midas.pvw.subgridBounds[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.pvw.extent[2],
            max: midas.pvw.extent[3],
            values: [midas.pvw.subgridBounds[2], midas.pvw.subgridBounds[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.pvw.extent[4],
            max: midas.pvw.extent[5],
            values: [midas.pvw.subgridBounds[4], midas.pvw.subgridBounds[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.pvw.extent[0],
            max: midas.pvw.extent[1]
        }).change(function () {
            container.find('.sliderX').slider('option', 'values', [$(this).val(), container.find('.extractSubgridMaxX').val()]);
        }).val(midas.pvw.subgridBounds[0]);
        container.find('.extractSubgridMaxX').spinbox({
            min: midas.pvw.extent[0],
            max: midas.pvw.extent[1]
        }).change(function () {
            container.find('.sliderX').slider('option', 'values', [container.find('.extractSubgridMinX').val(), $(this).val()]);
        }).val(midas.pvw.subgridBounds[1]);
        container.find('.extractSubgridMinY').spinbox({
            min: midas.pvw.extent[2],
            max: midas.pvw.extent[3]
        }).change(function () {
            container.find('.sliderY').slider('option', 'values', [$(this).val(), container.find('.extractSubgridMaxY').val()]);
        }).val(midas.pvw.subgridBounds[2]);
        container.find('.extractSubgridMaxY').spinbox({
            min: midas.pvw.extent[2],
            max: midas.pvw.extent[3]
        }).change(function () {
            container.find('.sliderY').slider('option', 'values', [container.find('.extractSubgridMinY').val(), $(this).val()]);
        }).val(midas.pvw.subgridBounds[3]);
        container.find('.extractSubgridMinZ').spinbox({
            min: midas.pvw.extent[4],
            max: midas.pvw.extent[5]
        }).change(function () {
            container.find('.sliderZ').slider('option', 'values', [$(this).val(), container.find('.extractSubgridMaxZ').val()]);
        }).val(midas.pvw.subgridBounds[4]);
        container.find('.extractSubgridMaxZ').spinbox({
            min: midas.pvw.extent[4],
            max: midas.pvw.extent[5]
        }).change(function () {
            container.find('.sliderZ').slider('option', 'values', [container.find('.extractSubgridMinZ').val(), $(this).val()]);
        }).val(midas.pvw.subgridBounds[5]);

        // setup button actions
        container.find('button.extractSubgridApply').click(function () {
            midas.pvw.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.pvw.start = function () {
    'use strict';
    if (typeof midas.pvw.preInitCallback == 'function') {
        midas.pvw.preInitCallback();
    }
    midas.pvw.loadData();
};

/** Callback for once the loadData RPC has returned */
midas.pvw.dataLoaded = function (resp) {
    'use strict';
    midas.pvw.mainProxy = resp;
    pv.viewport.render();
    midas.pvw.waitingDialog('Starting volume rendering...');
    pv.connection.session.call('vtk:volumeRender')
        .then(midas.pvw.vrStarted)
        .otherwise(midas.pvw.rpcFailure);
};

/** After volume rendering has started successfully, this gets called */
midas.pvw.vrStarted = function (resp) {
    'use strict';
    midas.pvw.bounds = resp.bounds;
    midas.pvw.extent = resp.extent;
    midas.pvw.subgridBounds = resp.extent;
    midas.pvw.scalarRange = resp.scalarRange;
    midas.pvw.sof = resp.sofPoints;
    midas.pvw.colorMap = resp.rgbPoints;
    midas.pvw.defaultColorMap = resp.rgbPoints;

    pv.viewport.render();
    $('div.MainDialog').dialog('close');
    midas.pvw.populateInfo();
    midas.pvw.setupOverlay();
    midas.pvw.setupExtractSubgrid();
    midas.pvw.setupScalarOpacity();
    midas.pvw.setupColorMapping();

    $('a.switchToSliceView').attr('href', json.global.webroot + '/pvw/paraview/slice' + encodeURIComponent(window.location.search));
};

/** Bind the renderer overlay buttons */
midas.pvw.setupOverlay = function () {
    'use strict';
    $('button.cameraPreset').click(function () {
        pv.connection.session.call('vtk:cameraPreset', $(this).attr('type'))
            .then(pv.viewport.render())
            .otherwise(midas.pvw.rpcFailure);
    });
};