Skip to content

Commit 121abd1

Browse files
committed
Fix circle-shapes area in measure_obs: use pi*r**2, not shapely Point.area (=0)
Circles are stored as `Point` geometries with a `radius` column, for which shapely `.area` is 0 — so `measure_obs` wrote area=0 and equivalent_diameter=0 for every circle (surfaced on the real Visium spots dataset, all circles). Compute their area as `pi * r**2`; equivalent diameter then equals the true diameter `2*r`. Polygons/multipolygons still use the geometric area. Adds a regression test on `blobs_circles`.
1 parent 8c87330 commit 121abd1

2 files changed

Lines changed: 20 additions & 5 deletions

File tree

‎src/spatialdata_plot/pl/utils.py‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4527,12 +4527,15 @@ def _compute_element_measurements(sdata: SpatialData, element_name: str) -> pd.D
45274527
model = get_model(element)
45284528
if model is ShapesModel:
45294529
centroids = element.geometry.centroid
4530+
# Circles are stored as ``Point`` geometries with a ``radius`` column, for which
4531+
# shapely ``.area`` is 0; their area is ``pi * r**2``. Polygons/multipolygons use
4532+
# the true geometric area.
4533+
if "radius" in element.columns:
4534+
area = np.pi * np.asarray(element["radius"], dtype=float) ** 2
4535+
else:
4536+
area = element.geometry.area.to_numpy()
45304537
return pd.DataFrame(
4531-
{
4532-
"x": centroids.x.to_numpy(),
4533-
"y": centroids.y.to_numpy(),
4534-
"area": element.geometry.area.to_numpy(),
4535-
},
4538+
{"x": centroids.x.to_numpy(), "y": centroids.y.to_numpy(), "area": area},
45364539
index=element.index,
45374540
)
45384541
if model is Labels2DModel:

‎tests/pl/test_utils.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,18 @@ def test_writes_for_shapes(self, sdata_blobs: SpatialData) -> None:
554554
rtol=1e-9,
555555
)
556556

557+
def test_circles_area_is_pi_r_squared(self, sdata_blobs: SpatialData) -> None:
558+
# Circles are stored as Point geometries (radius column); shapely .area is 0 for them,
559+
# so area must be pi * r**2 and equivalent diameter must equal the true diameter 2*r.
560+
_add_shapes_table(sdata_blobs, "blobs_circles", name="circles_table")
561+
measure_obs(sdata_blobs, "blobs_circles", table_name="circles_table")
562+
table = sdata_blobs["circles_table"]
563+
gdf = sdata_blobs["blobs_circles"]
564+
order = table.obs["instance_id"].to_numpy()
565+
r = gdf["radius"].to_numpy()[[list(gdf.index).index(i) for i in order]]
566+
np.testing.assert_allclose(table.obs["area"].to_numpy(), np.pi * r**2, rtol=1e-9)
567+
np.testing.assert_allclose(table.obs["equivalent_diameter"].to_numpy(), 2.0 * r, rtol=1e-9)
568+
557569
def test_inplace_false_leaves_original_untouched(self, sdata_blobs: SpatialData) -> None:
558570
out = measure_obs(sdata_blobs, "blobs_labels", inplace=False)
559571
assert isinstance(out, SpatialData)

0 commit comments

Comments
 (0)