@biongff/vizarr 1.1.1 → 1.3.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 (115) hide show
  1. package/LICENSE +1 -0
  2. package/dist/biongff-vizarr.cjs.js +1 -0
  3. package/dist/biongff-vizarr.es.js +9 -0
  4. package/dist/blosc-CeItQ6qj.cjs +17 -0
  5. package/dist/blosc-DaK8KnI4.js +719 -0
  6. package/dist/chunk-INHXZS53-DiyuLb3Z.js +14 -0
  7. package/dist/chunk-INHXZS53-z3BpFH8p.cjs +1 -0
  8. package/dist/deflate-DzrO3E-1.cjs +1 -0
  9. package/dist/deflate-Q4iCMMBa.js +10 -0
  10. package/dist/index-3CiumG5V.js +53876 -0
  11. package/dist/index-Ckhcz4QQ.cjs +3815 -0
  12. package/dist/index.d.ts +119 -0
  13. package/dist/jpeg-BdEIe3QG.js +533 -0
  14. package/dist/jpeg-DFzBPcur.cjs +1 -0
  15. package/dist/jpeg2k-1tnzEScI.js +24 -0
  16. package/dist/jpeg2k-CcF6vCxI.cjs +1 -0
  17. package/dist/lerc-35FtXNGP.cjs +1 -0
  18. package/dist/lerc-B6qlOBE2.js +1039 -0
  19. package/dist/lz4-CssV0LoA.js +643 -0
  20. package/dist/lz4-PFaIsPAh.cjs +15 -0
  21. package/dist/lzw-CeXp5T9s.js +84 -0
  22. package/dist/lzw-DnHvgKbO.cjs +1 -0
  23. package/dist/packbits-CAZoMCQq.js +24 -0
  24. package/dist/packbits-DwdUAwAt.cjs +1 -0
  25. package/dist/pako.esm-Bx5X36Wo.js +1074 -0
  26. package/dist/pako.esm-DZC2QrbJ.cjs +1 -0
  27. package/dist/raw-BB450wdT.cjs +1 -0
  28. package/dist/raw-oTi55tWo.js +9 -0
  29. package/dist/ref-Bd0nPTvp.js +196 -0
  30. package/dist/ref-CIxavIn6.cjs +1 -0
  31. package/dist/webimage-66a9vFf5.cjs +1 -0
  32. package/dist/webimage-CKRNzqyG.js +19 -0
  33. package/dist/zstd-Cqadn9HA.js +610 -0
  34. package/dist/zstd-_xUhkGOV.cjs +15 -0
  35. package/package.json +35 -21
  36. package/.gitattributes +0 -1
  37. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -32
  38. package/.github/ISSUE_TEMPLATE/documentation_update.md +0 -16
  39. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  40. package/.github/ISSUE_TEMPLATE/maintenance.md +0 -19
  41. package/.github/ISSUE_TEMPLATE/optimisation_request.md +0 -23
  42. package/.github/ISSUE_TEMPLATE/support_request.md +0 -25
  43. package/.github/PULL_REQUEST_TEMPLATE.md +0 -21
  44. package/.github/dependabot.yml +0 -13
  45. package/.github/workflows/deploy.yml +0 -56
  46. package/.github/workflows/npm-publish-dev.yml +0 -49
  47. package/.github/workflows/npm-publish.yml +0 -42
  48. package/.github/workflows/release.yml +0 -37
  49. package/.github/workflows/test.yml +0 -21
  50. package/.releaserc.json +0 -29
  51. package/CHANGELOG.md +0 -70
  52. package/CITATION.cff +0 -56
  53. package/CONTRIBUTING.md +0 -82
  54. package/README.md +0 -85
  55. package/assets/favicon.ico +0 -0
  56. package/assets/favicon.png +0 -0
  57. package/assets/logo-wide.png +0 -0
  58. package/assets/logo-wide.svg +0 -60
  59. package/assets/logo.png +0 -0
  60. package/assets/logo.svg +0 -36
  61. package/assets/robots.txt +0 -3
  62. package/assets/screenshot.png +0 -0
  63. package/biome.json +0 -34
  64. package/index.html +0 -24
  65. package/main.ts +0 -52
  66. package/patches/@vivjs__loaders@0.17.2.patch +0 -13
  67. package/python/.python-version +0 -1
  68. package/python/README.md +0 -14
  69. package/python/Untitled.ipynb +0 -40
  70. package/python/deno.json +0 -22
  71. package/python/notebooks/IDR_example.ipynb +0 -86
  72. package/python/notebooks/README.md +0 -35
  73. package/python/notebooks/create_fixture.py +0 -72
  74. package/python/notebooks/getting_started.ipynb +0 -217
  75. package/python/notebooks/mandelbrot.ipynb +0 -211
  76. package/python/notebooks/spatial_transformations.ipynb +0 -139
  77. package/python/pyproject.toml +0 -18
  78. package/python/src/vizarr/__init__.py +0 -10
  79. package/python/src/vizarr/_widget.js +0 -113
  80. package/python/src/vizarr/_widget.py +0 -67
  81. package/python/uv.lock +0 -2495
  82. package/src/ZarrPixelSource.ts +0 -182
  83. package/src/codecs/jpeg2k.ts +0 -22
  84. package/src/codecs/register.ts +0 -2
  85. package/src/components/LayerController/AcquisitionController.tsx +0 -46
  86. package/src/components/LayerController/AddChannelButton.tsx +0 -122
  87. package/src/components/LayerController/AxisOptions.tsx +0 -93
  88. package/src/components/LayerController/AxisSlider.tsx +0 -86
  89. package/src/components/LayerController/AxisSliders.tsx +0 -29
  90. package/src/components/LayerController/ChannelController.tsx +0 -84
  91. package/src/components/LayerController/ChannelOptions.tsx +0 -183
  92. package/src/components/LayerController/ColorPalette.tsx +0 -40
  93. package/src/components/LayerController/Content.tsx +0 -71
  94. package/src/components/LayerController/Header.tsx +0 -48
  95. package/src/components/LayerController/Labels.tsx +0 -73
  96. package/src/components/LayerController/LayerFitToViewportButton.tsx +0 -43
  97. package/src/components/LayerController/LayerVisibilityButton.tsx +0 -32
  98. package/src/components/LayerController/OpacitySlider.tsx +0 -25
  99. package/src/components/LayerController/index.tsx +0 -42
  100. package/src/components/Menu.tsx +0 -54
  101. package/src/components/Viewer.tsx +0 -136
  102. package/src/hooks.ts +0 -29
  103. package/src/index.tsx +0 -138
  104. package/src/io.ts +0 -276
  105. package/src/layers/grid-layer.ts +0 -214
  106. package/src/layers/label-layer.ts +0 -446
  107. package/src/layers/viv-layers.ts +0 -55
  108. package/src/lru-store.ts +0 -57
  109. package/src/ome.ts +0 -388
  110. package/src/state.ts +0 -202
  111. package/src/theme.ts +0 -80
  112. package/src/types.ts +0 -112
  113. package/src/utils.ts +0 -663
  114. package/tsconfig.json +0 -14
  115. package/vite.config.js +0 -47
@@ -1,182 +0,0 @@
1
- import * as zarr from "zarrita";
2
- import { assert } from "./utils";
3
-
4
- import type * as viv from "@vivjs/types";
5
-
6
- // TODO: Export from top-level zarrita
7
- type Slice = ReturnType<typeof zarr.slice>;
8
-
9
- const X_AXIS_NAME = "x";
10
- const Y_AXIS_NAME = "y";
11
- const RGBA_CHANNEL_AXIS_NAME = "_c";
12
-
13
- type VivPixelData = {
14
- data: zarr.TypedArray<Lowercase<viv.SupportedDtype>>;
15
- width: number;
16
- height: number;
17
- };
18
-
19
- export class ZarrPixelSource implements viv.PixelSource<Array<string>> {
20
- readonly labels: viv.Labels<Array<string>>;
21
- readonly tileSize: number;
22
- readonly dtype: viv.SupportedDtype;
23
- readonly meta?: viv.PixelSourceMeta;
24
- readonly #arr: zarr.Array<zarr.NumberDataType | zarr.BigintDataType, zarr.Readable>;
25
- readonly #transform: (
26
- arr: zarr.TypedArray<zarr.NumberDataType | zarr.BigintDataType>,
27
- ) => zarr.TypedArray<Lowercase<viv.SupportedDtype>>;
28
-
29
- #pendingId: undefined | number = undefined;
30
- #pending: Array<{
31
- resolve: (data: VivPixelData) => void;
32
- reject: (err: unknown) => void;
33
- request: {
34
- selection: Array<number | zarr.Slice>;
35
- signal?: AbortSignal;
36
- };
37
- }> = [];
38
-
39
- constructor(
40
- arr: zarr.Array<zarr.DataType, zarr.Readable>,
41
- options: { labels: viv.Labels<Array<string>>; tileSize: number; meta?: viv.PixelSourceMeta },
42
- ) {
43
- assert(arr.is("number") || arr.is("bigint"), `Unsupported viv dtype: ${arr.dtype}`);
44
- this.#arr = arr;
45
- this.labels = options.labels;
46
- this.tileSize = options.tileSize;
47
- this.meta = options.meta;
48
- /**
49
- * Some `zarrita` data types are not supported by Viv and require casting.
50
- *
51
- * Note how the casted type in the transform function is type-cast to `zarr.TypedArray<typeof arr.dtype>`.
52
- * This ensures that the function body is correct based on whatever type narrowing we do in the if/else
53
- * blocks based on dtype.
54
- *
55
- * TODO: Maybe we should add a console warning?
56
- */
57
- if (arr.dtype === "uint64" || arr.dtype === "int64") {
58
- this.dtype = "Uint32";
59
- this.#transform = (x) => Uint32Array.from(x as zarr.TypedArray<typeof arr.dtype>, (bint) => Number(bint));
60
- } else if (arr.dtype === "float16") {
61
- this.dtype = "Float32";
62
- this.#transform = (x) => new Float32Array(x as zarr.TypedArray<typeof arr.dtype>);
63
- } else {
64
- this.dtype = capitalize(arr.dtype);
65
- this.#transform = (x) => x as zarr.TypedArray<typeof arr.dtype>;
66
- }
67
- }
68
-
69
- get #width() {
70
- const lastIndex = this.shape.length - 1;
71
- return this.shape[this.labels.indexOf("c") === lastIndex ? lastIndex - 1 : lastIndex];
72
- }
73
-
74
- get #height() {
75
- const lastIndex = this.shape.length - 1;
76
- return this.shape[this.labels.indexOf("c") === lastIndex ? lastIndex - 2 : lastIndex - 1];
77
- }
78
-
79
- get shape() {
80
- return this.#arr.shape;
81
- }
82
-
83
- async getRaster(options: {
84
- selection: viv.PixelSourceSelection<Array<string>> | Array<number>;
85
- signal?: AbortSignal;
86
- }): Promise<viv.PixelData> {
87
- const { selection, signal } = options;
88
- return this.#fetchData({
89
- selection: buildZarrSelection(selection, {
90
- labels: this.labels,
91
- slices: { x: zarr.slice(null), y: zarr.slice(null) },
92
- }),
93
- signal,
94
- });
95
- }
96
-
97
- onTileError(_err: unknown): void {
98
- // no-op
99
- }
100
-
101
- async getTile(options: {
102
- x: number;
103
- y: number;
104
- selection: viv.PixelSourceSelection<Array<string>> | Array<number>;
105
- signal?: AbortSignal;
106
- }): Promise<viv.PixelData> {
107
- const { x, y, selection, signal } = options;
108
- return this.#fetchData({
109
- selection: buildZarrSelection(selection, {
110
- labels: this.labels,
111
- slices: {
112
- x: zarr.slice(x * this.tileSize, Math.min((x + 1) * this.tileSize, this.#width)),
113
- y: zarr.slice(y * this.tileSize, Math.min((y + 1) * this.tileSize, this.#height)),
114
- },
115
- }),
116
- signal,
117
- });
118
- }
119
-
120
- async #fetchData(request: { selection: Array<number | Slice>; signal?: AbortSignal }): Promise<viv.PixelData> {
121
- const { promise, resolve, reject } = Promise.withResolvers<VivPixelData>();
122
- this.#pending.push({ request, resolve, reject });
123
- this.#pendingId = this.#pendingId ?? requestAnimationFrame(() => this.#fetchPending());
124
- // @ts-expect-error - The missing generic ArrayBuffer type from Viv makes VivPixelData and viv.PixelData incompatible, even though they are.
125
- return promise;
126
- }
127
-
128
- /**
129
- * Fetch a pending batch of requests together and resolve independently.
130
- *
131
- * TODO: There could be more optimizations (e.g., multi-get)
132
- */
133
- async #fetchPending() {
134
- for (const { request, resolve, reject } of this.#pending) {
135
- zarr
136
- .get(this.#arr, request.selection, { opts: { signal: request.signal } })
137
- .then(({ data, shape }) => {
138
- resolve({
139
- data: this.#transform(data),
140
- width: shape[1],
141
- height: shape[0],
142
- });
143
- })
144
- .catch((error) => reject(error));
145
- }
146
- this.#pendingId = undefined;
147
- this.#pending = [];
148
- }
149
- }
150
-
151
- function buildZarrSelection(
152
- baseSelection: Record<string, number> | Array<number>,
153
- options: {
154
- labels: string[];
155
- slices: { x: zarr.Slice; y: zarr.Slice };
156
- },
157
- ): Array<Slice | number> {
158
- const { labels, slices } = options;
159
- let selection: Array<Slice | number>;
160
- if (Array.isArray(baseSelection)) {
161
- // shallow copy
162
- selection = [...baseSelection];
163
- } else {
164
- // initialize with zeros
165
- selection = Array.from({ length: labels.length }, () => 0);
166
- // fill in the selection
167
- for (const [key, idx] of Object.entries(baseSelection)) {
168
- selection[labels.indexOf(key)] = idx;
169
- }
170
- }
171
- selection[labels.indexOf(X_AXIS_NAME)] = slices.x;
172
- selection[labels.indexOf(Y_AXIS_NAME)] = slices.y;
173
- if (RGBA_CHANNEL_AXIS_NAME in labels) {
174
- selection[labels.indexOf(RGBA_CHANNEL_AXIS_NAME)] = zarr.slice(null);
175
- }
176
- return selection;
177
- }
178
-
179
- function capitalize<T extends string>(s: T): Capitalize<T> {
180
- // @ts-expect-error - TypeScript can't verify that the return type is correct
181
- return s[0].toUpperCase() + s.slice(1);
182
- }
@@ -1,22 +0,0 @@
1
- // TODO: Remove dependency on CDN for pdf.js source. Could use git submodule or
2
- // `deno bundle` since the pdf.js dist does not include ESM source code (only UMD or CJS).
3
-
4
- // @ts-ignore
5
- import { JpxImage } from "https://cdn.jsdelivr.net/gh/mozilla/pdf.js@30bd5f0/src/core/jpx.js";
6
-
7
- export default class Jpeg2k {
8
- kind = "bytes_to_bytes" as const;
9
- static codecId = "jpeg2k";
10
- static fromConfig(): Jpeg2k {
11
- return new Jpeg2k();
12
- }
13
- encode(_: Uint8Array): never {
14
- throw new Error("encode not implemented");
15
- }
16
- async decode(data: Uint8Array): Promise<Uint8Array> {
17
- const img = new JpxImage();
18
- img.failOnCorruptedImage = true;
19
- img.parse(data);
20
- return img.tiles[0].items;
21
- }
22
- }
@@ -1,2 +0,0 @@
1
- import { registry } from "zarrita";
2
- registry.set("jpeg2k", () => import("./jpeg2k").then((m) => m.default));
@@ -1,46 +0,0 @@
1
- import { Grid, NativeSelect } from "@mui/material";
2
- import React from "react";
3
- import type { ChangeEvent } from "react";
4
- import { useSourceData } from "../../hooks";
5
-
6
- function AcquisitionController() {
7
- const [sourceData] = useSourceData();
8
- const { acquisitionId, acquisitions } = sourceData;
9
-
10
- if (!acquisitions) {
11
- return null;
12
- }
13
-
14
- const handleSelectionChange = (event: ChangeEvent<HTMLSelectElement>) => {
15
- let value = event.target.value;
16
- const url = new URL(window.location.href);
17
- if (value === "-1") {
18
- url.searchParams.delete("acquisition");
19
- } else {
20
- url.searchParams.set("acquisition", value);
21
- }
22
- window.location.href = decodeURIComponent(url.href);
23
- };
24
-
25
- return (
26
- <>
27
- <Grid>
28
- <NativeSelect fullWidth style={{ fontSize: "0.7em" }} onChange={handleSelectionChange} value={acquisitionId}>
29
- <option value="-1" key="-1">
30
- Filter by Acquisition
31
- </option>
32
- {acquisitions.map((acq) => {
33
- acq = acq as Ome.Acquisition;
34
- return (
35
- <option value={acq.id} key={acq.id}>
36
- Acquisition: {acq.name}
37
- </option>
38
- );
39
- })}
40
- </NativeSelect>
41
- </Grid>
42
- </>
43
- );
44
- }
45
-
46
- export default AcquisitionController;
@@ -1,122 +0,0 @@
1
- import { Add } from "@mui/icons-material";
2
- import { Divider, IconButton, NativeSelect, Paper, Popover, Typography } from "@mui/material";
3
- import React, { useState } from "react";
4
- import type { ChangeEvent, MouseEvent } from "react";
5
-
6
- import { useLayerState, useSourceData } from "../../hooks";
7
- import { MAX_CHANNELS, calcDataRange, hexToRGB, resolveLoaderFromLayerProps } from "../../utils";
8
-
9
- function AddChannelButton() {
10
- const [source, setSource] = useSourceData();
11
- const [layer, setLayer] = useLayerState();
12
- const [anchorEl, setAnchorEl] = useState<null | Element>(null);
13
-
14
- const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
15
- setAnchorEl(event.currentTarget);
16
- };
17
-
18
- const handleClose = () => {
19
- setAnchorEl(null);
20
- };
21
-
22
- const handleChange = async (event: ChangeEvent<HTMLSelectElement>) => {
23
- handleClose();
24
- const channelIndex = +event.target.value;
25
- const channelSelection = [...source.defaults.selection];
26
- if (source.channel_axis) {
27
- channelSelection[source.channel_axis] = channelIndex;
28
- }
29
-
30
- // cacluate contrast limits if missing from source;
31
- let lim: [min: number, max: number];
32
- if (source.contrast_limits[channelIndex]) {
33
- lim = source.contrast_limits[channelIndex] as [number, number];
34
- } else {
35
- const loader = resolveLoaderFromLayerProps(layer.layerProps);
36
- const lowres = Array.isArray(loader) ? loader[loader.length - 1] : loader;
37
- lim = await calcDataRange(lowres, channelSelection);
38
- // Update source data with newly calculated limit
39
- setSource((prev) => {
40
- const clims = [...prev.contrast_limits];
41
- clims[channelIndex] = lim;
42
- return { ...prev, contrast_limits: clims };
43
- });
44
- }
45
-
46
- setLayer((prev) => {
47
- const { layerProps } = prev;
48
- const selections = [...layerProps.selections, channelSelection];
49
- const colors = [...layerProps.colors, hexToRGB(source.colors[channelIndex])];
50
- const contrastLimits = [...layerProps.contrastLimitsRange, lim];
51
- const channelsVisible = [...layerProps.channelsVisible, true];
52
- return {
53
- ...prev,
54
- layerProps: {
55
- ...layerProps,
56
- selections,
57
- colors,
58
- contrastLimits,
59
- contrastLimitsRange: [...contrastLimits],
60
- channelsVisible,
61
- },
62
- };
63
- });
64
- };
65
-
66
- const { names } = source;
67
- const open = Boolean(anchorEl);
68
- const id = open ? `layer-${source.id}-add-channel` : undefined;
69
- return (
70
- <>
71
- <IconButton
72
- onClick={handleClick}
73
- aria-describedby={id}
74
- style={{
75
- backgroundColor: "transparent",
76
- padding: 0,
77
- zIndex: 2,
78
- cursor: "pointer",
79
- }}
80
- disabled={layer.layerProps.selections.length === MAX_CHANNELS}
81
- >
82
- <Add />
83
- </IconButton>
84
- <Popover
85
- id={id}
86
- open={open}
87
- anchorEl={anchorEl}
88
- onClose={handleClose}
89
- anchorOrigin={{
90
- vertical: "bottom",
91
- horizontal: "left",
92
- }}
93
- transformOrigin={{
94
- vertical: "top",
95
- horizontal: "left",
96
- }}
97
- >
98
- <Paper style={{ padding: "0px 4px", marginBottom: 4, width: "8em" }}>
99
- <Typography variant="caption">selection: </Typography>
100
- <Divider />
101
- <NativeSelect
102
- fullWidth
103
- style={{ fontSize: "0.7em" }}
104
- id={`layer-${source.id}-channel-select`}
105
- onChange={handleChange}
106
- >
107
- <option aria-label="None" value="">
108
- None
109
- </option>
110
- {names.map((name, i) => (
111
- <option value={i} key={name}>
112
- {name}
113
- </option>
114
- ))}
115
- </NativeSelect>
116
- </Paper>
117
- </Popover>
118
- </>
119
- );
120
- }
121
-
122
- export default AddChannelButton;
@@ -1,93 +0,0 @@
1
- import { MoreHoriz } from "@mui/icons-material";
2
- import { Divider, IconButton, Input, Paper, Popover, Typography } from "@mui/material";
3
- import { styled } from "@mui/material/styles";
4
- import React, { useState } from "react";
5
- import type { ChangeEvent, MouseEvent } from "react";
6
- import { useLayerState, useSourceData } from "../../hooks";
7
-
8
- const DenseInput = styled(Input)`
9
- width: 5.5em;
10
- font-size: 0.7em;
11
- `;
12
-
13
- interface Props {
14
- axisIndex: number;
15
- max: number;
16
- }
17
-
18
- function AxisOptions({ axisIndex, max }: Props) {
19
- const [sourceData] = useSourceData();
20
- const [layer, setLayer] = useLayerState();
21
- const [anchorEl, setAnchorEl] = useState<null | Element>(null);
22
-
23
- const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
24
- setAnchorEl(event.currentTarget);
25
- };
26
-
27
- const handleClose = () => {
28
- setAnchorEl(null);
29
- };
30
-
31
- const handleIndexChange = (event: ChangeEvent<HTMLInputElement>) => {
32
- let value = +event.target.value;
33
- // Restrict value to valid range
34
- if (value < 0) value = 0;
35
- if (value > max) value = max;
36
-
37
- setLayer((prev) => {
38
- let layerProps = { ...prev.layerProps };
39
- // for each channel, update index
40
- layerProps.selections = layerProps.selections.map((ch) => {
41
- let new_ch = [...ch];
42
- new_ch[axisIndex] = value;
43
- return new_ch;
44
- });
45
-
46
- return { ...prev, layerProps };
47
- });
48
- };
49
-
50
- const open = Boolean(anchorEl);
51
- const id = open ? `${axisIndex}-index-${sourceData.id}-options` : undefined;
52
- const value = layer.layerProps.selections[0] ? layer.layerProps.selections[0][axisIndex] : 1;
53
-
54
- return (
55
- <>
56
- <IconButton
57
- onClick={handleClick}
58
- aria-describedby={id}
59
- style={{
60
- backgroundColor: "transparent",
61
- padding: 0,
62
- zIndex: 2,
63
- cursor: "pointer",
64
- }}
65
- >
66
- <MoreHoriz />
67
- </IconButton>
68
- <Popover
69
- id={id}
70
- open={open}
71
- anchorEl={anchorEl}
72
- onClose={handleClose}
73
- anchorOrigin={{
74
- vertical: "bottom",
75
- horizontal: "left",
76
- }}
77
- transformOrigin={{
78
- vertical: "top",
79
- horizontal: "left",
80
- }}
81
- >
82
- <Paper style={{ padding: "0px 4px", marginBottom: 4 }}>
83
- <Typography variant="caption">Index:</Typography>
84
- <Divider />
85
- <DenseInput value={value} onChange={handleIndexChange} type="number" id="max" fullWidth={false} />
86
- <Divider />
87
- </Paper>
88
- </Popover>
89
- </>
90
- );
91
- }
92
-
93
- export default AxisOptions;
@@ -1,86 +0,0 @@
1
- import { Divider, Grid, Slider, Typography } from "@mui/material";
2
- import { styled } from "@mui/material/styles";
3
- import * as React from "react";
4
- import { useLayerState, useSourceData } from "../../hooks";
5
- import DimensionOptions from "./AxisOptions";
6
-
7
- const DenseSlider = styled(Slider)`
8
- color: white;
9
- padding: 10px 0px 5px 0px;
10
- margin-right: 5px;
11
- :active { box-shadow: 0px 0px 0px 8px rgba(158, 158, 158, 0.16); }
12
- `;
13
-
14
- interface Props {
15
- axisIndex: number;
16
- max: number;
17
- }
18
-
19
- function AxisSlider({ axisIndex, max }: Props) {
20
- const [layer, setLayer] = useLayerState();
21
- const [sourceData] = useSourceData();
22
- const { axis_labels } = sourceData;
23
- let axisLabel = axis_labels[axisIndex];
24
- if (axisLabel === "t" || axisLabel === "z") {
25
- axisLabel = axisLabel.toUpperCase();
26
- }
27
- // state of the slider to update UI while dragging
28
- const [value, setValue] = React.useState(0);
29
-
30
- // If axis index change externally, need to update state
31
- React.useEffect(() => {
32
- // Use first channel to get initial value of slider - can be undefined on first render
33
- setValue(layer.layerProps.selections[0] ? layer.layerProps.selections[0][axisIndex] : 1);
34
- }, [layer.layerProps.selections, axisIndex]);
35
-
36
- const handleRelease = () => {
37
- setLayer((prev) => {
38
- let layerProps = { ...prev.layerProps };
39
- // for each channel, update index of this axis
40
- layerProps.selections = layerProps.selections.map((ch) => {
41
- let new_ch = [...ch];
42
- new_ch[axisIndex] = value;
43
- return new_ch;
44
- });
45
- return { ...prev, layerProps };
46
- });
47
- };
48
-
49
- const handleDrag = (_: Event, value: number | number[]) => {
50
- setValue(value as number);
51
- };
52
-
53
- return (
54
- <>
55
- <Grid>
56
- <Grid container justifyContent="space-between">
57
- <Grid size={{ xs: 10 }}>
58
- <div style={{ width: 165, overflow: "hidden", textOverflow: "ellipsis" }}>
59
- <Typography variant="caption" noWrap>
60
- {axisLabel}: {value}/{max}
61
- </Typography>
62
- </div>
63
- </Grid>
64
- <Grid size={{ xs: 1 }}>
65
- <DimensionOptions axisIndex={axisIndex} max={max} />
66
- </Grid>
67
- </Grid>
68
- <Grid container justifyContent="space-between">
69
- <Grid size={{ xs: 12 }}>
70
- <DenseSlider
71
- value={value}
72
- onChange={handleDrag}
73
- onChangeCommitted={handleRelease}
74
- min={0}
75
- max={max}
76
- step={1}
77
- />
78
- </Grid>
79
- </Grid>
80
- </Grid>
81
- <Divider />
82
- </>
83
- );
84
- }
85
-
86
- export default AxisSlider;
@@ -1,29 +0,0 @@
1
- import { Divider, Grid } from "@mui/material";
2
- import React from "react";
3
- import { useSourceData } from "../../hooks";
4
- import AxisSlider from "./AxisSlider";
5
-
6
- function AxisSliders() {
7
- const [sourceData] = useSourceData();
8
- const { axis_labels, channel_axis, loader } = sourceData;
9
-
10
- const sliders = axis_labels
11
- .slice(0, -2) // ignore last two axes, [y,x]
12
- .map((name, i): [string, number, number] => [name, i, loader[0].shape[i]]) // capture the name, index, and size of non-yx dims
13
- .filter((d) => {
14
- if (d[1] === channel_axis) return false; // ignore channel_axis (for OME-Zarr channel_axis === 1)
15
- if (d[2] > 1) return true; // keep if size > 1
16
- return false; // otherwise ignore as well
17
- })
18
- .map(([name, i, size]) => <AxisSlider key={name} axisIndex={i} max={size - 1} />);
19
-
20
- if (sliders.length === 0) return null;
21
- return (
22
- <>
23
- <Grid>{sliders}</Grid>
24
- <Divider />
25
- </>
26
- );
27
- }
28
-
29
- export default AxisSliders;
@@ -1,84 +0,0 @@
1
- import { RadioButtonChecked, RadioButtonUnchecked } from "@mui/icons-material";
2
- import { Grid, IconButton, Slider, Typography } from "@mui/material";
3
- import React from "react";
4
- import { useLayerState, useSourceData } from "../../hooks";
5
- import ChannelOptions from "./ChannelOptions";
6
-
7
- function ChannelController({ channelIndex }: { channelIndex: number }) {
8
- const [sourceData] = useSourceData();
9
- const [layer, setLayer] = useLayerState();
10
-
11
- const handleContrastChange = (_: Event, v: number | number[]) => {
12
- setLayer((prev) => {
13
- const contrastLimits = [...prev.layerProps.contrastLimits];
14
- contrastLimits[channelIndex] = v as [number, number];
15
- return { ...prev, layerProps: { ...prev.layerProps, contrastLimits } };
16
- });
17
- };
18
- const handleVisibilityChange = () => {
19
- setLayer((prev) => {
20
- const channelsVisible = [...prev.layerProps.channelsVisible];
21
- channelsVisible[channelIndex] = !channelsVisible[channelIndex];
22
- return { ...prev, layerProps: { ...prev.layerProps, channelsVisible } };
23
- });
24
- };
25
-
26
- const lp = layer.layerProps;
27
-
28
- // Material slider tries to sort in place. Need to copy.
29
- const value = [...lp.contrastLimits[channelIndex]];
30
- const color = `rgb(${lp.colormap ? [255, 255, 255] : lp.colors[channelIndex]})`;
31
- const on = lp.channelsVisible[channelIndex];
32
- const [min, max] = lp.contrastLimitsRange[channelIndex];
33
-
34
- const { channel_axis, names } = sourceData;
35
- const selection = lp.selections[channelIndex];
36
- const nameIndex = Number.isInteger(channel_axis) ? selection[channel_axis as number] : 0;
37
- const label = names[nameIndex];
38
- return (
39
- <>
40
- <Grid container justifyContent="space-between" wrap="nowrap">
41
- <Grid size={{ xs: 10 }}>
42
- <div style={{ width: 165, overflow: "hidden", textOverflow: "ellipsis" }}>
43
- <Typography variant="caption" noWrap>
44
- {label}
45
- </Typography>
46
- </div>
47
- </Grid>
48
- <Grid size={{ xs: 1 }}>
49
- <ChannelOptions channelIndex={channelIndex} />
50
- </Grid>
51
- </Grid>
52
- <Grid container justifyContent="space-between">
53
- <Grid size={{ xs: 2 }}>
54
- <IconButton
55
- style={{
56
- color,
57
- backgroundColor: "transparent",
58
- padding: 0,
59
- zIndex: 2,
60
- }}
61
- onClick={handleVisibilityChange}
62
- >
63
- {on ? <RadioButtonChecked /> : <RadioButtonUnchecked />}
64
- </IconButton>
65
- </Grid>
66
- <Grid size={{ xs: 10 }}>
67
- <Slider
68
- value={value}
69
- onChange={handleContrastChange}
70
- min={min}
71
- max={max}
72
- step={0.01}
73
- style={{
74
- padding: "10px 0px 5px 0px",
75
- color,
76
- }}
77
- />
78
- </Grid>
79
- </Grid>
80
- </>
81
- );
82
- }
83
-
84
- export default ChannelController;