worldarray 0.1.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. worldarray-0.3.0/CHANGELOG.md +103 -0
  2. {worldarray-0.1.1 → worldarray-0.3.0}/PKG-INFO +46 -5
  3. {worldarray-0.1.1 → worldarray-0.3.0}/README.md +45 -4
  4. {worldarray-0.1.1 → worldarray-0.3.0}/docs/spec.md +461 -14
  5. {worldarray-0.1.1 → worldarray-0.3.0}/pyproject.toml +1 -1
  6. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/__init__.py +30 -5
  7. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/array.py +416 -46
  8. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/context.py +31 -0
  9. worldarray-0.3.0/src/worldarray/grid.py +492 -0
  10. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/placement.py +43 -9
  11. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/store.py +28 -2
  12. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/units.py +43 -0
  13. worldarray-0.3.0/src/worldarray/write.py +829 -0
  14. worldarray-0.3.0/tests/fixtures/write.json +1690 -0
  15. worldarray-0.3.0/tests/test_grid.py +385 -0
  16. worldarray-0.3.0/tests/test_write.py +754 -0
  17. {worldarray-0.1.1 → worldarray-0.3.0}/uv.lock +1 -1
  18. worldarray-0.1.1/CHANGELOG.md +0 -35
  19. {worldarray-0.1.1 → worldarray-0.3.0}/.github/workflows/ci.yml +0 -0
  20. {worldarray-0.1.1 → worldarray-0.3.0}/.github/workflows/docs.yml +0 -0
  21. {worldarray-0.1.1 → worldarray-0.3.0}/.github/workflows/publish.yml +0 -0
  22. {worldarray-0.1.1 → worldarray-0.3.0}/.gitignore +0 -0
  23. {worldarray-0.1.1 → worldarray-0.3.0}/LICENSE +0 -0
  24. {worldarray-0.1.1 → worldarray-0.3.0}/docs/changelog.md +0 -0
  25. {worldarray-0.1.1 → worldarray-0.3.0}/docs/index.md +0 -0
  26. {worldarray-0.1.1 → worldarray-0.3.0}/mkdocs.yml +0 -0
  27. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/geometry.py +0 -0
  28. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/levels.py +0 -0
  29. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/resample.py +0 -0
  30. {worldarray-0.1.1 → worldarray-0.3.0}/src/worldarray/retry.py +0 -0
  31. {worldarray-0.1.1 → worldarray-0.3.0}/tests/conftest.py +0 -0
  32. {worldarray-0.1.1 → worldarray-0.3.0}/tests/fixtures/metadata.json +0 -0
  33. {worldarray-0.1.1 → worldarray-0.3.0}/tests/test_access.py +0 -0
  34. {worldarray-0.1.1 → worldarray-0.3.0}/tests/test_geometry.py +0 -0
  35. {worldarray-0.1.1 → worldarray-0.3.0}/tests/test_metadata.py +0 -0
  36. {worldarray-0.1.1 → worldarray-0.3.0}/tests/test_read.py +0 -0
  37. {worldarray-0.1.1 → worldarray-0.3.0}/tests/test_resample.py +0 -0
@@ -0,0 +1,103 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0 (2026-10-22)
4
+
5
+ Grids and blocks, built to spec draft 3 (Behaviours 17 to 19). Nothing on the read or
6
+ write side changes.
7
+
8
+ - `blocks(shape, block_shape, context=0, box=None, clip=True)`: the array (or a voxel
9
+ box, which may reach past the array) cut into write boxes that tile it with no overlap
10
+ and no gap, each with a read box grown by `context` (one int, one per axis, or
11
+ `(before, after)` pairs) and clipped to the array when `clip`; C order, `len`,
12
+ `blocks[id]` in O(1), `index_of(begin)`; a `Block` carries `index`, `id`, `begin`,
13
+ `end`, `read_begin`, `read_end`, `at_start` and `at_end` per axis and `inner`, the write
14
+ box's slices inside the read box. Plain picklable records; nothing is scheduled.
15
+ `vol.blocks()` defaults the block to `write_shape`, so default blocks are aligned writes.
16
+ `vol.write_chunk(index, data)` writes one whole unit.
17
+ - `Grid(shape, voxel_size, translation, axes, units, chunks, shards)`: a `WorldArray`
18
+ without a store, with `box`, `corner`, `index_box`, `chunks_covering`, `write_shape`,
19
+ `blocks` and `meta(dtype)`; `Grid.from_meta(meta, voxel_size=)` is an existing array's
20
+ grid or the resample target grid over it. `create(path, grid=g, dtype=)` creates from it.
21
+ - `level_factors(voxel_size, ratio=1.5, ...)`: the anisotropy-aware pyramid schedule
22
+ mesh-n-bone and cellmap-analyze share, per level.
23
+ - `units.abbreviation`, and `store.join`, `store.parent`, `store.basename` as public
24
+ URL-aware path helpers.
25
+
26
+ ## 0.2.0 (2026-10-22)
27
+
28
+ Metadata and placed creation, built to spec draft 3 (Behaviours 10 to 16). Nothing on the
29
+ read side changes.
30
+
31
+ - `ome_multiscales(axes, units, levels, version)` builds the complete OME-NGFF 0.4 or 0.5
32
+ group document with one explicit placement per level; `level_placement` and
33
+ `level_shape` derive a level from factors and an origin (Convention 1's rule inverted,
34
+ so a level built from a ROI origin is placed from it; `level_shape` absorbs float-noise
35
+ factors such as `0.3 / 0.1`). Units are written in OME's names (`"nm"` becomes
36
+ `"nanometer"`; `units.ome_name`), an unknown unit as given; the multiscale's `type` is
37
+ written when given. Expected documents are pinned in `tests/fixtures/write.json`, shared
38
+ with chunkmirage's TypeScript builder, each case with its resolved per-axis units and
39
+ types, fifteen cases.
40
+ - `create(path, shape, dtype, voxel_size, translation, chunks=..., ...)` writes the group
41
+ document (made when absent, else its one dataset entry added or replaced, every other
42
+ attribute kept) and then the array, as zarr v2 with OME 0.4 or zarr v3 with OME 0.5
43
+ following the group's format. `chunks` is required, codecs are explicit with a zstd
44
+ level 3 default, shards are a zarr v3 option. `exists` is `"error"` or `"adopt"` (an
45
+ identical array is opened for writing, a different one raises naming every difference,
46
+ a creation lost to another process is adopted after a short backoff). Nothing is ever
47
+ deleted, truncated or overwritten. Write units equal to the fill value are not stored
48
+ unless `store_fill=True`, and a stored unit written so loses its key, as zarr-python's
49
+ `write_empty_chunks=False` does. `create` also writes a bare group document for every
50
+ missing ancestor down from the `.zarr` root and follows the nearest existing ancestor's
51
+ zarr version, so a level made in a zarr v2 container is zarr v2 with OME 0.4 and the
52
+ hierarchy browses (cellmap-analyze, cellmap-flow). Adopting an identical array writes no
53
+ group document; after a write the document is read again and a lost entry is written
54
+ again, then raises (registration-station's concurrent block jobs).
55
+ - `ensure_group_entry` is public: list an array the package did not create, such as a
56
+ symlinked `s0` (mesh-n-bone), or a level filled before it is listed.
57
+ - `open(path, mode="r+")` opens an existing array for writing; a `WorldArray` pickles by
58
+ its arguments and its context's spec and reopens in the receiving process, a capped
59
+ context staying capped (mesh-n-bone's workers). `write.codecs_of` takes a path.
60
+ - `write_index(begin, end, data)` writes whole write units only (the shard when sharded,
61
+ else the chunk; the clipped last unit allowed), so writers in different processes need
62
+ no coordination; `partial=True` does tensorstore's read-modify-write. `write(box, data)`
63
+ maps a world box by the centre rule first. No cast, no padding, no resampling on write.
64
+ - `open_pyramid(path, mode="r+").add_level(name, factors=, origin=)` creates a level's
65
+ entry and empty array, placed and sized from the level below with its dtype, chunks,
66
+ shards, codecs and fill value copied; the downsampling itself stays with the caller.
67
+ - `chunk_key` and `parse_chunk_key` for zarr v2, zarr v3 (both encodings, sharded arrays
68
+ keyed by shard), N5 and precomputed, as free functions and on `WorldArray`;
69
+ `list_chunks` uses the same parser. `write_shape`, `ArrayMeta.shard_shape`,
70
+ `chunks_covering(write=True)`.
71
+ ## 0.1.1 (2026-10-22)
72
+
73
+ - funlib `voxel_size`/`offset` on the parent group place an array that carries none, for
74
+ zarr and N5 alike; funlib's keys on an N5 array are read in the array's C order, as
75
+ zarr-python's N5 store writes them (both from mesh-n-bone's review of its PR #48).
76
+ - A precomputed path that names a scale is that scale under `level=0` or `"first"` too;
77
+ other levels warn and are ignored, as on an array path.
78
+ - The GCE metadata guard is applied only off Google Cloud (decided from the DMI product
79
+ name and the `GCE_METADATA_*` variables), so metadata-server credentials keep working on
80
+ a Google VM.
81
+ - `Pyramid.placements()`, `voxel_sizes()` and `translations()` read the group document when
82
+ the pyramid was opened through one of its level paths too; they used to open every level
83
+ from there, and a missing level made them raise.
84
+ - **Placement change.** An N5 level with `downsamplingFactors` and no offset now sits with
85
+ its corner at the origin at every level, as Neuroglancer draws N5: translation
86
+ `f / 2 * base`. 0.1.0 followed BigDataViewer for `pixelResolution` pyramids, centring the
87
+ base's voxel 0 on the origin (`(f - 1) / 2 * base`, half a base voxel lower), while
88
+ placing `resolution` pyramids with the corner at the origin, so the two disagreed. Two
89
+ shared fixture cases move and are renamed: `n5_group_pixelresolution_and_downsampling_factors_corner_at_origin`
90
+ (translation 4, 4, 4) and `n5_bigstitcher_resolution_with_downsampling_factors_corner_at_origin`
91
+ (0.5, 0.157, 0.157).
92
+
93
+ ## 0.1.0 (2026-10-21)
94
+
95
+ - First version, built to `docs/spec.md`: open zarr v2, zarr v3, N5 and Neuroglancer
96
+ precomputed arrays and pyramids through tensorstore; one metadata precedence per format
97
+ with the voxel-centre rule for translations; index-space and world-box reads with
98
+ padding; level selection; nearest and linear resampling; retries; shared and capped
99
+ tensorstore contexts.
100
+ Built and trialled in one day against the readers it replaces in cellmap-flow,
101
+ cellmap-analyze, mesh-n-bone and chunkmirage, and checked on their own data by the
102
+ CineMap, liconn-err, registration-station and salamander repos; see `docs/spec.md`,
103
+ "Trial status". Hot-path reads cost 0.1 to 0.2 ms over a raw tensorstore read.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: worldarray
3
- Version: 0.1.1
3
+ Version: 0.3.0
4
4
  Summary: Read zarr, N5 and Neuroglancer precomputed volumes in world coordinates through tensorstore: correct voxel placement, pyramid levels, padded region reads, resampling. numpy + tensorstore only.
5
5
  Project-URL: Homepage, https://janeliascicomp.github.io/worldarray/
6
6
  Project-URL: Documentation, https://janeliascicomp.github.io/worldarray/
@@ -80,7 +80,10 @@ resampled when no level matches, fast enough for a model server or a thousand cl
80
80
  workers, with retries for flaky object stores and thread caps for shared hosts. No one
81
81
  package did all of that, so every tool wrote its own reader, and the readers drifted apart. worldarray is that reader, written once, with the rules stated and tested.
82
82
 
83
- Its dependencies are `tensorstore` and `numpy`. It is read-only in this version. The
83
+ Its dependencies are `tensorstore` and `numpy`. Since 0.2 it also writes the one thing
84
+ every tool wrote for itself and got wrong in its own way: the OME-NGFF metadata of a placed
85
+ array or pyramid level, and the array itself, created once and filled by aligned writes
86
+ from any number of jobs. It never deletes, overwrites or downsamples. The
84
87
  [specification](https://janeliascicomp.github.io/worldarray/spec/) is the contract it is
85
88
  built to; every rule below is a test.
86
89
 
@@ -115,6 +118,13 @@ vol.read_index((0, 0, 0), (64, 64, 64), padding=0)
115
118
 
116
119
  pyr = wa.open_pyramid(path) # every level, opened on first use
117
120
  pyr.select((16, 16, 16), mode="floor") # the level to read at 16 nm
121
+
122
+ # writing: a placed level, its OME-NGFF group document written for it (zarr v3 + OME 0.5 here)
123
+ out = wa.create("out.zarr/s0", shape=(512, 512, 512), dtype="uint64",
124
+ voxel_size=(8, 8, 8), translation=(4, 4, 4), chunks=(64, 64, 64))
125
+ out.write_index((0, 0, 0), (64, 64, 64), block) # whole chunks only: many jobs, no locks
126
+ out.write(box, block) # or by world box
127
+ s1 = wa.open_pyramid("out.zarr", mode="r+").add_level("s1", factors=2) # placed and sized; you fill it
118
128
  ```
119
129
 
120
130
  ## What it does
@@ -139,6 +149,18 @@ pyr.select((16, 16, 16), mode="floor") # the level to read at 16 nm
139
149
  cancelled future on timeout.
140
150
  - **Carries the geometry**: `Box` with `intersect`, `union`, `contains`, `grow`, `shift`,
141
151
  `snap` to a grid, and the voxel indices a world box selects.
152
+ - **Writes placed arrays** (0.2): the OME-NGFF 0.4 or 0.5 document from voxel size, centre
153
+ translation, axes and units, with each level's placement explicit or derived from
154
+ factors and an origin; `create` following the container's format and writing the group
155
+ documents an output needs down from its `.zarr` root, with explicit chunks and codecs,
156
+ and only two answers to "it exists": raise, or adopt an identical array after checking;
157
+ whole-unit writes from many processes with no coordination; `add_level` as metadata plus
158
+ an empty array; chunk keys for every format. Nothing is ever deleted or overwritten.
159
+ - **Plans outputs and blocks** (0.3): a store-less `Grid` with the same geometry as an
160
+ array, so an output is planned, cut into blocks and only then created; `blocks` tiling an
161
+ array or a box into write boxes with context-grown read boxes, in C order, as plain
162
+ picklable records a cluster job takes by id; the anisotropy-aware pyramid schedule.
163
+ Nothing is scheduled or run.
142
164
 
143
165
  ## The placement rule
144
166
 
@@ -152,7 +174,7 @@ moved half a voxel on reading:
152
174
  | OME-NGFF `translation` | centre | as is |
153
175
  | COSEM `transform.translate` | centre | as is |
154
176
  | `offset` beside `resolution` / `voxel_size` (`offset` missing: 0) | corner | `offset + voxel_size / 2` |
155
- | N5 `pixelResolution` or `resolution` with `downsamplingFactors f`, no offset | BigDataViewer | `(f - 1) / 2 * base` |
177
+ | N5 `pixelResolution` or `resolution` with `downsamplingFactors f`, no offset | corner at the origin at every level, as Neuroglancer draws N5 | `f / 2 * base` |
156
178
  | precomputed `voxel_offset` | corner | `(voxel_offset + 1/2) * resolution` |
157
179
  | nothing | a plain array | voxels of 1, corner at the origin |
158
180
 
@@ -176,13 +198,32 @@ wa.open_tensorstore(path, level=None, context=None)
176
198
  wa.shared_context(cache_bytes=0) ; wa.capped_context(cache_bytes=0, concurrency=1, network=True) ; wa.Retry(...)
177
199
  wa.list_chunk_keys(path, separator=".") # chunk indices from keys alone, no metadata needed
178
200
  wa.Box ; wa.units.TABLE
201
+
202
+ # writing (0.2)
203
+ wa.create(path, shape, dtype, voxel_size, translation, *, axes=None, units="nanometer", chunks=..., shards=None,
204
+ fill_value=0, codecs="zstd", format=None, attrs=None, group_attrs=None, exists="error", store_fill=False) -> WorldArray
205
+ wa.open(path, mode="r+") ; wa.open_pyramid(path, mode="r+").add_level(name, factors=, origin=0, ...)
206
+ wa.ome_multiscales(axes, units, levels, version="0.5", type=None) ; wa.level_placement(voxel_size, translation, factors, origin=0)
207
+ wa.level_shape(shape, factors, origin=0) ; wa.chunk_key(index, format, ...) ; wa.parse_chunk_key(key, format, ...)
208
+ wa.ensure_group_entry(group_path, name, voxel_size, translation, axes, units="nanometer") # list an array you made yourself
209
+ wa.units.ome_name("nm") -> "nanometer" ; wa.write.codecs_of(path) -> (codecs, shards)
210
+
211
+ # grids and blocks (0.3)
212
+ g = wa.Grid(shape, voxel_size, translation, axes=None, units="nanometer", chunks=None, shards=None) # or Grid.from_meta(meta, voxel_size=)
213
+ wa.create(path, grid=g, dtype="uint64")
214
+ for b in wa.blocks(shape, block_shape, context=0, box=None, clip=True): # also vol.blocks() and g.blocks()
215
+ b.begin, b.end, b.read_begin, b.read_end, b.inner, b.at_end, b.id # plain records; blocks[id] is O(1)
216
+ wa.level_factors(voxel_size, ratio=1.5, max_levels=None, stop_shape=None, shape=None)
217
+ wa.units.abbreviation("nanometer") ; wa.store.join(path, "s0") ; wa.store.parent(path) ; wa.store.basename(path)
179
218
  ```
180
219
 
181
220
  `WorldArray`: `ts`, `meta`, `grid`, `shape`, `dtype`, `axes`, `voxel_size`, `translation`,
182
- `corner`, `box`, `levels`, `level_name`, `level_path`, `source_voxel_size`, `resampled`;
221
+ `corner`, `box`, `levels`, `level_name`, `level_path`, `source_voxel_size`, `resampled`,
222
+ `writable`, `write_shape`;
183
223
  `read(box, voxel_size=, padding=, method=, mask=, anchor=, transform=, post_transform=, select=, strict=)`,
184
224
  `read_index(begin, end, padding=, mask=, select=, transform=)`, `index_box(box)`,
185
- `chunks_covering(begin, end)`, `list_chunks()`, `reopen(context=)`.
225
+ `chunks_covering(begin, end, write=)`, `list_chunks()`, `chunk_key(index)`, `parse_chunk_key(key)`,
226
+ `write_index(begin, end, data, partial=)`, `write(box, data, partial=)`, `reopen(context=)`.
186
227
 
187
228
  A box is a `(begin, end)` pair of floats in your units, half-open per axis, over the
188
229
  spatial axes or every axis; any object with `.begin`/`.end` or `.offset`/`.shape` also
@@ -21,7 +21,10 @@ resampled when no level matches, fast enough for a model server or a thousand cl
21
21
  workers, with retries for flaky object stores and thread caps for shared hosts. No one
22
22
  package did all of that, so every tool wrote its own reader, and the readers drifted apart. worldarray is that reader, written once, with the rules stated and tested.
23
23
 
24
- Its dependencies are `tensorstore` and `numpy`. It is read-only in this version. The
24
+ Its dependencies are `tensorstore` and `numpy`. Since 0.2 it also writes the one thing
25
+ every tool wrote for itself and got wrong in its own way: the OME-NGFF metadata of a placed
26
+ array or pyramid level, and the array itself, created once and filled by aligned writes
27
+ from any number of jobs. It never deletes, overwrites or downsamples. The
25
28
  [specification](https://janeliascicomp.github.io/worldarray/spec/) is the contract it is
26
29
  built to; every rule below is a test.
27
30
 
@@ -56,6 +59,13 @@ vol.read_index((0, 0, 0), (64, 64, 64), padding=0)
56
59
 
57
60
  pyr = wa.open_pyramid(path) # every level, opened on first use
58
61
  pyr.select((16, 16, 16), mode="floor") # the level to read at 16 nm
62
+
63
+ # writing: a placed level, its OME-NGFF group document written for it (zarr v3 + OME 0.5 here)
64
+ out = wa.create("out.zarr/s0", shape=(512, 512, 512), dtype="uint64",
65
+ voxel_size=(8, 8, 8), translation=(4, 4, 4), chunks=(64, 64, 64))
66
+ out.write_index((0, 0, 0), (64, 64, 64), block) # whole chunks only: many jobs, no locks
67
+ out.write(box, block) # or by world box
68
+ s1 = wa.open_pyramid("out.zarr", mode="r+").add_level("s1", factors=2) # placed and sized; you fill it
59
69
  ```
60
70
 
61
71
  ## What it does
@@ -80,6 +90,18 @@ pyr.select((16, 16, 16), mode="floor") # the level to read at 16 nm
80
90
  cancelled future on timeout.
81
91
  - **Carries the geometry**: `Box` with `intersect`, `union`, `contains`, `grow`, `shift`,
82
92
  `snap` to a grid, and the voxel indices a world box selects.
93
+ - **Writes placed arrays** (0.2): the OME-NGFF 0.4 or 0.5 document from voxel size, centre
94
+ translation, axes and units, with each level's placement explicit or derived from
95
+ factors and an origin; `create` following the container's format and writing the group
96
+ documents an output needs down from its `.zarr` root, with explicit chunks and codecs,
97
+ and only two answers to "it exists": raise, or adopt an identical array after checking;
98
+ whole-unit writes from many processes with no coordination; `add_level` as metadata plus
99
+ an empty array; chunk keys for every format. Nothing is ever deleted or overwritten.
100
+ - **Plans outputs and blocks** (0.3): a store-less `Grid` with the same geometry as an
101
+ array, so an output is planned, cut into blocks and only then created; `blocks` tiling an
102
+ array or a box into write boxes with context-grown read boxes, in C order, as plain
103
+ picklable records a cluster job takes by id; the anisotropy-aware pyramid schedule.
104
+ Nothing is scheduled or run.
83
105
 
84
106
  ## The placement rule
85
107
 
@@ -93,7 +115,7 @@ moved half a voxel on reading:
93
115
  | OME-NGFF `translation` | centre | as is |
94
116
  | COSEM `transform.translate` | centre | as is |
95
117
  | `offset` beside `resolution` / `voxel_size` (`offset` missing: 0) | corner | `offset + voxel_size / 2` |
96
- | N5 `pixelResolution` or `resolution` with `downsamplingFactors f`, no offset | BigDataViewer | `(f - 1) / 2 * base` |
118
+ | N5 `pixelResolution` or `resolution` with `downsamplingFactors f`, no offset | corner at the origin at every level, as Neuroglancer draws N5 | `f / 2 * base` |
97
119
  | precomputed `voxel_offset` | corner | `(voxel_offset + 1/2) * resolution` |
98
120
  | nothing | a plain array | voxels of 1, corner at the origin |
99
121
 
@@ -117,13 +139,32 @@ wa.open_tensorstore(path, level=None, context=None)
117
139
  wa.shared_context(cache_bytes=0) ; wa.capped_context(cache_bytes=0, concurrency=1, network=True) ; wa.Retry(...)
118
140
  wa.list_chunk_keys(path, separator=".") # chunk indices from keys alone, no metadata needed
119
141
  wa.Box ; wa.units.TABLE
142
+
143
+ # writing (0.2)
144
+ wa.create(path, shape, dtype, voxel_size, translation, *, axes=None, units="nanometer", chunks=..., shards=None,
145
+ fill_value=0, codecs="zstd", format=None, attrs=None, group_attrs=None, exists="error", store_fill=False) -> WorldArray
146
+ wa.open(path, mode="r+") ; wa.open_pyramid(path, mode="r+").add_level(name, factors=, origin=0, ...)
147
+ wa.ome_multiscales(axes, units, levels, version="0.5", type=None) ; wa.level_placement(voxel_size, translation, factors, origin=0)
148
+ wa.level_shape(shape, factors, origin=0) ; wa.chunk_key(index, format, ...) ; wa.parse_chunk_key(key, format, ...)
149
+ wa.ensure_group_entry(group_path, name, voxel_size, translation, axes, units="nanometer") # list an array you made yourself
150
+ wa.units.ome_name("nm") -> "nanometer" ; wa.write.codecs_of(path) -> (codecs, shards)
151
+
152
+ # grids and blocks (0.3)
153
+ g = wa.Grid(shape, voxel_size, translation, axes=None, units="nanometer", chunks=None, shards=None) # or Grid.from_meta(meta, voxel_size=)
154
+ wa.create(path, grid=g, dtype="uint64")
155
+ for b in wa.blocks(shape, block_shape, context=0, box=None, clip=True): # also vol.blocks() and g.blocks()
156
+ b.begin, b.end, b.read_begin, b.read_end, b.inner, b.at_end, b.id # plain records; blocks[id] is O(1)
157
+ wa.level_factors(voxel_size, ratio=1.5, max_levels=None, stop_shape=None, shape=None)
158
+ wa.units.abbreviation("nanometer") ; wa.store.join(path, "s0") ; wa.store.parent(path) ; wa.store.basename(path)
120
159
  ```
121
160
 
122
161
  `WorldArray`: `ts`, `meta`, `grid`, `shape`, `dtype`, `axes`, `voxel_size`, `translation`,
123
- `corner`, `box`, `levels`, `level_name`, `level_path`, `source_voxel_size`, `resampled`;
162
+ `corner`, `box`, `levels`, `level_name`, `level_path`, `source_voxel_size`, `resampled`,
163
+ `writable`, `write_shape`;
124
164
  `read(box, voxel_size=, padding=, method=, mask=, anchor=, transform=, post_transform=, select=, strict=)`,
125
165
  `read_index(begin, end, padding=, mask=, select=, transform=)`, `index_box(box)`,
126
- `chunks_covering(begin, end)`, `list_chunks()`, `reopen(context=)`.
166
+ `chunks_covering(begin, end, write=)`, `list_chunks()`, `chunk_key(index)`, `parse_chunk_key(key)`,
167
+ `write_index(begin, end, data, partial=)`, `write(box, data, partial=)`, `reopen(context=)`.
127
168
 
128
169
  A box is a `(begin, end)` pair of floats in your units, half-open per axis, over the
129
170
  spatial axes or every axis; any object with `.begin`/`.end` or `.offset`/`.shape` also