From 40a5a838b4d9555748ff42dca5b38af2aebeb371 Mon Sep 17 00:00:00 2001 From: Jeremy Maitin-Shepard Date: Mon, 21 Oct 2024 16:20:33 -0700 Subject: [PATCH 1/2] fix(datasource/precomputed): check PNG image dimensions less strictly TensorStore encodes chunks of shape (x_size, y_size, z_size, c_size) with width=x_size*y_size, height=z_size, while cloud-volume uses width=x_size, height=y_size*z_size. With this change, Neuroglancer will accept any width/height as long as `width * height = x_size * y_size * s_zize`. This loose checking was already used for the JPEG format. Fixes https://github.com/google/tensorstore/issues/196 --- src/async_computation/decode_jpeg.ts | 9 ++++---- src/async_computation/decode_jpeg_request.ts | 21 +++++++++--------- src/async_computation/decode_png.ts | 3 +++ src/async_computation/decode_png_request.ts | 23 ++++++++++---------- src/datasource/deepzoom/backend.ts | 2 ++ src/datasource/render/backend.ts | 5 +++-- src/sliceview/backend_chunk_decoders/jpeg.ts | 5 +++-- src/sliceview/backend_chunk_decoders/png.ts | 5 +++-- src/sliceview/png/index.ts | 13 ++++++----- 9 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/async_computation/decode_jpeg.ts b/src/async_computation/decode_jpeg.ts index d3f93ac1c8..c20dbea214 100644 --- a/src/async_computation/decode_jpeg.ts +++ b/src/async_computation/decode_jpeg.ts @@ -25,6 +25,7 @@ registerAsyncComputation( data: Uint8Array, width: number | undefined, height: number | undefined, + area: number | undefined, numComponents: number | undefined, convertToGrayscale: boolean, ) => { @@ -32,14 +33,14 @@ registerAsyncComputation( parser.parse(data); // Just check that the total number pixels matches the expected value. if ( - width !== undefined && - height !== undefined && - parser.width * parser.height !== width * height + (width !== undefined && width !== parser.width) || + (height !== undefined && height !== parser.height) || + (area !== undefined && parser.width * parser.height !== area) ) { throw new Error( "JPEG data does not have the expected dimensions: " + `width=${parser.width}, height=${parser.height}, ` + - `expected width=${width}, expected height=${height}`, + `expected width=${width}, expected height=${height}, expected area=${area}`, ); } width = parser.width; diff --git a/src/async_computation/decode_jpeg_request.ts b/src/async_computation/decode_jpeg_request.ts index 1df020d753..c5b9a7d1df 100644 --- a/src/async_computation/decode_jpeg_request.ts +++ b/src/async_computation/decode_jpeg_request.ts @@ -16,13 +16,14 @@ import type { DecodedImage } from "#src/async_computation/decode_png_request.js"; import { asyncComputation } from "#src/async_computation/index.js"; -export const decodeJpeg = - asyncComputation< - ( - data: Uint8Array, - width: number | undefined, - height: number | undefined, - numComponents: number | undefined, - convertToGrayscale: boolean, - ) => DecodedImage - >("decodeJpeg"); +export const decodeJpeg = asyncComputation< + ( + data: Uint8Array, + width: number | undefined, + height: number | undefined, + // Expected width * height + area: number | undefined, + numComponents: number | undefined, + convertToGrayscale: boolean, + ) => DecodedImage +>("decodeJpeg"); diff --git a/src/async_computation/decode_png.ts b/src/async_computation/decode_png.ts index bc64a2052e..ed33df0763 100644 --- a/src/async_computation/decode_png.ts +++ b/src/async_computation/decode_png.ts @@ -24,6 +24,8 @@ registerAsyncComputation( data: Uint8Array, width: number | undefined, height: number | undefined, + // Expected width * height + area: number | undefined, numComponents: number | undefined, bytesPerPixel: number, convertToGrayscale: boolean, @@ -32,6 +34,7 @@ registerAsyncComputation( data, width, height, + area, numComponents, bytesPerPixel, convertToGrayscale, diff --git a/src/async_computation/decode_png_request.ts b/src/async_computation/decode_png_request.ts index 06d8001ba4..0980b4e453 100644 --- a/src/async_computation/decode_png_request.ts +++ b/src/async_computation/decode_png_request.ts @@ -22,14 +22,15 @@ export interface DecodedImage { uint8Array: Uint8Array; } -export const decodePng = - asyncComputation< - ( - data: Uint8Array, - width: number | undefined, - height: number | undefined, - numComponents: number | undefined, - bytesPerPixel: number, - convertToGrayscale: boolean, - ) => DecodedImage - >("decodePng"); +export const decodePng = asyncComputation< + ( + data: Uint8Array, + width: number | undefined, + height: number | undefined, + // Expected width * height + area: number | undefined, + numComponents: number | undefined, + bytesPerPixel: number, + convertToGrayscale: boolean, + ) => DecodedImage +>("decodePng"); diff --git a/src/datasource/deepzoom/backend.ts b/src/datasource/deepzoom/backend.ts index 8f7cd5e5ee..0c7ac55a44 100644 --- a/src/datasource/deepzoom/backend.ts +++ b/src/datasource/deepzoom/backend.ts @@ -111,6 +111,7 @@ export class DeepzoomImageTileSource extends WithParameters( new Uint8Array(responseBuffer), undefined, undefined, + undefined, 3, 1, false, @@ -133,6 +134,7 @@ export class DeepzoomImageTileSource extends WithParameters( new Uint8Array(responseBuffer), undefined, undefined, + undefined, 3, false, ); diff --git a/src/datasource/render/backend.ts b/src/datasource/render/backend.ts index 2a41d29363..ad53c010c4 100644 --- a/src/datasource/render/backend.ts +++ b/src/datasource/render/backend.ts @@ -46,8 +46,9 @@ chunkDecoders.set( cancellationToken, [response], new Uint8Array(response), - chunkDataSize[0], - chunkDataSize[1] * chunkDataSize[2], + undefined, + undefined, + chunkDataSize[0] * chunkDataSize[1] * chunkDataSize[2], 3, true, ); diff --git a/src/sliceview/backend_chunk_decoders/jpeg.ts b/src/sliceview/backend_chunk_decoders/jpeg.ts index 9b814eebe7..854b84f667 100644 --- a/src/sliceview/backend_chunk_decoders/jpeg.ts +++ b/src/sliceview/backend_chunk_decoders/jpeg.ts @@ -31,8 +31,9 @@ export async function decodeJpegChunk( cancellationToken, [response], new Uint8Array(response), - chunkDataSize[0], - chunkDataSize[1] * chunkDataSize[2], + undefined, + undefined, + chunkDataSize[0] * chunkDataSize[1] * chunkDataSize[2], chunkDataSize[3] || 1, false, ); diff --git a/src/sliceview/backend_chunk_decoders/png.ts b/src/sliceview/backend_chunk_decoders/png.ts index 82b9cc8fa8..f5d34b5e50 100644 --- a/src/sliceview/backend_chunk_decoders/png.ts +++ b/src/sliceview/backend_chunk_decoders/png.ts @@ -33,8 +33,9 @@ export async function decodePngChunk( cancellationToken, [response], /*buffer=*/ new Uint8Array(response), - /*width=*/ chunkDataSize[0], - /*height=*/ chunkDataSize[1] * chunkDataSize[2], + /*width=*/ undefined, + /*height=*/ undefined, + /*area=*/ chunkDataSize[0] * chunkDataSize[1] * chunkDataSize[2], /*numComponents=*/ chunkDataSize[3] || 1, /*bytesPerPixel=*/ DATA_TYPE_BYTES[dataType], /*convertToGrayscale=*/ false, diff --git a/src/sliceview/png/index.ts b/src/sliceview/png/index.ts index 86a25797fb..335937304f 100644 --- a/src/sliceview/png/index.ts +++ b/src/sliceview/png/index.ts @@ -172,6 +172,7 @@ export async function decompressPng( buffer: Uint8Array, width: number | undefined, height: number | undefined, + area: number | undefined, numComponents: number | undefined, bytesPerPixel: number, convertToGrayscale: boolean, @@ -187,15 +188,17 @@ export async function decompressPng( if ( (width !== undefined && sx !== width) || (height !== undefined && sy !== height) || + (area !== undefined && sx * sy !== area) || (numComponents !== undefined && numComponents !== numChannels) || bytesPerPixel !== dataWidth ) { throw new Error( - `png: Image decode parameters did not match expected chunk parameters. - Expected: width: ${width} height: ${height} channels: ${numComponents} bytes per pixel: ${bytesPerPixel} - Decoded: width: ${sx} height: ${sy} channels: ${numChannels} bytes per pixel: ${dataWidth} - Convert to Grayscale? ${convertToGrayscale} - `, + `png: Image decode parameters did not match expected chunk parameters. ` + + `Expected: width: ${width} height: ${height} area: ${area} ` + + `channels: ${numComponents} bytes per pixel: ${bytesPerPixel}. ` + + `Decoded: width: ${sx} height: ${sy} channels: ${numChannels} ` + + `bytes per pixel: ${dataWidth}. ` + + `Convert to Grayscale? ${convertToGrayscale}`, ); } From 6f1386cb2bcdf78c447d1a10026aaae5b3bba02d Mon Sep 17 00:00:00 2001 From: Jeremy Maitin-Shepard Date: Mon, 21 Oct 2024 16:38:31 -0700 Subject: [PATCH 2/2] fix(mypy): fix type annotations in json_wrappers.py --- python/neuroglancer/json_wrappers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/neuroglancer/json_wrappers.py b/python/neuroglancer/json_wrappers.py index 7c4c0fb96d..7c18dc2524 100644 --- a/python/neuroglancer/json_wrappers.py +++ b/python/neuroglancer/json_wrappers.py @@ -450,7 +450,7 @@ class _Map(Map): def typed_set(wrapped_type: Callable[[Any], T]): - def wrapper(x, _readonly=False) -> Callable[[Any], Union[set[T], frozenset[T]]]: + def wrapper(x, _readonly=False) -> Union[set[T], frozenset[T]]: set_type = frozenset if _readonly else set kwargs: dict[str, Any] = dict() if hasattr(wrapped_type, "supports_readonly"):