lodstone 0.1.0a0__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 (54) hide show
  1. lodstone-0.1.0a0/.github/workflows/release.yml +47 -0
  2. lodstone-0.1.0a0/.github/workflows/test.yml +45 -0
  3. lodstone-0.1.0a0/.gitignore +7 -0
  4. lodstone-0.1.0a0/CHANGELOG.md +13 -0
  5. lodstone-0.1.0a0/LICENSE +21 -0
  6. lodstone-0.1.0a0/PKG-INFO +415 -0
  7. lodstone-0.1.0a0/README.md +361 -0
  8. lodstone-0.1.0a0/RELEASING.md +36 -0
  9. lodstone-0.1.0a0/examples/_ndv_block_overlay.py +134 -0
  10. lodstone-0.1.0a0/examples/napari_ome_zarr.py +63 -0
  11. lodstone-0.1.0a0/examples/napari_zebrahub.py +166 -0
  12. lodstone-0.1.0a0/examples/ndv_dense.py +232 -0
  13. lodstone-0.1.0a0/examples/stream_to_numpy.py +43 -0
  14. lodstone-0.1.0a0/pyproject.toml +48 -0
  15. lodstone-0.1.0a0/src/lodstone/__init__.py +123 -0
  16. lodstone-0.1.0a0/src/lodstone/adapters/__init__.py +21 -0
  17. lodstone-0.1.0a0/src/lodstone/adapters/napari.py +266 -0
  18. lodstone-0.1.0a0/src/lodstone/adapters/ndv.py +522 -0
  19. lodstone-0.1.0a0/src/lodstone/chunks.py +147 -0
  20. lodstone-0.1.0a0/src/lodstone/composition.py +143 -0
  21. lodstone-0.1.0a0/src/lodstone/datasets/__init__.py +453 -0
  22. lodstone-0.1.0a0/src/lodstone/datasets/generative.py +373 -0
  23. lodstone-0.1.0a0/src/lodstone/diagnostics.py +99 -0
  24. lodstone-0.1.0a0/src/lodstone/geometry.py +198 -0
  25. lodstone-0.1.0a0/src/lodstone/model.py +429 -0
  26. lodstone-0.1.0a0/src/lodstone/planner.py +722 -0
  27. lodstone-0.1.0a0/src/lodstone/py.typed +1 -0
  28. lodstone-0.1.0a0/src/lodstone/resident.py +411 -0
  29. lodstone-0.1.0a0/src/lodstone/runtime.py +107 -0
  30. lodstone-0.1.0a0/src/lodstone/source.py +21 -0
  31. lodstone-0.1.0a0/src/lodstone/sources/__init__.py +12 -0
  32. lodstone-0.1.0a0/src/lodstone/sources/array.py +141 -0
  33. lodstone-0.1.0a0/src/lodstone/sources/ome_zarr.py +258 -0
  34. lodstone-0.1.0a0/src/lodstone/sources/zarr.py +36 -0
  35. lodstone-0.1.0a0/src/lodstone/stream.py +892 -0
  36. lodstone-0.1.0a0/src/lodstone/target.py +91 -0
  37. lodstone-0.1.0a0/src/lodstone/testing.py +55 -0
  38. lodstone-0.1.0a0/src/lodstone/virtual.py +716 -0
  39. lodstone-0.1.0a0/tests/conftest.py +45 -0
  40. lodstone-0.1.0a0/tests/test_chunks.py +65 -0
  41. lodstone-0.1.0a0/tests/test_composition.py +74 -0
  42. lodstone-0.1.0a0/tests/test_datasets.py +213 -0
  43. lodstone-0.1.0a0/tests/test_diagnostics.py +52 -0
  44. lodstone-0.1.0a0/tests/test_generative.py +113 -0
  45. lodstone-0.1.0a0/tests/test_geometry.py +96 -0
  46. lodstone-0.1.0a0/tests/test_model.py +61 -0
  47. lodstone-0.1.0a0/tests/test_napari_adapter.py +163 -0
  48. lodstone-0.1.0a0/tests/test_ndv_adapter.py +352 -0
  49. lodstone-0.1.0a0/tests/test_ome_zarr.py +151 -0
  50. lodstone-0.1.0a0/tests/test_planner.py +595 -0
  51. lodstone-0.1.0a0/tests/test_resident.py +236 -0
  52. lodstone-0.1.0a0/tests/test_stream.py +834 -0
  53. lodstone-0.1.0a0/tests/test_virtual.py +333 -0
  54. lodstone-0.1.0a0/uv.lock +1184 -0
@@ -0,0 +1,47 @@
1
+ name: release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
15
+ with:
16
+ persist-credentials: false
17
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
18
+ with:
19
+ python-version: "3.13"
20
+ enable-cache: false
21
+ - name: Verify tag matches package version
22
+ shell: bash
23
+ run: |
24
+ package_version="$(uv version --short)"
25
+ test "${GITHUB_REF_NAME}" = "v${package_version}"
26
+ - run: uv build
27
+ - run: uvx twine check dist/*
28
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
29
+ with:
30
+ name: python-package-distributions
31
+ path: dist/
32
+ if-no-files-found: error
33
+
34
+ publish:
35
+ needs: build
36
+ runs-on: ubuntu-latest
37
+ environment:
38
+ name: pypi
39
+ url: https://pypi.org/p/lodstone
40
+ permissions:
41
+ id-token: write
42
+ steps:
43
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
44
+ with:
45
+ name: python-package-distributions
46
+ path: dist/
47
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,45 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ${{ matrix.os }}
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ os: [ubuntu-latest]
18
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
19
+ include:
20
+ - os: macos-latest
21
+ python-version: "3.13"
22
+ - os: windows-latest
23
+ python-version: "3.13"
24
+ steps:
25
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
26
+ with:
27
+ persist-credentials: false
28
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+ - run: uv run --extra test pytest
32
+
33
+ quality:
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
37
+ with:
38
+ persist-credentials: false
39
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6
40
+ with:
41
+ python-version: "3.13"
42
+ - run: uv run --group dev ruff check .
43
+ - run: uv run --group dev ruff format --check .
44
+ - run: uv run --extra ome-zarr --group dev pyright src
45
+ - run: uv build
@@ -0,0 +1,7 @@
1
+ .DS_Store
2
+ .pytest_cache/
3
+ .ruff_cache/
4
+ .venv/
5
+ __pycache__/
6
+ *.egg-info/
7
+ dist/
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ Lodstone follows semantic versioning. Viewer adapters remain provisional during
4
+ the 0.1 alpha series and may change between prereleases.
5
+
6
+ ## 0.1.0a0 - 2026-08-23
7
+
8
+ - Add renderer-neutral multiscale chunk planning and progressive streaming.
9
+ - Add bounded caches, cancellation, request reprioritization, and diagnostics.
10
+ - Add dense resident windows with coarse-to-fine composition.
11
+ - Add NumPy, Zarr, and OME-Zarr sources.
12
+ - Add experimental napari and ndv adapters.
13
+ - Support shared runtimes across layers and channels.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kyle I S Harrington
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,415 @@
1
+ Metadata-Version: 2.5
2
+ Name: lodstone
3
+ Version: 0.1.0a0
4
+ Summary: Renderer-neutral multiscale chunk planning and streaming
5
+ Project-URL: Repository, https://github.com/kephale/lodstone
6
+ Project-URL: Issues, https://github.com/kephale/lodstone/issues
7
+ Project-URL: Changelog, https://github.com/kephale/lodstone/blob/main/CHANGELOG.md
8
+ Author: Kyle I S Harrington
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Kyle I S Harrington
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Programming Language :: Python :: 3
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Programming Language :: Python :: 3.13
37
+ Classifier: Programming Language :: Python :: 3.14
38
+ Classifier: Typing :: Typed
39
+ Requires-Python: >=3.11
40
+ Requires-Dist: numpy>=1.26
41
+ Provides-Extra: datasets
42
+ Requires-Dist: zarr>=3; extra == 'datasets'
43
+ Provides-Extra: ome-zarr
44
+ Requires-Dist: aiohttp>=3.10; extra == 'ome-zarr'
45
+ Requires-Dist: fsspec>=2024.2; extra == 'ome-zarr'
46
+ Requires-Dist: zarr>=3; extra == 'ome-zarr'
47
+ Provides-Extra: test
48
+ Requires-Dist: dask[array]>=2022.1.1; extra == 'test'
49
+ Requires-Dist: pytest>=8; extra == 'test'
50
+ Requires-Dist: zarr>=3; extra == 'test'
51
+ Provides-Extra: zarr
52
+ Requires-Dist: zarr>=3; extra == 'zarr'
53
+ Description-Content-Type: text/markdown
54
+
55
+ # Lodstone
56
+
57
+ Lodstone is a renderer-neutral engine for view-dependent streaming of
58
+ multiscale chunked arrays. It turns a multiscale `Source` and a camera `View`
59
+ into progressive array `Update`s accepted by a viewer-specific `Target`.
60
+
61
+ It is intended to be shared by clients such as ChimeraX, napari, and ndv.
62
+ Lodstone does not create windows, textures, shaders, layers, or viewer models.
63
+
64
+ Lodstone is currently an alpha. The renderer-neutral core is the compatibility
65
+ boundary for the 0.1 series; viewer adapters are experimental and may change
66
+ between prereleases as their host applications establish public streaming APIs.
67
+
68
+ ```text
69
+ Source + View + Target
70
+
71
+ Planner
72
+
73
+ Stream
74
+ ```
75
+
76
+ ## Installation
77
+
78
+ ```bash
79
+ pip install lodstone
80
+ pip install "lodstone[ome-zarr]" # OME-Zarr and remote stores
81
+ pip install "lodstone[datasets]" # generative and local Zarr examples
82
+ ```
83
+
84
+ Lodstone supports Python 3.11 through 3.14. NumPy is its only required runtime
85
+ dependency.
86
+
87
+ ## Example
88
+
89
+ ```python
90
+ import numpy as np
91
+
92
+ from lodstone import Layout, Stream, View
93
+ from lodstone.sources import ArrayPyramidSource
94
+ from lodstone.testing import RecordingTarget
95
+
96
+ fine = np.arange(256 * 256, dtype=np.uint16).reshape(256, 256)
97
+ coarse = fine[::4, ::4]
98
+
99
+ fine_transform = np.eye(3)
100
+ coarse_transform = np.diag([4.0, 4.0, 1.0])
101
+ source = ArrayPyramidSource(
102
+ [fine, coarse],
103
+ axes=("y", "x"),
104
+ transforms=[fine_transform, coarse_transform],
105
+ chunks=[(64, 64), (32, 32)],
106
+ )
107
+
108
+ # A real integration implements this protocol to upload updates into its
109
+ # renderer. RecordingTarget is useful for tests and examples.
110
+ target = RecordingTarget(Layout(kind="tiled", block_shape=(64, 64)))
111
+
112
+ # Map the 256 by 256 world extent into clip coordinates [-1, 1].
113
+ world_to_clip = np.array(
114
+ [
115
+ [2 / 256, 0, 0, -1],
116
+ [0, 2 / 256, 0, -1],
117
+ [0, 0, 1, 0],
118
+ [0, 0, 0, 1],
119
+ ],
120
+ dtype=float,
121
+ )
122
+ view = View(
123
+ displayed_axes=(0, 1),
124
+ index=(None, None),
125
+ viewport=(800, 800),
126
+ world_to_clip=world_to_clip,
127
+ )
128
+
129
+ with Stream(source, target) as stream:
130
+ stream.update(view)
131
+ ```
132
+
133
+ Adapters that already have a renderer-specific region policy can call
134
+ ``stream.submit(view, plan)``. This executes the supplied regions exactly while
135
+ retaining Lodstone's cancellation, native-chunk cache, batching, pacing, and
136
+ stale-generation rejection. This is how the napari adapter preserves PR
137
+ #9067's camera-bounded 3-D loading behavior.
138
+
139
+ ``Planner.plan_region(...)`` provides an incremental migration path for those
140
+ adapters: the viewer may continue choosing the target level and bounded region
141
+ while Lodstone owns transform-aware ladder mapping, native-grid enumeration,
142
+ memory and axis limits, cache filtering, and delivery priority. Integrations
143
+ can compare its stable ``PlanTrace`` against an established planner and retain
144
+ their fallback whenever geometry differs.
145
+
146
+ Viewer integrations normally provide three small pieces:
147
+
148
+ 1. Camera and dimension state converted into `View`.
149
+ 2. A `Target` that applies array updates to the renderer.
150
+ 3. A dispatcher that runs target methods on the viewer's render/UI thread.
151
+
152
+ ```python
153
+ stream = Stream(
154
+ source,
155
+ target,
156
+ dispatch=run_on_viewer_thread,
157
+ )
158
+ ```
159
+
160
+ Viewers with several layers or channels should share a `Runtime`. It owns one
161
+ asynchronous scheduler and a bounded CPU staging pool, while each stream keeps
162
+ its independent request state. Heavy `stage`, `stage_prepare`, and
163
+ `stage_phase` work runs in that pool instead of blocking I/O and cancellation:
164
+
165
+ ```python
166
+ from lodstone import Runtime, Stream
167
+
168
+ runtime = Runtime(compute_workers=2)
169
+ streams = [
170
+ Stream(source, target, runtime=runtime, dispatch=run_on_viewer_thread)
171
+ for source, target in channels
172
+ ]
173
+ # Close streams first, then the shared runtime.
174
+ ```
175
+
176
+ ## napari adapter
177
+
178
+ The optional adapter uses the rendering architecture from napari PR #9067: a
179
+ single multiscale layer backed by bounded resident intervals, camera-selected
180
+ 2-D or 3-D tiles, and partial GPU texture uploads. It passes the source's
181
+ original lazy arrays through, so Zarr data is not materialized into dense
182
+ NumPy levels:
183
+
184
+ ```python
185
+ import napari
186
+ from lodstone.adapters.napari import NapariController
187
+ from lodstone.sources import OMEZarrSource
188
+
189
+ source = OMEZarrSource.open("https://example.org/image.zarr")
190
+ viewer = napari.Viewer()
191
+ controller = NapariController(viewer, source, fixed_index={0: 0})
192
+ napari.run()
193
+ controller.close()
194
+ ```
195
+
196
+ Pass ``layer_type="labels"`` for an integer segmentation pyramid. Image and
197
+ Labels layers use the same camera-driven Lodstone planner, cancellation,
198
+ caching, and delivery pipeline. Regular and rectilinear native chunk grids
199
+ are preserved rather than approximated by a single nominal chunk size.
200
+
201
+ This currently requires the `lodstone-integration` napari branch based on PR
202
+ #9067. Run
203
+ `examples/napari_ome_zarr.py` for a two-channel remote example. The core
204
+ package still has no napari or Qt dependency.
205
+
206
+ ## ndv adapter
207
+
208
+ The ndv adapter presents immutable dense phase snapshots through ndv's common
209
+ `ArrayCanvas` API, so the same target works with its VisPy and pygfx renderers.
210
+ Create an empty `ndv.ArrayViewer`, pass it and a source to `NDVController`, then
211
+ submit renderer-neutral `View` snapshots:
212
+
213
+ ```python
214
+ import ndv
215
+
216
+ from lodstone.adapters.ndv import NDVController
217
+
218
+ viewer = ndv.ArrayViewer()
219
+ controller = NDVController(viewer, source)
220
+ controller.update(view)
221
+ viewer.show()
222
+ ndv.run_app()
223
+ controller.close()
224
+ ```
225
+
226
+ The initial adapter supports translated dense 2-D and 3-D windows, hidden-axis
227
+ selections, camera-driven replanning, progressive phase replacement, independent
228
+ per-image world transforms, and shared runtimes. See `examples/ndv_dense.py`.
229
+ These capabilities currently require ndv's `lodstone-integration` branch until
230
+ its camera, dispatch, and image-transform APIs are released.
231
+
232
+ `examples/napari_zebrahub.py` opens one lazy timepoint from the public
233
+ ZSNS001 Zebrahub light-sheet series in 3-D. Its approximately 32 MiB native
234
+ chunks make it a useful stress test for cancellation, interaction holds, and
235
+ GPU upload pacing. The example exposes `--tile-mib`, `--interval-mib`, and
236
+ `--rate-mib` for tuning those constraints. Pass `--trace-chunks` to report the
237
+ exact desired/wanted tile counts alongside unique native chunks, cache hits,
238
+ joined in-flight reads, actual source reads, and evictions for every pass.
239
+ Pass `--diagnostic-levels` to perform the same real source reads while replacing
240
+ the returned pixels with solid categorical labels: magenta is missing content,
241
+ green is L0, yellow is L1, orange is L2, and deeper levels use additional
242
+ stable colors. This makes incomplete viewport coverage visible independently of
243
+ the image's contrast or texture values.
244
+
245
+ ## Public concepts
246
+
247
+ - **Source** — pyramid metadata and asynchronous regional reads.
248
+ - **View** — displayed axes, hidden-axis selections, viewport, and camera matrix.
249
+ - **Target** — desired dense/tiled/bricked layout and update delivery.
250
+ - **Planner** — deterministic visible-tile and LOD selection.
251
+ - **PlanCoverage / PlanDelta** — stable coverage identity plus retained,
252
+ requested, reprioritized, and released work across camera changes.
253
+ - **Stream** — cancellation, priorities, native-chunk reuse, CPU caching,
254
+ batching, progressive delivery, and stale-generation rejection.
255
+ - **Composition** — transform-aware nearest-neighbor backdrop sampling and
256
+ unloaded-chunk filling for bounded dense targets.
257
+ - **VirtualData** — a lazy full-shape array view backed by one movable,
258
+ chunk-aligned in-memory interval; `MultiScaleVirtualData` coordinates these
259
+ intervals and coarse-to-fine backdrop composition across pyramid levels.
260
+
261
+ Storage chunks and display tiles are deliberately distinct. A target may ask
262
+ for 32-cubed bricks while the Zarr source stores 16 by 64 by 64 chunks.
263
+ Lodstone reads each overlapping native chunk once and assembles the requested
264
+ display updates from its decoded cache.
265
+
266
+ Progressive planning starts at the coarsest level by default. Renderer
267
+ integrations can set `Planner(max_initial_voxel_footprint=...)` to choose the
268
+ coarsest initial level whose projected voxels stay within that many screen
269
+ pixels; the normal target level and napari's default behavior are unchanged.
270
+
271
+ `stream.diagnostics` separates renderer tiles from native storage activity for
272
+ the current or most recent generation. `stream.cache_events` records recent
273
+ `queued`, `loading`, `ready`, `failed`, and `evicted` transitions, while
274
+ `stream.chunk_states` exposes the latest state per native chunk. Native chunks
275
+ required by a delivery batch remain pinned until all its display regions have
276
+ been assembled, preventing mid-request eviction and avoidable rereads.
277
+
278
+ ## Source adapters
279
+
280
+ `ArrayPyramidSource` accepts NumPy, Dask, Zarr, or other indexable array-like
281
+ levels. `ZarrPyramidSource` opens explicitly named arrays in a Zarr group.
282
+ `OMEZarrSource` discovers nested pyramid levels, axes, and per-level scale and
283
+ translation transforms from OME-Zarr v0.1-v0.5 metadata. It also supports bare
284
+ array pyramids, bounded caches, remote storage options, level limits, and lazy
285
+ fixed-axis or singleton-axis selection.
286
+
287
+ The public chunk-grid utilities normalize NumPy, Dask, regular Zarr, and
288
+ rectilinear Zarr metadata into one exact grid used consistently by sources,
289
+ planners, resident buffers, and viewer adapters.
290
+
291
+ Zarr remains a lazy storage source. NumPy arrays are only the concrete buffers
292
+ delivered for requested regions.
293
+
294
+ `lodstone.datasets` provides reusable Mandelbrot and Mandelbulb pyramids,
295
+ including RGB variants, a local multiscale Zarr builder, and a convenience
296
+ loader for local or remote OME-Zarr data. These fixtures are renderer-neutral
297
+ and are shared by integration examples and LodStone's own source tests.
298
+
299
+ ## Target contract
300
+
301
+ ```python
302
+ class Target:
303
+ def layout(self, view, pyramid) -> Layout: ...
304
+ def apply(self, updates) -> None: ...
305
+ def discard(self, keys) -> None: ...
306
+ def redraw(self) -> None: ...
307
+ ```
308
+
309
+ Targets with bounded resident windows may additionally implement
310
+ `prepare(view, plan)` and `complete(view, plan)`. Preparation runs on the
311
+ viewer thread before any updates for a pass; completion runs after refinement
312
+ and stale-residency retirement. A plan exposes both its complete `desired`
313
+ tile ladder and the cache-filtered `wanted` reads. Interactive viewers can
314
+ call `stream.pause()` and `stream.resume()` without discarding the active pass.
315
+ `bytes_per_second` can pace aggregate source reads when decoding or remote I/O
316
+ would otherwise compete with interaction and rendering.
317
+
318
+ `prepare` may return a residency lease with dynamic `available_keys` and
319
+ `pending_keys` sets plus `release(keys)`. A lease confirms which target storage
320
+ survives replanning, allowing the stream to retain delivered overlap while it
321
+ keeps loading native chunks shared by the old and new request. Queued work is
322
+ rebuilt in the newest priority order and work outside the new coverage is
323
+ canceled. Legacy targets that return no lease retain conservative pass
324
+ replacement behavior. `stream.delta` exposes the latest `PlanDelta`.
325
+
326
+ Viewers may attach `InteractionState` to a `View` to describe camera motion
327
+ and angular, translation, and zoom velocity. Existing integrations can omit
328
+ it and retain their current policy.
329
+
330
+ Targets that need an atomic presentation point between coarse-to-fine stages
331
+ may also implement `phase_complete(view, plan, phase)`. The hook is optional;
332
+ existing targets continue to receive the same prepare, apply, complete, and
333
+ redraw calls.
334
+
335
+ Dense targets can use `ResidentArrays` to avoid allocating complete pyramid
336
+ levels. It stages one full-ND bounding window per desired level, preserves
337
+ overlapping content when the camera moves, translates absolute updates into
338
+ window-relative writes, and retires coarse/replaced storage on completion.
339
+ The viewer still owns the corresponding grid, texture, or volume objects:
340
+
341
+ ```python
342
+ from lodstone import Layout, ResidentArrays, ResidentLease
343
+
344
+ resident = ResidentArrays(source.pyramid, compose=True)
345
+
346
+
347
+ def layout(view, pyramid):
348
+ return Layout(kind="dense", memory_limit=512 * 1024**2, squeeze_hidden=False)
349
+
350
+
351
+ def prepare(view, plan):
352
+ transition = resident.prepare(plan)
353
+ # Create renderer resources for transition.prepared and remove
354
+ # transition.retired resources.
355
+ desired = plan.desired or plan.wanted
356
+ return ResidentLease(resident, frozenset(tile.key for tile in desired))
357
+
358
+
359
+ def apply(updates):
360
+ for change in resident.apply(updates):
361
+ # Patch or invalidate change.regions in the renderer resource.
362
+ pass
363
+
364
+
365
+ def complete(view, plan):
366
+ transition = resident.complete(plan)
367
+ # Present resident.active[plan.target_level] and retire old resources.
368
+ ```
369
+
370
+ With `compose=True`, coarse updates initialize and repair unloaded native
371
+ chunks in finer pending windows using the pyramid transforms. Directly loaded
372
+ fine chunks are never overwritten. Leaving composition disabled preserves the
373
+ original fill-value and same-level overlap behavior.
374
+
375
+ The initial expected layouts are:
376
+
377
+ | Client | Typical layout |
378
+ | --- | --- |
379
+ | ChimeraX | dense, uniform LOD |
380
+ | napari | tiled |
381
+ | ndv | dense initially |
382
+
383
+ Lodstone deliberately stops before physical GPU allocation. The target owns
384
+ textures, double buffering, shader indirection, and renderer invalidation.
385
+
386
+ ## Viewer compatibility
387
+
388
+ The first alpha is intended for integration development. It does not make the
389
+ streaming paths available in unmodified stable releases of every viewer.
390
+
391
+ | Client | Initial support | Required host version | Status |
392
+ | --- | --- | --- | --- |
393
+ | ChimeraX OME-Zarr | 3-D images, channels, one selected timepoint | `chimerax-ome-zarr` PR 22 | Experimental |
394
+ | napari | 2-D/3-D Image and Labels layers | napari PR 34 based on PR 9067 | Experimental |
395
+ | ndv + VisPy | 2-D/3-D dense clipmaps and camera replanning | ndv `lodstone-integration` branch | Reference ndv backend |
396
+ | ndv + PyGFX | Same renderer-neutral data path | ndv `lodstone-integration` branch | Experimental visual parity |
397
+
398
+ Integrations should pin an exact Lodstone prerelease. Compatibility is only
399
+ claimed for combinations exercised by the integration's native tests and smoke
400
+ tests; adapters remain provisional throughout the 0.1 alpha series.
401
+
402
+ ## Development
403
+
404
+ ```bash
405
+ uv run --extra test pytest
406
+ uv run --group dev ruff check .
407
+ uv run --group dev pyright src
408
+ ```
409
+
410
+ The test suite is network-independent. Remote opening and reading has also
411
+ been checked against the EBI IDR OME-Zarr v0.4 store used by
412
+ `chimerax-ome-zarr`.
413
+
414
+ Release maintainers should follow [`RELEASING.md`](RELEASING.md). Changes are
415
+ recorded in [`CHANGELOG.md`](CHANGELOG.md).