Tutorial 03

February 26, 2026 · View on GitHub

What you will learn

  • How to declare a desc.Rectangle descriptor
  • How the 2D viewer interaction works
  • How to read rectangle coordinates in processChunk
  • The pixel-space coordinate system (center + size format)
  • Graceful fallback when no rectangle is drawn

Prerequisites

  • Completed Tutorial 01.
  • Meshroom >= 2025.1 (required for 2D viewer shape support).

Declaring a Rectangle

desc.Rectangle(
    name="cropRegion",
    label="Crop Region",
    description="Draw a rectangle in the 2D viewer to define the crop area.",
    keyable=False,
)
  • keyable=False — a single static rectangle, shared across all views.
  • keyable=True, keyType="viewId" — one rectangle per image (per-view). Each view in the SfMData gets its own rectangle.

HelloCrop uses keyable=False for simplicity.

How the 2D viewer interaction works

  1. Select the HelloCrop node in the graph.
  2. Open the 2D viewer and load the input image.
  3. Draw a rectangle on the image. The viewer sends the coordinates to the node.
  4. When you compute the node, processChunk reads those coordinates.

See the How to test it section below for a concrete example with screenshots.

Reading coordinates in processChunk

observation = chunk.node.cropRegion.geometry.getObservation("0")
  • For keyable=False, the observation key is always "0".
  • For keyable=True, use str(view_id) to get the rectangle for a specific view.
  • Returns None if the user hasn't drawn a rectangle yet.

Coordinate system

The observation is a dictionary:

{
    "center": {"x": float, "y": float},
    "size": {"width": float, "height": float}
}

Coordinates are in pixel space (not normalized 0-1). To convert from center+size to a top-left corner for cropping:

x1 = int(center_x - width / 2)
y1 = int(center_y - height / 2)
x2 = int(center_x + width / 2)
y2 = int(center_y + height / 2)

Always clamp to image bounds before slicing:

x1 = max(0, x1)
y1 = max(0, y1)
x2 = min(image_width, x2)
y2 = min(image_height, y2)

Then crop with numpy slicing (row-first, column-second):

cropped = image[y1:y2, x1:x2]

Graceful fallback

When getObservation("0") returns None, no rectangle has been drawn. HelloCrop handles this by copying the full image as output and logging a warning:

if observation is None:
    chunk.logger.warning("No rectangle drawn. Outputting the full image.")
    cv2.imwrite(output_path, image)
    return

This ensures the node never crashes — it always produces an output.

How to test it

A pre-configured project is available: open meshroom/tuto03-hello-crop.mg in Meshroom (make sure you have run scripts/setup_projects.sh first). The rectangle is already drawn on the snail. Or set it up manually:

  1. Add a HelloCrop node to your graph.
  2. Set the Input Image field to the path of your test image. For this tutorial, we use hello-world-snail.png:
Input image: snail
  1. Drag and drop the same image into the Image Gallery panel (left side of Meshroom). This loads the image in the 2D viewer so you can draw the crop rectangle on it. A CameraInit node will automatically appear in the graph — this is normal, just ignore it.
  2. In the node attributes panel (bottom-right of the Meshroom window), click the small pencil icon Set Shape next to Crop Region. A rectangle will appear in the 2D viewer.
  3. Move and resize the rectangle to cover the area you want to crop:

Drawing a rectangle in the 2D viewer

  1. Click Compute.
  2. To view the output in the 2D viewer, double-click on the node or use the dropdown menu at the bottom of the 2D viewer and select Output Image.
  3. The output contains only the region inside the rectangle:

Cropped output in the 2D viewer

  1. Test without drawing a rectangle — the full image should be output.

Expected result

The output image contains exactly the pixels inside the drawn rectangle (plus any padding). The original image is not modified.