@cornerstonejs/core
Version:
Cornerstone3D Core
145 lines (144 loc) • 5.39 kB
JavaScript
import { Events as EVENTS, ViewportType } from '../../../enums/index.js';
import triggerEvent from '../../../utilities/triggerEvent.js';
import { computeECGChannelLayouts, computeECGRenderMetrics, drawECGGrid, drawECGLabels, drawECGTraces, ensureECGCanvasSize, getVisibleECGChannels, } from '../../../utilities/ECGUtilities.js';
import { resolveECGCanvasMapping } from './ecgViewportCamera.js';
export class CanvasECGRenderPath {
async addData(ctx, data, options) {
const waveform = data;
const rendering = {
renderMode: 'signal2d',
canvas: ctx.canvas,
canvasContext: ctx.canvasContext,
metrics: {
ecgWidth: 1,
ecgHeight: 1,
channelScale: 1,
worldToCanvasRatio: 1,
xOffsetCanvas: 0,
yOffsetCanvas: 0,
},
};
return {
rendering,
updateDataPresentation: (props) => {
this.updateDataPresentation(rendering, props);
},
applyViewState: (camera) => {
this.applyViewState(rendering, camera);
},
getFrameOfReferenceUID: () => {
return this.getFrameOfReferenceUID(ctx);
},
render: () => {
this.render(ctx, rendering, waveform);
},
removeData: () => {
this.removeData();
},
};
}
updateDataPresentation(rendering, props) {
rendering.currentDataPresentation = props;
}
applyViewState(rendering, camera) {
rendering.currentCamera = camera;
}
getFrameOfReferenceUID(ctx) {
return `ecg-viewport-${ctx.viewportId}`;
}
render(ctx, rendering, waveform) {
drawFrame(ctx, rendering, waveform);
}
removeData() {
}
}
export class CanvasECGPath {
constructor() {
this.id = 'ecg:canvas-signal';
this.type = ViewportType.ECG_NEXT;
}
matches(data, options) {
return data.type === 'ecg' && options.renderMode === 'signal2d';
}
createRenderPath() {
return new CanvasECGRenderPath();
}
}
function getEffectiveTransform(metrics, camera, canvas) {
const mapping = resolveECGCanvasMapping({
metrics,
camera,
canvas,
});
return {
effectiveRatio: mapping.effectiveRatio,
xOffset: mapping.xOffset,
yOffset: mapping.yOffset,
};
}
function computeTimeWindow(waveform, camera) {
const durationMs = (waveform.numberOfSamples / waveform.samplingFrequency) * 1000;
const startMs = Math.max(0, Math.min(camera.timeRange[0], durationMs));
const requestedEnd = Math.max(startMs + 1, camera.timeRange[1]);
const endMs = Math.max(startMs + 1, Math.min(requestedEnd, durationMs));
const startIndex = Math.max(0, Math.min(waveform.numberOfSamples - 1, Math.floor((startMs / 1000) * waveform.samplingFrequency)));
const endIndex = Math.max(startIndex + 1, Math.min(waveform.numberOfSamples, Math.ceil((endMs / 1000) * waveform.samplingFrequency)));
return {
startMs,
endMs,
startIndex,
endIndex,
};
}
function drawFrame(ecgCtx, ecgRendering, waveform) {
const { canvas, canvasContext, currentCamera, currentDataPresentation } = ecgRendering;
if (!currentCamera) {
return;
}
const visibleChannels = getVisibleECGChannels(waveform.channels, currentDataPresentation?.visibleChannels);
ensureECGCanvasSize(canvas);
const metrics = computeECGRenderMetrics({
canvas,
visibleChannels,
windowMs: Math.max(1, currentCamera.timeRange[1] - currentCamera.timeRange[0]),
valueRange: currentCamera.valueRange,
});
const layouts = computeECGChannelLayouts({
visibleChannels,
channelScale: metrics.channelScale,
});
const timeWindow = computeTimeWindow(waveform, currentCamera);
const dpr = window.devicePixelRatio || 1;
ecgRendering.metrics = metrics;
const { effectiveRatio, xOffset, yOffset } = getEffectiveTransform(metrics, currentCamera, canvas);
canvasContext.resetTransform();
canvasContext.fillStyle = '#000000';
canvasContext.fillRect(0, 0, canvas.width, canvas.height);
if (currentDataPresentation?.visible === false) {
return;
}
canvasContext.globalAlpha = currentDataPresentation?.opacity ?? 1;
canvasContext.setTransform(effectiveRatio * dpr, 0, 0, effectiveRatio * dpr, xOffset * dpr, yOffset * dpr);
drawECGGrid(canvasContext, metrics, {
showGrid: currentDataPresentation?.showGrid,
});
drawECGTraces({
ctx: canvasContext,
layouts,
ecgWidth: metrics.ecgWidth,
channelScale: metrics.channelScale,
startIndex: timeWindow.startIndex,
endIndex: timeWindow.endIndex,
lineWidth: currentDataPresentation?.lineWidth,
amplitudeScale: currentDataPresentation?.amplitudeScale,
});
drawECGLabels(canvasContext, layouts, metrics.worldToCanvasRatio);
canvasContext.resetTransform();
canvasContext.globalAlpha = 1;
triggerEvent(ecgCtx.element, EVENTS.IMAGE_RENDERED, {
element: ecgCtx.element,
viewportId: ecgCtx.viewportId,
renderingEngineId: ecgCtx.renderingEngineId,
rendering: ecgRendering,
});
}