Skip to content
Draft
Show file tree
Hide file tree
Changes from 113 commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
094cfdc
feat: add new dummy layer type: voxel annotation (vox)
briossant Nov 10, 2025
bb1c256
feat: add a new dummy pixel tool
briossant Nov 10, 2025
69b1e22
feat: retreive mouse position and current LOD scale
briossant Nov 10, 2025
488c184
feat: add support for voxel annotation rendering and specification
briossant Nov 10, 2025
fa6ecd5
feat: introduce VoxDummyChunkSource for procedural voxel annotation demo
briossant Nov 10, 2025
a52bad1
feat: no errors but no checkboard tho
briossant Nov 10, 2025
dcf7e12
feat: finaly the checkboard is showing, but it is a bit bugged out, i…
briossant Nov 10, 2025
935509b
doc: rework voxel annotation specs
briossant Nov 10, 2025
d0ce4ca
feat: working on the pixel tool, there are interaction but a bug seem…
briossant Nov 10, 2025
767006c
feat: pixel tool is now working as intended
briossant Nov 10, 2025
d94ee5a
feat: fix pixel tool not working when the scale is not equal to the g…
briossant Nov 10, 2025
b81186c
refactor: rename DummyMultiscaleVolumeChunkSource to VoxMultiscaleVol…
briossant Nov 10, 2025
a121d4b
feat: brush size, eraser mode and little trivial optimization
briossant Nov 10, 2025
cbff7a4
feat: continuous drawing and shape selection for the brush
briossant Nov 10, 2025
bbe859f
doc: add TODO list
briossant Nov 10, 2025
7eba8ec
feat: small improvement on the drawing render delay
briossant Nov 10, 2025
119450f
feat: persist voxel edits to backend and improve drawing responsiveness
briossant Nov 10, 2025
76975cf
feat: redesign toolbox with structured layout, tool selection, and ex…
briossant Nov 10, 2025
514ecd8
feat: implement new local voxel storage with IndexedDB, map initializ…
briossant Nov 10, 2025
85f6d69
doc: brainstorming LOD
briossant Nov 10, 2025
451c69b
feat: support region-based voxel initialization with corners, update …
briossant Nov 10, 2025
a8f9454
feat: expand TODOs with plans for segmentation compression, multi-use…
briossant Nov 10, 2025
f6e3547
feat: implement voxel label creation, persistence via IndexedDB, and …
briossant Nov 10, 2025
8ec4e90
feat: implement RPC-based voxel label persistence
briossant Nov 10, 2025
732132e
refactor: ran 'npm run format:fix'
briossant Nov 10, 2025
2c50dcb
feat: add support for remote voxel sources via HTTP(S) - (note: the l…
briossant Nov 10, 2025
32d7b72
feat: replace `setLabelIds` with `addLabel` for label management
briossant Nov 10, 2025
904591e
doc: add guidelines for junie and write project overview file
briossant Nov 10, 2025
eb4c0e3
feat: map creation and selection, the min scale is currently not save…
briossant Nov 10, 2025
8cfd644
feat: cleanup map init/selection implementation, the remote still nee…
briossant Nov 10, 2025
792c8fa
refactor: remove `VoxelPixelLegacyTool`, update references, and enabl…
briossant Nov 10, 2025
a718320
feat: add LOD locking for voxel rendering and extend brush size range
briossant Nov 10, 2025
3bab282
feat: move local and remote VoxSource to separate files, updated the …
briossant Nov 10, 2025
5e88dcf
feat: move local and remote VoxSource to separate files, updated the …
briossant Nov 10, 2025
5796116
feat: introduce chunk reload and downsample propagation APIs
briossant Nov 10, 2025
33d051a
feat: chunk reloading from the backend -> currently do not work due t…
briossant Nov 10, 2025
cad4a80
feat: add Zarr export functionality and dirty-tree upscaling (not wor…
briossant Nov 10, 2025
8cc92dc
feat: dirty tree upscaling is kinda working, at least enough to concl…
briossant Nov 10, 2025
ba6ae20
feat: restrict brush size and disable dirty tree upscaling
briossant Nov 10, 2025
a01be95
feat: introduce VoxelEditController for centralized edit handling and…
briossant Nov 10, 2025
944998a
refactor: improve chunk invalidation and reload workflows
briossant Nov 10, 2025
769f6ca
feat: add flood fill tool and export UI improvements
briossant Nov 10, 2025
1de7c59
feat: implement downscale job queue and improve chunk reload handling
briossant Nov 10, 2025
a030b50
feat: enhance flood fill stability and optimize Zarr export
briossant Nov 10, 2025
f47b41b
feat: rework map settings UI and add import/export improvements
briossant Nov 10, 2025
ea0832b
doc: update TODOs
briossant Nov 10, 2025
6c8ac37
feat: add Zarr import support and integrate remote chunk fallback
briossant Nov 10, 2025
9c2cfed
feat: add error handling for flood fill and display draw error messages
briossant Nov 10, 2025
f980626
Add `weekly-progress.md` to document the voxel annotation project's p…
briossant Nov 10, 2025
3810f61
feat: introduce `LabelsManager` for label management and cleanup `Vox…
briossant Nov 10, 2025
d16fb23
refactor: remove `RemoteVoxSource` and refactor related components fo…
briossant Nov 10, 2025
3dd2e99
feat: add Stateless S3 Authenticator (SSA) integration with KVStore
briossant Nov 10, 2025
1838e52
feat: upgrade SSA flow to OIDC-based authentication with PKCE support
briossant Nov 10, 2025
6890dbe
feat: enhance SSA KVStore integration and improve authentication logic
briossant Nov 10, 2025
e749b7a
feat: add backend integration for SSA KVStore
briossant Nov 10, 2025
f100dfa
refactor: consolidate SSA URL utilities and improve PKCE verifier gen…
briossant Nov 10, 2025
cbeb5ca
feat: add IndexedDB-backed KVStore implementation
briossant Nov 10, 2025
b51bf6b
feat: move the downscaling queue to the EditController backend and re…
briossant Nov 10, 2025
5c66105
feat: replace IndexedDB implementation with OPFS-backed KVStore
briossant Nov 10, 2025
5c35cdd
feat: add OPFS-backed KVStore module registration
briossant Nov 10, 2025
78056f7
chore: remove unused voxel annotation modules and associated logic
briossant Nov 10, 2025
d838eec
feat: enhance VoxUserLayer with configurable multiscale source option…
briossant Nov 10, 2025
dc74576
fix: rework getVoxelPositionFromMouse to give the right coords
briossant Nov 10, 2025
ac03537
refactor: simplify getVoxelPositionFromMouse logic by removing redund…
briossant Nov 10, 2025
73834c6
feat: optimize voxel drawing and transform caching
briossant Nov 10, 2025
118711c
feat: modularize `VolumeChunk` and chunk format handling
briossant Nov 10, 2025
402256c
feat: implement multiscale voxel editing and raw chunk encoding pipeline
briossant Nov 10, 2025
b24666e
feat: improve chunk handling and integrate SSA write/delete support (…
briossant Nov 10, 2025
35c58a1
feat: enhance retry logic, s3 support, and voxel annotation
briossant Nov 10, 2025
235af99
feat: add targeted chunk invalidation and improve voxel annotation pr…
briossant Nov 10, 2025
fd15293
feat: remove legacy local://voxel-annotations code and improve chunk …
briossant Nov 10, 2025
d59de80
feat: update voxel editing to support bigint values and improve label…
briossant Nov 10, 2025
3ef5f98
feat: refactor voxel annotation handling to support bigint and improv…
briossant Nov 10, 2025
ff0c70b
feat: convert voxel values to BigInt for improved precision in annota…
briossant Nov 10, 2025
9ceb2ad
feat: refactor voxel annotation to support multiscale resolutions and…
briossant Nov 10, 2025
c8b079b
feat: improve uncompressed chunk editing and fix TODOs
briossant Nov 10, 2025
06abea9
feat: add undo/redo support for voxel editing and enhance change trac…
briossant Nov 10, 2025
c51788d
fix: add mask for label generation to ensure it respect the DataType
briossant Nov 10, 2025
6c5cbf1
feat: enhance disk brush tool with arbitrary plane support
briossant Nov 10, 2025
45fb2c9
fix: ensure voxel coordinates align between CPU and GPU
briossant Nov 10, 2025
bb4698a
fix: adjust default `voxFloodMaxVoxels` and refine flood fill plane l…
briossant Nov 10, 2025
d2afe3f
chore: cleanup dev files and format + lint code
briossant Nov 10, 2025
cba1c9f
fix: properly align flood fill seed
briossant Nov 10, 2025
176f96a
refactor: start switching vox tools to the new tool system
briossant Nov 10, 2025
8c7fadb
refactor: fully migrate voxel tools to new tool system
briossant Nov 10, 2025
a1d56f1
feat: add dynamic cursor support for brush and flood fill tools
briossant Nov 10, 2025
c845d0d
feat: improve voxel layer rendering and UI enhancements
briossant Nov 10, 2025
fdef2c0
refactor: Merging vox layer into seg and img layers
briossant Nov 10, 2025
b46224e
feat: took care of every remaining ts errors
briossant Nov 10, 2025
a8feb09
feat: add writable subsource support and enable voxel editing
briossant Nov 10, 2025
595bba0
feat: add in-memory voxel editing capabilities
briossant Nov 10, 2025
ba01de6
feat: add support for compressed chunks in applyEdits() and zarr write()
briossant Nov 10, 2025
b2cc38d
feat: add dynamic cursor for voxel picker tool
briossant Nov 10, 2025
1a20724
refactor: replace the SingleScaleVolumeChunkSource with a proper Mult…
briossant Nov 10, 2025
557ce29
fix: finally found the bug of the drawing preview -> getChunk of InMe…
briossant Nov 10, 2025
c7edca0
refactor: rewrite the InMemoryVolumeChunkSource to no longer create a…
briossant Nov 10, 2025
0f38e50
feat: add chunk invalidation for the preview sources
briossant Nov 10, 2025
d89174a
fix: undo stack was corrupted
briossant Nov 10, 2025
85e7f2d
chore: lint + format
briossant Nov 10, 2025
83316aa
feat: save writable state to json state
briossant Nov 10, 2025
b7dec47
refactor: replace label system with paint value mechanism and simplif…
briossant Nov 10, 2025
c1cbb8b
feat: add state serialization and restoration for voxel editing context
briossant Nov 10, 2025
3acf73f
fix: planeNormal used to aligned floddfill and brush was wrong if the…
briossant Nov 10, 2025
48761ed
refactor: format + lint + cleanup
briossant Nov 10, 2025
8e11ffd
feat: ensure the draw tab is only visible if there is a writable source.
briossant Nov 10, 2025
e119731
fix: the brush cursor was disappearing because browsers have a limita…
briossant Nov 10, 2025
bef93e3
fix: ensure brush cursor is shown on tool activation
briossant Nov 10, 2025
b306e07
refactor(voxel-annotations): remove obsolete color controls and limit…
briossant Nov 10, 2025
3b7650e
fix(voxel-annotations): do not display the brush cursor in the 3d view
briossant Nov 10, 2025
3a7ebbc
refactor(voxel-annotations): rework chunk reloading pipeline -> wait …
briossant Nov 10, 2025
8eca17e
refactor(voxel-annotations): previous commit introduced serious flick…
briossant Nov 10, 2025
9c1cfd0
chore(voxel-annotations): Adjust flood fill to avoid small artifacts.
briossant Nov 10, 2025
2513c55
feat(voxel-annotations): add support for the seg picker tool to cycle…
briossant Nov 11, 2025
fb029f6
feat(datasource): add POC for dataset creation feature : ui + main pi…
briossant Nov 12, 2025
c6592b0
fix(datasource): correct the dataset creation POC to actually produce…
briossant Nov 12, 2025
0ab5bd5
feat(datasource): started generalizing dataset creation feature
briossant Nov 12, 2025
77b1317
chore: run format
briossant Nov 12, 2025
f67b9b8
refactor(datasource): simplify dataset creation UI and remove unused …
briossant Nov 13, 2025
898d8f5
fix(datasource): ensure datasource gets reloaded upon creation
briossant Nov 13, 2025
d4c171a
fix(voxel-annotation): support every data type (expect float32)
briossant Nov 13, 2025
cdd14be
feat(datasource): implement support for gzip and blosc codecs in enco…
briossant Nov 14, 2025
bc84ca0
chore: format code + update TODOs
briossant Nov 14, 2025
b5d90b8
refactor(datasource): following @chrisj comment: generalize KvStore i…
briossant Nov 14, 2025
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ tsconfig.tsbuildinfo
-/docs/python/api/index.rst
/docs/python/api/*.rst
/.vite
/.idea
/.local
/.env
31 changes: 31 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,37 @@
"neuroglancer/kvstore/s3:disabled": "./src/util/false.ts",
"default": "./src/kvstore/s3/register_backend.ts"
},
"#kvstore/opfs/register_frontend": {
"neuroglancer/kvstore/opfs:enabled": "./src/kvstore/opfs/register_frontend.ts",
"neuroglancer/kvstore:none_by_default": "./src/util/false.ts",
"neuroglancer/kvstore/opfs:disabled": "./src/util/false.ts",
"default": "./src/kvstore/opfs/register_frontend.ts"
},
"#kvstore/opfs/register_backend": {
"neuroglancer/kvstore/opfs:enabled": "./src/kvstore/opfs/register_backend.ts",
"neuroglancer/kvstore:none_by_default": "./src/util/false.ts",
"neuroglancer/kvstore/opfs:disabled": "./src/util/false.ts",
"default": "./src/kvstore/opfs/register_backend.ts"
},
"#kvstore/ssa_s3/register_credentials_provider": {
"neuroglancer/python": "./src/util/false.ts",
"neuroglancer/kvstore/ssa_s3:enabled": "./src/kvstore/ssa_s3/register_credentials_provider.ts",
"neuroglancer/kvstore:none_by_default": "./src/util/false.ts",
"neuroglancer/kvstore/ssa_s3:disabled": "./src/util/false.ts",
"default": "./src/kvstore/ssa_s3/register_credentials_provider.ts"
},
"#kvstore/ssa_s3/register_frontend": {
"neuroglancer/kvstore/ssa_s3:enabled": "./src/kvstore/ssa_s3/register_frontend.ts",
"neuroglancer/kvstore:none_by_default": "./src/util/false.ts",
"neuroglancer/kvstore/ssa_s3:disabled": "./src/util/false.ts",
"default": "./src/kvstore/ssa_s3/register_frontend.ts"
},
"#kvstore/ssa_s3/register_backend": {
"neuroglancer/kvstore/ssa_s3:enabled": "./src/kvstore/ssa_s3/register_backend.ts",
"neuroglancer/kvstore:none_by_default": "./src/util/false.ts",
"neuroglancer/kvstore/ssa_s3:disabled": "./src/util/false.ts",
"default": "./src/kvstore/ssa_s3/register_backend.ts"
},
"#kvstore/zip/register_frontend": {
"neuroglancer/kvstore/zip:enabled": "./src/kvstore/zip/register_frontend.ts",
"neuroglancer/kvstore:none_by_default": "./src/util/false.ts",
Expand Down
17 changes: 15 additions & 2 deletions src/chunk_manager/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
LayerChunkProgressInfo,
} from "#src/chunk_manager/base.js";
import {
CHUNK_SOURCE_INVALIDATE_CHUNKS_RPC_ID,
CHUNK_LAYER_STATISTICS_RPC_ID,
CHUNK_MANAGER_RPC_ID,
CHUNK_QUEUE_MANAGER_RPC_ID,
Expand Down Expand Up @@ -1110,8 +1111,10 @@ export class ChunkQueueManager extends SharedObjectCounterpart {
}
}

invalidateSourceCache(source: ChunkSource) {
for (const chunk of source.chunks.values()) {
invalidateCachedChunks(source: ChunkSource, keys: string[]) {
for (const key of keys) {
const chunk = source.chunks.get(key);
if (!chunk) continue;
switch (chunk.state) {
case ChunkState.DOWNLOADING:
cancelChunkDownload(chunk);
Expand All @@ -1123,6 +1126,10 @@ export class ChunkQueueManager extends SharedObjectCounterpart {
// Note: After calling this, chunk may no longer be valid.
this.updateChunkState(chunk, ChunkState.QUEUED);
}
}

invalidateSourceCache(source: ChunkSource) {
this.invalidateCachedChunks(source, [...source.chunks.keys()]);
this.rpc!.invoke("Chunk.update", { source: source.rpcId });
this.scheduleUpdate();
}
Expand Down Expand Up @@ -1378,6 +1385,12 @@ registerRPC(CHUNK_SOURCE_INVALIDATE_RPC_ID, function (x) {
source.chunkManager.queueManager.invalidateSourceCache(source);
});

registerRPC(CHUNK_SOURCE_INVALIDATE_CHUNKS_RPC_ID, function (x) {
const source = <ChunkSource>this.get(x.id);
source.chunkManager.queueManager.invalidateCachedChunks(source, x.keys);
source.chunkManager.queueManager.scheduleUpdate();
});

registerPromiseRPC(
REQUEST_CHUNK_STATISTICS_RPC_ID,
function (x: { queue: number }) {
Expand Down
2 changes: 2 additions & 0 deletions src/chunk_manager/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ export const PREFETCH_PRIORITY_MULTIPLIER = 1e13;
export const CHUNK_QUEUE_MANAGER_RPC_ID = "ChunkQueueManager";
export const CHUNK_MANAGER_RPC_ID = "ChunkManager";
export const CHUNK_SOURCE_INVALIDATE_RPC_ID = "ChunkSource.invalidate";
export const CHUNK_SOURCE_INVALIDATE_CHUNKS_RPC_ID =
"ChunkSource.invalidateChunks";

export const REQUEST_CHUNK_STATISTICS_RPC_ID =
"ChunkQueueManager.requestChunkStatistics";
Expand Down
21 changes: 21 additions & 0 deletions src/chunk_manager/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
LayerChunkProgressInfo,
} from "#src/chunk_manager/base.js";
import {
CHUNK_SOURCE_INVALIDATE_CHUNKS_RPC_ID,
CHUNK_LAYER_STATISTICS_RPC_ID,
CHUNK_MANAGER_RPC_ID,
CHUNK_QUEUE_MANAGER_RPC_ID,
Expand Down Expand Up @@ -463,6 +464,26 @@ export class ChunkSource extends SharedObject {
this.chunks.delete(key);
}

invalidateChunks(keys: string[]): void {
const validKeys: string[] = [];
for (const key of keys) {
const chunk = this.chunks.get(key);
if (chunk) {
validKeys.push(key);
this.deleteChunk(key);
}
}

if (validKeys.length > 0) {
this.rpc!.invoke(CHUNK_SOURCE_INVALIDATE_CHUNKS_RPC_ID, {
id: this.rpcId,
keys: validKeys,
});

this.chunkManager.chunkQueueManager.visibleChunksChanged.dispatch();
}
}

addChunk(key: string, chunk: Chunk) {
this.chunks.set(key, chunk);
}
Expand Down
1 change: 1 addition & 0 deletions src/chunk_worker.bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ import "#src/annotation/backend.js";
import "#src/datasource/enabled_backend_modules.js";
import "#src/kvstore/enabled_backend_modules.js";
import "#src/worker_rpc_context.js";
import "#src/voxel_annotation/edit_backend.js";
2 changes: 2 additions & 0 deletions src/datasource/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export interface DataSubsource {
singleMesh?: SingleMeshSource;
segmentPropertyMap?: SegmentPropertyMap;
segmentationGraph?: SegmentationGraphSource;
isPotentiallyWritable?: boolean;
}

export interface CompleteUrlOptionsBase extends Partial<ProgressOptions> {
Expand Down Expand Up @@ -216,6 +217,7 @@ export interface DataSourceWithRedirectInfo extends DataSource {

export interface DataSubsourceSpecification {
enabled?: boolean;
writable?: boolean;
}

export interface DataSourceSpecification {
Expand Down
102 changes: 102 additions & 0 deletions src/datasource/zarr/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ import {
import "#src/datasource/zarr/codec/gzip/decode.js";
import "#src/datasource/zarr/codec/sharding_indexed/decode.js";
import "#src/datasource/zarr/codec/transpose/decode.js";
import { encodeArray } from "#src/datasource/zarr/codec/encode.js";
import { ChunkKeyEncoding } from "#src/datasource/zarr/metadata/index.js";
import { WithSharedKvStoreContextCounterpart } from "#src/kvstore/backend.js";
import { postProcessRawData } from "#src/sliceview/backend_chunk_decoders/postprocess.js";
import { decodeChannel as decodeChannelUint32 } from "#src/sliceview/compressed_segmentation/decode_uint32.js";
import { decodeChannel as decodeChannelUint64 } from "#src/sliceview/compressed_segmentation/decode_uint64.js";
import type { VolumeChunk } from "#src/sliceview/volume/backend.js";
import { VolumeChunkSource } from "#src/sliceview/volume/backend.js";
import { DataType } from "#src/util/data_type.js";
import { registerSharedObject } from "#src/worker_rpc.js";

@registerSharedObject()
Expand Down Expand Up @@ -97,4 +101,102 @@ export class ZarrVolumeChunkSource extends WithParameters(
await postProcessRawData(chunk, signal, decoded);
}
}

async writeChunk(chunk: VolumeChunk): Promise<void> {
const { kvStore, getChunkKey, decodeCodecs } = this.chunkKvStore as any;
Copy link
Contributor

@chrisj chrisj Nov 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would avoid casting as any, it makes it hard to follow. kvStore in zarr is explictly a ReadableKvStore so I think you want to change that to a normal KvStore.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I will look into that. I originally did this as part of my non-destructive workflow (e.g. I wanted to modify as little as possible of preexisting neuroglancer code while prototyping), but this makes no more sense now.

if (!kvStore.write) {
throw new Error(
"ZarrVolumeChunkSource.writeChunk: underlying kvStore is not writable",
);
}
if (!chunk.data) {
throw new Error("ZarrVolumeChunkSource.writeChunk: missing chunk.data");
}
let dataToWrite = chunk.data;

const { compressedSegmentationBlockSize } = this.spec;
if (compressedSegmentationBlockSize !== undefined) {
const compressedData = chunk.data as Uint32Array;
const { chunkDataSize } = chunk;
if (!chunkDataSize) {
throw new Error("Cannot write chunk with unknown size.");
}
const numElements =
chunkDataSize[0] * chunkDataSize[1] * chunkDataSize[2];
const { dataType } = this.spec;
const baseOffset = compressedData.length > 0 ? compressedData[0] : 0;

if (dataType === DataType.UINT32) {
const uncompressedData = new Uint32Array(numElements);
if (baseOffset !== 0) {
decodeChannelUint32(
uncompressedData,
compressedData,
baseOffset,
chunkDataSize,
compressedSegmentationBlockSize,
);
}
dataToWrite = uncompressedData;
} else {
const uncompressedData = new BigUint64Array(numElements);
if (baseOffset !== 0) {
decodeChannelUint64(
uncompressedData,
compressedData,
baseOffset,
chunkDataSize,
compressedSegmentationBlockSize,
);
}
dataToWrite = uncompressedData;
}
}

const encoded = await encodeArray(
decodeCodecs,
dataToWrite as ArrayBufferView<ArrayBufferLike>,
new AbortController().signal,
);

const { parameters } = this;
const { chunkGridPosition } = chunk;
const { metadata } = parameters;
let baseKey = "";
const rank = this.spec.rank;
const { physicalToLogicalDimension } = metadata.codecs.layoutInfo[0];
let sep: string;
if (metadata.chunkKeyEncoding === ChunkKeyEncoding.DEFAULT) {
baseKey += "c";
sep = metadata.dimensionSeparator;
} else {
sep = "";
if (rank === 0) {
baseKey += "0";
}
}
const keyCoords = new Array<number>(rank);
const { readChunkShape } = metadata.codecs.layoutInfo[0];
const { chunkShape } = metadata;
for (
let fOrderPhysicalDim = 0;
fOrderPhysicalDim < rank;
++fOrderPhysicalDim
) {
const decodedDim =
physicalToLogicalDimension[rank - 1 - fOrderPhysicalDim];
keyCoords[decodedDim] = Math.floor(
(chunkGridPosition[fOrderPhysicalDim] * readChunkShape[decodedDim]) /
chunkShape[decodedDim],
);
}
for (let i = 0; i < rank; ++i) {
baseKey += `${sep}${keyCoords[i]}`;
sep = metadata.dimensionSeparator;
}

const key = getChunkKey(chunkGridPosition, baseKey) as string | unknown;
const arrayBuffer = new Uint8Array(encoded).buffer;
await kvStore.write!(key as any, arrayBuffer);
}
}
25 changes: 25 additions & 0 deletions src/datasource/zarr/codec/encode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Minimal Zarr encode pipeline to persist chunks.
* Supports only the common case of raw bytes (no transpose/compression/sharding).
*/
import type { CodecChainSpec } from "#src/datasource/zarr/codec/index.js";
import { CodecKind } from "#src/datasource/zarr/codec/index.js";

export async function encodeArray(
codecs: CodecChainSpec,
typed: ArrayBufferView<ArrayBufferLike>,
_signal: AbortSignal,
): Promise<Uint8Array<ArrayBufferLike>> {
// Only support simple "bytes" encoding with no array-to-array and no bytes-to-bytes codecs.
const hasArrayToArray = codecs[CodecKind.arrayToArray].length > 0;
const hasBytesToBytes = codecs[CodecKind.bytesToBytes].length > 0;
const arrayToBytes = codecs[CodecKind.arrayToBytes];
if (hasArrayToArray || hasBytesToBytes || arrayToBytes.name !== "bytes") {
throw new Error(
`encodeArray: Unsupported codec chain; only raw 'bytes' without additional codecs is supported. Got arrayToArray=${hasArrayToArray}, bytesToBytes=${hasBytesToBytes}, arrayToBytes=${arrayToBytes.name}`,
);
}
// For raw bytes, we can write the underlying buffer.
const { buffer, byteOffset, byteLength } = typed;
return new Uint8Array(buffer, byteOffset, byteLength);
}
2 changes: 1 addition & 1 deletion src/datasource/zarr/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ export class ZarrDataSource implements KvStoreBasedDataSourceProvider {
id: "default",
default: true,
url: undefined,
subsource: { volume },
subsource: { volume, isPotentiallyWritable: true },
},
{
id: "bounds",
Expand Down
2 changes: 2 additions & 0 deletions src/kvstore/enabled_backend_modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ import "#kvstore/ngauth/register";
import "#kvstore/ocdbt/register_backend";
import "#kvstore/s3/register_backend";
import "#kvstore/zip/register_backend";
import "#kvstore/ssa_s3/register_backend";
import "#kvstore/opfs/register_backend";
3 changes: 3 additions & 0 deletions src/kvstore/enabled_frontend_modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ import "#kvstore/ngauth/register";
import "#kvstore/ngauth/register_credentials_provider";
import "#kvstore/ocdbt/register_frontend";
import "#kvstore/s3/register_frontend";
import "#kvstore/ssa_s3/register_credentials_provider";
import "#kvstore/ssa_s3/register_frontend";
import "#kvstore/zip/register_frontend";
import "#kvstore/opfs/register_frontend";
10 changes: 9 additions & 1 deletion src/kvstore/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,15 @@ export interface ListableKvStore {
list?: (prefix: string, options: DriverListOptions) => Promise<ListResponse>;
}

export interface KvStore extends ReadableKvStore, ListableKvStore {
export interface WritableKvStore {
write?: (key: string, value: ArrayBuffer) => Promise<void>;
delete?: (key: string) => Promise<void>;
}

export interface KvStore
extends ReadableKvStore,
ListableKvStore,
WritableKvStore {
// Indicates that the only valid key is the empty string.
singleKey?: boolean;
}
Expand Down
Loading