goatpy.annotation

Add QuPath GeoJSON annotations to a SpatialData object produced by load_and_align().

The preferred workflow is to pass geojson_path directly to load_and_align() so annotations are transformed at registration time. Use add_qupath_annotations() when you want to add annotations to an already-built sdata object (e.g. loaded from disk).

The affine matrix stored in sdata[‘maldi_adata’].uns[‘he_transform’][‘affine_matrix’] is a 3x3 matrix mapping reg-resolution H&E coords -> buffer canvas coords. It was derived empirically by _fit_affine_from_pil() in auto_align.py, which fits a least-squares affine from a dense grid of point correspondences computed through PIL’s exact rotation geometry. This is robust to PIL’s internal conventions about expand=True, rotation centre, and canvas placement.

annotation.py then folds in the scale_to_reg and img_upscaling factors to produce the final native-H&E-pixel -> upscaled-canvas transform.

Functions

add_qupath_annotations(sdata, geojson_path[, ...])

annotations_to_pixels(→ spatialdata.SpatialData)

Label each pixel in sdata[table_name].obs with its annotation class.

annotate_per_pixel(→ spatialdata.SpatialData)

Add a categorical obs_column to maldi_adata.obs labelling each

Module Contents

goatpy.annotation.add_qupath_annotations(sdata, geojson_path, shapes_key='annotations', classification_key='classification', he_pixel_um=None)[source]
goatpy.annotation.annotations_to_pixels(sdata: spatialdata.SpatialData, shapes_key: str = 'annotations', classification_key: str = 'classification', table_name: str = 'maldi_adata', points_name: str = 'centroids', obs_column: str = 'annotation', other_label: str = 'other', coordinate_system: str = 'aligned') spatialdata.SpatialData[source]

Label each pixel in sdata[table_name].obs with its annotation class.

For each point in sdata.points[points_name], tests whether its (x, y) centroid falls within any polygon in sdata.shapes[shapes_key].

If coordinate_system=’global’, geometries and points are used as-is (i.e. automatic alignment where data is already in the correct space). Otherwise both shapes and points are transformed into coordinate_system before the point-in-polygon test (i.e. manual alignment).

The ID column in the points layer may be named ‘instance_id’ or ‘cell_id’; both are handled automatically and normalised to ‘instance_id’ for the join to adata.obs.

Parameters:
  • sdata (SpatialData object)

  • shapes_key (key in sdata.shapes holding annotation polygons)

  • classification_key (column in the shapes GeoDataFrame with class labels)

  • table_name (key in sdata.tables to annotate)

  • points_name (key in sdata.points holding pixel centroids)

  • obs_column (name of the new column added to adata.obs)

  • other_label (label for pixels outside all polygons)

  • coordinate_system (coordinate system to resolve coordinates in.) – Use ‘global’ for automatic alignment (no transform needed); use ‘aligned’ for manual alignment.

goatpy.annotation.annotate_per_pixel(sdata: spatialdata.SpatialData, shapes_key: str = 'annotations', classification_key: str = 'classification', table_name: str = 'maldi_adata', obs_column: str = 'annotation', overlap: float = 0.0, other_label: str = 'other', priority: list[str] | None = None, target_coordinate_system: str = 'global') spatialdata.SpatialData[source]

Add a categorical obs_column to maldi_adata.obs labelling each pixel with its annotation class (or other_label if none).

Parameters:
  • sdata (SpatialData) – Input spatial data object produced by load_and_align.

  • shapes_key (str) – Key in sdata.shapes containing the annotation GeoDataFrame. Default "annotations".

  • classification_key (str) – Column in the annotation GeoDataFrame holding class labels. Default "classification".

  • table_name (str) – Table in sdata.tables to annotate. Default "maldi_adata".

  • obs_column (str) – Name of the new column added to adata.obs. Default "annotation".

  • overlap (float) –

    Minimum fractional overlap (0–1) required for a pixel to be assigned to a class.

    • 0 (default) — centroid-in-polygon test via spatialdata.polygon_query. Fast.

    • > 0 — area-based test via Shapely intersection. A pixel is assigned only if intersection_area / pixel_area >= overlap. E.g. overlap=0.5 requires ≥ 50 % coverage. Slower.

  • other_label (str) – Label assigned to pixels that do not fall in any annotation. Default "other".

  • priority (list of str, optional) – Class names in ascending priority order. When a pixel overlaps multiple classes the last name in this list wins. If None, classes are iterated in the order they appear in the GeoDataFrame.

  • inplace (bool) – If True (default), modify sdata in place and return it. If False, work on a shallow copy of the table only (the obs column is still added to the copy stored in sdata — set False if you want to avoid mutating the original adata object).

Returns:

The input sdata with obs_column added to sdata[table_name].obs.

Return type:

SpatialData

Examples

>>> from goatpy.annotate import annotate_per_pixel

# Centroid-based (fast, default) >>> sdata = annotate_per_pixel(sdata)

# Area-based: pixel must be ≥ 50 % inside a polygon >>> sdata = annotate_per_pixel(sdata, overlap=0.5)

# Custom label for unannotated pixels >>> sdata = annotate_per_pixel(sdata, other_label=”background”)

# Control which class wins when polygons overlap >>> sdata = annotate_per_pixel(sdata, priority=[“Stroma”, “Tumor”])