Skip to content

Commit

Permalink
improved docstrings and error messages on exceptions
Browse files Browse the repository at this point in the history
  • Loading branch information
LucaMarconato committed Mar 29, 2024
1 parent 38e4281 commit 3b29f0b
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 31 deletions.
26 changes: 12 additions & 14 deletions src/spatialdata/_core/spatialdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,20 @@ class SpatialData:
"""
The SpatialData object.
The SpatialData object is a modular container for arbitrary combinations of SpatialElements. The elements
can be accesses separately and are stored as standard types (:class:`anndata.AnnData`,
The SpatialData object is a modular container for arbitrary combinations of SpatialElements and annotation tables.
The elements can be accesses separately and are stored as standard types (:class:`anndata.AnnData`,
:class:`geopandas.GeoDataFrame`, :class:`xarray.DataArray`).
The elements need to pass a validation step. To construct valid elements you can use the parsers that we
provide:
- :class:`~spatialdata.Image2DModel`,
- :class:`~spatialdata.Image3DModel`,
- :class:`~spatialdata.Labels2DModel`,
- :class:`~spatialdata.Labels3DModel`,
- :class:`~spatialdata.PointsModel`,
- :class:`~spatialdata.ShapesModel`,
- :class:`~spatialdata.TableModel`
Parameters
----------
Expand Down Expand Up @@ -96,18 +106,6 @@ class SpatialData:
The table can annotate regions (shapesor labels) and can be used to store additional information.
Points are not regions but 0-dimensional locations. They can't be annotated by a table, but they can store
annotation directly.
The elements need to pass a validation step. To construct valid elements you can use the parsers that we
provide:
- :class:`~spatialdata.Image2DModel`,
- :class:`~spatialdata.Image3DModel`,
- :class:`~spatialdata.Labels2DModel`,
- :class:`~spatialdata.Labels3DModel`,
- :class:`~spatialdata.PointsModel`,
- :class:`~spatialdata.ShapesModel`,
- :class:`~spatialdata.TableModel`
"""

@deprecation_alias(table="tables")
Expand Down
42 changes: 25 additions & 17 deletions src/spatialdata/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,10 @@ def validate(self, data: Any) -> None:
ValueError
If data is not valid.
"""
raise ValueError(f"Unsupported data type: {type(data)}.")
raise ValueError(
f"Unsupported data type: {type(data)}. Please use .parse() from Image2DModel, Image3DModel, Labels2DModel "
"or Labels3DModel to construct data that is guaranteed to be valid."
)

@validate.register(SpatialImage)
def _(self, data: SpatialImage) -> None:
Expand Down Expand Up @@ -316,26 +319,27 @@ def validate(cls, data: GeoDataFrame) -> None:
-------
None
"""
SUGGESTION = " Please use ShapesModel.parse() to construct data that is guaranteed to be valid."
if cls.GEOMETRY_KEY not in data:
raise KeyError(f"GeoDataFrame must have a column named `{cls.GEOMETRY_KEY}`.")
raise KeyError(f"GeoDataFrame must have a column named `{cls.GEOMETRY_KEY}`." + SUGGESTION)
if not isinstance(data[cls.GEOMETRY_KEY], GeoSeries):
raise ValueError(f"Column `{cls.GEOMETRY_KEY}` must be a GeoSeries.")
raise ValueError(f"Column `{cls.GEOMETRY_KEY}` must be a GeoSeries." + SUGGESTION)
if len(data[cls.GEOMETRY_KEY]) == 0:
raise ValueError(f"Column `{cls.GEOMETRY_KEY}` is empty.")
raise ValueError(f"Column `{cls.GEOMETRY_KEY}` is empty." + SUGGESTION)
geom_ = data[cls.GEOMETRY_KEY].values[0]
if not isinstance(geom_, (Polygon, MultiPolygon, Point)):
raise ValueError(
f"Column `{cls.GEOMETRY_KEY}` can only contain `Point`, `Polygon` or `MultiPolygon` shapes,"
f"but it contains {type(geom_)}."
f"but it contains {type(geom_)}." + SUGGESTION
)
if isinstance(geom_, Point):
if cls.RADIUS_KEY not in data.columns:
raise ValueError(f"Column `{cls.RADIUS_KEY}` not found.")
raise ValueError(f"Column `{cls.RADIUS_KEY}` not found." + SUGGESTION)
radii = data[cls.RADIUS_KEY].values
if np.any(radii <= 0):
raise ValueError("Radii of circles must be positive.")
raise ValueError("Radii of circles must be positive." + SUGGESTION)
if cls.TRANSFORM_KEY not in data.attrs:
raise ValueError(f":class:`geopandas.GeoDataFrame` does not contain `{TRANSFORM_KEY}`.")
raise ValueError(f":class:`geopandas.GeoDataFrame` does not contain `{TRANSFORM_KEY}`." + SUGGESTION)
if len(data) > 0:
n = data.geometry.iloc[0]._ndim
if n != 2:
Expand Down Expand Up @@ -482,12 +486,15 @@ def validate(cls, data: DaskDataFrame) -> None:
-------
None
"""
SUGGESTION = " Please use PointsModel.parse() to construct data that is guaranteed to be valid."
for ax in [X, Y, Z]:
if ax in data.columns:
# TODO: check why this can return int32 on windows.
assert data[ax].dtype in [np.int32, np.float32, np.float64, np.int64]
# TODO: check why this can return int32 on windows.
if ax in data.columns and data[ax].dtype not in [np.int32, np.float32, np.float64, np.int64]:
raise ValueError(f"Column `{ax}` must be of type `int` or `float`.")
if cls.TRANSFORM_KEY not in data.attrs:
raise ValueError(f":attr:`dask.dataframe.core.DataFrame.attrs` does not contain `{cls.TRANSFORM_KEY}`.")
raise ValueError(
f":attr:`dask.dataframe.core.DataFrame.attrs` does not contain `{cls.TRANSFORM_KEY}`." + SUGGESTION
)
if cls.ATTRS_KEY in data.attrs and "feature_key" in data.attrs[cls.ATTRS_KEY]:
feature_key = data.attrs[cls.ATTRS_KEY][cls.FEATURE_KEY]
if not isinstance(data[feature_key].dtype, CategoricalDtype):
Expand Down Expand Up @@ -794,19 +801,20 @@ def _validate_table_annotation_metadata(self, data: AnnData) -> None:
it is an internal validation of the annotation metadata of the table.
"""
SUGGESTION = " Please use TableModel.parse() to construct data that is guaranteed to be valid."
attr = data.uns[self.ATTRS_KEY]

if "region" not in attr:
raise ValueError(f"`region` not found in `adata.uns['{self.ATTRS_KEY}']`.")
raise ValueError(f"`region` not found in `adata.uns['{self.ATTRS_KEY}']`." + SUGGESTION)
if "region_key" not in attr:
raise ValueError(f"`region_key` not found in `adata.uns['{self.ATTRS_KEY}']`.")
raise ValueError(f"`region_key` not found in `adata.uns['{self.ATTRS_KEY}']`." + SUGGESTION)
if "instance_key" not in attr:
raise ValueError(f"`instance_key` not found in `adata.uns['{self.ATTRS_KEY}']`.")
raise ValueError(f"`instance_key` not found in `adata.uns['{self.ATTRS_KEY}']`." + SUGGESTION)

if attr[self.REGION_KEY_KEY] not in data.obs:
raise ValueError(f"`{attr[self.REGION_KEY_KEY]}` not found in `adata.obs`.")
raise ValueError(f"`{attr[self.REGION_KEY_KEY]}` not found in `adata.obs`. Please create the column.")
if attr[self.INSTANCE_KEY] not in data.obs:
raise ValueError(f"`{attr[self.INSTANCE_KEY]}` not found in `adata.obs`.")
raise ValueError(f"`{attr[self.INSTANCE_KEY]}` not found in `adata.obs`. Please create the column.")
if (dtype := data.obs[attr[self.INSTANCE_KEY]].dtype) not in [int, np.int16, np.int32, np.int64, "O"] or (
dtype == "O" and (val_dtype := type(data.obs[attr[self.INSTANCE_KEY]].iloc[0])) != str
):
Expand Down

0 comments on commit 3b29f0b

Please sign in to comment.