Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Converting polygon annotations into raster labels aligned with imagery

Open In Colab

1. Motivation

Semantic segmentation models predict a class for every pixel, which means their training labels also need to be per-pixel. A polygon file on its own does not have that structure; it needs to be converted onto the same grid as the imagery first.


2. Core idea

Rasterization takes vector geometries and “burns” them onto a raster grid that matches a reference image’s resolution, extent, and CRS, assigning a pixel value wherever a geometry overlaps that pixel.


3. Workflow

A. Define an output mask path

Use a clear output path so the mask is easy to find and document.

'data/raw/willisau_2024_swissbuildings3D_clip.tif'

B. Rasterise vector polygons

Use the source image as the reference raster. This makes the output mask match the image grid.

array([[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]], shape=(1031, 2181), dtype=uint8)

assing reference_raster=raster_path is what makes the output mask share the exact same resolution, extent, and CRS as the source imagery. Without this, the mask and the image could end up on subtly different grids, which would misalign every pixel comparison you make later.

C. Visualise the mask

Check the created mask before using it for chips.

<Figure size 1800x1000 with 1 Axes>

The resulting building mask shows building pixels as a separate value from the background. This is the pixel-level label image used for segmentation-style training.

D. Document the class values

The exact values depend on the task. A binary mask has two values. A multi-class mask may use several integer class IDs.

Example:

0 = background
1 = building
2 = road
3 = vegetation

Document these values clearly. A mask without class-value documentation is difficult to reuse.

E. Control edge cases

Two things matter most in practice, though you will not always need to change them: buffering, which expands each geometry slightly outward before rasterizing and is useful for correcting small, systematic misalignments between imagery and annotations; and an all_touched option, which marks a pixel as part of a feature if the geometry touches it at all, rather than only when the pixel’s center falls inside the geometry. This matters most for small or thin features that might otherwise be missed entirely. Note that vector_to_raster() has no buffer_radius argument of its own — buffer the geometries yourself with GeoPandas before passing them in, as shown below.

array([[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]], shape=(1031, 2181), dtype=uint8)
<Figure size 1000x700 with 1 Axes>
<Figure size 1000x700 with 1 Axes>
<Figure size 1000x700 with 1 Axes>

Look closely at the same handful of small buildings across the three outputs:

  • In mask_default.tif, very narrow structures (sheds, carports) may appear thinner than in the vector file, or vanish entirely if no pixel center falls inside them.

  • In mask_buffered.tif, every building appears clearly larger than in the default mask, since the geometries were buffered outward by 10 m (20 pixels at the 0.5 m resolution of this image) before rasterizing. This deliberately large value makes the effect easy to see. To correct annotations that are systematically a pixel or two off from the true building edges, a buffer of about 0.5–1 m would be appropriate for this imagery.

  • In mask_all_touched.tif, thin or small features that disappeared in the default mask should now be at least partially visible, since a geometry only needs to touch a pixel, not contain its center, to be included.


4. Python reactivation

vector_path.replace(".geojson", ".tif") is ordinary Python string manipulation, the same pattern you used constantly in SDS210 to derive one file path from another. It is a small habit worth keeping: deriving output paths from input paths, rather than typing them separately, reduces the chance of accidentally pointing two variables at mismatched files.


5. Common pitfalls

  • Forgetting reference_raster. Without it, the output mask may not share the same grid as your imagery, which breaks the pixel-for-pixel correspondence segmentation training depends on.

  • Assuming rasterization is lossless. Very small or thin features (narrow roads, small outbuildings) can disappear entirely at coarse resolutions unless you use the all-touched option.

  • Not checking class value assignment for multi-class masks. If your vector file’s class attribute is inconsistent (mixed types, missing values), the resulting mask can silently assign the wrong class to some features.


6. Mini task

Using your own project’s checked image/annotation pair from the previous page (or the sample data here), rasterize the annotation and visually compare the output mask against the original vector overlay.


7. Key takeaways

  • Rasterization converts vector annotations into the pixel-level masks that segmentation models require.

  • vector_to_raster() needs a reference_raster to guarantee the output mask shares the source imagery’s resolution, extent, and CRS.

  • Buffering the vector geometries yourself (there is no built-in buffer_radius argument) and the all_touched option help correct small misalignments and preserve small or thin features.

  • An attribute_field extends the same process to multi-class masks.