higlass-zarr-datafetchers
Version:
Fetch HiGlass track data from Zarr stores
177 lines (176 loc) • 6.2 kB
JavaScript
// src/ZarrMultivecDataFetcher.js
import {
open as zarrOpen,
root as zarrRoot,
get as zarrGet,
slice,
FetchStore
} from "zarrita";
function multivecChunksToTileDenseArray(chunks, tileShape, isRow) {
const fullTileLength = isRow ? tileShape[1] : tileShape[0] * tileShape[1];
const fullTileArray = new Float32Array(fullTileLength);
let offset = 0;
if (isRow) {
for (const chunk of chunks) {
const chunkData = chunk.data;
fullTileArray.set(chunkData, offset);
offset += chunkData.length;
}
} else {
const numSamples = tileShape[0];
for (let sampleI = 0; sampleI < numSamples; sampleI++) {
for (const chunk of chunks) {
const chunkData = chunk.data.subarray(sampleI * chunk.stride[0], (sampleI + 1) * chunk.stride[0]);
fullTileArray.set(chunkData, offset);
offset += chunkData.length;
}
}
}
return fullTileArray;
}
var ZarrMultivecDataFetcher = function ZarrMultivecDataFetcher2(HGC, ...args) {
if (!new.target) {
throw new Error(
'Uncaught TypeError: Class constructor cannot be invoked without "new"'
);
}
const { slugid } = HGC.libraries;
const {
absToChr,
parseChromsizesRows,
genomicRangeToChromosomeChunks,
DenseDataExtrema1D,
minNonZero,
maxNonZero
} = HGC.utils;
class ZarrMultivecDataFetcherClass {
constructor(dataConfig) {
this.dataConfig = dataConfig;
this.trackUid = slugid.nice();
if (dataConfig.hasStoreRoot) {
this.storeRoot = Promise.resolve(ZarrMultivecDataFetcher2.urlToStoreRoot[dataConfig.url]);
} else if (dataConfig.url) {
const { url, options = {} } = dataConfig;
this.store = new FetchStore(url, options);
this.storeRoot = Promise.resolve(zarrRoot(this.store));
}
if (dataConfig.row !== void 0) {
this.row = dataConfig.row;
}
}
tilesetInfo(callback) {
this.tilesetInfoLoading = true;
return this.storeRoot.then((root) => zarrOpen(root)).then((grp) => {
const attrs = grp.attrs;
this.tilesetInfoLoading = false;
const chromSizes = attrs.multiscales.map((d) => [d.name, d.metadata.chromsize]);
const finalChrom = attrs.multiscales[attrs.multiscales.length - 1];
const maxPos = finalChrom.metadata.chromoffset + finalChrom.metadata.chromsize;
const tileSize = attrs.shape[1];
const retVal = {
...attrs,
shape: [attrs.shape[1], attrs.shape[0]],
chromSizes,
tile_size: tileSize,
max_width: maxPos,
min_pos: [0],
max_pos: [maxPos],
max_zoom: Math.ceil(Math.log(maxPos / tileSize) / Math.log(2))
};
if (callback) {
callback(retVal);
}
return retVal;
}).catch((err) => {
this.tilesetInfoLoading = false;
if (callback) {
callback({
error: `Error parsing zarr multivec: ${err}`
});
}
});
}
fetchTilesDebounced(receivedTiles, tileIds) {
const tiles = {};
const validTileIds = [];
const tilePromises = [];
for (const tileId of tileIds) {
const parts = tileId.split(".");
const z = parseInt(parts[0], 10);
const x = parseInt(parts[1], 10);
if (Number.isNaN(x) || Number.isNaN(z)) {
console.warn("Invalid tile zoom or position:", z, x);
continue;
}
validTileIds.push(tileId);
tilePromises.push(this.tile(z, x, tileId));
}
Promise.all(tilePromises).then((values) => {
for (let i = 0; i < values.length; i++) {
const validTileId = validTileIds[i];
tiles[validTileId] = values[i];
tiles[validTileId].tilePositionId = validTileId;
}
receivedTiles(tiles);
});
return tiles;
}
tile(z, x, tileId) {
const { storeRoot } = this;
return this.tilesetInfo().then((tsInfo) => {
const resolution = +tsInfo.resolutions[z];
const tileSize = +tsInfo.tile_size;
const binSize = resolution;
const tileStart = x * tileSize * resolution;
const tileEnd = tileStart + tileSize * resolution;
const chromSizes = tsInfo.chromSizes;
const chromInfo = parseChromsizesRows(chromSizes);
const [chrStart, chrStartPos] = absToChr(tileStart, chromInfo);
const [chrEnd, chrEndPos] = absToChr(tileEnd, chromInfo);
const genomicStart = { chr: chrStart, pos: chrStartPos };
const genomicEnd = { chr: chrEnd, pos: chrEndPos };
const chrChunks = genomicRangeToChromosomeChunks(
chromSizes,
genomicStart,
genomicEnd,
binSize,
tileSize
);
return Promise.all(
chrChunks.map(([chrName, zStart, zEnd]) => {
return storeRoot.then((root) => zarrOpen(root.resolve(`/chromosomes/${chrName}/${resolution}/`), { kind: "array" })).then((arr) => this.row !== void 0 ? zarrGet(arr, [this.row, slice(zStart, zEnd)]) : zarrGet(arr, [null, slice(zStart, zEnd)]));
})
).then((chunks) => {
const dense = multivecChunksToTileDenseArray(chunks, [tsInfo.shape[1], tsInfo.shape[0]], this.row !== void 0);
return Promise.resolve({
dense,
denseDataExtrema: new DenseDataExtrema1D(dense),
dtype: "float32",
min_value: Math.min.apply(null, dense),
max_value: Math.max.apply(null, dense),
minNonZero: minNonZero(dense),
maxNonZero: maxNonZero(dense),
server: null,
size: 1,
shape: tsInfo.shape,
tileId,
tilePos: [x],
tilePositionId: tileId,
tilesetUid: null,
zoomLevel: z
});
});
});
}
}
return new ZarrMultivecDataFetcherClass(...args);
};
ZarrMultivecDataFetcher.config = {
type: "zarr-multivec"
};
ZarrMultivecDataFetcher.urlToStoreRoot = {};
var ZarrMultivecDataFetcher_default = ZarrMultivecDataFetcher;
export {
ZarrMultivecDataFetcher_default as ZarrMultivecDataFetcher
};
//# sourceMappingURL=index.js.map