UNPKG

@cornerstonejs/core

Version:
145 lines (144 loc) 5.39 kB
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, }); }