UNPKG

@loaders.gl/zarr

Version:

Framework-independent loaders for Zarr

8 lines (7 loc) 13.9 kB
{ "version": 3, "sources": ["index.js", "lib/utils.js", "lib/zarr-pixel-source.js", "lib/load-zarr.js"], "sourcesContent": ["// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nexport { loadZarr } from \"./lib/load-zarr.js\";\nexport { default as ZarrPixelSource } from \"./lib/zarr-pixel-source.js\";\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nimport { openGroup, HTTPStore } from 'zarr';\nexport function normalizeStore(source) {\n if (typeof source === 'string') {\n return new HTTPStore(source);\n }\n return source;\n}\nexport async function loadMultiscales(store, path = '') {\n const grp = await openGroup(store, path);\n const rootAttrs = (await grp.attrs.asObject());\n // Root of Zarr store must implement multiscales extension.\n // https://github.com/zarr-developers/zarr-specs/issues/50\n if (!Array.isArray(rootAttrs.multiscales)) {\n throw new Error('Cannot find Zarr multiscales metadata.');\n }\n const { datasets } = rootAttrs.multiscales[0];\n const promises = datasets.map((d) => grp.getItem(d.path));\n return {\n data: await Promise.all(promises),\n rootAttrs\n };\n}\n/*\n * Creates an ES6 map of 'label' -> index\n * > const labels = ['a', 'b', 'c', 'd'];\n * > const dims = getDims(labels);\n * > dims('a') === 0;\n * > dims('b') === 1;\n * > dims('c') === 2;\n * > dims('hi!'); // throws\n */\nexport function getDims(labels) {\n const lookup = new Map(labels.map((name, i) => [name, i]));\n if (lookup.size !== labels.length) {\n throw Error('Labels must be unique, found duplicated label.');\n }\n return (name) => {\n const index = lookup.get(name);\n if (index === undefined) {\n throw Error('Invalid dimension.');\n }\n return index;\n };\n}\nfunction prevPowerOf2(x) {\n return 2 ** Math.floor(Math.log2(x));\n}\n/*\n * Helper method to determine whether pixel data is interleaved or not.\n * > isInterleaved([1, 24, 24]) === false;\n * > isInterleaved([1, 24, 24, 3]) === true;\n */\nexport function isInterleaved(shape) {\n const lastDimSize = shape[shape.length - 1];\n return lastDimSize === 3 || lastDimSize === 4;\n}\nexport function guessTileSize(arr) {\n const interleaved = isInterleaved(arr.shape);\n const [yChunk, xChunk] = arr.chunks.slice(interleaved ? -3 : -2);\n const size = Math.min(yChunk, xChunk);\n // deck.gl requirement for power-of-two tile size.\n return prevPowerOf2(size);\n}\nexport function guessLabels(rootAttrs) {\n if ('omero' in rootAttrs) {\n return ['t', 'c', 'z', 'y', 'x'];\n }\n throw new Error('Could not infer dimension labels for Zarr source. Must provide dimension labels.');\n}\n/*\n * The 'indexer' for a Zarr-based source translates\n * a 'selection' to an array of indices that align to\n * the labeled dimensions.\n *\n * > const labels = ['a', 'b', 'y', 'x'];\n * > const indexer = getIndexer(labels);\n * > console.log(indexer({ a: 10, b: 20 }));\n * > // [10, 20, 0, 0]\n */\nexport function getIndexer(labels) {\n const size = labels.length;\n const dims = getDims(labels);\n return (sel) => {\n if (Array.isArray(sel)) {\n return [...sel];\n }\n const selection = Array(size).fill(0);\n for (const [key, value] of Object.entries(sel)) {\n selection[dims(key)] = value;\n }\n return selection;\n };\n}\nexport function getImageSize(source) {\n const interleaved = isInterleaved(source.shape);\n // 2D image data in Zarr are represented as (..., rows, columns [, bands])\n // If an image is interleaved (RGB/A), we need to ignore the last dimension (bands)\n // to get the height and weight of the image.\n const [height, width] = source.shape.slice(interleaved ? -3 : -2);\n return { height, width };\n}\n/**\n * Preserves (double) slashes earlier in the path, so this works better\n * for URLs. From https://stackoverflow.com/a/46427607\n * @param args parts of a path or URL to join.\n */\nexport function joinUrlParts(...args) {\n return args\n .map((part, i) => {\n if (i === 0)\n return part.trim().replace(/[/]*$/g, '');\n return part.trim().replace(/(^[/]*|[/]*$)/g, '');\n })\n .filter((x) => x.length)\n .join('/');\n}\nexport function validLabels(labels, shape) {\n if (labels.length !== shape.length) {\n throw new Error('Labels do not match Zarr array shape.');\n }\n const n = shape.length;\n if (isInterleaved(shape)) {\n // last three dimensions are [row, column, bands]\n return labels[n - 3] === 'y' && labels[n - 2] === 'x' && labels[n - 1] === '_c';\n }\n // last two dimensions are [row, column]\n return labels[n - 2] === 'y' && labels[n - 1] === 'x';\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nimport { BoundsCheckError, slice } from 'zarr';\nimport { getImageSize, isInterleaved, getIndexer } from \"./utils.js\";\nexport const DTYPE_LOOKUP = {\n u1: 'Uint8',\n u2: 'Uint16',\n u4: 'Uint32',\n f4: 'Float32',\n f8: 'Float64',\n i1: 'Int8',\n i2: 'Int16',\n i4: 'Int32'\n};\nclass ZarrPixelSource {\n labels;\n tileSize;\n _data;\n _indexer;\n _readChunks;\n constructor(data, labels, tileSize) {\n this._indexer = getIndexer(labels);\n this._data = data;\n const xChunkSize = data.chunks[this._xIndex];\n const yChunkSize = data.chunks[this._xIndex - 1];\n this._readChunks = tileSize === xChunkSize && tileSize === yChunkSize;\n this.labels = labels;\n this.tileSize = tileSize;\n }\n get shape() {\n return this._data.shape;\n }\n get dtype() {\n const suffix = this._data.dtype.slice(1);\n if (!(suffix in DTYPE_LOOKUP)) {\n throw Error(`Zarr dtype not supported, got ${suffix}.`);\n }\n return DTYPE_LOOKUP[suffix];\n }\n get _xIndex() {\n const interleave = isInterleaved(this._data.shape);\n return this._data.shape.length - (interleave ? 2 : 1);\n }\n _chunkIndex(selection, x, y) {\n const sel = this._indexer(selection);\n sel[this._xIndex] = x;\n sel[this._xIndex - 1] = y;\n return sel;\n }\n /**\n * Converts x, y tile indices to zarr dimension Slices within image bounds.\n */\n _getSlices(x, y) {\n const { height, width } = getImageSize(this);\n const [xStart, xStop] = [x * this.tileSize, Math.min((x + 1) * this.tileSize, width)];\n const [yStart, yStop] = [y * this.tileSize, Math.min((y + 1) * this.tileSize, height)];\n // Deck.gl can sometimes request edge tiles that don't exist. We throw\n // a BoundsCheckError which is picked up in `ZarrPixelSource.onTileError`\n // and ignored by deck.gl.\n if (xStart === xStop || yStart === yStop) {\n throw new BoundsCheckError('Tile slice is zero-sized.');\n }\n return [slice(xStart, xStop), slice(yStart, yStop)];\n }\n async getRaster({ selection }) {\n const sel = this._chunkIndex(selection, null, null);\n const { data, shape } = (await this._data.getRaw(sel));\n const [height, width] = shape;\n return { data, width, height };\n }\n async getTile(props) {\n const { x, y, selection, signal } = props;\n let res;\n if (this._readChunks) {\n // Can read chunks directly by key since tile size matches chunk shape\n const sel = this._chunkIndex(selection, x, y);\n res = await this._data.getRawChunk(sel, { storeOptions: { signal } });\n }\n else {\n // Need to use zarr fancy indexing to get desired tile size.\n const [xSlice, ySlice] = this._getSlices(x, y);\n const sel = this._chunkIndex(selection, xSlice, ySlice);\n res = await this._data.getRaw(sel);\n }\n const { data, shape: [height, width] } = res;\n return { data, width, height };\n }\n onTileError(err) {\n if (!(err instanceof BoundsCheckError)) {\n // Rethrow error if something other than tile being requested is out of bounds.\n throw err;\n }\n }\n}\nexport default ZarrPixelSource;\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nimport { loadMultiscales, guessTileSize, guessLabels, normalizeStore, validLabels } from \"./utils.js\";\nimport ZarrPixelSource from \"./zarr-pixel-source.js\";\nexport async function loadZarr(root, options = {}) {\n const store = normalizeStore(root);\n const { data, rootAttrs } = await loadMultiscales(store);\n const tileSize = guessTileSize(data[0]);\n // If no labels are provided, inspect the root attributes for the store.\n // For now, we only infer labels for OME-Zarr.\n const labels = options.labels ?? guessLabels(rootAttrs);\n if (!validLabels(labels, data[0].shape)) {\n throw new Error('Invalid labels for Zarr array dimensions.');\n }\n const pyramid = data.map((arr) => new ZarrPixelSource(arr, labels, tileSize));\n return {\n data: pyramid,\n metadata: rootAttrs\n };\n}\n"], "mappings": "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", "names": ["import_zarr"] }