Skip to content

Mask creation#8

Merged
CSSFrancis merged 3 commits intomainfrom
MaskCreation
May 2, 2026
Merged

Mask creation#8
CSSFrancis merged 3 commits intomainfrom
MaskCreation

Conversation

@CSSFrancis
Copy link
Copy Markdown
Owner

This pull request introduces a new interactive example for contrast-based image segmentation and adds a general-purpose, efficient mask overlay feature to the anyplotlib plotting library. The mask overlay allows boolean masks to be composited as semi-transparent colored overlays directly in the browser, with Python and JavaScript APIs for setting, clearing, and rendering overlays. Several UI and usability improvements are also included, such as improved click detection and more readable status labels.

New Example and Features

  • Added plot_segment_by_contrast.py interactive example demonstrating region-growing segmentation with live mask overlays, keyboard/mouse controls, and real-time mask updates.
  • Introduced a new set_overlay_mask method to Plot2D in figure_plots.py, allowing Python code to push boolean masks (as overlays) to the frontend, with customizable color and opacity.
  • Extended the plot state to include overlay_mask_b64, overlay_mask_color, and overlay_mask_alpha fields for mask overlay support.

Frontend Rendering and Usability Improvements

  • Implemented efficient client-side compositing of overlay masks in figure_esm.js, decoding base64 masks and blending them as colored overlays at the correct zoom/pan, without flicker or performance issues.
  • Improved click detection for interactive plots: distinguishes genuine clicks from pans based on movement and timing, enabling reliable region selection for segmentation or annotation tools. [1] [2] [3]
  • Enhanced UI appearance for status and size labels: larger padding, monospace font, rounded corners, and better readability. [1] [2] [3]

View State Preservation

  • When Python pushes new image data, the current zoom and pan state are preserved in the frontend, preventing unwanted resets during interactive workflows. [1] [2]

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.79%. Comparing base (90daec6) to head (a6b719e).

Additional details and impacted files
@@            Coverage Diff             @@
##             main       #8      +/-   ##
==========================================
+ Coverage   87.63%   87.79%   +0.16%     
==========================================
  Files           7        7              
  Lines        1876     1901      +25     
==========================================
+ Hits         1644     1669      +25     
  Misses        232      232              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a contrast-segmentation interactive example and introduces a general-purpose boolean mask overlay feature for anyplotlib 2D plots, along with frontend interaction/UI improvements (mask compositing, click detection, label styling, and view-state preservation).

Changes:

  • Add Plot2D.set_overlay_mask() and associated plot-state fields to push boolean overlay masks from Python to the frontend.
  • Implement client-side overlay-mask decoding/compositing in figure_esm.js, plus improved click-vs-pan detection and label styling tweaks.
  • Add a new interactive example (plot_segment_by_contrast.py) demonstrating region-growing segmentation with live overlays; remove two legacy top-level smoke-test scripts.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
test_pcolormesh.py Removes a legacy ad-hoc smoke-test script (not pytest-based).
test_figure.py Removes a legacy ad-hoc smoke-test script (not pytest-based).
anyplotlib/figure_plots.py Adds overlay-mask state + Python API (set_overlay_mask) for 2D plots.
anyplotlib/figure_esm.js Adds overlay compositing + click detection; tweaks UI styling; preserves view on state pushes.
Examples/Interactive/plot_segment_by_contrast.py New interactive segmentation example using the overlay-mask feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread anyplotlib/figure_esm.js Outdated
Comment on lines +673 to +677
// Preserve the current view (zoom/pan) so Python pushes don't reset it
if (p2.state && p2.kind === '2d') {
newState.zoom = p2.state.zoom;
newState.center_x = p2.state.center_x;
newState.center_y = p2.state.center_y;
Comment thread anyplotlib/figure_plots.py Outdated
Comment on lines +869 to +890
color : str, optional
CSS hex colour for the overlay, e.g. ``"#ff4444"``. Default red.
alpha : float, optional
Opacity in [0, 1]. Default 0.4 (40 % opaque).
"""
import base64
if mask is None:
self._state["overlay_mask_b64"] = ""
self._state["overlay_mask_color"] = color
self._state["overlay_mask_alpha"] = float(alpha)
else:
arr = np.asarray(mask)
if arr.shape != (self._state["image_height"], self._state["image_width"]):
raise ValueError(
f"mask shape {arr.shape} does not match image "
f"({self._state['image_height']} x {self._state['image_width']})"
)
# Convert to uint8: True/non-zero → 255, False/zero → 0
u8 = (np.asarray(arr, dtype=bool).view(np.uint8) * 255).astype(np.uint8)
self._state["overlay_mask_b64"] = base64.b64encode(u8.tobytes()).decode("ascii")
self._state["overlay_mask_color"] = color
self._state["overlay_mask_alpha"] = float(alpha)
Comment on lines +854 to +857
def set_overlay_mask(self, mask: "np.ndarray | None",
color: str = "#ff4444",
alpha: float = 0.4) -> None:
"""Set (or clear) a transparent boolean mask drawn over the image.
Comment thread anyplotlib/figure_esm.js
Comment on lines +2503 to +2512
// ── Click detection: short-duration + small-movement mousedown/up ────────
// Criteria: candidate still alive (not cleared by mousemove) AND ≤300 ms.
// We also re-check final distance as a safety net for document-level moves
// that didn't fire our mousemove guard (e.g. rapid trackpad flicks).
if(p.clickCandidate){
const _cc=p.clickCandidate; p.clickCandidate=null;
const _dx=cmx-_cc.mx, _dy=cmy-_cc.my;
const _dist2=_dx*_dx+_dy*_dy;
const _dt=Date.now()-_cc.t;
if(_dist2<=25&&_dt<=350){
Comment thread anyplotlib/figure_esm.js Outdated
Comment on lines +553 to +557
// Preserve the current view (zoom/pan) so Python pushes don't reset it
if (p2.state && p2.kind === '2d') {
newState.zoom = p2.state.zoom;
newState.center_x = p2.state.center_x;
newState.center_y = p2.state.center_y;
Comment thread anyplotlib/figure_esm.js
Comment on lines +1020 to +1026
const mob64=st.overlay_mask_b64||'';
if(mob64){
const mColor=st.overlay_mask_color||'#ff4444';
const mAlpha=st.overlay_mask_alpha!=null?st.overlay_mask_alpha:0.4;
const mKey=mob64+'|'+mColor+'|'+mAlpha;
if(!p.maskCache||p.maskCache.key!==mKey){
// Parse hex colour → r,g,b
Comment thread anyplotlib/figure_esm.js
Comment on lines +2519 to +2526
_emitEvent(p.id,'on_click',null,{
img_x:imgX, img_y:imgY,
phys_x:physX, phys_y:physY,
shift_key:_cc.shiftKey,
mouse_x:_cc.mx, mouse_y:_cc.my,
});
model.save_changes();
return;
raise ValueError(
f"mask shape {arr.shape} does not match image "
f"({self._state['image_height']} x {self._state['image_width']})"
)
Comment thread anyplotlib/figure_esm.js
Comment on lines +1018 to +1023
// overlay_mask_b64: base64 uint8 bytes (0|255), same iw×ih as image.
// Rendered at overlay_mask_alpha on top of the base image without clearing.
const mob64=st.overlay_mask_b64||'';
if(mob64){
const mColor=st.overlay_mask_color||'#ff4444';
const mAlpha=st.overlay_mask_alpha!=null?st.overlay_mask_alpha:0.4;
@CSSFrancis CSSFrancis merged commit ba0db6e into main May 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants