diff --git a/app/packages/looker/src/lookers/abstract.ts b/app/packages/looker/src/lookers/abstract.ts index 08991adba9..602e4bc7f0 100644 --- a/app/packages/looker/src/lookers/abstract.ts +++ b/app/packages/looker/src/lookers/abstract.ts @@ -293,11 +293,6 @@ export abstract class AbstractLooker< return; } - if (this.state.destroyed && this.sampleOverlays) { - // close all current overlays - this.pluckedOverlays.forEach((overlay) => overlay.cleanup?.()); - } - if ( !this.state.windowBBox || this.state.destroyed || @@ -609,6 +604,7 @@ export abstract class AbstractLooker< this.detach(); this.abortController.abort(); this.updater({ destroyed: true }); + this.sampleOverlays?.forEach((overlay) => overlay.cleanup?.()); } disable() { diff --git a/app/packages/looker/src/overlays/heatmap.ts b/app/packages/looker/src/overlays/heatmap.ts index d8fb8909d5..720aa8af36 100644 --- a/app/packages/looker/src/overlays/heatmap.ts +++ b/app/packages/looker/src/overlays/heatmap.ts @@ -208,6 +208,7 @@ export default class HeatmapOverlay public cleanup(): void { this.label.map?.bitmap?.close(); + this.targets = null; } } diff --git a/app/packages/looker/src/overlays/segmentation.ts b/app/packages/looker/src/overlays/segmentation.ts index 04c2fc693b..f16894ca7e 100644 --- a/app/packages/looker/src/overlays/segmentation.ts +++ b/app/packages/looker/src/overlays/segmentation.ts @@ -263,6 +263,7 @@ export default class SegmentationOverlay public cleanup(): void { this.label.mask?.bitmap?.close(); + this.targets = null; } } diff --git a/app/packages/looker/src/processOverlays.ts b/app/packages/looker/src/processOverlays.ts index c31ee19695..789fbd84d1 100644 --- a/app/packages/looker/src/processOverlays.ts +++ b/app/packages/looker/src/processOverlays.ts @@ -4,6 +4,8 @@ import { CONTAINS, Overlay } from "./overlays/base"; import { ClassificationsOverlay } from "./overlays/classifications"; +import HeatmapOverlay from "./overlays/heatmap"; +import SegmentationOverlay from "./overlays/segmentation"; import { BaseState } from "./state"; import { rotate } from "./util"; @@ -30,6 +32,25 @@ const processOverlays = ( if (!(overlay.field && overlay.field in bins)) continue; + // todo: find a better approach / place for this. + // for instance, this won't work in detection overlay, where + // we might want the bounding boxes but masks might not have been loaded + if ( + overlay instanceof SegmentationOverlay && + overlay.label.mask_path && + !overlay.label.mask + ) { + continue; + } + + if ( + overlay instanceof HeatmapOverlay && + overlay.label.map_path && + !overlay.label.map + ) { + continue; + } + if (!overlay.isShown(state)) continue; bins[overlay.field].push(overlay); diff --git a/app/packages/looker/src/worker/canvas-decoder.ts b/app/packages/looker/src/worker/canvas-decoder.ts index 56749a6d2e..d0ed29f69f 100644 --- a/app/packages/looker/src/worker/canvas-decoder.ts +++ b/app/packages/looker/src/worker/canvas-decoder.ts @@ -1,5 +1,10 @@ import { OverlayMask } from "../numpy"; +const offScreenCanvas = new OffscreenCanvas(1, 1); +const offScreenCanvasCtx = offScreenCanvas.getContext("2d", { + willReadFrequently: true, +})!; + const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; /** * Reads the PNG's image header chunk to determine the color type. @@ -53,20 +58,31 @@ export const decodeWithCanvas = async (blob: Blob) => { const imageBitmap = await createImageBitmap(blob); const { width, height } = imageBitmap; - const canvas = new OffscreenCanvas(width, height); - const ctx = canvas.getContext("2d")!; - ctx.drawImage(imageBitmap, 0, 0); + offScreenCanvas.width = width; + offScreenCanvas.height = height; + + offScreenCanvasCtx.drawImage(imageBitmap, 0, 0); + imageBitmap.close(); - const imageData = ctx.getImageData(0, 0, width, height); + const imageData = offScreenCanvasCtx.getImageData(0, 0, width, height); if (channels === 1) { // get rid of the G, B, and A channels, new buffer will be 1/4 the size - const data = new Uint8ClampedArray(width * height); - for (let i = 0; i < data.length; i++) { - data[i] = imageData.data[i * 4]; + const rawBuffer = imageData.data; + const totalPixels = width * height; + + let read = 0; + let write = 0; + + while (write < totalPixels) { + rawBuffer[write++] = rawBuffer[read]; + // skip "G,B,A" + read += 4; } - imageData.data.set(data); + + const grayScaleData = rawBuffer.slice(0, totalPixels); + rawBuffer.set(grayScaleData); } return { diff --git a/app/packages/spotlight/src/createScrollReader.ts b/app/packages/spotlight/src/createScrollReader.ts index d164f1b5ab..0a1abfbf39 100644 --- a/app/packages/spotlight/src/createScrollReader.ts +++ b/app/packages/spotlight/src/createScrollReader.ts @@ -9,6 +9,7 @@ export default function createScrollReader( render: (zooming: boolean, dispatchOffset?: boolean) => void, getScrollSpeedThreshold: () => number ) { + let animationFrameId: ReturnType; let destroyed = false; let prior: number; let scrolling = false; @@ -63,7 +64,7 @@ export default function createScrollReader( updateScrollStatus(); } - requestAnimationFrame(animate); + animationFrameId = requestAnimationFrame(animate); }; animate(); @@ -73,6 +74,14 @@ export default function createScrollReader( destroyed = true; element.removeEventListener("scroll", scroll); element.removeEventListener("scrollend", scrollEnd); + + if (timeout) { + clearTimeout(timeout); + } + + if (animationFrameId) { + cancelAnimationFrame(animationFrameId); + } }, zooming: () => zooming, };