spatialdata-js-util 0.1.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.
- spatialdata_js_util-0.1.0/PKG-INFO +412 -0
- spatialdata_js_util-0.1.0/README.md +379 -0
- spatialdata_js_util-0.1.0/pyproject.toml +88 -0
- spatialdata_js_util-0.1.0/setup.cfg +4 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/__init__.py +107 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/cli.py +803 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/__init__.py +67 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/backends.py +250 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/chunks.py +40 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/encoding.py +154 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/htj2k_wasm.py +266 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/names.py +26 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/probe/multicomponent.j2c +0 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/probe/multicomponent.json +3 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/probe/multicomponent.npy +0 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/vendor/__init__.py +1 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/vendor/encode-plane.mjs +164 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/vendor/openjph/index.mjs +131 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/vendor/openjph/wasm/libopenjph.mjs +2 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/vendor/openjph/wasm/libopenjph.wasm +0 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/vendor/openjph/wasm/libopenjph_simd.mjs +2 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/vendor/openjph/wasm/libopenjph_simd.wasm +0 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/codecs/zarr_codec.py +149 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/errors.py +5 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/images.py +849 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/index_permutations.py +311 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/points.py +445 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/points_zarr.py +799 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/provenance.py +17 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/pyramids.py +318 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/runners.py +458 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/store.py +305 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/tables.py +310 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/tui/__init__.py +0 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/tui/app.py +112 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/tui/models.py +45 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/tui/screens.py +1255 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/tui/widgets.py +50 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/verify.py +338 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util.egg-info/PKG-INFO +412 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util.egg-info/SOURCES.txt +59 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util.egg-info/dependency_links.txt +1 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util.egg-info/entry_points.txt +7 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util.egg-info/requires.txt +19 -0
- spatialdata_js_util-0.1.0/src/spatialdata_js_util.egg-info/top_level.txt +1 -0
- spatialdata_js_util-0.1.0/tests/test_htj2k_encode.py +76 -0
- spatialdata_js_util-0.1.0/tests/test_htj2k_encode_demo.py +37 -0
- spatialdata_js_util-0.1.0/tests/test_htj2k_quality.py +37 -0
- spatialdata_js_util-0.1.0/tests/test_integration.py +236 -0
- spatialdata_js_util-0.1.0/tests/test_points.py +324 -0
- spatialdata_js_util-0.1.0/tests/test_points_zarr.py +347 -0
- spatialdata_js_util-0.1.0/tests/test_pyramids.py +272 -0
- spatialdata_js_util-0.1.0/tests/test_recompress.py +631 -0
- spatialdata_js_util-0.1.0/tests/test_synthetic_images.py +65 -0
- spatialdata_js_util-0.1.0/tests/test_tables.py +505 -0
- spatialdata_js_util-0.1.0/tests/test_tui.py +464 -0
- spatialdata_js_util-0.1.0/tests/test_verify.py +163 -0
- spatialdata_js_util-0.1.0/tests/test_write_synthetic.py +43 -0
- spatialdata_js_util-0.1.0/tests/test_writer.py +150 -0
- spatialdata_js_util-0.1.0/tests/test_zarr.py +113 -0
- spatialdata_js_util-0.1.0/tests/test_zarr_codec.py +221 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spatialdata-js-util
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Read/write utilities for browser-oriented SpatialData stores (image codecs, Morton-indexed points, CSC tables)
|
|
5
|
+
Author: SpatialData.js contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Taylor-CCB-Group/SpatialData.js
|
|
8
|
+
Project-URL: Repository, https://github.com/Taylor-CCB-Group/SpatialData.js
|
|
9
|
+
Keywords: spatialdata,zarr,ome-zarr,htj2k,jpeg2000,spatial-omics
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: imagecodecs>=2025.8.2
|
|
18
|
+
Requires-Dist: numcodecs>=0.16
|
|
19
|
+
Requires-Dist: numpy>=2.0
|
|
20
|
+
Requires-Dist: pandas>=2.2
|
|
21
|
+
Requires-Dist: pyarrow>=18
|
|
22
|
+
Requires-Dist: zarr>=3.3.0
|
|
23
|
+
Provides-Extra: write
|
|
24
|
+
Requires-Dist: anndata>=0.12; extra == "write"
|
|
25
|
+
Requires-Dist: dask[array]>=2026.7.1; extra == "write"
|
|
26
|
+
Requires-Dist: ome-zarr>=0.16; extra == "write"
|
|
27
|
+
Requires-Dist: scipy>=1.11; extra == "write"
|
|
28
|
+
Requires-Dist: spatialdata>=0.8.0; extra == "write"
|
|
29
|
+
Provides-Extra: tui
|
|
30
|
+
Requires-Dist: textual>=1.0; extra == "tui"
|
|
31
|
+
Provides-Extra: all
|
|
32
|
+
Requires-Dist: spatialdata-js-util[tui,write]; extra == "all"
|
|
33
|
+
|
|
34
|
+
# spatialdata-js-util
|
|
35
|
+
|
|
36
|
+
Python utilities for producing — and reading back — SpatialData stores tuned for
|
|
37
|
+
incremental reading in the browser by
|
|
38
|
+
[SpatialData.js](https://github.com/Taylor-CCB-Group/SpatialData.js).
|
|
39
|
+
|
|
40
|
+
Three optimizations, all aimed at making a viewport cost a few small ranged reads
|
|
41
|
+
instead of a whole-element download:
|
|
42
|
+
|
|
43
|
+
| Area | What it does | Why |
|
|
44
|
+
|------|--------------|-----|
|
|
45
|
+
| **images** | Recompress rasters as JPEG 2000 or HTJ2K, and add [multiscale pyramids](#multiscale-pyramids) where they are missing | Tiles decode in the browser; HTJ2K is markedly cheaper to decode than JPEG 2000. Without a pyramid, a zoomed-out view still costs full-resolution chunks |
|
|
46
|
+
| **points** | Morton-sort Points Parquet with sentinel bbox rows and controlled row groups | A viewport maps to a few Parquet row-group reads |
|
|
47
|
+
| **tables** | Convert `X`/layers from CSR to CSC | Reading one gene becomes one contiguous range read, not a scan of every row |
|
|
48
|
+
|
|
49
|
+
Installing the package also **registers the image codecs with zarr**, so stores
|
|
50
|
+
written here open in ordinary `spatialdata.read_zarr` — see
|
|
51
|
+
[Reading stores back in Python](#reading-stores-back-in-python).
|
|
52
|
+
|
|
53
|
+
Every command is available three ways: an [interactive TUI](#interactive-tui)
|
|
54
|
+
(start here), the `spatialdata-js-util` CLI, and the [Python API](#python-api).
|
|
55
|
+
|
|
56
|
+
> **Status: experimental.** The HTJ2K codec identifier is not a standard
|
|
57
|
+
> OME-Zarr codec. Stores using it are readable by the `SpatialData.js` runtime
|
|
58
|
+
> and by Python with this package installed. It is published in the hope that,
|
|
59
|
+
> if useful, the approach can be adopted more broadly.
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install spatialdata-js-util
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
That base install is enough to **read** stores. To **write** them, add the
|
|
68
|
+
`write` extra, which pulls in `spatialdata`, `anndata`, `dask` and `scipy`:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install 'spatialdata-js-util[write]'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Requires Python 3.12+ and `spatialdata >= 0.8.0`.
|
|
75
|
+
|
|
76
|
+
## Interactive TUI
|
|
77
|
+
|
|
78
|
+
If you would rather not memorise flags, everything below is also available as a
|
|
79
|
+
terminal UI. It is the easiest way to get started.
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install 'spatialdata-js-util[tui]'
|
|
83
|
+
spatialdata-js-util tui
|
|
84
|
+
|
|
85
|
+
# Or pre-fill the store path so you skip the first prompt
|
|
86
|
+
spatialdata-js-util tui ~/data/xenium.zarr
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Pick a command from the home menu and the UI walks you through it:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
IMAGES Recompress rasters (JPEG 2000 / HTJ2K)
|
|
93
|
+
POINTS List Points elements in a Zarr store
|
|
94
|
+
POINTS Morton-sort Points from Zarr
|
|
95
|
+
POINTS Morton-sort CSV/Parquet file
|
|
96
|
+
POINTS Write multiscale Points Parquet
|
|
97
|
+
POINTS Write index permutations derivative store
|
|
98
|
+
TABLES List table elements in a Zarr store
|
|
99
|
+
TABLES Convert table matrices to CSC
|
|
100
|
+
CODECS Show HTJ2K backend availability
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The flow is the same for every command: **guided form → confirm → run → report.**
|
|
104
|
+
|
|
105
|
+
- **Forms** are pre-filled with sensible defaults, and with the store path you
|
|
106
|
+
already used this session. `Enter` advances between fields and submits on the
|
|
107
|
+
last one; `Escape` goes back; `q` quits.
|
|
108
|
+
- **Anything that overwrites asks first.** Rewriting `points/<key>/points.parquet`
|
|
109
|
+
in place, converting tables in place, or replacing an existing output store all
|
|
110
|
+
stop on a confirmation screen naming the exact path before touching anything.
|
|
111
|
+
- **Runs stream their JSON output** to a log pane, so a long recompression shows
|
|
112
|
+
progress rather than appearing to hang.
|
|
113
|
+
- The recompress form carries the [pyramid](#multiscale-pyramids) controls, so
|
|
114
|
+
single-resolution input can be made browser-ready without leaving the UI.
|
|
115
|
+
- **Writes are verified afterwards** and the report screen shows a pass/fail
|
|
116
|
+
table. For Morton writes that means:
|
|
117
|
+
|
|
118
|
+
| Check | Meaning |
|
|
119
|
+
|-------|---------|
|
|
120
|
+
| `column_present` | `morton_code_2d` column exists |
|
|
121
|
+
| `sentinel_prefix` | First 2–4 rows have `morton_code_2d == 0` |
|
|
122
|
+
| `sentinel_bbox` | Sentinel rows encode the full dataset x/y bounds |
|
|
123
|
+
| `morton_monotonic` | Morton codes non-decreasing after sentinels |
|
|
124
|
+
| `row_group_sentinels` | Row group 0 contains only sentinel rows |
|
|
125
|
+
| `no_uint_intermediates` | No persisted `*_uint` staging columns |
|
|
126
|
+
|
|
127
|
+
Multiscale and index-permutation runs show schema/manifest checks instead.
|
|
128
|
+
|
|
129
|
+
`CODECS → Show HTJ2K backend availability` is a good first stop: it tells you
|
|
130
|
+
which codec backend your environment resolved to, and whether it passed the
|
|
131
|
+
multi-component probe described [below](#htj2k-backends-and-why-there-is-a-probe).
|
|
132
|
+
|
|
133
|
+
### Reading stores back in Python
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
import spatialdata as sd
|
|
137
|
+
|
|
138
|
+
sdata = sd.read_zarr("store-htj2k.zarr") # HTJ2K/JP2K images decode transparently
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
No import of this package and no `register_codecs()` call is needed — the codecs
|
|
142
|
+
are advertised through `zarr.codecs` entry points, so zarr finds them as soon as
|
|
143
|
+
the distribution is installed. (`spatialdata_js_util.codecs.register_codecs()`
|
|
144
|
+
exists only for running from a source tree with no installed metadata.)
|
|
145
|
+
|
|
146
|
+
### HTJ2K backends, and why there is a probe
|
|
147
|
+
|
|
148
|
+
HTJ2K encode/decode can go through either of two OpenJPH builds:
|
|
149
|
+
|
|
150
|
+
- **`imagecodecs`** — native OpenJPH, pure `pip install`, no Node.js. Preferred.
|
|
151
|
+
- **`openjph-wasm`** — the vendored WASM the JS reader uses, driven by Node.js
|
|
152
|
+
worker processes. Requires `node` on `PATH`.
|
|
153
|
+
|
|
154
|
+
A WASM build previously used in this project silently decoded *every* component
|
|
155
|
+
of a multi-component codestream as component 0 — losing volumetric planes with
|
|
156
|
+
no error (see [`docs/multi-component-codec-findings.md`](docs/multi-component-codec-findings.md)).
|
|
157
|
+
Because that failure is silent, a backend is not trusted on the strength of its
|
|
158
|
+
library name. `imagecodecs` is admitted only if it decodes a committed
|
|
159
|
+
multi-component codestream — produced by the WASM encoder — to exactly the
|
|
160
|
+
expected samples. Any mismatch, and it is rejected in favour of the WASM path.
|
|
161
|
+
|
|
162
|
+
Check what your environment resolved to:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
spatialdata-js-util codecs info
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Pin a backend explicitly with `SPATIALDATA_JS_UTIL_HTJ2K_BACKEND=imagecodecs`
|
|
169
|
+
or `=openjph-wasm` (the probe still gates `imagecodecs`).
|
|
170
|
+
|
|
171
|
+
## Images
|
|
172
|
+
|
|
173
|
+
Recompress a store, preserving everything that is not an image. Path sources are
|
|
174
|
+
copied first, so tables, shapes, points and unconfigured rasters survive without
|
|
175
|
+
the whole object being loaded.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
spatialdata-js-util images recompress input.zarr output-htj2k.zarr \
|
|
179
|
+
--codec experimental.openjph_htj2k --preset balanced --chunks auto --overwrite
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
With `--sibling`, each original image is kept and a compressed copy added
|
|
183
|
+
alongside it (`morphology_focus:htj2k_balanced`), so tools without the codec keep
|
|
184
|
+
working.
|
|
185
|
+
|
|
186
|
+
### Presets and `--quality`
|
|
187
|
+
|
|
188
|
+
`--quality` is the OpenJPH quantization step: **lower means higher fidelity and a
|
|
189
|
+
larger codestream** — not a JPEG-style 0–100 scale. The step is relative to the
|
|
190
|
+
dtype's full dynamic range, so one input LSB is `1/256` for 8-bit data and
|
|
191
|
+
`1/65536` for 16-bit.
|
|
192
|
+
|
|
193
|
+
HTJ2K presets are therefore expressed as a **multiple of the input's LSB**, and
|
|
194
|
+
resolve to a concrete step from the raster's dtype:
|
|
195
|
+
|
|
196
|
+
| Preset | JPEG 2000 | HTJ2K | `uint8` | `uint16` |
|
|
197
|
+
|--------|-----------|-------|---------|----------|
|
|
198
|
+
| `lossless` | reversible, round-trip verified | `reversible=True` | — | — |
|
|
199
|
+
| `balanced` | `level=100` | 2 LSB | `0.0078125` | `0.000030518` |
|
|
200
|
+
| `small` | `level=75` | 5 LSB | `0.01953125` | `0.000076294` |
|
|
201
|
+
|
|
202
|
+
A step **below one input LSB is strictly worse than lossless**: it cannot buy
|
|
203
|
+
fidelity the data does not have, so the irreversible transform returns a
|
|
204
|
+
bit-identical image while encoding *larger* than `--preset lossless`. Passing
|
|
205
|
+
such a `--quality` warns and names the floor. The resolved step is recorded in
|
|
206
|
+
the manifest under `encode_options`.
|
|
207
|
+
|
|
208
|
+
An explicit `--quality` is an absolute step, not an LSB multiple, so pick it for
|
|
209
|
+
the raster's dtype — `0.0005` below is ~33 LSB of `uint16` morphology, but would
|
|
210
|
+
be 0.13 LSB (worse than lossless) on 8-bit input:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
spatialdata-js-util images recompress input.zarr out.zarr \
|
|
214
|
+
--codec experimental.openjph_htj2k --quality 0.0005 --sibling --overwrite
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Multiscale pyramids
|
|
218
|
+
|
|
219
|
+
Acquisition output is often written at a single resolution. A browser then has no
|
|
220
|
+
choice but to fetch full-resolution chunks however far out the user is zoomed —
|
|
221
|
+
which defeats the image codecs rather than complementing them. `--pyramid` gives
|
|
222
|
+
any single-level image a pyramid before recompressing it, so one command turns
|
|
223
|
+
single-resolution input into a browser-ready store:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
spatialdata-js-util images recompress input.zarr output.zarr \
|
|
227
|
+
--codec experimental.openjph_htj2k --preset balanced --pyramid --overwrite
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
To add pyramids without touching codecs:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
spatialdata-js-util images add-pyramid input.zarr output.zarr --overwrite
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
By default levels are chosen automatically: keep halving until the largest
|
|
237
|
+
spatial axis is `--pyramid-min-size` (1024) or smaller, so the coarsest level is
|
|
238
|
+
roughly one chunk. Override with `--pyramid-levels N` (a *total* count including
|
|
239
|
+
full resolution) and `--pyramid-downscale K`.
|
|
240
|
+
|
|
241
|
+
Images that already have more than one level are **skipped**, and the manifest
|
|
242
|
+
says so; `--pyramid-force` rebuilds them anyway. Pass `--labels` to
|
|
243
|
+
`add-pyramid` to include label elements — they are downsampled by the label
|
|
244
|
+
model, so ids are never averaged into values that identify no object.
|
|
245
|
+
|
|
246
|
+
Full resolution is copied through unchanged; only the added levels are new.
|
|
247
|
+
Levels are generated through SpatialData's own parsers, which get the per-level
|
|
248
|
+
scale *and* half-pixel translation right — hand-written `multiscales` metadata
|
|
249
|
+
tends to misalign every level against the full-resolution image.
|
|
250
|
+
|
|
251
|
+
Pyramids are always written to a **different** store than the source. SpatialData
|
|
252
|
+
refuses to delete the files backing a live element, so there is no in-place mode.
|
|
253
|
+
|
|
254
|
+
### Other image notes
|
|
255
|
+
|
|
256
|
+
Labels are written with Blosc/zstd. Browser image codecs support `uint8`,
|
|
257
|
+
`int8`, `uint16` and `int16` only. A sidecar manifest records the expanded
|
|
258
|
+
config, per-raster stats, the codec backend used, and decoded checksums:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
spatialdata-js-util images inspect output-htj2k.manifest.json
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
For repeatable multi-image runs, use a JSON config:
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{
|
|
268
|
+
"default_image": { "codec": "imagecodecs_jpeg2k", "preset": "lossless", "chunks": "auto" },
|
|
269
|
+
"images": {
|
|
270
|
+
"morphology_focus": { "preset": "balanced" },
|
|
271
|
+
"he_image": { "codec": "experimental.openjph_htj2k", "quality": 0.001 }
|
|
272
|
+
},
|
|
273
|
+
"default_labels": { "codec": "blosc", "clevel": 5 }
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Points
|
|
278
|
+
|
|
279
|
+
Morton-sorted Points Parquet, Vitessce-compatible:
|
|
280
|
+
|
|
281
|
+
- `morton_code_2d` added using 16 bits per axis
|
|
282
|
+
- the first 2–4 rows are sentinels with `morton_code_2d == 0` encoding the full
|
|
283
|
+
point bounding box, so readers can get bounds without a scan
|
|
284
|
+
- `{feature_key}_codes` added when the element declares a `feature_key`
|
|
285
|
+
- string/categorical columns moved to the right of the table
|
|
286
|
+
- row-group size controlled, so a viewport maps to a few row-group reads
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
# List Points elements
|
|
290
|
+
spatialdata-js-util points list ~/data/xenium.zarr
|
|
291
|
+
|
|
292
|
+
# Morton-sort transcripts in place on points/<key>/points.parquet
|
|
293
|
+
spatialdata-js-util points morton-from-zarr ~/data/xenium.zarr --points-key transcripts
|
|
294
|
+
|
|
295
|
+
# Morton-sort a CSV or Parquet file
|
|
296
|
+
spatialdata-js-util points morton input.csv output.parquet --feature-key feature_name
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Morton v1 belongs on the **canonical** element path `points/<key>/points.parquet`.
|
|
300
|
+
Use `--experimental` only for layouts standard readers cannot consume (see
|
|
301
|
+
[ADR 0002](../../docs/adr/0002-spatially-aware-vector-loading.md)).
|
|
302
|
+
|
|
303
|
+
`points index-permutations` builds a derivative store with several sort layouts
|
|
304
|
+
side by side for benchmarking. All of these are also available from the
|
|
305
|
+
[TUI](#interactive-tui), which verifies the output after each write.
|
|
306
|
+
|
|
307
|
+
### Multiscale Zarr points (experimental)
|
|
308
|
+
|
|
309
|
+
`points zarr` writes the layout of merfisheyes'
|
|
310
|
+
[points-zarr spec draft 0.1](https://github.com/kresnajenie/merfisheyes/blob/main/docs/points-zarr-spec.md):
|
|
311
|
+
levels of uniform random samples (each a quarter of the one below), each stored in
|
|
312
|
+
tile order and in feature order, with a row index. A viewer can then draw any zoom
|
|
313
|
+
from a bounded read, and read only the rows of the genes selected.
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
# A new store: a standard points element, with the copy beside it
|
|
317
|
+
spatialdata-js-util points zarr transcripts.parquet transcripts.zarr \
|
|
318
|
+
--feature-key feature_name --coordinates x_location,y_location
|
|
319
|
+
|
|
320
|
+
# Add the copy to an existing store's points element, from its own points.parquet
|
|
321
|
+
spatialdata-js-util points add-zarr ~/data/xenium.zarr --points-key transcripts
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The copy is the element's `experimental.points-zarr` subgroup; the element itself stays
|
|
325
|
+
an ordinary SpatialData points element, so spatialdata and other standard readers read
|
|
326
|
+
the store as before ([ADR 0006](../../docs/adr/0006-experimental-formats-beside-the-standard.md)).
|
|
327
|
+
Writing works out of core: Parquet input is read in batches, spilled to temporary files
|
|
328
|
+
beside the output (about 18 bytes a point, plus one level's sorted copy) and sorted one
|
|
329
|
+
band of tile rows at a time.
|
|
330
|
+
|
|
331
|
+
On exFAT (and other filesystems without extended attributes) macOS writes a `._name`
|
|
332
|
+
file beside each file that carries one, which doubles the file count and breaks
|
|
333
|
+
readers that list a directory (pyarrow reads `._part.0.parquet` as a part). Delete
|
|
334
|
+
them after writing: `find STORE -name '._*' -delete`.
|
|
335
|
+
|
|
336
|
+
## Tables
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# Rewrite every table's X (and named layers) as CSC, in place
|
|
340
|
+
spatialdata-js-util tables to-csc ~/data/xenium.zarr
|
|
341
|
+
|
|
342
|
+
# Or write a converted copy, leaving the source alone
|
|
343
|
+
spatialdata-js-util tables to-csc input.zarr output-csc.zarr --overwrite
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The conversion changes only the sparse layout, not the values, and the result
|
|
347
|
+
stays readable by any AnnData client. Dense matrices are left alone unless you
|
|
348
|
+
pass `--densify`, since sparsifying a dense matrix can make it bigger.
|
|
349
|
+
|
|
350
|
+
Only the matrices are rewritten — `obs`, `var`, `uns` and the rest are left
|
|
351
|
+
byte-for-byte alone, and what is written is pinned to the store's existing
|
|
352
|
+
encodings (no sharding, no `nullable-string-array` index). Re-serialising a whole
|
|
353
|
+
table would otherwise silently re-encode it with whatever conventions the
|
|
354
|
+
installed AnnData currently prefers, which can leave readers unable to find the
|
|
355
|
+
variable names even though every value is still present.
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
from spatialdata_js_util import convert_store_tables_to_csc, to_csc
|
|
359
|
+
|
|
360
|
+
convert_store_tables_to_csc("store.zarr") # whole store
|
|
361
|
+
csc = to_csc(adata.X) # or just a matrix
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Python API
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
from spatialdata_js_util import (
|
|
368
|
+
recompress_spatialdata,
|
|
369
|
+
add_pyramids,
|
|
370
|
+
has_pyramid,
|
|
371
|
+
write_morton_points_parquet,
|
|
372
|
+
convert_store_tables_to_csc,
|
|
373
|
+
backend_report,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
result = recompress_spatialdata(
|
|
377
|
+
"input.zarr", "output-htj2k.zarr",
|
|
378
|
+
codec="experimental.openjph_htj2k",
|
|
379
|
+
preset="balanced",
|
|
380
|
+
pyramid=True, # add levels to single-resolution images first
|
|
381
|
+
overwrite=True,
|
|
382
|
+
)
|
|
383
|
+
print(result.manifest_path)
|
|
384
|
+
|
|
385
|
+
# Or pyramids on their own
|
|
386
|
+
has_pyramid("input.zarr", "images", "morphology") # -> False
|
|
387
|
+
add_pyramids("input.zarr", "output.zarr", levels=4)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Monorepo development
|
|
391
|
+
|
|
392
|
+
From the repository root:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
pnpm test:python
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
The vendored `openjph-wasm` assets under
|
|
399
|
+
`src/spatialdata_js_util/codecs/vendor/openjph/` are gitignored and produced by
|
|
400
|
+
`node scripts/vendor-openjph-for-python.mjs`, which `pnpm test:python` runs
|
|
401
|
+
first. They are only needed for the WASM backend; the `imagecodecs` backend works
|
|
402
|
+
without them.
|
|
403
|
+
|
|
404
|
+
Fixture-generation scripts under `scripts/` are repo-local and not shipped in the
|
|
405
|
+
wheel. To regenerate the backend probe fixture (requires Node.js):
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
uv run --directory python/spatialdata-js-util python scripts/build_htj2k_probe.py
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
TypeScript callers can use `encodeHtj2kPlane()` from `zarrextra` directly; see
|
|
412
|
+
[`docs/htj2k-wasm-encode-design.md`](docs/htj2k-wasm-encode-design.md).
|