import contains from 'mout/src/array/contains'; import equals from 'mout/src/object/equals';
import AbstractImageBuilder from '../AbstractImageBuilder'; import CanvasOffscreenBuffer from '../../../Common/Misc/CanvasOffscreenBuffer';
import '../../../React/CollapsibleControls/CollapsibleControlFactory/LookupTableManagerWidget'; import '../../../React/CollapsibleControls/CollapsibleControlFactory/FloatImageControl'; import '../../../React/CollapsibleControls/CollapsibleControlFactory/QueryDataModelWidget';
const PROBE_CHANGE_TOPIC = 'probe-change'; const TIME_DATA_READY = 'time-data-ready';
export default class FloatDataImageBuilder extends AbstractImageBuilder {
constructor(queryDataModel, lookupTableManager) { super({ queryDataModel, lookupTableManager, handleRecord: true, dimensions: queryDataModel.originalData.FloatImage.dimensions, });
this.timeDataQueryDataModel = queryDataModel.clone(); this.registerObjectToFree(this.timeDataQueryDataModel);
this.light = 200; this.meshColor = [50, 50, 50]; this.timeData = { data: [], pending: false, };
this.metadata = queryDataModel.originalData.FloatImage; this.layers = this.metadata.layers; this.dimensions = this.metadata.dimensions; this.timeProbe = { x: this.dimensions[0] / 2, y: this.dimensions[1] / 2, query: this.timeDataQueryDataModel.getQuery(), enabled: false, draw: true, pending: false, forceUpdate: false, tIdx: this.queryDataModel.getIndex('time') || 0, updateValue: () => { this.timeProbe.value = this.timeProbe.dataValues ? this.timeProbe.dataValues[this.timeProbe.tIdx] : this.timeProbe.pending ? 'Fetching...' : ''; }, triggerChange: () => { this.timeProbe.forceUpdate = false; this.timeProbe.updateValue(); this.emit(PROBE_CHANGE_TOPIC, this.timeProbe); }, }; this.bgCanvas = new CanvasOffscreenBuffer( this.dimensions[0], this.dimensions[1] ); this.registerObjectToFree(this.bgCanvas);
this.lookupTableManager.addFields( this.metadata.ranges, this.queryDataModel.originalData.LookupTables );
this.registerSubscription( queryDataModel.onStateChange(() => { if (this.timeProbe.tIdx !== this.queryDataModel.getIndex('time')) { this.timeProbe.tIdx = this.queryDataModel.getIndex('time'); this.timeProbe.triggerChange(); } else { this.render(); } this.update(); }) );
this.registerSubscription( queryDataModel.on('pipeline_data', (data, envelope) => { this.layers.forEach((item) => { const dataId = `${item.name}_${item.array}`; const dataLight = `${item.name}__light`; const dataMesh = `${item.name}__mesh`; if (item.active && data[dataId]) { item.data = new window[item.type](data[dataId].data); item.light = new Uint8Array(data[dataLight].data); if (data[dataMesh]) { item.mesh = new Uint8Array(data[dataMesh].data); } } }); this.render(); }) );
this.registerSubscription( this.lookupTableManager.onChange((data, envelope) => { this.render(); }) );
this.registerSubscription( this.timeDataQueryDataModel.on('pipeline_data', (data, envelope) => { this.timeData.data.push(data); if ( this.timeData.data.length < this.timeDataQueryDataModel.getSize('time') ) { this.timeDataQueryDataModel.next('time'); this.timeData.pending = true; this.timeProbe.pending = true; const categories = this.getCategories(); this.timeDataQueryDataModel.fetchData({ name: 'pipeline_data', categories, }); } else { this.timeData.pending = false; this.timeProbe.pending = false; if (this.timeProbe.enabled) { this.getTimeChart(); } this.timeProbe.triggerChange(); this.emit(TIME_DATA_READY, { fields: [], xRange: [0, this.timeDataQueryDataModel.getSize('time')], fullData: this.timeData, }); } }) ); }
getCategories() { const categories = [];
this.layers.forEach((layer) => { if (layer.active) { categories.push([layer.name, layer.array].join('_')); categories.push(`${layer.name}__light`); if (layer.hasMesh && layer.meshActive) { categories.push(`${layer.name}__mesh`); } } });
return categories; }
update() { const categories = this.getCategories(); this.queryDataModel.fetchData({ name: 'pipeline_data', categories, }); }
fetchTimeData() { const categories = this.getCategories(); const query = this.queryDataModel.getQuery();
if ( this.timeData.pending || !this.timeDataQueryDataModel.getValues('time') ) { return; }
this.timeData.pending = true; this.timeProbe.pending = true; this.timeProbe.triggerChange();
this.timeData.data = []; this.timeProbe.query = query;
Object.keys(query).forEach((key) => { this.timeDataQueryDataModel.setValue(key, query[key]); });
this.timeDataQueryDataModel.first('time'); this.timeDataQueryDataModel.fetchData({ name: 'pipeline_data', categories, }); }
getTimeChart(xx, yy) { let x = xx; let y = yy; let probeHasChanged = !this.timeProbe.enabled || this.timeProbe.forceUpdate; this.timeProbe.enabled = true;
if (x === undefined && y === undefined) { x = this.timeProbe.x; y = this.timeProbe.y; } else { probeHasChanged = probeHasChanged || this.timeProbe.x !== x || this.timeProbe.y !== y; this.timeProbe.x = x; this.timeProbe.y = y; }
const qA = this.queryDataModel.getQuery(); const qB = this.timeProbe.query;
qB.time = qA.time; if (this.timeData.data.length === 0 || !equals(qA, qB)) { this.fetchTimeData(); return; }
const width = this.dimensions[0]; const height = this.dimensions[1]; const idx = (height - y - 1) * width + x;
let arrayType = ''; let field = null; let layerName = null;
this.layers.forEach((layer) => { if (layer.active && !Number.isNaN(layer.data[idx])) { arrayType = layer.type; field = layer.array; layerName = layer.name; } });
if ( layerName && this.timeProbe.layer !== layerName && field && this.timeProbe.field !== field ) { this.timeProbe.layer = layerName; this.timeProbe.field = field;
if (this.timeProbe.layer && this.timeProbe.field) { this.fetchTimeData(); } return; }
const timeValues = this.timeDataQueryDataModel.getValues('time'); const dataValues = []; const chartData = { xRange: [ Number(timeValues[0]), Number(timeValues[timeValues.length - 1]), ], fields: [ { name: field, data: dataValues, }, ], }; const timeSize = this.timeData.data.length;
if (field && this.lookupTableManager.getLookupTable(field)) { chartData.fields[0].range = this.lookupTableManager .getLookupTable(field) .getScalarRange(); }
this.timeProbe.dataValues = dataValues; this.timeProbe.tIdx = this.queryDataModel.getIndex('time');
const layerNameField = `${layerName}_${field}`; if (layerName && field && this.timeData.data[0][layerNameField]) { for (let i = 0; i < timeSize; i++) { const floatArray = new window[arrayType]( this.timeData.data[i][layerNameField].data ); dataValues.push(floatArray[idx]); } } else if (layerName && field && !this.timeData.data[0][layerNameField]) { this.fetchTimeData(); }
this.emit(TIME_DATA_READY, chartData); if (probeHasChanged) { this.timeProbe.triggerChange(); } this.render(); }
render() { const ctx = this.bgCanvas.get2DContext(); const width = this.dimensions[0]; const height = this.dimensions[1]; const size = width * height; const imageData = ctx.createImageData(width, height); const pixels = imageData.data;
function flipY(idx) { const x = idx % width; const y = Math.floor(idx / width);
return (height - y - 1) * width + x; }
ctx.clearRect(0, 0, width, height); this.layers.forEach((layer) => { if (layer.active) { const lut = this.lookupTableManager.getLookupTable(layer.array); for (let i = 0; i < size; i++) { const flipedY = flipY(i); const color = lut.getColor(layer.data[flipedY]); const light = layer.light ? layer.light[flipedY] ? layer.light[flipedY] - this.light : 0 : 0;
if (color[3]) { pixels[i * 4 + 0] = color[0] * 255 + light; pixels[i * 4 + 1] = color[1] * 255 + light; pixels[i * 4 + 2] = color[2] * 255 + light; pixels[i * 4 + 3] = color[3] * 255;
if ( layer.hasMesh && layer.meshActive && layer.mesh && layer.mesh[flipedY] ) { pixels[i * 4 + 0] = this.meshColor[0]; pixels[i * 4 + 1] = this.meshColor[1]; pixels[i * 4 + 2] = this.meshColor[2]; } } } } }); ctx.putImageData(imageData, 0, 0);
const currentQuery = this.queryDataModel.getQuery(); this.timeProbe.query.time = currentQuery.time; this.timeProbe.draw = equals(this.timeProbe.query, currentQuery);
if (this.timeProbe.enabled && this.timeProbe.draw) { const x = this.timeProbe.x; const y = this.timeProbe.y; const delta = 10;
ctx.beginPath(); ctx.moveTo(x - delta, y); ctx.lineTo(x + delta, y); ctx.moveTo(x, y - delta); ctx.lineTo(x, y + delta);
ctx.lineWidth = 4; ctx.strokeStyle = '#ffffff'; ctx.stroke(); ctx.lineWidth = 2; ctx.strokeStyle = '#000000'; ctx.stroke(); }
const readyImage = { canvas: this.bgCanvas.el, area: [0, 0, width, height], outputSize: [width, height], builder: this, arguments: this.queryDataModel.getQuery(), };
this.imageReady(readyImage); }
onTimeDataReady(callback) { return this.on(TIME_DATA_READY, callback); }
onProbeChange(callback) { return this.on(PROBE_CHANGE_TOPIC, callback); }
destroy() { super.destroy();
this.off();
this.bgCanvas = null; this.dimensions = null; this.layers = null; this.light = null; this.meshColor = null; this.metadata = null; this.timeData = null; this.timeDataQueryDataModel = null; this.timeProbe = null; }
getControlWidgets() { const model = this; const { lookupTableManager, queryDataModel } = this.getControlModels();
return [ { name: 'LookupTableManagerWidget', lookupTableManager, }, { name: 'FloatImageControl', model, }, { name: 'QueryDataModelWidget', queryDataModel, }, ]; }
getControlModels() { return { lookupTableManager: this.lookupTableManager, queryDataModel: this.queryDataModel, }; }
isMultiView() { return !contains(this.queryDataModel.originalData.type, 'single-view'); }
getLayers() { return this.layers; }
setLight(lightValue) { if (this.light !== lightValue) { this.light = lightValue; this.render(); } }
getLight() { return this.light; }
getTimeProbe() { return this.timeProbe; }
setMeshColor(r, g, b) { if ( this.meshColor[0] !== r && this.meshColor[1] !== g && this.meshColor[2] !== b ) { this.meshColor = [r, g, b]; this.update(); } }
getMeshColor() { return this.meshColor; }
updateLayerVisibility(name, visible) { const array = this.layers; let count = array.length;
while (count) { count -= 1; if (array[count].name === name) { array[count].active = visible; this.update(); if (this.timeProbe.enabled) { this.timeProbe.forceUpdate = true; this.getTimeChart(); } return; } } }
updateMaskLayerVisibility(name, visible) { const array = this.layers; let count = array.length;
while (count) { count -= 1; if (array[count].name === name) { array[count].meshActive = visible; this.update(); return; } } }
updateLayerColorBy(name, arrayName) { const array = this.layers; let count = array.length;
while (count) { count -= 1; if (array[count].name === name) { array[count].array = arrayName; this.update(); if (this.timeProbe.enabled) { this.getTimeChart(); } return; } } } }
|