spatialdata-js-util 0.1.0__tar.gz → 0.2.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 → spatialdata_js_util-0.2.0}/PKG-INFO +47 -12
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/README.md +45 -10
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/pyproject.toml +2 -2
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/__init__.py +6 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/cli.py +46 -11
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/backends.py +16 -1
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/images.py +7 -6
- spatialdata_js_util-0.2.0/src/spatialdata_js_util/pyramids.py +788 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/store.py +25 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/tui/screens.py +4 -4
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util.egg-info/PKG-INFO +47 -12
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util.egg-info/SOURCES.txt +1 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util.egg-info/requires.txt +1 -1
- spatialdata_js_util-0.2.0/tests/test_htj2k_native_floor.py +51 -0
- spatialdata_js_util-0.2.0/tests/test_pyramids.py +799 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_tui.py +1 -1
- spatialdata_js_util-0.1.0/src/spatialdata_js_util/pyramids.py +0 -318
- spatialdata_js_util-0.1.0/tests/test_pyramids.py +0 -272
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/setup.cfg +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/__init__.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/chunks.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/encoding.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/htj2k_wasm.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/names.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/probe/multicomponent.j2c +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/probe/multicomponent.json +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/probe/multicomponent.npy +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/vendor/__init__.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/vendor/encode-plane.mjs +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/vendor/openjph/index.mjs +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/vendor/openjph/wasm/libopenjph.mjs +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/vendor/openjph/wasm/libopenjph.wasm +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/vendor/openjph/wasm/libopenjph_simd.mjs +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/vendor/openjph/wasm/libopenjph_simd.wasm +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/zarr_codec.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/errors.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/index_permutations.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/points.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/points_zarr.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/provenance.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/runners.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/tables.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/tui/__init__.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/tui/app.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/tui/models.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/tui/widgets.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/verify.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util.egg-info/dependency_links.txt +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util.egg-info/entry_points.txt +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util.egg-info/top_level.txt +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_htj2k_encode.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_htj2k_encode_demo.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_htj2k_quality.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_integration.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_points.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_points_zarr.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_recompress.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_synthetic_images.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_tables.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_verify.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_write_synthetic.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_writer.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_zarr.py +0 -0
- {spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/tests/test_zarr_codec.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spatialdata-js-util
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Read/write utilities for browser-oriented SpatialData stores (image codecs, Morton-indexed points, CSC tables)
|
|
5
5
|
Author: SpatialData.js contributors
|
|
6
6
|
License: MIT
|
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
14
14
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
15
15
|
Requires-Python: >=3.12
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
|
-
Requires-Dist: imagecodecs>=
|
|
17
|
+
Requires-Dist: imagecodecs>=2026.6.26
|
|
18
18
|
Requires-Dist: numcodecs>=0.16
|
|
19
19
|
Requires-Dist: numpy>=2.0
|
|
20
20
|
Requires-Dist: pandas>=2.2
|
|
@@ -111,7 +111,8 @@ The flow is the same for every command: **guided form → confirm → run → re
|
|
|
111
111
|
- **Runs stream their JSON output** to a log pane, so a long recompression shows
|
|
112
112
|
progress rather than appearing to hang.
|
|
113
113
|
- The recompress form carries the [pyramid](#multiscale-pyramids) controls, so
|
|
114
|
-
single-resolution input can be made browser-ready without
|
|
114
|
+
single-resolution or shallow-pyramid input can be made browser-ready without
|
|
115
|
+
leaving the UI.
|
|
115
116
|
- **Writes are verified afterwards** and the report screen shows a pass/fail
|
|
116
117
|
table. For Morton writes that means:
|
|
117
118
|
|
|
@@ -238,18 +239,43 @@ spatial axis is `--pyramid-min-size` (1024) or smaller, so the coarsest level is
|
|
|
238
239
|
roughly one chunk. Override with `--pyramid-levels N` (a *total* count including
|
|
239
240
|
full resolution) and `--pyramid-downscale K`.
|
|
240
241
|
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
An image gets levels only when its coarsest existing level is still larger than
|
|
243
|
+
`--pyramid-min-size` (with `--pyramid-levels N`, when it has fewer than N
|
|
244
|
+
levels). Existing levels are kept and new ones are coarsened from the coarsest
|
|
245
|
+
level (full resolution, for a single-resolution image), so only that level is
|
|
246
|
+
read. Others are **skipped**, and the manifest says
|
|
247
|
+
why; `--pyramid-force` rebuilds every image from full resolution anyway. Pass `--labels` to
|
|
243
248
|
`add-pyramid` to include label elements — they are downsampled by the label
|
|
244
249
|
model, so ids are never averaged into values that identify no object.
|
|
245
250
|
|
|
246
|
-
|
|
247
|
-
Levels are generated through SpatialData's own parsers
|
|
248
|
-
scale *and* half-pixel
|
|
249
|
-
|
|
251
|
+
Existing levels are copied through unchanged; only the added levels are new.
|
|
252
|
+
Levels are generated through SpatialData's own parsers and the per-level
|
|
253
|
+
transforms are derived from level shapes, which gets the scale *and* half-pixel
|
|
254
|
+
translation right — hand-written `multiscales` metadata tends to misalign every
|
|
255
|
+
level against the full-resolution image.
|
|
250
256
|
|
|
251
|
-
|
|
252
|
-
refuses to delete the files backing a live element
|
|
257
|
+
`add-pyramid` and `recompress --pyramid` write to a **different** store than the
|
|
258
|
+
source, because SpatialData refuses to delete the files backing a live element.
|
|
259
|
+
Adding levels deletes nothing, though, so there are two ways to skip the copy:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
spatialdata-js-util images add-pyramid store.zarr --in-place
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`--in-place` appends the missing levels to the store itself. Existing arrays are
|
|
266
|
+
not read back, rewritten or recompressed; the new arrays are written first and
|
|
267
|
+
then declared in the element's `zarr.json` and the store's consolidated metadata,
|
|
268
|
+
so an interrupted run leaves a store that still reads as before and that a rerun
|
|
269
|
+
completes. New levels use the coarsest level's codecs when those are plain zarr
|
|
270
|
+
codecs, and zarr's defaults (zstd) when it uses an image codec, so they never add
|
|
271
|
+
JPEG 2000 or HTJ2K. It cannot replace levels, so `--pyramid-force` and
|
|
272
|
+
`--overwrite` do not apply, and it stops rather than append to levels whose
|
|
273
|
+
transforms do not follow their shapes. Do not run it while anything else writes
|
|
274
|
+
the store.
|
|
275
|
+
|
|
276
|
+
To avoid even that when you are about to write a `SpatialData` object anyway, call
|
|
277
|
+
`ensure_pyramids(sdata)`: it replaces the elements that need levels in memory and
|
|
278
|
+
writes nothing.
|
|
253
279
|
|
|
254
280
|
### Other image notes
|
|
255
281
|
|
|
@@ -364,9 +390,12 @@ csc = to_csc(adata.X) # or just a matrix
|
|
|
364
390
|
## Python API
|
|
365
391
|
|
|
366
392
|
```python
|
|
393
|
+
import spatialdata as sd
|
|
367
394
|
from spatialdata_js_util import (
|
|
368
395
|
recompress_spatialdata,
|
|
369
396
|
add_pyramids,
|
|
397
|
+
add_pyramids_in_place,
|
|
398
|
+
ensure_pyramids,
|
|
370
399
|
has_pyramid,
|
|
371
400
|
write_morton_points_parquet,
|
|
372
401
|
convert_store_tables_to_csc,
|
|
@@ -377,7 +406,7 @@ result = recompress_spatialdata(
|
|
|
377
406
|
"input.zarr", "output-htj2k.zarr",
|
|
378
407
|
codec="experimental.openjph_htj2k",
|
|
379
408
|
preset="balanced",
|
|
380
|
-
pyramid=True, # add levels to
|
|
409
|
+
pyramid=True, # add levels to images whose coarsest level is too large
|
|
381
410
|
overwrite=True,
|
|
382
411
|
)
|
|
383
412
|
print(result.manifest_path)
|
|
@@ -385,6 +414,12 @@ print(result.manifest_path)
|
|
|
385
414
|
# Or pyramids on their own
|
|
386
415
|
has_pyramid("input.zarr", "images", "morphology") # -> False
|
|
387
416
|
add_pyramids("input.zarr", "output.zarr", levels=4)
|
|
417
|
+
add_pyramids_in_place("input.zarr") # append levels to the store itself
|
|
418
|
+
|
|
419
|
+
# Or in memory, before a write you were doing anyway
|
|
420
|
+
sdata = sd.read_zarr("input.zarr")
|
|
421
|
+
ensure_pyramids(sdata, include_labels=True)
|
|
422
|
+
sdata.write("output.zarr")
|
|
388
423
|
```
|
|
389
424
|
|
|
390
425
|
## Monorepo development
|
|
@@ -78,7 +78,8 @@ The flow is the same for every command: **guided form → confirm → run → re
|
|
|
78
78
|
- **Runs stream their JSON output** to a log pane, so a long recompression shows
|
|
79
79
|
progress rather than appearing to hang.
|
|
80
80
|
- The recompress form carries the [pyramid](#multiscale-pyramids) controls, so
|
|
81
|
-
single-resolution input can be made browser-ready without
|
|
81
|
+
single-resolution or shallow-pyramid input can be made browser-ready without
|
|
82
|
+
leaving the UI.
|
|
82
83
|
- **Writes are verified afterwards** and the report screen shows a pass/fail
|
|
83
84
|
table. For Morton writes that means:
|
|
84
85
|
|
|
@@ -205,18 +206,43 @@ spatial axis is `--pyramid-min-size` (1024) or smaller, so the coarsest level is
|
|
|
205
206
|
roughly one chunk. Override with `--pyramid-levels N` (a *total* count including
|
|
206
207
|
full resolution) and `--pyramid-downscale K`.
|
|
207
208
|
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
An image gets levels only when its coarsest existing level is still larger than
|
|
210
|
+
`--pyramid-min-size` (with `--pyramid-levels N`, when it has fewer than N
|
|
211
|
+
levels). Existing levels are kept and new ones are coarsened from the coarsest
|
|
212
|
+
level (full resolution, for a single-resolution image), so only that level is
|
|
213
|
+
read. Others are **skipped**, and the manifest says
|
|
214
|
+
why; `--pyramid-force` rebuilds every image from full resolution anyway. Pass `--labels` to
|
|
210
215
|
`add-pyramid` to include label elements — they are downsampled by the label
|
|
211
216
|
model, so ids are never averaged into values that identify no object.
|
|
212
217
|
|
|
213
|
-
|
|
214
|
-
Levels are generated through SpatialData's own parsers
|
|
215
|
-
scale *and* half-pixel
|
|
216
|
-
|
|
218
|
+
Existing levels are copied through unchanged; only the added levels are new.
|
|
219
|
+
Levels are generated through SpatialData's own parsers and the per-level
|
|
220
|
+
transforms are derived from level shapes, which gets the scale *and* half-pixel
|
|
221
|
+
translation right — hand-written `multiscales` metadata tends to misalign every
|
|
222
|
+
level against the full-resolution image.
|
|
217
223
|
|
|
218
|
-
|
|
219
|
-
refuses to delete the files backing a live element
|
|
224
|
+
`add-pyramid` and `recompress --pyramid` write to a **different** store than the
|
|
225
|
+
source, because SpatialData refuses to delete the files backing a live element.
|
|
226
|
+
Adding levels deletes nothing, though, so there are two ways to skip the copy:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
spatialdata-js-util images add-pyramid store.zarr --in-place
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
`--in-place` appends the missing levels to the store itself. Existing arrays are
|
|
233
|
+
not read back, rewritten or recompressed; the new arrays are written first and
|
|
234
|
+
then declared in the element's `zarr.json` and the store's consolidated metadata,
|
|
235
|
+
so an interrupted run leaves a store that still reads as before and that a rerun
|
|
236
|
+
completes. New levels use the coarsest level's codecs when those are plain zarr
|
|
237
|
+
codecs, and zarr's defaults (zstd) when it uses an image codec, so they never add
|
|
238
|
+
JPEG 2000 or HTJ2K. It cannot replace levels, so `--pyramid-force` and
|
|
239
|
+
`--overwrite` do not apply, and it stops rather than append to levels whose
|
|
240
|
+
transforms do not follow their shapes. Do not run it while anything else writes
|
|
241
|
+
the store.
|
|
242
|
+
|
|
243
|
+
To avoid even that when you are about to write a `SpatialData` object anyway, call
|
|
244
|
+
`ensure_pyramids(sdata)`: it replaces the elements that need levels in memory and
|
|
245
|
+
writes nothing.
|
|
220
246
|
|
|
221
247
|
### Other image notes
|
|
222
248
|
|
|
@@ -331,9 +357,12 @@ csc = to_csc(adata.X) # or just a matrix
|
|
|
331
357
|
## Python API
|
|
332
358
|
|
|
333
359
|
```python
|
|
360
|
+
import spatialdata as sd
|
|
334
361
|
from spatialdata_js_util import (
|
|
335
362
|
recompress_spatialdata,
|
|
336
363
|
add_pyramids,
|
|
364
|
+
add_pyramids_in_place,
|
|
365
|
+
ensure_pyramids,
|
|
337
366
|
has_pyramid,
|
|
338
367
|
write_morton_points_parquet,
|
|
339
368
|
convert_store_tables_to_csc,
|
|
@@ -344,7 +373,7 @@ result = recompress_spatialdata(
|
|
|
344
373
|
"input.zarr", "output-htj2k.zarr",
|
|
345
374
|
codec="experimental.openjph_htj2k",
|
|
346
375
|
preset="balanced",
|
|
347
|
-
pyramid=True, # add levels to
|
|
376
|
+
pyramid=True, # add levels to images whose coarsest level is too large
|
|
348
377
|
overwrite=True,
|
|
349
378
|
)
|
|
350
379
|
print(result.manifest_path)
|
|
@@ -352,6 +381,12 @@ print(result.manifest_path)
|
|
|
352
381
|
# Or pyramids on their own
|
|
353
382
|
has_pyramid("input.zarr", "images", "morphology") # -> False
|
|
354
383
|
add_pyramids("input.zarr", "output.zarr", levels=4)
|
|
384
|
+
add_pyramids_in_place("input.zarr") # append levels to the store itself
|
|
385
|
+
|
|
386
|
+
# Or in memory, before a write you were doing anyway
|
|
387
|
+
sdata = sd.read_zarr("input.zarr")
|
|
388
|
+
ensure_pyramids(sdata, include_labels=True)
|
|
389
|
+
sdata.write("output.zarr")
|
|
355
390
|
```
|
|
356
391
|
|
|
357
392
|
## Monorepo development
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "spatialdata-js-util"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Read/write utilities for browser-oriented SpatialData stores (image codecs, Morton-indexed points, CSC tables)"
|
|
5
5
|
requires-python = ">=3.12"
|
|
6
6
|
readme = "README.md"
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
16
16
|
]
|
|
17
17
|
dependencies = [
|
|
18
|
-
"imagecodecs>=
|
|
18
|
+
"imagecodecs>=2026.6.26",
|
|
19
19
|
"numcodecs>=0.16",
|
|
20
20
|
"numpy>=2.0",
|
|
21
21
|
"pandas>=2.2",
|
|
@@ -40,7 +40,10 @@ from .images import (
|
|
|
40
40
|
from .pyramids import (
|
|
41
41
|
PyramidResult,
|
|
42
42
|
add_pyramids,
|
|
43
|
+
add_pyramids_in_place,
|
|
44
|
+
ensure_pyramids,
|
|
43
45
|
has_pyramid,
|
|
46
|
+
needs_pyramid,
|
|
44
47
|
resolve_scale_factors,
|
|
45
48
|
)
|
|
46
49
|
from .points import (
|
|
@@ -84,16 +87,19 @@ __all__ = [
|
|
|
84
87
|
"RecompressedSpatialData",
|
|
85
88
|
"add_points_zarr_to_store",
|
|
86
89
|
"add_pyramids",
|
|
90
|
+
"add_pyramids_in_place",
|
|
87
91
|
"anndata_to_csc",
|
|
88
92
|
"backend_report",
|
|
89
93
|
"build_spatialdata_multiscale_metadata",
|
|
90
94
|
"convert_store_tables_to_csc",
|
|
91
95
|
"dtype_quantum",
|
|
96
|
+
"ensure_pyramids",
|
|
92
97
|
"has_pyramid",
|
|
93
98
|
"htj2k_available",
|
|
94
99
|
"htj2k_preset_quality",
|
|
95
100
|
"is_htj2k_codec",
|
|
96
101
|
"morton_sort_points",
|
|
102
|
+
"needs_pyramid",
|
|
97
103
|
"plan_column_encodings",
|
|
98
104
|
"recompress_spatialdata",
|
|
99
105
|
"register_codecs",
|
|
@@ -128,8 +128,10 @@ def _add_pyramid_args(parser: argparse.ArgumentParser, *, with_flag: bool) -> No
|
|
|
128
128
|
"--pyramid",
|
|
129
129
|
action="store_true",
|
|
130
130
|
help=(
|
|
131
|
-
"
|
|
132
|
-
"level
|
|
131
|
+
"Before recompressing, add pyramid levels to any image whose coarsest "
|
|
132
|
+
"level is larger than --pyramid-min-size: a single-resolution image is "
|
|
133
|
+
"built from full resolution, a shallow pyramid is extended from its "
|
|
134
|
+
"coarsest level. Implied by the other --pyramid-* options."
|
|
133
135
|
),
|
|
134
136
|
)
|
|
135
137
|
parser.add_argument(
|
|
@@ -159,18 +161,36 @@ def _add_pyramid_args(parser: argparse.ArgumentParser, *, with_flag: bool) -> No
|
|
|
159
161
|
parser.add_argument(
|
|
160
162
|
"--pyramid-force",
|
|
161
163
|
action="store_true",
|
|
162
|
-
help="Rebuild
|
|
164
|
+
help="Rebuild every pyramid from full resolution, including ones already deep enough",
|
|
163
165
|
)
|
|
164
166
|
|
|
165
167
|
|
|
166
168
|
def _images_add_pyramid(args: argparse.Namespace) -> None:
|
|
167
|
-
from .pyramids import add_pyramids
|
|
169
|
+
from .pyramids import add_pyramids, add_pyramids_in_place
|
|
168
170
|
|
|
169
171
|
def command() -> dict[str, Any]:
|
|
172
|
+
image_keys = args.image_key.split(",") if args.image_key else None
|
|
173
|
+
if args.in_place:
|
|
174
|
+
if args.dest is not None:
|
|
175
|
+
raise WriterCommandError("--in-place takes no destination; omit DEST.")
|
|
176
|
+
if args.overwrite or args.pyramid_force:
|
|
177
|
+
raise WriterCommandError(
|
|
178
|
+
"--in-place only adds levels, so --overwrite and --pyramid-force do not apply."
|
|
179
|
+
)
|
|
180
|
+
return add_pyramids_in_place(
|
|
181
|
+
args.source,
|
|
182
|
+
image_keys=image_keys,
|
|
183
|
+
include_labels=args.labels,
|
|
184
|
+
levels=args.pyramid_levels,
|
|
185
|
+
downscale=args.pyramid_downscale,
|
|
186
|
+
min_size=args.pyramid_min_size,
|
|
187
|
+
).manifest
|
|
188
|
+
if args.dest is None:
|
|
189
|
+
raise WriterCommandError("Give a destination store, or pass --in-place.")
|
|
170
190
|
return add_pyramids(
|
|
171
191
|
args.source,
|
|
172
192
|
args.dest,
|
|
173
|
-
image_keys=
|
|
193
|
+
image_keys=image_keys,
|
|
174
194
|
include_labels=args.labels,
|
|
175
195
|
levels=args.pyramid_levels,
|
|
176
196
|
downscale=args.pyramid_downscale,
|
|
@@ -265,16 +285,31 @@ def _add_images_commands(subparsers: argparse._SubParsersAction) -> None:
|
|
|
265
285
|
|
|
266
286
|
add_pyramid = image_commands.add_parser(
|
|
267
287
|
"add-pyramid",
|
|
268
|
-
help="give
|
|
288
|
+
help="give rasters the overview levels they lack",
|
|
269
289
|
description=(
|
|
270
|
-
"Copy a store and add
|
|
271
|
-
"
|
|
272
|
-
"
|
|
273
|
-
"
|
|
290
|
+
"Copy a store and add pyramid levels to rasters whose coarsest level is "
|
|
291
|
+
"larger than --pyramid-min-size (or that have fewer than --pyramid-levels "
|
|
292
|
+
"levels), keeping any levels they already have, so a zoomed-out browser "
|
|
293
|
+
"view does not have to read full-resolution chunks. Codecs are left "
|
|
294
|
+
"unchanged; use 'images recompress --pyramid' to do both in one pass. "
|
|
295
|
+
"With --in-place the levels are appended to SOURCE itself and nothing is "
|
|
296
|
+
"copied or rewritten."
|
|
274
297
|
),
|
|
275
298
|
)
|
|
276
299
|
add_pyramid.add_argument("source", help="Existing SpatialData Zarr store")
|
|
277
|
-
add_pyramid.add_argument(
|
|
300
|
+
add_pyramid.add_argument(
|
|
301
|
+
"dest",
|
|
302
|
+
nargs="?",
|
|
303
|
+
help="Output SpatialData Zarr store (omit with --in-place)",
|
|
304
|
+
)
|
|
305
|
+
add_pyramid.add_argument(
|
|
306
|
+
"--in-place",
|
|
307
|
+
action="store_true",
|
|
308
|
+
help=(
|
|
309
|
+
"Append the missing levels to SOURCE instead of writing a copy. Existing "
|
|
310
|
+
"arrays are not touched; new levels use plain zarr codecs."
|
|
311
|
+
),
|
|
312
|
+
)
|
|
278
313
|
add_pyramid.add_argument(
|
|
279
314
|
"--image-key",
|
|
280
315
|
metavar="KEYS",
|
{spatialdata_js_util-0.1.0 → spatialdata_js_util-0.2.0}/src/spatialdata_js_util/codecs/backends.py
RENAMED
|
@@ -35,6 +35,7 @@ from __future__ import annotations
|
|
|
35
35
|
|
|
36
36
|
import json
|
|
37
37
|
import os
|
|
38
|
+
import warnings
|
|
38
39
|
from functools import lru_cache
|
|
39
40
|
from pathlib import Path
|
|
40
41
|
from typing import Any, Protocol
|
|
@@ -205,6 +206,17 @@ def resolve_backend() -> Htj2kBackend | None:
|
|
|
205
206
|
# so it is trusted on availability; every other backend must prove it.
|
|
206
207
|
if name == BACKEND_OPENJPH_WASM:
|
|
207
208
|
if _BACKENDS[name].available():
|
|
209
|
+
if not requested:
|
|
210
|
+
# Reaching here means `imagecodecs` failed, usually because the
|
|
211
|
+
# installed release has no HTJ2K for this platform. Encoding
|
|
212
|
+
# still works, but only while Node.js is on PATH.
|
|
213
|
+
warnings.warn(
|
|
214
|
+
"imagecodecs has no usable HTJ2K; falling back to the openjph-wasm "
|
|
215
|
+
"backend, which needs Node.js. Upgrade imagecodecs to the version "
|
|
216
|
+
"spatialdata-js-util requires to encode without Node.",
|
|
217
|
+
RuntimeWarning,
|
|
218
|
+
stacklevel=2,
|
|
219
|
+
)
|
|
208
220
|
return _BACKENDS[name]
|
|
209
221
|
elif backend_passes_probe(name):
|
|
210
222
|
return _BACKENDS[name]
|
|
@@ -212,9 +224,12 @@ def resolve_backend() -> Htj2kBackend | None:
|
|
|
212
224
|
|
|
213
225
|
|
|
214
226
|
def reset_backend_cache() -> None:
|
|
215
|
-
"""Clear memoized backend selection (used by tests that flip the env var)."""
|
|
227
|
+
"""Clear memoized backend selection (used by tests that flip the env var or PATH)."""
|
|
228
|
+
from .htj2k_wasm import wasm_encode_available
|
|
229
|
+
|
|
216
230
|
resolve_backend.cache_clear()
|
|
217
231
|
backend_passes_probe.cache_clear()
|
|
232
|
+
wasm_encode_available.cache_clear()
|
|
218
233
|
|
|
219
234
|
|
|
220
235
|
def require_backend() -> Htj2kBackend:
|
|
@@ -614,7 +614,7 @@ def _stage_pyramids(
|
|
|
614
614
|
) -> tuple[list[dict[str, Any]], dict[str, Path]]:
|
|
615
615
|
"""Build pyramids for *image_keys* into a staging store.
|
|
616
616
|
|
|
617
|
-
Returns the per-raster reports and, for each key that was
|
|
617
|
+
Returns the per-raster reports and, for each key that was given levels,
|
|
618
618
|
the root to read its levels from.
|
|
619
619
|
"""
|
|
620
620
|
import spatialdata as sd
|
|
@@ -635,7 +635,7 @@ def _stage_pyramids(
|
|
|
635
635
|
roots = {
|
|
636
636
|
report["path"].split("/", 1)[1]: staging_path
|
|
637
637
|
for report in reports
|
|
638
|
-
if report.get("action")
|
|
638
|
+
if report.get("action") in ("rebuilt", "extended")
|
|
639
639
|
}
|
|
640
640
|
return reports, roots
|
|
641
641
|
|
|
@@ -675,10 +675,11 @@ def recompress_spatialdata(
|
|
|
675
675
|
shapes, points, and unconfigured rasters intact without loading the whole
|
|
676
676
|
object.
|
|
677
677
|
|
|
678
|
-
With *pyramid*, any configured image
|
|
679
|
-
given
|
|
680
|
-
|
|
681
|
-
|
|
678
|
+
With *pyramid*, any configured image whose coarsest level is larger than
|
|
679
|
+
*pyramid_min_size* is given the levels it lacks before it is recompressed,
|
|
680
|
+
so single-resolution or shallow-pyramid acquisition output becomes
|
|
681
|
+
browser-ready in one pass. Images whose pyramid is already deep enough are
|
|
682
|
+
left alone, and *pyramid_force* rebuilds them from full resolution. See
|
|
682
683
|
`spatialdata_js_util.pyramids` for how levels are chosen.
|
|
683
684
|
"""
|
|
684
685
|
|