Skip to content

Commit f62d2a2

Browse files
authored
feat(geotiff): Consolidate helpers for GeoTIFF images. (#1469)
* rebase, linting/prettier * lint * Remove DTYPE_VALUES constant
1 parent c1f7d7b commit f62d2a2

11 files changed

+63
-136
lines changed

modules/geotiff/src/constants.ts

-83
This file was deleted.

modules/geotiff/src/index.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
export {loadGeoTiff} from './lib/load-geotiff';
2-
export {loadOMETiff} from './lib/load-ome-tiff';
32
export {default as TiffPixelSource} from './lib/tiff-pixel-source';

modules/geotiff/src/lib/load-geotiff.ts

+35-16
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import {fromUrl, fromBlob} from 'geotiff';
2-
import type {GeoTIFF} from 'geotiff';
1+
import {fromUrl, fromBlob, GeoTIFF} from 'geotiff';
32

43
import {
54
// createPoolProxy,
@@ -8,31 +7,45 @@ import {
87
} from './utils/proxies';
98
// import Pool from './lib/Pool';
109

11-
import {loadOMETiff} from './load-ome-tiff';
10+
import {loadOmeTiff, isOmeTiff} from './ome/load-ome-tiff';
11+
import type TiffPixelSource from './tiff-pixel-source';
1212

13-
interface TiffOptions {
13+
/** Options for initializing a tiff pixel source. */
14+
interface GeoTIFFOptions {
15+
/** Headers passed to each underlying request. */
1416
headers?: Record<string, unknown>;
17+
/** Performance enhancment to index the remote tiff source using pre-computed byte-offsets. Generated via https://github.com/ilan-gold/generate-tiff-offsets */
1518
offsets?: number[];
19+
/** Indicates whether a multi-threaded pool of image decoders should be used to decode tiles. */
1620
pool?: boolean;
1721
}
1822

23+
interface GeoTIFFData {
24+
data: TiffPixelSource<string[]>[];
25+
metadata: Record<string, unknown>;
26+
}
27+
1928
/**
2029
* Opens an OME-TIFF via URL and returns data source and associated metadata for first image.
2130
*
22-
* @param {(string | File)} source url or File object.
23-
* @param {{ headers: (undefined | Headers), offsets: (undefined | number[]), pool: (undefined | boolean ) }} opts
24-
* Options for initializing a tiff pixel source. Headers are passed to each underlying fetch request. Offests are
25-
// * a performance enhancment to index the remote tiff source using pre-computed byte-offsets. Pool indicates whether a
26-
* multi-threaded pool of image decoders should be used to decode tiles (default = true).
27-
* @return {Promise<{ data: TiffPixelSource[], metadata: ImageMeta }>} data source and associated OME-Zarr metadata.
31+
* @param source url string, File/Blob object, or GeoTIFF object
32+
* @param opts options for initializing a tiff pixel source.
33+
* - `opts.headers` are passed to each underlying fetch request.
34+
* - `opts.offsets` are a performance enhancment to index the remote tiff source using pre-computed byte-offsets.
35+
* - `opts.pool` indicates whether a multi-threaded pool of image decoders should be used to decode tiles (default = true).
36+
* @return data source and associated OME-Zarr metadata.
2837
*/
29-
export async function loadGeoTiff(source: string | File, opts: TiffOptions = {}) {
30-
const {headers, offsets, pool = true} = opts;
31-
32-
let tiff: GeoTIFF;
38+
export async function loadGeoTiff(
39+
source: string | Blob | GeoTIFF,
40+
opts: GeoTIFFOptions = {}
41+
): Promise<GeoTIFFData> {
42+
const {headers, offsets} = opts;
3343

3444
// Create tiff source
35-
if (typeof source === 'string') {
45+
let tiff: GeoTIFF;
46+
if (source instanceof GeoTIFF) {
47+
tiff = source;
48+
} else if (typeof source === 'string') {
3649
tiff = await fromUrl(source, headers);
3750
} else {
3851
tiff = await fromBlob(source);
@@ -61,5 +74,11 @@ export async function loadGeoTiff(source: string | File, opts: TiffOptions = {})
6174
*/
6275
checkProxies(tiff);
6376

64-
return loadOMETiff(tiff);
77+
const firstImage = await tiff.getImage(0);
78+
79+
if (isOmeTiff(firstImage)) {
80+
return loadOmeTiff(tiff, firstImage);
81+
}
82+
83+
throw new Error('GeoTIFF not recognized.');
6584
}

modules/geotiff/src/lib/load-ome-tiff.ts modules/geotiff/src/lib/ome/load-ome-tiff.ts

+10-17
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
1-
import type {GeoTIFF} from 'geotiff';
2-
import {fromString} from './ome/omexml';
1+
import type {GeoTIFF, GeoTIFFImage} from 'geotiff';
32

4-
import TiffPixelSource from './tiff-pixel-source';
5-
import {getOmeLegacyIndexer, getOmeSubIFDIndexer, OmeTiffIndexer} from './ome/ome-indexers';
6-
import {getOmePixelSourceMeta} from './ome/ome-utils';
3+
import TiffPixelSource from '../tiff-pixel-source';
4+
import {getOmeLegacyIndexer, getOmeSubIFDIndexer, OmeTiffIndexer} from './ome-indexers';
5+
import {getOmePixelSourceMeta} from './ome-utils';
6+
import {fromString} from './omexml';
7+
import type {OmeTiffSelection} from './ome-indexers';
78

8-
export interface OmeTiffSelection {
9-
t: number;
10-
c: number;
11-
z: number;
12-
}
9+
export const isOmeTiff = (img: GeoTIFFImage) => img.fileDirectory.ImageDescription.includes('<OME');
1310

14-
export async function loadOMETiff(tiff: GeoTIFF) {
11+
export async function loadOmeTiff(tiff: GeoTIFF, firstImage: GeoTIFFImage) {
1512
// Get first image from tiff and inspect OME-XML metadata
16-
const firstImage = await tiff.getImage(0);
1713
const {
1814
ImageDescription,
1915
SubIFDs,
@@ -25,7 +21,7 @@ export async function loadOMETiff(tiff: GeoTIFF) {
2521
* Image pyramids are stored differently between versions of Bioformats.
2622
* Thus we need a different indexer depending on which format we have.
2723
*/
28-
let levels;
24+
let levels: number;
2925
let pyramidIndexer: OmeTiffIndexer;
3026

3127
if (SubIFDs) {
@@ -51,8 +47,5 @@ export async function loadOMETiff(tiff: GeoTIFF) {
5147
return source;
5248
});
5349

54-
return {
55-
data,
56-
metadata: imgMeta
57-
};
50+
return {data, metadata: imgMeta};
5851
}

modules/geotiff/src/lib/ome/ome-indexers.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import type {GeoTIFFImage, GeoTIFF, ImageFileDirectory} from 'geotiff';
22
import type {OMEXML} from '../ome/omexml';
3-
// TODO - circular dependency
4-
import type {OmeTiffSelection} from '../load-ome-tiff';
53

4+
export type OmeTiffSelection = {t: number; c: number; z: number};
65
export type OmeTiffIndexer = (sel: OmeTiffSelection, z: number) => Promise<GeoTIFFImage>;
76

87
/*

modules/geotiff/src/lib/ome/ome-utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {getDims, getLabels} from './utils';
22
import type {OMEXML, UnitsLength} from './omexml';
33

4-
const DTYPE_LOOKUP = {
4+
export const DTYPE_LOOKUP = {
55
uint8: 'Uint8',
66
uint16: 'Uint16',
77
uint32: 'Uint32',

modules/geotiff/src/lib/tiff-pixel-source.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
PixelSource,
66
PixelSourceSelection,
77
PixelSourceMeta,
8-
SupportedDtype,
8+
Dtype,
99
Labels,
1010
RasterSelection,
1111
TileSelection,
@@ -17,7 +17,7 @@ class TiffPixelSource<S extends string[]> implements PixelSource<S> {
1717

1818
constructor(
1919
indexer: (sel: PixelSourceSelection<S>) => Promise<GeoTIFFImage>,
20-
public dtype: SupportedDtype,
20+
public dtype: Dtype,
2121
public tileSize: number,
2222
public shape: number[],
2323
public labels: Labels<S>,

modules/geotiff/src/types.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type {DTYPE_VALUES} from './constants';
1+
import {DTYPE_LOOKUP} from './lib/ome/ome-utils';
22

3-
export type SupportedDtype = keyof typeof DTYPE_VALUES;
4-
export type SupportedTypedArray = InstanceType<typeof globalThis[`${SupportedDtype}Array`]>;
3+
export type Dtype = typeof DTYPE_LOOKUP[keyof typeof DTYPE_LOOKUP];
4+
export type TypedArray = InstanceType<typeof globalThis[`${Dtype}Array`]>;
55

66
export interface PixelData {
7-
data: SupportedTypedArray;
7+
data: TypedArray;
88
width: number;
99
height: number;
1010
}
@@ -47,7 +47,7 @@ export interface PixelSource<S extends string[]> {
4747
getTile(sel: TileSelection<S>): Promise<PixelData>;
4848
onTileError(err: Error): void;
4949
shape: number[];
50-
dtype: SupportedDtype;
50+
dtype: Dtype;
5151
labels: Labels<S>;
5252
tileSize: number;
5353
meta?: PixelSourceMeta;

modules/geotiff/src/typings/geotiff.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
declare type TypedArray = import('../constants').TypedArray;
1+
declare type TypedArray = import('../types').TypedArray;
22

33
declare module 'geotiff' {
44
function fromUrl(url: string, headers?: Record<string, unknown>): Promise<GeoTIFF>;

modules/geotiff/test/ome-tiff.spec.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import test from 'tape-promise/tape';
22
import {fromFile} from 'geotiff';
33
import {resolvePath, isBrowser} from '@loaders.gl/core';
44

5-
import {loadOMETiff} from '@loaders.gl/geotiff';
5+
import {loadGeoTiff} from '@loaders.gl/geotiff';
66

77
const TIFF_URL = resolvePath('@loaders.gl/geotiff/test/data/multi-channel.ome.tif');
88

@@ -12,7 +12,7 @@ test('Creates correct TiffPixelSource for OME-TIFF.', async (t) => {
1212
return;
1313
}
1414
const tiff = await fromFile(TIFF_URL);
15-
const {data} = await loadOMETiff(tiff);
15+
const {data} = await loadGeoTiff(tiff);
1616
t.equal(data.length, 1, 'image should not be pyramidal.');
1717
const [base] = data;
1818
t.deepEqual(base.labels, ['t', 'c', 'z', 'y', 'x'], 'should have DimensionOrder "XYZCT".');
@@ -28,7 +28,7 @@ test('Get raster data.', async (t) => {
2828
return;
2929
}
3030
const tiff = await fromFile(TIFF_URL);
31-
const {data} = await loadOMETiff(tiff);
31+
const {data} = await loadGeoTiff(tiff);
3232
const [base] = data;
3333

3434
for (let c = 0; c < 3; c += 1) {
@@ -54,7 +54,7 @@ test('Correct OME-XML.', async (t) => {
5454
return;
5555
}
5656
const tiff = await fromFile(TIFF_URL);
57-
const {metadata} = await loadOMETiff(tiff);
57+
const {metadata} = await loadGeoTiff(tiff);
5858
const {Name, Pixels} = metadata;
5959
t.equal(Name, 'multi-channel.ome.tif', `Name should be 'multi-channel.ome.tif'.`);
6060
t.equal(Pixels.SizeC, 3, 'Should have three channels.');

modules/geotiff/test/tiff.spec.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import test from 'tape-promise/tape';
22
import {fromFile} from 'geotiff';
33
import {resolvePath, isBrowser} from '@loaders.gl/core';
44

5-
import {loadOMETiff} from '@loaders.gl/geotiff';
5+
import {loadGeoTiff} from '@loaders.gl/geotiff';
66

77
const TIFF_URL = resolvePath('@loaders.gl/geotiff/test/data/multi-channel.ome.tif');
88

@@ -12,7 +12,7 @@ test('Creates correct TiffPixelSource for OME-TIFF.', async (t) => {
1212
return;
1313
}
1414
const tiff = await fromFile(TIFF_URL);
15-
const {data} = await loadOMETiff(tiff);
15+
const {data} = await loadGeoTiff(tiff);
1616
t.equal(data.length, 1, 'image should not be pyramidal.');
1717
const [base] = data;
1818
t.deepEqual(base.labels, ['t', 'c', 'z', 'y', 'x'], 'should have DimensionOrder "XYZCT".');
@@ -28,7 +28,7 @@ test('Get raster data.', async (t) => {
2828
return;
2929
}
3030
const tiff = await fromFile(TIFF_URL);
31-
const {data} = await loadOMETiff(tiff);
31+
const {data} = await loadGeoTiff(tiff);
3232
const [base] = data;
3333

3434
for (let c = 0; c < 3; c += 1) {
@@ -54,7 +54,7 @@ test('Correct OME-XML.', async (t) => {
5454
return;
5555
}
5656
const tiff = await fromFile(TIFF_URL);
57-
const {metadata} = await loadOMETiff(tiff);
57+
const {metadata} = await loadGeoTiff(tiff);
5858
const {Name, Pixels} = metadata;
5959
t.equal(Name, 'multi-channel.ome.tif', `Name should be 'multi-channel.ome.tif'.`);
6060
t.equal(Pixels.SizeC, 3, 'Should have three channels.');

0 commit comments

Comments
 (0)