Mark2D#

class quantem.widget.Mark2D(**kwargs: Any)[source]#

Bases: AnyWidget

Interactive point picker for 2D images.

Click on an image to select points (atom positions, features, lattice vectors). Supports gallery mode for comparing multiple images, pre-loaded points from detection algorithms, multiple ROI overlays with pixel statistics, snap-to-peak for precise atom column picking, and calibrated distance measurements between points.

Parameters:
  • data (array_like) –

    Image data. Accepts:

    • 2D array (H, W) — single image

    • 3D array (N, H, W) — gallery of N images

    • List of 2D arrays — gallery (resized to common dimensions)

    • Dataset2d object — array and sampling auto-extracted

  • scale (float, default 1.0) – Display scale factor. Values > 1 enlarge the canvas.

  • dot_size (int, default 12) – Diameter of point markers in CSS pixels.

  • max_points (int, default 10) – Maximum number of points per image. Oldest points are removed when the limit is exceeded.

  • ncols (int, default 3) – Number of columns in the gallery grid (ignored for single images).

  • labels (list of str, optional) – Per-image labels shown below each gallery tile and in the header. Defaults to "Image 1", "Image 2", etc.

  • marker_border (int, default 2) – Border width of point markers in pixels (0–6). The border grows inward from the marker edge, so the overall marker size stays constant. Set to 0 for borderless markers.

  • marker_opacity (float, default 1.0) – Opacity of point markers (0.1–1.0).

  • label_size (int, default 0) – Font size in pixels for the numbered label above each marker. 0 means auto-scale relative to dot_size.

  • label_color (str, default "") – CSS color for numbered labels (e.g. "white", "#ff0"). Empty string uses the automatic theme color.

  • pixel_size (float, default 0.0) – Pixel size in angstroms. When set, the widget displays a calibrated scale bar and shows point-to-point distances in physical units (angstroms or nanometers). 0 means uncalibrated.

  • points (list or ndarray, optional) –

    Pre-populate the widget with initial points. Useful for reviewing or refining positions from an atom-finding algorithm. Accepts:

    • List of (row, col) tuples: [(10, 20), (30, 40)]

    • List of dicts with optional shape/color: [{"row": 10, "col": 20, "shape": "star", "color": "#f00"}]

    • NumPy array of shape (N, 2) with columns [row, col]

    • For gallery: list of the above, one per image

    When shape or color are omitted, they cycle through the built-in palettes (5 shapes, 10 colors).

  • marker_shape (str, default "circle") – Active marker shape for new points. One of "circle", "triangle", "square", "diamond", "star". Synced bidirectionally — changes in the UI are reflected in Python.

  • marker_color (str, default "#f44336") – Active marker color for new points (CSS color string). Synced bidirectionally — changes in the UI are reflected in Python.

  • snap_enabled (bool, default False) – Whether snap-to-peak is active. When True, clicked positions are snapped to the local intensity maximum within snap_radius.

  • snap_radius (int, default 5) – Search radius in pixels for snap-to-peak.

  • title (str, default "") – Title displayed in the widget header. Empty string shows "Mark2D".

  • show_stats (bool, default True) – Show statistics bar (mean, min, max, std) below the canvas.

  • cmap (str, default "gray") – Colormap for image rendering. Options: "gray", "inferno", "viridis", "plasma", "magma", "hot".

  • auto_contrast (bool, default True) – Enable automatic contrast via 2–98% percentile clipping. When False, contrast is controlled by the histogram slider.

  • log_scale (bool, default False) – Apply log(1+x) transform before rendering. Useful for images with large dynamic range (e.g. diffraction patterns).

  • show_fft (bool, default False) – Show FFT power spectrum alongside the image.

  • disabled_tools (list of str, optional) – Tool groups to disable in the frontend UI/interaction layer. This is useful for shared notebooks where viewers should not be able to modify selected controls or annotations. Supported values: "points", "roi", "profile", "display", "marker_style", "snap", "navigation", "view", "export", "all".

  • disable_points (bool, default False) – Convenience flag equivalent to including "points" in disabled_tools.

  • disable_roi (bool, default False) – Convenience flag equivalent to including "roi" in disabled_tools.

  • disable_profile (bool, default False) – Convenience flag equivalent to including "profile" in disabled_tools.

  • disable_display (bool, default False) – Convenience flag equivalent to including "display" in disabled_tools.

  • disable_marker_style (bool, default False) – Convenience flag equivalent to including "marker_style" in disabled_tools.

  • disable_snap (bool, default False) – Convenience flag equivalent to including "snap" in disabled_tools.

  • disable_navigation (bool, default False) – Convenience flag equivalent to including "navigation" in disabled_tools.

  • disable_view (bool, default False) – Convenience flag equivalent to including "view" in disabled_tools.

  • disable_export (bool, default False) – Convenience flag equivalent to including "export" in disabled_tools.

  • disable_all (bool, default False) – Convenience flag equivalent to disabled_tools=["all"].

  • hidden_tools (list of str, optional) – Tool groups to hide from the frontend UI. Hidden tools are also interaction-locked (equivalent to disabled for behavior), but their controls are not rendered. Supported values: "points", "roi", "profile", "display", "marker_style", "snap", "navigation", "view", "export", "all".

  • hide_points (bool, default False) – Convenience flag equivalent to including "points" in hidden_tools.

  • hide_roi (bool, default False) – Convenience flag equivalent to including "roi" in hidden_tools.

  • hide_profile (bool, default False) – Convenience flag equivalent to including "profile" in hidden_tools.

  • hide_display (bool, default False) – Convenience flag equivalent to including "display" in hidden_tools.

  • hide_marker_style (bool, default False) – Convenience flag equivalent to including "marker_style" in hidden_tools.

  • hide_snap (bool, default False) – Convenience flag equivalent to including "snap" in hidden_tools.

  • hide_navigation (bool, default False) – Convenience flag equivalent to including "navigation" in hidden_tools.

  • hide_view (bool, default False) – Convenience flag equivalent to including "view" in hidden_tools.

  • hide_export (bool, default False) – Convenience flag equivalent to including "export" in hidden_tools.

  • hide_all (bool, default False) – Convenience flag equivalent to hidden_tools=["all"].

selected_points#

Currently placed points, synced bidirectionally with the widget.

  • Single image: flat list of point dicts [{"row": 10, "col": 20, "shape": "circle", "color": "#f44336"}, ...]

  • Gallery mode: list of lists, one per image [[point, ...], [point, ...], ...]

Each point dict has keys: row (int), col (int), shape (str), color (str). You can set this attribute from Python to update the widget in real time.

Type:

list

roi_list#

Currently defined ROI overlays, synced with the widget. Each ROI is a dict with keys:

  • id (int) — unique identifier

  • mode (str) — "circle", "square", or "rectangle"

  • row, col (int) — center position in image pixels

  • radius (int) — radius for circle/square modes

  • rectW, rectH (int) — width/height for rectangle mode

  • color (str) — CSS stroke color

  • opacity (float) — opacity (0.1–1.0)

Set from Python to programmatically define ROIs, or read after interactive use to retrieve user-defined regions.

Type:

list

Notes

Marker shapes: circle, triangle, square, diamond, star (5 shapes that cycle automatically).

Marker colors: 10 colors that cycle: red, green, blue, orange, purple, cyan, yellow, pink, lime, deep orange.

Snap-to-peak: When enabled in the UI, clicking snaps the point to the local intensity maximum within a configurable search radius. Useful for precise atom column picking on HAADF-STEM images.

Distance measurements: Distances between consecutive points are displayed in the point list. With pixel_size set, distances are shown in angstroms (< 10 Å) or nanometers (>= 10 Å).

ROI statistics: When an ROI is active, the widget computes and displays mean, standard deviation, min, max, and pixel count for the region. When multiple ROIs exist, a summary table shows all ROI stats. Active ROIs also show dotted horizontal/vertical center guide lines.

Pairwise distances: When 2+ points are placed, a table below the point list shows distances between all pairs of points.

Line profile: Toggle “Profile” mode in the controls, then click two points to sample intensity along a line. A sparkline graph with calibrated x-axis appears below the canvas. Also available programmatically via set_profile(), profile_values, and profile_distance.

Keyboard shortcuts (widget must be focused):

  • Delete / Backspace — remove last point (undo)

  • Ctrl+Z / Cmd+Z — undo

  • Ctrl+Shift+Z / Cmd+Shift+Z — redo

  • 16 — select ROI #1–6

  • Arrow keys — nudge active ROI by 1 pixel

  • Arrow keys (no ROI, gallery) — navigate between images

  • Escape — deselect ROI

Examples

Basic point picking:

>>> import numpy as np
>>> from quantem.widget import Mark2D
>>> img = np.random.rand(256, 256).astype(np.float32)
>>> w = Mark2D(img, scale=1.5, max_points=5)
>>> w  # display in notebook; click to place points
>>> w.selected_points  # read back placed points

Pre-loaded points from a detection algorithm:

>>> peaks = find_atom_columns(img)  # returns (N, 2) array
>>> w = Mark2D(img, points=peaks, pixel_size=0.82)
>>> # Points appear immediately; user can add/remove/adjust

Pre-loaded points with custom appearance:

>>> pts = [
...     {"row": 200, "col": 100, "shape": "star", "color": "#ff0"},
...     {"row": 250, "col": 150, "shape": "diamond", "color": "#0ff"},
... ]
>>> w = Mark2D(img, points=pts, marker_border=0, marker_opacity=0.8)

Gallery mode for comparing multiple images:

>>> imgs = [original, filtered, denoised]
>>> w = Mark2D(imgs, ncols=3, labels=["Raw", "Filtered", "Denoised"])
>>> # Points are tracked independently per image

Gallery with per-image pre-loaded points:

>>> per_image_pts = [[(10, 20)], [(30, 40), (50, 60)], []]
>>> w = Mark2D(imgs, points=per_image_pts)

Programmatic ROI management:

>>> w = Mark2D(img)
>>> w.add_roi(row=128, col=128, mode="circle", radius=30, color="#0f0")
>>> w.add_roi(row=200, col=200, mode="rectangle", rect_w=80, rect_h=40)
>>> w.roi_list  # inspect ROI parameters
>>> w.roi_center()  # center of most recently added ROI -> (200, 200)
>>> w.roi_radius()  # radius for circle/square, None for rectangle
>>> w.roi_size()  # shape-aware size dict (e.g. width/height for rectangle)
>>> w.roi_list = []  # clear all ROIs

Snap-to-peak for precise atom picking:

>>> w = Mark2D(haadf_image, snap_enabled=True, snap_radius=8,
...             pixel_size=0.82)
>>> # Clicks auto-snap to the nearest intensity maximum

Custom marker defaults:

>>> w = Mark2D(img, marker_shape="star", marker_color="#ff9800")
>>> # All new points will be orange stars until changed in the UI

Human-friendly tool locking:

>>> w = Mark2D(img, disable_points=True, disable_roi=True, disable_display=True)
>>> w_read_only = Mark2D(img, disable_all=True)
>>> w_clean = Mark2D(img, hide_display=True, hide_export=True)

Save and restore full widget state (state portability):

>>> # User A: create widget, place points and ROIs interactively
>>> w = Mark2D(img, pixel_size=1.5)
>>> # ... user clicks to place points, adds ROIs, enables snap ...
>>> state = {
...     "points": w.selected_points,
...     "rois": w.roi_list,
...     "marker_shape": w.marker_shape,
...     "marker_color": w.marker_color,
...     "snap_enabled": w.snap_enabled,
...     "snap_radius": w.snap_radius,
... }
>>> # User B: restore exact same state on another machine
>>> w2 = Mark2D(img, pixel_size=1.5,
...              points=state["points"],
...              marker_shape=state["marker_shape"],
...              marker_color=state["marker_color"],
...              snap_enabled=state["snap_enabled"],
...              snap_radius=state["snap_radius"])
>>> w2.roi_list = state["rois"]

Line profile (programmatic):

>>> w = Mark2D(img, pixel_size=0.82)
>>> w.set_profile((10, 20), (100, 200))
>>> w.profile_values  # sampled intensities along the line
>>> w.profile_distance  # total distance in angstroms

Export points as NumPy array:

>>> w = Mark2D(img, points=[(10, 20), (30, 40)])
>>> w.points_as_array()  # shape (2, 2), columns [row, col]
>>> w.points_as_dict()  # [{"row": 10, "col": 20}, ...]

Key Methods

Mark2D.set_image(data, labels=None)[source]#

Replace the displayed image(s) and reset all points.

Can switch between single-image and gallery modes. All existing points are cleared; ROIs are preserved.

Parameters:
  • data (array_like) – 2D array (H, W), 3D array (N, H, W), or list of 2D arrays. Same formats as the constructor.

  • labels (list of str, optional) – Per-image labels for gallery mode. If None, defaults to "Image 1", "Image 2", etc.

Examples

>>> w = Mark2D(img1)
>>> w.set_image(img2)  # switch to a different image
>>> w.set_image([img1, img2, img3], labels=["A", "B", "C"])
Mark2D.save_image(path: str | Path, *, idx: int | None = None, include_markers: bool = True, format: str | None = None, dpi: int = 150) Path[source]#

Save current image as PNG or PDF, optionally with marker overlays.

Parameters:
  • path (str or pathlib.Path) – Output file path.

  • idx (int, optional) – Image index in gallery mode. Defaults to current selected_idx.

  • include_markers (bool, default True) – If True, render marker points on the exported image.

  • format (str, optional) – ‘png’ or ‘pdf’. If omitted, inferred from file extension.

  • dpi (int, default 150) – Output DPI metadata.

Returns:

The written file path.

Return type:

pathlib.Path

Mark2D.add_roi(row, col, shape='square', radius=30, width=60, height=40, color='#0f0', opacity=0.8)[source]#

Add an ROI overlay to the widget.

Multiple ROIs can be added. Each gets a unique ID. In the widget, the user can click ROI centers to select them, drag to reposition, and adjust size/color/opacity interactively. The widget also displays pixel statistics (mean, std, min, max) for the active ROI.

Parameters:
  • row (int) – Center position in image pixel coordinates (row, col).

  • col (int) – Center position in image pixel coordinates (row, col).

  • shape (str, default "circle") – ROI shape. One of "circle", "square", or "rectangle".

  • radius (int, default 30) – Radius in pixels for circle and square modes.

  • width (int, default 60) – Width in pixels for rectangle mode.

  • height (int, default 40) – Height in pixels for rectangle mode.

  • color (str, default "#0f0") – Stroke color as a CSS color string (e.g. "#ff0", "red").

  • opacity (float, default 0.8) – Stroke opacity (0.1–1.0).

Examples

>>> w = Mark2D(img)
>>> w.add_roi(128, 128)  # green circle at center
>>> w.add_roi(50, 50, shape="square", radius=20, color="#ff0")
>>> w.add_roi(200, 100, shape="rectangle", width=80, height=30)
>>> len(w.roi_list)  # 3
Mark2D.clear_rois()[source]#

Remove all ROI overlays.

Examples

>>> w.add_roi(100, 100)
>>> w.add_roi(200, 200)
>>> w.clear_rois()
>>> w.roi_list  # []
Mark2D.roi_center(index: int | None = None, roi_id: int | None = None)[source]#

Return ROI center coordinates as (row, col).

By default, returns the center of the most recently added ROI.

Parameters:
  • index (int, optional) – ROI list index to query. Supports negative indexing.

  • roi_id (int, optional) – ROI id value to query. Mutually exclusive with index.

Returns:

Center point (row, col).

Return type:

tuple of int

Mark2D.roi_radius(index: int | None = None, roi_id: int | None = None)[source]#

Return ROI radius for circle/square ROIs.

For rectangle ROIs, returns None (use roi_size() for rectangle width/height). By default, queries the most recently added ROI.

Parameters:
  • index (int, optional) – ROI list index to query. Supports negative indexing.

  • roi_id (int, optional) – ROI id value to query. Mutually exclusive with index.

Returns:

Radius in pixels for circle/square ROIs, otherwise None.

Return type:

int or None

Mark2D.roi_size(index: int | None = None, roi_id: int | None = None)[source]#

Return shape-aware ROI size information.

  • circle / square -> {"shape", "radius", "diameter"}

  • rectangle -> {"shape", "width", "height"}

Parameters:
  • index (int, optional) – ROI list index to query. Supports negative indexing.

  • roi_id (int, optional) – ROI id value to query. Mutually exclusive with index.

Returns:

Shape-aware size dictionary for the selected ROI.

Return type:

dict

Mark2D.set_profile(start: tuple, end: tuple)[source]#

Set a line profile between two points.

The profile is drawn on the canvas and intensity values are sampled along the line with bilinear interpolation. A sparkline graph appears below the canvas.

Parameters:
  • start (tuple of (row, col)) – Start point in image pixel coordinates.

  • end (tuple of (row, col)) – End point in image pixel coordinates.

Examples

>>> w = Mark2D(img, pixel_size=0.82)
>>> w.set_profile((10, 20), (100, 200))
>>> w.profile_values  # sampled intensities along the line
Mark2D.clear_profile()[source]#

Clear the current line profile.

Mark2D.points_as_array()[source]#

Return placed points as a NumPy array of shape (N, 2) with columns [row, col].

In gallery mode, returns a list of arrays (one per image).

Examples

>>> w = Mark2D(img, points=[(10, 20), (30, 40)])
>>> w.points_as_array()
array([[10, 20],
       [30, 40]])
Mark2D.points_as_dict()[source]#

Return placed points as a list of {"row": int, "col": int} dicts.

In gallery mode, returns a list of lists (one per image).

Examples

>>> w = Mark2D(img, points=[(10, 20), (30, 40)])
>>> w.points_as_dict()
[{'row': 10, 'col': 20}, {'row': 30, 'col': 40}]
Mark2D.clear_points()[source]#

Remove all placed points from all images.

Examples

>>> w.clear_points()
>>> w.selected_points  # [] or [[], [], ...]
Mark2D.state_dict()[source]#

Return a dict of all restorable widget state.

Use this to persist the widget state across kernel restarts. Pass the returned dict as the state parameter to a new Mark2D to restore everything.

Examples

>>> w = Mark2D(img, pixel_size=1.5)
>>> # ... user places points, adds ROIs, changes settings ...
>>> state = w.state_dict()
>>> # Later (or after kernel restart):
>>> w2 = Mark2D(img, state=state)
Mark2D.save(path: str)[source]#

Save widget state to a JSON file.

Parameters:

path (str) – File path to write (e.g. "analysis.json").

Examples

>>> w = Mark2D(img)
>>> # ... place points, add ROIs ...
>>> w.save("my_analysis.json")
>>> # After kernel restart:
>>> w2 = Mark2D(img, state="my_analysis.json")
Mark2D.load_state_dict(state)[source]#

Restore widget state from a dict returned by state_dict().

Parameters:

state (dict) – State dict from a previous state_dict() call. Missing keys are silently skipped.

Examples

>>> state = old_widget.state_dict()
>>> new_widget = Mark2D(img)
>>> new_widget.load_state_dict(state)
Mark2D.summary()[source]#

Print a detailed summary of the widget state.

Shows image info, display settings, points with coordinates, ROI details, and marker configuration.

Examples

>>> w = Mark2D(img, points=[(10, 20), (30, 40)],
...             pixel_size=0.82, cmap='viridis')
>>> w.summary()
Mark2D
═══════════════════════════════
Image:    128×128 (0.82 Å/px)
Display:  viridis | auto contrast | linear
...