Skip to content

Commit e6bc0cf

Browse files
authored
Merge pull request #27 from CSSFrancis/release/v0.2.0
Release v0.2.0
2 parents e49d29c + d4d0e1b commit e6bc0cf

13 files changed

Lines changed: 119 additions & 66 deletions

‎CHANGELOG.rst‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,108 @@ Fragment files in ``upcoming_changes/`` are assembled into this file by
1010

1111
.. towncrier release notes start
1212
13+
0.2.0 (2026-07-10)
14+
==================
15+
16+
New Features
17+
------------
18+
19+
- 2-D scalar images can now render on the **GPU via WebGPU** (``imshow(...,
20+
gpu="auto"|True|False)``): the image uploads as an R8 texture and a WGSL fragment
21+
shader applies the colormap LUT + contrast (clim) in one draw, replacing the
22+
per-pixel JavaScript colormap loop. Large images (≳1 megapixel) take the GPU path
23+
automatically; everything below the threshold, RGB images, ``gpu=False``, and any
24+
device without WebGPU keep the identical Canvas2D path. ``plot.gpu_active`` reports
25+
which path ran. Verified on an NVIDIA Pascal GPU.
26+
- Arbitrarily large images can now display through **tile mode**
27+
(``imshow(..., tile="auto")``): the figure shows a downsampled overview as its
28+
base and, after each zoom/pan settles, samples a high-resolution detail tile of
29+
just the visible region at panel resolution — deep zooms stay crisp without
30+
ever shipping the full-resolution frame. ``Plot2D.enable_tile`` /
31+
``update_tile_source`` swap the underlying frame while the zoom and
32+
subselection persist (live-data contract), and a pluggable ``TileBackend``
33+
(default: a fast vectorised numpy box-mean) lets out-of-core or GPU sources
34+
own the sampling.
35+
- Markers gained a ``clip_display`` option controlling whether they draw
36+
outside the current axes view or are clipped to it.
37+
- ``set_extent`` now updates the axes state (calibrated units / scale bar), so
38+
applying a calibrated extent after figure creation renders labelled axes
39+
instead of bare pixels.
40+
- Regular ``pcolormesh`` meshes are detected and rasterized to an image for
41+
display (fast IPF-style heatmaps) instead of drawing per-quad.
42+
43+
44+
Bug Fixes
45+
---------
46+
47+
- Fixed a display freeze under the Electron binary pixel transport: the routing
48+
layer stripped the pixel key out of the slimmed geom JSON, so the renderer's
49+
"unchanged → skip re-upload" caches (Canvas2D blit cache, WebGPU texture,
50+
overlay-mask cache) fell back to a 4-sampled-byte fingerprint of the buffer —
51+
two frames differing anywhere else collided and the display stayed frozen on
52+
the old frame (seen as a stale overview after a movie scrub). The slimmed geom
53+
now carries a small ``\x00bin:<checksum>`` content token under the pixel key,
54+
binary buffers are additionally stamped with an arrival sequence as a fallback
55+
key, and the overlay-mask draw path now reads the binary byte side-channel
56+
(it previously only decoded base64, so masks never displayed over the binary
57+
transport).
58+
- Fixed the WebGPU 2-D image path sampling a vertically MIRRORED window when the
59+
view was panned off-centre: the shader applied a global ``1 - v`` flip after
60+
interpolating the ``[v0, v1]`` uv window, which sampled ``[1-v1, 1-v0]``
61+
instead — correct only for a full or vertically-centred view. Symptoms: pan-y
62+
moved the image the wrong way on GPU-rendered panels, and markers/widgets
63+
(drawn by the shared Canvas2D overlay transform, which was always correct)
64+
appeared detached from the image features they marked. The base and
65+
detail-tile passes share the shader, so both are fixed. GPU-vs-CPU screenshot
66+
parity tests (zoom, pan, markers, widgets, detail tile) now run on real WebGPU
67+
in headless Chromium (``channel="chromium"`` + ``--enable-unsafe-webgpu``) and
68+
skip on machines with no adapter.
69+
- ``Plot2D.set_data`` no longer makes a float64 copy of every incoming frame.
70+
The float64 cast now happens lazily in the ``.data`` property (the only reader),
71+
so a frame stream — e.g. scrubbing an in-situ movie — keeps the source dtype and
72+
skips a ~12 ms float64 copy of a 4k frame per tick. ``.data`` still returns a
73+
read-only float64 copy, unchanged for callers.
74+
- The Electron binary pixel transport now ships the RAW uint8 image bytes end to
75+
end, instead of base64-encoding them in ``set_data`` only to base64-decode them
76+
straight back in the routing layer. ``Plot2D.set_data`` stashes the raw bytes on
77+
the Figure's ``_raw_pixels`` side-table and leaves a tiny content-checksum
78+
change-token in ``image_b64``; ``_electron._route_change`` ships those bytes to a
79+
PLOTBIN frame directly. This removes the ~20 ms base64 encode, the ~17 ms decode,
80+
and the megabyte ``json.dumps`` of the pixel string from every scrub frame — a
81+
2.2x faster ``set_data`` (≈98 ms → ≈44 ms on a 2048² frame) and ~25% less
82+
bytes-on-wire. Non-Electron hosts (Jupyter / Pyodide / standalone / ``save_html``)
83+
are unchanged: they have no PLOTBIN channel, so the token is resolved back to
84+
inline base64 via ``Plot2D.resolve_pixel_tokens`` when the figure state is
85+
serialised for them.
86+
- Tile mode: a data update while zoomed in (``update_tile_source`` with a detail
87+
tile shown) refreshes only the detail tile, leaving the overview base on the
88+
old frame — zooming out then flashed the pre-update frame. The skipped
89+
overview is now marked stale and re-sampled once on the next view settle
90+
(riding the same push as the detail/clear), preserving the per-frame skip
91+
optimisation while never exposing stale base pixels.
92+
- Interactive zoom/pan on a 2-D image no longer re-serialises (and re-transmits)
93+
the full image on every mouse tick. The wheel/pan/orbit handlers write only the
94+
light *view* state back to the ``panel_<id>_json`` trait now, excluding the
95+
cached geometry (pixels, colormap LUT) that ``_applyGeom`` splices into the panel
96+
state for drawing. Previously the whole frame was ``JSON.stringify``-d per tick —
97+
catastrophically so on the binary transport, where the pixel buffer is a
98+
``Uint8Array`` that stringifies to a ``{"0":..,"1":..}`` object with one key per
99+
byte — which stalled zoom on large images.
100+
- Fixed a first-paint race under the Electron binary transport: binary
101+
side-table bytes that arrived before ``render()`` attached its listeners were
102+
stranded, leaving the first frame blank until the next update — they are now
103+
spliced into the initial paint.
104+
105+
106+
Maintenance
107+
-----------
108+
109+
- ``anyplotlib.__version__`` is now exposed from the package metadata.
110+
- Per-frame hot-path costs trimmed: the colormap LUT is cached instead of being
111+
rebuilt every frame (~100 ms), and small-range data rescales in float32
112+
(~60 ms → ~27 ms per 2048² frame).
113+
114+
13115
0.1.0 (2026-06-24)
14116
==================
15117

‎docs/_root/index.html‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
<meta charset="utf-8" />
55
<title>anyplotlib – redirecting…</title>
66
<!-- Redirect to the latest dev docs. -->
7-
<meta http-equiv="refresh" content="0; url=dev/" />
8-
<link rel="canonical" href="dev/" />
7+
<meta http-equiv="refresh" content="0; url=v0.2.0/" />
8+
<link rel="canonical" href="v0.2.0/" />
99
</head>
1010
<body>
1111
<p>
12-
Redirecting to <a href="dev/">dev documentation</a>…
12+
Redirecting to <a href="v0.2.0/">v0.2.0 documentation</a>…
1313
</p>
1414
</body>
1515
</html>

‎docs/_root/switcher.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
"version": "dev",
55
"url": "https://cssfrancis.github.io/anyplotlib/dev/"
66
},
7+
{
8+
"name": "v0.2.0 (stable)",
9+
"version": "v0.2.0",
10+
"url": "https://cssfrancis.github.io/anyplotlib/v0.2.0/"
11+
},
712
{
813
"name": "v0.1.0 (stable)",
914
"version": "v0.1.0",

‎docs/conf.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
project = "anyplotlib"
1818
copyright = "2026, anyplotlib contributors"
1919
author = "anyplotlib contributors"
20-
release = "0.1.0"
20+
release = "0.2.0"
2121

2222
# When built in CI the workflow sets DOCS_VERSION to the tag name (e.g.
2323
# "v0.1.0") or "dev". Fall back to "dev" for local builds.

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ exclude = [
1313

1414
[project]
1515
name = "anyplotlib"
16-
version = "0.1.0"
16+
version = "0.2.0"
1717
description = "A plotting library using python, javascript and anywidget for performant in browser plotting."
1818
readme = "README.md"
1919
license = { text = "MIT" }

‎upcoming_changes/README.rst‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ Each fragment is a plain ``.rst`` file named::
1313
{PR_number}.{type}.rst
1414

1515
where ``{PR_number}`` is the GitHub pull-request number and ``{type}`` is one
16-
of:
16+
of the types below.
17+
18+
If a change has no natural PR number (e.g. work batched on a long-lived
19+
feature branch), name the file ``+{slug}.{type}.rst`` — the leading ``+``
20+
marks it as an "orphan" fragment so towncrier omits the issue link. Without
21+
it, the slug is rendered as a broken PR link in the changelog (this bit the
22+
0.2.0 notes).
1723

1824
================= ==============================================================
1925
Type Use when …

‎upcoming_changes/binary_token_cache_freeze.bugfix.rst‎

Lines changed: 0 additions & 11 deletions
This file was deleted.

‎upcoming_changes/gpu_pan_v_mirror.bugfix.rst‎

Lines changed: 0 additions & 11 deletions
This file was deleted.

‎upcoming_changes/lazy_data_cast.bugfix.rst‎

Lines changed: 0 additions & 5 deletions
This file was deleted.

‎upcoming_changes/raw_pixel_transport.bugfix.rst‎

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)