anyplotlib 0.7.1__py3-none-any.whl → 0.7.2__py3-none-any.whl

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.
@@ -45,6 +45,40 @@ def _binary_transport_active() -> bool:
45
45
  return os.environ.get("APL_BINARY_TRANSPORT") == "1"
46
46
 
47
47
 
48
+ def _reduce_mask_any(mask, out_h: int, out_w: int):
49
+ """Reduce a boolean *mask* to ``(out_h, out_w)`` with a block ANY.
50
+
51
+ A block of the source is True in the output if ANY of its pixels is. For a
52
+ mask that is the only defensible reduction: it marks *where objects are*,
53
+ and an object smaller than one output block must not vanish because the
54
+ sampled pixel happened to miss it.
55
+
56
+ The tail rows/columns that do not divide evenly are folded into the last
57
+ block rather than cropped, so an object at the right or bottom edge is kept.
58
+ """
59
+ import numpy as _np
60
+
61
+ src_h, src_w = mask.shape
62
+ if (src_h, src_w) == (out_h, out_w):
63
+ return mask
64
+ ys = max(1, src_h // out_h)
65
+ xs = max(1, src_w // out_w)
66
+ keep_h, keep_w = (src_h // ys) * ys, (src_w // xs) * xs
67
+ small = mask[:keep_h, :keep_w].reshape(
68
+ keep_h // ys, ys, keep_w // xs, xs).any(axis=(1, 3))
69
+ out = _np.zeros((out_h, out_w), bool)
70
+ sh, sw = min(out_h, small.shape[0]), min(out_w, small.shape[1])
71
+ out[:sh, :sw] = small[:sh, :sw]
72
+ # Fold any cropped tail into the final block so edge objects survive.
73
+ if keep_h < src_h and sh > 0:
74
+ out[sh - 1, :sw] |= mask[keep_h:, :keep_w].reshape(
75
+ 1, src_h - keep_h, keep_w // xs, xs).any(axis=(1, 3))[0, :sw]
76
+ if keep_w < src_w and sw > 0:
77
+ out[:sh, sw - 1] |= mask[:keep_h, keep_w:].reshape(
78
+ keep_h // ys, ys, 1, src_w - keep_w).any(axis=(1, 3))[:sh, 0]
79
+ return out
80
+
81
+
48
82
  class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
49
83
  """2-D image plot panel.
50
84
 
@@ -1115,6 +1149,13 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
1115
1149
  Boolean array aligned to the image data. ``True`` / non-zero
1116
1150
  pixels are filled with *color* at transparency *alpha*.
1117
1151
  Pass ``None`` to clear the overlay.
1152
+
1153
+ In TILE mode pass the mask at the FULL image resolution: the
1154
+ renderer composites the mask against the OVERVIEW texture, so it
1155
+ is reduced to the base grid here. A mask already at the base grid
1156
+ is accepted unchanged. (Both are accepted because neither is
1157
+ wrong; what was wrong was accepting only the full-resolution one
1158
+ and then shipping bytes the renderer silently discards.)
1118
1159
  color : str, optional
1119
1160
  CSS hex colour for the overlay, e.g. ``"#ff4444"``. Default red.
1120
1161
  Must be in ``#RRGGBB`` format.
@@ -1137,14 +1178,32 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
1137
1178
  self._state["overlay_mask_alpha"] = alpha
1138
1179
  else:
1139
1180
  arr = np.asarray(mask)
1140
- if arr.shape != (self._state["image_height"], self._state["image_width"]):
1141
- raise ValueError(
1142
- f"mask shape {arr.shape} does not match image "
1143
- f"({self._state['image_height']} x {self._state['image_width']})"
1144
- )
1181
+ ih, iw = self._state["image_height"], self._state["image_width"]
1182
+ bh = int(self._state.get("base_height") or 0)
1183
+ bw = int(self._state.get("base_width") or 0)
1184
+ # THE TILE-MODE SHAPE TRAP. The renderer sizes the mask against
1185
+ # `base_width || image_width` -- the OVERVIEW grid -- but tile mode
1186
+ # sets image_width/height to the FULL native frame. So validating
1187
+ # only against the image shape rejected the one shape that renders
1188
+ # and accepted the one the renderer drops on the floor
1189
+ # (`mBytes.length===iw*ih` else `maskCache=null`, silently). Both
1190
+ # shapes are legal here; the overview is what actually ships.
1191
+ tiled = bw > 0 and bh > 0 and (bh, bw) != (ih, iw)
1192
+ if arr.shape != (ih, iw) and not (tiled and arr.shape == (bh, bw)):
1193
+ want = f"({ih} x {iw})"
1194
+ if tiled:
1195
+ want += f" or the tile overview ({bh} x {bw})"
1196
+ raise ValueError(f"mask shape {arr.shape} does not match image {want}")
1145
1197
  # For origin='lower' the image data was flipped; flip mask to match.
1146
1198
  if self._origin == "lower":
1147
1199
  arr = np.flipud(arr)
1200
+ if tiled and arr.shape == (ih, iw):
1201
+ # Block ANY, never a subsample: a mask marks objects that are
1202
+ # often only a few pixels across, and striding a 4096² mask
1203
+ # down to 1024² drops three quarters of them at random. ANY
1204
+ # keeps every object visible at the cost of fattening it, which
1205
+ # is the right trade for an overlay you are looking at.
1206
+ arr = _reduce_mask_any(np.asarray(arr, dtype=bool), bh, bw)
1148
1207
  # Convert to uint8: True/non-zero → 255, False/zero → 0
1149
1208
  u8 = (np.asarray(arr, dtype=bool).view(np.uint8) * 255).astype(np.uint8)
1150
1209
  self._state["overlay_mask_b64"] = base64.b64encode(u8.tobytes()).decode("ascii")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: anyplotlib
3
- Version: 0.7.1
3
+ Version: 0.7.2
4
4
  Summary: A plotting library using python, javascript and anywidget for performant in browser plotting.
5
5
  Project-URL: Homepage, https://cssfrancis.github.io/anyplotlib/
6
6
  Project-URL: Repository, https://github.com/CSSFrancis/anyplotlib
@@ -21,7 +21,7 @@ anyplotlib/plot1d/_plot1d.py,sha256=H0bK4ThKV-3haxs0ish48Ye65M-K9KRNwzoFRNTXjso,
21
21
  anyplotlib/plot1d/_plotbar.py,sha256=2hbj3I86TJr5Zds4a179iNQ9CyQTG8ys9MqY8xBTX_Y,17356
22
22
  anyplotlib/plot2d/__init__.py,sha256=_BVLJeGKuTmlZPpfPW2d99qRamBvOSyx3hsru7ylTzk,264
23
23
  anyplotlib/plot2d/_layer.py,sha256=gONf5R2bYsf_Oymx3Y0RmPrTvFDzVnbDGBKFWkhyAKM,5598
24
- anyplotlib/plot2d/_plot2d.py,sha256=kqHmGWi9kphKhqBuJtBvhBhNJR1neo1L8A3lvqb7Dj4,116119
24
+ anyplotlib/plot2d/_plot2d.py,sha256=w1p0eIm_MQnacBZsvZ5yKV9YpR3ybM0qH-aHHwH-7Rc,119353
25
25
  anyplotlib/plot2d/_plotmesh.py,sha256=qXOsiQQ13yhPar5KToAIz02sqivdKz3eXcHI7QZsv04,4547
26
26
  anyplotlib/plot2d/_tile_backend.py,sha256=BfJ0X-AbWJvImeNcBW9PzkO3b-JQ_CDBP3vOfdNocOw,7225
27
27
  anyplotlib/plot3d/__init__.py,sha256=m851XQKGzx5bywIbBK0qntBxzJU7zwxqf6xnOQ8pD1M,138
@@ -40,7 +40,7 @@ anyplotlib/widgets/_base.py,sha256=WMY_s_KKNVpd-3VceAwZeY5JHhtvJp1nuVbYkmqNiBw,9
40
40
  anyplotlib/widgets/_widgets1d.py,sha256=SXnBDm25th1u9lPHlpQIdVACaQcVCvKUeF-ZJ9VMeVs,7784
41
41
  anyplotlib/widgets/_widgets2d.py,sha256=XZmqmgRq7g0P2-xkC2hMRPfZFtq8t-nfjbRc7w2wIXA,19938
42
42
  anyplotlib/widgets/_widgets3d.py,sha256=zRd2q5O2KePxU5raCnvO-LatMlpGQjnDMPhG61d40Ks,1638
43
- anyplotlib-0.7.1.dist-info/METADATA,sha256=xYw_ifXvYZ6xBDfReG7bjNrxWtWb1hgsdinOW2mU28s,7163
44
- anyplotlib-0.7.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
45
- anyplotlib-0.7.1.dist-info/licenses/LICENSE,sha256=NonSXCjWxcYbaMxPnkgFzPpP0bVBTwUDpuDKyKC2uvQ,1071
46
- anyplotlib-0.7.1.dist-info/RECORD,,
43
+ anyplotlib-0.7.2.dist-info/METADATA,sha256=IyT9uH8qJxx6r-0zp3yeeayG9yBxI0B3DXk9rfImZY0,7163
44
+ anyplotlib-0.7.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
45
+ anyplotlib-0.7.2.dist-info/licenses/LICENSE,sha256=NonSXCjWxcYbaMxPnkgFzPpP0bVBTwUDpuDKyKC2uvQ,1071
46
+ anyplotlib-0.7.2.dist-info/RECORD,,