Skip to content

Commit

Permalink
Merge branch 'main' into lane/vis-todos
Browse files Browse the repository at this point in the history
  • Loading branch information
lanesawyer authored Oct 2, 2024
2 parents 7b8088c + 8cb3cf6 commit 8262fbc
Show file tree
Hide file tree
Showing 29 changed files with 6,390 additions and 4,168 deletions.
34 changes: 6 additions & 28 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
Allen Institute Software License – This software license is the 2-clause BSD
license plus clause a third clause that prohibits redistribution and use for
commercial purposes without further permission.
Copyright 2024 Allen Institute

Copyright © 2019. Allen Institute. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

3. Redistributions and use for commercial purposes are not permitted without
the Allen Institute’s written permission. For purposes of this license,
commercial purposes are the incorporation of the Allen Institute's software
into anything for which you will charge fees or other compensation or use of
the software to perform a commercial service for a third party. Contact
[email protected] for commercial licensing opportunities.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
17 changes: 17 additions & 0 deletions examples/dzi.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html>
<body>
<div
id="sidebar"
style="top: 0; left: 0; width: 15%; height: 100%; position: absolute"
></div>
<div
id="main"
style="top: 0; left: 15%; width: 85%; height: 100%; position: absolute"
/>
</body>
</html>
<script
type="module"
src="./src/dzi/dzi.ts"
></script>
18 changes: 6 additions & 12 deletions examples/index.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
<!doctype html>
<html>
<body>
<div
id="sidebar"
style="top: 0; left: 0; width: 15%; height: 100%; position: absolute"
></div>
<canvas
id="glCanvas"
style="top: 0; left: 15%; width: 85%; height: 100%; position: absolute"
/>
EXAMPLES
<br />
<ul>
<li><a href="/dzi">Deep Zoom Image</a><br /></li>
<li><a href="/layers">Layers</a><br /></li>
</ul>
</body>
</html>
<script
type="module"
src="./src/layers.ts"
></script>
17 changes: 17 additions & 0 deletions examples/layers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html>
<body>
<div
id="sidebar"
style="top: 0; left: 0; width: 15%; height: 100%; position: absolute"
></div>
<canvas
id="glCanvas"
style="top: 0; left: 15%; width: 85%; height: 100%; position: absolute"
/>
</body>
</html>
<script
type="module"
src="./src/layers.ts"
></script>
3 changes: 2 additions & 1 deletion examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"email": "[email protected]"
}
],
"license": "TBD",
"license": "BSD-3-Clause",
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand All @@ -45,6 +45,7 @@
"dependencies": {
"@alleninstitute/vis-geometry": "workspace:*",
"@alleninstitute/vis-scatterbrain": "workspace:*",
"@alleninstitute/vis-dzi": "workspace:*",
"@czi-sds/components": "^20.0.1",
"@emotion/css": "^11.11.2",
"@emotion/react": "^11.11.4",
Expand Down
16 changes: 12 additions & 4 deletions examples/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,23 @@
3. run `pnpm build`
4. `cd examples/`
5. `pnpm run dev`
6. navigate to the running app (default `localhost://5173`)
6. navigate to the running app (default `localhost://5173`). you can click a link to a specific example, or just use the address bar (`localhost://5173/{path_to_desired_example}`)

## Why?
## DZI Example

### Why?

A simple proof of concept for displaying DZI (Deep zoom images) using our utilities, as well as demonstrating how to share an Offscreen canvas to render WebGL to multiple client canvases.

## Layers Example

### Why?

the goal of this (rather complicated) example app is not to show off a cool app - rather its goal is to show that we can build complexity by composing simple, focused modules. As we (the AIBS Apps team) have developed ABC-Atlas, we've tried to make sure our visualization code stays general, and that each part does as little as possible. The result is that it was fairly easy to combine those components into this new app, which mixes a (terrible) UI, scatter-plot rendering, polygon-mesh rendering (for annotations) and multi-channel volumetric rendering into independent layers. Although each of these data types appear different, our caching, fetching, visibility determination, and render-scheduling code is the same regardless of the datatype to be rendered. All that is required is to fill in a simple interface, and provide a low-level "renderer" plugin in order to add a new "layer" type.

## Demo Script
### Demo Script

### Programmatic Configuration
#### Programmatic Configuration

After starting the app in a browser, you'll be greeted by a blank screen. We're going to demonstrate programmatic access to the features of this demo. The goal here is not to make users invoke command-line arguments, but rather just an easy way for interested parties to "peak under the hood". All the visualizations are configured here via simple json objects - it would not be a stretch to read these configuration options at initialization-time via URL parameters for example.

Expand Down
8 changes: 4 additions & 4 deletions examples/src/common/loaders/ome-zarr/zarr-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export function pickBestScale(
const choice = datasets.reduce(
(bestSoFar, cur) =>
dstToDesired(vxlPitch(planeSizeInVoxels(plane, axes, bestSoFar)!), pxPitch) >
dstToDesired(vxlPitch(planeSizeInVoxels(plane, axes, cur)!), pxPitch)
dstToDesired(vxlPitch(planeSizeInVoxels(plane, axes, cur)!), pxPitch)
? cur
: bestSoFar,
datasets[0]
Expand All @@ -135,9 +135,9 @@ export function sizeInUnits(
plane:
| AxisAlignedPlane
| {
u: OmeDimension;
v: OmeDimension;
},
u: OmeDimension;
v: OmeDimension;
},
axes: readonly AxisDesc[],
dataset: DatasetWithShape
): vec2 | undefined {
Expand Down
6 changes: 6 additions & 0 deletions examples/src/dzi/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import { TwoClientsPOC } from './double';

export function AppUi() {
return <TwoClientsPOC />;
}
70 changes: 70 additions & 0 deletions examples/src/dzi/double.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { RenderServerProvider } from './render-server-provider';
import React from 'react';
import { DziView } from './dziView';
import type { DziImage, DziRenderSettings } from '@alleninstitute/vis-dzi';
import { Box2D, Vec2, type box2D } from '@alleninstitute/vis-geometry';

const example: DziImage = {
format: 'jpeg',
imagesUrl:
'https://idk-etl-prod-download-bucket.s3.amazonaws.com/idf-23-10-pathology-images/pat_images_HPW332DMO29NC92JPWA/H20.33.029-A12-I6-primary/H20.33.029-A12-I6-primary_files/',
overlap: 1,
size: {
width: 13446,
height: 11596,
},
tileSize: 512,
};
const exampleDzi: DziImage = {
imagesUrl: 'https://openseadragon.github.io/example-images/highsmith/highsmith_files/',
format: 'jpg',
overlap: 2,
size: {
width: 7026,
height: 9221,
},
tileSize: 256,
};
const exampleSettings: DziRenderSettings = {
camera: {
screenSize: [500, 500],
view: Box2D.create([0, 0], [1, 1]),
},
};

export function TwoClientsPOC() {
const [view, setView] = useState<box2D>(Box2D.create([0, 0], [1, 1]));
const zoom = (e: React.WheelEvent<HTMLCanvasElement>) => {
const scale = e.deltaY > 0 ? 1.1 : 0.9;
const m = Box2D.midpoint(view);
const v = Box2D.translate(Box2D.scale(Box2D.translate(view, Vec2.scale(m, -1)), [scale, scale]), m);
setView(v);
};
const overlay = useRef<HTMLImageElement>(new Image());
useEffect(() => {
overlay.current.onload = () => {
console.log('loaded svg!');
};
overlay.current.src =
'https://idk-etl-prod-download-bucket.s3.amazonaws.com/idf-22-07-pathology-image-move/pat_images_JGCXWER774NLNWX2NNR/7179-A6-I6-MTG-classified/annotation.svg';
}, []);
return (
<RenderServerProvider>
<DziView
id="left"
svgOverlay={overlay.current}
dzi={example}
camera={{ ...exampleSettings.camera, view }}
wheel={zoom}
/>
<DziView
id="right"
dzi={exampleDzi}
svgOverlay={overlay.current}
camera={{ ...exampleSettings.camera, view }}
wheel={zoom}
/>
</RenderServerProvider>
);
}
5 changes: 5 additions & 0 deletions examples/src/dzi/dzi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createRoot } from 'react-dom/client';
import { AppUi } from './app';

const uiroot = createRoot(document.getElementById('main')!);
uiroot.render(AppUi());
110 changes: 110 additions & 0 deletions examples/src/dzi/dziView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { useContext, useEffect, useRef, useState } from 'react';
import {
buildDziRenderer,
type DziImage,
type DziRenderSettings,
type DziTile,
type GpuProps as CachedPixels,
buildAsyncDziRenderer,
} from '@alleninstitute/vis-dzi';
import React from 'react';
import { buildAsyncRenderer, type RenderFrameFn } from '@alleninstitute/vis-scatterbrain';
import { isEqual } from 'lodash';
import { renderServerContext } from './render-server-provider';
import { Vec2, type vec2 } from '@alleninstitute/vis-geometry';

type Props = {
id: string;
dzi: DziImage;
svgOverlay: HTMLImageElement;
wheel: (e: React.WheelEvent<HTMLCanvasElement>) => void;
} & DziRenderSettings;

function buildCompositor(svg: HTMLImageElement, settings: DziRenderSettings) {
return (ctx: CanvasRenderingContext2D, image: ImageData) => {
const { width, height } = svg;
const { camera } = settings;
const svgSize: vec2 = [width, height];
const start = Vec2.mul(camera.view.minCorner, svgSize);
const wh = Vec2.sub(Vec2.mul(camera.view.maxCorner, svgSize), start);
const [sx, sy] = start;
const [sw, sh] = wh;
// first, draw the results from webGL
ctx.putImageData(image, 0, 0);
// then add our svg overlay
ctx.drawImage(svg, sx, sy, sw, sh, 0, 0, ctx.canvas.width, ctx.canvas.height);
};
}

export function DziView(props: Props) {
const { svgOverlay, camera, dzi, wheel, id } = props;
const server = useContext(renderServerContext);
const cnvs = useRef<HTMLCanvasElement>(null);

// this is a demo, so rather than work hard to have a referentially stable camera,
// we just memoize it like so to prevent over-rendering
const [cam, setCam] = useState(camera);
useEffect(() => {
if (!isEqual(cam, camera)) {
setCam(camera);
}
}, [camera]);

// the renderer needs WebGL for us to create it, and WebGL needs a canvas to exist, and that canvas needs to be the same canvas forever
// hence the awkwardness of refs + an effect to initialize the whole hting
const renderer =
useRef<
ReturnType<typeof buildAsyncRenderer<DziImage, DziTile, DziRenderSettings, string, string, CachedPixels>>
>();

useEffect(() => {
if (server && server.regl) {
renderer.current = buildAsyncDziRenderer(server.regl);
}
return () => {
if (cnvs.current) {
server?.destroyClient(cnvs.current);
}
};
}, [server]);

useEffect(() => {
if (server && renderer.current && cnvs.current) {
const renderMyData: RenderFrameFn<DziImage, DziTile> = (target, cache, callback) => {
if (renderer.current) {
// erase the frame before we start drawing on it
return renderer.current(dzi, { camera: cam }, callback, target, cache);
}
return null;
};
const compose = buildCompositor(svgOverlay, { camera: cam });
server.beginRendering(
renderMyData,
(e) => {
switch (e.status) {
case 'begin':
server.regl?.clear({ framebuffer: e.target, color: [0, 0, 0, 0], depth: 1 });
break;
case 'progress':
// wanna see the tiles as they arrive?
e.server.copyToClient(compose);
break;
case 'finished': {
e.server.copyToClient(compose);
}
}
},
cnvs.current
);
}
}, [server, renderer.current, cnvs.current, cam]);
return (
<canvas
id={id}
ref={cnvs}
onWheel={wheel}
width={camera.screenSize[0]}
height={camera.screenSize[1]}
></canvas>
);
}
13 changes: 13 additions & 0 deletions examples/src/dzi/render-server-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { RenderServer } from '@alleninstitute/vis-scatterbrain';
import React, { createContext, useEffect, useRef, type PropsWithChildren } from 'react';

export const renderServerContext = createContext<RenderServer | null>(null);

export function RenderServerProvider(props: PropsWithChildren<{}>) {
const server = useRef<RenderServer>();
const { children } = props;
useEffect(() => {
server.current = new RenderServer([2048, 2048], []);
}, []);
return <renderServerContext.Provider value={server.current ?? null}>{children}</renderServerContext.Provider>;
}
8 changes: 8 additions & 0 deletions examples/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import react from '@vitejs/plugin-react-swc';
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
build: {
rollupOptions: {
input: {
layers: path.resolve(__dirname, './layers.html'),
dzi: path.resolve(__dirname, './dzi.html'),
},
},
},
plugins: [react()],
resolve: {
alias: {
Expand Down
Loading

0 comments on commit 8262fbc

Please sign in to comment.