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
Ngff v0.4 #139
Ngff v0.4 #139
Conversation
Thanks for opening this PR! Is it ready for review? |
Yes, thanks. I think the current PR is ready for review. However, the 0.4 axes spec PR is still open, so it could change (although probably not in the limited parts that affect this PR). But I have been holding off generating larger HCS example datasets in 0.4 spec, so there aren't any public examples for testing (just been testing this PR on smaller local plates). But I could go ahead and do a medium plate if that would be useful? Actually, I meant to ask you about supporting some of the other axes features that are added in ome/ngff#57, such as transformations (scaling and affine transforms). |
Great. I'll work on the review.
I'll need to take a look at the spec more closely, but there are two things that stand out to me which are likely related but we work on in separate PRs:
It's probably a bit too low-level, but affine transformations are configurable per-layer via an API we already have (https://github.com/hms-dbmi/vizarr/blob/main/example/spatial_transformations.ipynb). On a first pass, we can hopefully map the transformation defined in NGFF to this representation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great. Just some minor suggestions, thanks @will-moore !
Let's try to get this merged and then we can focus on follow-up PRs for scale bar and transformations.
src/io.ts
Outdated
let labels = getAxisLabels(data[0], config.axis_labels); | ||
let channel_axis = labels.indexOf('c'); | ||
if (!config.axis_labels && multiscales && multiscales[0].axes) { | ||
const axes = getNgffAxes(multiscales); | ||
labels = getNgffAxisLabels(axes); | ||
channel_axis = axes.findIndex((axis) => axis.type === 'channel'); | ||
} | ||
const tileSize = guessTileSize(data[0]); | ||
const loader = data.map((d) => new ZarrPixelSource(d, labels, tileSize)); | ||
const [base] = loader; | ||
|
||
// If explicit channel axis is provided, try to load as multichannel. | ||
if ('channel_axis' in config || labels.includes('c')) { | ||
if ('channel_axis' in config || channel_axis > -1) { | ||
config = config as MultichannelConfig; | ||
return loadMultiChannel(config, loader, Number(config.channel_axis ?? labels.indexOf('c'))); | ||
return loadMultiChannel(config, loader, Number(config.channel_axis ?? channel_axis)); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is starting to get pretty complicated. The issue is that there are multiple ways labels
and channel_axis
can be specified, and it's difficult to see what takes precedent over each. The way I see it:
Vizarr layer config > NGFF metadata > guessed labels
if that is the case, we could move this logic:
let data: ZarrArray[];
let labels: [...string, 'y', 'x'];
let channel_axis: number = -1;
if (node instance of ZarrGroup) {
// ...
data = await loadMultiscales(node, attrs.multiscales);
if (attrs.multiscales[0].axes) {
const axes = getNgffAxes(attrs.multiscales);
labels = getNgffAxisLabels(axes);
channel_axis = axes.findIndex((axis) => axis.type === 'channel');
}
}
// ...
// explicit override in config > ngff > guessed from data shape
labels = config.axis_labels ?? (labels ?? guessAxisLabels(data[0]));
channel_axis = config.channel_axis ?? (channel_axis ?? labels.indexOf('c'));
src/utils.ts
Outdated
if (multiscales[0].axes) { | ||
axes = multiscales[0].axes.map((axis) => { | ||
// axis may be string 'x' (v0.3) or object | ||
let name: string = axis instanceof String ? (axis as string) : (axis as Ome.Axis).name; | ||
let type = getDefaultType(name); | ||
if (!(axis instanceof String) && (axis as Ome.Axis).type) { | ||
type = (axis as Ome.Axis).type as string; | ||
} | ||
return { name, type }; | ||
}); | ||
} | ||
return axes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of type assertions here which are an escape-hatch from the TS type checker (so if the types ever change this might break). Perhaps something like this would be more explicit/safe:
if (multiscales[0].axes) { | |
axes = multiscales[0].axes.map((axis) => { | |
// axis may be string 'x' (v0.3) or object | |
let name: string = axis instanceof String ? (axis as string) : (axis as Ome.Axis).name; | |
let type = getDefaultType(name); | |
if (!(axis instanceof String) && (axis as Ome.Axis).type) { | |
type = (axis as Ome.Axis).type as string; | |
} | |
return { name, type }; | |
}); | |
} | |
return axes; | |
if (multiscales[0].axes) { | |
axes = multiscales[0].axes.map((axis) => { | |
// axis may be string 'x' (v0.3) or object | |
if (typeof axis === 'string') { | |
return { name: axis, type: getDefaultType(axis) } | |
} | |
const { name, type } = axis; | |
return { name, type: type ?? getDefaultType(name) }; | |
}); | |
} |
types/ome.ts
Outdated
interface Multiscale { | ||
datasets: { path: string }[]; | ||
version?: string; | ||
axes?: string[]; | ||
axes?: (string | Axis)[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is allowed that these types are interleaved? e.g. ['x', { name: 'y', type: 'space' }]
?
Otherwise the type here would be string[] | Axis[]
Thanks. Those changes seem to be working well locally - think I still need to fix TypeScript... |
Just checked this out and seeing how hard it is to please TS. Also the nested ternaries aren't great. What if we had a helper function for this? Keep the implementation in type Labels = [...string[], 'y', 'x'];
function getAxisLabelsAndChannelAxis(
config: ImageLayerConfig,
ngffAxes: Ome.Axis[] | undefined,
arr: ZarrArray,
): { labels: Labels, channel_axis: number } {
// type cast string[] to Labels
const maybeAxisLabels = config.axis_labels as undefined | Labels;
// ensure numeric if provided
const maybeChannelAxis = 'channel_axis' in config ? Number(config.channel_axis) : undefined;
// Use ngff axes metadata if labels or channel axis aren't explicitly provided
if (ngffAxes) {
const labels = maybeAxisLabels ?? getNgffAxisLabels(ngffAxes);
const channel_axis = maybeChannelAxis ?? ngffAxes.findIndex((axis) => axis.type === 'channel');
return { labels, channel_axis }
}
// create dummy axis labels if not provided and try to guess channel_axis if missing
const labels = maybeAxisLabels ?? getAxisLabels(arr);
const channel_axis = maybeChannelAxis ?? labels.indexOf('c');
return { labels, channel_axis }
} and then in the main body export async function createSourceData(config: ImageLayerConfig): Promise<SourceData> {
const node = await open(config.source);
let data: ZarrArray[];
let axes: Ome.Axis[] | undefined;
if (node instanceof ZarrGroup) {
// ...
data = await loadMultiscales(node, attrs.multiscales);
if (attrs.multiscales[0].axes) {
axes = getNgffAxes(attrs.multiscales);
}
} else {
data = [node];
}
const { channel_axis, labels } = getAxisLabelsAndChannelAxis(config, axes, data[0]);
// ...
} |
Thanks manzt!
Thanks @manzt that looks good. However, for some reason, plates have stopped working. I had them working locally earlier on this PR, but it seems that a PR change isn't responsible since I also see the same error when I revert to older commits and also at https://hms-dbmi.github.io/vizarr/?source=https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.1/plates/422.zarr Thanks |
This is really surprising since at one point those plates where working on vizarr v0.2 & v0.3. The console errors in Chrome are very cryptic, so I tried other browsers to debug (Firefox 98 & Safari 15.1) but those actually seem to work... What version of Chrome are you using? I'm wondering if something happened with the latest update to Chrome that for some reason breaks this layer. Have you seen this before, @ilan-gold? |
Ah, yes: https://deploy-preview-139--vizarr.netlify.app/?source=https://minio-dev.openmicroscopy.org/idr/v0.4/2022-01-24/plates/idr0004/1752.zarr is working fine for me in Firefox. |
Somethign must have happened in Chrome, if you can see the error on the hms-dbmi website. That's not to say there's not something wrong with what we have, but it was working before. I do understand the error, although I can't figure out why it's being thrown since usampler2D is defined. @manzt Can you point me to a working plate for the viv 0.12 PR so I can see if that resolves things? |
are all pinned builds of vizarr corresponding to the tagged releases. So to see a regression in v0.2 & v0.3 & @will-moore. We are working to get the latest viv release integrated in Vizarr, which has some changes to our shaders anyways. So as long as the Firefox workaround is ok at the moment, we will try to get latest Viv release added before digging into this further. It could end up that this just gets resolved with upgrading Viv/deck.gl. |
Add support for v0.4 data
test with e.g. https://minio-dev.openmicroscopy.org/idr/v0.4/2022-01-05/idr0062/6001240.zarr
Deployed at:
https://deploy-preview-139--vizarr.netlify.app/?source=https://minio-dev.openmicroscopy.org/idr/v0.4/2022-01-05/idr0062/6001240.zarr