Skip to content

Commit

Permalink
feat: fully support ome zarr
Browse files Browse the repository at this point in the history
  • Loading branch information
xgui3783 committed Nov 6, 2024
1 parent 399fae0 commit 78283c8
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 4 deletions.
48 changes: 48 additions & 0 deletions frontend/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const XFORM_FILE_TYPE = "https://voluba.apps.hbp.eu/@types/transform"
type CoordSpace = Record<'x'|'y'|'z', export_nehuba.Dimension>

export const cvtToNm = {
micrometer: (v: number) => v * 1e3,
pm: (v: number) => v * 1e-3,
nm: (v: number) => v,
μm: (v: number) => v * 1e3,
Expand All @@ -91,6 +92,7 @@ export const cvtToNm = {
export type VoxelUnit = keyof typeof cvtToNm

export const cvtNmTo: Record<VoxelUnit, (nm: number) => number> = {
micrometer: (v: number) => v * 1e-3,
pm: v => v * 1e3,
nm: v => v,
μm: v => v * 1e-3,
Expand Down Expand Up @@ -231,6 +233,7 @@ type Volume = {
'neuroglancer/precomputed'?: string
'neuroglancer/precomputed/surface'?: string
'neuroglancer/n5'?: string
'neuroglancer/zarr'?: string
}
}

Expand Down Expand Up @@ -261,6 +264,7 @@ export function parseGSUrl(url: string): string {
const protocol: Record<string, keyof Volume['providers']> = {
"precomputed://": "neuroglancer/precomputed",
"n5://": "neuroglancer/n5",
"zarr://": "neuroglancer/zarr",
}

export function extractProtocolUrl(ngUrl: string): Volume {
Expand Down Expand Up @@ -417,3 +421,47 @@ export type EbrainsWorkflowPollResponse = {
progresses: EbrainsPublishResult[]
user: User
}

export type ZarrAttrs = {
multiscales: {
name: string
version: string
axes: {
name: "x" | "y" | "z" | string
type: "space" | string
units: "micrometer" | string
}[]
datasets: {
coordinateTransformations: {
scale: number[]
type: "scale" | string
}[]
path: string
}[]
}[]
}

export type BloscCompressor = {
blocksize: 0 | number
clevel: number
cname: "zstd" | string
id: "blosc"
shuffle: 1 | number
}

export type CompressorType = BloscCompressor

export type ZarrArray = {
chunks: number[]
dimension_separator: string

dtype: "<u2" | string

order: "C" | "F"
shape: number[]
zarr_format: number

compressor: CompressorType
fill_value: 0
filters: unknown
}
76 changes: 74 additions & 2 deletions frontend/src/state/inputs/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createSelector, select } from '@ngrx/store';
import { nameSpace, LocalState } from './consts';
import { Observable, combineLatest, distinctUntilChanged, forkJoin, from, map, of, pipe, switchMap, throwError } from 'rxjs';
import { arrayEqual, canBeConverted, cvtToNm, TVolume, parseGSUrl } from 'src/const';
import { arrayEqual, canBeConverted, cvtToNm, TVolume, parseGSUrl, ZarrAttrs, ZarrArray } from 'src/const';


const featureSelector = (state: any) => state[nameSpace] as LocalState;
Expand Down Expand Up @@ -55,6 +55,7 @@ const incInfoPipe = pipe(
const volume = incoming.volumes[0]
const precomputedUrl = volume.providers["neuroglancer/precomputed"]
const n5Url = volume.providers["neuroglancer/n5"]
const zarrUrl = volume.providers["neuroglancer/zarr"]
if (!!precomputedUrl) {
return from(
fetch(`${precomputedUrl}/info`).then(res => res.json() as Promise<{ scales: { size: number[], resolution: number[] }[] }>)
Expand Down Expand Up @@ -82,6 +83,58 @@ const incInfoPipe = pipe(
})
)
}
if (!!zarrUrl) {

return from(
fetch(parseGSUrl(`${zarrUrl}/.zattrs`)).then(res => res.json() as Promise<ZarrAttrs>)
).pipe(
switchMap(zarrattr => {
const { multiscales } = zarrattr
if (multiscales.length !== 1) {
return throwError(() => new Error(`Can only deal with one dataset, but got ${multiscales.length}`))
}
const scale = multiscales[0]
const { axes, datasets } = scale
if (axes.length !== 3) {
return throwError(() => new Error(`Can only deal with axes with length 3`))
}
for (const { type, units } of axes){
if (type !== "space") {
return throwError(() => new Error(`Can only deal with space axes`))
}
}
if (datasets.length === 0) {
return throwError(() => new Error(`must have at least one dataset`))
}
const dataset = datasets[0]
const { coordinateTransformations, path } = dataset
if (coordinateTransformations.length !== 1) {
return throwError(() => new Error(`Can only deal with one coordate transforms, but got ${coordinateTransformations.length}`))
}
const coordinateTransformation = coordinateTransformations[0]
const { scale: _scale } = coordinateTransformation

return from(
fetch(parseGSUrl(`${zarrUrl}/${path}/.zarray`)).then(res => res.json() as Promise<ZarrArray>)
).pipe(
map(({ shape }) => {
return {
"neuroglancer/zarr": {
axes,
datasets: [
{
scale: _scale,
shape
}
]
}
} as IncInfo
})
)

})
)
}
return throwError(() => new Error(`volume is neither precomputed nor n5`))
})
)
Expand Down Expand Up @@ -117,6 +170,20 @@ export const incVoxelSize = pipe(
return cvtToNm[unit](resolution[idx])
})
}
if (!!v["neuroglancer/zarr"]) {
const { datasets, axes } = v["neuroglancer/zarr"]
const dataset = datasets[0]
const { scale, shape } = dataset
return (scale as number[]).map((v, idx: number) => {
const unit = axes[idx].units
if (!(unit in cvtToNm)) {
console.warn(`${unit} cannot be converted. Using 1 as default`)
return 1
}
return cvtToNm[unit as keyof typeof cvtToNm](v)
})

}
console.warn(`v voxel size cannot be found: ${v}`)
return null
})
Expand All @@ -139,7 +206,12 @@ export const incVoxelExtents = pipe(
const { dimensions } = s0 as { blockSize: number[], dimensions: number[] }
return dimensions
}
console.warn(`v voxel size cannot be found: ${v}`)
if (!!v["neuroglancer/zarr"]) {
const { datasets } = v["neuroglancer/zarr"]
const dataset = datasets[0]
return dataset.shape as number[]
}
console.warn(`voxel extent cannot be found: ${v}`)
return null
})
)
4 changes: 2 additions & 2 deletions frontend/src/views/input-volumes/input-volumes.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,10 @@
</mat-label>
<input type="text" formControlName="url" matInput>
<mat-hint>
Neuroglancer URL, must start with precomputed:// or n5://
Neuroglancer URL, must start with precomputed://, zarr:// or n5://
</mat-hint>
<mat-error *ngIf="newVolumeCtrl.controls.url.errors">
Malformed URL. Must start with <code>precomputed://</code> or <code>n5://</code>
Malformed URL. Must start with <code>precomputed://</code>, <code>zarr://</code> or <code>n5://</code>
</mat-error>
</mat-form-field>
</form>
Expand Down

0 comments on commit 78283c8

Please sign in to comment.