From bed17c056bad71d8c0d330ff2fb0f30fc6d78478 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Mon, 4 Aug 2025 12:13:58 +0000 Subject: [PATCH 01/17] add zarrita dependency --- package-lock.json | 60 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 61 insertions(+) diff --git a/package-lock.json b/package-lock.json index 73e6ad120..73ba9a8cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,6 +74,7 @@ "use-debounce": "^10.0.2", "uuid": "^11.0.3", "wordcloud": "^1.2.2", + "zarrita": "^0.5.2", "zod": "^3.25.49", "zustand": "^5.0.6" }, @@ -7866,6 +7867,16 @@ "license": "Apache-2.0", "peer": true }, + "node_modules/@zarrita/storage": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@zarrita/storage/-/storage-0.1.1.tgz", + "integrity": "sha512-6/NUCvpzsIxfxeMv59jRTl/bOZg3GZfMP6iR8EIqrTaaE0S2jLL/ceX1OxcFBKnuA8/Z2YmgX4SFBHwFGrCcsw==", + "license": "MIT", + "dependencies": { + "reference-spec-reader": "^0.2.0", + "unzipit": "^1.4.3" + } + }, "node_modules/@zip.js/zip.js": { "version": "2.7.52", "license": "BSD-3-Clause", @@ -12615,6 +12626,12 @@ "balanced-match": "^1.0.0" } }, + "node_modules/reference-spec-reader": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/reference-spec-reader/-/reference-spec-reader-0.2.0.tgz", + "integrity": "sha512-q0mfCi5yZSSHXpCyxjgQeaORq3tvDsxDyzaadA/5+AbAUwRyRuuTh0aRQuE/vAOt/qzzxidJ5iDeu1cLHaNBlQ==", + "license": "MIT" + }, "node_modules/refractor": { "version": "3.6.0", "license": "MIT", @@ -14049,6 +14066,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unzipit": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.3.tgz", + "integrity": "sha512-gsq2PdJIWWGhx5kcdWStvNWit9FVdTewm4SEG7gFskWs+XCVaULt9+BwuoBtJiRE8eo3L1IPAOrbByNLtLtIlg==", + "license": "MIT", + "dependencies": { + "uzip-module": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.1", "dev": true, @@ -14119,6 +14148,12 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/uzip-module": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", + "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==", + "license": "MIT" + }, "node_modules/vfile": { "version": "6.0.3", "license": "MIT", @@ -14843,6 +14878,31 @@ "node": ">=12" } }, + "node_modules/zarrita": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/zarrita/-/zarrita-0.5.2.tgz", + "integrity": "sha512-BN/miFnvBuVqLLObrI08crkbyi+Oc/HWZpgCmWusrWUNIauIV9vR5zlvpSAnhOjZwjomEKFb728K7ax7FiDgJQ==", + "license": "MIT", + "dependencies": { + "@zarrita/storage": "^0.1.1", + "numcodecs": "^0.3.2" + } + }, + "node_modules/zarrita/node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/zarrita/node_modules/numcodecs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/numcodecs/-/numcodecs-0.3.2.tgz", + "integrity": "sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw==", + "license": "MIT", + "dependencies": { + "fflate": "^0.8.0" + } + }, "node_modules/zod": { "version": "3.25.49", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.49.tgz", diff --git a/package.json b/package.json index 89af46421..efc4e8f27 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "use-debounce": "^10.0.2", "uuid": "^11.0.3", "wordcloud": "^1.2.2", + "zarrita": "^0.5.2", "zod": "^3.25.49", "zustand": "^5.0.6" }, From 6ad4d809d8733815c222f01d6a04cbfc9fd46d2f Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Mon, 4 Aug 2025 12:54:05 +0000 Subject: [PATCH 02/17] very rough debug sketch of viewing some local xenium zarr --- .../components/DebugJsonDialogComponent.tsx | 2 + src/react/components/ZarritaSketch.tsx | 201 ++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/react/components/ZarritaSketch.tsx diff --git a/src/react/components/DebugJsonDialogComponent.tsx b/src/react/components/DebugJsonDialogComponent.tsx index 38e2e7a6b..f4fe13e57 100644 --- a/src/react/components/DebugJsonDialogComponent.tsx +++ b/src/react/components/DebugJsonDialogComponent.tsx @@ -6,6 +6,7 @@ import "react18-json-view/src/dark.css"; import { useMemo, useState } from "react"; import { useDebounce } from "use-debounce"; import "../../utilities/css/JsonDialogStyles.css"; +import ZarritaSketch from "./ZarritaSketch"; type JSONObject = { [key: string]: any }; @@ -86,6 +87,7 @@ export default function ({ json, header }: { json: any; header?: string }) { }} collapsed={1} /> + ); } diff --git a/src/react/components/ZarritaSketch.tsx b/src/react/components/ZarritaSketch.tsx new file mode 100644 index 000000000..489b86754 --- /dev/null +++ b/src/react/components/ZarritaSketch.tsx @@ -0,0 +1,201 @@ +import * as zarr from "zarrita"; +import { ZipFileStore } from "@zarrita/storage"; +import { useEffect, useState } from "react"; +import JsonView from "react18-json-view"; + + +async function test() { + const url = "http://localhost:8081/"; + // these xenium stores don't have the 'consolidated' metadata + // - but if we're making our own spec, we likely will. + const cellsStore = await zarr.tryWithConsolidated(new zarr.FetchStore(`${url}/cells.zarr`)); + const analysisStore = await zarr.tryWithConsolidated(new zarr.FetchStore(`${url}/analysis.zarr`)); + + const cellsRoot = await zarr.open(cellsStore); + const anRoot = await zarr.open(analysisStore); + + const id = await zarr.open(cellsRoot.resolve("cell_id")); + if (id.kind === "group") throw new Error("Expected array for cell_id, got a group"); + // todo - version that'll map from indices + + function makeCellId(cellIdPrefix: number, datasetSuffix: number) { + // Step 1: Convert to hex and pad to 8 characters + const hex = cellIdPrefix.toString(16).padStart(8, '0'); + + // Step 2: Shift hex characters to the custom range aโ€“p + const shiftMap = { + '0': 'a', '1': 'b', '2': 'c', '3': 'd', + '4': 'e', '5': 'f', '6': 'g', '7': 'h', + '8': 'i', '9': 'j', 'a': 'k', 'b': 'l', + 'c': 'm', 'd': 'n', 'e': 'o', 'f': 'p' + } as const; + + //@ts-expect-error - could have a better type for shiftMap, or verify that hex only contains valid hex characters + const shifted = hex.split('').map(char => shiftMap[char]).join(''); + + // Step 3: Append dash and suffix + return `${shifted}-${datasetSuffix}`; + } + function parseCellId(cellId: string) { + // Step 1: Split into shifted prefix and suffix + const [shifted, suffixStr] = cellId.split('-'); + + // Step 2: Reverse the custom shift mapping + const reverseMap = { + 'a': '0', 'b': '1', 'c': '2', 'd': '3', + 'e': '4', 'f': '5', 'g': '6', 'h': '7', + 'i': '8', 'j': '9', 'k': 'a', 'l': 'b', + 'm': 'c', 'n': 'd', 'o': 'e', 'p': 'f' + }; + + // @ts-expect-error - could have a better type for reverseMap, or verify that shifted only contains valid characters + const hex = shifted.split('').map(char => reverseMap[char]).join(''); + + // Step 3: Convert hex string to integer + const cell_id_prefix = Number.parseInt(hex, 16); + + // Step 4: Parse suffix + const dataset_suffix = Number.parseInt(suffixStr, 10); + + return { cell_id_prefix, dataset_suffix }; + } + + function reconstructCellIds(cellIdArray: Uint32Array) { + const n = cellIdArray.length / 2; + const ids = new Array(n); + for (let i = 0; i < n; i++) { + const prefix = cellIdArray[i * 2]; + const suffix = cellIdArray[i * 2 + 1]; + ids[i] = makeCellId(prefix, suffix); + } + return ids; + } + const summary = await zarr.open(cellsRoot.resolve("cell_summary"), { kind: "array"}); + // all cell_ids, with `shape: [59388, 1]` (flatten how?) + // const ids = await zarr.get(id, [null, zarr.slice(1)]); + const ids = await zarr.get(id); + // can we find a specific cell `kalnagga-1`? + // it should have location 665.253552.41 3552.41, area 87.3, Cluster 8... + // let's find the indices & indptrs for cluster 8 (from Graph-Based Clustering, ie group 0)... + const clusterIndices = await zarr.open(anRoot.resolve("cell_groups/0/indices"), { kind: "array" }); + const clusterIndptr = await zarr.open(anRoot.resolve("cell_groups/0/indptr"), { kind: "array" }); + // I should be able to find the indptr (7, zero-based), to know where to start looking in indices for our cluster... I'll also want the next one so I know the region to slice + const indPtrs = await zarr.get(clusterIndptr); //could have sliced here, but meh... it's small + if (!(indPtrs.data instanceof Uint32Array)) throw new Error("Expected Uint32Array for indptr"); + const c8Slice = zarr.slice(indPtrs.data[7], indPtrs.data[8]); + const c8Indices = await zarr.get(clusterIndices, [c8Slice]); //this gives 2966 indices, as expected + + //... so I should be able to use c8Indices to... profit??? + const allCellIds = (await zarr.get(id, [null, 0])).data; + if (!(allCellIds instanceof Uint32Array)) throw new Error("Expected Uint32Array for cell IDs"); + + // this is heavy, we'd likely want to do things differently + if (!(ids.data instanceof Uint32Array)) throw new Error("Expected Uint32Array for cell IDs"); + const allCellIdStrings = reconstructCellIds(ids.data); + if (!(c8Indices.data instanceof Uint32Array)) throw new Error("Expected Uint32Array for c8 indices"); + const c8CellIds = c8Indices.data.map(i => allCellIds[i]); + //still trying to find this one cell I decided I was interested in... + //we also know the area is ~87.3mu^2 - `/cell_summary` should have `[x, y, cell_area, ...]`... + const allCellAreas = await zarr.get(summary, [null, 2]); + //! oops... if we map over a Uint32Array, we get another Uint32Array... + //but we should have a Float64Array here + // const c8Areas = new Float64Array(c8Indices.data).map(i => allCellAreas.data[i]); + //@ts-expect-error - todo + const c8Areas = Float64Array.from(c8Indices.data, i => allCellAreas.data[i]); + const queriedCellSize = 87.3; + // find the indices of cells with areas similar to what we're looking for... + //@ts-expect-error - todo + const closeIndices = c8Areas.map((v, i) => Math.abs(queriedCellSize - v) <= 0.1 ? c8Indices.data[i] : -1).filter(i => i !== -1); + const n = closeIndices.length; + // now let's get the centroids for these cells... + // const queryX = 665.25, queryY = 3552.41; //for ref, this is the one we are hoping to find + const allCellCentroids = await zarr.get(summary, [null, zarr.slice(0, 2)]); + // or is there a transform matrix involved? + const transform = await zarr.open(cellsRoot.resolve("/masks/homogeneous_transform"), { kind: "array" }); + // looks like a scale matrix, with x&y 4.7058820724487305 + const transformData = await zarr.get(transform, [null, null]); + + + const numVertsArr = await zarr.get( + await zarr.open(cellsRoot.resolve("polygon_num_vertices"), { kind: "array" }), + [0, null] // shape: [2, N] do we have a both nucleus and cell boundary in here maybe? + ); + const numVertsSet = new Set(Array.from(numVertsArr.data)); + function computeOffsets(numVerts: any) { + if (numVerts.length === 0) { + return new Uint32Array(0); + } + const offsets = new Uint32Array(numVerts.length); + let sum = 0; + for (let i = 0; i < numVerts.length; i++) { + offsets[i] = sum; + sum += numVerts[i]; + } + return offsets; + } + + const offsets = computeOffsets(numVertsArr.data); + // async function loadPolygonVertices(cellIndex: number, numVerts: Uint32Array) { + // const start = offsets[cellIndex]; + // const count = numVerts[cellIndex]; + // const polyVerts = await zarr.get( + // await zarr.open(cellsRoot.resolve("polygon_vertices"), { kind: "array" }), + // [zarr.slice(0, 2), zarr.slice(start, start + count)] + // ); + // return { + // x: polyVerts.data.subarray(0, count), + // y: polyVerts.data.subarray(count) + // }; + // } + + + const queriedCells = await Promise.all([...closeIndices].map(async i => { + const start = offsets[i]; + //@ts-expect-error + const count = numVertsArr.data[i]; //always 13? Is that right? Unlucky for some... + + // Error: Input contains an empty iterator. + // const polyVerts = await zarr.get( + // await zarr.open(cellsRoot.resolve("polygon_vertices")), + // [zarr.slice(0,2), zarr.slice(start, start + count)] + // ); + // const [xRow, yRow] = [polyVerts.data.subarray(0, count), polyVerts.data.subarray(count)]; + // const polyVerts = loadPolygonVertices() //same... + return { + id: allCellIdStrings[i], + //@ts-expect-error + x: allCellCentroids.data[i * 2 + 0], + //@ts-expect-error + y: allCellCentroids.data[i * 2 + 1], + //@ts-expect-error + cellArea: allCellAreas.data[i], + start, count, + // polyVerts + } + })); + + // numVertsArr.data is Uint32Array of shape [nCells] + // const summary0 = (await zarr.get(summary, [zarr.slice(1), null])); + // // how many vertices does this cell have? + // const polyNumVerts = await zarr.open(cellsRoot.resolve("polygon_num_vertices")); + const polyVerts = await zarr.open(cellsRoot.resolve("polygon_vertices")); + // const numVerts0 = (await zarr.get(polyNumVerts, [zarr.slice(1), 0])).data[0]; + // // a whole lot of 0? + // const verts = (await zarr.get(polyVerts, [1, zarr.slice(0, numVerts0)])); + + return { + queriedCells, offsets, numVertsSet, polyVerts, numVertsArr + } +} + + +export default function ZarritaSketch() { + const url = "http://localhost:8080/"; + const [view, setView] = useState(); + useEffect(() => { + test().then(setView); + }, []); // url should be defined in the parent component + + + return ; +} \ No newline at end of file From d390f8c6c5b7efa00e6c7244e0aeb8ecae85d8bc Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Mon, 4 Aug 2025 13:19:54 +0000 Subject: [PATCH 03/17] rearrange zarrita code, better typing, use zip stores --- src/react/components/ZarritaSketch.tsx | 130 +++++++++++++------------ 1 file changed, 67 insertions(+), 63 deletions(-) diff --git a/src/react/components/ZarritaSketch.tsx b/src/react/components/ZarritaSketch.tsx index 489b86754..39295c55a 100644 --- a/src/react/components/ZarritaSketch.tsx +++ b/src/react/components/ZarritaSketch.tsx @@ -3,85 +3,87 @@ import { ZipFileStore } from "@zarrita/storage"; import { useEffect, useState } from "react"; import JsonView from "react18-json-view"; +function makeXeniumCellId(cellIdPrefix: number, datasetSuffix: number) { + // Step 1: Convert to hex and pad to 8 characters + const hex = cellIdPrefix.toString(16).padStart(8, '0'); + + // Step 2: Shift hex characters to the custom range aโ€“p + const shiftMap = { + '0': 'a', '1': 'b', '2': 'c', '3': 'd', + '4': 'e', '5': 'f', '6': 'g', '7': 'h', + '8': 'i', '9': 'j', 'a': 'k', 'b': 'l', + 'c': 'm', 'd': 'n', 'e': 'o', 'f': 'p' + } as const; + + //@ts-expect-error - could have a better type for shiftMap, or verify that hex only contains valid hex characters + const shifted = hex.split('').map(char => shiftMap[char]).join(''); + + // Step 3: Append dash and suffix + return `${shifted}-${datasetSuffix}`; +} -async function test() { - const url = "http://localhost:8081/"; - // these xenium stores don't have the 'consolidated' metadata - // - but if we're making our own spec, we likely will. - const cellsStore = await zarr.tryWithConsolidated(new zarr.FetchStore(`${url}/cells.zarr`)); - const analysisStore = await zarr.tryWithConsolidated(new zarr.FetchStore(`${url}/analysis.zarr`)); +function parseXeniumCellId(cellId: string) { + // Step 1: Split into shifted prefix and suffix + const [shifted, suffixStr] = cellId.split('-'); - const cellsRoot = await zarr.open(cellsStore); - const anRoot = await zarr.open(analysisStore); + // Step 2: Reverse the custom shift mapping + const reverseMap = { + 'a': '0', 'b': '1', 'c': '2', 'd': '3', + 'e': '4', 'f': '5', 'g': '6', 'h': '7', + 'i': '8', 'j': '9', 'k': 'a', 'l': 'b', + 'm': 'c', 'n': 'd', 'o': 'e', 'p': 'f' + }; - const id = await zarr.open(cellsRoot.resolve("cell_id")); - if (id.kind === "group") throw new Error("Expected array for cell_id, got a group"); - // todo - version that'll map from indices + // @ts-expect-error - could have a better type for reverseMap, or verify that shifted only contains valid characters + const hex = shifted.split('').map(char => reverseMap[char]).join(''); - function makeCellId(cellIdPrefix: number, datasetSuffix: number) { - // Step 1: Convert to hex and pad to 8 characters - const hex = cellIdPrefix.toString(16).padStart(8, '0'); + // Step 3: Convert hex string to integer + const cell_id_prefix = Number.parseInt(hex, 16); - // Step 2: Shift hex characters to the custom range aโ€“p - const shiftMap = { - '0': 'a', '1': 'b', '2': 'c', '3': 'd', - '4': 'e', '5': 'f', '6': 'g', '7': 'h', - '8': 'i', '9': 'j', 'a': 'k', 'b': 'l', - 'c': 'm', 'd': 'n', 'e': 'o', 'f': 'p' - } as const; + // Step 4: Parse suffix + const dataset_suffix = Number.parseInt(suffixStr, 10); - //@ts-expect-error - could have a better type for shiftMap, or verify that hex only contains valid hex characters - const shifted = hex.split('').map(char => shiftMap[char]).join(''); + return { cell_id_prefix, dataset_suffix }; +} - // Step 3: Append dash and suffix - return `${shifted}-${datasetSuffix}`; +function reconstructCellIds(cellIdArray: Uint32Array) { + const n = cellIdArray.length / 2; + const ids = new Array(n); + for (let i = 0; i < n; i++) { + const prefix = cellIdArray[i * 2]; + const suffix = cellIdArray[i * 2 + 1]; + ids[i] = makeXeniumCellId(prefix, suffix); } - function parseCellId(cellId: string) { - // Step 1: Split into shifted prefix and suffix - const [shifted, suffixStr] = cellId.split('-'); - - // Step 2: Reverse the custom shift mapping - const reverseMap = { - 'a': '0', 'b': '1', 'c': '2', 'd': '3', - 'e': '4', 'f': '5', 'g': '6', 'h': '7', - 'i': '8', 'j': '9', 'k': 'a', 'l': 'b', - 'm': 'c', 'n': 'd', 'o': 'e', 'p': 'f' - }; - - // @ts-expect-error - could have a better type for reverseMap, or verify that shifted only contains valid characters - const hex = shifted.split('').map(char => reverseMap[char]).join(''); + return ids; +} - // Step 3: Convert hex string to integer - const cell_id_prefix = Number.parseInt(hex, 16); +async function test() { + const url = "http://localhost:8080"; + // these xenium stores don't have the 'consolidated' metadata + // - but if we're making our own spec, we likely will. + // const cellsStore = await zarr.tryWithConsolidated(new zarr.FetchStore(`${url}/cells.zarr`)); + const cellsStore = await zarr.tryWithConsolidated(ZipFileStore.fromUrl(`${url}/cells.zarr.zip`)) as zarr.Readable; + const analysisStore = await zarr.tryWithConsolidated(ZipFileStore.fromUrl(`${url}/analysis.zarr.zip`)) as zarr.Readable; + // const transcriptsStore = await zarr.tryWithConsolidated(ZipFileStore.fromUrl(`${url}/transcripts.zarr.zip`)) as zarr.Readable; - // Step 4: Parse suffix - const dataset_suffix = Number.parseInt(suffixStr, 10); + const cellsRoot = await zarr.open(cellsStore); + const anRoot = await zarr.open(analysisStore); - return { cell_id_prefix, dataset_suffix }; - } + const id = await zarr.open(cellsRoot.resolve("cell_id"), { kind: "array" }); + if (!id.is("number")) throw new Error("Expected cell_id to be an array of numbers"); - function reconstructCellIds(cellIdArray: Uint32Array) { - const n = cellIdArray.length / 2; - const ids = new Array(n); - for (let i = 0; i < n; i++) { - const prefix = cellIdArray[i * 2]; - const suffix = cellIdArray[i * 2 + 1]; - ids[i] = makeCellId(prefix, suffix); - } - return ids; - } const summary = await zarr.open(cellsRoot.resolve("cell_summary"), { kind: "array"}); - // all cell_ids, with `shape: [59388, 1]` (flatten how?) - // const ids = await zarr.get(id, [null, zarr.slice(1)]); const ids = await zarr.get(id); // can we find a specific cell `kalnagga-1`? // it should have location 665.253552.41 3552.41, area 87.3, Cluster 8... // let's find the indices & indptrs for cluster 8 (from Graph-Based Clustering, ie group 0)... const clusterIndices = await zarr.open(anRoot.resolve("cell_groups/0/indices"), { kind: "array" }); + if (!clusterIndices.is("number")) throw new Error("Expected cluster indices to be an array of numbers"); const clusterIndptr = await zarr.open(anRoot.resolve("cell_groups/0/indptr"), { kind: "array" }); + if (!clusterIndptr.is("number")) throw new Error("Expected cluster indptr to be an array of numbers"); // I should be able to find the indptr (7, zero-based), to know where to start looking in indices for our cluster... I'll also want the next one so I know the region to slice const indPtrs = await zarr.get(clusterIndptr); //could have sliced here, but meh... it's small - if (!(indPtrs.data instanceof Uint32Array)) throw new Error("Expected Uint32Array for indptr"); + const c8Slice = zarr.slice(indPtrs.data[7], indPtrs.data[8]); const c8Indices = await zarr.get(clusterIndices, [c8Slice]); //this gives 2966 indices, as expected @@ -104,7 +106,6 @@ async function test() { const c8Areas = Float64Array.from(c8Indices.data, i => allCellAreas.data[i]); const queriedCellSize = 87.3; // find the indices of cells with areas similar to what we're looking for... - //@ts-expect-error - todo const closeIndices = c8Areas.map((v, i) => Math.abs(queriedCellSize - v) <= 0.1 ? c8Indices.data[i] : -1).filter(i => i !== -1); const n = closeIndices.length; // now let's get the centroids for these cells... @@ -154,12 +155,15 @@ async function test() { //@ts-expect-error const count = numVertsArr.data[i]; //always 13? Is that right? Unlucky for some... - // Error: Input contains an empty iterator. + // const polyVertsArr = await zarr.open(cellsRoot.resolve("polygon_vertices"), { kind: "array" }); + // if (!polyVertsArr.is("number")) throw new Error("Expected polygon_vertices to be an array of numbers"); + // // Error: Input contains an empty iterator. // const polyVerts = await zarr.get( - // await zarr.open(cellsRoot.resolve("polygon_vertices")), - // [zarr.slice(0,2), zarr.slice(start, start + count)] + // polyVertsArr, + // [zarr.slice(0,2), zarr.slice(start, start + count)] // ); - // const [xRow, yRow] = [polyVerts.data.subarray(0, count), polyVerts.data.subarray(count)]; + // // polyVerts + // const [xRow, yRow] = [polyVerts.data.slice(0, count), polyVerts.data.subarray(count)]; // const polyVerts = loadPolygonVertices() //same... return { id: allCellIdStrings[i], From ffe79685f5ac13897ff4dbb58ff39dce931e6755 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Mon, 4 Aug 2025 13:42:01 +0000 Subject: [PATCH 04/17] more zarr sketching --- src/react/components/ZarritaSketch.tsx | 86 ++++++++++++++------------ 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/src/react/components/ZarritaSketch.tsx b/src/react/components/ZarritaSketch.tsx index 39295c55a..ba59ea480 100644 --- a/src/react/components/ZarritaSketch.tsx +++ b/src/react/components/ZarritaSketch.tsx @@ -46,7 +46,7 @@ function parseXeniumCellId(cellId: string) { return { cell_id_prefix, dataset_suffix }; } -function reconstructCellIds(cellIdArray: Uint32Array) { +function reconstructCellIds(cellIdArray: zarr.TypedArray) { const n = cellIdArray.length / 2; const ids = new Array(n); for (let i = 0; i < n; i++) { @@ -57,8 +57,8 @@ function reconstructCellIds(cellIdArray: Uint32Array) { return ids; } +const url = "http://localhost:8080"; async function test() { - const url = "http://localhost:8080"; // these xenium stores don't have the 'consolidated' metadata // - but if we're making our own spec, we likely will. // const cellsStore = await zarr.tryWithConsolidated(new zarr.FetchStore(`${url}/cells.zarr`)); @@ -73,6 +73,7 @@ async function test() { if (!id.is("number")) throw new Error("Expected cell_id to be an array of numbers"); const summary = await zarr.open(cellsRoot.resolve("cell_summary"), { kind: "array"}); + if (!summary.is("number")) throw new Error("Expected cell_summary to be an array of numbers"); const ids = await zarr.get(id); // can we find a specific cell `kalnagga-1`? // it should have location 665.253552.41 3552.41, area 87.3, Cluster 8... @@ -88,37 +89,32 @@ async function test() { const c8Indices = await zarr.get(clusterIndices, [c8Slice]); //this gives 2966 indices, as expected //... so I should be able to use c8Indices to... profit??? - const allCellIds = (await zarr.get(id, [null, 0])).data; - if (!(allCellIds instanceof Uint32Array)) throw new Error("Expected Uint32Array for cell IDs"); // this is heavy, we'd likely want to do things differently - if (!(ids.data instanceof Uint32Array)) throw new Error("Expected Uint32Array for cell IDs"); const allCellIdStrings = reconstructCellIds(ids.data); - if (!(c8Indices.data instanceof Uint32Array)) throw new Error("Expected Uint32Array for c8 indices"); - const c8CellIds = c8Indices.data.map(i => allCellIds[i]); //still trying to find this one cell I decided I was interested in... - //we also know the area is ~87.3mu^2 - `/cell_summary` should have `[x, y, cell_area, ...]`... + //we also know the area is ~87.3ยตm^2 - `/cell_summary` should have `[x, y, cell_area, ...]`... const allCellAreas = await zarr.get(summary, [null, 2]); //! oops... if we map over a Uint32Array, we get another Uint32Array... //but we should have a Float64Array here // const c8Areas = new Float64Array(c8Indices.data).map(i => allCellAreas.data[i]); - //@ts-expect-error - todo const c8Areas = Float64Array.from(c8Indices.data, i => allCellAreas.data[i]); const queriedCellSize = 87.3; // find the indices of cells with areas similar to what we're looking for... const closeIndices = c8Areas.map((v, i) => Math.abs(queriedCellSize - v) <= 0.1 ? c8Indices.data[i] : -1).filter(i => i !== -1); - const n = closeIndices.length; // now let's get the centroids for these cells... // const queryX = 665.25, queryY = 3552.41; //for ref, this is the one we are hoping to find const allCellCentroids = await zarr.get(summary, [null, zarr.slice(0, 2)]); // or is there a transform matrix involved? const transform = await zarr.open(cellsRoot.resolve("/masks/homogeneous_transform"), { kind: "array" }); // looks like a scale matrix, with x&y 4.7058820724487305 - const transformData = await zarr.get(transform, [null, null]); + // const transformData = await zarr.get(transform, [null, null]); + const numVerts = await zarr.open(cellsRoot.resolve("polygon_num_vertices"), { kind: "array" }); + if (!numVerts.is("number")) throw new Error("Expected polygon_num_vertices to be an array of numbers"); const numVertsArr = await zarr.get( - await zarr.open(cellsRoot.resolve("polygon_num_vertices"), { kind: "array" }), + numVerts, [0, null] // shape: [2, N] do we have a both nucleus and cell boundary in here maybe? ); const numVertsSet = new Set(Array.from(numVertsArr.data)); @@ -136,45 +132,41 @@ async function test() { } const offsets = computeOffsets(numVertsArr.data); - // async function loadPolygonVertices(cellIndex: number, numVerts: Uint32Array) { - // const start = offsets[cellIndex]; - // const count = numVerts[cellIndex]; - // const polyVerts = await zarr.get( - // await zarr.open(cellsRoot.resolve("polygon_vertices"), { kind: "array" }), - // [zarr.slice(0, 2), zarr.slice(start, start + count)] - // ); - // return { - // x: polyVerts.data.subarray(0, count), - // y: polyVerts.data.subarray(count) - // }; - // } + + // broken... + async function loadPolygonVertices(cellIndex: number) { + const start = offsets[cellIndex]; + + const count = numVertsArr.data[cellIndex]; + const polyVertsArr = await zarr.open(cellsRoot.resolve("polygon_vertices"), { kind: "array" }); + if (!polyVertsArr.is("number")) throw new Error("Expected polygon_vertices to be an array of numbers"); + try { + const polyVerts = await zarr.get( + polyVertsArr, + [zarr.slice(0, 2), zarr.slice(start, start + count)] + ); + return { + x: polyVerts.data.subarray(0, count), + y: polyVerts.data.subarray(count) + }; + } catch (error) { + return error; + } + } const queriedCells = await Promise.all([...closeIndices].map(async i => { const start = offsets[i]; - //@ts-expect-error const count = numVertsArr.data[i]; //always 13? Is that right? Unlucky for some... - // const polyVertsArr = await zarr.open(cellsRoot.resolve("polygon_vertices"), { kind: "array" }); - // if (!polyVertsArr.is("number")) throw new Error("Expected polygon_vertices to be an array of numbers"); - // // Error: Input contains an empty iterator. - // const polyVerts = await zarr.get( - // polyVertsArr, - // [zarr.slice(0,2), zarr.slice(start, start + count)] - // ); - // // polyVerts - // const [xRow, yRow] = [polyVerts.data.slice(0, count), polyVerts.data.subarray(count)]; - // const polyVerts = loadPolygonVertices() //same... + const polyVerts = await loadPolygonVertices(i); //still broken... return { id: allCellIdStrings[i], - //@ts-expect-error x: allCellCentroids.data[i * 2 + 0], - //@ts-expect-error y: allCellCentroids.data[i * 2 + 1], - //@ts-expect-error cellArea: allCellAreas.data[i], start, count, - // polyVerts + polyVerts } })); @@ -193,11 +185,25 @@ async function test() { } +async function transcriptTest() { + const transcriptsStore = await zarr.tryWithConsolidated(ZipFileStore.fromUrl(`${url}/transcripts.zarr.zip`)) as zarr.Readable; + // let's try to get the locations of all transcripts in the most zoomed out pyramid level, with a quality threshold of 0.5 + const transcriptsRoot = await zarr.open(transcriptsStore); + // todo get the grids & check the shape to find the most zoomed out level... + // for now, cheating & hardcoding the path... in a spatial view, we'll need to resolve pyramid levels & locations properly + const grids = await zarr.open(transcriptsRoot.resolve("grids/6/0,0"), { kind: "group" }); + const location = await zarr.open(grids.resolve("location"), { kind: "array" }); + if (!location.is("number")) throw new Error("Expected location to be an array of numbers"); + const quality = await zarr.open(grids.resolve("quality_score"), { kind: "array" }); + + return { grids }; +} + export default function ZarritaSketch() { const url = "http://localhost:8080/"; const [view, setView] = useState(); useEffect(() => { - test().then(setView); + transcriptTest().then(setView); }, []); // url should be defined in the parent component From 6396c515cd3bf55903dc7f03400175d77aa70261 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Mon, 4 Aug 2025 13:54:21 +0000 Subject: [PATCH 05/17] transcript query test --- src/react/components/ZarritaSketch.tsx | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/react/components/ZarritaSketch.tsx b/src/react/components/ZarritaSketch.tsx index ba59ea480..2c4a39eb1 100644 --- a/src/react/components/ZarritaSketch.tsx +++ b/src/react/components/ZarritaSketch.tsx @@ -187,7 +187,8 @@ async function test() { async function transcriptTest() { const transcriptsStore = await zarr.tryWithConsolidated(ZipFileStore.fromUrl(`${url}/transcripts.zarr.zip`)) as zarr.Readable; - // let's try to get the locations of all transcripts in the most zoomed out pyramid level, with a quality threshold of 0.5 + // let's try to get the locations of all transcripts in the most zoomed out pyramid level, with a quality threshold + const qualityThreshold = 10; const transcriptsRoot = await zarr.open(transcriptsStore); // todo get the grids & check the shape to find the most zoomed out level... // for now, cheating & hardcoding the path... in a spatial view, we'll need to resolve pyramid levels & locations properly @@ -195,8 +196,23 @@ async function transcriptTest() { const location = await zarr.open(grids.resolve("location"), { kind: "array" }); if (!location.is("number")) throw new Error("Expected location to be an array of numbers"); const quality = await zarr.open(grids.resolve("quality_score"), { kind: "array" }); + if (!quality.is("float32")) throw new Error("Expected quality_scores to be float32"); + // let's do that quality filter - find indices where quality is above 30 + const qualityData = await zarr.get(quality); + const highQualityIndices = Uint32Array.from( + Array.from(qualityData.data).map((q, i) => q <= qualityThreshold ? i : -1).filter(i => i !== -1) + ); + // now let's get the locations for these indices + const locationData = await zarr.get(location); + const locations = Array.from(highQualityIndices).map(i => { + const x = locationData.data[i * 3]; + const y = locationData.data[i * 3 + 1]; + const z = locationData.data[i * 3 + 2]; + const q = qualityData.data[i]; + return { x, y, z, q }; + }); - return { grids }; + return { locations }; } export default function ZarritaSketch() { From c1a0cf439d4409c24dd99ff2997adada3937e51b Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Tue, 5 Aug 2025 13:28:31 +0000 Subject: [PATCH 06/17] more sketching --- src/react/components/ZarritaSketch.tsx | 50 ++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/react/components/ZarritaSketch.tsx b/src/react/components/ZarritaSketch.tsx index 2c4a39eb1..a22e8dcd8 100644 --- a/src/react/components/ZarritaSketch.tsx +++ b/src/react/components/ZarritaSketch.tsx @@ -186,18 +186,28 @@ async function test() { async function transcriptTest() { + const startTime = performance.now(); const transcriptsStore = await zarr.tryWithConsolidated(ZipFileStore.fromUrl(`${url}/transcripts.zarr.zip`)) as zarr.Readable; // let's try to get the locations of all transcripts in the most zoomed out pyramid level, with a quality threshold const qualityThreshold = 10; const transcriptsRoot = await zarr.open(transcriptsStore); // todo get the grids & check the shape to find the most zoomed out level... // for now, cheating & hardcoding the path... in a spatial view, we'll need to resolve pyramid levels & locations properly - const grids = await zarr.open(transcriptsRoot.resolve("grids/6/0,0"), { kind: "group" }); - const location = await zarr.open(grids.resolve("location"), { kind: "array" }); + const smallestGrid = await zarr.open(transcriptsRoot.resolve("grids/6/0,0"), { kind: "group" }); + // Description for root group attributes: + //??? where are these "root group attributes"? + // `fov_names` `list[str]` Names of the FOVs used in the dataset as referenced by the FOV indices (/grids/[grid_index]/[grid_position]/id). + + // const fovNames = await zarr.open(transcriptsRoot.resolve("fov_names"), { kind: "array" }); + // if (!fovNames.is("string")) throw new Error("Expected fov_names to be an array of strings"); + // const fovNamesData = await zarr.get(fovNames); + // let's just log the fov names for now + // console.log("FOV Names:", fovNamesData.data); + const location = await zarr.open(smallestGrid.resolve("location"), { kind: "array" }); if (!location.is("number")) throw new Error("Expected location to be an array of numbers"); - const quality = await zarr.open(grids.resolve("quality_score"), { kind: "array" }); + const quality = await zarr.open(smallestGrid.resolve("quality_score"), { kind: "array" }); if (!quality.is("float32")) throw new Error("Expected quality_scores to be float32"); - // let's do that quality filter - find indices where quality is above 30 + // let's do that quality filter const qualityData = await zarr.get(quality); const highQualityIndices = Uint32Array.from( Array.from(qualityData.data).map((q, i) => q <= qualityThreshold ? i : -1).filter(i => i !== -1) @@ -210,16 +220,42 @@ async function transcriptTest() { const z = locationData.data[i * 3 + 2]; const q = qualityData.data[i]; return { x, y, z, q }; - }); + }).sort((a, b) => a.q - b.q); // sort by quality score + const endTime = performance.now(); + return { locations, time: endTime - startTime }; +} + +async function testMerfishSpatialData() { + // this only worked after I renamed `metadata` to `.metadata`??? + const url = "http://localhost:8081/data.zarr/"; + const store = new zarr.FetchStore(url); + const merfish = await zarr.tryWithConsolidated(store); //returns `any` according to lsp??? + // const url = "http://localhost:8081/merfish.zip"; + // const store = ZipFileStore.fromUrl(url); + // const merfish = await zarr.tryWithConsolidated(store) as zarr.AsyncReadable; //missing key /zarr.json + const root = await zarr.open(merfish, { kind: "group" }); - return { locations }; + return { root }; +} + +async function testXeniumS3() { + const url = "https://s3.embl.de/spatialdata/spatialdata-sandbox/xenium_rep2_io.zarr/"; + const store = new zarr.FetchStore(url, { + overrides: { + mode: "cors" + } + }); + const xenium = await zarr.tryWithConsolidated(store); + return { xenium }; + // const root = await zarr.open(xenium); } export default function ZarritaSketch() { const url = "http://localhost:8080/"; const [view, setView] = useState(); useEffect(() => { - transcriptTest().then(setView); + // transcriptTest().then(setView); + testMerfishSpatialData().then(setView); }, []); // url should be defined in the parent component From 656c6a11a58393d25b8b6458aba9fcf3b7499c59 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Wed, 6 Aug 2025 13:31:49 +0100 Subject: [PATCH 07/17] add vite-plugin-node-polyfills to fix zarrita build error --- package-lock.json | 1762 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + vite.config.mts | 4 + 3 files changed, 1696 insertions(+), 71 deletions(-) diff --git a/package-lock.json b/package-lock.json index 73ba9a8cf..db38754d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,7 @@ "tailwindcss-animate": "^1.0.7", "use-debounce": "^10.0.2", "uuid": "^11.0.3", + "vite-plugin-node-polyfills": "^0.24.0", "wordcloud": "^1.2.2", "zarrita": "^0.5.2", "zod": "^3.25.49", @@ -1611,7 +1612,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1628,7 +1628,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1645,7 +1644,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1662,7 +1660,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1679,7 +1676,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1696,7 +1692,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1713,7 +1708,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1730,7 +1724,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1747,7 +1740,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1764,7 +1756,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1781,7 +1772,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1798,7 +1788,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1815,7 +1804,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1832,7 +1820,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1849,7 +1836,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1866,7 +1852,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1899,7 +1884,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1916,7 +1900,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1933,7 +1916,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1950,7 +1932,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1967,7 +1948,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1984,7 +1964,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2001,7 +1980,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2018,7 +1996,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2035,7 +2012,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2168,7 +2144,7 @@ }, "node_modules/@jridgewell/source-map": { "version": "0.3.6", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -3938,6 +3914,74 @@ "dev": true, "license": "MIT" }, + "node_modules/@rollup/plugin-inject": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", + "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-inject/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.44.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.2.tgz", @@ -3945,7 +3989,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3959,7 +4002,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3973,7 +4015,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3987,7 +4028,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4001,7 +4041,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4015,7 +4054,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4029,7 +4067,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4043,7 +4080,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4057,7 +4093,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4071,7 +4106,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4085,7 +4119,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4099,7 +4132,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4113,7 +4145,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4127,7 +4158,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4141,7 +4171,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4168,7 +4197,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4182,7 +4210,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4196,7 +4223,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4210,7 +4236,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -7889,7 +7914,7 @@ }, "node_modules/acorn": { "version": "8.12.1", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "bin": { @@ -7977,6 +8002,36 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -8034,6 +8089,21 @@ "postcss": "^8.1.0" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/axios": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", @@ -8086,8 +8156,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/bignumber.js": { "version": "9.3.0", @@ -8112,6 +8181,12 @@ "dev": true, "license": "MIT" }, + "node_modules/bn.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -8131,6 +8206,12 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT" + }, "node_modules/brotli": { "version": "1.3.3", "license": "MIT", @@ -8139,6 +8220,142 @@ "base64-js": "^1.1.2" } }, + "node_modules/browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.17.0" + } + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-rsa/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/browserify-sign": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", + "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.5", + "hash-base": "~3.0", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.7", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/browserify-sign/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserify-zlib/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/browserslist": { "version": "4.24.0", "dev": true, @@ -8178,12 +8395,48 @@ "node": ">=0.10.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-from": { "version": "1.1.2", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT" + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "license": "MIT" + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -8194,6 +8447,53 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "license": "MIT", @@ -8384,9 +8684,42 @@ "node": ">=6.0" } }, - "node_modules/class-variance-authority": { - "version": "0.7.0", - "license": "Apache-2.0", + "node_modules/cipher-base": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cipher-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "license": "Apache-2.0", "dependencies": { "clsx": "2.0.0" }, @@ -8494,6 +8827,17 @@ "@floating-ui/utils": "^0.2.5" } }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "1.9.0", "license": "MIT" @@ -8537,6 +8881,55 @@ "node": ">= 6" } }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, "node_modules/cross-env": { "version": "7.0.3", "dev": true, @@ -8573,6 +8966,32 @@ "node": "*" } }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/css-color-keywords": { "version": "1.0.0", "license": "ISC", @@ -9185,6 +9604,40 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delaunator": { "version": "5.0.1", "license": "ISC", @@ -9206,6 +9659,16 @@ "node": ">=6" } }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/devlop": { "version": "1.1.0", "license": "MIT", @@ -9221,6 +9684,23 @@ "version": "1.2.2", "license": "Apache-2.0" }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, "node_modules/dlv": { "version": "1.1.3", "license": "MIT" @@ -9240,6 +9720,18 @@ "csstype": "^3.0.2" } }, + "node_modules/domain-browser": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.22.0.tgz", + "integrity": "sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/dompurify": { "version": "2.5.8", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.8.tgz", @@ -9260,6 +9752,20 @@ "version": "1.5.7", "license": "Apache-2.0" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/earcut": { "version": "2.2.4", "license": "ISC" @@ -9273,6 +9779,27 @@ "dev": true, "license": "ISC" }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, "node_modules/emoji-regex": { "version": "9.2.2", "license": "MIT" @@ -9325,6 +9852,24 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -9332,11 +9877,22 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.6", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -9381,7 +9937,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -9482,13 +10037,21 @@ }, "node_modules/events": { "version": "3.3.0", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, "node_modules/expect-type": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", @@ -9603,6 +10166,22 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/focus-trap": { "version": "7.6.0", "license": "MIT", @@ -9629,6 +10208,21 @@ } } }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/foreground-child": { "version": "3.3.0", "license": "ISC", @@ -9764,6 +10358,43 @@ "node": ">=10.19" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gl-matrix": { "version": "3.4.3", "license": "MIT" @@ -9802,6 +10433,18 @@ "license": "BSD-2-Clause", "peer": true }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "dev": true, @@ -9834,6 +10477,88 @@ "version": "0.7.8", "license": "SEE LICENSE IN LICENSE.txt" }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/hash-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { "version": "2.0.2", "license": "MIT", @@ -10056,6 +10781,17 @@ "version": "1.0.0", "license": "CC0-1.0" }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "license": "BSD-3-Clause", @@ -10116,6 +10852,12 @@ "node": ">= 14" } }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "license": "MIT" + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -10240,6 +10982,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "license": "MIT" @@ -10258,6 +11016,18 @@ "version": "1.1.6", "license": "MIT" }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.15.1", "license": "MIT", @@ -10298,6 +11068,24 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "license": "MIT", @@ -10316,6 +11104,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "license": "MIT", @@ -10340,6 +11144,39 @@ "dev": true, "license": "MIT" }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isarray": { "version": "1.0.0", "license": "MIT" @@ -10348,6 +11185,15 @@ "version": "2.0.0", "license": "ISC" }, + "node_modules/isomorphic-timers-promises": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-timers-promises/-/isomorphic-timers-promises-1.0.1.tgz", + "integrity": "sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/jackspeak": { "version": "3.4.3", "license": "BlueOak-1.0.0", @@ -10660,6 +11506,21 @@ "node": ">=6.11.5" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "license": "MIT" @@ -10767,7 +11628,6 @@ "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -10824,6 +11684,15 @@ "integrity": "sha512-4vRUvPyxdO8cWULGTh9dZWL2tZK6LDBvj+OGHBER7poH9Qdt7kXEoj20wiz4lQUbUXQZFjPbe5mVDo9nutizCw==", "license": "MIT" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/math.gl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/math.gl/-/math.gl-4.1.0.tgz", @@ -10841,6 +11710,17 @@ "is-buffer": "~1.1.6" } }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", @@ -11661,6 +12541,25 @@ "version": "0.2.2", "license": "MIT" }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, "node_modules/mime-db": { "version": "1.52.0", "license": "MIT", @@ -11678,6 +12577,18 @@ "node": ">= 0.6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT" + }, "node_modules/minimatch": { "version": "9.0.5", "license": "ISC", @@ -11812,6 +12723,64 @@ "dev": true, "license": "MIT" }, + "node_modules/node-stdlib-browser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-stdlib-browser/-/node-stdlib-browser-1.3.1.tgz", + "integrity": "sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==", + "license": "MIT", + "dependencies": { + "assert": "^2.0.0", + "browser-resolve": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^5.7.1", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "create-require": "^1.1.1", + "crypto-browserify": "^3.12.1", + "domain-browser": "4.22.0", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "isomorphic-timers-promises": "^1.0.1", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "pkg-dir": "^5.0.0", + "process": "^0.11.10", + "punycode": "^1.4.1", + "querystring-es3": "^0.2.1", + "readable-stream": "^3.6.0", + "stream-browserify": "^3.0.0", + "stream-http": "^3.2.0", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.1", + "url": "^0.11.4", + "util": "^0.12.4", + "vm-browserify": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-stdlib-browser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, + "node_modules/node-stdlib-browser/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "license": "MIT", @@ -11860,6 +12829,99 @@ "node": ">= 6" } }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "license": "MIT" + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-queue": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.4.1.tgz", @@ -11908,6 +12970,43 @@ "node": ">=6" } }, + "node_modules/parse-asn1": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "hash-base": "~3.0", + "pbkdf2": "^3.1.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse-asn1/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/parse-entities": { "version": "4.0.2", "license": "MIT", @@ -11974,6 +13073,21 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "license": "MIT", @@ -12038,6 +13152,74 @@ "pbf": "bin/pbf" } }, + "node_modules/pbkdf2": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", + "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", + "license": "MIT", + "dependencies": { + "create-hash": "~1.1.3", + "create-hmac": "^1.1.7", + "ripemd160": "=2.0.1", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.11", + "to-buffer": "^1.2.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/pbkdf2/node_modules/create-hash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "sha.js": "^2.4.0" + } + }, + "node_modules/pbkdf2/node_modules/hash-base": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1" + } + }, + "node_modules/pbkdf2/node_modules/ripemd160": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", + "license": "MIT", + "dependencies": { + "hash-base": "^2.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/pbkdf2/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -12068,6 +13250,18 @@ "node": ">= 6" } }, + "node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/point-in-polygon-hao": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/point-in-polygon-hao/-/point-in-polygon-hao-1.2.4.tgz", @@ -12094,6 +13288,15 @@ "splaytree": "^3.1.0" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -12306,6 +13509,15 @@ "node": ">=6" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "license": "MIT" @@ -12339,6 +13551,26 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "dev": true, @@ -12355,6 +13587,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/quadbin": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/quadbin/-/quadbin-0.4.2.tgz", @@ -12366,6 +13613,14 @@ "node": ">=18" } }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "funding": [ @@ -12401,13 +13656,21 @@ }, "node_modules/randombytes": { "version": "2.1.0", - "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "^5.1.0" } }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "node_modules/rbush": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", @@ -12854,6 +14117,16 @@ "node": ">=0.10.0" } }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, "node_modules/robust-predicates": { "version": "3.0.2", "license": "Unlicense" @@ -12862,7 +14135,6 @@ "version": "4.44.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.2.tgz", "integrity": "sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -12905,7 +14177,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12948,6 +14219,23 @@ "version": "5.1.2", "license": "MIT" }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "license": "MIT" @@ -13007,10 +14295,67 @@ "randombytes": "^2.1.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "license": "MIT" }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sha.js/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/shallowequal": { "version": "1.1.0", "license": "MIT" @@ -13032,6 +14377,78 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -13111,7 +14528,7 @@ }, "node_modules/source-map-support": { "version": "0.5.21", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -13121,7 +14538,7 @@ }, "node_modules/source-map-support/node_modules/source-map": { "version": "0.6.1", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "peer": true, "engines": { @@ -13168,6 +14585,56 @@ "dev": true, "license": "MIT" }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "license": "MIT", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/stream-http/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "license": "MIT", @@ -13527,7 +14994,7 @@ }, "node_modules/terser": { "version": "5.34.1", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "peer": true, "dependencies": { @@ -13579,7 +15046,7 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true }, @@ -13618,6 +15085,18 @@ "node": ">=0.8" } }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "license": "MIT", + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/timezone-groups": { "version": "0.10.2", "license": "MIT", @@ -13644,7 +15123,6 @@ "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.4.4", @@ -13661,7 +15139,6 @@ "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -13676,7 +15153,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -13740,6 +15216,46 @@ "dev": true, "license": "MIT" }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/to-buffer/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "license": "MIT", @@ -13903,6 +15419,12 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "license": "MIT" + }, "node_modules/turf-jsts": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz", @@ -13919,6 +15441,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/typedoc": { "version": "0.28.5", "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.5.tgz", @@ -14116,6 +15652,25 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "license": "MIT" + }, "node_modules/use-debounce": { "version": "10.0.3", "license": "MIT", @@ -14133,6 +15688,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "license": "MIT" @@ -14214,7 +15782,6 @@ "version": "6.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -14326,11 +15893,26 @@ } } }, + "node_modules/vite-plugin-node-polyfills": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.24.0.tgz", + "integrity": "sha512-GA9QKLH+vIM8NPaGA+o2t8PDfFUl32J8rUp1zQfMKVJQiNkOX4unE51tR6ppl6iKw5yOrDAdSH7r/UIFLCVhLw==", + "license": "MIT", + "dependencies": { + "@rollup/plugin-inject": "^5.0.5", + "node-stdlib-browser": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/davidmyersdev" + }, + "peerDependencies": { + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, "node_modules/vite/node_modules/fdir": { "version": "6.4.6", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", - "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -14345,7 +15927,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -14485,6 +16066,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -14646,6 +16233,27 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -14866,6 +16474,18 @@ "node": ">= 14.6" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zarr": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/zarr/-/zarr-0.6.3.tgz", diff --git a/package.json b/package.json index efc4e8f27..97687f867 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "tailwindcss-animate": "^1.0.7", "use-debounce": "^10.0.2", "uuid": "^11.0.3", + "vite-plugin-node-polyfills": "^0.24.0", "wordcloud": "^1.2.2", "zarrita": "^0.5.2", "zod": "^3.25.49", diff --git a/vite.config.mts b/vite.config.mts index 791597766..f732d396b 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -5,6 +5,9 @@ import type { RollupOptions } from 'rollup'; // Import RollupOptions from rollup import * as path from 'node:path'; import { execSync } from 'node:child_process'; import * as fs from 'node:fs'; +// zarrita seems to need a polyfill for Buffer - seems like a bug +import { nodePolyfills } from 'vite-plugin-node-polyfills' + const flaskURL = "http://127.0.0.1:5055"; const port = 5170; @@ -170,6 +173,7 @@ export default defineConfig(env => { }, }, plugins: [ + nodePolyfills(), react({ include: [/\.tsx?$/, /\.jsx?$/], babel: { From dafee62cce47f2eb4b53c04c668e577da7c77cc2 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Thu, 7 Aug 2025 08:36:25 +0000 Subject: [PATCH 08/17] add spatialdata dependency (and zarr by association) --- python/poetry.lock | 1494 ++++++++++++++++++++++++++++++++++++++++- python/pyproject.toml | 1 + 2 files changed, 1484 insertions(+), 11 deletions(-) diff --git a/python/poetry.lock b/python/poetry.lock index c210fba82..9582c052c 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -1,5 +1,31 @@ # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +[[package]] +name = "aiobotocore" +version = "2.23.2" +description = "Async client for aws services using botocore and aiohttp" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiobotocore-2.23.2-py3-none-any.whl", hash = "sha256:5ca24feb49be73bd6cd92e82e95aefb0647c07bb85ca57000a0361b9554503d8"}, + {file = "aiobotocore-2.23.2.tar.gz", hash = "sha256:9c2cbd6e813bb6c60b7f20fc11897976a583c57b0093a87bebfe80a9b08746b2"}, +] + +[package.dependencies] +aiohttp = ">=3.9.2,<4.0.0" +aioitertools = ">=0.5.1,<1.0.0" +botocore = ">=1.39.7,<1.39.9" +jmespath = ">=0.7.1,<2.0.0" +multidict = ">=6.0.0,<7.0.0" +python-dateutil = ">=2.1,<3.0.0" +wrapt = ">=1.10.10,<2.0.0" + +[package.extras] +awscli = ["awscli (>=1.41.7,<1.41.9)"] +boto3 = ["boto3 (>=1.39.7,<1.39.9)"] +httpx = ["httpx (>=0.25.1,<0.29)"] + [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -121,6 +147,22 @@ yarl = ">=1.17.0,<2.0" [package.extras] speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "brotlicffi ; platform_python_implementation != \"CPython\""] +[[package]] +name = "aioitertools" +version = "0.12.0" +description = "itertools and builtins for AsyncIO and mixed iterables" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "aioitertools-0.12.0-py3-none-any.whl", hash = "sha256:fc1f5fac3d737354de8831cbba3eb04f79dd649d8f3afb4c5b114925e662a796"}, + {file = "aioitertools-0.12.0.tar.gz", hash = "sha256:c2a9055b4fbb7705f561b9d86053e8af5d10cc845d22c32008c43490b2d8dd6b"}, +] + +[package.extras] +dev = ["attribution (==1.8.0)", "black (==24.8.0)", "build (>=1.2)", "coverage (==7.6.1)", "flake8 (==7.1.1)", "flit (==3.9.0)", "mypy (==1.11.2)", "ufmt (==2.7.1)", "usort (==1.0.8.post1)"] +docs = ["sphinx (==8.0.2)", "sphinx-mdinclude (==0.6.2)"] + [[package]] name = "aiosignal" version = "1.3.2" @@ -294,6 +336,17 @@ types-python-dateutil = ">=2.8.10" doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] +[[package]] +name = "asciitree" +version = "0.3.3" +description = "Draws ASCII trees." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "asciitree-0.3.3.tar.gz", hash = "sha256:4aa4b9b649f85e3fcb343363d97564aa1fb62e249677f2e18a96765145cc0f6e"}, +] + [[package]] name = "astroid" version = "3.3.10" @@ -501,6 +554,26 @@ files = [ {file = "blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf"}, ] +[[package]] +name = "botocore" +version = "1.39.8" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "botocore-1.39.8-py3-none-any.whl", hash = "sha256:ab43f79c6893271934faba7ae1987a313d59576361c544c70a5391ade560891d"}, + {file = "botocore-1.39.8.tar.gz", hash = "sha256:3848bd9057ea8dbc059e7764eda63bda575727ad1101dbd03636ab4a6f283fa5"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} + +[package.extras] +crt = ["awscrt (==0.23.8)"] + [[package]] name = "cachetools" version = "5.5.2" @@ -723,6 +796,18 @@ files = [ [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} +[[package]] +name = "cloudpickle" +version = "3.1.1" +description = "Pickler class to extend the standard pickle.Pickler functionality" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e"}, + {file = "cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64"}, +] + [[package]] name = "colorama" version = "0.4.6" @@ -736,6 +821,26 @@ files = [ ] markers = {main = "platform_system == \"Windows\"", auth = "platform_system == \"Windows\"", backend = "platform_system == \"Windows\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", docs = "sys_platform == \"win32\""} +[[package]] +name = "colorcet" +version = "3.1.0" +description = "Collection of perceptually uniform colormaps" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "colorcet-3.1.0-py3-none-any.whl", hash = "sha256:2a7d59cc8d0f7938eeedd08aad3152b5319b4ba3bcb7a612398cc17a384cb296"}, + {file = "colorcet-3.1.0.tar.gz", hash = "sha256:2921b3cd81a2288aaf2d63dbc0ce3c26dcd882e8c389cc505d6886bf7aa9a4eb"}, +] + +[package.extras] +all = ["colorcet[doc]", "colorcet[examples]", "colorcet[tests-extra]", "colorcet[tests]"] +doc = ["colorcet[examples]", "nbsite (>=0.8.4)", "sphinx-copybutton"] +examples = ["bokeh", "holoviews", "matplotlib", "numpy"] +tests = ["packaging", "pre-commit", "pytest (>=2.8.5)", "pytest-cov"] +tests-examples = ["colorcet[examples]", "nbval"] +tests-extra = ["colorcet[tests]", "pytest-mpl"] + [[package]] name = "comm" version = "0.2.2" @@ -907,6 +1012,83 @@ files = [ docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] tests = ["pytest", "pytest-cov", "pytest-xdist"] +[[package]] +name = "dask" +version = "2024.11.2" +description = "Parallel PyData with Task Scheduling" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "dask-2024.11.2-py3-none-any.whl", hash = "sha256:6115c4b76015e8d9d9c2922b6a0a1c850e283fb7fee74eebbd2e28e9c117c30d"}, + {file = "dask-2024.11.2.tar.gz", hash = "sha256:9a72bee3f149ff89bc492340d4bcba33d5dd3e3a9d471d2b4b3872f2d71ddaae"}, +] + +[package.dependencies] +click = ">=8.1" +cloudpickle = ">=3.0.0" +dask-expr = {version = ">=1.1,<1.2", optional = true, markers = "extra == \"dataframe\""} +fsspec = ">=2021.09.0" +importlib-metadata = {version = ">=4.13.0", markers = "python_version < \"3.12\""} +numpy = {version = ">=1.24", optional = true, markers = "extra == \"array\""} +packaging = ">=20.0" +pandas = {version = ">=2.0", optional = true, markers = "extra == \"dataframe\""} +partd = ">=1.4.0" +pyyaml = ">=5.3.1" +toolz = ">=0.10.0" + +[package.extras] +array = ["numpy (>=1.24)"] +complete = ["dask[array,dataframe,diagnostics,distributed]", "lz4 (>=4.3.2)", "pyarrow (>=14.0.1)"] +dataframe = ["dask-expr (>=1.1,<1.2)", "dask[array]", "pandas (>=2.0)"] +diagnostics = ["bokeh (>=3.1.0)", "jinja2 (>=2.10.3)"] +distributed = ["distributed (==2024.11.2)"] +test = ["pandas[test]", "pre-commit", "pytest", "pytest-cov", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist"] + +[[package]] +name = "dask-expr" +version = "1.1.19" +description = "High Level Expressions for Dask" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "dask_expr-1.1.19-py3-none-any.whl", hash = "sha256:b2931c20241a3bc1978ccccc4b8a2f7b27b15bb85ce89fec04595bc5bcf20cf5"}, + {file = "dask_expr-1.1.19.tar.gz", hash = "sha256:5c8a50924bf6718bb630d58f11311e3d928c7037af913e096fa0eb3f0a493b9f"}, +] + +[package.dependencies] +dask = "2024.11.2" +pandas = ">=2" +pyarrow = ">=14.0.1" + +[package.extras] +analyze = ["crick", "distributed", "graphviz"] + +[[package]] +name = "dask-image" +version = "2024.5.3" +description = "Distributed image processing" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "dask_image-2024.5.3-py3-none-any.whl", hash = "sha256:b936f6d19a52974094cf8b7c6b51a3fdea77d9c7c80ca9862d764eda7ffbcd9a"}, + {file = "dask_image-2024.5.3.tar.gz", hash = "sha256:0c9f1fdf0215800aada7a706ae6d263f5995fe1dbf87b657f797ca1dec48823c"}, +] + +[package.dependencies] +dask = {version = ">=2024.4.1", extras = ["array", "dataframe"]} +numpy = ">=1.18" +pandas = ">=2.0.0" +pims = ">=0.4.1" +scipy = ">=1.7.0" +tifffile = ">=2018.10.18" + +[package.extras] +gpu = ["cupy (>=9.0.0)"] +test = ["Flake8-pyproject", "build (>=1.2.1)", "coverage (>=7.2.1)", "flake8 (>=6.0.0)", "pytest (>=7.2.2)", "pytest-cov (>=4.0.0)", "pytest-flake8 (>=1.1.1)", "pytest-timeout (>=2.3.1)", "twine (>=3.1.1)"] + [[package]] name = "dataclasses-json" version = "0.6.7" @@ -923,6 +1105,35 @@ files = [ marshmallow = ">=3.18.0,<4.0.0" typing-inspect = ">=0.4.0,<1" +[[package]] +name = "datashader" +version = "0.18.2" +description = "Data visualization toolchain based on aggregating into a grid" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "datashader-0.18.2-py3-none-any.whl", hash = "sha256:2aa90e867a46b1e75248f32a47c5b14bb5dc869524152f88c0af8369d47359e7"}, + {file = "datashader-0.18.2.tar.gz", hash = "sha256:53ef0bc35b9ba1034bdf290a2e28babf0fa2b075a3e5a822c79dcde12183d1ff"}, +] + +[package.dependencies] +colorcet = "*" +multipledispatch = "*" +numba = "*" +numpy = "*" +packaging = "*" +pandas = "*" +param = "*" +pyct = "*" +requests = "*" +scipy = "*" +toolz = "*" +xarray = "*" + +[package.extras] +tests = ["pytest"] + [[package]] name = "debugpy" version = "1.8.14" @@ -983,6 +1194,55 @@ files = [ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] +[[package]] +name = "deprecated" +version = "1.2.18" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, + {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] + +[[package]] +name = "distributed" +version = "2024.11.2" +description = "Distributed scheduler for Dask" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "distributed-2024.11.2-py3-none-any.whl", hash = "sha256:a455ae031689aee151172b3492de5dd637b67531720619c171df988974985cdf"}, + {file = "distributed-2024.11.2.tar.gz", hash = "sha256:60e430ab9d438102535f342521bc2673db08fcd373a4474b042caf9fe9fcf990"}, +] + +[package.dependencies] +click = ">=8.0" +cloudpickle = ">=3.0.0" +dask = "2024.11.2" +jinja2 = ">=2.10.3" +locket = ">=1.0.0" +msgpack = ">=1.0.2" +packaging = ">=20.0" +psutil = ">=5.8.0" +pyyaml = ">=5.4.1" +sortedcontainers = ">=2.0.5" +tblib = ">=1.6.0" +toolz = ">=0.11.2" +tornado = ">=6.2.0" +urllib3 = ">=1.26.5" +zict = ">=3.0.0" + [[package]] name = "distro" version = "1.9.0" @@ -1397,6 +1657,49 @@ files = [ {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, ] +[[package]] +name = "fsspec" +version = "2025.7.0" +description = "File-system specification" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21"}, + {file = "fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58"}, +] + +[package.dependencies] +s3fs = {version = "*", optional = true, markers = "extra == \"s3\""} + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff (>=0.5)"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard ; python_version < \"3.14\""] +tqdm = ["tqdm"] + [[package]] name = "generate-tiff-offsets" version = "0.1.9" @@ -1415,6 +1718,30 @@ tifffile = ">=2023.9.26" [package.extras] test = ["pytest"] +[[package]] +name = "geopandas" +version = "1.1.1" +description = "Geographic pandas extensions" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "geopandas-1.1.1-py3-none-any.whl", hash = "sha256:589e61aaf39b19828843df16cb90234e72897e2579be236f10eee0d052ad98e8"}, + {file = "geopandas-1.1.1.tar.gz", hash = "sha256:1745713f64d095c43e72e08e753dbd271678254b24f2e01db8cdb8debe1d293d"}, +] + +[package.dependencies] +numpy = ">=1.24" +packaging = "*" +pandas = ">=2.0.0" +pyogrio = ">=0.7.2" +pyproj = ">=3.5.0" +shapely = ">=2.0.0" + +[package.extras] +all = ["GeoAlchemy2", "SQLAlchemy (>=2.0)", "folium", "geopy", "mapclassify (>=2.5)", "matplotlib (>=3.7)", "psycopg[binary] (>=3.1.0)", "pyarrow (>=10.0.0)", "scipy", "xyzservices"] +dev = ["codecov", "pre-commit", "pytest (>=3.1.0)", "pytest-cov", "pytest-xdist", "ruff"] + [[package]] name = "gevent" version = "25.5.1" @@ -1811,6 +2138,40 @@ test = ["Pillow (>=9) ; platform_python_implementation != \"PyPy\"", "cairocffi test-musl = ["cairocffi (>=1.2.0)", "networkx (>=2.5)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)"] test-win-arm64 = ["cairocffi (>=1.2.0)", "networkx (>=2.5)", "pytest (>=7.0.1)", "pytest-timeout (>=2.1.0)"] +[[package]] +name = "imageio" +version = "2.37.0" +description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed"}, + {file = "imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=8.3.2" + +[package.extras] +all-plugins = ["astropy", "av", "imageio-ffmpeg", "numpy (>2)", "pillow-heif", "psutil", "rawpy", "tifffile"] +all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"] +build = ["wheel"] +dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"] +docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] +ffmpeg = ["imageio-ffmpeg", "psutil"] +fits = ["astropy"] +full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpy (>2)", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "rawpy", "sphinx (<6)", "tifffile", "wheel"] +gdal = ["gdal"] +itk = ["itk"] +linting = ["black", "flake8"] +pillow-heif = ["pillow-heif"] +pyav = ["av"] +rawpy = ["numpy (>2)", "rawpy"] +test = ["fsspec[github]", "pytest", "pytest-cov"] +tifffile = ["tifffile"] + [[package]] name = "imagesize" version = "1.4.1" @@ -1823,6 +2184,31 @@ files = [ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, ] +[[package]] +name = "importlib-metadata" +version = "8.7.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.12\"" +files = [ + {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, + {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + [[package]] name = "iniconfig" version = "2.1.0" @@ -2111,6 +2497,18 @@ files = [ {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, ] +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + [[package]] name = "joblib" version = "1.5.1" @@ -2668,6 +3066,26 @@ requests-toolbelt = ">=1.0.0,<2.0.0" [package.extras] langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] +[[package]] +name = "lazy-loader" +version = "0.4" +description = "Makes it easy to load subpackages and functions on demand." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"}, + {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"}, +] + +[package.dependencies] +packaging = "*" + +[package.extras] +dev = ["changelist (==0.5)"] +lint = ["pre-commit (==3.7.0)"] +test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] + [[package]] name = "legacy-api-wrap" version = "1.4.1" @@ -2761,6 +3179,43 @@ files = [ {file = "llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4"}, ] +[[package]] +name = "locket" +version = "1.0.0" +description = "File-based locks for Python on Linux and Windows" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +files = [ + {file = "locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3"}, + {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"}, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "3.0.2" @@ -2925,6 +3380,18 @@ files = [ [package.dependencies] traitlets = "*" +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mistune" version = "3.1.3" @@ -2940,6 +3407,76 @@ files = [ [package.dependencies] typing-extensions = {version = "*", markers = "python_version < \"3.11\""} +[[package]] +name = "msgpack" +version = "1.1.1" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "msgpack-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:353b6fc0c36fde68b661a12949d7d49f8f51ff5fa019c1e47c87c4ff34b080ed"}, + {file = "msgpack-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79c408fcf76a958491b4e3b103d1c417044544b68e96d06432a189b43d1215c8"}, + {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78426096939c2c7482bf31ef15ca219a9e24460289c00dd0b94411040bb73ad2"}, + {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b17ba27727a36cb73aabacaa44b13090feb88a01d012c0f4be70c00f75048b4"}, + {file = "msgpack-1.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a17ac1ea6ec3c7687d70201cfda3b1e8061466f28f686c24f627cae4ea8efd0"}, + {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:88d1e966c9235c1d4e2afac21ca83933ba59537e2e2727a999bf3f515ca2af26"}, + {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f6d58656842e1b2ddbe07f43f56b10a60f2ba5826164910968f5933e5178af75"}, + {file = "msgpack-1.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96decdfc4adcbc087f5ea7ebdcfd3dee9a13358cae6e81d54be962efc38f6338"}, + {file = "msgpack-1.1.1-cp310-cp310-win32.whl", hash = "sha256:6640fd979ca9a212e4bcdf6eb74051ade2c690b862b679bfcb60ae46e6dc4bfd"}, + {file = "msgpack-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8b65b53204fe1bd037c40c4148d00ef918eb2108d24c9aaa20bc31f9810ce0a8"}, + {file = "msgpack-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:71ef05c1726884e44f8b1d1773604ab5d4d17729d8491403a705e649116c9558"}, + {file = "msgpack-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:36043272c6aede309d29d56851f8841ba907a1a3d04435e43e8a19928e243c1d"}, + {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a32747b1b39c3ac27d0670122b57e6e57f28eefb725e0b625618d1b59bf9d1e0"}, + {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b10fdb84a43e50d38057b06901ec9da52baac6983d3f709d8507f3889d43f"}, + {file = "msgpack-1.1.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0c325c3f485dc54ec298d8b024e134acf07c10d494ffa24373bea729acf704"}, + {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:88daaf7d146e48ec71212ce21109b66e06a98e5e44dca47d853cbfe171d6c8d2"}, + {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8b55ea20dc59b181d3f47103f113e6f28a5e1c89fd5b67b9140edb442ab67f2"}, + {file = "msgpack-1.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a28e8072ae9779f20427af07f53bbb8b4aa81151054e882aee333b158da8752"}, + {file = "msgpack-1.1.1-cp311-cp311-win32.whl", hash = "sha256:7da8831f9a0fdb526621ba09a281fadc58ea12701bc709e7b8cbc362feabc295"}, + {file = "msgpack-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fd1b58e1431008a57247d6e7cc4faa41c3607e8e7d4aaf81f7c29ea013cb458"}, + {file = "msgpack-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae497b11f4c21558d95de9f64fff7053544f4d1a17731c866143ed6bb4591238"}, + {file = "msgpack-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:33be9ab121df9b6b461ff91baac6f2731f83d9b27ed948c5b9d1978ae28bf157"}, + {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f64ae8fe7ffba251fecb8408540c34ee9df1c26674c50c4544d72dbf792e5ce"}, + {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a494554874691720ba5891c9b0b39474ba43ffb1aaf32a5dac874effb1619e1a"}, + {file = "msgpack-1.1.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb643284ab0ed26f6957d969fe0dd8bb17beb567beb8998140b5e38a90974f6c"}, + {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d275a9e3c81b1093c060c3837e580c37f47c51eca031f7b5fb76f7b8470f5f9b"}, + {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fd6b577e4541676e0cc9ddc1709d25014d3ad9a66caa19962c4f5de30fc09ef"}, + {file = "msgpack-1.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb29aaa613c0a1c40d1af111abf025f1732cab333f96f285d6a93b934738a68a"}, + {file = "msgpack-1.1.1-cp312-cp312-win32.whl", hash = "sha256:870b9a626280c86cff9c576ec0d9cbcc54a1e5ebda9cd26dab12baf41fee218c"}, + {file = "msgpack-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:5692095123007180dca3e788bb4c399cc26626da51629a31d40207cb262e67f4"}, + {file = "msgpack-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3765afa6bd4832fc11c3749be4ba4b69a0e8d7b728f78e68120a157a4c5d41f0"}, + {file = "msgpack-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8ddb2bcfd1a8b9e431c8d6f4f7db0773084e107730ecf3472f1dfe9ad583f3d9"}, + {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:196a736f0526a03653d829d7d4c5500a97eea3648aebfd4b6743875f28aa2af8"}, + {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d592d06e3cc2f537ceeeb23d38799c6ad83255289bb84c2e5792e5a8dea268a"}, + {file = "msgpack-1.1.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4df2311b0ce24f06ba253fda361f938dfecd7b961576f9be3f3fbd60e87130ac"}, + {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e4141c5a32b5e37905b5940aacbc59739f036930367d7acce7a64e4dec1f5e0b"}, + {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b1ce7f41670c5a69e1389420436f41385b1aa2504c3b0c30620764b15dded2e7"}, + {file = "msgpack-1.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4147151acabb9caed4e474c3344181e91ff7a388b888f1e19ea04f7e73dc7ad5"}, + {file = "msgpack-1.1.1-cp313-cp313-win32.whl", hash = "sha256:500e85823a27d6d9bba1d057c871b4210c1dd6fb01fbb764e37e4e8847376323"}, + {file = "msgpack-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:6d489fba546295983abd142812bda76b57e33d0b9f5d5b71c09a583285506f69"}, + {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bba1be28247e68994355e028dcd668316db30c1f758d3241a7b903ac78dcd285"}, + {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8f93dcddb243159c9e4109c9750ba5b335ab8d48d9522c5308cd05d7e3ce600"}, + {file = "msgpack-1.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fbbc0b906a24038c9958a1ba7ae0918ad35b06cb449d398b76a7d08470b0ed9"}, + {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:61e35a55a546a1690d9d09effaa436c25ae6130573b6ee9829c37ef0f18d5e78"}, + {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1abfc6e949b352dadf4bce0eb78023212ec5ac42f6abfd469ce91d783c149c2a"}, + {file = "msgpack-1.1.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:996f2609ddf0142daba4cefd767d6db26958aac8439ee41db9cc0db9f4c4c3a6"}, + {file = "msgpack-1.1.1-cp38-cp38-win32.whl", hash = "sha256:4d3237b224b930d58e9d83c81c0dba7aacc20fcc2f89c1e5423aa0529a4cd142"}, + {file = "msgpack-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:da8f41e602574ece93dbbda1fab24650d6bf2a24089f9e9dbb4f5730ec1e58ad"}, + {file = "msgpack-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5be6b6bc52fad84d010cb45433720327ce886009d862f46b26d4d154001994b"}, + {file = "msgpack-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a89cd8c087ea67e64844287ea52888239cbd2940884eafd2dcd25754fb72232"}, + {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d75f3807a9900a7d575d8d6674a3a47e9f227e8716256f35bc6f03fc597ffbf"}, + {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d182dac0221eb8faef2e6f44701812b467c02674a322c739355c39e94730cdbf"}, + {file = "msgpack-1.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b13fe0fb4aac1aa5320cd693b297fe6fdef0e7bea5518cbc2dd5299f873ae90"}, + {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:435807eeb1bc791ceb3247d13c79868deb22184e1fc4224808750f0d7d1affc1"}, + {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4835d17af722609a45e16037bb1d4d78b7bdf19d6c0128116d178956618c4e88"}, + {file = "msgpack-1.1.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8ef6e342c137888ebbfb233e02b8fbd689bb5b5fcc59b34711ac47ebd504478"}, + {file = "msgpack-1.1.1-cp39-cp39-win32.whl", hash = "sha256:61abccf9de335d9efd149e2fff97ed5974f2481b3353772e8e2dd3402ba2bd57"}, + {file = "msgpack-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:40eae974c873b2992fd36424a5d9407f93e97656d999f43fca9d29f820899084"}, + {file = "msgpack-1.1.1.tar.gz", hash = "sha256:77b79ce34a2bdab2594f490c8e80dd62a02d650b91a75159a63ec413b8d104cd"}, +] + [[package]] name = "mudata" version = "0.2.3" @@ -3085,6 +3622,45 @@ files = [ [package.dependencies] typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} +[[package]] +name = "multipledispatch" +version = "1.0.0" +description = "Multiple dispatch" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "multipledispatch-1.0.0-py3-none-any.whl", hash = "sha256:0c53cd8b077546da4e48869f49b13164bebafd0c2a5afceb6bb6a316e7fb46e4"}, + {file = "multipledispatch-1.0.0.tar.gz", hash = "sha256:5c839915465c68206c3e9c473357908216c28383b425361e5d144594bf85a7e0"}, +] + +[[package]] +name = "multiscale-spatial-image" +version = "2.0.2" +description = "Generate a multiscale, chunked, multi-dimensional spatial image data structure that can be serialized to OME-NGFF." +optional = false +python-versions = "<3.13,>=3.10" +groups = ["main"] +files = [ + {file = "multiscale_spatial_image-2.0.2-py3-none-any.whl", hash = "sha256:0c25fe7d743293bec06811ccd19842c0998dcdb99b58a0102116340d97d7dfcc"}, + {file = "multiscale_spatial_image-2.0.2.tar.gz", hash = "sha256:3b0ef86559fd9369ca384b657a740edfaf567b308021ac51281a3efdb6ccdcfc"}, +] + +[package.dependencies] +dask = "*" +numpy = "*" +python-dateutil = "*" +spatial-image = ">=0.2.1" +xarray = ">=2024.10.0" +zarr = "*" + +[package.extras] +dask-image = ["dask-image"] +imagej = ["pyimagej"] +itk = ["itk-filtering (>=5.3.0)"] +notebooks = ["matplotlib (>=3.9.2,<4)", "ome-types (>=0.5.1.post1,<0.6)", "tqdm (>=4.66.4,<5)"] +test = ["dask-image", "fsspec", "ipfsspec", "itk-filtering (>=5.3.0)", "jsonschema", "nbmake", "pooch", "pytest", "pytest-mypy", "urllib3"] + [[package]] name = "mypy-extensions" version = "1.1.0" @@ -3316,12 +3892,88 @@ llvmlite = "==0.44.*" numpy = ">=1.24,<2.3" [[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" +name = "numcodecs" +version = "0.13.1" +description = "A Python package providing buffer compression and transformation codecs for use in data storage and communication applications." optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "numcodecs-0.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:96add4f783c5ce57cc7e650b6cac79dd101daf887c479a00a29bc1487ced180b"}, + {file = "numcodecs-0.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:237b7171609e868a20fd313748494444458ccd696062f67e198f7f8f52000c15"}, + {file = "numcodecs-0.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96e42f73c31b8c24259c5fac6adba0c3ebf95536e37749dc6c62ade2989dca28"}, + {file = "numcodecs-0.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:eda7d7823c9282e65234731fd6bd3986b1f9e035755f7fed248d7d366bb291ab"}, + {file = "numcodecs-0.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2eda97dd2f90add98df6d295f2c6ae846043396e3d51a739ca5db6c03b5eb666"}, + {file = "numcodecs-0.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a86f5367af9168e30f99727ff03b27d849c31ad4522060dde0bce2923b3a8bc"}, + {file = "numcodecs-0.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233bc7f26abce24d57e44ea8ebeb5cd17084690b4e7409dd470fdb75528d615f"}, + {file = "numcodecs-0.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:796b3e6740107e4fa624cc636248a1580138b3f1c579160f260f76ff13a4261b"}, + {file = "numcodecs-0.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5195bea384a6428f8afcece793860b1ab0ae28143c853f0b2b20d55a8947c917"}, + {file = "numcodecs-0.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3501a848adaddce98a71a262fee15cd3618312692aa419da77acd18af4a6a3f6"}, + {file = "numcodecs-0.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2230484e6102e5fa3cc1a5dd37ca1f92dfbd183d91662074d6f7574e3e8f53"}, + {file = "numcodecs-0.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:e5db4824ebd5389ea30e54bc8aeccb82d514d28b6b68da6c536b8fa4596f4bca"}, + {file = "numcodecs-0.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a60d75179fd6692e301ddfb3b266d51eb598606dcae7b9fc57f986e8d65cb43"}, + {file = "numcodecs-0.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f593c7506b0ab248961a3b13cb148cc6e8355662ff124ac591822310bc55ecf"}, + {file = "numcodecs-0.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80d3071465f03522e776a31045ddf2cfee7f52df468b977ed3afdd7fe5869701"}, + {file = "numcodecs-0.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:90d3065ae74c9342048ae0046006f99dcb1388b7288da5a19b3bddf9c30c3176"}, + {file = "numcodecs-0.13.1.tar.gz", hash = "sha256:a3cf37881df0898f3a9c0d4477df88133fe85185bffe57ba31bcc2fa207709bc"}, +] + +[package.dependencies] +numpy = ">=1.7" + +[package.extras] +docs = ["mock", "numpydoc", "pydata-sphinx-theme", "sphinx", "sphinx-issues"] +msgpack = ["msgpack"] +pcodec = ["pcodec (>=0.2.0)"] +test = ["coverage", "pytest", "pytest-cov"] +test-extras = ["importlib-metadata"] +zfpy = ["numpy (<2.0.0)", "zfpy (>=1.0.0)"] + +[[package]] +name = "numcodecs" +version = "0.15.1" +description = "A Python package providing buffer compression and transformation codecs for use in data storage and communication applications." +optional = false +python-versions = ">=3.11" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "numcodecs-0.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:698f1d59511488b8fe215fadc1e679a4c70d894de2cca6d8bf2ab770eed34dfd"}, + {file = "numcodecs-0.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bef8c8e64fab76677324a07672b10c31861775d03fc63ed5012ca384144e4bb9"}, + {file = "numcodecs-0.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdfaef9f5f2ed8f65858db801f1953f1007c9613ee490a1c56233cd78b505ed5"}, + {file = "numcodecs-0.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:e2547fa3a7ffc9399cfd2936aecb620a3db285f2630c86c8a678e477741a4b3c"}, + {file = "numcodecs-0.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b0a9d9cd29a0088220682dda4a9898321f7813ff7802be2bbb545f6e3d2f10ff"}, + {file = "numcodecs-0.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a34f0fe5e5f3b837bbedbeb98794a6d4a12eeeef8d4697b523905837900b5e1c"}, + {file = "numcodecs-0.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a09e22140f2c691f7df26303ff8fa2dadcf26d7d0828398c0bc09b69e5efa3"}, + {file = "numcodecs-0.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:daed6066ffcf40082da847d318b5ab6123d69ceb433ba603cb87c323a541a8bc"}, + {file = "numcodecs-0.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3d82b70500cf61e8d115faa0d0a76be6ecdc24a16477ee3279d711699ad85f3"}, + {file = "numcodecs-0.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1d471a1829ce52d3f365053a2bd1379e32e369517557c4027ddf5ac0d99c591e"}, + {file = "numcodecs-0.15.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dfdea4a67108205edfce99c1cb6cd621343bc7abb7e16a041c966776920e7de"}, + {file = "numcodecs-0.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:a4f7bdb26f1b34423cb56d48e75821223be38040907c9b5954eeb7463e7eb03c"}, + {file = "numcodecs-0.15.1.tar.gz", hash = "sha256:eeed77e4d6636641a2cc605fbc6078c7a8f2cc40f3dfa2b3f61e52e6091b04ff"}, +] + +[package.dependencies] +deprecated = "*" +numpy = ">=1.24" + +[package.extras] +crc32c = ["crc32c (>=2.7)"] +docs = ["numpydoc", "pydata-sphinx-theme", "sphinx", "sphinx-issues"] +msgpack = ["msgpack"] +pcodec = ["pcodec (>=0.3,<0.4)"] +test = ["coverage", "pytest", "pytest-cov"] +test-extras = ["importlib_metadata"] +zfpy = ["zfpy (>=1.0.0)"] + +[[package]] +name = "numpy" +version = "1.26.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -3423,6 +4075,56 @@ pint = ["pint (>=0.15)"] test = ["lxml", "mypy", "numpy", "pdbpp ; sys_platform != \"win32\"", "pint", "pytest", "pytest-codspeed", "pytest-cov", "pytest-mypy-plugins", "ruff", "types-lxml ; python_version >= \"3.8\"", "xmlschema (<2.5)", "xsdata[cli]"] test-qt = ["pytest-qt", "qtpy"] +[[package]] +name = "ome-zarr" +version = "0.10.2" +description = "Implementation of images in Zarr files." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "ome_zarr-0.10.2-py3-none-any.whl", hash = "sha256:524f17e2e2d7277fc11da0c527e743b427cc3953ec27d82215fff1c4c11adb00"}, + {file = "ome_zarr-0.10.2.tar.gz", hash = "sha256:9fcc401681708a67c4449d2d085c5d3182bb4d371c34c87b6dc603d485f62df3"}, +] + +[package.dependencies] +aiohttp = "<4" +dask = "*" +distributed = "*" +fsspec = {version = ">=0.8,<2021.07.0 || >2021.07.0", extras = ["s3"]} +numpy = "*" +requests = "*" +scikit-image = "*" +toolz = "*" +zarr = ">=2.8.1,<3" + +[[package]] +name = "ome-zarr" +version = "0.11.1" +description = "Implementation of images in Zarr files." +optional = false +python-versions = ">3.10" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "ome_zarr-0.11.1-py3-none-any.whl", hash = "sha256:e8ffc93de78558be990ed3b1b2b186b97ca0c7eb2cb460a7cffb861f4f13a8f1"}, + {file = "ome_zarr-0.11.1.tar.gz", hash = "sha256:f178e336412eaefdb74172b280095cd9bd497bfeb235c1b1c2b0965c21aa1af0"}, +] + +[package.dependencies] +aiohttp = "<4" +dask = "*" +fsspec = {version = ">=0.8,<2021.07.0 || >2021.07.0,<2023.9.0 || >2023.9.0", extras = ["s3"]} +numpy = "*" +requests = "*" +scikit-image = "*" +toolz = "*" +zarr = ">=2.8.1,<3" + +[package.extras] +tests = ["pytest"] + [[package]] name = "openai" version = "1.93.0" @@ -3657,6 +4359,26 @@ files = [ {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, ] +[[package]] +name = "param" +version = "2.2.1" +description = "Make your Python code clearer and more reliable by declaring Parameters." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "param-2.2.1-py3-none-any.whl", hash = "sha256:e3a4ca7f3d7610615129a55dbde2e90eb67d11cef70936487b0a59717dba0bdc"}, + {file = "param-2.2.1.tar.gz", hash = "sha256:ba1f7cec6455ea8ad96f641f4082759bf1057dcbe629aa79d956b25973252422"}, +] + +[package.extras] +all = ["aiohttp", "cloudpickle", "gmpy", "ipython", "jsonschema", "nbval", "nest-asyncio", "numpy", "odfpy", "openpyxl", "pandas", "panel", "pyarrow", "pytest", "pytest-asyncio", "pytest-xdist", "tables", "xlrd"] +examples = ["aiohttp", "pandas", "panel"] +tests = ["pytest", "pytest-asyncio"] +tests-deser = ["odfpy", "openpyxl", "pyarrow", "tables", "xlrd"] +tests-examples = ["aiohttp", "nbval", "pandas", "panel", "pytest", "pytest-asyncio", "pytest-xdist"] +tests-full = ["aiohttp", "cloudpickle", "gmpy", "ipython", "jsonschema", "nbval", "nest-asyncio", "numpy", "odfpy", "openpyxl", "pandas", "panel", "pyarrow", "pytest", "pytest-asyncio", "pytest-xdist", "tables", "xlrd"] + [[package]] name = "parso" version = "0.8.4" @@ -3673,6 +4395,25 @@ files = [ qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["docopt", "pytest"] +[[package]] +name = "partd" +version = "1.4.2" +description = "Appendable key-value storage" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f"}, + {file = "partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c"}, +] + +[package.dependencies] +locket = "*" +toolz = "*" + +[package.extras] +complete = ["blosc", "numpy (>=1.20.0)", "pandas (>=1.3)", "pyzmq"] + [[package]] name = "patsy" version = "1.0.1" @@ -3805,6 +4546,24 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] +[[package]] +name = "pims" +version = "0.7" +description = "Python Image Sequence" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pims-0.7.tar.gz", hash = "sha256:55907a4c301256086d2aa4e34a5361b9109f24e375c2071e1117b9491e82946b"}, +] + +[package.dependencies] +imageio = "*" +numpy = ">=1.19" +packaging = "*" +slicerator = ">=0.9.8" +tifffile = "*" + [[package]] name = "platformdirs" version = "4.3.8" @@ -3838,6 +4597,28 @@ files = [ dev = ["pre-commit", "tox"] testing = ["coverage", "pytest", "pytest-benchmark"] +[[package]] +name = "pooch" +version = "1.8.2" +description = "A friend to fetch your data files" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pooch-1.8.2-py3-none-any.whl", hash = "sha256:3529a57096f7198778a5ceefd5ac3ef0e4d06a6ddaf9fc2d609b806f25302c47"}, + {file = "pooch-1.8.2.tar.gz", hash = "sha256:76561f0de68a01da4df6af38e9955c4c9d1a5c90da73f7e40276a5728ec83d10"}, +] + +[package.dependencies] +packaging = ">=20.0" +platformdirs = ">=2.5.0" +requests = ">=2.19.0" + +[package.extras] +progress = ["tqdm (>=4.41.0,<5.0.0)"] +sftp = ["paramiko (>=2.7.0)"] +xxhash = ["xxhash (>=1.4.3)"] + [[package]] name = "prometheus-client" version = "0.22.1" @@ -3982,7 +4763,7 @@ version = "7.0.0" description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." optional = false python-versions = ">=3.6" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, @@ -3995,6 +4776,7 @@ files = [ {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, ] +markers = {main = "python_version == \"3.10\""} [package.extras] dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] @@ -4059,6 +4841,62 @@ files = [ [package.extras] tests = ["pytest"] +[[package]] +name = "pyarrow" +version = "21.0.0" +description = "Python library for Apache Arrow" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyarrow-21.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e563271e2c5ff4d4a4cbeb2c83d5cf0d4938b891518e676025f7268c6fe5fe26"}, + {file = "pyarrow-21.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fee33b0ca46f4c85443d6c450357101e47d53e6c3f008d658c27a2d020d44c79"}, + {file = "pyarrow-21.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7be45519b830f7c24b21d630a31d48bcebfd5d4d7f9d3bdb49da9cdf6d764edb"}, + {file = "pyarrow-21.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:26bfd95f6bff443ceae63c65dc7e048670b7e98bc892210acba7e4995d3d4b51"}, + {file = "pyarrow-21.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bd04ec08f7f8bd113c55868bd3fc442a9db67c27af098c5f814a3091e71cc61a"}, + {file = "pyarrow-21.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9b0b14b49ac10654332a805aedfc0147fb3469cbf8ea951b3d040dab12372594"}, + {file = "pyarrow-21.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9d9f8bcb4c3be7738add259738abdeddc363de1b80e3310e04067aa1ca596634"}, + {file = "pyarrow-21.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c077f48aab61738c237802836fc3844f85409a46015635198761b0d6a688f87b"}, + {file = "pyarrow-21.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:689f448066781856237eca8d1975b98cace19b8dd2ab6145bf49475478bcaa10"}, + {file = "pyarrow-21.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:479ee41399fcddc46159a551705b89c05f11e8b8cb8e968f7fec64f62d91985e"}, + {file = "pyarrow-21.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:40ebfcb54a4f11bcde86bc586cbd0272bac0d516cfa539c799c2453768477569"}, + {file = "pyarrow-21.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8d58d8497814274d3d20214fbb24abcad2f7e351474357d552a8d53bce70c70e"}, + {file = "pyarrow-21.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:585e7224f21124dd57836b1530ac8f2df2afc43c861d7bf3d58a4870c42ae36c"}, + {file = "pyarrow-21.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:555ca6935b2cbca2c0e932bedd853e9bc523098c39636de9ad4693b5b1df86d6"}, + {file = "pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd"}, + {file = "pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876"}, + {file = "pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d"}, + {file = "pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e"}, + {file = "pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82"}, + {file = "pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623"}, + {file = "pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18"}, + {file = "pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a"}, + {file = "pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe"}, + {file = "pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd"}, + {file = "pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61"}, + {file = "pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d"}, + {file = "pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99"}, + {file = "pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636"}, + {file = "pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da"}, + {file = "pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7"}, + {file = "pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6"}, + {file = "pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8"}, + {file = "pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503"}, + {file = "pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79"}, + {file = "pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10"}, + {file = "pyarrow-21.0.0-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:a7f6524e3747e35f80744537c78e7302cd41deee8baa668d56d55f77d9c464b3"}, + {file = "pyarrow-21.0.0-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:203003786c9fd253ebcafa44b03c06983c9c8d06c3145e37f1b76a1f317aeae1"}, + {file = "pyarrow-21.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3b4d97e297741796fead24867a8dabf86c87e4584ccc03167e4a811f50fdf74d"}, + {file = "pyarrow-21.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:898afce396b80fdda05e3086b4256f8677c671f7b1d27a6976fa011d3fd0a86e"}, + {file = "pyarrow-21.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:067c66ca29aaedae08218569a114e413b26e742171f526e828e1064fcdec13f4"}, + {file = "pyarrow-21.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0c4e75d13eb76295a49e0ea056eb18dbd87d81450bfeb8afa19a7e5a75ae2ad7"}, + {file = "pyarrow-21.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:cdc4c17afda4dab2a9c0b79148a43a7f4e1094916b3e18d8975bfd6d6d52241f"}, + {file = "pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc"}, +] + +[package.extras] +test = ["cffi", "hypothesis", "pandas", "pytest", "pytz"] + [[package]] name = "pyasn1" version = "0.6.1" @@ -4099,6 +4937,27 @@ files = [ ] markers = {main = "sys_platform == \"win32\" and platform_python_implementation == \"CPython\"", auth = "platform_python_implementation != \"PyPy\""} +[[package]] +name = "pyct" +version = "0.5.0" +description = "Python package common tasks for users (e.g. copy examples, fetch data, ...)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pyct-0.5.0-py2.py3-none-any.whl", hash = "sha256:a4038a8885059ab8cac6f946ea30e0b5e6bdbe0b92b6723f06737035f9d65e8c"}, + {file = "pyct-0.5.0.tar.gz", hash = "sha256:dd9f4ac5cbd8e37c352c04036062d3c5f67efec76d404761ef16b0cbf26aa6a0"}, +] + +[package.dependencies] +param = ">=1.7.0" + +[package.extras] +build = ["param (>=1.7.0)", "setuptools"] +cmd = ["pyyaml", "requests"] +doc = ["nbsite", "sphinx-ioam-theme"] +tests = ["flake8", "pytest"] + [[package]] name = "pydantic" version = "2.11.7" @@ -4258,7 +5117,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["dev", "docs"] +groups = ["main", "dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -4304,6 +5163,58 @@ numba = ">=0.51.2" scikit-learn = ">=0.18" scipy = ">=1.0" +[[package]] +name = "pyogrio" +version = "0.11.1" +description = "Vectorized spatial vector file format I/O using GDAL/OGR" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pyogrio-0.11.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:838ead7df8388d938ce848354e384ae5aa46fe7c5f74f9da2d58f064bda053f7"}, + {file = "pyogrio-0.11.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:6f51aa9fc3632e6dcb3dd5562b4a56a3a31850c3f630aef3587d5889a1f65275"}, + {file = "pyogrio-0.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4982107653ce30de395678b50a1ee00299a4cfcb41043778f1b66c5911b8adbe"}, + {file = "pyogrio-0.11.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:7b20ffbf72013d464012d8f0f69322459a6528bef08c85f85b8a42b056f730b0"}, + {file = "pyogrio-0.11.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:5b8d60ead740b366cdc2f3b076d21349e5a5d4b9a0e6726922c5a031206b93b2"}, + {file = "pyogrio-0.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:1948027b2809f2248f69b069ab9833d56b53658f182a3b418d12d3d3eb9959d7"}, + {file = "pyogrio-0.11.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d36162ddc1a309bb941a3cfb550b8f88c862c67ef2f52df6460100e5e958bbc6"}, + {file = "pyogrio-0.11.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:845c78d5e7c9ec1c7d00250c07e144e5fe504fdb4ccdc141d9413f85b8c55c91"}, + {file = "pyogrio-0.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50aa869509f189fa1bff4d90d2d4c7860b963e693af85f2957646306e882b631"}, + {file = "pyogrio-0.11.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0f44dd2d849d32aea3f73647c74083996917e446479645bf93de6656160f2d"}, + {file = "pyogrio-0.11.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:36b910d4037694b2935b5b1c1eb757dcc2906dca05cb2992cbdaf1291b54ff97"}, + {file = "pyogrio-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:cb744097f302f19dcc5c93ee5e9cfd707b864c9a418e399f0908406a60003728"}, + {file = "pyogrio-0.11.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f186456ebe5d5f61e7bd883bad25a59d43d6304178d4f0d3e03273f42b40a4cc"}, + {file = "pyogrio-0.11.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b8a199bc0e421eac444af96942b7553268e43d0cadf30d0d6d41017de05b7e9e"}, + {file = "pyogrio-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afce80b4b32f043fcf76a50e8572e3ad8d9d3e6abbbfa6137f0975ba55c4eeb8"}, + {file = "pyogrio-0.11.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0cfd79caf0b8cb7bbf30b419dff7f21509169efcf4d431172c61b44fe1029dba"}, + {file = "pyogrio-0.11.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ab3aa6dbf2441d2407ce052233f2966324a3cff752bd43d99e4c779ea54e0a16"}, + {file = "pyogrio-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:cd10035eb3b5e5a43bdafbd777339d2274e9b75972658364f0ce31c4d3400d1e"}, + {file = "pyogrio-0.11.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3b368c597357ff262f3b46591ded86409462ee594ef42556708b090d121f873c"}, + {file = "pyogrio-0.11.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:1cb82cfd3493f32396e9c3f9255e17885610f62a323870947f4e04dd59bc3595"}, + {file = "pyogrio-0.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d61aae22e67030fd354f03e21c6462537bf56160134dd8663709335a5a46b28"}, + {file = "pyogrio-0.11.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:76150a3cd787c31628191c7abc6f8c796660125852fb65ae15dd7be1e9196816"}, + {file = "pyogrio-0.11.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e929452f6988c0365dd32ff2485d9488160a709fee28743abbbc18d663169ed0"}, + {file = "pyogrio-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6d56862b89a05fccd7211171c88806b6ec9b5effb79bf807cce0a57c1f2a606"}, + {file = "pyogrio-0.11.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:9ae8efbe4f9f215b2321655f988be8bb133829037dbefebc2643f52da4e7782a"}, + {file = "pyogrio-0.11.1-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:7cbbc24a785cca733b80c96e8e10f7c316df295786ac9900c145e2b12f828050"}, + {file = "pyogrio-0.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e924de96f1a436567fb57cd94b02b2572c066663c5b6431d2827993d8f3a646"}, + {file = "pyogrio-0.11.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:580001084562b55059f161b8c8f2c15135a4523256a3b910ea3a58cd8ffb6c4f"}, + {file = "pyogrio-0.11.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:56d2315f28cdbde98c23f719c85a0f0ee1953a1eae617505c7349c660847dbf5"}, + {file = "pyogrio-0.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:db372785b2a32ad6006477366c4c07285d98f7a7e6d356b2eba15a4fbaaa167f"}, + {file = "pyogrio-0.11.1.tar.gz", hash = "sha256:e1441dc9c866f10d8e6ae7ea9249a10c1f57ea921b1f19a5b0977ab91ef8082c"}, +] + +[package.dependencies] +certifi = "*" +numpy = "*" +packaging = "*" + +[package.extras] +benchmark = ["pytest-benchmark"] +dev = ["cython"] +geopandas = ["geopandas"] +test = ["pytest", "pytest-cov"] + [[package]] name = "pyparsing" version = "3.2.3" @@ -4319,6 +5230,52 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyproj" +version = "3.7.1" +description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "pyproj-3.7.1-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:bf09dbeb333c34e9c546364e7df1ff40474f9fddf9e70657ecb0e4f670ff0b0e"}, + {file = "pyproj-3.7.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:6575b2e53cc9e3e461ad6f0692a5564b96e7782c28631c7771c668770915e169"}, + {file = "pyproj-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cb516ee35ed57789b46b96080edf4e503fdb62dbb2e3c6581e0d6c83fca014b"}, + {file = "pyproj-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e47c4e93b88d99dd118875ee3ca0171932444cdc0b52d493371b5d98d0f30ee"}, + {file = "pyproj-3.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3e8d276caeae34fcbe4813855d0d97b9b825bab8d7a8b86d859c24a6213a5a0d"}, + {file = "pyproj-3.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f173f851ee75e54acdaa053382b6825b400cb2085663a9bb073728a59c60aebb"}, + {file = "pyproj-3.7.1-cp310-cp310-win32.whl", hash = "sha256:f550281ed6e5ea88fcf04a7c6154e246d5714be495c50c9e8e6b12d3fb63e158"}, + {file = "pyproj-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3537668992a709a2e7f068069192138618c00d0ba113572fdd5ee5ffde8222f3"}, + {file = "pyproj-3.7.1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:a94e26c1a4950cea40116775588a2ca7cf56f1f434ff54ee35a84718f3841a3d"}, + {file = "pyproj-3.7.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:263b54ba5004b6b957d55757d846fc5081bc02980caa0279c4fc95fa0fff6067"}, + {file = "pyproj-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6d6a2ccd5607cd15ef990c51e6f2dd27ec0a741e72069c387088bba3aab60fa"}, + {file = "pyproj-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c5dcf24ede53d8abab7d8a77f69ff1936c6a8843ef4fcc574646e4be66e5739"}, + {file = "pyproj-3.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c2e7449840a44ce860d8bea2c6c1c4bc63fa07cba801dcce581d14dcb031a02"}, + {file = "pyproj-3.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0829865c1d3a3543f918b3919dc601eea572d6091c0dd175e1a054db9c109274"}, + {file = "pyproj-3.7.1-cp311-cp311-win32.whl", hash = "sha256:6181960b4b812e82e588407fe5c9c68ada267c3b084db078f248db5d7f45d18a"}, + {file = "pyproj-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ad0ff443a785d84e2b380869fdd82e6bfc11eba6057d25b4409a9bbfa867970"}, + {file = "pyproj-3.7.1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:2781029d90df7f8d431e29562a3f2d8eafdf233c4010d6fc0381858dc7373217"}, + {file = "pyproj-3.7.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d61bf8ab04c73c1da08eedaf21a103b72fa5b0a9b854762905f65ff8b375d394"}, + {file = "pyproj-3.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04abc517a8555d1b05fcee768db3280143fe42ec39fdd926a2feef31631a1f2f"}, + {file = "pyproj-3.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084c0a475688f934d386c2ab3b6ce03398a473cd48adfda70d9ab8f87f2394a0"}, + {file = "pyproj-3.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a20727a23b1e49c7dc7fe3c3df8e56a8a7acdade80ac2f5cca29d7ca5564c145"}, + {file = "pyproj-3.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bf84d766646f1ebd706d883755df4370aaf02b48187cedaa7e4239f16bc8213d"}, + {file = "pyproj-3.7.1-cp312-cp312-win32.whl", hash = "sha256:5f0da2711364d7cb9f115b52289d4a9b61e8bca0da57f44a3a9d6fc9bdeb7274"}, + {file = "pyproj-3.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:aee664a9d806612af30a19dba49e55a7a78ebfec3e9d198f6a6176e1d140ec98"}, + {file = "pyproj-3.7.1-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:5f8d02ef4431dee414d1753d13fa82a21a2f61494737b5f642ea668d76164d6d"}, + {file = "pyproj-3.7.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b853ae99bda66cbe24b4ccfe26d70601d84375940a47f553413d9df570065e0"}, + {file = "pyproj-3.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83db380c52087f9e9bdd8a527943b2e7324f275881125e39475c4f9277bdeec4"}, + {file = "pyproj-3.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b35ed213892e211a3ce2bea002aa1183e1a2a9b79e51bb3c6b15549a831ae528"}, + {file = "pyproj-3.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8b15b0463d1303bab113d1a6af2860a0d79013c3a66fcc5475ce26ef717fd4f"}, + {file = "pyproj-3.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:87229e42b75e89f4dad6459200f92988c5998dfb093c7c631fb48524c86cd5dc"}, + {file = "pyproj-3.7.1-cp313-cp313-win32.whl", hash = "sha256:d666c3a3faaf3b1d7fc4a544059c4eab9d06f84a604b070b7aa2f318e227798e"}, + {file = "pyproj-3.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:d3caac7473be22b6d6e102dde6c46de73b96bc98334e577dfaee9886f102ea2e"}, + {file = "pyproj-3.7.1.tar.gz", hash = "sha256:60d72facd7b6b79853f19744779abcd3f804c4e0d4fa8815469db20c9f640a47"}, +] + +[package.dependencies] +certifi = "*" + [[package]] name = "pyright" version = "1.1.402" @@ -4909,6 +5866,25 @@ files = [ {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, ] +[[package]] +name = "rich" +version = "14.1.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "rpds-py" version = "0.25.1" @@ -5078,6 +6054,27 @@ files = [ {file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"}, ] +[[package]] +name = "s3fs" +version = "2025.7.0" +description = "Convenient Filesystem interface over S3" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "s3fs-2025.7.0-py3-none-any.whl", hash = "sha256:b6b2d3f84b6aa1c2ba5e62e39dd9410cf54f10a2cce1ea6db1ba0d1a6bcce685"}, + {file = "s3fs-2025.7.0.tar.gz", hash = "sha256:5e7f9ec0cad7745155e3eb86fae15b1481fa29946bf5b3a4ce3a60701ce6022d"}, +] + +[package.dependencies] +aiobotocore = ">=2.5.4,<3.0.0" +aiohttp = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1" +fsspec = "2025.7.0" + +[package.extras] +awscli = ["aiobotocore[awscli] (>=2.5.4,<3.0.0)"] +boto3 = ["aiobotocore[boto3] (>=2.5.4,<3.0.0)"] + [[package]] name = "scanpy" version = "1.11.2" @@ -5131,6 +6128,56 @@ skmisc = ["scikit-misc (>=0.1.4)"] test = ["dask-ml", "dask[array] (>=2022.09.2)", "igraph", "igraph (>=0.10.8)", "leidenalg (>=0.9.0)", "louvain (>=0.8.2)", "pytest (>=8.2.2)", "pytest-cov", "pytest-mock", "pytest-randomly", "pytest-rerunfailures", "pytest-xdist[psutil]", "scikit-image", "scikit-misc (>=0.1.4)", "tuna", "zarr (<3)"] test-min = ["pytest (>=8.2.2)", "pytest-cov", "pytest-mock", "pytest-randomly", "pytest-rerunfailures", "pytest-xdist[psutil]", "tuna"] +[[package]] +name = "scikit-image" +version = "0.25.2" +description = "Image processing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78"}, + {file = "scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063"}, + {file = "scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99"}, + {file = "scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09"}, + {file = "scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054"}, + {file = "scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17"}, + {file = "scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0"}, + {file = "scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173"}, + {file = "scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641"}, + {file = "scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b"}, + {file = "scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb"}, + {file = "scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed"}, + {file = "scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d"}, + {file = "scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824"}, + {file = "scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2"}, + {file = "scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da"}, + {file = "scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc"}, + {file = "scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341"}, + {file = "scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147"}, + {file = "scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f"}, + {file = "scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd"}, + {file = "scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde"}, +] + +[package.dependencies] +imageio = ">=2.33,<2.35.0 || >2.35.0" +lazy-loader = ">=0.4" +networkx = ">=3.0" +numpy = ">=1.24" +packaging = ">=21" +pillow = ">=10.1" +scipy = ">=1.11.4" +tifffile = ">=2022.8.12" + +[package.extras] +build = ["Cython (>=3.0.8)", "build (>=1.2.1)", "meson-python (>=0.16)", "ninja (>=1.11.1.1)", "numpy (>=2.0)", "pythran (>=0.16)", "spin (==0.13)"] +data = ["pooch (>=1.6.0)"] +developer = ["ipython", "pre-commit", "tomli ; python_version < \"3.11\""] +docs = ["PyWavelets (>=1.6)", "dask[array] (>=2023.2.0)", "intersphinx-registry (>=0.2411.14)", "ipykernel", "ipywidgets", "kaleido (==0.2.1)", "matplotlib (>=3.7)", "myst-parser", "numpydoc (>=1.7)", "pandas (>=2.0)", "plotly (>=5.20)", "pooch (>=1.6)", "pydata-sphinx-theme (>=0.16)", "pytest-doctestplus", "scikit-learn (>=1.2)", "seaborn (>=0.11)", "sphinx (>=8.0)", "sphinx-copybutton", "sphinx-gallery[parallel] (>=0.18)", "sphinx_design (>=0.5)", "tifffile (>=2022.8.12)"] +optional = ["PyWavelets (>=1.6)", "SimpleITK", "astropy (>=5.0)", "cloudpickle (>=1.1.1)", "dask[array] (>=2023.2.0)", "matplotlib (>=3.7)", "pooch (>=1.6.0)", "pyamg (>=5.2)", "scikit-learn (>=1.2)"] +test = ["asv", "numpydoc (>=1.7)", "pooch (>=1.6.0)", "pytest (>=8)", "pytest-cov (>=2.11.0)", "pytest-doctestplus", "pytest-faulthandler", "pytest-localserver"] + [[package]] name = "scikit-learn" version = "1.7.0" @@ -5376,6 +6423,64 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov ; platform_python_implementation != \"PyPy\"", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "shapely" +version = "2.1.1" +description = "Manipulation and analysis of geometric objects" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "shapely-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d8ccc872a632acb7bdcb69e5e78df27213f7efd195882668ffba5405497337c6"}, + {file = "shapely-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f24f2ecda1e6c091da64bcbef8dd121380948074875bd1b247b3d17e99407099"}, + {file = "shapely-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45112a5be0b745b49e50f8829ce490eb67fefb0cea8d4f8ac5764bfedaa83d2d"}, + {file = "shapely-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c10ce6f11904d65e9bbb3e41e774903c944e20b3f0b282559885302f52f224a"}, + {file = "shapely-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:61168010dfe4e45f956ffbbaf080c88afce199ea81eb1f0ac43230065df320bd"}, + {file = "shapely-2.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cacf067cdff741cd5c56a21c52f54ece4e4dad9d311130493a791997da4a886b"}, + {file = "shapely-2.1.1-cp310-cp310-win32.whl", hash = "sha256:23b8772c3b815e7790fb2eab75a0b3951f435bc0fce7bb146cb064f17d35ab4f"}, + {file = "shapely-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:2c7b2b6143abf4fa77851cef8ef690e03feade9a0d48acd6dc41d9e0e78d7ca6"}, + {file = "shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7"}, + {file = "shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea"}, + {file = "shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7"}, + {file = "shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753"}, + {file = "shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647"}, + {file = "shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0"}, + {file = "shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab"}, + {file = "shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93"}, + {file = "shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43"}, + {file = "shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad"}, + {file = "shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9"}, + {file = "shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef"}, + {file = "shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1"}, + {file = "shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d"}, + {file = "shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8"}, + {file = "shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a"}, + {file = "shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48"}, + {file = "shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6"}, + {file = "shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c"}, + {file = "shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a"}, + {file = "shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de"}, + {file = "shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8"}, + {file = "shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52"}, + {file = "shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97"}, + {file = "shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d"}, + {file = "shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05"}, + {file = "shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0"}, + {file = "shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913"}, + {file = "shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d"}, + {file = "shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9"}, + {file = "shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db"}, + {file = "shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7"}, + {file = "shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772"}, +] + +[package.dependencies] +numpy = ">=1.21" + +[package.extras] +docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] +test = ["pytest", "pytest-cov", "scipy-doctest"] + [[package]] name = "simple-websocket" version = "1.1.0" @@ -5407,6 +6512,18 @@ files = [ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +[[package]] +name = "slicerator" +version = "1.1.0" +description = "A lazy-loading, fancy-sliceable iterable." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "slicerator-1.1.0-py3-none-any.whl", hash = "sha256:167668d48c6d3a5ba0bd3d54b2688e81ee267dc20aef299e547d711e6f3c441a"}, + {file = "slicerator-1.1.0.tar.gz", hash = "sha256:44010a7f5cd87680c07213b5cabe81d1fb71252962943e5373ee7d14605d6046"}, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -5431,6 +6548,19 @@ files = [ {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + [[package]] name = "soupsieve" version = "2.7" @@ -5443,6 +6573,71 @@ files = [ {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, ] +[[package]] +name = "spatial-image" +version = "1.2.1" +description = "A multi-dimensional spatial image data structure for scientific Python." +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "spatial_image-1.2.1-py3-none-any.whl", hash = "sha256:312c7f289284ed7f72eae50ca1b29b25b74db73f6daff745666af5ec27ad5436"}, + {file = "spatial_image-1.2.1.tar.gz", hash = "sha256:84b5eae611674c7951e8019d10f7c0c2698d7e1e4bc1f4329fdc35fe3fdc0ccd"}, +] + +[package.dependencies] +numpy = "*" +xarray = ">=2024.10.0" +xarray-dataclasses = "*" + +[[package]] +name = "spatialdata" +version = "0.4.0" +description = "Spatial data format." +optional = false +python-versions = "<3.13,>=3.10" +groups = ["main"] +files = [ + {file = "spatialdata-0.4.0-py3-none-any.whl", hash = "sha256:f0a3f76efda57a221a775686a4d376093bfea560492697f40820eb3ec6d6ea89"}, + {file = "spatialdata-0.4.0.tar.gz", hash = "sha256:6c7d1abf366fd5e61e9c9b489d04ad644a9609489588fa3436e59f4c96b67846"}, +] + +[package.dependencies] +anndata = ">=0.9.1" +click = "*" +dask = ">=2024.4.1,<=2024.11.2" +dask-image = "*" +fsspec = "*" +geopandas = ">=0.14" +multiscale-spatial-image = ">=2.0.2" +networkx = "*" +numba = ">=0.55.0" +numpy = "*" +ome-zarr = ">=0.8.4" +pandas = "*" +pooch = "*" +pyarrow = "*" +rich = "*" +scikit-image = "*" +scipy = "*" +setuptools = "*" +shapely = ">=2.0.1" +spatial-image = ">=1.1.0" +typing-extensions = ">=4.8.0" +xarray = ">=2024.10.0" +xarray-dataclasses = ">=1.8.0" +xarray-schema = "*" +xarray-spatial = ">=0.3.5" +zarr = "<3" + +[package.extras] +benchmark = ["asv"] +dev = ["bump2version"] +docs = ["ipython (>=8.6.0)", "myst-nb", "sphinx (>=4.5)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx-book-theme (>=1.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pytest", "sphinxcontrib-bibtex (>=1.0.0)"] +extra = ["napari-spatialdata[all]", "spatialdata-io", "spatialdata-plot"] +test = ["pytest", "pytest-cov", "pytest-mock", "torch"] +torch = ["torch"] + [[package]] name = "sphinx" version = "7.4.7" @@ -5818,6 +7013,19 @@ files = [ [package.extras] widechars = ["wcwidth"] +[[package]] +name = "tblib" +version = "3.1.0" +description = "Traceback serialization library." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "tblib-3.1.0-py3-none-any.whl", hash = "sha256:670bb4582578134b3d81a84afa1b016128b429f3d48e6cbbaecc9d15675e984e"}, + {file = "tblib-3.1.0.tar.gz", hash = "sha256:06404c2c9f07f66fee2d7d6ad43accc46f9c3361714d9b8426e7f47e595cd652"}, +] + [[package]] name = "tenacity" version = "8.5.0" @@ -5886,7 +7094,7 @@ version = "2025.5.10" description = "Read and write TIFF files" optional = false python-versions = ">=3.10" -groups = ["dev"] +groups = ["main", "dev"] markers = "python_version == \"3.10\"" files = [ {file = "tifffile-2025.5.10-py3-none-any.whl", hash = "sha256:e37147123c0542d67bc37ba5cdd67e12ea6fbe6e86c52bee037a9eb6a064e5ad"}, @@ -5910,7 +7118,7 @@ version = "2025.6.11" description = "Read and write TIFF files" optional = false python-versions = ">=3.11" -groups = ["dev"] +groups = ["main", "dev"] markers = "python_version >= \"3.11\"" files = [ {file = "tifffile-2025.6.11-py3-none-any.whl", hash = "sha256:32effb78b10b3a283eb92d4ebf844ae7e93e151458b0412f38518b4e6d2d7542"}, @@ -6038,13 +7246,25 @@ files = [ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +[[package]] +name = "toolz" +version = "1.0.0" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236"}, + {file = "toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02"}, +] + [[package]] name = "tornado" version = "6.5.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7"}, {file = "tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6"}, @@ -6059,6 +7279,7 @@ files = [ {file = "tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7"}, {file = "tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c"}, ] +markers = {main = "python_version == \"3.10\""} [[package]] name = "tqdm" @@ -6295,6 +7516,95 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] +[[package]] +name = "wrapt" +version = "1.17.2" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, + {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, + {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, + {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, + {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, + {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, + {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, + {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, + {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, + {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, + {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, + {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, + {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, + {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, + {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, + {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, + {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, +] + [[package]] name = "wsproto" version = "1.2.0" @@ -6310,6 +7620,88 @@ files = [ [package.dependencies] h11 = ">=0.9.0,<1" +[[package]] +name = "xarray" +version = "2024.11.0" +description = "N-D labeled arrays and datasets in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "xarray-2024.11.0-py3-none-any.whl", hash = "sha256:6ee94f63ddcbdd0cf3909d1177f78cdac756640279c0e32ae36819a89cdaba37"}, + {file = "xarray-2024.11.0.tar.gz", hash = "sha256:1ccace44573ddb862e210ad3ec204210654d2c750bec11bbe7d842dfc298591f"}, +] + +[package.dependencies] +numpy = ">=1.24" +packaging = ">=23.2" +pandas = ">=2.1" + +[package.extras] +accel = ["bottleneck", "flox", "numba (>=0.54)", "numbagg", "opt_einsum", "scipy"] +complete = ["xarray[accel,etc,io,parallel,viz]"] +dev = ["hypothesis", "jinja2", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-env", "pytest-timeout", "pytest-xdist", "ruff", "sphinx", "sphinx_autosummary_accessors", "xarray[complete]"] +etc = ["sparse"] +io = ["cftime", "fsspec", "h5netcdf", "netCDF4", "pooch", "pydap ; python_version < \"3.10\"", "scipy", "zarr"] +parallel = ["dask[complete]"] +viz = ["cartopy", "matplotlib", "nc-time-axis", "seaborn"] + +[[package]] +name = "xarray-dataclasses" +version = "1.9.1" +description = "xarray data creation by data classes" +optional = false +python-versions = "<3.14,>=3.8" +groups = ["main"] +files = [ + {file = "xarray_dataclasses-1.9.1-py3-none-any.whl", hash = "sha256:3590556ef795ebbb33908b484a58311192d880cbb511666fe4c354569c3c468f"}, + {file = "xarray_dataclasses-1.9.1.tar.gz", hash = "sha256:e5e88e10622b2bcb4db6087d636327f6e0311ae66fa10e94ea571805ed6937ad"}, +] + +[package.dependencies] +numpy = {version = ">=1.22,<3.0", markers = "python_version >= \"3.9\" and python_version < \"3.14\""} +typing-extensions = ">=4.0,<5.0" +xarray = {version = ">=2022.3,<2025.0", markers = "python_version >= \"3.9\" and python_version < \"3.14\""} + +[[package]] +name = "xarray-schema" +version = "0.0.3" +description = "Schema validation for Xarray objects" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "xarray-schema-0.0.3.tar.gz", hash = "sha256:9c6c760489c0690a70394b2ad1368b32f8fa1333911c361b4adf249384212920"}, + {file = "xarray_schema-0.0.3-py3-none-any.whl", hash = "sha256:aa6f856626b2e100213ba290407797464608b2555bb8e0b26093a97fe1ba38ce"}, +] + +[package.dependencies] +numpy = ">=1.21" +xarray = ">=0.16" + +[[package]] +name = "xarray-spatial" +version = "0.4.0" +description = "xarray-based spatial analysis tools" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "xarray_spatial-0.4.0-py3-none-any.whl", hash = "sha256:6c207c90142f3fbba142e67bfd7f69767246aba6146aca9e130581b35799c9df"}, + {file = "xarray_spatial-0.4.0.tar.gz", hash = "sha256:cc2da7630acc49b1fefbeb3f4509c15d6eea93d12938ee8f617395a70a6a2da2"}, +] + +[package.dependencies] +datashader = ">=0.15.0" +numba = "*" +numpy = "*" +xarray = "*" + +[package.extras] +doc = ["Jinja2 (>=2.11)", "dask[dataframe]", "geopandas", "ipykernel", "matplotlib", "nbsphinx", "numpydoc", "pandoc", "pydata-sphinx-theme", "sphinx", "sphinx-panels", "sphinx-rtd-theme"] +optional = ["awkward (>=1.4)", "geopandas", "rtxpy", "shapely", "spatialpandas"] +tests = ["flake8", "isort", "noise (>=1.2.2)", "pytest", "pytest-cov"] + [[package]] name = "xsdata" version = "24.3.1" @@ -6451,6 +7843,86 @@ idna = ">=2.0" multidict = ">=4.0" propcache = ">=0.2.1" +[[package]] +name = "zarr" +version = "2.18.3" +description = "An implementation of chunked, compressed, N-dimensional arrays for Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "zarr-2.18.3-py3-none-any.whl", hash = "sha256:b1f7dfd2496f436745cdd4c7bcf8d3b4bc1dceef5fdd0d589c87130d842496dd"}, + {file = "zarr-2.18.3.tar.gz", hash = "sha256:2580d8cb6dd84621771a10d31c4d777dca8a27706a1a89b29f42d2d37e2df5ce"}, +] + +[package.dependencies] +asciitree = "*" +fasteners = {version = "*", markers = "sys_platform != \"emscripten\""} +numcodecs = ">=0.10.0" +numpy = ">=1.24" + +[package.extras] +docs = ["numcodecs[msgpack]", "numpydoc", "pydata-sphinx-theme", "sphinx", "sphinx-automodapi", "sphinx-copybutton", "sphinx-design", "sphinx-issues"] +jupyter = ["ipytree (>=0.2.2)", "ipywidgets (>=8.0.0)", "notebook"] + +[[package]] +name = "zarr" +version = "2.18.7" +description = "An implementation of chunked, compressed, N-dimensional arrays for Python" +optional = false +python-versions = ">=3.11" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "zarr-2.18.7-py3-none-any.whl", hash = "sha256:ac3dc4033e9ae4e9d7b5e27c97ea3eaf1003cc0a07f010bd83d5134bf8c4b223"}, + {file = "zarr-2.18.7.tar.gz", hash = "sha256:b2b8f66f14dac4af66b180d2338819981b981f70e196c9a66e6bfaa9e59572f5"}, +] + +[package.dependencies] +asciitree = "*" +fasteners = {version = "*", markers = "sys_platform != \"emscripten\""} +numcodecs = ">=0.10.0,<0.14.0 || >0.14.0,<0.14.1 || >0.14.1,<0.16" +numpy = ">=1.24" + +[package.extras] +docs = ["numcodecs[msgpack] (!=0.14.0,!=0.14.1,<0.16)", "numpydoc", "pydata-sphinx-theme", "pytest-doctestplus", "sphinx", "sphinx-automodapi", "sphinx-copybutton", "sphinx-issues", "sphinx_design"] +jupyter = ["ipytree (>=0.2.2)", "ipywidgets (>=8.0.0)", "notebook"] + +[[package]] +name = "zict" +version = "3.0.0" +description = "Mutable mapping tools" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version == \"3.10\"" +files = [ + {file = "zict-3.0.0-py2.py3-none-any.whl", hash = "sha256:5796e36bd0e0cc8cf0fbc1ace6a68912611c1dbd74750a3f3026b9b9d6a327ae"}, + {file = "zict-3.0.0.tar.gz", hash = "sha256:e321e263b6a97aafc0790c3cfb3c04656b7066e6738c37fffcca95d803c9fba5"}, +] + +[[package]] +name = "zipp" +version = "3.23.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.12\"" +files = [ + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + [[package]] name = "zope-event" version = "5.1" @@ -6528,4 +8000,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "8d9e1933a47e18f69dc35f535decc7c2c064304b38636f6cdf6aab3a170c733d" +content-hash = "e186ec42646bbbf91b2fed9f9e23b07d326ca12f26ea8d06005765e0c48a7609" diff --git a/python/pyproject.toml b/python/pyproject.toml index 161f90bb3..85f9cb8be 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -39,6 +39,7 @@ tabulate = "^0.9.0" python-dotenv = "^1.0.1" nbformat = "^5.10.4" ruff = "^0.3.7" +spatialdata = "^0.4.0" [tool.poetry.group.dev] optional = true From c4c5a4a29f5e81443aacd79e9bb5b17458b23766 Mon Sep 17 00:00:00 2001 From: Peter Todd Date: Thu, 7 Aug 2025 14:33:50 +0100 Subject: [PATCH 09/17] embryonic start to thinking about having zarr dataloader --- src/charts/charts.d.ts | 16 ++++++++++++++-- src/dataloaders/DataLoaderUtil.ts | 15 ++++++++++++--- src/dataloaders/ZarrDataLoader.ts | 15 +++++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 src/dataloaders/ZarrDataLoader.ts diff --git a/src/charts/charts.d.ts b/src/charts/charts.d.ts index b7ab8dfe2..bf6d992de 100644 --- a/src/charts/charts.d.ts +++ b/src/charts/charts.d.ts @@ -144,6 +144,16 @@ export type DataSourceLinks = Record< { rows_as_columns?: RowsAsColumnsLink } >; +/** + * This is a very provisional type for a way of specifying a zarr store that can be used to load data. + * + * I would prefer the definition for this to be in a schema, will need to get back to this later. + */ +export type ExperimentalZarrStore = { + path: string, + type: "xe_cells" | "xe_transcripts" | "xe_analysis" +} + /** * nb - there should be a type for the entries in `datasources.json`... * *but this is not that type!* even though it is being used in places where that type is expected. @@ -153,7 +163,7 @@ export type DataSourceLinks = Record< */ export type DataSource = { name: DataSourceName; - charts: Chart[]; + charts: Chart[]; //what's this? probably an artifact of me not understanding when I first wrote this... dataStore: DataStore; contentDiv: HTMLDivElement; menuBar: HTMLDivElement; @@ -162,7 +172,9 @@ export type DataSource = { links?: DataSourceLinks; size: number; columns: DataColumn[]; -}; + zarr?: ExperimentalZarrStore; +};// | ExperimentalZarrStore; ? maybe we can have a kind of datasource that just says what kind of preset type of store it is? or not. +// maybe that the type DropdownMappedValue = { [P in T]: string; diff --git a/src/dataloaders/DataLoaderUtil.ts b/src/dataloaders/DataLoaderUtil.ts index 1f1d7d692..130cdfaed 100644 --- a/src/dataloaders/DataLoaderUtil.ts +++ b/src/dataloaders/DataLoaderUtil.ts @@ -1,4 +1,3 @@ -import { createEl } from "@/utilities/ElementsTyped"; import { getArrayBufferDataLoader, getLocalCompressedBinaryDataLoader, @@ -8,6 +7,7 @@ import { isColumnLoaded } from "@/lib/columnTypeHelpers"; import { createMdvPortal } from "@/react/react_utils"; import ErrorComponentReactWrapper from "@/react/components/ErrorComponentReactWrapper"; import { decompressData } from "./DataLoaders"; +import getZarrDataLoader from "./ZarrDataLoader"; let projectRoot = ""; /** @@ -71,6 +71,12 @@ export function getProjectURL(url: string, trailingSlash = true) { if (p.startsWith("/")) return new URL(p, window.location.href).href; return p; } +export type DataLoader = { + function: (columns: DataColumn[], dataSource: string, size: number) => Promise, + viewLoader: (view: string) => Promise, + rowDataLoader: (dataSource: string, index: string) => Promise, + binaryDataLoader: (dataSource: string, name: string) => Promise, +} export function getDataLoader( isStaticFolder: boolean, @@ -79,8 +85,7 @@ export function getDataLoader( url: string, ) { const root = url.endsWith("/") ? url.substring(0, url.length - 1) : url; - - return isStaticFolder + const loader = isStaticFolder ? { function: getLocalCompressedBinaryDataLoader(datasources, root), viewLoader: async (view: string) => views[view], @@ -93,6 +98,10 @@ export function getDataLoader( rowDataLoader: loadRowData, binaryDataLoader: loadBinaryData, }; + if (datasources.some(ds => ds.zarr)) { + return getZarrDataLoader(datasources, root); + } + return loader; //PJT - want to clarify /binarydata/ & /rowdata/ folders, and use of .b vs .gz async function loadRowDataStatic(datasource: string, index: string) { diff --git a/src/dataloaders/ZarrDataLoader.ts b/src/dataloaders/ZarrDataLoader.ts new file mode 100644 index 000000000..abdf4a36e --- /dev/null +++ b/src/dataloaders/ZarrDataLoader.ts @@ -0,0 +1,15 @@ +import type { DataColumn, DataSource } from "@/charts/charts"; +import type { DataLoader } from "./DataLoaderUtil"; + + +export default function getZarrDataLoader(datasources: DataSource[], root: string): DataLoader { + throw new Error("Not implemented"); + // return { + // function: async (columns: DataColumn[], dataSource: string, size: number) => { + // const zarr = datasources.find(ds => ds.name === dataSource)?.zarr; + // }, + // viewLoader: async (view: string) => views[view], + // rowDataLoader: loadRowDataStatic, + // binaryDataLoader: loadBinaryDataStatic, + // } +}; \ No newline at end of file From 23816b105f5d3b389296b69a3c10ea7ee6846859 Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Tue, 12 Aug 2025 11:35:32 -0400 Subject: [PATCH 10/17] add Zarr metadata viewer dialog and menu integration Introduces a new ZarrMetadataDialog for exploring locally hosted Xenium Zarr dataset metadata, including dialog components, utility functions for metadata extraction, and integration into the menu bar with a new 'Add Remote Datasource' button. This enables users to fetch and inspect remote Zarr datasets directly from the UI. --- .../dialogs/zarrStream/ZarrMetadataDialog.tsx | 682 ++++++++++++++++++ .../zarrStream/ZarrMetadataDialogWrapper.tsx | 50 ++ .../dialogs/zarrStream/zarrMetadataUtils.ts | 455 ++++++++++++ src/react/components/MenuBarComponent.tsx | 17 +- 4 files changed, 1201 insertions(+), 3 deletions(-) create mode 100644 src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx create mode 100644 src/charts/dialogs/zarrStream/ZarrMetadataDialogWrapper.tsx create mode 100644 src/charts/dialogs/zarrStream/zarrMetadataUtils.ts diff --git a/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx b/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx new file mode 100644 index 000000000..3d9f3f46a --- /dev/null +++ b/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx @@ -0,0 +1,682 @@ +import type React from "react"; +import { + useState, + useCallback, + useReducer, + type PropsWithChildren, + forwardRef, + useEffect, +} from "react"; +import { observer } from "mobx-react-lite"; +import { useProject } from "../../../modules/ProjectContext"; +import { + CloudDownload as CloudDownloadIcon, + Folder as FolderIcon, + Image as ImageIcon, + TableChart as TableIcon, + Info as InfoIcon, +} from "@mui/icons-material"; +import ReusableDialog from "../ReusableDialog"; +import { debugExploreWithHTTP, debugExploreZarrDataset, extractZarrMetadata } from "./zarrMetadataUtils"; + +// Import zarr-js - you'll need to install this: npm install zarr +// import { openGroup } from "zarr"; + +export const Container = ({ children }: PropsWithChildren) => { + return ( +
+ {children} +
+ ); +}; + +const StatusContainer = ({ children }: PropsWithChildren) => { + return ( +
+ {children} +
+ ); +}; + +const MetadataContainer = ({ children }: PropsWithChildren) => ( +
+ {children} +
+); + +const SectionHeader = ({ children, icon }: PropsWithChildren & { icon?: React.ReactNode }) => ( +
+ {icon} +

{children}

+
+); + +const MetadataGrid = ({ children }: PropsWithChildren) => ( +
+ {children} +
+); + +const MetadataCard = ({ title, children }: PropsWithChildren & { title: string }) => ( +
+

{title}

+
{children}
+
+); + +const DataTable = ({ data, maxRows = 10 }: { data: Record; maxRows?: number }) => { + const entries = Object.entries(data).slice(0, maxRows); + + return ( +
+ + + + + + + + + {entries.map(([key, value]) => ( + + + + + ))} + +
KeyValue
{key} + {typeof value === 'object' ? JSON.stringify(value, null, 2).slice(0, 100) + '...' : String(value)} +
+ {Object.entries(data).length > maxRows && ( +
+ Showing {maxRows} of {Object.entries(data).length} entries +
+ )} +
+ ); +}; + +const Spinner = () => { + return ( +
+ ); +}; + +const colorStyles = { + blue: { + bgColor: "bg-blue-600", + hoverColor: "hover:bg-blue-700", + darkBgColor: "dark:bg-blue-800", + darkHoverColor: "dark:bg-blue-900", + }, + red: { + bgColor: "bg-red-600", + hoverColor: "hover:bg-red-700", + darkBgColor: "dark:bg-red-800", + darkHoverColor: "dark:bg-red-900", + }, + green: { + bgColor: "bg-green-600", + hoverColor: "hover:bg-green-700", + darkBgColor: "dark:bg-green-800", + darkHoverColor: "dark:bg-green-900", + }, + gray: { + bgColor: "bg-gray-600", + hoverColor: "hover:bg-gray-700", + darkBgColor: "dark:bg-gray-800", + darkHoverColor: "dark:bg-gray-900", + }, +}; + +type ButtonProps = { + onClick: () => void; + color?: "blue" | "red" | "green" | "gray"; + disabled?: boolean; + size?: string; + marginTop?: string; +} & PropsWithChildren; + +export const Button = ({ + onClick, + color = "blue", + disabled = false, + size = "px-5 py-2.5", + marginTop = "mt-2.5", + children, +}: ButtonProps) => { + const { bgColor, hoverColor, darkBgColor, darkHoverColor } = + colorStyles[color] || colorStyles.blue; + + return ( + + ); +}; + +const Message = ({ children }: PropsWithChildren) => ( +

+ {children} +

+); + +const ErrorContainer = ({ children }: PropsWithChildren) => ( +
+ {children} +
+); + +const ErrorHeading = ({ children }: PropsWithChildren) => ( +

{children}

+); + +// Types for Zarr metadata +interface ZarrMetadata { + datasetStructure: { + groups: string[]; + arrays: string[]; + }; + spatialData?: { + coordinateSystems: Record; + elements: Record; + transformations: any[]; + }; + images?: { + [key: string]: { + shape: number[]; + dtype: string; + chunks: number[]; + dimensions: string[]; + physicalSizeX?: number; + physicalSizeY?: number; + physicalSizeZ?: number; + units?: string; + channels?: string[]; + }; + }; + tables?: { + [key: string]: { + shape: number[]; + obsNames: string[]; + varNames: string[]; + observations: Record; + variables: Record; + embeddings?: string[]; + }; + }; + labels?: { + [key: string]: { + shape: number[]; + dtype: string; + labelValues: number[]; + categories?: string[]; + }; + }; + groupAttributes: Record; +} + +type MetadataActionType = + | "SET_URL" + | "SET_IS_LOADING" + | "SET_SUCCESS" + | "SET_ERROR" + | "SET_METADATA"; + +const DEFAULT_REDUCER_STATE = { + url: "", + isLoading: false, + success: false, + error: null as unknown, + metadata: null as ZarrMetadata | null, +} as const; + +type ReducerState = typeof DEFAULT_REDUCER_STATE; + +type ReducerPayload = T extends "SET_URL" + ? string + : T extends "SET_IS_LOADING" + ? boolean + : T extends "SET_SUCCESS" + ? boolean + : T extends "SET_ERROR" + ? { message: string; details?: string } + : T extends "SET_METADATA" + ? ZarrMetadata + : never; + +const reducer = ( + state: ReducerState, + action: { type: T; payload: any }, +) => { + switch (action.type) { + case "SET_URL": + return { ...state, url: action.payload }; + case "SET_IS_LOADING": + return { ...state, isLoading: action.payload }; + case "SET_SUCCESS": + return { ...state, success: action.payload }; + case "SET_ERROR": + return { ...state, error: action.payload }; + case "SET_METADATA": + return { ...state, metadata: action.payload }; + default: + return state; + } +}; + +export interface ZarrMetadataDialogComponentProps { + onClose: () => void; + onResize: (width: number, height: number) => void; +} + +const ZarrMetadataDialogComponent: React.FC = + observer(({ onClose, onResize }: ZarrMetadataDialogComponentProps) => { + const { root } = useProject(); + const [state, dispatch] = useReducer(reducer, DEFAULT_REDUCER_STATE); + + // Mock function to simulate zarr metadata fetching + // Replace this with actual zarr-js implementation + const fetchZarrMetadata = async (url: string): Promise => { + // Simulate network delay + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Mock data structure based on Xenium dataset + return { + datasetStructure: { + groups: ["images", "labels", "tables"], + arrays: ["images/tissue_image", "labels/nuclei_segmentation", "tables/my_ann_data"] + }, + spatialData: { + coordinateSystems: { + "global": { + "axes": ["x", "y"], + "units": ["micrometer", "micrometer"] + } + }, + elements: { + "tissue_image": "image", + "nuclei_segmentation": "labels", + "my_ann_data": "table" + }, + transformations: [] + }, + images: { + "tissue_image": { + shape: [4096, 4096, 3], + dtype: "uint8", + chunks: [512, 512, 3], + dimensions: ["y", "x", "c"], + physicalSizeX: 0.2125, + physicalSizeY: 0.2125, + units: "micrometer", + channels: ["Red", "Green", "Blue"] + } + }, + tables: { + "my_ann_data": { + shape: [156432, 541], + obsNames: ["cell_001", "cell_002", "cell_003"], + varNames: ["GAPDH", "ACTB", "MYC"], + observations: { + "cell_type": "string", + "total_counts": "float64", + "n_genes_by_counts": "int64" + }, + variables: { + "gene_symbol": "string", + "highly_variable": "bool", + "mean": "float64" + }, + embeddings: ["X_umap", "X_pca"] + } + }, + labels: { + "nuclei_segmentation": { + shape: [4096, 4096], + dtype: "uint32", + labelValues: [0, 1, 2, 3], + categories: ["background", "nucleus"] + } + }, + groupAttributes: { + "created_by": "spatialdata-io", + "version": "0.1.0", + "experiment_type": "Xenium", + "organism": "Homo sapiens" + } + }; + }; + + const handleUrlChange = useCallback((event: React.ChangeEvent) => { + dispatch({ type: "SET_URL", payload: event.target.value }); + }, []); + + const handleFetchMetadata = async () => { + if (!state.url.trim()) { + dispatch({ + type: "SET_ERROR", + payload: { + message: "Please enter a valid Zarr dataset URL", + }, + }); + return; + } + + dispatch({ type: "SET_IS_LOADING", payload: true }); + dispatch({ type: "SET_ERROR", payload: null }); + + try { + let transformedUrl = state.url.trim(); + + // Match EMBL Zarr URLs and transform them for the local proxy + if (transformedUrl.startsWith("https://s3.embl.de/spatialdata/")) { + const subpath = transformedUrl.replace("https://s3.embl.de/spatialdata/", ""); + transformedUrl = `${window.location.origin}/zarr_proxy/${subpath}`; + } else if (transformedUrl.startsWith('/zarr_proxy/')) { + transformedUrl = `${window.location.origin}${transformedUrl}`; + } + + // For debugging, you can keep this, but it's not needed for the final logic. + // await debugExploreZarrDataset(transformedUrl); + + // Call the primary extraction function. It has its own internal fallback. + const metadata = await extractZarrMetadata(transformedUrl); + + if (!metadata) { + throw new Error("Metadata could not be extracted."); + } + + dispatch({ type: "SET_METADATA", payload: metadata }); + dispatch({ type: "SET_SUCCESS", payload: true }); + onResize(1200, 800); + + } catch (error) { + console.error("Error fetching Zarr metadata:", error); + dispatch({ + type: "SET_ERROR", + payload: { + message: "Failed to fetch metadata from the provided URL", + details: error instanceof Error ? error.message : String(error), + }, + }); + } finally { + dispatch({ type: "SET_IS_LOADING", payload: false }); + } + }; + + const handleClose = useCallback(() => { + onResize(600, 400); // Reset to default size + onClose(); + }, [onClose, onResize]); + + const resetForm = () => { + dispatch({ type: "SET_URL", payload: "" }); + dispatch({ type: "SET_ERROR", payload: null }); + dispatch({ type: "SET_SUCCESS", payload: false }); + dispatch({ type: "SET_METADATA", payload: null }); + onResize(600, 400); + }; + + // Set initial dialog size + useEffect(() => { + onResize(600, 400); + }, [onResize]); + + const renderMetadataContent = () => { + if (!state.metadata) return null; + + const { metadata } = state; + const attrs = metadata.groupAttributes; + + return ( +
+ {/* Dataset Overview */} + + }>Dataset Overview + + + {state.url} + + +
+
Name: {attrs.name || 'Unknown'}
+
Version: {attrs.major_version}.{attrs.minor_version}
+
UUID: {attrs.dataset_uuid?.slice(0, 8)}...
+
Format: {attrs.data_format}
+
+
+ +
+
Units: {attrs.spatial_units}
+
Coordinate Space: {attrs.coordinate_space}
+
FOV Count: {attrs.fov_names?.length || 0}
+
+
+
+
+ + {/* Transcripts Data */} + + }>Transcript Data + + +
+
Total RNAs: {attrs.number_rnas?.toLocaleString() || 'Unknown'}
+
Total Genes: {attrs.number_genes?.toLocaleString() || 'Unknown'}
+
Codewords: {attrs.codeword_count?.toLocaleString() || 'Unknown'}
+
+
+ +
+ {attrs.codeword_gene_names && ( + <> +
Target Genes: {attrs.codeword_gene_names.filter((name: string) => !name.startsWith('NegControl') && !name.startsWith('UnassignedCodeword')).length}
+
Negative Controls: {attrs.codeword_gene_names.filter((name: string) => name.startsWith('NegControl')).length}
+
Unassigned: {attrs.codeword_gene_names.filter((name: string) => name.startsWith('UnassignedCodeword')).length}
+ + )} +
+
+ +
+ {attrs.fov_names?.slice(0, 10).map((fov: string) => ( +
{fov}
+ ))} + {attrs.fov_names?.length > 10 &&
...and {attrs.fov_names.length - 10} more
} +
+
+
+ + {/* Sample Genes */} + {attrs.gene_names && ( +
+
Sample Target Genes
+
+ {attrs.gene_names.filter((name: string) => !name.startsWith('NegControl') && !name.startsWith('UnassignedCodeword')).slice(0, 18).map((gene: string) => ( +
{gene}
+ ))} +
+ {attrs.gene_names.length > 18 && ( +
+ Showing 18 of {attrs.gene_names.filter((name: string) => !name.startsWith('NegControl') && !name.startsWith('UnassignedCodeword')).length} target genes +
+ )} +
+ )} +
+ + {/* Raw Structure Explorer */} + {metadata.rawStructure && ( + + }>Zarr Structure +
+
+ Groups: {metadata.datasetStructure.groups.length} | Arrays: {metadata.datasetStructure.arrays.length} +
+ {metadata.datasetStructure.groups.length === 0 && metadata.datasetStructure.arrays.length === 0 && ( +
+ This appears to be a root-level Zarr group with metadata only. The subdirectories (codeword_category, density, gene_category, grids) were not accessible as Zarr arrays or groups. +
+ )} + {Object.keys(attrs).length > 0 && ( +
+
Root Attributes ({Object.keys(attrs).length} total)
+ + !['codeword_gene_mapping', 'codeword_gene_names', 'gene_names', 'gene_index_map', 'fov_names'].includes(key) + ).slice(0, 10))} maxRows={10} /> +
+ )} +
+
+ )} + + {/* Gene Index Mapping */} + {attrs.gene_index_map && ( + + }>Gene Index Mapping +
+
+ Maps gene names to their indices in the expression matrix +
+ !gene.startsWith('NegControl') && !gene.startsWith('UnassignedCodeword')) + .slice(0, 20) + )} + maxRows={20} + /> +
+ Showing sample gene mappings (target genes only) +
+
+
+ )} +
+ ); + }; + return ( + + {state.isLoading ? ( + + Fetching Zarr metadata, please wait... + + + ) : state.error ? ( + <> + + Error +

{(state.error as any).message}

+ {(state.error as any).details && ( +
+ Details +
+                                        {(state.error as any).details}
+                                    
+
+ )} +
+
+ + +
+ + ) : state.success && state.metadata ? ( + <> + {renderMetadataContent()} +
+ + +
+ + ) : ( + <> +
+ +
+ + +

+ Enter the URL of a remote Zarr dataset in SpatialData format +

+
+
+ + +
+
+ + )} +
+ ); + }); + +const Wrapper = (props: ZarrMetadataDialogComponentProps) => { + const [open, setOpen] = useState(true); + + const handleClose = () => { + setOpen(false); + props.onClose(); + }; + + return ( + } + /> + ); +}; + +export default Wrapper; \ No newline at end of file diff --git a/src/charts/dialogs/zarrStream/ZarrMetadataDialogWrapper.tsx b/src/charts/dialogs/zarrStream/ZarrMetadataDialogWrapper.tsx new file mode 100644 index 000000000..b2d62bc0b --- /dev/null +++ b/src/charts/dialogs/zarrStream/ZarrMetadataDialogWrapper.tsx @@ -0,0 +1,50 @@ +import { createMdvPortal } from "@/react/react_utils"; +import { BaseDialog } from "../../../utilities/Dialog"; +import ZarrMetadataDialogComponent from "./ZarrMetadataDialog"; + +class ZarrMetadataDialogReact extends BaseDialog { + root: ReturnType; + + constructor() { + super( + { + title: "Zarr Dataset Metadata Viewer", + width: 600, + height: 400, + }, + null, + ); + this.outer.classList.add("zarrMetadataDialog"); + if (!this.dialog) throw new Error("no dialog??"); + + this.root = createMdvPortal( + this.close()} + onResize={(width: number, height: number) => this.resizeDialog(width, height)} + />, + this.dialog, + ); + + if (this.dialog.parentElement) { + this.dialog.parentElement.style.display = "none"; + } + } + + resizeDialog(width: number, height: number): void { + if (this.outer) { + this.outer.style.width = `${width}px`; + this.outer.style.height = `${height}px`; + } + } + + close(): void { + super.close(); + if (this.root) { + this.root.unmount(); + } + } +} + +BaseDialog.experiment["ZarrMetadataDialogReact"] = ZarrMetadataDialogReact; + +export default ZarrMetadataDialogReact; \ No newline at end of file diff --git a/src/charts/dialogs/zarrStream/zarrMetadataUtils.ts b/src/charts/dialogs/zarrStream/zarrMetadataUtils.ts new file mode 100644 index 000000000..42d0e193f --- /dev/null +++ b/src/charts/dialogs/zarrStream/zarrMetadataUtils.ts @@ -0,0 +1,455 @@ +import * as zarr from 'zarrita'; + +// Types for Zarr metadata structure +export interface ZarrMetadata { + datasetStructure: { + groups: string[]; + arrays: string[]; + }; + spatialData?: { + coordinateSystems: Record; + elements: Record; + transformations: any[]; + }; + images?: { + [key: string]: { + shape: number[]; + dtype: string; + chunks: number[]; + dimensions: string[]; + physicalSizeX?: number; + physicalSizeY?: number; + physicalSizeZ?: number; + units?: string; + channels?: string[]; + }; + }; + tables?: { + [key: string]: { + shape: number[]; + obsNames: string[]; + varNames: string[]; + observations: Record; + variables: Record; + embeddings?: string[]; + }; + }; + labels?: { + [key: string]: { + shape: number[]; + dtype: string; + labelValues: number[]; + categories?: string[]; + }; + }; + groupAttributes: Record; + rawStructure: ZarrNode; +} + +export interface ZarrNode { + name: string; + path: string; + type: 'group' | 'array'; + attributes?: Record; + shape?: number[]; + dtype?: string; + chunks?: number[]; + children?: ZarrNode[]; + arrayMetadata?: { + compressor?: any; + filters?: any[]; + fill_value?: any; + order?: string; + dimension_separator?: string; + }; +} + +/** + * Recursively explores a Zarr group structure and extracts metadata + */ +async function exploreZarrGroup( + group: any, + path = "", + visited: Set = new Set() +): Promise { + const fullPath = path || '/'; + + // Prevent infinite recursion + if (visited.has(fullPath)) { + return { + name: path.split('/').pop() || 'root', + path: fullPath, + type: 'group', + children: [] + }; + } + visited.add(fullPath); + + const node: ZarrNode = { + name: path.split('/').pop() || 'root', + path: fullPath, + type: 'group', + attributes: {}, + children: [] + }; + + try { + // Get group attributes + if (group.attrs) { + node.attributes = { ...group.attrs }; + } + + // Get all keys in the group + const keys = group.keys ? await group.keys() : []; + + for (const key of keys) { + try { + const childPath = path ? `${path}/${key}` : key; + const child = group.resolve ? group.resolve(key) : await group.getItem(key); + + if (!child) continue; + + // Check if it's an array or group + try { + const opened = await zarr.open(child); + + // Type guard: check if 'opened' is an array by checking for 'shape' property + if ('shape' in opened && Array.isArray((opened as any).shape)) { + // It's an array + const arrayNode: ZarrNode = { + name: key, + path: childPath, + type: 'array', + shape: (opened as any).shape, + dtype: (opened as any).dtype, + chunks: (opened as any).chunks, + attributes: (opened as any).attrs || {}, + arrayMetadata: { + compressor: (opened as any).compressor, + filters: (opened as any).filters, + fill_value: (opened as any).fill_value, + order: (opened as any).order, + dimension_separator: (opened as any).dimension_separator + } + }; + node.children?.push(arrayNode); + } else { + // It's a group, recurse + const groupNode = await exploreZarrGroup(opened, childPath, visited); + node.children?.push(groupNode); + } + } catch (arrayError) { + // If we can't open as array, try as group + try { + const groupNode = await exploreZarrGroup(child, childPath, visited); + node.children?.push(groupNode); + } catch (groupError) { + console.warn(`Could not explore ${childPath}:`, groupError); + } + } + } catch (error) { + console.warn(`Error processing key ${key}:`, error); + } + } + } catch (error) { + console.warn(`Error exploring group at ${path}:`, error); + } + + return node; +} + +/** + * Flattens the tree structure to extract groups and arrays + */ +function flattenZarrStructure(node: ZarrNode): { groups: string[]; arrays: string[] } { + const groups: string[] = []; + const arrays: string[] = []; + + function traverse(n: ZarrNode) { + if (n.type === 'group' && n.path !== '/') { + groups.push(n.path); + } else if (n.type === 'array') { + arrays.push(n.path); + } + + n.children?.forEach(traverse); + } + + traverse(node); + return { groups, arrays }; +} + +/** + * Extract spatial data metadata following SpatialData conventions + */ +function extractSpatialMetadata(rootNode: ZarrNode): ZarrMetadata['spatialData'] { + const spatialData: ZarrMetadata['spatialData'] = { + coordinateSystems: {}, + elements: {}, + transformations: [] + }; + + // Look for spatial data attributes in root + if (rootNode.attributes) { + if (rootNode.attributes.coordinateSystems) { + spatialData.coordinateSystems = rootNode.attributes.coordinateSystems; + } + if (rootNode.attributes.elements) { + spatialData.elements = rootNode.attributes.elements; + } + if (rootNode.attributes.transformations) { + spatialData.transformations = rootNode.attributes.transformations; + } + } + + return Object.keys(spatialData.coordinateSystems).length > 0 || + Object.keys(spatialData.elements).length > 0 || + spatialData.transformations.length > 0 + ? spatialData + : undefined; +} + +/** + * Extract image metadata + */ +function extractImageMetadata(node: ZarrNode): ZarrMetadata['images'] { + const images: NonNullable = {}; + + function findImages(n: ZarrNode, basePath = '') { + if (n.type === 'array' && n.shape && n.shape.length >= 2) { + // Likely an image if it has 2+ dimensions + const name = basePath ? `${basePath}/${n.name}` : n.name; + + images[name] = { + shape: n.shape, + dtype: n.dtype || 'unknown', + chunks: n.chunks || [], + dimensions: n.attributes?.axes || n.shape.map((_, i) => `dim_${i}`), + physicalSizeX: n.attributes?.physicalSizeX, + physicalSizeY: n.attributes?.physicalSizeY, + physicalSizeZ: n.attributes?.physicalSizeZ, + units: n.attributes?.units, + channels: n.attributes?.channels + }; + } + + n.children?.forEach(child => { + const childPath = basePath ? `${basePath}/${child.name}` : child.name; + findImages(child, childPath); + }); + } + + findImages(node); + return Object.keys(images).length > 0 ? images : undefined; +} + +/** + * Extract table/AnnData metadata + */ +function extractTableMetadata(node: ZarrNode): ZarrMetadata['tables'] { + const tables: NonNullable = {}; + + function findTables(n: ZarrNode, basePath = '') { + // Look for AnnData-like structures + const hasObs = n.children?.some(c => c.name === 'obs'); + const hasVar = n.children?.some(c => c.name === 'var'); + const hasX = n.children?.some(c => c.name === 'X'); + + if (hasObs && hasVar && hasX) { + const name = basePath ? `${basePath}/${n.name}` : n.name; + const obsArray = n.children?.find(c => c.name === 'obs'); + const varArray = n.children?.find(c => c.name === 'var'); + const xArray = n.children?.find(c => c.name === 'X'); + + tables[name] = { + shape: xArray?.shape || [0, 0], + obsNames: [], // Would need to actually read the data to get these + varNames: [], // Would need to actually read the data to get these + observations: obsArray?.attributes || {}, + variables: varArray?.attributes || {}, + embeddings: n.children + ?.filter(c => c.name.startsWith('X_')) + ?.map(c => c.name) || [] + }; + } + + n.children?.forEach(child => { + const childPath = basePath ? `${basePath}/${child.name}` : child.name; + findTables(child, childPath); + }); + } + + findTables(node); + return Object.keys(tables).length > 0 ? tables : undefined; +} + +/** + * Extract label metadata + */ +function extractLabelMetadata(node: ZarrNode): ZarrMetadata['labels'] { + const labels: NonNullable = {}; + + function findLabels(n: ZarrNode, basePath = '') { + // Look for integer arrays that might be labels + if (n.type === 'array' && n.dtype && + (n.dtype.includes('int') || n.dtype.includes('uint')) && + n.shape && n.shape.length >= 2) { + + const name = basePath ? `${basePath}/${n.name}` : n.name; + + labels[name] = { + shape: n.shape, + dtype: n.dtype, + labelValues: [], // Would need to read data to get actual values + categories: n.attributes?.categories + }; + } + + n.children?.forEach(child => { + const childPath = basePath ? `${basePath}/${child.name}` : child.name; + findLabels(child, childPath); + }); + } + + findLabels(node); + return Object.keys(labels).length > 0 ? labels : undefined; +} + +/** + * Main function to extract Zarr metadata from a URL + */ +export async function extractZarrMetadata(url: string): Promise { + try { + // Ensure URL ends with proper separator + const normalizedUrl = url.endsWith('/') ? url.slice(0, -1) : url; + + // Create store and try to open with consolidated metadata first + const store = new zarr.FetchStore(normalizedUrl); + let root; + + try { + // Try with consolidated metadata first (more efficient) + const consolidatedStore = await zarr.tryWithConsolidated(store); + root = await zarr.open(consolidatedStore); + } catch (consolidatedError) { + console.log('Consolidated metadata not available, falling back to regular access'); + // Fall back to regular store + root = await zarr.open(store); + } + + // Recursively explore the structure + console.log('Exploring Zarr structure...'); + const rawStructure = await exploreZarrGroup(root); + + // Extract flattened structure + const { groups, arrays } = flattenZarrStructure(rawStructure); + + // Extract specialized metadata + const spatialData = extractSpatialMetadata(rawStructure); + const images = extractImageMetadata(rawStructure); + const tables = extractTableMetadata(rawStructure); + const labels = extractLabelMetadata(rawStructure); + + const metadata: ZarrMetadata = { + datasetStructure: { groups, arrays }, + groupAttributes: rawStructure.attributes || {}, + rawStructure, + spatialData, + images, + tables, + labels + }; + + console.log('Zarr metadata extraction completed:', metadata); + return metadata; + + } catch (error) { + console.error('Error extracting Zarr metadata:', error); + throw new Error(`Failed to extract metadata: ${error instanceof Error ? error.message : String(error)}`); + } +} + +/** + * Debug function to explore Zarr dataset structure via HTTP + */ +export async function debugExploreWithHTTP(url: string): Promise { + try { + // Try to fetch the directory listing + const response = await fetch(url); + const html = await response.text(); + + console.log('HTTP directory listing:', html); + + // Try to fetch .zgroup and .zattrs if they exist + try { + const zgroupResponse = await fetch(`${url}/.zgroup`); + if (zgroupResponse.ok) { + const zgroupData = await zgroupResponse.json(); + console.log('.zgroup content:', zgroupData); + } + } catch (e) { + console.log('No .zgroup file found or not accessible'); + } + + try { + const zattrsResponse = await fetch(`${url}/.zattrs`); + if (zattrsResponse.ok) { + const zattrsData = await zattrsResponse.json(); + console.log('.zattrs content:', zattrsData); + } + } catch (e) { + console.log('No .zattrs file found or not accessible'); + } + + } catch (error) { + console.error('HTTP exploration failed:', error); + } +} + +/** + * Debug function to explore Zarr dataset using zarrita + */ +export async function debugExploreZarrDataset(url: string): Promise { + try { + console.log(`Debugging Zarr dataset at: ${url}`); + + const store = new zarr.FetchStore(url); + console.log('Store created:', store); + + const root = await zarr.open(store); + console.log('Root opened:', root); + + // Try to get keys if root is a group + if ((root as any).keys && typeof (root as any).keys === 'function') { + const keys = await (root as any).keys(); + console.log('Root keys:', keys); + + // Explore each key + for (const key of keys.slice(0, 3)) { // Limit to first 3 for debugging + try { + const item = (root as any).resolve(key); + const opened = await zarr.open(item); + if ('shape' in opened) { + console.log(`Item ${key}:`, { + shape: (opened as any).shape, + dtype: (opened as any).dtype, + attrs: (opened as any).attrs + }); + } else { + console.log(`Item ${key} is a group, not an array.`); + } + } catch (itemError) { + console.log(`Could not open item ${key}:`, itemError); + } + } + } + + // Try to access attributes + console.log('Root attributes:', root.attrs); + + } catch (error) { + console.error('Zarr exploration failed:', error); + } +} + +export default extractZarrMetadata; \ No newline at end of file diff --git a/src/react/components/MenuBarComponent.tsx b/src/react/components/MenuBarComponent.tsx index ddc4d8c5b..a5bd1e610 100644 --- a/src/react/components/MenuBarComponent.tsx +++ b/src/react/components/MenuBarComponent.tsx @@ -4,6 +4,7 @@ import { Save as SaveIcon, SaveAs as SaveAsIcon, Add as AddIcon, + AddLink as AddLinkIcon, Remove as RemoveIcon, CloudUpload as CloudUploadIcon, PestControl as PestControlIcon, @@ -13,6 +14,7 @@ import IconWithTooltip from "./IconWithTooltip"; import ViewSelectorWrapper from "./ViewSelectorComponent"; import ViewThumbnailComponent from "./ViewThumbnailComponent"; import FileUploadDialogReact from "@/charts/dialogs/FileUploadDialogWrapper"; +import ZarrMetadataDialog from "@/charts/dialogs/zarrStream/ZarrMetadataDialogWrapper"; import { fetchJsonConfig } from "@/dataloaders/DataLoaderUtil"; import BaseChart from "@/charts/BaseChart"; import { useProject } from "@/modules/ProjectContext"; @@ -61,6 +63,10 @@ const MenuBarComponent = () => { new FileUploadDialogReact(); }; + const handleAddRemoteDataSourceClick = () => { + new ZarrMetadataDialog(); + }; + const handleDebugButtonClick = async () => { setError(null); setOpen(false); @@ -125,9 +131,14 @@ const MenuBarComponent = () => { )} {config.permission === "edit" && ( - - - + <> + + + + + + + )} From f8fee80856630cabc34456ccc04bf5b204887cda Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Tue, 2 Sep 2025 15:47:30 -0400 Subject: [PATCH 11/17] add zarr, fsspec, and requests to backend dependencies - zarr<3.0: core array and metadata parsing - fsspec^2025.7.0: flexible filesystem + HTTP access - requests^2.32.5: robust HTTP client for dataset fetching --- python/poetry.lock | 10 +++++----- python/pyproject.toml | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/python/poetry.lock b/python/poetry.lock index 9582c052c..e0c9a254d 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -5785,14 +5785,14 @@ files = [ [[package]] name = "requests" -version = "2.32.4" +version = "2.32.5" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main", "auth", "dev", "docs", "llm"] files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, ] [package.dependencies] @@ -8000,4 +8000,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "e186ec42646bbbf91b2fed9f9e23b07d326ca12f26ea8d06005765e0c48a7609" +content-hash = "96b8edb424d92a766461196c6d5b8754c82b3cf2b54726dcc3ba1de102fa6147" diff --git a/python/pyproject.toml b/python/pyproject.toml index 85f9cb8be..b86a41b02 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -40,6 +40,9 @@ python-dotenv = "^1.0.1" nbformat = "^5.10.4" ruff = "^0.3.7" spatialdata = "^0.4.0" +zarr = "<3.0" +fsspec = "^2025.7.0" +requests = "^2.32.5" [tool.poetry.group.dev] optional = true From 4013c8782c31d83de74d13b141fbb16244d5916c Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Tue, 2 Sep 2025 15:48:53 -0400 Subject: [PATCH 12/17] added live xenium-zarr metadata preview to ZarrMetadataDialog The dialog now fetches metadata directly from the backend API, removing mock responses. Includes updated placeholders for local datasets and streamlined error messages for MVP testing. --- .../dialogs/zarrStream/ZarrMetadataDialog.tsx | 399 +++++++++++------- 1 file changed, 243 insertions(+), 156 deletions(-) diff --git a/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx b/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx index 3d9f3f46a..49ac2c02d 100644 --- a/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx +++ b/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx @@ -285,77 +285,34 @@ const ZarrMetadataDialogComponent: React.FC = const { root } = useProject(); const [state, dispatch] = useReducer(reducer, DEFAULT_REDUCER_STATE); - // Mock function to simulate zarr metadata fetching - // Replace this with actual zarr-js implementation + // Fetch zarr metadata from backend API const fetchZarrMetadata = async (url: string): Promise => { - // Simulate network delay - await new Promise(resolve => setTimeout(resolve, 2000)); + const apiUrl = `${root}/zarr/metadata?url=${encodeURIComponent(url)}`; - // Mock data structure based on Xenium dataset - return { - datasetStructure: { - groups: ["images", "labels", "tables"], - arrays: ["images/tissue_image", "labels/nuclei_segmentation", "tables/my_ann_data"] - }, - spatialData: { - coordinateSystems: { - "global": { - "axes": ["x", "y"], - "units": ["micrometer", "micrometer"] - } - }, - elements: { - "tissue_image": "image", - "nuclei_segmentation": "labels", - "my_ann_data": "table" - }, - transformations: [] - }, - images: { - "tissue_image": { - shape: [4096, 4096, 3], - dtype: "uint8", - chunks: [512, 512, 3], - dimensions: ["y", "x", "c"], - physicalSizeX: 0.2125, - physicalSizeY: 0.2125, - units: "micrometer", - channels: ["Red", "Green", "Blue"] - } - }, - tables: { - "my_ann_data": { - shape: [156432, 541], - obsNames: ["cell_001", "cell_002", "cell_003"], - varNames: ["GAPDH", "ACTB", "MYC"], - observations: { - "cell_type": "string", - "total_counts": "float64", - "n_genes_by_counts": "int64" - }, - variables: { - "gene_symbol": "string", - "highly_variable": "bool", - "mean": "float64" - }, - embeddings: ["X_umap", "X_pca"] - } - }, - labels: { - "nuclei_segmentation": { - shape: [4096, 4096], - dtype: "uint32", - labelValues: [0, 1, 2, 3], - categories: ["background", "nucleus"] - } + console.log('ZarrMetadataDialog DEBUG:'); + console.log('- root:', root); + console.log('- constructed apiUrl:', apiUrl); + console.log('- zarr url parameter:', url); + + const response = await fetch(apiUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', }, - groupAttributes: { - "created_by": "spatialdata-io", - "version": "0.1.0", - "experiment_type": "Xenium", - "organism": "Homo sapiens" - } - }; + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error(errorData.message || `HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.message || 'Failed to fetch metadata'); + } + + return data.metadata; }; const handleUrlChange = useCallback((event: React.ChangeEvent) => { @@ -377,21 +334,10 @@ const ZarrMetadataDialogComponent: React.FC = dispatch({ type: "SET_ERROR", payload: null }); try { - let transformedUrl = state.url.trim(); - - // Match EMBL Zarr URLs and transform them for the local proxy - if (transformedUrl.startsWith("https://s3.embl.de/spatialdata/")) { - const subpath = transformedUrl.replace("https://s3.embl.de/spatialdata/", ""); - transformedUrl = `${window.location.origin}/zarr_proxy/${subpath}`; - } else if (transformedUrl.startsWith('/zarr_proxy/')) { - transformedUrl = `${window.location.origin}${transformedUrl}`; - } + const zarrUrl = state.url.trim(); - // For debugging, you can keep this, but it's not needed for the final logic. - // await debugExploreZarrDataset(transformedUrl); - - // Call the primary extraction function. It has its own internal fallback. - const metadata = await extractZarrMetadata(transformedUrl); + // Call backend API to fetch metadata + const metadata = await fetchZarrMetadata(zarrUrl); if (!metadata) { throw new Error("Metadata could not be extracted."); @@ -438,6 +384,15 @@ const ZarrMetadataDialogComponent: React.FC = const { metadata } = state; const attrs = metadata.groupAttributes; + + // Extract data from different groups + const cellsAttrs = attrs.cells || {}; + const transcriptsAttrs = attrs.transcripts || {}; + const analysisAttrs = attrs.analysis || {}; + const rootAttrs = { ...attrs }; + delete rootAttrs.cells; + delete rootAttrs.transcripts; + delete rootAttrs.analysis; return (
@@ -448,93 +403,225 @@ const ZarrMetadataDialogComponent: React.FC = {state.url} - +
-
Name: {attrs.name || 'Unknown'}
-
Version: {attrs.major_version}.{attrs.minor_version}
-
UUID: {attrs.dataset_uuid?.slice(0, 8)}...
-
Format: {attrs.data_format}
+
Groups: {metadata.datasetStructure.groups.length}
+
Arrays: {metadata.datasetStructure.arrays.length}
+
Format: Zarr v2
- +
-
Units: {attrs.spatial_units}
-
Coordinate Space: {attrs.coordinate_space}
-
FOV Count: {attrs.fov_names?.length || 0}
-
-
- - - - {/* Transcripts Data */} - - }>Transcript Data - - -
-
Total RNAs: {attrs.number_rnas?.toLocaleString() || 'Unknown'}
-
Total Genes: {attrs.number_genes?.toLocaleString() || 'Unknown'}
-
Codewords: {attrs.codeword_count?.toLocaleString() || 'Unknown'}
-
-
- -
- {attrs.codeword_gene_names && ( - <> -
Target Genes: {attrs.codeword_gene_names.filter((name: string) => !name.startsWith('NegControl') && !name.startsWith('UnassignedCodeword')).length}
-
Negative Controls: {attrs.codeword_gene_names.filter((name: string) => name.startsWith('NegControl')).length}
-
Unassigned: {attrs.codeword_gene_names.filter((name: string) => name.startsWith('UnassignedCodeword')).length}
- + {Object.keys(metadata.tables || {}).length > 0 ? ( + Object.entries(metadata.tables || {}).map(([name, info]: [string, any]) => ( +
+ {name}: {info.shape?.join(' ร— ') || 'Unknown shape'} +
+ )) + ) : ( +
No table data found
)}
- -
- {attrs.fov_names?.slice(0, 10).map((fov: string) => ( -
{fov}
- ))} - {attrs.fov_names?.length > 10 &&
...and {attrs.fov_names.length - 10} more
} -
-
- {/* Sample Genes */} - {attrs.gene_names && ( + {/* Discovered Groups */} + {metadata.datasetStructure.groups.length > 0 && (
-
Sample Target Genes
-
- {attrs.gene_names.filter((name: string) => !name.startsWith('NegControl') && !name.startsWith('UnassignedCodeword')).slice(0, 18).map((gene: string) => ( -
{gene}
+
Discovered Zarr Groups
+
+ {metadata.datasetStructure.groups.map((group: string) => ( +
+ {group} +
+ ))} +
+
+ )} + + {/* Discovered Arrays */} + {metadata.datasetStructure.arrays.length > 0 && ( +
+
Discovered Zarr Arrays
+
+ {metadata.datasetStructure.arrays.map((array: string) => ( +
+
{array}
+ {metadata.tables?.[array.split('/').pop() || ''] && ( +
+ Shape: {metadata.tables[array.split('/').pop() || ''].shape?.join(' ร— ')} | + Type: {metadata.tables[array.split('/').pop() || ''].dtype} +
+ )} +
))}
- {attrs.gene_names.length > 18 && ( -
- Showing 18 of {attrs.gene_names.filter((name: string) => !name.startsWith('NegControl') && !name.startsWith('UnassignedCodeword')).length} target genes -
- )}
)} - {/* Raw Structure Explorer */} - {metadata.rawStructure && ( + {/* Cell Data Section */} + {Object.keys(cellsAttrs).length > 0 && ( - }>Zarr Structure -
-
- Groups: {metadata.datasetStructure.groups.length} | Arrays: {metadata.datasetStructure.arrays.length} + }>Cell Data (cells.zarr) + + +
+ {metadata.datasetStructure.arrays + .filter((arr: string) => arr.startsWith('cells.zarr/')) + .map((arr: string) => ( +
{arr.replace('cells.zarr/', '')}
+ )) + } +
+
+ +
+ {Object.entries(cellsAttrs).slice(0, 5).map(([key, value]) => ( +
+ {key}: {typeof value === 'object' ? JSON.stringify(value).slice(0, 50) + '...' : String(value)} +
+ ))} + {Object.keys(cellsAttrs).length > 5 && ( +
...and {Object.keys(cellsAttrs).length - 5} more
+ )} +
+
+
+ + )} + + {/* Transcript Data Section */} + {Object.keys(transcriptsAttrs).length > 0 && ( + + }>Transcript Data (transcripts.zarr) + + +
+
Total RNAs: {transcriptsAttrs.number_rnas?.toLocaleString() || 'Unknown'}
+
Total Genes: {transcriptsAttrs.number_genes?.toLocaleString() || 'Unknown'}
+
Codewords: {transcriptsAttrs.codeword_count?.toLocaleString() || 'Unknown'}
+
+
+ +
+ {transcriptsAttrs.codeword_gene_names && ( + <> +
Target Genes: {transcriptsAttrs.codeword_gene_names.filter((name: string) => !name.startsWith('NegControl') && !name.startsWith('UnassignedCodeword')).length}
+
Negative Controls: {transcriptsAttrs.codeword_gene_names.filter((name: string) => name.startsWith('NegControl')).length}
+
Unassigned: {transcriptsAttrs.codeword_gene_names.filter((name: string) => name.startsWith('UnassignedCodeword')).length}
+ + )} +
+
+ +
+ {metadata.datasetStructure.arrays + .filter((arr: string) => arr.startsWith('transcripts.zarr/')) + .slice(0, 8) + .map((arr: string) => ( +
{arr.replace('transcripts.zarr/', '')}
+ )) + } + {metadata.datasetStructure.arrays.filter((arr: string) => arr.startsWith('transcripts.zarr/')).length > 8 && ( +
...and {metadata.datasetStructure.arrays.filter((arr: string) => arr.startsWith('transcripts.zarr/')).length - 8} more
+ )} +
+
+
+ + {/* Sample Genes */} + {transcriptsAttrs.gene_names && ( +
+
Sample Target Genes
+
+ {transcriptsAttrs.gene_names.filter((name: string) => !name.startsWith('NegControl') && !name.startsWith('UnassignedCodeword')).slice(0, 24).map((gene: string) => ( +
{gene}
+ ))} +
+ {transcriptsAttrs.gene_names.length > 24 && ( +
+ Showing 24 of {transcriptsAttrs.gene_names.filter((name: string) => !name.startsWith('NegControl') && !name.startsWith('UnassignedCodeword')).length} target genes +
+ )}
- {metadata.datasetStructure.groups.length === 0 && metadata.datasetStructure.arrays.length === 0 && ( -
- This appears to be a root-level Zarr group with metadata only. The subdirectories (codeword_category, density, gene_category, grids) were not accessible as Zarr arrays or groups. + )} + + {/* Fields of View */} + {transcriptsAttrs.fov_names && ( +
+
Fields of View ({transcriptsAttrs.fov_names.length} total)
+
+ {transcriptsAttrs.fov_names.slice(0, 32).map((fov: string) => ( +
{fov}
+ ))}
- )} - {Object.keys(attrs).length > 0 && ( -
-
Root Attributes ({Object.keys(attrs).length} total)
- - !['codeword_gene_mapping', 'codeword_gene_names', 'gene_names', 'gene_index_map', 'fov_names'].includes(key) - ).slice(0, 10))} maxRows={10} /> + {transcriptsAttrs.fov_names.length > 32 && ( +
+ Showing first 32 fields of view +
+ )} +
+ )} + + )} + + {/* Analysis Data Section */} + {Object.keys(analysisAttrs).length > 0 && ( + + }>Analysis Data (analysis.zarr) + + +
+ {metadata.datasetStructure.arrays + .filter((arr: string) => arr.startsWith('analysis.zarr/')) + .map((arr: string) => ( +
{arr.replace('analysis.zarr/', '')}
+ )) + } +
+
+ +
+ {metadata.tables?.cell_groups && ( + <> +
Shape: {metadata.tables.cell_groups.shape?.join(' ร— ') || 'Unknown'}
+
Type: {metadata.tables.cell_groups.dtype || 'Unknown'}
+
Description: Cell grouping/clustering analysis results
+ + )} +
+
+ +
+ {Object.entries(analysisAttrs).slice(0, 5).map(([key, value]) => ( +
+ {key}: {typeof value === 'object' ? JSON.stringify(value).slice(0, 50) + '...' : String(value)} +
+ ))} + {Object.keys(analysisAttrs).length > 5 && ( +
...and {Object.keys(analysisAttrs).length - 5} more
+ )} +
+
+
+
+ )} + + {/* Additional Root Attributes */} + {Object.keys(rootAttrs).length > 0 && ( + + }>Root Attributes +
+ + !['codeword_gene_mapping', 'codeword_gene_names', 'gene_names', 'gene_index_map', 'fov_names', 'cell_groups'].includes(key) + ).slice(0, 15))} + maxRows={15} + /> + {Object.keys(rootAttrs).length > 15 && ( +
+ Showing 15 of {Object.keys(rootAttrs).length} root attributes
)}
@@ -542,7 +629,7 @@ const ZarrMetadataDialogComponent: React.FC = )} {/* Gene Index Mapping */} - {attrs.gene_index_map && ( + {(transcriptsAttrs.gene_index_map || rootAttrs.gene_index_map) && ( }>Gene Index Mapping
@@ -551,11 +638,11 @@ const ZarrMetadataDialogComponent: React.FC =
!gene.startsWith('NegControl') && !gene.startsWith('UnassignedCodeword')) - .slice(0, 20) + .slice(0, 25) )} - maxRows={20} + maxRows={25} />
Showing sample gene mappings (target genes only) @@ -635,10 +722,10 @@ const ZarrMetadataDialogComponent: React.FC = value={state.url} onChange={handleUrlChange} className="w-full p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:text-gray-300 dark:bg-gray-800 dark:border-gray-600" - placeholder="https://s3.embl.de/spatialdata/spatialdata-sandbox/xenium_rep1_io.zarr" + placeholder="http://localhost:8000/your-zarr-dataset.zarr" />

- Enter the URL of a remote Zarr dataset in SpatialData format + Enter the URL of a Zarr dataset (supports HTTP/HTTPS)

From 8f1cf8cf5d5f61448f742c543d12080b55cef1c2 Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Tue, 2 Sep 2025 15:50:00 -0400 Subject: [PATCH 13/17] add zarr_utils backend module for preprocessing workflow Adds ZarrMetadataExtractor and supporting utilities to parse and normalize Zarr dataset structure. --- python/mdvtools/zarr_utils.py | 475 ++++++++++++++++++++++++++++++++++ 1 file changed, 475 insertions(+) create mode 100644 python/mdvtools/zarr_utils.py diff --git a/python/mdvtools/zarr_utils.py b/python/mdvtools/zarr_utils.py new file mode 100644 index 000000000..eade15784 --- /dev/null +++ b/python/mdvtools/zarr_utils.py @@ -0,0 +1,475 @@ +""" +Zarr utilities for MDV platform - backend implementation of Zarr metadata extraction +""" + +import zarr +import aiohttp +import asyncio +import json +from typing import Dict, Any, Optional, List +from urllib.parse import urlparse, urljoin +import logging + +logger = logging.getLogger(__name__) + + +class ZarrMetadataExtractor: + """Extracts metadata from Zarr stores for the MDV platform""" + + def __init__(self): + self.timeout = aiohttp.ClientTimeout(total=30) + + async def fetch_zarr_metadata(self, url: str) -> Dict[str, Any]: + """ + Fetch metadata from a Zarr dataset URL using async HTTP requests. + + Args: + url: URL to the Zarr dataset (can be HTTP/HTTPS) + + Returns: + Dictionary containing structured metadata for frontend display + + Raises: + Exception: If metadata extraction fails + """ + try: + print(f"[ZARR] Starting fetch_zarr_metadata for: {url}") + logger.info(f"Fetching Zarr metadata from: {url}") + + if not url.startswith(('http://', 'https://')): + raise ValueError(f"Unsupported URL scheme: {url}") + + print(f"[ZARR] Creating aiohttp session...") + # Use async HTTP approach to avoid fsspec conflicts + async with aiohttp.ClientSession(timeout=self.timeout) as session: + print(f"[ZARR] Session created, calling _fetch_http_zarr_metadata...") + metadata = await self._fetch_http_zarr_metadata(session, url) + print(f"[ZARR] _fetch_http_zarr_metadata returned: {type(metadata)}") + + print(f"[ZARR] Successfully extracted Zarr metadata") + logger.info("Successfully extracted Zarr metadata") + return metadata + + except Exception as e: + logger.error(f"Error fetching Zarr metadata from {url}: {str(e)}") + raise Exception(f"Failed to fetch metadata from {url}: {str(e)}") + + async def _fetch_http_zarr_metadata(self, session: aiohttp.ClientSession, url: str) -> Dict[str, Any]: + """ + Fetch Zarr metadata over HTTP using async requests. + """ + print(f"[ZARR] _fetch_http_zarr_metadata called with url: {url}") + logger.info(f"Fetching HTTP Zarr metadata from: {url}") + + # Ensure URL ends with / + if not url.endswith('/'): + url = url + '/' + + print(f"[ZARR] Normalized URL: {url}") + + # Try to fetch consolidated metadata first + metadata_url = urljoin(url, '.zmetadata') + + try: + print(f"[ZARR] Attempting to fetch consolidated metadata from: {metadata_url}") + logger.info(f"Attempting to fetch consolidated metadata from: {metadata_url}") + async with session.get(metadata_url) as response: + print(f"[ZARR] Consolidated metadata response status: {response.status}") + if response.status == 200: + logger.info("Found consolidated metadata") + consolidated_meta = await response.json() + print(f"[ZARR] Consolidated metadata keys: {list(consolidated_meta.keys())}") + return await self._parse_consolidated_metadata(consolidated_meta, url) + else: + print(f"[ZARR] No consolidated metadata found (status: {response.status}), trying direct exploration") + logger.info(f"No consolidated metadata found (status: {response.status}), trying direct exploration") + + except Exception as e: + print(f"[ZARR] Exception fetching consolidated metadata: {e}") + logger.info(f"Failed to fetch consolidated metadata: {e}") + + # Fallback: try to explore structure directly + print(f"[ZARR] Falling back to direct structure exploration") + return await self._explore_zarr_http_structure(session, url) + + async def _parse_consolidated_metadata(self, consolidated_meta: Dict, url: str) -> Dict[str, Any]: + """Parse consolidated Zarr metadata.""" + logger.info("Parsing consolidated metadata") + + metadata = { + "datasetStructure": { + "groups": [], + "arrays": [] + }, + "spatialData": None, + "images": {}, + "tables": {}, + "labels": {}, + "groupAttributes": {}, + "rawStructure": True + } + + # Extract root attributes from metadata + if 'metadata' in consolidated_meta and '' in consolidated_meta['metadata']: + root_meta = consolidated_meta['metadata'][''] + if 'attributes' in root_meta: + metadata["groupAttributes"] = root_meta['attributes'] + + # Process all items in the zarr structure + zarr_meta = consolidated_meta.get('metadata', {}) + + for path, item_meta in zarr_meta.items(): + if not path: # Skip root + continue + + if 'zarr_format' in item_meta: # This is an array + metadata["datasetStructure"]["arrays"].append(path) + + # Classify the array + array_name = path.split('/')[-1] + if self._is_image_name(array_name): + metadata["images"][array_name] = self._extract_array_metadata_from_meta(item_meta) + elif self._is_table_name(array_name): + metadata["tables"][array_name] = self._extract_array_metadata_from_meta(item_meta) + elif self._is_label_name(array_name): + metadata["labels"][array_name] = self._extract_array_metadata_from_meta(item_meta) + + elif 'attributes' in item_meta: # This is a group + metadata["datasetStructure"]["groups"].append(path) + + logger.info(f"Parsed consolidated metadata: {len(metadata['datasetStructure']['arrays'])} arrays, {len(metadata['datasetStructure']['groups'])} groups") + return metadata + + async def _explore_zarr_http_structure(self, session: aiohttp.ClientSession, url: str) -> Dict[str, Any]: + """Explore Zarr structure via direct HTTP requests.""" + logger.info("Exploring Zarr structure via HTTP") + + metadata = { + "datasetStructure": { + "groups": [], + "arrays": [] + }, + "spatialData": None, + "images": {}, + "tables": {}, + "labels": {}, + "groupAttributes": {}, + "rawStructure": True + } + + # Try to get root attributes + try: + attrs_url = urljoin(url, '.zattrs') + async with session.get(attrs_url) as response: + if response.status == 200: + attrs_text = await response.text() + metadata["groupAttributes"] = json.loads(attrs_text) + logger.info(f"Found root attributes: {list(metadata['groupAttributes'].keys())}") + except Exception as e: + logger.warning(f"Could not fetch root attributes: {e}") + + # Look for known Xenium structures + xenium_structures = ['cells.zarr/', 'transcripts.zarr/', 'analysis.zarr/', 'cells.zarr.zip', 'transcripts.zarr.zip'] + + for structure in xenium_structures: + try: + structure_url = urljoin(url, structure) + logger.info(f"Checking for structure: {structure_url}") + + # Check if it's a zarr group by looking for .zgroup + zgroup_url = urljoin(structure_url, '.zgroup') + async with session.get(zgroup_url) as response: + if response.status == 200: + logger.info(f"Found Zarr group: {structure}") + metadata["datasetStructure"]["groups"].append(structure.rstrip('/')) + + # Get attributes for this group + attrs_url = urljoin(structure_url, '.zattrs') + try: + async with session.get(attrs_url) as response: + if response.status == 200: + # Handle JSON parsing regardless of content type + attrs_text = await response.text() + group_attrs = json.loads(attrs_text) + logger.info(f"Group {structure} attributes: {list(group_attrs.keys())}") + + # Store attributes in groupAttributes with path prefix + group_name = structure.rstrip('/').replace('.zarr', '') + metadata["groupAttributes"][group_name] = group_attrs + except Exception as e: + logger.warning(f"Could not fetch attributes for {structure}: {e}") + + # Explore arrays within this group + await self._explore_group_structure(session, structure_url, structure.rstrip('/'), metadata) + + except Exception as e: + logger.info(f"Structure {structure} not found or inaccessible: {e}") + continue + + logger.info(f"HTTP-based structure exploration completed. Found {len(metadata['datasetStructure']['groups'])} groups, {len(metadata['datasetStructure']['arrays'])} arrays") + return metadata + + async def _explore_group_structure(self, session: aiohttp.ClientSession, base_url: str, group_path: str, metadata: Dict[str, Any]): + """Explore the structure within a Zarr group.""" + logger.info(f"Exploring group structure: {group_path}") + + # Known array patterns in Xenium data + known_arrays = { + 'cells.zarr': ['cell_id', 'cell_summary'], + 'transcripts.zarr': ['codeword_category', 'gene_category', 'metrics_density'], + 'analysis.zarr': ['cell_groups'] + } + + group_name = group_path.replace('.zarr', '') + arrays_to_check = known_arrays.get(f'{group_name}.zarr', []) + + for array_name in arrays_to_check: + try: + array_url = urljoin(base_url, f'{array_name}/') + zarray_url = urljoin(array_url, '.zarray') + + async with session.get(zarray_url) as response: + if response.status == 200: + # Handle JSON parsing regardless of content type + zarray_text = await response.text() + array_meta = json.loads(zarray_text) + full_array_path = f"{group_path}/{array_name}" + metadata["datasetStructure"]["arrays"].append(full_array_path) + + logger.info(f"Found array: {full_array_path} with shape {array_meta.get('shape', 'unknown')}") + + # Classify and store array metadata + if self._is_table_name(array_name) or group_name in ['cells', 'analysis']: + metadata["tables"][array_name] = { + "shape": array_meta.get('shape', []), + "dtype": array_meta.get('dtype', 'unknown'), + "chunks": array_meta.get('chunks', []), + "group": group_name + } + + # Get array attributes + try: + attrs_url = urljoin(array_url, '.zattrs') + async with session.get(attrs_url) as response: + if response.status == 200: + attrs_text = await response.text() + array_attrs = json.loads(attrs_text) + metadata["tables"][array_name]["attributes"] = array_attrs + except Exception as e: + logger.info(f"Could not get attributes for array {array_name}: {e}") + + except Exception as e: + logger.info(f"Array {array_name} in {group_path} not found: {e}") + continue + + def _is_image_name(self, name: str) -> bool: + """Determine if name suggests image data""" + image_patterns = ['image', 'img', 'tissue', 'dapi', 'he', 'rgb'] + name_lower = name.lower() + return any(pattern in name_lower for pattern in image_patterns) + + def _is_table_name(self, name: str) -> bool: + """Determine if name suggests tabular data""" + table_patterns = ['table', 'obs', 'var', 'cell', 'gene', 'transcript'] + name_lower = name.lower() + return any(pattern in name_lower for pattern in table_patterns) + + def _is_label_name(self, name: str) -> bool: + """Determine if name suggests label data""" + label_patterns = ['label', 'segmentation', 'mask', 'nuclei', 'cell_id'] + name_lower = name.lower() + return any(pattern in name_lower for pattern in label_patterns) + + def _extract_array_metadata_from_meta(self, item_meta: Dict) -> Dict[str, Any]: + """Extract array metadata from consolidated metadata.""" + attrs = item_meta.get('attributes', {}) + + return { + "shape": item_meta.get('shape', []), + "dtype": item_meta.get('dtype', 'unknown'), + "chunks": item_meta.get('chunks', []), + "attributes": attrs + } + + def _extract_metadata_structure(self, root, url: str) -> Dict[str, Any]: + """Extract structured metadata from zarr root group""" + + metadata = { + "datasetStructure": { + "groups": [], + "arrays": [] + }, + "spatialData": None, + "images": {}, + "tables": {}, + "labels": {}, + "groupAttributes": {}, + "rawStructure": True + } + + # Get root attributes + if hasattr(root, 'attrs'): + metadata["groupAttributes"] = dict(root.attrs) + + # Recursively explore structure + self._explore_group(root, metadata, "") + + # Check for spatial data conventions + if self._is_spatial_data_format(root): + metadata["spatialData"] = self._extract_spatial_data_info(root) + + return metadata + + def _explore_group(self, group, metadata: Dict[str, Any], path_prefix: str): + """Recursively explore zarr group structure""" + + for key in group.keys(): + full_path = f"{path_prefix}/{key}" if path_prefix else key + + try: + item = group[key] + + if isinstance(item, zarr.Group): + metadata["datasetStructure"]["groups"].append(full_path) + # Recurse into subgroups + self._explore_group(item, metadata, full_path) + + elif isinstance(item, zarr.Array): + metadata["datasetStructure"]["arrays"].append(full_path) + + # Classify arrays based on name patterns and attributes + if self._is_image_array(key, item): + metadata["images"][key] = self._extract_image_metadata(item) + elif self._is_table_array(key, item): + metadata["tables"][key] = self._extract_table_metadata(item) + elif self._is_label_array(key, item): + metadata["labels"][key] = self._extract_label_metadata(item) + + except Exception as e: + logger.warning(f"Could not process item {full_path}: {e}") + continue + + def _is_spatial_data_format(self, root) -> bool: + """Check if this follows SpatialData conventions""" + attrs = getattr(root, 'attrs', {}) + return ('spatialdata' in str(attrs).lower() or + any(key in attrs for key in ['coordinate_systems', 'elements'])) + + def _extract_spatial_data_info(self, root) -> Dict[str, Any]: + """Extract spatial data specific information""" + attrs = dict(getattr(root, 'attrs', {})) + + spatial_info = { + "coordinateSystems": {}, + "elements": {}, + "transformations": [] + } + + # Look for coordinate system info + if 'coordinate_systems' in attrs: + spatial_info["coordinateSystems"] = attrs['coordinate_systems'] + + # Look for element mappings + if 'elements' in attrs: + spatial_info["elements"] = attrs['elements'] + + return spatial_info + + def _is_image_array(self, name: str, array: zarr.Array) -> bool: + """Determine if array represents image data""" + # Check shape - images typically have 2+ dimensions + if len(array.shape) < 2: + return False + + # Check name patterns + image_patterns = ['image', 'img', 'tissue', 'dapi', 'he', 'rgb'] + name_lower = name.lower() + + return any(pattern in name_lower for pattern in image_patterns) + + def _is_table_array(self, name: str, array: zarr.Array) -> bool: + """Determine if array represents tabular data""" + table_patterns = ['table', 'obs', 'var', 'cell', 'gene', 'transcript'] + name_lower = name.lower() + + return any(pattern in name_lower for pattern in table_patterns) + + def _is_label_array(self, name: str, array: zarr.Array) -> bool: + """Determine if array represents label/segmentation data""" + label_patterns = ['label', 'segmentation', 'mask', 'nuclei', 'cell_id'] + name_lower = name.lower() + + return any(pattern in name_lower for pattern in label_patterns) + + def _extract_image_metadata(self, array: zarr.Array) -> Dict[str, Any]: + """Extract metadata specific to image arrays""" + attrs = dict(getattr(array, 'attrs', {})) + + return { + "shape": list(array.shape), + "dtype": str(array.dtype), + "chunks": list(array.chunks) if hasattr(array, 'chunks') else [], + "dimensions": attrs.get('dimensions', [f"dim_{i}" for i in range(len(array.shape))]), + "physicalSizeX": attrs.get('physicalSizeX'), + "physicalSizeY": attrs.get('physicalSizeY'), + "physicalSizeZ": attrs.get('physicalSizeZ'), + "units": attrs.get('units'), + "channels": attrs.get('channels', []) + } + + def _extract_table_metadata(self, array: zarr.Array) -> Dict[str, Any]: + """Extract metadata specific to table arrays""" + attrs = dict(getattr(array, 'attrs', {})) + + # Try to get a small sample of data for preview + obs_names = [] + var_names = [] + + try: + # For 2D arrays, try to interpret as observation x variable matrix + if len(array.shape) == 2: + obs_names = [f"obs_{i}" for i in range(min(3, array.shape[0]))] + var_names = [f"var_{i}" for i in range(min(3, array.shape[1]))] + except Exception: + pass + + return { + "shape": list(array.shape), + "obsNames": obs_names, + "varNames": var_names, + "observations": attrs.get('observations', {}), + "variables": attrs.get('variables', {}), + "embeddings": attrs.get('embeddings', []) + } + + def _extract_label_metadata(self, array: zarr.Array) -> Dict[str, Any]: + """Extract metadata specific to label arrays""" + attrs = dict(getattr(array, 'attrs', {})) + + # Try to get unique values (sample) + label_values = [] + try: + if array.size < 1000000: # Only sample if reasonably sized + sample_data = array[...] + unique_vals = list(set(sample_data.flat))[:10] # Get first 10 unique values + label_values = [int(v) for v in unique_vals if isinstance(v, (int, float))] + except Exception: + pass + + return { + "shape": list(array.shape), + "dtype": str(array.dtype), + "labelValues": label_values, + "categories": attrs.get('categories', []) + } + + +# Global instance for reuse +_extractor_instance = None + +def get_zarr_extractor() -> ZarrMetadataExtractor: + """Get or create global ZarrMetadataExtractor instance""" + global _extractor_instance + if _extractor_instance is None: + _extractor_instance = ZarrMetadataExtractor() + return _extractor_instance \ No newline at end of file From ead572c79a72644573e816a9104322e0cb8d6456 Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Tue, 2 Sep 2025 15:55:11 -0400 Subject: [PATCH 14/17] add /zarr/metadata endpoint to server.py - Exposes new GET /zarr/metadata?url=... route - Integrates with ZarrMetadataExtractor to fetch and normalize dataset metadata - Returns structured JSON with dataset groups, arrays, and attributes - Provides error handling for invalid URLs, missing files, or format issues --- python/mdvtools/server.py | 91 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/python/mdvtools/server.py b/python/mdvtools/server.py index d713f3f86..5468a33b2 100644 --- a/python/mdvtools/server.py +++ b/python/mdvtools/server.py @@ -28,10 +28,12 @@ from datetime import datetime from typing import Optional import threading +import asyncio import scanpy as sc from mdvtools.conversions import convert_scanpy_to_mdv from mdvtools.server_extension import MDVServerOptions from mdvtools.logging_config import get_logger +from mdvtools.zarr_utils import get_zarr_extractor logger = get_logger(__name__) @@ -154,6 +156,95 @@ def get_json_file(file: str): return f"Problem parsing state file: {e}", 500 return send_file(path) + # Zarr metadata endpoint for fetching remote Zarr dataset information + @project_bp.route("/zarr/metadata", methods=["GET"]) + def get_zarr_metadata(): + """ + Fetch metadata from a Zarr dataset URL. + + Query Parameters: + url: The URL of the Zarr dataset to fetch metadata from + + Returns: + JSON response with metadata structure or error message + """ + log("=== ZARR METADATA REQUEST ===") + log(f"Request method: {request.method}") + log(f"Request path: {request.path}") + log(f"Request args: {request.args}") + log(f"Project: {project.id}") + + zarr_url = None + try: + zarr_url = request.args.get('url') + log(f"Extracted zarr_url: {zarr_url}") + + if not zarr_url: + log("ERROR: Missing 'url' parameter") + return jsonify({ + "error": "Missing 'url' parameter", + "message": "Please provide a 'url' query parameter with the Zarr dataset URL" + }), 400 + + # Validate URL format + if not zarr_url.startswith(('http://', 'https://')): + log(f"ERROR: Invalid URL format: {zarr_url}") + return jsonify({ + "error": "Invalid URL format", + "message": "URL must start with http:// or https://" + }), 400 + + # Convert localhost to host.docker.internal if running in Docker + if 'localhost' in zarr_url and os.path.exists('/.dockerenv'): + zarr_url = zarr_url.replace('localhost', 'host.docker.internal') + log(f"Running in Docker, converted URL to: {zarr_url}") + + log(f"URL validation passed, fetching metadata for: {zarr_url}") + + # Extract metadata using zarr utilities + log("Getting zarr extractor...") + extractor = get_zarr_extractor() + log(f"Zarr extractor: {extractor}") + + log("Calling fetch_zarr_metadata...") + print("Creating new event loop for async zarr call...") + # Run the async function in the current event loop + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + print("Running fetch_zarr_metadata in event loop...") + metadata = loop.run_until_complete(extractor.fetch_zarr_metadata(zarr_url)) + print(f"Event loop completed, metadata type: {type(metadata)}") + except Exception as loop_e: + print(f"Exception in event loop: {loop_e}") + raise loop_e + finally: + print("Closing event loop...") + loop.close() + print("Event loop closed") + log(f"Metadata extraction successful, keys: {list(metadata.keys()) if isinstance(metadata, dict) else type(metadata)}") + print(f"Metadata extraction successful, keys: {list(metadata.keys()) if isinstance(metadata, dict) else type(metadata)}") + + response_data = { + "success": True, + "metadata": metadata, + "url": zarr_url + } + log("Returning successful response") + return jsonify(response_data) + + except Exception as e: + log(f"EXCEPTION in get_zarr_metadata: {str(e)}") + log(f"Exception type: {type(e)}") + import traceback + log(f"Full traceback: {traceback.format_exc()}") + + return jsonify({ + "error": "Metadata extraction failed", + "message": str(e), + "url": zarr_url + }), 500 + # gets the raw byte data and packages it in the correct response @project_bp.route("/get_data", methods=["POST"]) def get_data(): From 671d3bda6762e1772a69347992ff6a68e388f362 Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Tue, 2 Sep 2025 16:15:23 -0400 Subject: [PATCH 15/17] add initial support for spatial data metadata visualisation --- dir_2.txt | 123 ++++++ python/mdvtools/server.py | 50 +-- python/mdvtools/zarr_utils.py | 366 +++++++++++++++++- .../dialogs/zarrStream/ZarrMetadataDialog.tsx | 95 ++++- 4 files changed, 598 insertions(+), 36 deletions(-) create mode 100644 dir_2.txt diff --git a/dir_2.txt b/dir_2.txt new file mode 100644 index 000000000..b3f034f8d --- /dev/null +++ b/dir_2.txt @@ -0,0 +1,123 @@ + +D:\mdv\datasets\xenium_rep1_io\data.zarr +โ”œโ”€โ”€ .zattrs +โ”œโ”€โ”€ .zgroup +โ”œโ”€โ”€ images +โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”œโ”€โ”€ morphology_focus +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”‚ โ””โ”€โ”€ 0 (...and 4 more) +โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ””โ”€โ”€ morphology_mip +โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ””โ”€โ”€ 0 (...and 4 more) +โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”œโ”€โ”€ points +โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ””โ”€โ”€ transcripts +โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ””โ”€โ”€ points.parquet +โ”‚ โ”œโ”€โ”€ part.0.parquet +โ”‚ โ”œโ”€โ”€ part.1.parquet +โ”‚ โ”œโ”€โ”€ part.2.parquet +โ”‚ โ”œโ”€โ”€ part.3.parquet +โ”‚ โ”œโ”€โ”€ part.4.parquet +โ”‚ โ”œโ”€โ”€ part.5.parquet +โ”‚ โ”œโ”€โ”€ part.6.parquet +โ”‚ โ””โ”€โ”€ part.7.parquet +โ”œโ”€โ”€ shapes +โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”œโ”€โ”€ cell_boundaries +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”‚ โ””โ”€โ”€ shapes.parquet +โ”‚ โ””โ”€โ”€ cell_circles +โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ””โ”€โ”€ shapes.parquet +โ”œโ”€โ”€ tables +โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ””โ”€โ”€ table +โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”œโ”€โ”€ X +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”‚ โ”œโ”€โ”€ data +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 64 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ indices +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 64 chunks] +โ”‚ โ”‚ โ””โ”€โ”€ indptr +โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 2 chunks] +โ”‚ โ”œโ”€โ”€ layers +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ””โ”€โ”€ .zgroup +โ”‚ โ”œโ”€โ”€ obs +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”‚ โ”œโ”€โ”€ _index +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ cell_area +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ cell_id +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 2 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ control_codeword_counts +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ control_probe_counts +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ nucleus_area +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ region +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ categories +โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ codes +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ total_counts +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] +โ”‚ โ”‚ โ””โ”€โ”€ transcript_counts +โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] +โ”‚ โ”œโ”€โ”€ obsm +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”‚ โ””โ”€โ”€ spatial +โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] +โ”‚ โ”œโ”€โ”€ obsp +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ””โ”€โ”€ .zgroup +โ”‚ โ”œโ”€โ”€ raw +โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”œโ”€โ”€ uns +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”‚ โ””โ”€โ”€ spatialdata_attrs +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”‚ โ”œโ”€โ”€ instance_key +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ region +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”‚ โ””โ”€โ”€ region_key +โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”œโ”€โ”€ var +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup +โ”‚ โ”‚ โ”œโ”€โ”€ _index +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ feature_types +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”‚ โ”œโ”€โ”€ gene_ids +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”‚ โ””โ”€โ”€ genome +โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] +โ”‚ โ”œโ”€โ”€ varm +โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ”‚ โ””โ”€โ”€ .zgroup +โ”‚ โ””โ”€โ”€ varp +โ”‚ โ”œโ”€โ”€ .zattrs +โ”‚ โ””โ”€โ”€ .zgroup +โ””โ”€โ”€ zmetadata diff --git a/python/mdvtools/server.py b/python/mdvtools/server.py index 5468a33b2..a7f17ff2c 100644 --- a/python/mdvtools/server.py +++ b/python/mdvtools/server.py @@ -156,64 +156,64 @@ def get_json_file(file: str): return f"Problem parsing state file: {e}", 500 return send_file(path) - # Zarr metadata endpoint for fetching remote Zarr dataset information - @project_bp.route("/zarr/metadata", methods=["GET"]) - def get_zarr_metadata(): + # Universal metadata endpoint for fetching remote dataset information + @project_bp.route("/get_metadata", methods=["GET"]) + def get_metadata(): """ - Fetch metadata from a Zarr dataset URL. + Fetch metadata from a dataset URL (supports Zarr and SpatialData formats). Query Parameters: - url: The URL of the Zarr dataset to fetch metadata from + url: The URL of the dataset to fetch metadata from Returns: JSON response with metadata structure or error message """ - log("=== ZARR METADATA REQUEST ===") + log("=== METADATA REQUEST ===") log(f"Request method: {request.method}") log(f"Request path: {request.path}") log(f"Request args: {request.args}") log(f"Project: {project.id}") - zarr_url = None + dataset_url = None try: - zarr_url = request.args.get('url') - log(f"Extracted zarr_url: {zarr_url}") + dataset_url = request.args.get('url') + log(f"Extracted dataset_url: {dataset_url}") - if not zarr_url: + if not dataset_url: log("ERROR: Missing 'url' parameter") return jsonify({ "error": "Missing 'url' parameter", - "message": "Please provide a 'url' query parameter with the Zarr dataset URL" + "message": "Please provide a 'url' query parameter with the dataset URL" }), 400 # Validate URL format - if not zarr_url.startswith(('http://', 'https://')): - log(f"ERROR: Invalid URL format: {zarr_url}") + if not dataset_url.startswith(('http://', 'https://')): + log(f"ERROR: Invalid URL format: {dataset_url}") return jsonify({ "error": "Invalid URL format", "message": "URL must start with http:// or https://" }), 400 # Convert localhost to host.docker.internal if running in Docker - if 'localhost' in zarr_url and os.path.exists('/.dockerenv'): - zarr_url = zarr_url.replace('localhost', 'host.docker.internal') - log(f"Running in Docker, converted URL to: {zarr_url}") + if 'localhost' in dataset_url and os.path.exists('/.dockerenv'): + dataset_url = dataset_url.replace('localhost', 'host.docker.internal') + log(f"Running in Docker, converted URL to: {dataset_url}") - log(f"URL validation passed, fetching metadata for: {zarr_url}") + log(f"URL validation passed, fetching metadata for: {dataset_url}") - # Extract metadata using zarr utilities - log("Getting zarr extractor...") + # Extract metadata using zarr utilities (supports both Zarr and SpatialData) + log("Getting metadata extractor...") extractor = get_zarr_extractor() - log(f"Zarr extractor: {extractor}") + log(f"Metadata extractor: {extractor}") log("Calling fetch_zarr_metadata...") - print("Creating new event loop for async zarr call...") + print("Creating new event loop for async metadata call...") # Run the async function in the current event loop loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: print("Running fetch_zarr_metadata in event loop...") - metadata = loop.run_until_complete(extractor.fetch_zarr_metadata(zarr_url)) + metadata = loop.run_until_complete(extractor.fetch_zarr_metadata(dataset_url)) print(f"Event loop completed, metadata type: {type(metadata)}") except Exception as loop_e: print(f"Exception in event loop: {loop_e}") @@ -228,13 +228,13 @@ def get_zarr_metadata(): response_data = { "success": True, "metadata": metadata, - "url": zarr_url + "url": dataset_url } log("Returning successful response") return jsonify(response_data) except Exception as e: - log(f"EXCEPTION in get_zarr_metadata: {str(e)}") + log(f"EXCEPTION in get_metadata: {str(e)}") log(f"Exception type: {type(e)}") import traceback log(f"Full traceback: {traceback.format_exc()}") @@ -242,7 +242,7 @@ def get_zarr_metadata(): return jsonify({ "error": "Metadata extraction failed", "message": str(e), - "url": zarr_url + "url": dataset_url }), 500 # gets the raw byte data and packages it in the correct response diff --git a/python/mdvtools/zarr_utils.py b/python/mdvtools/zarr_utils.py index eade15784..c2cabc3e7 100644 --- a/python/mdvtools/zarr_utils.py +++ b/python/mdvtools/zarr_utils.py @@ -14,14 +14,15 @@ class ZarrMetadataExtractor: - """Extracts metadata from Zarr stores for the MDV platform""" + """Extracts metadata from Zarr stores and SpatialData formats for the MDV platform""" def __init__(self): self.timeout = aiohttp.ClientTimeout(total=30) async def fetch_zarr_metadata(self, url: str) -> Dict[str, Any]: """ - Fetch metadata from a Zarr dataset URL using async HTTP requests. + Fetch metadata from a Zarr dataset or SpatialData URL using async HTTP requests. + Automatically detects the format and extracts appropriate metadata. Args: url: URL to the Zarr dataset (can be HTTP/HTTPS) @@ -42,18 +43,126 @@ async def fetch_zarr_metadata(self, url: str) -> Dict[str, Any]: print(f"[ZARR] Creating aiohttp session...") # Use async HTTP approach to avoid fsspec conflicts async with aiohttp.ClientSession(timeout=self.timeout) as session: - print(f"[ZARR] Session created, calling _fetch_http_zarr_metadata...") - metadata = await self._fetch_http_zarr_metadata(session, url) - print(f"[ZARR] _fetch_http_zarr_metadata returned: {type(metadata)}") + print(f"[ZARR] Session created, detecting dataset format...") + dataset_format = await self._detect_dataset_format(session, url) + print(f"[ZARR] Detected format: {dataset_format}") + + if dataset_format == "spatialdata": + print(f"[ZARR] Processing as SpatialData format...") + metadata = await self._fetch_spatialdata_metadata(session, url) + else: + print(f"[ZARR] Processing as standard Zarr format...") + metadata = await self._fetch_http_zarr_metadata(session, url) + + print(f"[ZARR] Metadata extraction returned: {type(metadata)}") - print(f"[ZARR] Successfully extracted Zarr metadata") - logger.info("Successfully extracted Zarr metadata") + print(f"[ZARR] Successfully extracted metadata") + logger.info("Successfully extracted metadata") return metadata except Exception as e: logger.error(f"Error fetching Zarr metadata from {url}: {str(e)}") raise Exception(f"Failed to fetch metadata from {url}: {str(e)}") + async def _detect_dataset_format(self, session: aiohttp.ClientSession, url: str) -> str: + """ + Detect if the dataset is SpatialData format or standard Zarr format. + SpatialData typically has organized groups: images, points, shapes, tables. + """ + if not url.endswith('/'): + url = url + '/' + + logger.info(f"Detecting dataset format for: {url}") + + # Check for SpatialData indicators + spatialdata_groups = ['images', 'points', 'shapes', 'tables'] + found_groups = 0 + + for group in spatialdata_groups: + try: + group_url = urljoin(url, f'{group}/') + zgroup_url = urljoin(group_url, '.zgroup') + + async with session.get(zgroup_url) as response: + if response.status == 200: + found_groups += 1 + logger.info(f"Found SpatialData group: {group}") + except Exception as e: + logger.debug(f"Could not check group {group}: {e}") + + # If we found 2 or more SpatialData-specific groups, classify as SpatialData + if found_groups >= 2: + logger.info("Detected SpatialData format") + return "spatialdata" + else: + logger.info("Detected standard Zarr format") + return "zarr" + + async def _fetch_spatialdata_metadata(self, session: aiohttp.ClientSession, url: str) -> Dict[str, Any]: + """ + Fetch metadata from a SpatialData-formatted Zarr store. + """ + logger.info(f"Fetching SpatialData metadata from: {url}") + + if not url.endswith('/'): + url = url + '/' + + metadata = { + "datasetFormat": "spatialdata", + "datasetStructure": { + "groups": [], + "arrays": [] + }, + "spatialData": { + "coordinateSystems": {}, + "elements": {}, + "transformations": [] + }, + "images": {}, + "tables": {}, + "labels": {}, + "points": {}, + "shapes": {}, + "groupAttributes": {}, + "rawStructure": True + } + + # Get root attributes + try: + attrs_url = urljoin(url, '.zattrs') + async with session.get(attrs_url) as response: + if response.status == 200: + attrs_text = await response.text() + metadata["groupAttributes"] = json.loads(attrs_text) + logger.info(f"Found root attributes: {list(metadata['groupAttributes'].keys())}") + except Exception as e: + logger.warning(f"Could not fetch root attributes: {e}") + + # Process SpatialData groups + spatialdata_groups = { + 'images': self._process_images_group, + 'points': self._process_points_group, + 'shapes': self._process_shapes_group, + 'tables': self._process_tables_group + } + + for group_name, processor in spatialdata_groups.items(): + try: + group_url = urljoin(url, f'{group_name}/') + zgroup_url = urljoin(group_url, '.zgroup') + + async with session.get(zgroup_url) as response: + if response.status == 200: + logger.info(f"Processing SpatialData group: {group_name}") + metadata["datasetStructure"]["groups"].append(group_name) + await processor(session, group_url, metadata) + + except Exception as e: + logger.info(f"Group {group_name} not found or inaccessible: {e}") + + logger.info(f"SpatialData metadata extraction completed. Found {len(metadata['datasetStructure']['groups'])} groups, {len(metadata['datasetStructure']['arrays'])} arrays") + return metadata + async def _fetch_http_zarr_metadata(self, session: aiohttp.ClientSession, url: str) -> Dict[str, Any]: """ Fetch Zarr metadata over HTTP using async requests. @@ -97,6 +206,7 @@ async def _parse_consolidated_metadata(self, consolidated_meta: Dict, url: str) logger.info("Parsing consolidated metadata") metadata = { + "datasetFormat": "zarr", "datasetStructure": { "groups": [], "arrays": [] @@ -145,6 +255,7 @@ async def _explore_zarr_http_structure(self, session: aiohttp.ClientSession, url logger.info("Exploring Zarr structure via HTTP") metadata = { + "datasetFormat": "zarr", "datasetStructure": { "groups": [], "arrays": [] @@ -463,6 +574,247 @@ def _extract_label_metadata(self, array: zarr.Array) -> Dict[str, Any]: "categories": attrs.get('categories', []) } + async def _process_images_group(self, session: aiohttp.ClientSession, group_url: str, metadata: Dict[str, Any]): + """Process the images group in SpatialData format.""" + logger.info(f"Processing images group at: {group_url}") + + # Look for common image names in SpatialData/Xenium format + image_names = ['morphology_focus', 'morphology_mip', 'he', 'dapi'] + + for image_name in image_names: + try: + image_url = urljoin(group_url, f'{image_name}/') + zgroup_url = urljoin(image_url, '.zgroup') + + async with session.get(zgroup_url) as response: + if response.status == 200: + logger.info(f"Found image: {image_name}") + full_path = f"images/{image_name}" + metadata["datasetStructure"]["groups"].append(full_path) + + # Get image metadata + image_metadata = await self._extract_spatialdata_image_metadata(session, image_url, image_name) + metadata["images"][image_name] = image_metadata + + except Exception as e: + logger.debug(f"Image {image_name} not found: {e}") + + async def _process_points_group(self, session: aiohttp.ClientSession, group_url: str, metadata: Dict[str, Any]): + """Process the points group in SpatialData format.""" + logger.info(f"Processing points group at: {group_url}") + + # Look for points data (typically transcripts in Xenium) + points_names = ['transcripts'] + + for points_name in points_names: + try: + points_url = urljoin(group_url, f'{points_name}/') + zgroup_url = urljoin(points_url, '.zgroup') + + async with session.get(zgroup_url) as response: + if response.status == 200: + logger.info(f"Found points data: {points_name}") + full_path = f"points/{points_name}" + metadata["datasetStructure"]["groups"].append(full_path) + + # Get points metadata + points_metadata = await self._extract_spatialdata_points_metadata(session, points_url, points_name) + metadata["points"][points_name] = points_metadata + + except Exception as e: + logger.debug(f"Points data {points_name} not found: {e}") + + async def _process_shapes_group(self, session: aiohttp.ClientSession, group_url: str, metadata: Dict[str, Any]): + """Process the shapes group in SpatialData format.""" + logger.info(f"Processing shapes group at: {group_url}") + + # Look for shape data (cell boundaries, circles, etc.) + shapes_names = ['cell_boundaries', 'cell_circles', 'nuclei'] + + for shape_name in shapes_names: + try: + shape_url = urljoin(group_url, f'{shape_name}/') + zgroup_url = urljoin(shape_url, '.zgroup') + + async with session.get(zgroup_url) as response: + if response.status == 200: + logger.info(f"Found shape data: {shape_name}") + full_path = f"shapes/{shape_name}" + metadata["datasetStructure"]["groups"].append(full_path) + + # Get shapes metadata + shapes_metadata = await self._extract_spatialdata_shapes_metadata(session, shape_url, shape_name) + metadata["shapes"][shape_name] = shapes_metadata + + except Exception as e: + logger.debug(f"Shape data {shape_name} not found: {e}") + + async def _process_tables_group(self, session: aiohttp.ClientSession, group_url: str, metadata: Dict[str, Any]): + """Process the tables group in SpatialData format.""" + logger.info(f"Processing tables group at: {group_url}") + + # Look for table data + table_names = ['table'] + + for table_name in table_names: + try: + table_url = urljoin(group_url, f'{table_name}/') + zgroup_url = urljoin(table_url, '.zgroup') + + async with session.get(zgroup_url) as response: + if response.status == 200: + logger.info(f"Found table data: {table_name}") + full_path = f"tables/{table_name}" + metadata["datasetStructure"]["groups"].append(full_path) + + # Get table metadata - this is the most complex part + table_metadata = await self._extract_spatialdata_table_metadata(session, table_url, table_name) + metadata["tables"][table_name] = table_metadata + + except Exception as e: + logger.debug(f"Table data {table_name} not found: {e}") + + async def _extract_spatialdata_image_metadata(self, session: aiohttp.ClientSession, image_url: str, image_name: str) -> Dict[str, Any]: + """Extract metadata from SpatialData image.""" + metadata = { + "name": image_name, + "type": "image", + "scales": [] + } + + # Look for scale directories (0, 1, 2, etc.) + for scale in range(5): # Check scales 0-4 + try: + scale_url = urljoin(image_url, f'{scale}/') + zarray_url = urljoin(scale_url, '.zarray') + + async with session.get(zarray_url) as response: + if response.status == 200: + zarray_text = await response.text() + array_meta = json.loads(zarray_text) + + scale_info = { + "scale": scale, + "shape": array_meta.get('shape', []), + "dtype": array_meta.get('dtype', 'unknown'), + "chunks": array_meta.get('chunks', []) + } + metadata["scales"].append(scale_info) + + except Exception as e: + logger.debug(f"Scale {scale} not found for image {image_name}: {e}") + break + + return metadata + + async def _extract_spatialdata_points_metadata(self, session: aiohttp.ClientSession, points_url: str, points_name: str) -> Dict[str, Any]: + """Extract metadata from SpatialData points.""" + return { + "name": points_name, + "type": "points", + "format": "parquet", + "description": f"Point data for {points_name}" + } + + async def _extract_spatialdata_shapes_metadata(self, session: aiohttp.ClientSession, shapes_url: str, shape_name: str) -> Dict[str, Any]: + """Extract metadata from SpatialData shapes.""" + return { + "name": shape_name, + "type": "shapes", + "format": "parquet", + "description": f"Shape data for {shape_name}" + } + + async def _extract_spatialdata_table_metadata(self, session: aiohttp.ClientSession, table_url: str, table_name: str) -> Dict[str, Any]: + """Extract metadata from SpatialData table (most complex - AnnData-like structure).""" + metadata = { + "name": table_name, + "type": "table", + "components": {} + } + + # Look for standard AnnData components + components = ['X', 'obs', 'var', 'obsm', 'varm', 'layers', 'uns'] + + for component in components: + try: + component_url = urljoin(table_url, f'{component}/') + zgroup_url = urljoin(component_url, '.zgroup') + + async with session.get(zgroup_url) as response: + if response.status == 200: + logger.info(f"Found table component: {component}") + full_path = f"tables/{table_name}/{component}" + + if component == 'X': + # X matrix - sparse or dense + x_metadata = await self._extract_x_matrix_metadata(session, component_url) + metadata["components"][component] = x_metadata + elif component in ['obs', 'var']: + # Observation/variable metadata + metadata["components"][component] = { + "type": "dataframe", + "description": f"{component} annotations" + } + elif component in ['obsm', 'varm']: + # Multi-dimensional annotations + metadata["components"][component] = { + "type": "multidimensional", + "description": f"{component} multidimensional data" + } + else: + metadata["components"][component] = { + "type": "group", + "description": f"{component} data" + } + + except Exception as e: + logger.debug(f"Table component {component} not found: {e}") + + return metadata + + async def _extract_x_matrix_metadata(self, session: aiohttp.ClientSession, x_url: str) -> Dict[str, Any]: + """Extract metadata from X matrix (expression data).""" + # Check for sparse matrix components + sparse_components = ['data', 'indices', 'indptr'] + found_sparse = 0 + + for component in sparse_components: + try: + comp_url = urljoin(x_url, f'{component}/') + zarray_url = urljoin(comp_url, '.zarray') + + async with session.get(zarray_url) as response: + if response.status == 200: + found_sparse += 1 + + except Exception: + pass + + if found_sparse == 3: + return { + "type": "sparse_matrix", + "format": "csr", + "description": "Sparse expression matrix" + } + else: + # Try dense matrix + try: + zarray_url = urljoin(x_url, '.zarray') + async with session.get(zarray_url) as response: + if response.status == 200: + return { + "type": "dense_matrix", + "description": "Dense expression matrix" + } + except Exception: + pass + + return { + "type": "unknown", + "description": "Expression matrix" + } + # Global instance for reuse _extractor_instance = None diff --git a/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx b/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx index 49ac2c02d..00145e079 100644 --- a/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx +++ b/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx @@ -285,9 +285,9 @@ const ZarrMetadataDialogComponent: React.FC = const { root } = useProject(); const [state, dispatch] = useReducer(reducer, DEFAULT_REDUCER_STATE); - // Fetch zarr metadata from backend API + // Fetch dataset metadata from backend API (supports Zarr and SpatialData) const fetchZarrMetadata = async (url: string): Promise => { - const apiUrl = `${root}/zarr/metadata?url=${encodeURIComponent(url)}`; + const apiUrl = `${root}/get_metadata?url=${encodeURIComponent(url)}`; console.log('ZarrMetadataDialog DEBUG:'); console.log('- root:', root); @@ -403,11 +403,11 @@ const ZarrMetadataDialogComponent: React.FC = {state.url} - +
Groups: {metadata.datasetStructure.groups.length}
Arrays: {metadata.datasetStructure.arrays.length}
-
Format: Zarr v2
+
Format: {(metadata as any).datasetFormat === 'spatialdata' ? 'SpatialData' : 'Zarr v2'}
@@ -460,6 +460,93 @@ const ZarrMetadataDialogComponent: React.FC = )} + {/* SpatialData-specific sections */} + {(metadata as any).datasetFormat === 'spatialdata' && ( + <> + {/* Images Section */} + {Object.keys((metadata as any).images || {}).length > 0 && ( + + }>Images + + {Object.entries((metadata as any).images || {}).map(([name, info]: [string, any]) => ( + +
+
Type: {info.type}
+ {info.scales && info.scales.length > 0 && ( +
Scales: {info.scales.length}
+ )} + {info.scales && info.scales[0] && ( +
Shape: {info.scales[0].shape?.join(' ร— ')}
+ )} +
+
+ ))} +
+
+ )} + + {/* Points Section */} + {Object.keys((metadata as any).points || {}).length > 0 && ( + + }>Points Data + + {Object.entries((metadata as any).points || {}).map(([name, info]: [string, any]) => ( + +
+
Type: {info.type}
+
Format: {info.format}
+
Description: {info.description}
+
+
+ ))} +
+
+ )} + + {/* Shapes Section */} + {Object.keys((metadata as any).shapes || {}).length > 0 && ( + + }>Shapes Data + + {Object.entries((metadata as any).shapes || {}).map(([name, info]: [string, any]) => ( + +
+
Type: {info.type}
+
Format: {info.format}
+
Description: {info.description}
+
+
+ ))} +
+
+ )} + + {/* Tables Section for SpatialData */} + {Object.keys((metadata as any).tables || {}).length > 0 && (metadata as any).datasetFormat === 'spatialdata' && ( + + }>Tables (AnnData-like) + + {Object.entries((metadata as any).tables || {}).map(([name, info]: [string, any]) => ( + +
+
Type: {info.type}
+ {info.components && Object.keys(info.components).length > 0 && ( +
+ Components: {Object.keys(info.components).join(', ')} +
+ )} + {info.components?.X && ( +
Expression Matrix: {info.components.X.type}
+ )} +
+
+ ))} +
+
+ )} + + )} + {/* Cell Data Section */} {Object.keys(cellsAttrs).length > 0 && ( From 81fa876fb8141a9a9c35d5dba4bf4905e5c04b74 Mon Sep 17 00:00:00 2001 From: Alejandro Quijada Leyton Date: Thu, 4 Sep 2025 15:45:42 -0400 Subject: [PATCH 16/17] extend metadata integration with SpatialData support - added automatic dataset format detection in ZarrMetadataExtractor (zarr_utils.py) - implemented specialized processors for SpatialData (images, points, shapes, AnnData tables) - responses now include a datasetFormat field (spatialdata or zarr) - enhanced dialog to display SpatialData-specific details (image scales, table components, etc.) --- dir_2.txt | 123 ------ python/mdvtools/zarr_utils.py | 355 ++++++++++++++++- .../dialogs/zarrStream/ZarrMetadataDialog.tsx | 359 ++++++++++++++++-- 3 files changed, 665 insertions(+), 172 deletions(-) delete mode 100644 dir_2.txt diff --git a/dir_2.txt b/dir_2.txt deleted file mode 100644 index b3f034f8d..000000000 --- a/dir_2.txt +++ /dev/null @@ -1,123 +0,0 @@ - -D:\mdv\datasets\xenium_rep1_io\data.zarr -โ”œโ”€โ”€ .zattrs -โ”œโ”€โ”€ .zgroup -โ”œโ”€โ”€ images -โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”œโ”€โ”€ morphology_focus -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”‚ โ””โ”€โ”€ 0 (...and 4 more) -โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ””โ”€โ”€ morphology_mip -โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ””โ”€โ”€ 0 (...and 4 more) -โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”œโ”€โ”€ points -โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ””โ”€โ”€ transcripts -โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ””โ”€โ”€ points.parquet -โ”‚ โ”œโ”€โ”€ part.0.parquet -โ”‚ โ”œโ”€โ”€ part.1.parquet -โ”‚ โ”œโ”€โ”€ part.2.parquet -โ”‚ โ”œโ”€โ”€ part.3.parquet -โ”‚ โ”œโ”€โ”€ part.4.parquet -โ”‚ โ”œโ”€โ”€ part.5.parquet -โ”‚ โ”œโ”€โ”€ part.6.parquet -โ”‚ โ””โ”€โ”€ part.7.parquet -โ”œโ”€โ”€ shapes -โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”œโ”€โ”€ cell_boundaries -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”‚ โ””โ”€โ”€ shapes.parquet -โ”‚ โ””โ”€โ”€ cell_circles -โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ””โ”€โ”€ shapes.parquet -โ”œโ”€โ”€ tables -โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ””โ”€โ”€ table -โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”œโ”€โ”€ X -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”‚ โ”œโ”€โ”€ data -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 64 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ indices -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 64 chunks] -โ”‚ โ”‚ โ””โ”€โ”€ indptr -โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 2 chunks] -โ”‚ โ”œโ”€โ”€ layers -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ””โ”€โ”€ .zgroup -โ”‚ โ”œโ”€โ”€ obs -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”‚ โ”œโ”€โ”€ _index -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ cell_area -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ cell_id -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 2 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ control_codeword_counts -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ control_probe_counts -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ nucleus_area -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ region -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ categories -โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ codes -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ total_counts -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] -โ”‚ โ”‚ โ””โ”€โ”€ transcript_counts -โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] -โ”‚ โ”œโ”€โ”€ obsm -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”‚ โ””โ”€โ”€ spatial -โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 4 chunks] -โ”‚ โ”œโ”€โ”€ obsp -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ””โ”€โ”€ .zgroup -โ”‚ โ”œโ”€โ”€ raw -โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”œโ”€โ”€ uns -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”‚ โ””โ”€โ”€ spatialdata_attrs -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”‚ โ”œโ”€โ”€ instance_key -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ region -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”‚ โ””โ”€โ”€ region_key -โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”œโ”€โ”€ var -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ”œโ”€โ”€ .zgroup -โ”‚ โ”‚ โ”œโ”€โ”€ _index -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ feature_types -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”‚ โ”œโ”€โ”€ gene_ids -โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”‚ โ””โ”€โ”€ genome -โ”‚ โ”‚ โ””โ”€โ”€ [zarr array with 1 chunks] -โ”‚ โ”œโ”€โ”€ varm -โ”‚ โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ”‚ โ””โ”€โ”€ .zgroup -โ”‚ โ””โ”€โ”€ varp -โ”‚ โ”œโ”€โ”€ .zattrs -โ”‚ โ””โ”€โ”€ .zgroup -โ””โ”€โ”€ zmetadata diff --git a/python/mdvtools/zarr_utils.py b/python/mdvtools/zarr_utils.py index c2cabc3e7..207dc7808 100644 --- a/python/mdvtools/zarr_utils.py +++ b/python/mdvtools/zarr_utils.py @@ -679,10 +679,22 @@ async def _extract_spatialdata_image_metadata(self, session: aiohttp.ClientSessi metadata = { "name": image_name, "type": "image", - "scales": [] + "scales": [], + "technical_details": { + "pyramid_levels": 0, + "total_size_mb": 0, + "coordinate_system": None, + "pixel_size": None + }, + "image_info": { + "channels": [], + "dimensions": [], + "bit_depth": None + } } # Look for scale directories (0, 1, 2, etc.) + total_size_bytes = 0 for scale in range(5): # Check scales 0-4 try: scale_url = urljoin(image_url, f'{scale}/') @@ -693,18 +705,72 @@ async def _extract_spatialdata_image_metadata(self, session: aiohttp.ClientSessi zarray_text = await response.text() array_meta = json.loads(zarray_text) + shape = array_meta.get('shape', []) + dtype = array_meta.get('dtype', 'unknown') + chunks = array_meta.get('chunks', []) + + # Calculate approximate size + if shape: + element_count = 1 + for dim in shape: + element_count *= dim + + # Estimate bytes per element based on dtype + bytes_per_element = 1 + if 'int16' in dtype or 'float16' in dtype: + bytes_per_element = 2 + elif 'int32' in dtype or 'float32' in dtype: + bytes_per_element = 4 + elif 'int64' in dtype or 'float64' in dtype: + bytes_per_element = 8 + + scale_size_bytes = element_count * bytes_per_element + total_size_bytes += scale_size_bytes + scale_info = { "scale": scale, - "shape": array_meta.get('shape', []), - "dtype": array_meta.get('dtype', 'unknown'), - "chunks": array_meta.get('chunks', []) + "shape": shape, + "dtype": dtype, + "chunks": chunks, + "size_mb": round(scale_size_bytes / (1024 * 1024), 2) if shape else 0 } + + # Extract image-specific information from the first scale + if scale == 0 and shape: + if len(shape) >= 2: + metadata["image_info"]["dimensions"] = [f"dim_{i}" for i in range(len(shape))] + if len(shape) >= 3: + # Assume last dimension might be channels + metadata["image_info"]["channels"] = [f"channel_{i}" for i in range(shape[-1])] + + metadata["image_info"]["bit_depth"] = dtype + metadata["scales"].append(scale_info) + metadata["technical_details"]["pyramid_levels"] += 1 except Exception as e: logger.debug(f"Scale {scale} not found for image {image_name}: {e}") break + metadata["technical_details"]["total_size_mb"] = round(total_size_bytes / (1024 * 1024), 2) + + # Try to get image attributes for coordinate system info + try: + attrs_url = urljoin(image_url, '.zattrs') + async with session.get(attrs_url) as response: + if response.status == 200: + attrs_text = await response.text() + attrs_data = json.loads(attrs_text) + + # Look for coordinate system or spatial metadata + if 'coordinate_systems' in attrs_data: + metadata["technical_details"]["coordinate_system"] = attrs_data['coordinate_systems'] + if 'pixel_size' in attrs_data: + metadata["technical_details"]["pixel_size"] = attrs_data['pixel_size'] + + except Exception as e: + logger.debug(f"Could not get image attributes for {image_name}: {e}") + return metadata async def _extract_spatialdata_points_metadata(self, session: aiohttp.ClientSession, points_url: str, points_name: str) -> Dict[str, Any]: @@ -730,7 +796,19 @@ async def _extract_spatialdata_table_metadata(self, session: aiohttp.ClientSessi metadata = { "name": table_name, "type": "table", - "components": {} + "components": {}, + "statistics": { + "n_obs": 0, + "n_vars": 0, + "memory_usage_mb": 0, + "data_types": [] + }, + "biological_context": { + "gene_list_preview": [], + "cell_type_categories": [], + "spatial_range": {}, + "quality_metrics": {} + } } # Look for standard AnnData components @@ -750,18 +828,50 @@ async def _extract_spatialdata_table_metadata(self, session: aiohttp.ClientSessi # X matrix - sparse or dense x_metadata = await self._extract_x_matrix_metadata(session, component_url) metadata["components"][component] = x_metadata - elif component in ['obs', 'var']: - # Observation/variable metadata - metadata["components"][component] = { - "type": "dataframe", - "description": f"{component} annotations" - } - elif component in ['obsm', 'varm']: - # Multi-dimensional annotations - metadata["components"][component] = { - "type": "multidimensional", - "description": f"{component} multidimensional data" - } + + # Extract dimensions for statistics + if 'shape' in x_metadata: + metadata["statistics"]["n_obs"] = x_metadata["shape"][0] if len(x_metadata["shape"]) > 0 else 0 + metadata["statistics"]["n_vars"] = x_metadata["shape"][1] if len(x_metadata["shape"]) > 1 else 0 + + elif component == 'obs': + # Observation metadata - extract cell information + obs_metadata = await self._extract_obs_metadata(session, component_url) + metadata["components"][component] = obs_metadata + + # Extract biological context from obs + if 'categorical_columns' in obs_metadata: + for col in obs_metadata['categorical_columns']: + if any(keyword in col.lower() for keyword in ['cell_type', 'cluster', 'annotation']): + metadata["biological_context"]["cell_type_categories"] = obs_metadata.get('column_info', {}).get(col, {}).get('categories', []) + + # Extract spatial information + spatial_cols = [col for col in obs_metadata.get('columns', []) if any(keyword in col.lower() for keyword in ['x', 'y', 'spatial', 'coord'])] + if spatial_cols: + metadata["biological_context"]["spatial_range"] = { + "spatial_columns": spatial_cols, + "has_spatial_data": True + } + + elif component == 'var': + # Variable metadata - extract gene information + var_metadata = await self._extract_var_metadata(session, component_url) + metadata["components"][component] = var_metadata + + # Extract gene list preview + if 'gene_names' in var_metadata: + metadata["biological_context"]["gene_list_preview"] = var_metadata["gene_names"][:20] # First 20 genes + + elif component == 'obsm': + # Multi-dimensional observations (embeddings, spatial coords) + obsm_metadata = await self._extract_obsm_metadata(session, component_url) + metadata["components"][component] = obsm_metadata + + elif component == 'uns': + # Unstructured metadata + uns_metadata = await self._extract_uns_metadata(session, component_url) + metadata["components"][component] = uns_metadata + else: metadata["components"][component] = { "type": "group", @@ -778,6 +888,9 @@ async def _extract_x_matrix_metadata(self, session: aiohttp.ClientSession, x_url # Check for sparse matrix components sparse_components = ['data', 'indices', 'indptr'] found_sparse = 0 + shape = [] + dtype = "unknown" + chunks = [] for component in sparse_components: try: @@ -787,15 +900,37 @@ async def _extract_x_matrix_metadata(self, session: aiohttp.ClientSession, x_url async with session.get(zarray_url) as response: if response.status == 200: found_sparse += 1 - + if component == 'data': + # Get data array metadata + zarray_text = await response.text() + array_meta = json.loads(zarray_text) + dtype = array_meta.get('dtype', 'unknown') + chunks = array_meta.get('chunks', []) + except Exception: pass if found_sparse == 3: + # Try to get shape from indptr + try: + indptr_url = urljoin(x_url, 'indptr/.zarray') + async with session.get(indptr_url) as response: + if response.status == 200: + indptr_text = await response.text() + indptr_meta = json.loads(indptr_text) + n_obs = indptr_meta.get('shape', [0])[0] - 1 if indptr_meta.get('shape') else 0 + shape = [n_obs, "unknown"] + except Exception: + pass + return { "type": "sparse_matrix", "format": "csr", - "description": "Sparse expression matrix" + "description": "Sparse expression matrix", + "shape": shape, + "dtype": dtype, + "chunks": chunks, + "sparsity": "compressed sparse row format" } else: # Try dense matrix @@ -803,9 +938,14 @@ async def _extract_x_matrix_metadata(self, session: aiohttp.ClientSession, x_url zarray_url = urljoin(x_url, '.zarray') async with session.get(zarray_url) as response: if response.status == 200: + zarray_text = await response.text() + array_meta = json.loads(zarray_text) return { "type": "dense_matrix", - "description": "Dense expression matrix" + "description": "Dense expression matrix", + "shape": array_meta.get('shape', []), + "dtype": array_meta.get('dtype', 'unknown'), + "chunks": array_meta.get('chunks', []) } except Exception: pass @@ -815,6 +955,181 @@ async def _extract_x_matrix_metadata(self, session: aiohttp.ClientSession, x_url "description": "Expression matrix" } + async def _extract_obs_metadata(self, session: aiohttp.ClientSession, obs_url: str) -> Dict[str, Any]: + """Extract metadata from obs (observations/cells) component.""" + metadata = { + "type": "dataframe", + "description": "Cell/observation annotations", + "columns": [], + "categorical_columns": [], + "numerical_columns": [], + "column_info": {}, + "sample_data": {} + } + + # Try to find column arrays + common_obs_columns = [ + 'cell_id', '_index', 'cell_area', 'nucleus_area', 'total_counts', + 'transcript_counts', 'control_probe_counts', 'region', 'cell_type', + 'cluster', 'leiden', 'louvain' + ] + + for col_name in common_obs_columns: + try: + col_url = urljoin(obs_url, f'{col_name}/') + zarray_url = urljoin(col_url, '.zarray') + + async with session.get(zarray_url) as response: + if response.status == 200: + zarray_text = await response.text() + array_meta = json.loads(zarray_text) + + metadata["columns"].append(col_name) + col_info = { + "shape": array_meta.get('shape', []), + "dtype": array_meta.get('dtype', 'unknown') + } + + # Check if it's categorical (has categories subdirectory) + try: + cat_url = urljoin(col_url, 'categories/.zarray') + async with session.get(cat_url) as cat_response: + if cat_response.status == 200: + metadata["categorical_columns"].append(col_name) + col_info["type"] = "categorical" + # Could extract category names here if needed + else: + # Check dtype for numerical vs string + if any(num_type in str(array_meta.get('dtype', '')).lower() + for num_type in ['int', 'float', 'double']): + metadata["numerical_columns"].append(col_name) + col_info["type"] = "numerical" + else: + col_info["type"] = "string" + except Exception: + # Fallback type detection + if any(num_type in str(array_meta.get('dtype', '')).lower() + for num_type in ['int', 'float', 'double']): + metadata["numerical_columns"].append(col_name) + col_info["type"] = "numerical" + else: + col_info["type"] = "string" + + metadata["column_info"][col_name] = col_info + + except Exception as e: + logger.debug(f"Column {col_name} not found in obs: {e}") + + return metadata + + async def _extract_var_metadata(self, session: aiohttp.ClientSession, var_url: str) -> Dict[str, Any]: + """Extract metadata from var (variables/genes) component.""" + metadata = { + "type": "dataframe", + "description": "Gene/feature annotations", + "columns": [], + "gene_names": [], + "gene_types": [], + "column_info": {} + } + + # Common var columns + common_var_columns = ['_index', 'gene_ids', 'feature_types', 'genome', 'gene_symbols'] + + for col_name in common_var_columns: + try: + col_url = urljoin(var_url, f'{col_name}/') + zarray_url = urljoin(col_url, '.zarray') + + async with session.get(zarray_url) as response: + if response.status == 200: + zarray_text = await response.text() + array_meta = json.loads(zarray_text) + + metadata["columns"].append(col_name) + metadata["column_info"][col_name] = { + "shape": array_meta.get('shape', []), + "dtype": array_meta.get('dtype', 'unknown') + } + + # If this looks like gene names, try to extract some + if col_name in ['_index', 'gene_ids', 'gene_symbols']: + # For now, we can't easily extract the actual data without loading + # But we can note the presence and shape + gene_count = array_meta.get('shape', [0])[0] if array_meta.get('shape') else 0 + metadata["gene_names"] = [f"Gene_{i}" for i in range(min(20, gene_count))] # Placeholder + + except Exception as e: + logger.debug(f"Column {col_name} not found in var: {e}") + + return metadata + + async def _extract_obsm_metadata(self, session: aiohttp.ClientSession, obsm_url: str) -> Dict[str, Any]: + """Extract metadata from obsm (multidimensional observations).""" + metadata = { + "type": "multidimensional", + "description": "Multidimensional observations (embeddings, coordinates)", + "embeddings": {}, + "spatial_coords": {} + } + + # Common obsm arrays + common_obsm = ['spatial', 'X_pca', 'X_umap', 'X_tsne', 'X_leiden', 'coordinates'] + + for array_name in common_obsm: + try: + array_url = urljoin(obsm_url, f'{array_name}/') + zarray_url = urljoin(array_url, '.zarray') + + async with session.get(zarray_url) as response: + if response.status == 200: + zarray_text = await response.text() + array_meta = json.loads(zarray_text) + + array_info = { + "shape": array_meta.get('shape', []), + "dtype": array_meta.get('dtype', 'unknown'), + "chunks": array_meta.get('chunks', []) + } + + if 'spatial' in array_name.lower() or 'coord' in array_name.lower(): + metadata["spatial_coords"][array_name] = array_info + else: + metadata["embeddings"][array_name] = array_info + + except Exception as e: + logger.debug(f"Obsm array {array_name} not found: {e}") + + return metadata + + async def _extract_uns_metadata(self, session: aiohttp.ClientSession, uns_url: str) -> Dict[str, Any]: + """Extract metadata from uns (unstructured) component.""" + metadata = { + "type": "unstructured", + "description": "Unstructured metadata and analysis results", + "contents": {} + } + + # Common uns contents + common_uns = ['spatialdata_attrs', 'neighbors', 'pca', 'umap', 'rank_genes_groups'] + + for item_name in common_uns: + try: + item_url = urljoin(uns_url, f'{item_name}/') + zgroup_url = urljoin(item_url, '.zgroup') + + async with session.get(zgroup_url) as response: + if response.status == 200: + metadata["contents"][item_name] = { + "type": "group", + "description": f"Analysis results: {item_name}" + } + + except Exception as e: + logger.debug(f"Uns item {item_name} not found: {e}") + + return metadata + # Global instance for reuse _extractor_instance = None diff --git a/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx b/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx index 00145e079..3fead0e24 100644 --- a/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx +++ b/src/charts/dialogs/zarrStream/ZarrMetadataDialog.tsx @@ -15,6 +15,12 @@ import { Image as ImageIcon, TableChart as TableIcon, Info as InfoIcon, + Assessment as AssessmentIcon, + Storage as StorageIcon, + Biotech as BiotechIcon, + Memory as MemoryIcon, + ExpandMore as ExpandMoreIcon, + GetApp as DownloadIcon, } from "@mui/icons-material"; import ReusableDialog from "../ReusableDialog"; import { debugExploreWithHTTP, debugExploreZarrDataset, extractZarrMetadata } from "./zarrMetadataUtils"; @@ -96,6 +102,87 @@ const DataTable = ({ data, maxRows = 10 }: { data: Record; maxRows? ); }; +const ExpandableSection = ({ title, children, defaultExpanded = false }: PropsWithChildren & { title: string; defaultExpanded?: boolean }) => { + const [expanded, setExpanded] = useState(defaultExpanded); + + return ( +
+ + {expanded && ( +
+ {children} +
+ )} +
+ ); +}; + +const StatisticCard = ({ label, value, unit = "", icon }: { label: string; value: string | number; unit?: string; icon?: React.ReactNode }) => ( +
+ {icon &&
{icon}
} +
+
{label}
+
+ {value}{unit} +
+
+
+); + +const ArrayPreviewTable = ({ columns, columnInfo }: { columns: string[]; columnInfo: Record }) => ( +
+ + + + + + + + + + + {columns.slice(0, 10).map((col) => { + const info = columnInfo[col] || {}; + return ( + + + + + + + ); + })} + +
ColumnTypeShapeDType
{col} + + {info.type || 'unknown'} + + + {info.shape ? info.shape.join(' ร— ') : 'N/A'} + + {info.dtype || 'N/A'} +
+ {columns.length > 10 && ( +
+ Showing 10 of {columns.length} columns +
+ )} +
+); + const Spinner = () => { return (
= {Object.keys((metadata as any).images || {}).length > 0 && ( }>Images - - {Object.entries((metadata as any).images || {}).map(([name, info]: [string, any]) => ( - -
-
Type: {info.type}
- {info.scales && info.scales.length > 0 && ( -
Scales: {info.scales.length}
- )} - {info.scales && info.scales[0] && ( -
Shape: {info.scales[0].shape?.join(' ร— ')}
- )} + {Object.entries((metadata as any).images || {}).map(([name, info]: [string, any]) => ( + +
+ } + /> + } + /> + } + /> + } + /> +
+ + {info.scales && info.scales.length > 0 && ( +
+
Scale Details
+
+ + + + + + + + + + + {info.scales.map((scale: any, idx: number) => ( + + + + + + + ))} + +
ScaleShapeSizeChunks
{scale.scale}{scale.shape?.join(' ร— ')}{scale.size_mb || 0} MB{scale.chunks?.join(' ร— ')}
+
- - ))} - + )} +
+ ))} )} @@ -525,23 +652,175 @@ const ZarrMetadataDialogComponent: React.FC = {Object.keys((metadata as any).tables || {}).length > 0 && (metadata as any).datasetFormat === 'spatialdata' && ( }>Tables (AnnData-like) - - {Object.entries((metadata as any).tables || {}).map(([name, info]: [string, any]) => ( - -
-
Type: {info.type}
- {info.components && Object.keys(info.components).length > 0 && ( -
- Components: {Object.keys(info.components).join(', ')} + {Object.entries((metadata as any).tables || {}).map(([name, info]: [string, any]) => ( + + {/* Statistics Overview */} +
+ } + /> + } + /> + } + /> + } + /> +
+ + {/* Biological Context */} + {info.biological_context && ( + +
+ {/* Gene Preview */} + {info.biological_context.gene_list_preview && info.biological_context.gene_list_preview.length > 0 && ( +
+
Gene Preview (Top 20)
+
+
+ {info.biological_context.gene_list_preview.map((gene: string, idx: number) => ( + + {gene} + + ))} +
+
+
+ )} + + {/* Cell Type Categories */} + {info.biological_context.cell_type_categories && info.biological_context.cell_type_categories.length > 0 && ( +
+
Cell Type Categories
+
+
+ {info.biological_context.cell_type_categories.slice(0, 10).map((cellType: string, idx: number) => ( + + {cellType} + + ))} +
+
+
+ )} +
+ + {/* Spatial Information */} + {info.biological_context.spatial_range && info.biological_context.spatial_range.has_spatial_data && ( +
+
Spatial Information
+
+
+ Spatial Columns: {info.biological_context.spatial_range.spatial_columns?.join(', ')} +
+
)} - {info.components?.X && ( -
Expression Matrix: {info.components.X.type}
+
+ )} + + {/* Component Details */} + {info.components && Object.keys(info.components).length > 0 && ( + + {/* Expression Matrix (X) */} + {info.components.X && ( +
+
Expression Matrix (X)
+
+
+
Type
+
{info.components.X.type}
+
+
+
Format
+
{info.components.X.format || 'N/A'}
+
+
+
Shape
+
{info.components.X.shape?.join(' ร— ') || 'N/A'}
+
+
+
Data Type
+
{info.components.X.dtype || 'N/A'}
+
+
+
)} -
- - ))} - + + {/* Observations (obs) */} + {info.components.obs && ( +
+
Cell Annotations (obs)
+ {info.components.obs.columns && info.components.obs.columns.length > 0 && ( + + )} +
+ )} + + {/* Variables (var) */} + {info.components.var && ( +
+
Gene Annotations (var)
+ {info.components.var.columns && info.components.var.columns.length > 0 && ( + + )} +
+ )} + + {/* Multidimensional observations (obsm) */} + {info.components.obsm && ( +
+
Embeddings & Coordinates (obsm)
+
+ {/* Embeddings */} + {info.components.obsm.embeddings && Object.keys(info.components.obsm.embeddings).length > 0 && ( +
+
Embeddings
+ {Object.entries(info.components.obsm.embeddings).map(([embName, embInfo]: [string, any]) => ( +
+
{embName}
+
Shape: {embInfo.shape?.join(' ร— ')}
+
+ ))} +
+ )} + + {/* Spatial Coordinates */} + {info.components.obsm.spatial_coords && Object.keys(info.components.obsm.spatial_coords).length > 0 && ( +
+
Spatial Coordinates
+ {Object.entries(info.components.obsm.spatial_coords).map(([coordName, coordInfo]: [string, any]) => ( +
+
{coordName}
+
Shape: {coordInfo.shape?.join(' ร— ')}
+
+ ))} +
+ )} +
+
+ )} + + )} + + ))} )} @@ -815,13 +1094,35 @@ const ZarrMetadataDialogComponent: React.FC = Enter the URL of a Zarr dataset (supports HTTP/HTTPS)

-
+
+ + {state.metadata && ( + + )} +