diff --git a/fixtures/v2/data.zarr/.zmetadata b/fixtures/v2/data.zarr/.zmetadata index 9e21c3af..82fbc2fa 100644 --- a/fixtures/v2/data.zarr/.zmetadata +++ b/fixtures/v2/data.zarr/.zmetadata @@ -472,6 +472,38 @@ ], "zarr_format": 2 }, + "3d.chunked.mixed.i2.C.fixedscaleoffset/.zarray": { + "chunks": [ + 3, + 3, + 1 + ], + "compressor": { + "blocksize": 0, + "clevel": 5, + "cname": "lz4", + "id": "blosc", + "shuffle": 1 + }, + "dtype": " { expect(res.shape).toStrictEqual([3, 3, 3]); expect(res.stride).toStrictEqual([1, 3, 9]); }); + + it("3d.chunked.mixed.i2.C.fixedscaleoffset", async () => { + let arr = await zarr.open.v2(store.resolve("/3d.chunked.mixed.i2.C.fixedscaleoffset"), { + kind: "array", + }); + let res = await get(arr); + expect(res.data).toStrictEqual(new Int16Array(range(27))); + expect(res.shape).toStrictEqual([3, 3, 3]); + expect(res.stride).toStrictEqual([9, 3, 1]); + }); }); diff --git a/packages/zarrita/src/codecs.ts b/packages/zarrita/src/codecs.ts index 82e35a10..7d6ecc1b 100644 --- a/packages/zarrita/src/codecs.ts +++ b/packages/zarrita/src/codecs.ts @@ -2,6 +2,7 @@ import type { Codec as _Codec } from "numcodecs"; import { BitroundCodec } from "./codecs/bitround.js"; import { BytesCodec } from "./codecs/bytes.js"; import { Crc32cCodec } from "./codecs/crc32c.js"; +import { FixedScaleOffsetCodec } from "./codecs/fixedscaleoffset.js"; import { GzipCodec } from "./codecs/gzip.js"; import { JsonCodec } from "./codecs/json2.js"; import { TransposeCodec } from "./codecs/transpose.js"; @@ -35,7 +36,8 @@ function create_default_registry(): Map Promise> { .set("crc32c", () => Crc32cCodec) .set("vlen-utf8", () => VLenUTF8) .set("json2", () => JsonCodec) - .set("bitround", () => BitroundCodec); + .set("bitround", () => BitroundCodec) + .set("fixedscaleoffset", () => FixedScaleOffsetCodec); } export const registry: Map Promise> = diff --git a/packages/zarrita/src/codecs/fixedscaleoffset.ts b/packages/zarrita/src/codecs/fixedscaleoffset.ts new file mode 100644 index 00000000..49b61df8 --- /dev/null +++ b/packages/zarrita/src/codecs/fixedscaleoffset.ts @@ -0,0 +1,56 @@ +import type { Chunk, NumberDataType, TypedArrayConstructor } from "../metadata.js" +import { coerce_dtype, get_ctr } from "../util.js" + +type FixedScaleOffsetConfig = { + offset: number; + scale: number; + dtype: string; + astype?: string; +} + +export class FixedScaleOffsetCodec { + readonly kind = "array_to_array"; + + #offset: number; + #scale: number; + #TypedArrayIn: TypedArrayConstructor + #TypedArrayOut: TypedArrayConstructor + + constructor(configuration: FixedScaleOffsetConfig) { + const { data_type } = coerce_dtype(configuration.dtype); + this.#TypedArrayIn = get_ctr(data_type as NumberDataType); + const { data_type: as_data_type } = coerce_dtype(configuration.astype ?? configuration.dtype); + this.#TypedArrayOut = get_ctr(as_data_type as NumberDataType); + + this.#offset = configuration.offset; + this.#scale = configuration.scale; + } + + static fromConfig(configuration: FixedScaleOffsetConfig) { + return new FixedScaleOffsetCodec(configuration); + } + + encode(arr: Chunk): Chunk { + const data = new this.#TypedArrayOut(arr.data.length); + arr.data.forEach((value: number, i: number) => { + data[i] = (value - this.#offset) * this.#scale; + }); + return { + data, + shape: arr.shape, + stride: arr.stride + } + } + + decode(arr: Chunk): Chunk { + const out_data = new this.#TypedArrayIn(arr.data.length); + arr.data.forEach((value: number, i: number) => { + out_data[i] = (value / this.#scale) + this.#offset; + }); + return { + data: out_data, + shape: arr.shape, + stride: arr.stride, + }; + } +} diff --git a/packages/zarrita/src/util.ts b/packages/zarrita/src/util.ts index 3146cccc..c28912d5 100644 --- a/packages/zarrita/src/util.ts +++ b/packages/zarrita/src/util.ts @@ -119,7 +119,7 @@ export function create_chunk_key_encoder({ throw new Error(`Unknown chunk key encoding: ${name}`); } -function coerce_dtype( +export function coerce_dtype( dtype: string, ): { data_type: DataType } | { data_type: DataType; endian: "little" | "big" } { if (dtype === "|O") { diff --git a/scripts/generate-v2.py b/scripts/generate-v2.py index 7045a370..cebb6571 100644 --- a/scripts/generate-v2.py +++ b/scripts/generate-v2.py @@ -12,7 +12,7 @@ import zarr import numpy as np -from numcodecs import Zlib, Blosc, LZ4, Zstd, VLenUTF8 +from numcodecs import Zlib, Blosc, LZ4, Zstd, VLenUTF8, FixedScaleOffset SELF_DIR = pathlib.Path(__file__).parent @@ -170,6 +170,25 @@ chunks=(3, 3, 1), ) +# 3d.chunked.mixed.i2.C.fixedscaleoffset +root.create_dataset( + "3d.chunked.mixed.i2.F.fixedscaleoffset", + data=np.arange(27).reshape(3, 3, 3), + order="F", + dtype="i2", + chunks=(3, 3, 1), + filters=[FixedScaleOffset(offset=1, scale=2, dtype="i2")], +) + +# 3d.chunked.mixed.i2.C.fixedscaleoffset +root.create_dataset( + "3d.chunked.mixed.i2.C.fixedscaleoffset", + data=np.arange(27).reshape(3, 3, 3), + order="C", + dtype="i2", + chunks=(3, 3, 1), + filters=[FixedScaleOffset(offset=1, scale=2, dtype="i2")], +) # 3d.chunked.o data = np.array(