Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use readRasters for reading Tile Data #319

Merged
merged 8 commits into from
Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

- Fix default component args API.
- Change WebGL setting so that textures of non-multiple-of-4 length bind and display.
- No longer pad tiles - instead truncate to the correct "raster size" so that the tile does not "bleed over."
- Update preprint title in README.md
- Use `readRasters` for TIFF for fetching tiles so that we are robust to non-uniformly sized tiles - thus we no longer need to "pad tiles."
- Switch to Github Actions
- Don't show snackbar if image provided is one of our demos.

Expand Down
80 changes: 37 additions & 43 deletions src/loaders/OMETiffLoader.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import OMEXML from './omeXML';
import { isInTileBounds, byteSwapInplace, dimensionsFromOMEXML } from './utils';
import { DTYPE_VALUES } from '../constants';
import { isInTileBounds, dimensionsFromOMEXML } from './utils';

const DTYPE_LOOKUP = {
uint8: '<u1',
Expand Down Expand Up @@ -46,7 +45,9 @@ export default class OMETiffLoader {
this.height = this.omexml.SizeY;
this.tileSize = firstImage.getTileWidth();
const { SubIFDs } = firstImage.fileDirectory;
this.numLevels = SubIFDs?.length || this.omexml.getNumberOfImages();
this.numLevels = SubIFDs
? SubIFDs.length + 1
: this.omexml.getNumberOfImages();
this.isBioFormats6Pyramid = SubIFDs;
this.isPyramid = this.numLevels > 1;
this.dimensions = dimensionsFromOMEXML(this.omexml);
Expand Down Expand Up @@ -156,19 +157,22 @@ export default class OMETiffLoader {
image = await tiff.getImage(pyramidIndex);
return this._getChannel({ image, x, y, z, signal });
});
const tiles = await Promise.all(tileRequests);
const truncated = this._truncateTiles(tiles, { x, y, z });
const data = await Promise.all(tileRequests);
const { height, width } = this._getTileExtent({
x,
y,
z
});
if (signal?.aborted) return null;
return truncated;
return { data, height, width };
}

/**
* Returns full image panes (at level z if pyramid)
* @param {number} z positive integer (0 === highest zoom level)
* @param {Array} loaderSelection, Array of number Arrays specifying channel selections
* @returns {Object} data: TypedArray[], width: number, height: number
* Default is `{data: [], width, height}`.

* Default is `{data: [], width, height}`.
*/
async getRaster({ z, loaderSelection }) {
const { tiff, omexml, isBioFormats6Pyramid, pool } = this;
Expand Down Expand Up @@ -231,7 +235,6 @@ export default class OMETiffLoader {
/**
* Returns image width and height (at pyramid level z) without fetching data.
* This information is inferrable from the provided omexml.
* This is only used by the OverviewLayer for inferring the box size.
* It is NOT the actual pixel-size but rather the image size
* without any padding.
* @param {number} z positive integer (0 === highest zoom level)
Expand Down Expand Up @@ -303,21 +306,27 @@ export default class OMETiffLoader {
};
}

async _getChannel({ image, x, y, signal }) {
const { dtype } = this;
const { TypedArray } = DTYPE_VALUES[dtype];
const tile = await image.getTileOrStrip(x, y, 0, this.pool, signal);
const data = new TypedArray(tile.data);
async _getChannel({ image, x, y, z, signal }) {
const { tileSize, pool } = this;
const { height, width } = this._getTileExtent({
x,
y,
z
});
// Passing in the height and width explicitly prevents resampling that geotiff does without such parameters.
const [data] = await image.readRasters({
window: [
x * tileSize,
y * tileSize,
x * tileSize + width,
y * tileSize + height
],
pool,
signal,
width,
height
});
if (signal?.aborted) return null;
/*
* The endianness of JavaScript TypedArrays are determined by the endianness
* of the end-users' hardware. Nearly all desktop computers are x86 (little endian),
* so we flip bytes in place for big-endian buffers. This is substantially faster than using
* the DataView API.
*/
if (!image.littleEndian) {
byteSwapInplace(data);
}
if (this.omexml.Type.startsWith('int')) {
// Uint view isn't correct for underling buffer, need to take an
// IntXArray view and cast to UintXArray.
Expand Down Expand Up @@ -354,13 +363,14 @@ export default class OMETiffLoader {
}

/**
* Trunactes tiles on the edge to match the height/width reported by the image.
* For a given resolution level, z, the expected tile size on the boundary
* of the image should be exactly enough to fit the image bounds at the resolution level.
* This function returns that size or the parametrized tileSize from TIFF file.
* @param {tileData: TypedArray[]} data The array to be filled in.
* @param {Object} tile { x, y, z }
* @returns {TypedArray} TypedArray
*/
_truncateTiles(data, tile) {
const { x, y, z } = tile;
_getTileExtent({ x, y, z }) {
const { tileSize } = this;
let height = tileSize;
let width = tileSize;
Expand All @@ -376,22 +386,6 @@ export default class OMETiffLoader {
if (y === maxYTileCoord) {
height = zoomLevelHeight % tileSize;
}
if (
(width === tileSize && height === tileSize) ||
data[0].length === width * height
) {
return { data, width, height };
}
const truncated = data.map(d => {
const newData = new d.constructor(height * width);
// Take strips (rows) from original tile data and fill new smaller buffer
for (let i = 0; i < height; i += 1) {
const offset = i * tileSize; // offset in tile
const strip = d.subarray(offset, offset + width); // get strip with new width
newData.set(strip, i * width); // copy strip from input d to byte offset in truncated
}
return newData;
});
return { data: truncated, width, height };
return { height, width };
}
}
2 changes: 1 addition & 1 deletion test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ end() { echo travis_fold':'end:$1; }
die() { set +v; echo "$*" 1>&2 ; sleep 1; exit 1; }

start changelog
if [ "$GITHUB_REF" != 'master' ]; then
if [ "$GITHUB_REF" != 'refs/heads/master' ]; then
diff CHANGELOG.md <(curl "https://raw.githubusercontent.com/hms-dbmi/viv/master/CHANGELOG.md") \
&& die 'Update CHANGELOG.md'
fi
Expand Down