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

Integrate TIFF access into vitessce-image-viewer and Improve Metadata Caching #31

Merged
merged 8 commits into from
Feb 4, 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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ peer depenedencies from this package into your own:
"@luma.gl/core": "8.0.3",
"@luma.gl/shadertools": "8.0.3",
"deck.gl": "^8.1.0-alpha.1",
"geotiff": "^1.0.0-beta.6",
"math.gl": "^3.1.3",
"nebula.gl": "^0.17.1",
"zarr": "^0.1.4"
Expand Down Expand Up @@ -44,7 +45,7 @@ This component can be used with an already existing `DeckGL` setup.

##### `getTileData` (Function) **POTENIAL FUTURE BREAKING CHANGES WITH NEW FEATURES**

`getTileData` given x, y, z indices of the tile, returns the tile data or a Promise that resolves to the tile data. Alternatively, pass in `useZarr` as true to use `zarr` and our funcionality. Soon, there will be a `useGeoTIFF` as well. Look
`getTileData` given x, y, z indices of the tile, returns the tile data or a Promise that resolves to the tile data. Alternatively, pass in `useZarr` as true to use `zarr` and our functionality. Otherwise, you can use `useTiff` to make range requests directly against a pyramid/tiled tiff. Look
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think useZarr is out, for now? Update docs if so.

at [this](IMAGE_RENDERING.md) for how the zarr should be laid out.

Receives arguments:
Expand Down
8 changes: 3 additions & 5 deletions demo/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ const OrangeSlider = withStyles({
}
})(Slider)



export default class App extends PureComponent {

constructor(props){
Expand Down Expand Up @@ -91,17 +89,17 @@ export default class App extends PureComponent {
],
}
const propSettings = {
useTiff: true,
imageHeight: source.height * source.tileSize,
imageWidth: source.width * source.tileSize,
tileSize: source.tileSize,
sourceChannels: source.channels,
minZoom: Math.floor(
-1 * Math.log2(Math.max(source.height * source.tileSize, source.width * source.tileSize)),
)
),
maxZoom: -9
}
const useZarr = true
const props = {
useZarr,
initialViewState,
...propSettings,
...this.state
Expand Down
15 changes: 11 additions & 4 deletions demo/src/source-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ export const source = {
height: 141,
width: 206,
tileSize: 256,
// channels: {
// channel_0: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/single_channel_pyramid/img_pyramid/channel_0",
// channel_1: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/single_channel_pyramid/img_pyramid/channel_1",
// channel_2: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/single_channel_pyramid/img_pyramid/channel_2",
// channel_3: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/single_channel_pyramid/img_pyramid/channel_3",
// },
channels: {
channel_0: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/single_channel_pyramid/img_pyramid/channel_0",
channel_1: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/single_channel_pyramid/img_pyramid/channel_1",
channel_2: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/single_channel_pyramid/img_pyramid/channel_2",
channel_3: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/single_channel_pyramid/img_pyramid/channel_3",
channel_0: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/tiff-pyramid/vanderbilt_test_0.ome.tiff",
channel_1: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/tiff-pyramid/vanderbilt_test_1.ome.tiff",
channel_2: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/tiff-pyramid/vanderbilt_test_2.ome.tiff",
channel_3: "https://vitessce-vanderbilt-data.storage.googleapis.com/test-data/vanderbilt-data/tiff-pyramid/vanderbilt_test_3.ome.tiff",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • How do you generate the data that goes on the google service? We need the scripts documented, like in vitessce-data.
  • Make a variable for the repeated string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting to this data task today. Will do for repeated string.

}

};
17 changes: 15 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"babel-preset-stage-0": "^6.24.1",
"css-loader": "^3.4.2",
"deck.gl": "^8.1.0-alpha.1",
"geotiff": "^1.0.0-beta.6",
"html-webpack-plugin": "^3.2.0",
"math.gl": "^3.1.3",
"mini-css-extract-plugin": "^0.9.0",
Expand Down
5 changes: 3 additions & 2 deletions src/layers/microscopy-viewer-layer/data-utils/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import loadZarr from './zarr-utils';
import {loadZarr, getZarrConnections} from './zarr-utils';
import {loadTiff, getTiffConnections} from './tiff-utils';

export {loadZarr}
export {loadZarr, loadTiff, getTiffConnections, getZarrConnections}
46 changes: 46 additions & 0 deletions src/layers/microscopy-viewer-layer/data-utils/tiff-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {fromUrl, Pool, getDecoder } from 'geotiff/dist/geotiff.bundle.min.js';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it something about their packaging that forces us to reference it this way?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. People seem to have trouble with it


async function loadTile({image, channel, x, y, pool}) {
var tile = await image.getTileOrStrip(x,y, 0, pool)
var dataObj = {}
const is8Bits = image.fileDirectory.BitsPerSample[0]===8
const is16Bits = image.fileDirectory.BitsPerSample[0]===16
const is32Bits = image.fileDirectory.BitsPerSample[0]===32
dataObj[channel] = (is8Bits && new Uint8Array(tile.data)) ||
(is16Bits && new Uint16Array(tile.data)) ||
(is32Bits && new Uint32Array(tile.data))
return dataObj
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use semicolons... I know they're not really necessary in JS, but consistency is good.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will run prettier/eslint later

}

export function loadTiff({connections, x, y, z, pool}){
const configListPromises = Object.keys(connections).map((channel) => {
var image = connections[channel][z]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's not use var.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will run prettier/eslint later

return loadTile({image, channel, x, y, pool})
})
return Promise.all(configListPromises).then((dataList) => {
const orderedList = []
const dataObj = Object.assign({}, ...dataList)
Object.keys(dataObj).sort().forEach((key) => {
orderedList.push(dataObj[key]);
})
return orderedList;
});
}

export function getTiffConnections({sourceChannels, maxZoom}){
const tiffConnections = Object.keys(sourceChannels).map(async (channel) => {
var tiff = await fromUrl(sourceChannels[channel])
var imageObj = {}
for(var i = 0; i < -maxZoom; i++) {
var image = await tiff.getImage(i)
if(!imageObj[channel]) {
imageObj[channel] = [image]
} else {
imageObj[channel].push(image)
}

}
return imageObj
})
return Promise.all(tiffConnections)
}
50 changes: 26 additions & 24 deletions src/layers/microscopy-viewer-layer/data-utils/zarr-utils.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,23 @@
import { slice, openArray } from 'zarr';

var zarrArrays = {}

async function getData({ config, tileSize, x, y, stride, tilingWidth }) {
async function getData({ arr, channel, tileSize, x, y, stride, tilingWidth }) {
const arrSlice = slice(stride * tilingWidth * y + stride * x, stride * tilingWidth * y + stride * (x + 1));
const zarrKey = config.zarrConfig.store + config.zarrConfig.path;
if (!zarrArrays[zarrKey]) {
zarrArrays[zarrKey] = await openArray(config.zarrConfig);
}
const arr = zarrArrays[zarrKey];
const dataSlice = await arr.get([arrSlice]);
const { data } = dataSlice;
const { channelName } = config;
const dataObj = {}
dataObj[channelName] = data
dataObj[channel] = data
return dataObj;
}

export default function loadZarr({ sourceChannels, tileSize, x, y, z, imageWidth }) {
export function loadZarr({ connections, tileSize, x, y, z, imageWidth }) {
const tilingWidth = Math.ceil(imageWidth / (tileSize * (2 ** z)));
const dataNames = ['redData', 'greenData', 'blueData']; // denotes order rgb
const configList = Object.keys(sourceChannels).map((channel, i) => ({
channelName: channel,
channelType: dataNames[i],
zarrConfig: {
store: `${sourceChannels[channel]}/`,
path: `pyramid_${z}.zarr`,
mode: 'r',
},
}));
const stride = tileSize * tileSize;
const configListPromises = configList.map((config) => {
const channelPromises = Object.keys(connections).map((channel) => {
return getData({
config, tileSize, x, y, stride, tilingWidth,
arr: connections[channel][z], channel, tileSize, x, y, stride, tilingWidth,
});
});
return Promise.all(configListPromises).then((dataList) => {
return Promise.all(channelPromises).then((dataList) => {
const orderedList = []
const dataObj = Object.assign({}, ...dataList)
Object.keys(dataObj).sort().forEach(function(key) {
Expand All @@ -44,3 +26,23 @@ export default function loadZarr({ sourceChannels, tileSize, x, y, z, imageWidth
return orderedList;
});
}

export function getZarrConnections({sourceChannels, maxZoom}) {
const zarrConnections = Object.keys(sourceChannels).map(async (channel) => {
var zarrObj = {}
for(var i = 0; i < -maxZoom; i++) {
var zarr = await openArray({
store: `${sourceChannels[channel]}/`,
path: `pyramid_${i}.zarr`,
mode: 'r',
})
if(!zarrObj[channel]) {
zarrObj[channel] = [zarr]
} else {
zarrObj[channel].push(zarr)
}
}
return zarrObj
})
return Promise.all(zarrConnections)
}
97 changes: 97 additions & 0 deletions src/layers/microscopy-viewer-layer/microscopy-viewer-layer-base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {BaseTileLayer} from '@deck.gl/layers';
import {Texture2D} from '@luma.gl/webgl'
import GL from '@luma.gl/constants';
import { COORDINATE_SYSTEM } from 'deck.gl';
import { XRLayer } from '../xr-layer';
import {tileToBoundingBox} from './tiling-utils';
import {getTileIndices} from './tiling-utils';
import { loadZarr, loadTiff } from './data-utils';

const defaultProps = Object.assign({}, BaseTileLayer.defaultProps, {
id: `microscopy-tile-layer`,
pickable: false,
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
maxZoom: 0,
onViewportLoad: false,
renderSubLayers: (props) => {
const {
bbox: {
west, south, east, north,
},
} = props.tile;
const { sliderValues, data, colorValues } = props;
const xrl = new XRLayer(props, {
id: `XR-Layer-${west}-${south}-${east}-${north}`,
pickable: false,
coordinateSystem: COORDINATE_SYSTEM.CARTESIAN,
rgbData: data,
sliderValues,
colorValues,
bounds: [west, south, east, north],
visible: true,
});
return xrl;
},
});

export class MicroscopyViewerLayerBase extends BaseTileLayer {

constructor(props) {
const minZoom = Math.floor(-1 * Math.log2(Math.max(props.imageHeight, props.imageWidth)));
const {sliderValues, colorValues} = props
var orderedSliderValues = []
var orderedColorValues = []
Object.keys(sliderValues).sort().forEach(function(key) {
orderedSliderValues.push(sliderValues[key]);
})
Object.keys(colorValues).sort().forEach(function(key) {
orderedColorValues.push(colorValues[key]);
})
var diff = 6 - orderedSliderValues.length
for (var i = 0; i < diff; i++) {
orderedSliderValues.push(65535);
}
var diff = 6 - orderedColorValues.length
for (var j = 0; j < diff; j++) {
orderedColorValues.push([0,0,0]);
}
orderedColorValues = orderedColorValues.map(color => color.map(ch => ch / 255))
const getZarr = ({ x, y, z }) => {
return loadZarr({
x, y, z: -1 * z, ...props,
});
}
const getTiff = ({ x, y, z }) => {
return loadTiff({
x, y, z: -1 * z, ...props,
});
}
const getTileData = (props.useZarr && getZarr) ||
(props.useTiff && getTiff) ||
props.getTileData
const overrideValuesProps = Object.assign(
{}, props, {
sliderValues: orderedSliderValues,
colorValues: orderedColorValues,
minZoom,
getTileData,
getTileIndices: (viewport, maxZoom, minZoom) => {
return getTileIndices({
viewport, maxZoom, minZoom, ...props,
});
},
tileToBoundingBox: (x, y, z) => {
return tileToBoundingBox({
x, y, z, ...props,
});
},
}
)
const layerProps = Object.assign({}, defaultProps, overrideValuesProps)
super(layerProps)
}

}

MicroscopyViewerLayerBase.layerName = 'MicroscopyViewerLayerBase';
MicroscopyViewerLayerBase.defaultProps = defaultProps;
Comment on lines +1 to +97
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy and pasted into a new file :)

Loading