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

V05 dev2 #36

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
425c53f
Handle 404 for /zarr.json AND /.zattrs
will-moore Jun 7, 2024
d709352
Load v0.5 schemas from github PR branch
will-moore Jun 7, 2024
23d9b26
Fix display of Array info for Zarr v3
will-moore Jun 12, 2024
190c0b7
Fix display of .zarray .zattrs and zarr.json in UI
will-moore Jun 12, 2024
6940281
Update loading of zarr chunks with zarrita
will-moore Jun 13, 2024
f6de93d
Handle 0.5-dev version
will-moore Jun 14, 2024
1054356
Top of 3D cube - align lines with front face
will-moore Jun 14, 2024
f650c1e
Update to zarrita.js 0.4.0-next.14
will-moore Jun 25, 2024
e5b5bef
Update version check to correspond to fix in schemas PR
will-moore Jun 28, 2024
fdd8a55
Handle v0.5-dev2 data
will-moore Jul 3, 2024
e150aa9
Load version.schema for v0.5 schemas
will-moore Jul 4, 2024
e12265f
Check dimension_names for all zarr.json
will-moore Jul 4, 2024
4f54cb4
Temporarily hard-code zarr3 for neuroglancer
joshmoore Jul 11, 2024
8070bc5
Fix loading of zarr v3 plates
will-moore Jul 11, 2024
3a1ecfb
Temporarily hard-code a zarrita-version of vizarr
joshmoore Jul 15, 2024
205ce33
Revert "Temporarily hard-code a zarrita-version of vizarr"
will-moore Aug 7, 2024
56b21f4
Support display of shards
will-moore Aug 14, 2024
7d1f32a
Handle labels for v0.5 data
will-moore Aug 16, 2024
290a066
Update zarrita to 0.4.0-next.15
will-moore Aug 16, 2024
723a104
Load and display Ro-create json
will-moore Aug 21, 2024
5159b0c
Check Ro-Crate name, description, license
will-moore Aug 21, 2024
b816514
Handle data with no sharding codecs
will-moore Aug 22, 2024
0256c07
Try to parse and lookup Organism and Imaging method from Ro-crate
will-moore Aug 22, 2024
e596e00
Use different warning levels for missing metadata
will-moore Aug 22, 2024
e09b012
Improve version checking. Handle missing version 0.4
will-moore Sep 2, 2024
d2dfb1f
Fix bioformats2raw page with v0.5 data
will-moore Sep 3, 2024
0efe2a2
Fix validation of Wells in Plate view
will-moore Sep 3, 2024
1d55e98
Add ro-crate-metadata support to bioformats2raw component
will-moore Sep 24, 2024
c0374f5
Load _version schema. Validate using 'attributes' as JSON root
will-moore Sep 27, 2024
4341aaa
remove console.trace()
will-moore Sep 27, 2024
eb21241
Only validate jsonData.attributes for v0.5 data
will-moore Sep 27, 2024
b1216b6
Handle /OME/zarr.json series metadata for bf2raw data
will-moore Oct 1, 2024
2853654
Fix bytesPerPixel for Zarr v3 dtypes
will-moore Oct 2, 2024
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
248 changes: 178 additions & 70 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
},
"dependencies": {
"ajv": "^8.11.0",
"ndarray": "^1.0.19",
"svelte-icons-pack": "^1.4.6",
"svelte-simple-modal": "^1.4.1",
"zarr": "^0.6.0"
"zarrita": "^0.4.0-next.15"
}
}
2 changes: 1 addition & 1 deletion public/ngff_viewers.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"name": "neuroglancer",
"logo": "/neuroglancer.png",
"//": "NB: The {URL} in the href is replaced with NGFF image URL",
"href": "https://neuroglancer-demo.appspot.com/#!{%22layers%22:[{%22source%22:%22zarr://{URL}%22,%22name%22:%22OME-NGFF%22}]}"
"href": "https://neuroglancer-demo.appspot.com/#!{%22layers%22:[{%22source%22:%22zarr3://{URL}%22,%22name%22:%22OME-NGFF%22}]}"
},
{
"name": "Allen Cell Feature Explorer 3D viewer",
Expand Down
15 changes: 9 additions & 6 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import Modal from "svelte-simple-modal";
import SplashScreen from "./SplashScreen.svelte";

import { getJson } from "./utils";
import { getZarrGroupAttrs } from "./utils";
import CheckMark from "./CheckMark.svelte";

const searchParams = new URLSearchParams(window.location.search);
Expand All @@ -15,14 +15,17 @@
source = source.slice(0, -1);
}

let location = window.location.href;

let promise;

if (source) {
// load JSON to be validated...
console.log("Loading JSON... " + source + "/.zattrs");
promise = getJson(source + "/.zattrs");
console.log("Loading JSON... " + source);
promise = getZarrGroupAttrs(source);
}

function isBioFormats2Raw(data) {
let omeAttrs = data?.attributes?.ome || data;
return omeAttrs["bioformats2raw.layout"] === 3 && !omeAttrs.plate;
}
</script>

Expand All @@ -38,7 +41,7 @@
{:then data}
<Title {source} zattrs={data} />
<div>
{#if data["bioformats2raw.layout"] === 3 && !data.plate}
{#if isBioFormats2Raw(data)}
<Bioformats2rawLayout rootAttrs={data} {source} />
{:else}
<JsonValidator rootAttrs={data} {source} />
Expand Down
72 changes: 61 additions & 11 deletions src/Bioformats2rawLayout/index.svelte
Original file line number Diff line number Diff line change
@@ -1,27 +1,72 @@
<script>
import { getXmlDom, getJson, validate } from "../utils";
import { getJson, getXmlDom, getZarrGroupAttrs, validate, getVersion, getZarrGroupAttrsFileName } from "../utils";
import JsonBrowser from "../JsonBrowser/index.svelte";
import ImageContainer from "../JsonValidator/Well/ImageContainer.svelte";
import RoCrate from "../JsonValidator/RoCrate/index.svelte";

export let source;
export let rootAttrs;

const metadataName = "OME/METADATA.ome.xml";

const version = getVersion(rootAttrs);
console.log("BF2RAW version", version, rootAttrs);
const zarrAttrsFileName = getZarrGroupAttrsFileName(version);
console.log("zarrAttrsFileName", zarrAttrsFileName);

// source/OME/METADATA.ome.xml
const metadataUrl = `${source}/${metadataName}`;

async function loadXml(url) {
let dom = await getXmlDom(url);
async function loadXmlOrSeries(url) {
// We can get the series info from /OME/METADATA.ome.xml or
// /OME/zarr.json group attributes
// returns {images: [{name, id}]} or {error: message}
let dom;
try {
dom = await getXmlDom(url);
} catch (error) {}
let xmlRsp;
if (dom) {
xmlRsp = parseXml(dom);
}
// Try to get series info
let rsp = {};
let series;
try {
let zarrAttrs = await getJson(`${source}/OME/${zarrAttrsFileName}`);
series = zarrAttrs?.attributes?.ome?.series || zarrAttrs.series;
} catch (error) {}

if (series && xmlRsp && xmlRsp?.images) {
// MUST match if we have both...
if (series.length !== xmlRsp.images.length) {
rsp.errors = [
`Length mismatch: series: ${series.length} != ome.xml: ${5}`,
];
}
}
if (series) {
rsp.images = series.map((seriesName) => ({ name: seriesName, path: seriesName }));
} else if (xmlRsp) {
rsp = xmlRsp;
} else {
rsp.errors = ["No OME/METADATA.ome.xml or /OME series found"];
}
return rsp;
}

function parseXml(dom) {
const root = dom.documentElement;

let rsp = { images: [] };
let index = 0;
for (const child of root.children) {
console.log(child.tagName);
if (child.tagName === "Image") {
rsp.images.push({
name: child.getAttribute("Name"),
id: child.getAttribute("ID"),
path: "" + index++,
});
}
// error handling - parsererror gives html doc
Expand All @@ -35,12 +80,12 @@
}
return rsp;
}
const promise = loadXml(metadataUrl);
const promise = loadXmlOrSeries(metadataUrl);

// wait for schema to be cached, so we don't load them multiple times
// let schemasPromise = getSchema("0.2", "image");
async function preloadSchema(imagePath) {
let imgAttrs = await getJson(imagePath + "/.zattrs");
let imgAttrs = getZarrGroupAttrs(imagePath);
console.log("preloadSchema", imgAttrs);
let errs = await validate(imgAttrs);
return errs;
Expand All @@ -53,7 +98,7 @@
</script>

<article>
Reading: <a href={source}>/{zarrName}/.zattrs</a>
Reading: <a href={source}>/{zarrName}/{zarrAttrsFileName}</a>

<div class="json">
<JsonBrowser name="" version="" contents={rootAttrs} expanded />
Expand All @@ -71,14 +116,14 @@
<div>loading schema...</div>
{:then ok}
<ul>
{#each metadataJson.images as image, i}
{#each metadataJson.images as image}
<li class="image">
/{i}
<a title="Open Image" href="{url}?source={source}/{i}/"
/{image.path}
<a title="Open Image" href="{url}?source={source}/{image.path}/"
>{image.name}</a
>

<ImageContainer {source} path={i} />
<ImageContainer {source} path={image.path} />
</li>
{/each}
</ul>
Expand All @@ -90,7 +135,7 @@
<!-- Error handling... -->
{#if metadataJson.errors}
<div class="error">
<h2>Error parsing {metadataName}</h2>
<h3>Error loading series metadata:</h3>
{#each metadataJson.errors as err, i}
<div>{err}</div>
{/each}
Expand All @@ -99,6 +144,11 @@
{:catch error}
<p style="color: red">{error.message}</p>
{/await}

<!-- for v0.5+ we check for ro-crate-metadata.json -->
{#if !["0.1", "0.2", "0.3", "0.4"].includes(version)}
<RoCrate {source}></RoCrate>
{/if}
</article>

<style>
Expand Down
31 changes: 31 additions & 0 deletions src/JsonBrowser/DetailsPrePanel.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@


<script>
export let jsonData;
export let summary;
</script>

<details>
<summary>{summary}</summary>
<pre><code>{JSON.stringify(jsonData, null, 2)}</code></pre>
</details>


<style>

details {
font-size: 1.1em;
text-align: left;
margin: 0 15px;
}
pre {
margin-top: 10px;
color: #faebd7;
background-color: #2c3e50;
padding: 10px;
font-size: 14px;
border-radius: 10px;
overflow: auto;
}

</style>
7 changes: 5 additions & 2 deletions src/JsonValidator/Labels/LabelsInfoLink.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<script>
export let source;
export let labelsAttrs;
export let zarrAttrsFileName;
console.log("labelsAttrs:", labelsAttrs);
const url = window.location.origin + window.location.pathname;
let labelsPaths;
if (labelsAttrs.labels) {
labelsPaths = labelsAttrs.labels;
} else if (labelsAttrs?.attributes?.ome?.labels) {
labelsPaths = labelsAttrs.attributes.ome.labels;
}
</script>

Expand All @@ -19,11 +22,11 @@
{/each}
</ul>
{:else}
<div style="color:red">labels/.zattrs has no 'labels' list</div>
<div style="color:red">labels/{zarrAttrsFileName} has no 'labels' list</div>
{/if}

<details>
<summary>labels/.zattrs</summary>
<summary>labels/{zarrAttrsFileName}</summary>
<pre><code>{JSON.stringify(labelsAttrs, null, 2)}</code></pre>
</details>
</div>
Expand Down
36 changes: 31 additions & 5 deletions src/JsonValidator/MultiscaleArrays/Multiscale.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { getJson } from "../../utils";
import { getZarrArrayJson } from "../../utils";
import CheckMark from "../../CheckMark.svelte";

export let source;
Expand All @@ -15,6 +15,11 @@

const permitDtypeMismatch = ["0.1", "0.2", "0.3", "0.4"].includes(version);
const checkDimSeparator = ["0.2", "0.3", "0.4"].includes(version);
const allowMissingDimNames = ["0.1", "0.2", "0.3", "0.4"].includes(version);
let successMsg = "dtypes match and shapes are consistent";
if (!allowMissingDimNames) {
successMsg = "dimension_names checked, " + successMsg;
}

function allEqual(items) {
return items.every((value) => value == items[0]);
Expand All @@ -29,14 +34,17 @@
let dimCounts = [];
let shapes = [];
let dimSeparators = [];
let zarrayJsonList = [];

for (let i = 0; i < datasets.length; i++) {
let dataset = datasets[i];
let zarray = await getJson(source + "/" + dataset.path + "/.zarray");
let zarray = await getZarrArrayJson(source + "/" + dataset.path);
zarrayJsonList.push(zarray);
// Need to handle Zarr v2 and Zarr v3:
dimCounts.push(zarray.shape.length);
dtypes.push(zarray.dtype);
dtypes.push(zarray.dtype || zarray.data_type);
shapes.push(zarray.shape);
dimSeparators.push(zarray.dimension_separator);
dimSeparators.push(zarray.dimension_separator || zarray.chunk_key_encoding?.configuration?.separator);
}

// Each check is {msg: "Message"}, with status: "warning" if it isn't an Error.
Expand Down Expand Up @@ -72,6 +80,24 @@
});
}
});

if (!allowMissingDimNames) {
let axesNames = JSON.stringify(axes.map(axis => axis.name));
zarrayJsonList.forEach((arrData, i) => {
let msg;
if (!arrData.dimension_names) {
msg = `No dimension_names found for dataset ${i}`;
} else {
let dimNames = JSON.stringify(arrData.dimension_names);
if (dimNames != axesNames) {
msg = `dimension_names: ${dimNames} don't match axes names: ${axesNames}`;
}
}
if (msg) {
checks.push({msg});
}
});
}
}
if (checkDimSeparator) {
dimSeparators.forEach((sep) => {
Expand All @@ -95,7 +121,7 @@
<!-- only show X if not valid - no tick if valid -->
<CheckMark valid={false} />
{:else}
<p title="dtypes match and shapes are consistent">
<p title="{successMsg}">
{datasets.length} Datasets checked <span style="color:green">✓</span>
</p>
{/if}
Expand Down
30 changes: 22 additions & 8 deletions src/JsonValidator/MultiscaleArrays/ZarrArray/ChunkLoader.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
<script>
import { openArray, slice } from "zarr";
import { range } from "../../../utils";
// import { openArray, slice } from "zarr";
import { range, getChunkAndShardShapes } from "../../../utils";
import { get, writable } from "svelte/store";
import ChunkViewer from "./ChunkViewer.svelte";
import * as zarr from "zarrita";
import { slice } from "@zarrita/indexing";


export let source;
export let path;
export let zarray;

// zarrita wants /path/to/data.zarr directory
let zarrPath = path.replace("/zarr.json", "").replace("/.zarray", "")

let showChunks = false;
let chunk;

const chunks = zarray.chunks;
const [chunks, shards] = getChunkAndShardShapes(zarray);
const chunkCounts = zarray.shape.map((sh, index) =>
Math.ceil(sh / chunks[index])
);
Expand All @@ -33,20 +39,28 @@
if (!showChunks) return;
// clear previous chunk
chunk = undefined;
const store = await openArray({ store: source + "/" + path, mode: "r" });

// Use zarr like this, otherwise we get "Error: Unknown codec: bytes"
// let zarr = await import("https://cdn.jsdelivr.net/npm/zarrita@next/+esm");
let url = source + "/" + zarrPath;
const store = new zarr.FetchStore(url);
const arr = await zarr.open(store, { kind: "array" });

// we want to get exactly 1 chunk
// e.g. chunkIndices is (0, 1, 0, 0) and chunk is (1, 125, 125, 125)
// we want to get [0, 125:250, 0:125, 0:125]
let ch = store.meta.chunks;
const indices = get(chunkIndices).map((index, dim) => {
let ch = arr.chunks;
const indices = get(chunkIndices);
let slices = indices.map((index, dim) => {
if (ch[dim] > 1) {
return slice(index * ch[dim], (index + 1) * ch[dim]);
} else {
return index * ch[dim];
return index;
}
});
chunk = await store.get(indices);
// Updating chunk will trigger ChunkViewer to re-render
console.log("loading chunk", slices)
chunk = await zarr.get(arr, slices);
}

chunkIndices.subscribe(function () {
Expand Down
Loading