Skip to content

pymmcore_plus.experimental.simulate

Simulated microscope sample for testing and development.

This module provides tools for creating virtual microscope samples that integrate with CMMCorePlus. When a sample is installed on a core, image acquisition returns rendered images based on the sample objects and current microscope state (stage position, exposure, pixel size, etc.).

Examples:

Create a sample with some objects and use it with a core:

>>> from pymmcore_plus import CMMCorePlus
>>> from pymmcore_plus.experimental.simulate import (
...     Sample,
...     Point,
...     Line,
...     Rectangle,
...     RenderConfig,
... )
>>>
>>> # Create core and load config
>>> core = CMMCorePlus.instance()
>>> core.loadSystemConfiguration()
>>>
>>> # Define sample objects (coordinates in microns)
>>> sample = Sample(
...     [
...         Point(0, 0, intensity=200, radius=5),
...         Point(50, 50, intensity=150, radius=3),
...         Line((0, 0), (100, 100), intensity=100),
...         Rectangle((20, 20), width=30, height=20, intensity=180, fill=True),
...     ]
... )
>>>
>>> # Use as context manager
>>> with sample.patch(core):
...     core.snapImage()
...     img = core.getImage()  # Returns rendered simulation
...     print(img.shape, img.dtype)

Custom render configuration:

>>> config = RenderConfig(
...     noise_std=5.0,  # More noise
...     defocus_scale=0.2,  # More blur with Z
...     shot_noise=False,  # Disable shot noise
...     bit_depth=16,  # 16-bit output
... )
>>> sample = Sample([Point(0, 0)], config=config)

Manual install/uninstall:

>>> sample.install(core)
>>> # ... do stuff ...
>>> sample.uninstall()

Classes

Name Description
Arc An arc (partial ellipse outline) in the sample.
Bitmap A bitmap image placed in the sample.
Ellipse An ellipse in the sample.
Line A line segment in the sample.
Point A circular point/spot in the sample.
Polygon A polygon in the sample.
Rectangle A rectangle in the sample.
RegularPolygon A regular polygon inscribed in a circle.
RenderConfig Configuration for the rendering engine.
Sample A simulated microscope sample that integrates with CMMCorePlus.
SampleObject Base class for drawable sample objects.

Functions

Name Description
rects_intersect Check if two rectangles intersect.

Attributes

Name Description
Bounds

Classes

Arc dataclass

Arc(
    center: tuple[float, float],
    rx: float,
    ry: float,
    start_angle: float,
    end_angle: float,
    intensity: int = 255,
    width: int = 1,
)

Bases: SampleObject

An arc (partial ellipse outline) in the sample.

Parameters:

Name Type Description Default
center tuple[float, float]

Center (x, y) in world units.

required
rx float

X radius in world units.

required
ry float

Y radius in world units.

required
start_angle float

Start angle in degrees.

required
end_angle float

End angle in degrees.

required
intensity int

Brightness value (0-255). Default 255.

255
width int

Arc width in pixels. Default 1.

1

center

center: tuple[float, float]

rx

rx: float

ry

ry: float

start_angle

start_angle: float

end_angle

end_angle: float

intensity

intensity: int = 255

width

width: int = 1

draw

draw(draw_context: ImageDraw, transform: TransformFn, scale: float) -> None

draw_cv2

draw_cv2(img: ndarray, transform: TransformFn, scale: float) -> None

get_bounds

get_bounds() -> Bounds

Bitmap dataclass

Bitmap(
    top_left: tuple[float, float],
    data: ndarray | Image | str,
    bitmap_scale: float = 1.0,
)

Bases: SampleObject

A bitmap image placed in the sample.

The bitmap is placed at a fixed position and scale in world coordinates. Each pixel of the bitmap corresponds to one world unit.

Parameters:

Name Type Description Default
top_left tuple[float, float]

Top-left corner (x, y) in world units.

required
data ndarray | Image | str

Image data as numpy array, PIL Image, or path to image file.

required
bitmap_scale float

World units per bitmap pixel. Default 1.0 (1 pixel = 1 world unit).

1.0

top_left

top_left: tuple[float, float]

data

data: ndarray | Image | str

bitmap_scale

bitmap_scale: float = 1.0

draw

draw(draw_context: ImageDraw, transform: TransformFn, scale: float) -> None

get_bounds

get_bounds() -> Bounds

Ellipse dataclass

Ellipse(
    center: tuple[float, float],
    rx: float,
    ry: float,
    intensity: int = 255,
    fill: bool = False,
    width: int = 1,
)

Bases: SampleObject

An ellipse in the sample.

Parameters:

Name Type Description Default
center tuple[float, float]

Center (x, y) in world units.

required
rx float

X radius in world units.

required
ry float

Y radius in world units.

required
intensity int

Brightness value (0-255). Default 255.

255
fill bool

If True, fill the ellipse. If False, draw outline only. Default False.

False
width int

Outline width in pixels (only used if fill=False). Default 1.

1

center

center: tuple[float, float]

rx

rx: float

ry

ry: float

intensity

intensity: int = 255

fill

fill: bool = False

width

width: int = 1

draw

draw(draw_context: ImageDraw, transform: TransformFn, scale: float) -> None

draw_cv2

draw_cv2(img: ndarray, transform: TransformFn, scale: float) -> None

get_bounds

get_bounds() -> Bounds

Line dataclass

Line(
    start: tuple[float, float],
    end: tuple[float, float],
    intensity: int = 255,
    width: int = 1,
)

Bases: SampleObject

A line segment in the sample.

Parameters:

Name Type Description Default
start tuple[float, float]

Start point (x, y) in world units.

required
end tuple[float, float]

End point (x, y) in world units.

required
intensity int

Brightness value (0-255). Default 255.

255
width int

Line width in pixels. Default 1.

1

start

start: tuple[float, float]

end

end: tuple[float, float]

intensity

intensity: int = 255

width

width: int = 1

draw

draw(draw_context: ImageDraw, transform: TransformFn, scale: float) -> None

draw_cv2

draw_cv2(img: ndarray, transform: TransformFn, scale: float) -> None

get_bounds

get_bounds() -> Bounds

Point dataclass

Point(x: float, y: float, intensity: int = 255, radius: float = 2.0)

Bases: SampleObject

A circular point/spot in the sample.

Parameters:

Name Type Description Default
x float

X coordinate in world units.

required
y float

Y coordinate in world units.

required
intensity int

Brightness value (0-255). Default 255.

255
radius float

Radius in world units. Default 2.0.

2.0

x

x: float

y

y: float

intensity

intensity: int = 255

radius

radius: float = 2.0

draw

draw(draw_context: ImageDraw, transform: TransformFn, scale: float) -> None

draw_cv2

draw_cv2(img: ndarray, transform: TransformFn, scale: float) -> None

get_bounds

get_bounds() -> Bounds

Polygon dataclass

Polygon(
    vertices: Sequence[tuple[float, float]],
    intensity: int = 255,
    fill: bool = False,
    width: int = 1,
)

Bases: SampleObject

A polygon in the sample.

Parameters:

Name Type Description Default
vertices Sequence[tuple[float, float]]

List of (x, y) vertices in world units.

required
intensity int

Brightness value (0-255). Default 255.

255
fill bool

If True, fill the polygon. Default False.

False
width int

Outline width in pixels (only used if fill=False). Default 1.

1

vertices

vertices: Sequence[tuple[float, float]]

intensity

intensity: int = 255

fill

fill: bool = False

width

width: int = 1

draw

draw(draw_context: ImageDraw, transform: TransformFn, scale: float) -> None

draw_cv2

draw_cv2(img: ndarray, transform: TransformFn, scale: float) -> None

get_bounds

get_bounds() -> Bounds

Rectangle dataclass

Rectangle(
    top_left: tuple[float, float],
    width: float,
    height: float,
    intensity: int = 255,
    fill: bool = False,
    corner_radius: float = 0,
    line_width: int = 1,
)

Bases: SampleObject

A rectangle in the sample.

Parameters:

Name Type Description Default
top_left tuple[float, float]

Top-left corner (x, y) in world units.

required
width float

Width in world units.

required
height float

Height in world units.

required
intensity int

Brightness value (0-255). Default 255.

255
fill bool

If True, fill the rectangle. If False, draw outline only. Default False.

False
corner_radius float

Corner radius for rounded rectangles, in world units. Default 0.

0
line_width int

Outline width in pixels (only used if fill=False). Default 1.

1

top_left

top_left: tuple[float, float]

width

width: float

height

height: float

intensity

intensity: int = 255

fill

fill: bool = False

corner_radius

corner_radius: float = 0

line_width

line_width: int = 1

bottom_right property

bottom_right: tuple[float, float]

Bottom-right corner coordinates.

draw

draw(draw_context: ImageDraw, transform: TransformFn, scale: float) -> None

draw_cv2

draw_cv2(img: ndarray, transform: TransformFn, scale: float) -> None

get_bounds

get_bounds() -> Bounds

RegularPolygon dataclass

RegularPolygon(
    center: tuple[float, float],
    radius: float,
    n_sides: int,
    rotation: float = 0,
    intensity: int = 255,
    fill: bool = False,
    width: int = 1,
    _vertices: list[tuple[float, float]] = list(),
)

Bases: SampleObject

A regular polygon inscribed in a circle.

Parameters:

Name Type Description Default
center tuple[float, float]

Center (x, y) in world units.

required
radius float

Radius of bounding circle in world units.

required
n_sides int

Number of sides.

required
rotation float

Rotation angle in degrees. Default 0.

0
intensity int

Brightness value (0-255). Default 255.

255
fill bool

If True, fill the polygon. Default False.

False
width int

Outline width in pixels (only used if fill=False). Default 1.

1

center

center: tuple[float, float]

radius

radius: float

n_sides

n_sides: int

rotation

rotation: float = 0

intensity

intensity: int = 255

fill

fill: bool = False

width

width: int = 1

draw

draw(draw_context: ImageDraw, transform: TransformFn, scale: float) -> None

draw_cv2

draw_cv2(img: ndarray, transform: TransformFn, scale: float) -> None

get_bounds

get_bounds() -> Bounds

SampleObject

Bases: ABC

Base class for drawable sample objects.

All coordinates are in world-space (typically microns). Objects are rendered by calling draw() with a transform function that converts world coordinates to pixel coordinates.

Subclasses must implement: - draw(): Render the object onto a PIL ImageDraw context - get_bounds(): Return the bounding box in world coordinates - intensity: Property returning the object's intensity value

intensity

intensity: int

draw abstractmethod

draw(draw_context: ImageDraw, transform: TransformFn, scale: float) -> None

Draw this object onto the given context.

Parameters:

Name Type Description Default
draw_context ImageDraw

PIL ImageDraw context to draw on.

required
transform TransformFn

Function to convert (world_x, world_y) to (pixel_x, pixel_y).

required
scale float

Pixels per world unit (e.g., pixels per micron).

required

get_bounds abstractmethod

get_bounds() -> Bounds

Return bounding box in world coordinates.

Returns:

Type Description
Bounds

Tuple of (left, top, right, bottom) in world units.

draw_cv2

draw_cv2(img: ndarray, transform: TransformFn, scale: float) -> None

Draw this object using OpenCV (optional, for performance).

Default implementation falls back to PIL via a temporary image. Subclasses can override for better performance.

Parameters:

Name Type Description Default
img ndarray

Image array to draw on (modified in-place).

required
transform TransformFn

Function to convert (world_x, world_y) to (pixel_x, pixel_y).

required
scale float

Pixels per world unit.

required

should_draw

should_draw(fov_rect: Bounds) -> bool

Check if this object intersects the field of view.

Parameters:

Name Type Description Default
fov_rect Bounds

Field of view rectangle in world coordinates.

required

Returns:

Type Description
bool

True if object should be drawn (intersects FOV).

RenderConfig dataclass

RenderConfig(
    shot_noise: bool = True,
    defocus_scale: float = 0.125,
    base_blur: float = 1.5,
    random_seed: int | None = None,
    backend: Backend = "auto",
)

Configuration for the rendering engine.

Parameters:

Name Type Description Default
shot_noise bool

Whether to apply Poisson (shot) noise. Default True.

True
defocus_scale float

Blur radius per unit of Z distance from focus. Default 0.125. blur_radius = base_blur + abs(z) * defocus_scale

0.125
base_blur float

Minimum blur radius (at perfect focus). Default 1.0.

1.5
random_seed int | None

Random seed for reproducible noise. Default None (random).

None
backend Backend

Rendering backend: "auto" (default), "pil", or "cv2". "auto" uses cv2 if available, otherwise PIL. "cv2" raises ImportError if opencv-python is not installed.

'auto'

shot_noise

shot_noise: bool = True

defocus_scale

defocus_scale: float = 0.125

base_blur

base_blur: float = 1.5

random_seed

random_seed: int | None = None

backend

backend: Backend = 'auto'

Sample

Sample(objects: Iterable[SampleObject], config: RenderConfig | None = None)

A simulated microscope sample that integrates with CMMCorePlus.

Use sample.patch(core) as a context manager to intercept image acquisition calls and return rendered images based on microscope state.

Parameters:

Name Type Description Default
objects Iterable[SampleObject]

Sample objects to render (points, lines, shapes, etc.).

required
config RenderConfig | None

Rendering configuration. If None, uses default config.

None

objects property

objects: list[SampleObject]

List of sample objects.

config property

config: RenderConfig

Rendering configuration.

render

render(state: SummaryMetaV1) -> ndarray

Render the sample for the given microscope state.

patch

patch(core: CMMCorePlus) -> Generator[Sample, None, None]

Patch the core to use this sample for image generation.

Parameters:

Name Type Description Default
core CMMCorePlus

The core instance to patch.

required

Yields:

Type Description
Sample

This sample instance.

Functions

rects_intersect

rects_intersect(a: Bounds, b: Bounds) -> bool

Check if two rectangles intersect.

Parameters:

Name Type Description Default
a Bounds

First rectangle as (left, top, right, bottom).

required
b Bounds

Second rectangle as (left, top, right, bottom).

required

Returns:

Type Description
bool

True if rectangles intersect.

Attributes