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

Feature/niftizarr #585

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
595 changes: 115 additions & 480 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"@types/codemirror": "5.60.15",
"@types/gl-matrix": "^2.4.5",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.11.20",
"@types/node": "^20.14.1",
"@types/pako": "^2.0.3",
"@types/yargs": "^17.0.32",
"@typescript-eslint/eslint-plugin": "^7.0.2",
Expand Down Expand Up @@ -277,6 +277,24 @@
"neuroglancer/datasource/nifti:disabled": "./src/datasource/nifti/register_default.ts",
"default": "./src/datasource/nifti/register_default.ts"
},
"#datasource/niftizarr/backend": {
"neuroglancer/datasource/niftizarr:enabled": "./src/datasource/zarr/backend.ts",
"neuroglancer/datasource:none_by_default": "./src/util/false.ts",
"neuroglancer/datasource/niftizarr:disabled": "./src/datasource/zarr/backend.ts",
"default": "./src/datasource/zarr/backend.ts"
},
"#datasource/niftizarr/async_computation": {
"neuroglancer/datasource/niftizarr:enabled": "./src/datasource/zarr/async_computation.ts",
"neuroglancer/datasource:none_by_default": "./src/util/false.ts",
"neuroglancer/datasource/niftizarr:disabled": "./src/datasource/zarr/async_computation.ts",
"default": "./src/datasource/zarr/async_computation.ts"
},
"#datasource/niftizarr/register_default": {
"neuroglancer/datasource/niftizarr:enabled": "./src/datasource/niftizarr/register_default.ts",
"neuroglancer/datasource:none_by_default": "./src/util/false.ts",
"neuroglancer/datasource/niftizarr:disabled": "./src/datasource/niftizarr/register_default.ts",
"default": "./src/datasource/niftizarr/register_default.ts"
},
"#datasource/obj/backend": {
"neuroglancer/datasource/obj:enabled": "./src/datasource/obj/backend.ts",
"neuroglancer/datasource:none_by_default": "./src/util/false.ts",
Expand Down
1 change: 1 addition & 0 deletions src/datasource/enabled_frontend_modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ import "#datasource/precomputed/register_default";
import "#datasource/render/register_default";
import "#datasource/vtk/register_default";
import "#datasource/zarr/register_default";
import "#datasource/niftizarr/register_default";
10 changes: 5 additions & 5 deletions src/datasource/nifti/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ async function decodeNiftiFile(
return { data, size: buffer.byteLength };
}

function getNiftiFileData(
export function getNiftiFileData(
chunkManager: Borrowed<ChunkManager>,
credentialsProvider: SpecialProtocolCredentialsProvider,
url: string,
Expand All @@ -100,7 +100,7 @@ function getNiftiFileData(

const NIFTI_HEADER_INFO_PRIORITY = 1000;

async function getNiftiHeaderInfo(
export async function getNiftiHeaderInfo(
chunkManager: Borrowed<ChunkManager>,
credentialsProvider: SpecialProtocolCredentialsProvider,
url: string,
Expand All @@ -119,7 +119,7 @@ async function getNiftiHeaderInfo(
return data.header;
}

function convertAffine(affine: number[][]) {
export function convertAffine(affine: number[][]) {
return mat4.fromValues(
affine[0][0],
affine[1][0],
Expand All @@ -140,7 +140,7 @@ function convertAffine(affine: number[][]) {
);
}

enum NiftiDataType {
export enum NiftiDataType {
NONE = 0,
BINARY = 1,
UINT8 = 2,
Expand All @@ -160,7 +160,7 @@ enum NiftiDataType {
COMPLEX256 = 2048,
}

const DATA_TYPE_CONVERSIONS = new Map([
export const DATA_TYPE_CONVERSIONS = new Map([
[NiftiDataType.INT8, { dataType: DataType.INT8 }],
[NiftiDataType.UINT8, { dataType: DataType.UINT8 }],
[NiftiDataType.INT16, { dataType: DataType.INT16 }],
Expand Down
2 changes: 1 addition & 1 deletion src/datasource/nifti/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class NiftiMultiscaleVolumeChunkSource extends MultiscaleVolumeChunkSourc
}
}

function getNiftiVolumeInfo(
export function getNiftiVolumeInfo(
chunkManager: ChunkManager,
credentialsProvider: SpecialProtocolCredentialsProvider,
url: string,
Expand Down
204 changes: 204 additions & 0 deletions src/datasource/niftizarr/frontend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/**
* @license
* Copyright 2016 Google Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @file Support for displaying single NIfTI Zarr (https://github.com/lincbrain/linc-docs/blob/dd3411c15643df2662a26dee752a394dfc282872/dev/nifti_zarr_format.md)
* files as volumes.
*/

import { makeDataBoundsBoundingBoxAnnotationSet } from "#src/annotation/index.js";
import {
makeCoordinateSpace,
makeIdentityTransformedBoundingBox,
} from "#src/coordinate_transform.js";
import type {
CompleteUrlOptions,
DataSource,
GetDataSourceOptions,
} from "#src/datasource/index.js";
import { DataSourceProvider } from "#src/datasource/index.js";
import {
getNiftiVolumeInfo,
} from "#src/datasource/nifti/frontend.ts";
import type { ZarrMultiscaleInfo } from "#src/datasource/zarr/frontend.ts";
import {
getMetadata,
resolveOmeMultiscale,
getMultiscaleInfoForSingleArray,
MultiscaleVolumeChunkSource
} from "#src/datasource/zarr/frontend.ts";
import "#src/datasource/zarr/codec/blosc/resolve.js";
import "#src/datasource/zarr/codec/zstd/resolve.js";
import "#src/datasource/zarr/codec/bytes/resolve.js";
import "#src/datasource/zarr/codec/crc32c/resolve.js";
import "#src/datasource/zarr/codec/gzip/resolve.js";
import "#src/datasource/zarr/codec/sharding_indexed/resolve.js";
import "#src/datasource/zarr/codec/transpose/resolve.js";
import {
parseDimensionSeparator,
} from "#src/datasource/zarr/metadata/parse.js";
import { parseOmeMetadata } from "#src/datasource/zarr/ome.js";
import { uncancelableToken } from "#src/util/cancellation.js";
import { completeHttpPath } from "#src/util/http_path_completion.js";
import {
parseQueryStringParameters,
verifyObject,
verifyOptionalObjectProperty,
} from "#src/util/json.js";
import {
parseSpecialUrl,
} from "#src/util/special_protocol_request.js";


export class NiftiZarrDataSource extends DataSourceProvider {
constructor(public zarrVersion: 2 | 3 | undefined = undefined) {
super();
}
get description() {
const versionStr =
this.zarrVersion === undefined ? "" : ` v${this.zarrVersion}`;
return `Nifti Zarr ${versionStr} data source`;
}
get(options: GetDataSourceOptions): Promise<DataSource> {
// Pattern is infallible.
let [, providerUrl, query] =
options.providerUrl.match(/([^?]*)(?:\?(.*))?$/)!;
const parameters = parseQueryStringParameters(query || "");
verifyObject(parameters);
const dimensionSeparator = verifyOptionalObjectProperty(
parameters,
"dimension_separator",
parseDimensionSeparator,
);
if (providerUrl.endsWith("/")) {
providerUrl = providerUrl.substring(0, providerUrl.length - 1);
}
return options.chunkManager.memoize.getUncounted(
{
type: "zarr:MultiscaleVolumeChunkSource",
providerUrl,
dimensionSeparator,
},
async () => {
const { url, credentialsProvider } = parseSpecialUrl(
providerUrl,
options.credentialsManager,
);
const metadata = await getMetadata(
options.chunkManager,
credentialsProvider,
url,
{
zarrVersion: this.zarrVersion,
explicitDimensionSeparator: dimensionSeparator,
},
);
if (metadata === undefined) {
throw new Error("No zarr metadata found");
}
let multiscaleInfo: ZarrMultiscaleInfo;
if (metadata.nodeType === "group") {
// May be an OME-zarr multiscale dataset.
const multiscale = parseOmeMetadata(url, metadata.userAttributes);
if (multiscale === undefined) {
throw new Error("Neither array nor OME multiscale metadata found");
}
multiscaleInfo = await resolveOmeMultiscale(
options.chunkManager,
credentialsProvider,
multiscale,
{
zarrVersion: metadata.zarrVersion,
explicitDimensionSeparator: dimensionSeparator,
},
);
} else {
multiscaleInfo = getMultiscaleInfoForSingleArray(url, metadata);
}
const volumeZarr = new MultiscaleVolumeChunkSource(
options.chunkManager,
credentialsProvider,
multiscaleInfo,
);

// nifti head stored separately
const niftiUrl = url.concat("/nifti/0");
const info = await getNiftiVolumeInfo(
options.chunkManager,
credentialsProvider,
niftiUrl,
uncancelableToken,
);
const box = {
lowerBounds: new Float64Array(info.rank),
upperBounds: Float64Array.from(info.volumeSize),
};
const inputSpace = makeCoordinateSpace({
rank: info.rank,
names: info.sourceNames,
scales: info.sourceScales,
units: info.units,
boundingBoxes: [makeIdentityTransformedBoundingBox(box)],
});
const outputSpace = makeCoordinateSpace({
rank: info.rank,
names: info.viewNames,
scales: info.viewScales,
units: info.units,
});

// the code in activateDataSubsources is really bad and requires you name the variable "volume"
// could change the code in index.ts but this is how it is in neuroglancer so wanted to follow convention
const volume = volumeZarr;

return {
// use tranformation from the nifti portion of header
modelTransform: {
sourceRank: info.rank,
rank: info.rank,
inputSpace,
outputSpace,
transform: info.transform,
},
subsources: [
{
id: "default",
default: true,
url: undefined,
subsource: { volume }, // volume from zarr portion
},
{
id: "bounds",
default: true,
url: undefined,
subsource: {
staticAnnotations: makeDataBoundsBoundingBoxAnnotationSet(volumeZarr.modelSpace.bounds), // volume from zarr portion
},
},
],
};
},
);
}

completeUrl(options: CompleteUrlOptions) {
return completeHttpPath(
options.credentialsManager,
options.providerUrl,
options.cancellationToken,
);
}
}
23 changes: 23 additions & 0 deletions src/datasource/niftizarr/register_default.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* @license
* Copyright 2017 Google Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { registerProvider } from "#src/datasource/default_provider.js";
import { NiftiZarrDataSource } from "#src/datasource/niftizarr/frontend.js";

registerProvider("niftizarr", () => new NiftiZarrDataSource());
registerProvider("niftizarr2", () => new NiftiZarrDataSource(2));
registerProvider("niftizarr3", () => new NiftiZarrDataSource(3));

8 changes: 4 additions & 4 deletions src/datasource/zarr/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ interface ZarrScaleInfo {
metadata: ArrayMetadata;
}

interface ZarrMultiscaleInfo {
export interface ZarrMultiscaleInfo {
coordinateSpace: CoordinateSpace;
dataType: DataType;
scales: ZarrScaleInfo[];
Expand Down Expand Up @@ -262,7 +262,7 @@ function getNormalizedDimensionNames(
});
}

function getMultiscaleInfoForSingleArray(
export function getMultiscaleInfoForSingleArray(
url: string,
metadata: ArrayMetadata,
): ZarrMultiscaleInfo {
Expand Down Expand Up @@ -296,7 +296,7 @@ function getMultiscaleInfoForSingleArray(
};
}

async function resolveOmeMultiscale(
export async function resolveOmeMultiscale(
chunkManager: ChunkManager,
credentialsProvider: SpecialProtocolCredentialsProvider,
multiscale: OmeMultiscaleMetadata,
Expand Down Expand Up @@ -385,7 +385,7 @@ async function resolveOmeMultiscale(
};
}

async function getMetadata(
export async function getMetadata(
chunkManager: ChunkManager,
credentialsProvider: SpecialProtocolCredentialsProvider,
url: string,
Expand Down