UNPKG

vitessce

Version:

Vitessce app and React component library

131 lines (121 loc) 4.51 kB
import { ZarrPixelSource, loadOmeTiff } from '@hms-dbmi/viv'; import { openArray } from 'zarr'; import rasterSchema from '../schemas/raster.schema.json'; import JsonLoader from './JsonLoader'; import { AbstractLoaderError } from './errors'; import LoaderResult from './LoaderResult'; import { initializeRasterLayersAndChannels } from '../components/spatial/utils'; async function initLoader(imageData) { const { type, url, metadata, requestInit, } = imageData; switch (type) { case ('zarr'): { const { dimensions, isPyramid, transform, } = metadata || {}; const labels = dimensions.map(d => d.field); let source; if (isPyramid) { const metadataUrl = `${url}${ url.slice(-1) === '/' ? '' : '/' }.zmetadata`; const response = await fetch(metadataUrl); const { metadata: zarrMetadata } = await response.json(); const paths = Object.keys(zarrMetadata) .filter(metaKey => metaKey.includes('.zarray')) .map(arrMetaKeys => arrMetaKeys.slice(0, -7)); const data = await Promise.all( paths.map(path => openArray({ store: url, path })), ); const [yChunk, xChunk] = data[0].chunks.slice(-2); const size = Math.min(yChunk, xChunk); // deck.gl requirement for power-of-two tile size. const tileSize = 2 ** Math.floor(Math.log2(size)); source = data.map(d => new ZarrPixelSource(d, labels, tileSize)); } else { const data = await openArray({ store: url }); source = new ZarrPixelSource(data, labels); } return { data: source, metadata: { dimensions, transform }, channels: (dimensions.find(d => d.field === 'channel') || dimensions[0]).values }; } case ('ome-tiff'): { let loader; // Fetch offsets for ome-tiff if needed. if (metadata && 'omeTiffOffsetsUrl' in metadata) { const { omeTiffOffsetsUrl } = metadata; const res = await fetch(omeTiffOffsetsUrl, (requestInit || {})); if (res.ok) { const offsets = await res.json(); loader = await loadOmeTiff( url, { offsets, headers: requestInit?.headers, }, ); } else { throw new Error('Offsets not found but provided.'); } } else { loader = await loadOmeTiff(url, { headers: requestInit?.headers }); } const { Pixels: { Channels } } = loader.metadata; const channels = Array.isArray(Channels) ? Channels.map((channel, i) => channel.Name || `Channel ${i}`) : [Channels.Name || `Channel ${0}`]; return { ...loader, channels }; } default: { throw Error(`Image type (${type}) is not supported`); } } } export default class RasterLoader extends JsonLoader { constructor(dataSource, params) { const { url, options } = params; if (!url && options) { // eslint-disable-next-line no-param-reassign dataSource.url = URL.createObjectURL(new Blob([JSON.stringify(options)])); } super(dataSource, params); this.schema = rasterSchema; } async load() { const payload = await super.load().catch(reason => Promise.resolve(reason)); if (payload instanceof AbstractLoaderError) { return Promise.reject(payload); } const { data: raster } = payload; const { images, renderLayers, usePhysicalSizeScaling = false } = raster; // Get image name and URL tuples. const urls = images .filter(image => !image.url.includes('zarr')) .map(image => ([image.url, image.name])); // Add a loaderCreator function for each image layer. const imagesWithLoaderCreators = images.map(image => ({ ...image, loaderCreator: async () => initLoader(image), })); // TODO: use options for initial selection of channels // which omit domain/slider ranges. if (!this.autoImageCache) { this.autoImageCache = initializeRasterLayersAndChannels( imagesWithLoaderCreators, renderLayers, usePhysicalSizeScaling, ); } return this.autoImageCache.then((autoImages) => { const [autoImageLayers, imageLayerLoaders, imageLayerMeta] = autoImages; const coordinationValues = { spatialRasterLayers: autoImageLayers, }; return new LoaderResult( { loaders: imageLayerLoaders, meta: imageLayerMeta }, urls, coordinationValues, ); }); } }