@loaders.gl/zarr
Version:
Framework-independent loaders for Zarr
97 lines (96 loc) • 3.43 kB
JavaScript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { BoundsCheckError, slice } from 'zarr';
import { getImageSize, isInterleaved, getIndexer } from "./utils.js";
export const DTYPE_LOOKUP = {
u1: 'Uint8',
u2: 'Uint16',
u4: 'Uint32',
f4: 'Float32',
f8: 'Float64',
i1: 'Int8',
i2: 'Int16',
i4: 'Int32'
};
class ZarrPixelSource {
labels;
tileSize;
_data;
_indexer;
_readChunks;
constructor(data, labels, tileSize) {
this._indexer = getIndexer(labels);
this._data = data;
const xChunkSize = data.chunks[this._xIndex];
const yChunkSize = data.chunks[this._xIndex - 1];
this._readChunks = tileSize === xChunkSize && tileSize === yChunkSize;
this.labels = labels;
this.tileSize = tileSize;
}
get shape() {
return this._data.shape;
}
get dtype() {
const suffix = this._data.dtype.slice(1);
if (!(suffix in DTYPE_LOOKUP)) {
throw Error(`Zarr dtype not supported, got ${suffix}.`);
}
return DTYPE_LOOKUP[suffix];
}
get _xIndex() {
const interleave = isInterleaved(this._data.shape);
return this._data.shape.length - (interleave ? 2 : 1);
}
_chunkIndex(selection, x, y) {
const sel = this._indexer(selection);
sel[this._xIndex] = x;
sel[this._xIndex - 1] = y;
return sel;
}
/**
* Converts x, y tile indices to zarr dimension Slices within image bounds.
*/
_getSlices(x, y) {
const { height, width } = getImageSize(this);
const [xStart, xStop] = [x * this.tileSize, Math.min((x + 1) * this.tileSize, width)];
const [yStart, yStop] = [y * this.tileSize, Math.min((y + 1) * this.tileSize, height)];
// Deck.gl can sometimes request edge tiles that don't exist. We throw
// a BoundsCheckError which is picked up in `ZarrPixelSource.onTileError`
// and ignored by deck.gl.
if (xStart === xStop || yStart === yStop) {
throw new BoundsCheckError('Tile slice is zero-sized.');
}
return [slice(xStart, xStop), slice(yStart, yStop)];
}
async getRaster({ selection }) {
const sel = this._chunkIndex(selection, null, null);
const { data, shape } = (await this._data.getRaw(sel));
const [height, width] = shape;
return { data, width, height };
}
async getTile(props) {
const { x, y, selection, signal } = props;
let res;
if (this._readChunks) {
// Can read chunks directly by key since tile size matches chunk shape
const sel = this._chunkIndex(selection, x, y);
res = await this._data.getRawChunk(sel, { storeOptions: { signal } });
}
else {
// Need to use zarr fancy indexing to get desired tile size.
const [xSlice, ySlice] = this._getSlices(x, y);
const sel = this._chunkIndex(selection, xSlice, ySlice);
res = await this._data.getRaw(sel);
}
const { data, shape: [height, width] } = res;
return { data, width, height };
}
onTileError(err) {
if (!(err instanceof BoundsCheckError)) {
// Rethrow error if something other than tile being requested is out of bounds.
throw err;
}
}
}
export default ZarrPixelSource;