@biongff/vizarr 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/biongff-vizarr.cjs.js +1 -0
  2. package/dist/biongff-vizarr.es.js +7 -0
  3. package/dist/blosc-CeItQ6qj.cjs +17 -0
  4. package/dist/blosc-DaK8KnI4.js +719 -0
  5. package/dist/chunk-INHXZS53-DiyuLb3Z.js +14 -0
  6. package/dist/chunk-INHXZS53-z3BpFH8p.cjs +1 -0
  7. package/dist/deflate-BeJu5ryY.js +10 -0
  8. package/dist/deflate-Ctyqttht.cjs +1 -0
  9. package/dist/index-BR2zkK5Y.cjs +3811 -0
  10. package/dist/index-CU_d8468.js +49053 -0
  11. package/dist/index.d.ts +67 -0
  12. package/dist/jpeg-BWDIgcDK.js +533 -0
  13. package/dist/jpeg-Mp_UXZAJ.cjs +1 -0
  14. package/dist/jpeg2k-1tnzEScI.js +24 -0
  15. package/dist/jpeg2k-CcF6vCxI.cjs +1 -0
  16. package/dist/lerc-BJaDi7aO.js +1039 -0
  17. package/dist/lerc-D-6RihsZ.cjs +1 -0
  18. package/dist/lz4-CssV0LoA.js +643 -0
  19. package/dist/lz4-PFaIsPAh.cjs +15 -0
  20. package/dist/lzw-DBp1lk8U.js +84 -0
  21. package/dist/lzw-De76dcEC.cjs +1 -0
  22. package/dist/packbits-B6vt-6GN.cjs +1 -0
  23. package/dist/packbits-MT4dFhEA.js +24 -0
  24. package/dist/pako.esm-Bx5X36Wo.js +1074 -0
  25. package/dist/pako.esm-DZC2QrbJ.cjs +1 -0
  26. package/dist/raw-BF-NUDI1.js +9 -0
  27. package/dist/raw-vwCA5qWn.cjs +1 -0
  28. package/dist/ref-DAXKePD4.cjs +1 -0
  29. package/dist/ref-HqVJFnCG.js +196 -0
  30. package/dist/webimage-BQlxeHEQ.cjs +1 -0
  31. package/dist/webimage-DTyyK3Ev.js +19 -0
  32. package/dist/zstd-Cqadn9HA.js +610 -0
  33. package/dist/zstd-_xUhkGOV.cjs +15 -0
  34. package/package.json +25 -20
  35. package/.gitattributes +0 -1
  36. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
  37. package/.github/ISSUE_TEMPLATE/documentation_update.md +0 -16
  38. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  39. package/.github/ISSUE_TEMPLATE/maintenance.md +0 -19
  40. package/.github/ISSUE_TEMPLATE/optimisation_request.md +0 -23
  41. package/.github/ISSUE_TEMPLATE/support_request.md +0 -25
  42. package/.github/PULL_REQUEST_TEMPLATE.md +0 -21
  43. package/.github/dependabot.yml +0 -13
  44. package/.github/workflows/deploy.yml +0 -56
  45. package/.github/workflows/npm-publish-dev.yml +0 -49
  46. package/.github/workflows/npm-publish.yml +0 -42
  47. package/.github/workflows/release.yml +0 -37
  48. package/.github/workflows/test.yml +0 -21
  49. package/.releaserc.json +0 -29
  50. package/CHANGELOG.md +0 -56
  51. package/CITATION.cff +0 -56
  52. package/CONTRIBUTING.md +0 -82
  53. package/README.md +0 -85
  54. package/assets/favicon.ico +0 -0
  55. package/assets/favicon.png +0 -0
  56. package/assets/logo-wide.png +0 -0
  57. package/assets/logo-wide.svg +0 -60
  58. package/assets/logo.png +0 -0
  59. package/assets/logo.svg +0 -36
  60. package/assets/robots.txt +0 -3
  61. package/assets/screenshot.png +0 -0
  62. package/biome.json +0 -34
  63. package/index.html +0 -24
  64. package/main.ts +0 -52
  65. package/patches/@vivjs__loaders@0.17.2.patch +0 -13
  66. package/python/.python-version +0 -1
  67. package/python/README.md +0 -14
  68. package/python/Untitled.ipynb +0 -40
  69. package/python/deno.json +0 -22
  70. package/python/notebooks/IDR_example.ipynb +0 -86
  71. package/python/notebooks/README.md +0 -35
  72. package/python/notebooks/create_fixture.py +0 -72
  73. package/python/notebooks/getting_started.ipynb +0 -217
  74. package/python/notebooks/mandelbrot.ipynb +0 -211
  75. package/python/notebooks/spatial_transformations.ipynb +0 -139
  76. package/python/pyproject.toml +0 -18
  77. package/python/src/vizarr/__init__.py +0 -10
  78. package/python/src/vizarr/_widget.js +0 -113
  79. package/python/src/vizarr/_widget.py +0 -67
  80. package/python/uv.lock +0 -2495
  81. package/src/ZarrPixelSource.ts +0 -182
  82. package/src/codecs/jpeg2k.ts +0 -22
  83. package/src/codecs/register.ts +0 -2
  84. package/src/components/LayerController/AcquisitionController.tsx +0 -46
  85. package/src/components/LayerController/AddChannelButton.tsx +0 -122
  86. package/src/components/LayerController/AxisOptions.tsx +0 -93
  87. package/src/components/LayerController/AxisSlider.tsx +0 -86
  88. package/src/components/LayerController/AxisSliders.tsx +0 -29
  89. package/src/components/LayerController/ChannelController.tsx +0 -84
  90. package/src/components/LayerController/ChannelOptions.tsx +0 -183
  91. package/src/components/LayerController/ColorPalette.tsx +0 -40
  92. package/src/components/LayerController/Content.tsx +0 -71
  93. package/src/components/LayerController/Header.tsx +0 -48
  94. package/src/components/LayerController/Labels.tsx +0 -73
  95. package/src/components/LayerController/LayerFitToViewportButton.tsx +0 -43
  96. package/src/components/LayerController/LayerVisibilityButton.tsx +0 -32
  97. package/src/components/LayerController/OpacitySlider.tsx +0 -25
  98. package/src/components/LayerController/index.tsx +0 -42
  99. package/src/components/Menu.tsx +0 -54
  100. package/src/components/Viewer.tsx +0 -136
  101. package/src/hooks.ts +0 -29
  102. package/src/index.tsx +0 -138
  103. package/src/io.ts +0 -276
  104. package/src/layers/grid-layer.ts +0 -214
  105. package/src/layers/label-layer.ts +0 -446
  106. package/src/layers/viv-layers.ts +0 -55
  107. package/src/lru-store.ts +0 -57
  108. package/src/ome.ts +0 -388
  109. package/src/state.ts +0 -202
  110. package/src/theme.ts +0 -80
  111. package/src/types.ts +0 -112
  112. package/src/utils.ts +0 -655
  113. package/tsconfig.json +0 -14
  114. package/vite.config.js +0 -47
@@ -1,214 +0,0 @@
1
- import { CompositeLayer, SolidPolygonLayer, TextLayer } from "deck.gl";
2
- import pMap from "p-map";
3
-
4
- import { ColorPaletteExtension, XRLayer } from "@hms-dbmi/viv";
5
- import type { SupportedTypedArray } from "@vivjs/types";
6
- import type { CompositeLayerProps, PickingInfo, SolidPolygonLayerProps, TextLayerProps } from "deck.gl";
7
- import type { ZarrPixelSource } from "../ZarrPixelSource";
8
- import { assert } from "../utils";
9
- import type { BaseLayerProps } from "./viv-layers";
10
-
11
- export interface GridLoader {
12
- loader: ZarrPixelSource;
13
- row: number;
14
- col: number;
15
- name: string;
16
- }
17
-
18
- type Polygon = Array<[number, number]>;
19
-
20
- export interface GridLayerProps
21
- extends Omit<CompositeLayerProps, "loaders" | "modelMatrix" | "opacity" | "onClick" | "id">,
22
- BaseLayerProps {
23
- loaders: GridLoader[];
24
- rows: number;
25
- columns: number;
26
- spacer?: number;
27
- text?: boolean;
28
- concurrency?: number;
29
- }
30
-
31
- function scaleBounds(width: number, height: number, translate = [0, 0], scale = 1) {
32
- const [left, top] = translate;
33
- const right = width * scale + left;
34
- const bottom = height * scale + top;
35
- return [left, bottom, right, top];
36
- }
37
-
38
- function validateWidthHeight(d: { data: { width: number; height: number } }[]) {
39
- const [first] = d;
40
- // Return early if no grid data. Maybe throw an error?
41
- const { width, height } = first.data;
42
- // Verify that all grid data is same shape (ignoring undefined)
43
- for (const { data } of d) {
44
- if (!data) continue;
45
- assert(data.width === width && data.height === height, "Grid data is not same shape.");
46
- }
47
- return { width, height };
48
- }
49
-
50
- function refreshGridData(props: GridLayerProps) {
51
- const { loaders, selections = [] } = props;
52
- let { concurrency } = props;
53
- if (concurrency && selections.length > 0) {
54
- // There are `loaderSelection.length` requests per loader. This block scales
55
- // the provided concurrency to map to the number of actual requests.
56
- concurrency = Math.ceil(concurrency / selections.length);
57
- }
58
- const mapper = async (d: GridLoader) => {
59
- const promises = selections.map((selection) => d.loader.getRaster({ selection }));
60
- const tiles = await Promise.all(promises);
61
- return {
62
- ...d,
63
- data: {
64
- data: tiles.map((d) => d.data),
65
- width: tiles[0].width,
66
- height: tiles[0].height,
67
- },
68
- };
69
- };
70
- return pMap(loaders, mapper, { concurrency });
71
- }
72
-
73
- type SharedLayerState = {
74
- gridData: Awaited<ReturnType<typeof refreshGridData>>;
75
- width: number;
76
- height: number;
77
- };
78
-
79
- class GridLayer extends CompositeLayer<CompositeLayerProps & GridLayerProps> {
80
- static layerName = "VizarrGridLayer";
81
- static defaultProps = {
82
- // @ts-expect-error - XRLayer props are not typed
83
- ...XRLayer.defaultProps,
84
- // Special grid props
85
- loaders: { type: "array", value: [], compare: true },
86
- spacer: { type: "number", value: 5, compare: true },
87
- rows: { type: "number", value: 0, compare: true },
88
- columns: { type: "number", value: 0, compare: true },
89
- concurrency: { type: "number", value: 10, compare: false }, // set concurrency for queue
90
- text: { type: "boolean", value: false, compare: true },
91
- // Deck.gl
92
- onClick: { type: "function", value: null, compare: true },
93
- onHover: { type: "function", value: null, compare: true },
94
- };
95
-
96
- get #state(): SharedLayerState {
97
- // @ts-expect-error - typed as any by deck
98
- return this.state;
99
- }
100
-
101
- set #state(state: SharedLayerState) {
102
- this.state = state;
103
- }
104
-
105
- initializeState() {
106
- this.#state = { gridData: [], width: 0, height: 0 };
107
- refreshGridData(this.props).then((gridData) => {
108
- const { width, height } = validateWidthHeight(gridData);
109
- this.setState({ gridData, width, height });
110
- });
111
- }
112
-
113
- updateState({
114
- props,
115
- oldProps,
116
- changeFlags,
117
- }: {
118
- props: GridLayerProps;
119
- oldProps: GridLayerProps;
120
- changeFlags: {
121
- propsChanged: string | boolean | null;
122
- };
123
- }) {
124
- const { propsChanged } = changeFlags;
125
- const loaderChanged = typeof propsChanged === "string" && propsChanged.includes("props.loaders");
126
- const loaderSelectionChanged = props.selections !== oldProps.selections;
127
- if (loaderChanged || loaderSelectionChanged) {
128
- // Only fetch new data to render if loader has changed
129
- refreshGridData(this.props).then((gridData) => {
130
- this.setState({ gridData });
131
- });
132
- }
133
- }
134
-
135
- getPickingInfo({ info }: { info: PickingInfo }) {
136
- // provide Grid row and column info for mouse events (hover & click)
137
- if (!info.coordinate) {
138
- return info;
139
- }
140
- const spacer = this.props.spacer || 0;
141
- const { width, height } = this.#state;
142
- const [x, y] = info.coordinate;
143
- const row = Math.floor(y / (height + spacer));
144
- const column = Math.floor(x / (width + spacer));
145
- return {
146
- ...info,
147
- gridCoord: { row, column },
148
- };
149
- }
150
-
151
- renderLayers() {
152
- const { gridData, width, height } = this.#state;
153
- if (width === 0 || height === 0) return null; // early return if no data
154
-
155
- const { rows, columns, spacer = 0, id = "" } = this.props;
156
- type Data = { row: number; col: number; loader: Pick<ZarrPixelSource, "dtype">; data: Array<SupportedTypedArray> };
157
- const layers = gridData.map((d) => {
158
- const y = d.row * (height + spacer);
159
- const x = d.col * (width + spacer);
160
- const layerProps = {
161
- channelData: d.data, // coerce to null if no data
162
- bounds: scaleBounds(width, height, [x, y]),
163
- id: `${id}-GridLayer-${d.row}-${d.col}`,
164
- dtype: d.loader.dtype || "Uint16", // fallback if missing,
165
- pickable: false,
166
- extensions: [new ColorPaletteExtension()],
167
- };
168
- // @ts-expect-error - XRLayer props are not well typed
169
- return new XRLayer({ ...this.props, ...layerProps });
170
- });
171
-
172
- if (this.props.pickable) {
173
- type Data = { polygon: Polygon };
174
- const bottom = rows * (height + spacer);
175
- const right = columns * (width + spacer);
176
- const polygon = [
177
- [0, 0],
178
- [right, 0],
179
- [right, bottom],
180
- [0, bottom],
181
- ] satisfies Polygon;
182
- const layerProps = {
183
- data: [{ polygon }],
184
- getPolygon: (d) => d.polygon,
185
- getFillColor: [0, 0, 0, 0], // transparent
186
- getLineColor: [0, 0, 0, 0],
187
- pickable: true, // enable picking
188
- id: `${id}-GridLayer-picking`,
189
- } satisfies SolidPolygonLayerProps<Data>;
190
- const layer = new SolidPolygonLayer<Data, SolidPolygonLayerProps<Data>>({ ...this.props, ...layerProps });
191
- layers.push(layer);
192
- }
193
-
194
- if (this.props.text) {
195
- type Data = { col: number; row: number; name: string };
196
- const layer = new TextLayer<Data, TextLayerProps<Data>>({
197
- id: `${id}-GridLayer-text`,
198
- data: gridData,
199
- getPosition: (d) => [d.col * (width + spacer), d.row * (height + spacer)],
200
- getText: (d) => d.name,
201
- getColor: [255, 255, 255, 255],
202
- getSize: 16,
203
- getAngle: 0,
204
- getTextAnchor: "start",
205
- getAlignmentBaseline: "top",
206
- });
207
- layers.push(layer);
208
- }
209
-
210
- return layers;
211
- }
212
- }
213
-
214
- export { GridLayer };
@@ -1,446 +0,0 @@
1
- import { BitmapLayer, TileLayer, _Tileset2D as Tileset2D } from "deck.gl";
2
- import * as utils from "../utils";
3
-
4
- import type { FilterContext } from "@deck.gl/core";
5
- import type { _Tile2DHeader as Tile2DHeader } from "@deck.gl/geo-layers";
6
- import type { BitmapLayerPickingInfo } from "@deck.gl/layers";
7
- import type { GetPickingInfoParams, Layer, UpdateParameters } from "deck.gl";
8
- import { Matrix4, clamp } from "math.gl";
9
- import type * as zarr from "zarrita";
10
- import type { ZarrPixelSource } from "../ZarrPixelSource";
11
- import { transformBox } from "../utils";
12
-
13
- type Texture = ReturnType<BitmapLayer["context"]["device"]["createTexture"]>;
14
-
15
- export const DEFAULT_LABEL_OPACITY = 0.5;
16
-
17
- export type OmeColor = Readonly<{
18
- labelValue: number;
19
- rgba: readonly [r: number, g: number, b: number, a: number];
20
- value?: string | number | null;
21
- }>;
22
-
23
- export interface LabelLayerProps {
24
- id: string;
25
- loader: Array<ZarrPixelSource>;
26
- selection: Array<number>;
27
- opacity: number;
28
- modelMatrix: Matrix4;
29
- colors?: ReadonlyArray<OmeColor>;
30
- pickable?: boolean;
31
- }
32
-
33
- export interface GrayscaleBitmapLayerPickingInfo extends BitmapLayerPickingInfo {
34
- label?: number;
35
- value?: string | number | null;
36
- }
37
-
38
- /**
39
- * @see {@href https://ngff.openmicroscopy.org/0.5/index.html#labels-md}
40
- *
41
- * The pixels of the label images MUST be integer data types, i.e. one of [uint8, int8, uint16, int16, uint32, int32, uint64, int64].
42
- */
43
- type LabelDataType = zarr.Uint8 | zarr.Int8 | zarr.Uint16 | zarr.Int16 | zarr.Uint32 | zarr.Int32;
44
- // TODO: bigint data types are supported by the spec but not by Viv's data loader.
45
- // | zarr.Uint64
46
- // | zarr.Int64;
47
-
48
- /** The decoded tile data from a OME-NGFF label source */
49
- type LabelPixelData = {
50
- data: zarr.TypedArray<LabelDataType>;
51
- width: number;
52
- height: number;
53
- };
54
-
55
- // @TODO: update deck.gl, this bug is fixed in version 9.2.0
56
- // Extend Tileset2D to use modelMatrix in isTileVisible
57
- // so picking works in the transformed tiles
58
- class LabelTileset2D extends Tileset2D {
59
- isTileVisible(
60
- tile: Tile2DHeader,
61
- cullRect?: { x: number; y: number; width: number; height: number },
62
- modelMatrix?: Matrix4 | null,
63
- ) {
64
- if (!tile.isVisible) {
65
- return false;
66
- }
67
-
68
- // @ts-ignore
69
- if (cullRect && this._viewport) {
70
- // @ts-ignore
71
- const boundsArr = this._getCullBounds({
72
- // @ts-ignore
73
- viewport: this._viewport,
74
- // @ts-ignore
75
- z: this._zRange,
76
- cullRect,
77
- });
78
- let { bbox } = tile;
79
- for (const [minX, minY, maxX, maxY] of boundsArr) {
80
- let overlaps: boolean;
81
- if ("west" in bbox) {
82
- overlaps = bbox.west < maxX && bbox.east > minX && bbox.south < maxY && bbox.north > minY;
83
- } else {
84
- if (modelMatrix && !Matrix4.IDENTITY.equals(modelMatrix)) {
85
- const [left, top, right, bottom] = transformBox(
86
- [bbox.left, bbox.top, bbox.right, bbox.bottom],
87
- modelMatrix,
88
- );
89
- bbox = { left, top, right, bottom };
90
- }
91
- // top/bottom could be swapped depending on the indexing system
92
- const y0 = Math.min(bbox.top, bbox.bottom);
93
- const y1 = Math.max(bbox.top, bbox.bottom);
94
- overlaps = bbox.left < maxX && bbox.right > minX && y0 < maxY && y1 > minY;
95
- }
96
- if (overlaps) {
97
- return true;
98
- }
99
- }
100
- return false;
101
- }
102
- return true;
103
- }
104
- }
105
-
106
- export class LabelLayer extends TileLayer<LabelPixelData, LabelLayerProps> {
107
- static layerName = "VizarrLabelLayer";
108
- // @ts-expect-error - only way to extend the base state type
109
- state!: { colorTexture: Texture; valueMap?: Map<number, string | number | null> } & TileLayer["state"];
110
-
111
- constructor(props: LabelLayerProps) {
112
- const { id, loader, selection, opacity, modelMatrix, colors, ...restTileLayerProps } = props;
113
-
114
- const resolutions = loader;
115
- const dimensions = {
116
- height: resolutions[0].shape.at(-2),
117
- width: resolutions[0].shape.at(-1),
118
- };
119
- utils.assert(dimensions.width && dimensions.height);
120
- const tileSize = getTileSizeForResolutions(resolutions);
121
- super({
122
- TilesetClass: LabelTileset2D,
123
- id: `labels-${id}`,
124
- extent: [0, 0, dimensions.width, dimensions.height],
125
- tileSize: tileSize,
126
- minZoom: Math.round(-(resolutions.length - 1)),
127
- opacity: opacity,
128
- maxZoom: 0,
129
- modelMatrix: modelMatrix,
130
- colors: colors,
131
- zoomOffset: Math.round(Math.log2(modelMatrix ? modelMatrix.getScale()[0] : 1)),
132
- updateTriggers: {
133
- getTileData: [loader, selection],
134
- },
135
- async getTileData({ index, signal }) {
136
- const { x, y, z } = index;
137
- const resolution = resolutions[Math.round(-z)];
138
- const request = { x, y, signal, selection: selection };
139
- let { data, width, height } = await resolution.getTile(request);
140
- utils.assert(
141
- !(data instanceof Float32Array) && !(data instanceof Float64Array),
142
- `The pixels of labels MUST be integer data types, got ${JSON.stringify(resolution.dtype)}`,
143
- );
144
- return { data, width, height };
145
- },
146
- ...restTileLayerProps,
147
- });
148
- }
149
-
150
- renderSubLayers(
151
- params: TileLayer["props"] & {
152
- data: LabelPixelData;
153
- tile: {
154
- index: { x: number; y: number; z: number };
155
- boundingBox: [min: Array<number>, max: Array<number>];
156
- };
157
- },
158
- ): Layer {
159
- const { tile, data, ...props } = params;
160
- const [[left, bottom], [right, top]] = tile.boundingBox;
161
- utils.assert(props.extent, "missing extent");
162
- const [_x0, _y0, width, height] = props.extent;
163
- return new GrayscaleBitmapLayer({
164
- id: `tile-${tile.index.x}.${tile.index.y}.${tile.index.z}-${props.id}`,
165
- pixelData: data,
166
- opacity: props.opacity,
167
- modelMatrix: props.modelMatrix,
168
- colorTexture: this.state.colorTexture,
169
- valueMap: this.state.valueMap,
170
- bounds: [clamp(left, 0, width), clamp(top, 0, height), clamp(right, 0, width), clamp(bottom, 0, height)],
171
- // For underlying class
172
- image: new ImageData(data.width, data.height),
173
- pickable: props.pickable ?? false,
174
- modelMatrixInverse: props.modelMatrix ? new Matrix4(props.modelMatrix).invert() : undefined,
175
- });
176
- }
177
-
178
- filterSubLayer({ layer, cullRect }: FilterContext) {
179
- const { tile } = (layer as Layer<{ tile: Tile2DHeader }>).props;
180
- const { modelMatrix } = this.props;
181
- const tileset = this.state.tileset as unknown as LabelTileset2D;
182
- return tileset.isTileVisible(tile, cullRect, modelMatrix ? new Matrix4(modelMatrix) : null);
183
- }
184
-
185
- updateState({ props, oldProps, changeFlags, ...rest }: UpdateParameters<this>): void {
186
- super.updateState({ props, oldProps, changeFlags, ...rest });
187
- // we make the colorTexture on this layer so we can share it amoung all the sublayers
188
- if (props.colors !== oldProps.colors || !this.state.colorTexture) {
189
- this.state.colorTexture?.destroy();
190
- const colorTexture = createColorTexture({
191
- source: props.colors,
192
- maxTextureDimension2D: this.context.device.limits.maxTextureDimension2D,
193
- });
194
- this.setState({
195
- colorTexture: this.context.device.createTexture({
196
- width: colorTexture.width,
197
- height: colorTexture.height,
198
- data: colorTexture.data,
199
- dimension: "2d",
200
- mipmaps: false,
201
- sampler: {
202
- minFilter: "nearest",
203
- magFilter: "nearest",
204
- addressModeU: "clamp-to-edge",
205
- addressModeV: "clamp-to-edge",
206
- },
207
- format: "rgba8unorm",
208
- }),
209
- valueMap: props.colors ? new Map(props.colors.map((c) => [c.labelValue, c.value])) : null,
210
- });
211
- }
212
- }
213
- }
214
-
215
- export class GrayscaleBitmapLayer extends BitmapLayer<{
216
- pixelData: LabelPixelData;
217
- colorTexture: Texture;
218
- valueMap?: Map<number, string | number | null>;
219
- modelMatrixInverse?: Matrix4;
220
- }> {
221
- static layerName = "VizarrGrayscaleBitmapLayer";
222
- // @ts-expect-error - only way to extend the base state type
223
- state!: { texture: Texture; valueMap?: Map<number, string | number | null> } & BitmapLayer["state"];
224
-
225
- getPickingInfo(params: GetPickingInfoParams): GrayscaleBitmapLayerPickingInfo {
226
- const info = super.getPickingInfo(params) as GrayscaleBitmapLayerPickingInfo;
227
-
228
- // Get label value
229
- if (!info.coordinate) {
230
- return info;
231
- }
232
- const { pixelData, bounds, modelMatrixInverse, valueMap } = this.props;
233
- const { data, width, height } = pixelData;
234
- let [x, y] = info.coordinate;
235
- if (modelMatrixInverse && !Matrix4.IDENTITY.equals(modelMatrixInverse)) {
236
- [x, y] = modelMatrixInverse.transformAsPoint([x, y]);
237
- }
238
- const [left, bottom, right, top] = bounds as number[];
239
-
240
- if (right - left === 0 || top - bottom === 0) {
241
- console.log("Picking info has zero-sized bounds");
242
- return info;
243
- }
244
-
245
- const normX = (x - left) / (right - left);
246
- const normY = (y - bottom) / (top - bottom);
247
- const pixelX = Math.floor(normX * width);
248
- const pixelY = Math.floor((1 - normY) * height);
249
- const clampedX = clamp(pixelX, 0, width);
250
- const clampedY = clamp(pixelY, 0, height);
251
-
252
- const index = clampedY * width + clampedX;
253
-
254
- if (index < 0 || index >= data.length) {
255
- return info;
256
- }
257
- const label = data[index];
258
- const value = valueMap ? valueMap.get(label) : null;
259
- info.label = label;
260
- info.value = value;
261
- return info;
262
- }
263
-
264
- // Temporary workaround for ANGLE bug https://issues.angleproject.org/issues/401546698
265
- // "Error during validation: Two textures of different types use the same sampler location."
266
- // Force grayscaleTexture to float32 to use sampler2D
267
- getShaders() {
268
- // const sampler = (
269
- // {
270
- // Uint8Array: "usampler2D",
271
- // Uint16Array: "usampler2D",
272
- // Uint32Array: "usampler2D",
273
- // Int8Array: "isampler2D",
274
- // Int16Array: "isampler2D",
275
- // Int32Array: "isampler2D",
276
- // } as const
277
- // )[typedArrayConstructorName(this.props.pixelData.data)];
278
- const sampler = "sampler2D"; // Use sampler2D for ANGLE bug workaround
279
- // replace the builtin fragment shader with our own
280
- return {
281
- ...super.getShaders(),
282
- fs: `\
283
- #version 300 es
284
- #define SHADER_NAME grayscale-bitmap-layer-fragment-shader
285
-
286
- precision highp float;
287
- precision highp int;
288
- precision highp ${sampler};
289
-
290
- uniform ${sampler} grayscaleTexture;
291
- uniform sampler2D colorTexture;
292
- uniform float colorTextureWidth;
293
- uniform float colorTextureHeight;
294
- uniform float opacity;
295
-
296
- in vec2 vTexCoord;
297
- out vec4 fragColor;
298
-
299
- void main() {
300
- int index = int(texture(grayscaleTexture, vTexCoord).r);
301
- float x = (mod(float(index), colorTextureWidth) + 0.5) / colorTextureWidth;
302
- float y = (floor(float(index) / colorTextureWidth) + 0.5) / colorTextureHeight;
303
- vec2 uv = vec2(x, y);
304
- vec3 color = texture(colorTexture, uv).rgb;
305
- fragColor = vec4(color, ((index > 0) ? 1.0 : 0.0) * opacity);
306
- }
307
- `,
308
- };
309
- }
310
-
311
- updateState({ props, oldProps, changeFlags, ...rest }: UpdateParameters<this>): void {
312
- super.updateState({ props, oldProps, changeFlags, ...rest });
313
- if (props.pixelData !== oldProps.pixelData) {
314
- this.state.texture?.destroy();
315
- this.setState({
316
- texture: this.context.device.createTexture({
317
- width: props.pixelData.width,
318
- height: props.pixelData.height,
319
- data: new Float32Array(props.pixelData.data), // Force float32 for ANGLE bug workaround
320
- dimension: "2d",
321
- mipmaps: false,
322
- sampler: {
323
- minFilter: "nearest",
324
- magFilter: "nearest",
325
- addressModeU: "clamp-to-edge",
326
- addressModeV: "clamp-to-edge",
327
- },
328
- format: "r32float", // ANGLE bug workaround
329
- // format: (
330
- // {
331
- // Uint8Array: "r8uint",
332
- // Uint16Array: "r16uint",
333
- // Uint32Array: "r32uint",
334
- // Int8Array: "r8sint",
335
- // Int16Array: "r16sint",
336
- // Int32Array: "r32sint",
337
- // } as const
338
- // )[typedArrayConstructorName(props.pixelData.data)],
339
- }),
340
- });
341
- }
342
- }
343
-
344
- draw(opts: unknown) {
345
- const { model, texture } = this.state;
346
- const { colorTexture } = this.props;
347
-
348
- if (model && texture && colorTexture) {
349
- model.setUniforms({
350
- colorTextureWidth: colorTexture.width,
351
- colorTextureHeight: colorTexture.height,
352
- });
353
- model.setBindings({
354
- grayscaleTexture: texture,
355
- colorTexture: colorTexture,
356
- });
357
- }
358
- super.draw(opts);
359
- }
360
- }
361
-
362
- function getTileSizeForResolutions(resolutions: Array<ZarrPixelSource>): number {
363
- const tileSize = resolutions[0].tileSize;
364
- utils.assert(
365
- resolutions.every((resolution) => resolution.tileSize === tileSize),
366
- "resolutions must all have the same tile size",
367
- );
368
- return tileSize;
369
- }
370
-
371
- const SEEN_LUTS = new WeakSet<ReadonlyArray<OmeColor>>();
372
-
373
- /**
374
- * Creates a color lookup table (LUT) as a 2D texture.
375
- *
376
- * @param options.source - The source lookup table.
377
- * @param options.maxTextureDimension2D - The maximum texture dimension size.
378
- */
379
- function createColorTexture(options: {
380
- source?: ReadonlyArray<OmeColor>;
381
- maxTextureDimension2D: number;
382
- }) {
383
- const { source, maxTextureDimension2D } = options;
384
- const fallback = {
385
- data: DEFAULT_COLOR_TEXTURE,
386
- width: DEFAULT_COLOR_TEXTURE.length / 4,
387
- height: 1,
388
- };
389
-
390
- if (!source) {
391
- return fallback;
392
- }
393
-
394
- // pack the colors into a 2D texture
395
- const size = Math.max(...source.map((e) => e.labelValue)) + 1;
396
- const width = Math.min(size, maxTextureDimension2D);
397
- const height = Math.ceil(size / width);
398
-
399
- if (width > maxTextureDimension2D || height > maxTextureDimension2D) {
400
- if (!SEEN_LUTS.has(source)) {
401
- console.warn("[vizarr] Skipping color palette from OME-NGFF `image-label` source: max texture dimension limit.");
402
- SEEN_LUTS.add(source);
403
- }
404
- return fallback;
405
- }
406
-
407
- const data = new Uint8Array(width * height * 4);
408
- for (const { labelValue, rgba } of source) {
409
- const x = labelValue % width;
410
- const y = Math.floor(labelValue / width);
411
- const texIndex = (y * width + x) * 4;
412
- data[texIndex] = rgba[0];
413
- data[texIndex + 1] = rgba[1];
414
- data[texIndex + 2] = rgba[2];
415
- data[texIndex + 3] = rgba[3];
416
- }
417
-
418
- return { data, width, height };
419
- }
420
-
421
- // From Vitessce https://github.com/vitessce/vitessce/blob/03c6d5d843640982e984a0e309f1ba1807085128/packages/utils/other-utils/src/components.ts#L50-L67
422
- const DEFAULT_COLOR_TEXTURE = Uint8Array.from(
423
- [
424
- [0, 73, 73],
425
- [0, 146, 146],
426
- [255, 109, 182],
427
- [255, 182, 219],
428
- [73, 0, 146],
429
- [0, 109, 219],
430
- [182, 109, 255],
431
- [109, 182, 255],
432
- [182, 219, 255],
433
- [146, 0, 0],
434
- [146, 72, 0],
435
- [219, 109, 0],
436
- [36, 255, 36],
437
- [255, 255, 109],
438
- [255, 255, 255],
439
- ].flatMap((color) => [...color, 255]),
440
- );
441
-
442
- function typedArrayConstructorName(arr: zarr.TypedArray<LabelDataType>) {
443
- const ArrayType = arr.constructor as zarr.TypedArrayConstructor<LabelDataType>;
444
- const name = ArrayType.name as `${Capitalize<LabelDataType>}Array`;
445
- return name;
446
- }