# Edit2D — All Features Comprehensive demo of the Edit2D crop, pad, and mask tool.
[1]:
# Install in Google Colab
try:
import google.colab
!pip install -q -i https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ quantem-widget
except ImportError:
pass # Not in Colab, skip
[2]:
try:
%load_ext autoreload
%autoreload 2
%env ANYWIDGET_HMR=1
except Exception:
pass # autoreload unavailable (Colab Python 3.12+)
env: ANYWIDGET_HMR=1
[3]:
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
import numpy as np
import torch
import quantem.widget
from quantem.widget import Edit2D
device = torch.device("mps" if torch.backends.mps.is_available() else "cuda" if torch.cuda.is_available() else "cpu")
def make_crystal(size=256, seed=0):
"""Simulate a crystal lattice image with point defects (GPU-accelerated)."""
gen = torch.Generator(device="cpu").manual_seed(seed)
y, x = torch.meshgrid(torch.arange(size, device=device, dtype=torch.float32),
torch.arange(size, device=device, dtype=torch.float32), indexing="ij")
# Two-beam lattice fringes
img = torch.cos(2 * torch.pi * 0.08 * x) + torch.cos(2 * torch.pi * 0.08 * y)
# Vectorized point defects (no Python loop)
n_defects = 5
cy = torch.randint(20, size - 20, (n_defects,), generator=gen).to(device).float()
cx = torch.randint(20, size - 20, (n_defects,), generator=gen).to(device).float()
# Broadcast: (n_defects, 1, 1) vs (1, size, size)
r2 = (y.unsqueeze(0) - cy[:, None, None])**2 + (x.unsqueeze(0) - cx[:, None, None])**2
img += (3.0 * torch.exp(-r2 / 8)).sum(dim=0)
noise = torch.randn(size, size, generator=gen).to(device) * 0.2
img += noise
return img.cpu().numpy().astype(np.float32)
image = make_crystal(256)
print(f"quantem.widget {quantem.widget.__version__}")
quantem.widget 0.4.0a3
1. Basic Crop#
Drag the crop rectangle interactively, or set bounds programmatically.
[4]:
w = Edit2D(image, title="Basic Crop")
w
[4]:
[5]:
# Read back the crop
print(f"Bounds: {w.crop_bounds}")
print(f"Size: {w.crop_size}")
print(f"Result: {w.result.shape}")
Bounds: (0, 0, 256, 256)
Size: (256, 256)
Result: (256, 256)
2. Programmatic Bounds#
Set crop bounds as (top, left, bottom, right) in image coordinates.
[6]:
Edit2D(image, bounds=(50, 50, 200, 200), title="Center Crop (150x150)")
[6]:
3. Padding (Bounds Beyond Image)#
Negative bounds or bounds exceeding image dimensions produce padding with fill_value.
[7]:
w_pad = Edit2D(image, bounds=(-30, -30, 286, 286), fill_value=0.0, title="Padded (30px border)")
w_pad
[7]:
[8]:
result = w_pad.result
print(f"Padded result: {result.shape}") # 316x316
print(f"Corner value (should be fill): {result[0, 0]}")
print(f"Center value (should be data): {result[30, 30]:.4f}")
Padded result: (316, 316)
Corner value (should be fill): 0.0
Center value (should be data): 2.0983
4. Mask Mode#
Paint a binary mask over the image. Masked pixels are set to fill_value in the result.
[9]:
w_mask = Edit2D(image, mode="mask", fill_value=0.0, title="Mask Mode")
w_mask
[9]:
[10]:
from quantem.widget import Show2D
# Create a programmatic circular mask (e.g., masking a contamination spot)
mask = np.zeros((256, 256), dtype=np.uint8)
cy, cx, r = 128, 128, 40
yy, xx = np.ogrid[:256, :256]
mask[(yy - cy)**2 + (xx - cx)**2 <= r**2] = 1
w_mask.mask_bytes = mask.tobytes()
print(f"Mask shape: {w_mask.mask.shape}")
print(f"Masked pixels: {w_mask.mask.sum()}")
# View the masked result — masked region is replaced with fill_value
Show2D(w_mask.result, title="Masked Result (center spot removed)")
Mask shape: (256, 256)
Masked pixels: 5025
[10]:
5. Multi-Image Mode#
Apply the same crop/mask to multiple images at once.
[11]:
images = [make_crystal(256, seed=i) for i in range(3)]
w_multi = Edit2D(images, labels=["Crystal A", "Crystal B", "Crystal C"],
bounds=(30, 30, 220, 220), title="Multi-Image Crop")
w_multi
[11]:
[12]:
results = w_multi.result
print(f"Number of results: {len(results)}")
for i, r in enumerate(results):
print(f" Image {i}: {r.shape}")
Number of results: 3
Image 0: (190, 190)
Image 1: (190, 190)
Image 2: (190, 190)
6. Display Options#
Log scale, colormap, and scale bar.
[13]:
Edit2D(image, cmap="viridis", log_scale=True,
pixel_size=2.5, title="Viridis + Log Scale")
[13]:
7. Replace Data with set_image()#
Swap the underlying data while preserving display settings.
[14]:
w_swap = Edit2D(image, cmap="inferno", title="Original")
print(f"Before: {w_swap.height}x{w_swap.width}")
new_image = make_crystal(128, seed=42)
w_swap.set_image(new_image)
print(f"After: {w_swap.height}x{w_swap.width}")
print(f"Cmap preserved: {w_swap.cmap}")
w_swap
Before: 256x256
After: 128x128
Cmap preserved: inferno
[14]:
8. State Persistence#
Save and restore widget settings across sessions.
[15]:
w_state = Edit2D(image, cmap="plasma", bounds=(20, 30, 200, 220),
fill_value=5.0, title="State Demo")
w_state.summary()
State Demo
════════════════════════════════
Image: 256×256
Mode: crop
Crop: (20, 30) → (200, 220) = 180×190
Fill: 5.0
Display: plasma | auto | linear
[16]:
# Save and inspect
w_state.save("/tmp/edit2d_state.json")
print(w_state.state_dict())
{'title': 'State Demo', 'cmap': 'plasma', 'mode': 'crop', 'log_scale': False, 'auto_contrast': True, 'show_controls': True, 'show_stats': True, 'show_display_controls': True, 'show_edit_controls': True, 'show_histogram': True, 'disabled_tools': [], 'hidden_tools': [], 'pixel_size': 0.0, 'fill_value': 5.0, 'crop_top': 20, 'crop_left': 30, 'crop_bottom': 200, 'crop_right': 220, 'shared': True}
[17]:
# Restore from file
w2 = Edit2D(image, state="/tmp/edit2d_state.json")
w2.summary()
State Demo
════════════════════════════════
Image: 256×256
Mode: crop
Crop: (20, 30) → (200, 220) = 180×190
Fill: 5.0
Display: plasma | auto | linear
[18]:
import os
os.remove("/tmp/edit2d_state.json")