@biongff/vizarr 1.1.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.
- package/dist/biongff-vizarr.cjs.js +1 -0
- package/dist/biongff-vizarr.es.js +7 -0
- package/dist/blosc-CeItQ6qj.cjs +17 -0
- package/dist/blosc-DaK8KnI4.js +719 -0
- package/dist/chunk-INHXZS53-DiyuLb3Z.js +14 -0
- package/dist/chunk-INHXZS53-z3BpFH8p.cjs +1 -0
- package/dist/deflate-BeJu5ryY.js +10 -0
- package/dist/deflate-Ctyqttht.cjs +1 -0
- package/dist/index-BR2zkK5Y.cjs +3811 -0
- package/dist/index-CU_d8468.js +49053 -0
- package/dist/index.d.ts +67 -0
- package/dist/jpeg-BWDIgcDK.js +533 -0
- package/dist/jpeg-Mp_UXZAJ.cjs +1 -0
- package/dist/jpeg2k-1tnzEScI.js +24 -0
- package/dist/jpeg2k-CcF6vCxI.cjs +1 -0
- package/dist/lerc-BJaDi7aO.js +1039 -0
- package/dist/lerc-D-6RihsZ.cjs +1 -0
- package/dist/lz4-CssV0LoA.js +643 -0
- package/dist/lz4-PFaIsPAh.cjs +15 -0
- package/dist/lzw-DBp1lk8U.js +84 -0
- package/dist/lzw-De76dcEC.cjs +1 -0
- package/dist/packbits-B6vt-6GN.cjs +1 -0
- package/dist/packbits-MT4dFhEA.js +24 -0
- package/dist/pako.esm-Bx5X36Wo.js +1074 -0
- package/dist/pako.esm-DZC2QrbJ.cjs +1 -0
- package/dist/raw-BF-NUDI1.js +9 -0
- package/dist/raw-vwCA5qWn.cjs +1 -0
- package/dist/ref-DAXKePD4.cjs +1 -0
- package/dist/ref-HqVJFnCG.js +196 -0
- package/dist/webimage-BQlxeHEQ.cjs +1 -0
- package/dist/webimage-DTyyK3Ev.js +19 -0
- package/dist/zstd-Cqadn9HA.js +610 -0
- package/dist/zstd-_xUhkGOV.cjs +15 -0
- package/package.json +24 -19
- package/.gitattributes +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
- package/.github/ISSUE_TEMPLATE/documentation_update.md +0 -16
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/ISSUE_TEMPLATE/maintenance.md +0 -19
- package/.github/ISSUE_TEMPLATE/optimisation_request.md +0 -23
- package/.github/ISSUE_TEMPLATE/support_request.md +0 -25
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -21
- package/.github/dependabot.yml +0 -13
- package/.github/workflows/deploy.yml +0 -56
- package/.github/workflows/npm-publish-dev.yml +0 -49
- package/.github/workflows/npm-publish.yml +0 -42
- package/.github/workflows/release.yml +0 -37
- package/.github/workflows/test.yml +0 -21
- package/.releaserc.json +0 -29
- package/CHANGELOG.md +0 -70
- package/CITATION.cff +0 -56
- package/CONTRIBUTING.md +0 -82
- package/README.md +0 -85
- package/assets/favicon.ico +0 -0
- package/assets/favicon.png +0 -0
- package/assets/logo-wide.png +0 -0
- package/assets/logo-wide.svg +0 -60
- package/assets/logo.png +0 -0
- package/assets/logo.svg +0 -36
- package/assets/robots.txt +0 -3
- package/assets/screenshot.png +0 -0
- package/biome.json +0 -34
- package/index.html +0 -24
- package/main.ts +0 -52
- package/patches/@vivjs__loaders@0.17.2.patch +0 -13
- package/python/.python-version +0 -1
- package/python/README.md +0 -14
- package/python/Untitled.ipynb +0 -40
- package/python/deno.json +0 -22
- package/python/notebooks/IDR_example.ipynb +0 -86
- package/python/notebooks/README.md +0 -35
- package/python/notebooks/create_fixture.py +0 -72
- package/python/notebooks/getting_started.ipynb +0 -217
- package/python/notebooks/mandelbrot.ipynb +0 -211
- package/python/notebooks/spatial_transformations.ipynb +0 -139
- package/python/pyproject.toml +0 -18
- package/python/src/vizarr/__init__.py +0 -10
- package/python/src/vizarr/_widget.js +0 -113
- package/python/src/vizarr/_widget.py +0 -67
- package/python/uv.lock +0 -2495
- package/src/ZarrPixelSource.ts +0 -182
- package/src/codecs/jpeg2k.ts +0 -22
- package/src/codecs/register.ts +0 -2
- package/src/components/LayerController/AcquisitionController.tsx +0 -46
- package/src/components/LayerController/AddChannelButton.tsx +0 -122
- package/src/components/LayerController/AxisOptions.tsx +0 -93
- package/src/components/LayerController/AxisSlider.tsx +0 -86
- package/src/components/LayerController/AxisSliders.tsx +0 -29
- package/src/components/LayerController/ChannelController.tsx +0 -84
- package/src/components/LayerController/ChannelOptions.tsx +0 -183
- package/src/components/LayerController/ColorPalette.tsx +0 -40
- package/src/components/LayerController/Content.tsx +0 -71
- package/src/components/LayerController/Header.tsx +0 -48
- package/src/components/LayerController/Labels.tsx +0 -73
- package/src/components/LayerController/LayerFitToViewportButton.tsx +0 -43
- package/src/components/LayerController/LayerVisibilityButton.tsx +0 -32
- package/src/components/LayerController/OpacitySlider.tsx +0 -25
- package/src/components/LayerController/index.tsx +0 -42
- package/src/components/Menu.tsx +0 -54
- package/src/components/Viewer.tsx +0 -136
- package/src/hooks.ts +0 -29
- package/src/index.tsx +0 -138
- package/src/io.ts +0 -276
- package/src/layers/grid-layer.ts +0 -214
- package/src/layers/label-layer.ts +0 -446
- package/src/layers/viv-layers.ts +0 -55
- package/src/lru-store.ts +0 -57
- package/src/ome.ts +0 -388
- package/src/state.ts +0 -202
- package/src/theme.ts +0 -80
- package/src/types.ts +0 -112
- package/src/utils.ts +0 -663
- package/tsconfig.json +0 -14
- package/vite.config.js +0 -47
package/src/layers/viv-layers.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type-complete interfaces to Viv's core layers
|
|
3
|
-
*
|
|
4
|
-
* @module
|
|
5
|
-
*/
|
|
6
|
-
import { ImageLayer as BaseImageLayer, MultiscaleImageLayer as BaseMultiscaleImageLayer } from "@hms-dbmi/viv";
|
|
7
|
-
|
|
8
|
-
import type { Layer } from "deck.gl";
|
|
9
|
-
import type { Matrix4 } from "math.gl";
|
|
10
|
-
import type { ZarrPixelSource } from "../ZarrPixelSource";
|
|
11
|
-
|
|
12
|
-
export interface BaseLayerProps {
|
|
13
|
-
id: string;
|
|
14
|
-
contrastLimits: Array<[min: number, max: number]>;
|
|
15
|
-
colors: [r: number, g: number, b: number][];
|
|
16
|
-
channelsVisible: Array<boolean>;
|
|
17
|
-
opacity: number;
|
|
18
|
-
colormap: string; // TODO: more precise
|
|
19
|
-
selections: number[][];
|
|
20
|
-
modelMatrix: Matrix4;
|
|
21
|
-
contrastLimitsRange: [min: number, max: number][];
|
|
22
|
-
pickable?: boolean;
|
|
23
|
-
onClick?: (e: Record<string, unknown>) => void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface MultiscaleImageLayerProps extends BaseLayerProps {
|
|
27
|
-
loader: Array<ZarrPixelSource>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// @ts-expect-error - Viv does faithfully implement the Layer interface, just not captured in the types
|
|
31
|
-
export class MultiscaleImageLayer
|
|
32
|
-
extends BaseMultiscaleImageLayer<string[]>
|
|
33
|
-
implements Layer<MultiscaleImageLayerProps>
|
|
34
|
-
{
|
|
35
|
-
static layerName = "VizarMultiscaleImageLayer";
|
|
36
|
-
// biome-ignore lint/complexity/noUselessConstructor: Necessary for TypeScript to get the types
|
|
37
|
-
constructor(props: MultiscaleImageLayerProps) {
|
|
38
|
-
// @ts-expect-error - Viv's types are not correct
|
|
39
|
-
super(props);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface ImageLayerProps extends BaseLayerProps {
|
|
44
|
-
loader: ZarrPixelSource;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// @ts-expect-error - Viv does faithfully implement the Layer interface, just not captured in the types
|
|
48
|
-
export class ImageLayer extends BaseImageLayer<string[]> implements Layer<ImageLayerProps> {
|
|
49
|
-
static layerName = "VizarrImageLayer";
|
|
50
|
-
// biome-ignore lint/complexity/noUselessConstructor: Necessary for TypeScript to get the types
|
|
51
|
-
constructor(props: ImageLayerProps) {
|
|
52
|
-
// @ts-expect-error - Viv's types are not correct
|
|
53
|
-
super(props);
|
|
54
|
-
}
|
|
55
|
-
}
|
package/src/lru-store.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import QuickLRU from "quick-lru";
|
|
2
|
-
|
|
3
|
-
import type * as zarr from "zarrita";
|
|
4
|
-
|
|
5
|
-
type RangeQuery =
|
|
6
|
-
| {
|
|
7
|
-
offset: number;
|
|
8
|
-
length: number;
|
|
9
|
-
}
|
|
10
|
-
| {
|
|
11
|
-
suffixLength: number;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
function normalizeKey(key: string, range?: RangeQuery) {
|
|
15
|
-
if (!range) return key;
|
|
16
|
-
if ("suffixLength" in range) return `${key}:-${range.suffixLength}`;
|
|
17
|
-
return `${key}:${range.offset}:${range.offset + range.length - 1}`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function lru<S extends zarr.Readable>(store: S, maxSize = 100) {
|
|
21
|
-
const cache = new QuickLRU<string, Promise<Uint8Array | undefined>>({ maxSize });
|
|
22
|
-
let getRange = store.getRange ? store.getRange.bind(store) : undefined;
|
|
23
|
-
function get(...args: Parameters<S["get"]>) {
|
|
24
|
-
const [key, opts] = args;
|
|
25
|
-
const cacheKey = normalizeKey(key);
|
|
26
|
-
const cached = cache.get(cacheKey);
|
|
27
|
-
if (cached) return cached;
|
|
28
|
-
const result = Promise.resolve(store.get(key, opts)).catch((err) => {
|
|
29
|
-
cache.delete(cacheKey);
|
|
30
|
-
throw err;
|
|
31
|
-
});
|
|
32
|
-
cache.set(cacheKey, result);
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
if (getRange) {
|
|
36
|
-
const _getRange = getRange;
|
|
37
|
-
getRange = (...args: Parameters<NonNullable<S["getRange"]>>) => {
|
|
38
|
-
const [key, range, opts] = args;
|
|
39
|
-
const cacheKey = normalizeKey(key, range);
|
|
40
|
-
const cached = cache.get(cacheKey);
|
|
41
|
-
if (cached) return cached;
|
|
42
|
-
const result = Promise.resolve(_getRange?.(key, range, opts)).catch((err) => {
|
|
43
|
-
cache.delete(cacheKey);
|
|
44
|
-
throw err;
|
|
45
|
-
});
|
|
46
|
-
cache.set(cacheKey, result);
|
|
47
|
-
return result;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
return new Proxy(store, {
|
|
51
|
-
get(target, prop, receiver) {
|
|
52
|
-
if (prop === "get") return get;
|
|
53
|
-
if (prop === "getRange" && getRange) return getRange;
|
|
54
|
-
return Reflect.get(target, prop, receiver);
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
}
|
package/src/ome.ts
DELETED
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
import pMap from "p-map";
|
|
2
|
-
import * as zarr from "zarrita";
|
|
3
|
-
import type { ImageLabels, ImageLayerConfig, OnClickData, SourceData } from "./state";
|
|
4
|
-
|
|
5
|
-
import { ZarrPixelSource } from "./ZarrPixelSource";
|
|
6
|
-
import * as utils from "./utils";
|
|
7
|
-
|
|
8
|
-
export async function loadWell(
|
|
9
|
-
config: ImageLayerConfig,
|
|
10
|
-
grp: zarr.Group<zarr.Readable>,
|
|
11
|
-
wellAttrs: Ome.Well,
|
|
12
|
-
): Promise<SourceData> {
|
|
13
|
-
// Can filter Well fields by URL query ?acquisition=ID
|
|
14
|
-
const acquisitionId: number | undefined = config.acquisition ? Number.parseInt(config.acquisition) : undefined;
|
|
15
|
-
let acquisitions: Ome.Acquisition[] = [];
|
|
16
|
-
|
|
17
|
-
utils.assert(wellAttrs?.images, "Well .zattrs missing images");
|
|
18
|
-
utils.assert(grp.path, "Cannot inspect zarr path to open well.");
|
|
19
|
-
|
|
20
|
-
const [row, col] = grp.path.split("/").filter(Boolean).slice(-2);
|
|
21
|
-
|
|
22
|
-
let { images } = wellAttrs;
|
|
23
|
-
|
|
24
|
-
// Do we have more than 1 Acquisition?
|
|
25
|
-
const acqIds = images.flatMap((img) => (img.acquisition ? [img.acquisition] : []));
|
|
26
|
-
|
|
27
|
-
if (acqIds.length > 1) {
|
|
28
|
-
// Need to get acquisitions metadata from parent Plate
|
|
29
|
-
const platePath = grp.path.replace(`${row}/${col}`, "");
|
|
30
|
-
const plate = await zarr.open(grp.resolve(platePath));
|
|
31
|
-
const plateAttrs = utils.resolveAttrs(plate.attrs) as { plate: Ome.Plate };
|
|
32
|
-
acquisitions = plateAttrs.plate.acquisitions ?? [];
|
|
33
|
-
// filter imagePaths by acquisition
|
|
34
|
-
if (acquisitionId && acqIds.includes(acquisitionId)) {
|
|
35
|
-
images = images.filter((img) => img.acquisition === acquisitionId);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const imgPaths = images.map((img) => img.path);
|
|
40
|
-
const cols = Math.ceil(Math.sqrt(imgPaths.length));
|
|
41
|
-
const rows = Math.ceil(imgPaths.length / cols);
|
|
42
|
-
|
|
43
|
-
// Use first image for rendering settings, resolutions etc.
|
|
44
|
-
const first = await zarr.open(grp.resolve(imgPaths[0]), { kind: "group" });
|
|
45
|
-
const imgAttrs = utils.resolveAttrs(first.attrs);
|
|
46
|
-
|
|
47
|
-
utils.assert(utils.isMultiscales(imgAttrs), "Path for image is not valid.");
|
|
48
|
-
let resolution = imgAttrs.multiscales[0].datasets[0].path;
|
|
49
|
-
|
|
50
|
-
// Create loader for every Image.
|
|
51
|
-
const promises = imgPaths.map((p) => {
|
|
52
|
-
const loc = grp.resolve(utils.join(p, resolution));
|
|
53
|
-
// @ts-expect-error - ok flag to avoid loading unused attrs
|
|
54
|
-
const arr: zarr.Array<zarr.DataType, zarr.Readable> = zarr.open(loc, { kind: "array", attrs: false });
|
|
55
|
-
return arr;
|
|
56
|
-
});
|
|
57
|
-
const data = await Promise.all(promises);
|
|
58
|
-
const axes = utils.getNgffAxes(imgAttrs.multiscales);
|
|
59
|
-
const axis_labels = utils.getNgffAxisLabels(axes);
|
|
60
|
-
|
|
61
|
-
const tileSize = utils.guessTileSize(data[0]);
|
|
62
|
-
const loaders = utils.range(rows).flatMap((row) => {
|
|
63
|
-
// filter to remove any empty row/col position
|
|
64
|
-
return utils
|
|
65
|
-
.range(cols)
|
|
66
|
-
.filter((col) => col + row * cols < data.length)
|
|
67
|
-
.map((col) => {
|
|
68
|
-
const offset = col + row * cols;
|
|
69
|
-
return {
|
|
70
|
-
name: String(offset),
|
|
71
|
-
row,
|
|
72
|
-
col,
|
|
73
|
-
loader: new ZarrPixelSource(data[offset], { labels: axis_labels, tileSize }),
|
|
74
|
-
};
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
let meta: Meta;
|
|
79
|
-
if (utils.isOmeMultiscales(imgAttrs)) {
|
|
80
|
-
meta = parseOmeroMeta(imgAttrs.omero, axes);
|
|
81
|
-
} else {
|
|
82
|
-
const lowres = loaders.at(-1);
|
|
83
|
-
utils.assert(lowres, "Expected at least one resolution, found none.");
|
|
84
|
-
meta = await defaultMeta(lowres.loader, axis_labels);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const sourceData: SourceData = {
|
|
88
|
-
loaders,
|
|
89
|
-
...meta,
|
|
90
|
-
axis_labels,
|
|
91
|
-
loader: [loaders[0].loader],
|
|
92
|
-
model_matrix: utils.parseMatrix(config.model_matrix),
|
|
93
|
-
defaults: {
|
|
94
|
-
selection: meta.defaultSelection,
|
|
95
|
-
colormap: config.colormap ?? "",
|
|
96
|
-
opacity: config.opacity ?? 1,
|
|
97
|
-
},
|
|
98
|
-
name: `Well ${row}${col}`,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (acquisitions.length > 0) {
|
|
102
|
-
// To show acquisition chooser in UI
|
|
103
|
-
sourceData.acquisitions = acquisitions;
|
|
104
|
-
sourceData.acquisitionId = acquisitionId || -1;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
sourceData.rows = rows;
|
|
108
|
-
sourceData.columns = cols;
|
|
109
|
-
sourceData.onClick = (info: OnClickData) => {
|
|
110
|
-
let gridCoord = info.gridCoord;
|
|
111
|
-
if (!gridCoord) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const { row, column } = gridCoord;
|
|
115
|
-
let imgSource = undefined;
|
|
116
|
-
if (typeof config.source === "string" && grp.path && !Number.isNaN(row) && !Number.isNaN(column)) {
|
|
117
|
-
const field = row * cols + column;
|
|
118
|
-
imgSource = utils.join(config.source, imgPaths[field]);
|
|
119
|
-
}
|
|
120
|
-
if (config.onClick) {
|
|
121
|
-
info.layer = undefined;
|
|
122
|
-
info.imageSource = imgSource;
|
|
123
|
-
config.onClick(info);
|
|
124
|
-
} else if (imgSource) {
|
|
125
|
-
window.open(`${window.location.origin + window.location.pathname}?source=${imgSource}`);
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
return sourceData;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export async function loadPlate(
|
|
133
|
-
config: ImageLayerConfig,
|
|
134
|
-
grp: zarr.Group<zarr.Readable>,
|
|
135
|
-
plateAttrs: Ome.Plate,
|
|
136
|
-
): Promise<SourceData> {
|
|
137
|
-
utils.assert(plateAttrs?.rows || plateAttrs?.columns, "Plate .zattrs missing rows, columns or wells");
|
|
138
|
-
|
|
139
|
-
const rows = plateAttrs.rows.map((row) => row.name);
|
|
140
|
-
const columns = plateAttrs.columns.map((row) => row.name);
|
|
141
|
-
|
|
142
|
-
// Fields are by index and we assume at least 1 per Well
|
|
143
|
-
const wellPaths = plateAttrs.wells.map((well) => well.path);
|
|
144
|
-
const zarrVersion = await utils.guessZarrVersion(grp);
|
|
145
|
-
|
|
146
|
-
// Use first image as proxy for others.
|
|
147
|
-
const wellAttrs = await utils.getAttrsOnly<{ well: Ome.Well }>(grp, {
|
|
148
|
-
path: wellPaths[0],
|
|
149
|
-
zarrVersion,
|
|
150
|
-
});
|
|
151
|
-
utils.assert("well" in wellAttrs, "Path for image is not valid, not a well.");
|
|
152
|
-
|
|
153
|
-
const imgPath = wellAttrs.well.images[0].path;
|
|
154
|
-
const imgAttrs = await utils.getAttrsOnly<Ome.Attrs>(grp, {
|
|
155
|
-
path: utils.join(wellPaths[0], imgPath),
|
|
156
|
-
zarrVersion,
|
|
157
|
-
});
|
|
158
|
-
utils.assert("multiscales" in imgAttrs, "Path for image is not valid.");
|
|
159
|
-
|
|
160
|
-
// Lowest resolution is the 'path' of the last 'dataset' from the first multiscales
|
|
161
|
-
const { datasets } = imgAttrs.multiscales[0];
|
|
162
|
-
const resolution = datasets[datasets.length - 1].path;
|
|
163
|
-
|
|
164
|
-
async function getImgPath(wellPath: string) {
|
|
165
|
-
const wellAttrs = await utils.getAttrsOnly<{ well: Ome.Well }>(grp, {
|
|
166
|
-
path: wellPath,
|
|
167
|
-
zarrVersion,
|
|
168
|
-
});
|
|
169
|
-
utils.assert("well" in wellAttrs, "Path for image is not valid, not a well.");
|
|
170
|
-
return utils.join(wellPath, wellAttrs.well.images[0].path);
|
|
171
|
-
}
|
|
172
|
-
const wellImagePaths = await Promise.all(wellPaths.map(getImgPath));
|
|
173
|
-
|
|
174
|
-
// Create loader for every Well. Some loaders may be undefined if Wells are missing.
|
|
175
|
-
const mapper = async ([key, path]: string[]) => {
|
|
176
|
-
// @ts-expect-error - we don't need the meta for these arrays
|
|
177
|
-
let arr: zarr.Array<zarr.DataType, zarr.Readable> = await zarr.open(grp.resolve(path), {
|
|
178
|
-
kind: "array",
|
|
179
|
-
attrs: false,
|
|
180
|
-
});
|
|
181
|
-
return [key, arr] as const;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const promises = await pMap(
|
|
185
|
-
wellImagePaths.map((p) => [p, utils.join(p, resolution)]),
|
|
186
|
-
mapper,
|
|
187
|
-
{ concurrency: 10 },
|
|
188
|
-
);
|
|
189
|
-
const data = await Promise.all(promises);
|
|
190
|
-
const axes = utils.getNgffAxes(imgAttrs.multiscales);
|
|
191
|
-
const axis_labels = utils.getNgffAxisLabels(axes);
|
|
192
|
-
const tileSize = utils.guessTileSize(data[0][1]);
|
|
193
|
-
const loaders = data.map((d) => {
|
|
194
|
-
const [row, col] = d[0].split("/");
|
|
195
|
-
return {
|
|
196
|
-
name: `${row}${col}`,
|
|
197
|
-
row: rows.indexOf(row),
|
|
198
|
-
col: columns.indexOf(col),
|
|
199
|
-
loader: new ZarrPixelSource(d[1], { labels: axis_labels, tileSize }),
|
|
200
|
-
};
|
|
201
|
-
});
|
|
202
|
-
let meta: Meta;
|
|
203
|
-
if ("omero" in imgAttrs) {
|
|
204
|
-
meta = parseOmeroMeta(imgAttrs.omero, axes);
|
|
205
|
-
} else {
|
|
206
|
-
const lowres = loaders.at(-1);
|
|
207
|
-
utils.assert(lowres, "Expected at least one resolution, found none.");
|
|
208
|
-
meta = await defaultMeta(lowres.loader, axis_labels);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Load Image to use for channel names, rendering settings, sizeZ, sizeT etc.
|
|
212
|
-
const sourceData: SourceData = {
|
|
213
|
-
loaders,
|
|
214
|
-
...meta,
|
|
215
|
-
axis_labels,
|
|
216
|
-
loader: [loaders[0].loader],
|
|
217
|
-
model_matrix: utils.parseMatrix(config.model_matrix),
|
|
218
|
-
defaults: {
|
|
219
|
-
selection: meta.defaultSelection,
|
|
220
|
-
colormap: config.colormap ?? "",
|
|
221
|
-
opacity: config.opacity ?? 1,
|
|
222
|
-
},
|
|
223
|
-
name: plateAttrs.name || "Plate",
|
|
224
|
-
rows: rows.length,
|
|
225
|
-
columns: columns.length,
|
|
226
|
-
};
|
|
227
|
-
// Us onClick from image config or Open Well in new window
|
|
228
|
-
sourceData.onClick = (info: OnClickData) => {
|
|
229
|
-
let gridCoord = info.gridCoord;
|
|
230
|
-
if (!gridCoord) {
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
const { row, column } = gridCoord;
|
|
234
|
-
let imgSource = undefined;
|
|
235
|
-
if (typeof config.source === "string" && grp.path && !Number.isNaN(row) && !Number.isNaN(column)) {
|
|
236
|
-
imgSource = utils.join(config.source, rows[row], columns[column]);
|
|
237
|
-
}
|
|
238
|
-
if (config.onClick) {
|
|
239
|
-
info.layer = undefined;
|
|
240
|
-
info.imageSource = imgSource;
|
|
241
|
-
config.onClick(info);
|
|
242
|
-
} else if (imgSource) {
|
|
243
|
-
window.open(`${window.location.origin + window.location.pathname}?source=${imgSource}`);
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
return sourceData;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export async function loadOmeMultiscales(
|
|
250
|
-
config: ImageLayerConfig,
|
|
251
|
-
grp: zarr.Group<zarr.Readable>,
|
|
252
|
-
attrs: { multiscales: Ome.Multiscale[] },
|
|
253
|
-
): Promise<SourceData> {
|
|
254
|
-
const { name, opacity = 1, colormap = "" } = config;
|
|
255
|
-
const data = await utils.loadMultiscales(grp, attrs.multiscales);
|
|
256
|
-
const axes = utils.getNgffAxes(attrs.multiscales);
|
|
257
|
-
const axis_labels = utils.getNgffAxisLabels(axes);
|
|
258
|
-
const tileSize = utils.guessTileSize(data[0]);
|
|
259
|
-
let meta: Meta;
|
|
260
|
-
if (utils.isOmeMultiscales(attrs)) {
|
|
261
|
-
meta = parseOmeroMeta(attrs.omero, axes);
|
|
262
|
-
} else {
|
|
263
|
-
const lowresArray = data.at(-1);
|
|
264
|
-
utils.assert(lowresArray, "Expected at least one resolution in multiscales, found none.");
|
|
265
|
-
const lowresSource = new ZarrPixelSource(lowresArray, { labels: axis_labels, tileSize });
|
|
266
|
-
meta = await defaultMeta(lowresSource, axis_labels);
|
|
267
|
-
}
|
|
268
|
-
const physicalSizes = utils.getPhysicalSizes(utils.resolveAttrs(attrs));
|
|
269
|
-
const loader = data.map(
|
|
270
|
-
(arr, i) =>
|
|
271
|
-
new ZarrPixelSource(arr, {
|
|
272
|
-
labels: axis_labels,
|
|
273
|
-
tileSize,
|
|
274
|
-
...(i === 0 ? { meta: { physicalSizes } } : {}),
|
|
275
|
-
}),
|
|
276
|
-
);
|
|
277
|
-
const labels = await resolveOmeLabelsFromMultiscales(grp);
|
|
278
|
-
return {
|
|
279
|
-
loader: loader,
|
|
280
|
-
axis_labels,
|
|
281
|
-
model_matrix: config.model_matrix
|
|
282
|
-
? utils.parseMatrix(config.model_matrix)
|
|
283
|
-
: utils.coordinateTransformationsToMatrix(attrs.multiscales),
|
|
284
|
-
defaults: {
|
|
285
|
-
selection: meta.defaultSelection,
|
|
286
|
-
colormap,
|
|
287
|
-
opacity,
|
|
288
|
-
},
|
|
289
|
-
...meta,
|
|
290
|
-
name: meta.name ?? name,
|
|
291
|
-
labels: await Promise.all(labels.map((name) => loadOmeImageLabel(grp.resolve("labels"), name))),
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async function loadOmeImageLabel(root: zarr.Location<zarr.Readable>, name: string): Promise<ImageLabels[number]> {
|
|
296
|
-
const grp = await zarr.open(root.resolve(name), { kind: "group" });
|
|
297
|
-
const attrs = utils.resolveAttrs(grp.attrs);
|
|
298
|
-
utils.assert(utils.isOmeImageLabel(attrs), "No 'image-label' metadata.");
|
|
299
|
-
const data = await utils.loadMultiscales(grp, attrs.multiscales);
|
|
300
|
-
const baseResolution = data.at(0);
|
|
301
|
-
utils.assert(baseResolution, "No base resolution found for multiscale labels.");
|
|
302
|
-
const tileSize = utils.guessTileSize(baseResolution);
|
|
303
|
-
const axes = utils.getNgffAxes(attrs.multiscales);
|
|
304
|
-
const labels = utils.getNgffAxisLabels(axes);
|
|
305
|
-
const colors = (attrs["image-label"].colors ?? []).map((d) => ({ labelValue: d["label-value"], rgba: d.rgba }));
|
|
306
|
-
return {
|
|
307
|
-
name,
|
|
308
|
-
modelMatrix: utils.coordinateTransformationsToMatrix(attrs.multiscales),
|
|
309
|
-
loader: data.map((arr) => new ZarrPixelSource(arr, { labels, tileSize })),
|
|
310
|
-
colors: colors.length > 0 ? colors : undefined,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async function resolveOmeLabelsFromMultiscales(grp: zarr.Group<zarr.Readable>): Promise<Array<string>> {
|
|
315
|
-
return zarr
|
|
316
|
-
.open(grp.resolve("labels"), { kind: "group" })
|
|
317
|
-
.then(({ attrs }) => (utils.resolveAttrs(attrs).labels ?? []) as Array<string>)
|
|
318
|
-
.catch((e) => {
|
|
319
|
-
utils.rethrowUnless(e, zarr.NodeNotFoundError);
|
|
320
|
-
return [];
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
type Meta = {
|
|
325
|
-
name: string | undefined;
|
|
326
|
-
names: Array<string>;
|
|
327
|
-
colors: Array<string>;
|
|
328
|
-
contrast_limits: Array<[number, number] | undefined>;
|
|
329
|
-
visibilities: Array<boolean>;
|
|
330
|
-
channel_axis: number | null;
|
|
331
|
-
defaultSelection: Array<number>;
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
async function defaultMeta(loader: ZarrPixelSource, axis_labels: string[]): Promise<Meta> {
|
|
335
|
-
const channel_axis = axis_labels.indexOf("c");
|
|
336
|
-
const channel_count = channel_axis === -1 ? 1 : loader.shape[channel_axis];
|
|
337
|
-
const visibilities = utils.getDefaultVisibilities(channel_count);
|
|
338
|
-
const contrast_limits = await utils.calcConstrastLimits(loader, channel_axis, visibilities);
|
|
339
|
-
const colors = utils.getDefaultColors(channel_count, visibilities);
|
|
340
|
-
return {
|
|
341
|
-
name: "Image",
|
|
342
|
-
names: utils.range(channel_count).map((i) => `channel_${i}`),
|
|
343
|
-
colors,
|
|
344
|
-
contrast_limits,
|
|
345
|
-
visibilities,
|
|
346
|
-
channel_axis: axis_labels.includes("c") ? axis_labels.indexOf("c") : null,
|
|
347
|
-
defaultSelection: axis_labels.map(() => 0),
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function parseOmeroMeta({ rdefs, channels, name }: Ome.Omero, axes: Ome.Axis[]): Meta {
|
|
352
|
-
const t = rdefs?.defaultT ?? 0;
|
|
353
|
-
const z = rdefs?.defaultZ ?? 0;
|
|
354
|
-
const greyscale = rdefs?.model === "greyscale";
|
|
355
|
-
|
|
356
|
-
const colors: string[] = [];
|
|
357
|
-
const contrast_limits: [min: number, max: number][] = [];
|
|
358
|
-
const visibilities: boolean[] = [];
|
|
359
|
-
const names: string[] = [];
|
|
360
|
-
|
|
361
|
-
channels.forEach((c, index) => {
|
|
362
|
-
colors.push(c.color);
|
|
363
|
-
contrast_limits.push([c.window.start, c.window.end]);
|
|
364
|
-
visibilities.push(c.active);
|
|
365
|
-
names.push(c.label || `${index}`);
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
if (greyscale && colors.length === 1) {
|
|
369
|
-
colors[0] = "FFFFFF";
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const defaultSelection = axes.map((axis) => {
|
|
373
|
-
if (axis.type === "time") return t;
|
|
374
|
-
if (axis.name === "z") return z;
|
|
375
|
-
return 0;
|
|
376
|
-
});
|
|
377
|
-
const channel_axis = axes.findIndex((axis) => axis.type === "channel");
|
|
378
|
-
|
|
379
|
-
return {
|
|
380
|
-
name,
|
|
381
|
-
names,
|
|
382
|
-
colors,
|
|
383
|
-
contrast_limits,
|
|
384
|
-
visibilities,
|
|
385
|
-
channel_axis,
|
|
386
|
-
defaultSelection,
|
|
387
|
-
};
|
|
388
|
-
}
|
package/src/state.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { type Atom, atom } from "jotai";
|
|
2
|
-
import { atomFamily, splitAtom, waitForAll } from "jotai/utils";
|
|
3
|
-
import { RedirectError, rethrowUnless } from "./utils";
|
|
4
|
-
|
|
5
|
-
import type { Deck, Layer } from "deck.gl";
|
|
6
|
-
import type { PrimitiveAtom } from "jotai";
|
|
7
|
-
import type { AtomFamily } from "jotai/vanilla/utils/atomFamily";
|
|
8
|
-
import type { Matrix4 } from "math.gl";
|
|
9
|
-
import type * as zarr from "zarrita";
|
|
10
|
-
import type { ZarrPixelSource } from "./ZarrPixelSource";
|
|
11
|
-
import { initLayerStateFromSource } from "./io";
|
|
12
|
-
|
|
13
|
-
import { GridLayer, type GridLayerProps, type GridLoader } from "./layers/grid-layer";
|
|
14
|
-
import { LabelLayer, type LabelLayerProps, type OmeColor } from "./layers/label-layer";
|
|
15
|
-
import {
|
|
16
|
-
ImageLayer,
|
|
17
|
-
type ImageLayerProps,
|
|
18
|
-
MultiscaleImageLayer,
|
|
19
|
-
type MultiscaleImageLayerProps,
|
|
20
|
-
} from "./layers/viv-layers";
|
|
21
|
-
|
|
22
|
-
export interface ViewState {
|
|
23
|
-
zoom: number;
|
|
24
|
-
target: [number, number];
|
|
25
|
-
width?: number;
|
|
26
|
-
height?: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface BaseConfig {
|
|
30
|
-
source: string | zarr.Readable;
|
|
31
|
-
axis_labels?: string[];
|
|
32
|
-
name?: string;
|
|
33
|
-
colormap?: string;
|
|
34
|
-
opacity?: number;
|
|
35
|
-
acquisition?: string;
|
|
36
|
-
model_matrix?: string | number[];
|
|
37
|
-
onClick?: (e: unknown) => void;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface MultichannelConfig extends BaseConfig {
|
|
41
|
-
colors?: string[];
|
|
42
|
-
channel_axis?: number;
|
|
43
|
-
contrast_limits?: [min: number, max: number][];
|
|
44
|
-
names?: string[];
|
|
45
|
-
visibilities?: boolean[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface SingleChannelConfig extends BaseConfig {
|
|
49
|
-
color?: string;
|
|
50
|
-
contrast_limits?: [min: number, max: number];
|
|
51
|
-
visibility?: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export type ImageLayerConfig = MultichannelConfig | SingleChannelConfig;
|
|
55
|
-
|
|
56
|
-
export type OnClickData = Record<string, unknown> & {
|
|
57
|
-
gridCoord?: { row: number; column: number };
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export type ImageLabels = Array<{
|
|
61
|
-
name: string;
|
|
62
|
-
loader: ZarrPixelSource[];
|
|
63
|
-
modelMatrix: Matrix4;
|
|
64
|
-
colors?: ReadonlyArray<OmeColor>;
|
|
65
|
-
}>;
|
|
66
|
-
|
|
67
|
-
export type SourceData = {
|
|
68
|
-
loader: ZarrPixelSource[];
|
|
69
|
-
loaders?: GridLoader[]; // for OME plates
|
|
70
|
-
rows?: number;
|
|
71
|
-
columns?: number;
|
|
72
|
-
acquisitions?: Ome.Acquisition[];
|
|
73
|
-
acquisitionId?: number;
|
|
74
|
-
name?: string;
|
|
75
|
-
channel_axis: number | null;
|
|
76
|
-
colors: string[];
|
|
77
|
-
names: string[];
|
|
78
|
-
contrast_limits: ([min: number, max: number] | undefined)[];
|
|
79
|
-
visibilities: boolean[];
|
|
80
|
-
defaults: {
|
|
81
|
-
selection: number[];
|
|
82
|
-
colormap: string;
|
|
83
|
-
opacity: number;
|
|
84
|
-
};
|
|
85
|
-
model_matrix: Matrix4;
|
|
86
|
-
axis_labels: string[];
|
|
87
|
-
onClick?: (e: OnClickData) => void;
|
|
88
|
-
labels?: ImageLabels;
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
type LayerType = "image" | "multiscale" | "grid";
|
|
92
|
-
type LayerPropsMap = {
|
|
93
|
-
image: ImageLayerProps;
|
|
94
|
-
multiscale: MultiscaleImageLayerProps;
|
|
95
|
-
grid: GridLayerProps;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export type LayerState<T extends LayerType = LayerType> = {
|
|
99
|
-
kind: T;
|
|
100
|
-
layerProps: LayerPropsMap[T];
|
|
101
|
-
on: boolean;
|
|
102
|
-
labels?: Array<{
|
|
103
|
-
layerProps: Omit<LabelLayerProps, "selection">;
|
|
104
|
-
on: boolean;
|
|
105
|
-
transformSourceSelection: (sourceSelection: Array<number>) => Array<number>;
|
|
106
|
-
}>;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
type WithId<T> = T & { id: string };
|
|
110
|
-
|
|
111
|
-
export const viewStateAtom = atom<ViewState | null>(null);
|
|
112
|
-
export const sourceErrorAtom = atom<string | null>(null);
|
|
113
|
-
|
|
114
|
-
export interface Redirect {
|
|
115
|
-
url: string;
|
|
116
|
-
message: string;
|
|
117
|
-
}
|
|
118
|
-
export const redirectObjAtom = atom<Redirect | null>(null);
|
|
119
|
-
|
|
120
|
-
export const viewportAtom = atom<Deck | null>(null);
|
|
121
|
-
|
|
122
|
-
export const sourceInfoAtom = atom<WithId<SourceData>[]>([]);
|
|
123
|
-
|
|
124
|
-
export const addImageAtom = atom(null, async (get, set, config: ImageLayerConfig) => {
|
|
125
|
-
const { createSourceData } = await import("./io");
|
|
126
|
-
const id = Math.random().toString(36).slice(2);
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const sourceData = await createSourceData(config);
|
|
130
|
-
const prevSourceInfo = get(sourceInfoAtom);
|
|
131
|
-
if (!sourceData.name) {
|
|
132
|
-
sourceData.name = `image_${Object.keys(prevSourceInfo).length}`;
|
|
133
|
-
}
|
|
134
|
-
set(sourceInfoAtom, [...prevSourceInfo, { id, ...sourceData }]);
|
|
135
|
-
} catch (err) {
|
|
136
|
-
rethrowUnless(err, Error);
|
|
137
|
-
if (err instanceof RedirectError) {
|
|
138
|
-
set(redirectObjAtom, { message: err.message, url: err.url });
|
|
139
|
-
} else {
|
|
140
|
-
set(sourceErrorAtom, err.message);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
export const sourceInfoAtomAtoms = splitAtom(sourceInfoAtom);
|
|
146
|
-
|
|
147
|
-
export const layerFamilyAtom: AtomFamily<WithId<SourceData>, PrimitiveAtom<WithId<LayerState>>> = atomFamily(
|
|
148
|
-
(param: WithId<SourceData>) => atom({ ...initLayerStateFromSource(param), id: param.id }),
|
|
149
|
-
(a, b) => a.id === b.id,
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
export type VizarrLayer =
|
|
153
|
-
| Layer<MultiscaleImageLayerProps>
|
|
154
|
-
| Layer<ImageLayerProps>
|
|
155
|
-
| Layer<GridLayerProps>
|
|
156
|
-
| Layer<LabelLayerProps>;
|
|
157
|
-
|
|
158
|
-
const LayerConstructors = {
|
|
159
|
-
image: ImageLayer,
|
|
160
|
-
multiscale: MultiscaleImageLayer,
|
|
161
|
-
grid: GridLayer,
|
|
162
|
-
} as const;
|
|
163
|
-
|
|
164
|
-
const layerInstanceFamily = atomFamily((a: Atom<LayerState>) =>
|
|
165
|
-
atom((get) => {
|
|
166
|
-
const { on, layerProps, kind } = get(a);
|
|
167
|
-
if (!on) {
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
170
|
-
const Layer = LayerConstructors[kind];
|
|
171
|
-
// @ts-expect-error - TS can't resolve that Layer & layerProps bound together
|
|
172
|
-
return new Layer({ ...layerProps, pickable: layerProps.pickable ?? false }) as VizarrLayer;
|
|
173
|
-
}),
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
const imageLabelsIstanceFamily = atomFamily((a: Atom<LayerState>) =>
|
|
177
|
-
atom((get) => {
|
|
178
|
-
const { on, labels, layerProps } = get(a);
|
|
179
|
-
if (!on || !labels) {
|
|
180
|
-
return [];
|
|
181
|
-
}
|
|
182
|
-
return labels.map((label) =>
|
|
183
|
-
label.on
|
|
184
|
-
? new LabelLayer({
|
|
185
|
-
...label.layerProps,
|
|
186
|
-
selection: label.transformSourceSelection(layerProps.selections[0]),
|
|
187
|
-
pickable: true,
|
|
188
|
-
})
|
|
189
|
-
: null,
|
|
190
|
-
);
|
|
191
|
-
}),
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
export const layerAtoms = atom((get) => {
|
|
195
|
-
const layerAtoms = [];
|
|
196
|
-
for (const sourceAtom of get(sourceInfoAtomAtoms)) {
|
|
197
|
-
const layerStateAtom = layerFamilyAtom(get(sourceAtom));
|
|
198
|
-
layerAtoms.push(layerInstanceFamily(layerStateAtom));
|
|
199
|
-
layerAtoms.push(imageLabelsIstanceFamily(layerStateAtom));
|
|
200
|
-
}
|
|
201
|
-
return get(waitForAll(layerAtoms)).flat();
|
|
202
|
-
});
|