Skip to content

Commit

Permalink
noahs comments
Browse files Browse the repository at this point in the history
  • Loading branch information
TheMooseman committed Jan 13, 2025
1 parent c17368c commit 03fd5b6
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 175 deletions.
38 changes: 37 additions & 1 deletion examples/src/common/camera.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,44 @@
import type { box2D, vec2 } from '@alleninstitute/vis-geometry';
import { Box2D, Vec2, type box2D, type vec2 } from '@alleninstitute/vis-geometry';

// a basic camera, for viewing slices
export type Camera = {
readonly view: box2D; // a view in 'data space'
readonly screen: vec2; // what that view projects to in display space, aka pixels
readonly projection: 'webImage' | 'cartesian';
};
/**
* Zooms relative to your current mouse position
* @param view your current view
* @param screenSize the size of your canvas/screen
* @param zoomScale the scale you want to apply to your view
* @param mousePos the offsetX and offsetY of your mouse
*/
export function zoom(view: box2D, screenSize: vec2, zoomScale: number, mousePos: vec2) {
// translate mouse pos to data space
// offset divided by screen size gives us a percentage of the canvas where the mouse is
// multiply percentage by view size to make it data space
// add offset of the min corner so that the position takes into account any box offset
const zoomPoint: vec2 = Vec2.add(view.minCorner, Vec2.mul(Vec2.div(mousePos, screenSize), Box2D.size(view)));

// scale the box with our new zoom point as the center
const newView = Box2D.translate(
Box2D.scale(Box2D.translate(view, Vec2.scale(zoomPoint, -1)), [zoomScale, zoomScale]),
zoomPoint
);

return newView;
}

/**
*
* @param view your current view
* @param screenSize the size of your screen/canvas
* @param mousePos your mouse position
* @returns new view that has your pan applied
*/
export function pan(view: box2D, screenSize: vec2, mousePos: vec2) {
const relativePos = Vec2.div(Vec2.mul(mousePos, [-1, -1]), screenSize);
const scaledOffset = Vec2.mul(relativePos, Box2D.size(view));
const newView = Box2D.translate(view, scaledOffset);
return newView;
}
81 changes: 81 additions & 0 deletions examples/src/dzi/decode-dzi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { DziImage } from '@alleninstitute/vis-dzi';

// DZI files will come with XML or JSON to give you important information such as the width, height, and format.
// Below is a function for parsing an xml with that data, althought sometimes it comes in JSON format.
// At the end of the file you can see two examples of the metadata format you might see, one as XML and another as JSON
/**
* This function helps decode xml metadata for a dzi file.
* @param s xml string
* @param url url for dzi file
* @returns formatted dzi image data
*/
function decodeDziXml(s: string, url: string): DziImage | undefined {
const parser = new DOMParser();
const doc = parser.parseFromString(s, 'text/xml');
// catch any errors if the xml is malformed
const err = doc.querySelector('Error');
if (err) return undefined;

if (doc) {
const img = doc.getElementsByTagName('Image')[0];
const size = doc.getElementsByTagName('Size')[0];
// format: as jpg/png
// overlap: how much overlap there is between images so that we can compensate the rendering
// tile size: how big in pixels each tile is
const [format, overlap, tileSize] = [
img.getAttribute('Format'),
img.getAttribute('Overlap'),
img.getAttribute('TileSize'),
];
if (size && format && overlap && tileSize) {
// width and height of the image, so we can properly size the view
const width = size.getAttribute('Width');
const height = size.getAttribute('Height');

// the url ends with .dzi to denote that we're reaching for a dzi file
// in order to get the images from that url we need to remove the .dzi
// and replace it with _files/ so that the image viewer knows where to look
const dataLoc = url.split('.dzi')[0];
if (width && height && dataLoc) {
return {
imagesUrl: `${dataLoc}_files/`,
format: format as 'jpeg' | 'png' | 'jpg' | 'JPG' | 'PNG',
overlap: Number.parseInt(overlap, 10),
tileSize: Number.parseInt(tileSize, 10),
size: {
width: Number.parseInt(width, 10),
height: Number.parseInt(height, 10),
},
};
}
}
}

return undefined;
}

/* Example XML
<?xml version="1.0" encoding="UTF-8"?>
<Image xmlns="http://schemas.microsoft.com/deepzoom/2008"
Format="jpg"
Overlap="2"
TileSize="256" >
<Size Height="9221"
Width="7026"/>
</Image>
*/

/* Example JSON
{
"Image": {
"xmlns": "http://schemas.microsoft.com/deepzoom/2008",
"Format": "jpg",
"Overlap": "2",
"TileSize": "256",
"Size": {
"Height": "9221",
"Width": "7026"
}
}
}
*/
32 changes: 27 additions & 5 deletions examples/src/dzi/dzi-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import type { DziImage, DziRenderSettings } from '@alleninstitute/vis-dzi';
import { Box2D, Vec2, type box2D, type vec2 } from '@alleninstitute/vis-geometry';
import { DziViewer } from './dzi-viewer';
import { pan, zoom } from '~/common/camera';

// We know the sizes and formats ahead of time for these examples,
// if you'd like to see how to get this data from an endpoint with a dzi file check out use-dzi-image.ts
Expand Down Expand Up @@ -48,13 +49,30 @@ export function DziDemo() {
// the DZI renderer expects a "relative" camera - that means a box, from 0 to 1. 0 is the bottom or left of the image,
// and 1 is the top or right of the image, regardless of the aspect ratio of that image.
const [view, setView] = useState<box2D>(Box2D.create([0, 0], [1, 1]));
const zoom = (e: WheelEvent) => {
const [dragging, setDragging] = useState(false);

const handleZoom = (e: WheelEvent) => {
e.preventDefault();
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);
const zoomScale = e.deltaY > 0 ? 1.1 : 0.9;
const v = zoom(view, screenSize, zoomScale, [e.offsetX, e.offsetY]);
setView(v);
};

const handlePan = (e: React.MouseEvent<HTMLCanvasElement>) => {
if (dragging) {
const v = pan(view, screenSize, [e.movementX, e.movementY]);
setView(v);
}
};

const handleMouseDown = () => {
setDragging(true);
};

const handleMouseUp = () => {
setDragging(false);
};

const overlay = useRef<HTMLImageElement>(new Image());

const camera: DziRenderSettings['camera'] = useMemo(() => ({ screenSize, view }), [view]);
Expand All @@ -78,7 +96,11 @@ export function DziDemo() {
dzi={v}
camera={camera}
svgOverlay={overlay.current}
onWheel={zoom}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
onMouseMove={handlePan}
onWheel={handleZoom}
/>
</div>
))}
Expand Down
80 changes: 0 additions & 80 deletions examples/src/dzi/use-dzi-image.ts

This file was deleted.

14 changes: 7 additions & 7 deletions examples/src/layers/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ const slide32 = 'MQ1B9QBZFIPXQO6PETJ';
const colorByGene: ColumnRequest = { name: '88', type: 'QUANTITATIVE' };
const scottpoc = 'https://tissuecyte-ome-zarr-poc.s3.amazonaws.com/40_128_128/1145081396';

export const examples: Record<string, any> = {
['reconstructed']: {
export const examples = {
reconstructed: {
colorBy: colorByGene,
type: 'ScatterPlotGridConfig',
url: 'https://bkp-2d-visualizations-stage.s3.amazonaws.com/wmb_ccf_04112024-20240419205547/4STCSZBXHYOI0JUUA3M/ScatterBrain.json',
} as ScatterplotGridConfig,
['oneSlide']: {
oneSlide: {
colorBy: colorByGene,
slideId: slide32,
type: 'ScatterPlotGridSlideConfig',
url: 'https://bkp-2d-visualizations-stage.s3.amazonaws.com/wmb_ccf_04112024-20240419205547/4STCSZBXHYOI0JUUA3M/ScatterBrain.json',
} as ScatterPlotGridSlideConfig,
['tissueCyte396']: {
tissueCyte396: {
type: 'ZarrSliceGridConfig',
gamut: {
R: { index: 0, gamut: { max: 600, min: 0 } },
Expand All @@ -31,7 +31,7 @@ export const examples: Record<string, any> = {
slices: 142,
url: scottpoc,
} as ZarrSliceGridConfig,
['tissueCyteSlice']: {
tissueCyteSlice: {
type: 'zarrSliceConfig',
gamut: {
R: { index: 0, gamut: { max: 600, min: 0 } },
Expand All @@ -42,7 +42,7 @@ export const examples: Record<string, any> = {
planeParameter: 0.5,
url: scottpoc,
} as ZarrSliceConfig,
['versa1']: {
versa1: {
url: 'https://neuroglancer-vis-prototype.s3.amazonaws.com/VERSA/scratch/0500408166/',
type: 'ZarrSliceGridConfig',
gamut: {
Expand All @@ -53,7 +53,7 @@ export const examples: Record<string, any> = {
plane: 'xy',
slices: 4,
} as ZarrSliceGridConfig,
['structureAnnotation']: {
structureAnnotation: {
type: 'AnnotationGridConfig',
url: 'https://bkp-2d-visualizations-stage.s3.amazonaws.com/wmb_ccf_04112024-20240419205547/4STCSZBXHYOI0JUUA3M/ScatterBrain.json',
levelFeature: '73GVTDXDEGE27M2XJMT',
Expand Down
33 changes: 8 additions & 25 deletions examples/src/omezarr/omezarr-demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { type OmeZarrDataset, loadOmeZarr, sizeInUnits } from '@alleninstitute/v
import { OmezarrViewer } from './omezarr-viewer';
import { type RenderSettings } from '@alleninstitute/vis-omezarr';
import { Box2D, Vec2, type box2D, type Interval, type vec2 } from '@alleninstitute/vis-geometry';
import { pan, zoom } from '~/common/camera';

const demo_versa = 'https://neuroglancer-vis-prototype.s3.amazonaws.com/VERSA/scratch/0500408166/';

Expand Down Expand Up @@ -43,39 +44,21 @@ export function OmezarrDemo() {
const size = sizeInUnits('xy', v.multiscales[0].axes, v.multiscales[0].datasets[0]);
if (size) {
console.log(size);
setView(Box2D.create([0, 0], [size[0], size[1]]));
setView(Box2D.create([0, 0], size));
}
});
}, []);

const zoom = (e: WheelEvent) => {
const handleZoom = (e: WheelEvent) => {
e.preventDefault();

const zoomScale = e.deltaY > 0 ? 1.1 : 0.9;

// translate mouse pos to data space
// offset divided by screen size gives us a percentage of the canvas where the mouse is
// multiply percentage by view size to make it data space
// add offset of the min corner so that the position takes into account any box offset
const zoomPoint: vec2 = Vec2.add(
view.minCorner,
Vec2.mul(Vec2.div([e.offsetX, e.offsetY], screenSize), Box2D.size(view))
);

// scale the box with our new zoom point as the center
const v = Box2D.translate(
Box2D.scale(Box2D.translate(view, Vec2.scale(zoomPoint, -1)), [zoomScale, zoomScale]),
zoomPoint
);

const v = zoom(view, screenSize, zoomScale, [e.offsetX, e.offsetY]);
setView(v);
};

const pan = (e: React.MouseEvent<HTMLCanvasElement>) => {
const handlePan = (e: React.MouseEvent<HTMLCanvasElement>) => {
if (dragging) {
const pos = Vec2.div([-e.movementX, -e.movementY], screenSize);
const scaledOffset = Vec2.mul(pos, Box2D.size(view));
const v = Box2D.translate(view, scaledOffset);
const v = pan(view, screenSize, [e.movementX, e.movementY]);
setView(v);
}
};
Expand Down Expand Up @@ -106,8 +89,8 @@ export function OmezarrDemo() {
id="omezarr-viewer"
screenSize={screenSize}
settings={settings}
onWheel={zoom}
onMouseMove={pan}
onWheel={handleZoom}
onMouseMove={handlePan}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
Expand Down
Loading

0 comments on commit 03fd5b6

Please sign in to comment.