From def8e6b8f5f75a2b9af18ccb8962de407d6ebafb Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 12:13:25 -0400 Subject: [PATCH 01/14] fixed donot-multipolygons --- src/spatialdata_plot/pl/utils.py | 263 +++++++++---------------------- 1 file changed, 78 insertions(+), 185 deletions(-) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index c2941d91..5b899691 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -10,11 +10,11 @@ from typing import Any, Literal, Union import matplotlib +import matplotlib.patches import matplotlib.patches as mpatches import matplotlib.patches as mplp import matplotlib.path as mpath import matplotlib.pyplot as plt -import multiscale_spatial_image as msi import numpy as np import pandas as pd import shapely @@ -48,7 +48,6 @@ from scanpy.plotting._tools.scatterplots import _add_categorical_legend from scanpy.plotting._utils import add_colors_for_categorical_sample_annotation from scanpy.plotting.palettes import default_20, default_28, default_102 -from shapely.geometry import LineString, Polygon from skimage.color import label2rgb from skimage.morphology import erosion, square from skimage.segmentation import find_boundaries @@ -233,6 +232,30 @@ def _sanitise_na_color(na_color: ColorLike | None) -> tuple[str, bool]: raise ValueError(f"Invalid na_color value: {na_color}") +def _get_centroid_of_pathpatch(pathpatch: matplotlib.patches.PathPatch) -> tuple[float, float]: + # Extract the vertices from the PathPatch + path = pathpatch.get_path() + vertices = path.vertices + x = vertices[:, 0] + y = vertices[:, 1] + + area = 0.5 * np.sum(x[:-1] * y[1:] - x[1:] * y[:-1]) + + # Calculate the centroid coordinates + centroid_x = np.sum((x[:-1] + x[1:]) * (x[:-1] * y[1:] - x[1:] * y[:-1])) / (6 * area) + centroid_y = np.sum((y[:-1] + y[1:]) * (x[:-1] * y[1:] - x[1:] * y[:-1])) / (6 * area) + + return centroid_x, centroid_y + + +def _scale_pathpatch_around_centroid(pathpatch: matplotlib.patches.PathPatch, scale_factor: float) -> None: + + centroid = _get_centroid_of_pathpatch(pathpatch) + vertices = pathpatch.get_path().vertices + scaled_vertices = np.array([centroid + (vertex - centroid) * scale_factor for vertex in vertices]) + pathpatch.get_path().vertices = scaled_vertices + + def _get_collection_shape( shapes: list[GeoDataFrame], c: Any, @@ -302,63 +325,64 @@ def _get_collection_shape( outline_c = outline_c * fill_c.shape[0] shapes_df = pd.DataFrame(shapes, copy=True) - - # remove empty points/polygons shapes_df = shapes_df[shapes_df["geometry"].apply(lambda geom: not geom.is_empty)] - - # reset index of shapes_df for case of spatial query shapes_df = shapes_df.reset_index(drop=True) - rows = [] - - def assign_fill_and_outline_to_row( - shapes: list[GeoDataFrame], fill_c: list[Any], outline_c: list[Any], row: pd.Series, idx: int + def _assign_fill_and_outline_to_row( + fill_c: list[Any], outline_c: list[Any], row: dict[str, Any], idx: int, is_multiple_shapes: bool ) -> None: try: - if len(shapes) > 1 and len(fill_c) == 1: - row["fill_c"] = fill_c - row["outline_c"] = outline_c + if is_multiple_shapes and len(fill_c) == 1: + row["fill_c"] = fill_c[0] + row["outline_c"] = outline_c[0] else: row["fill_c"] = fill_c[idx] row["outline_c"] = outline_c[idx] except IndexError as e: - raise IndexError("Could not assign fill and outline colors due to a mismatch in row-numbers.") from e - - # Match colors to the geometry, potentially expanding the row in case of - # multipolygons - for idx, row in shapes_df.iterrows(): - geom = row["geometry"] - if geom.geom_type == "Polygon": - row = row.to_dict() - coords = np.array(geom.exterior.coords) - centroid = np.mean(coords, axis=0) - scaled_coords = [(centroid + (np.array(coord) - centroid) * s).tolist() for coord in geom.exterior.coords] - row["geometry"] = mplp.Polygon(scaled_coords, closed=True) - assign_fill_and_outline_to_row(shapes, fill_c, outline_c, row, idx) - rows.append(row) - - elif geom.geom_type == "MultiPolygon": - # mp = _make_patch_from_multipolygon(geom) - for polygon in geom.geoms: - mp_copy = row.to_dict() - coords = np.array(polygon.exterior.coords) - centroid = np.mean(coords, axis=0) - scaled_coords = [(centroid + (coord - centroid) * s).tolist() for coord in coords] - mp_copy["geometry"] = mplp.Polygon(scaled_coords, closed=True) - assign_fill_and_outline_to_row(shapes, fill_c, outline_c, mp_copy, idx) - rows.append(mp_copy) - - elif geom.geom_type == "Point": - row = row.to_dict() - scaled_radius = row["radius"] * s - row["geometry"] = mplp.Circle( - (geom.x, geom.y), radius=scaled_radius - ) # Circle is always scaled from its center - assign_fill_and_outline_to_row(shapes, fill_c, outline_c, row, idx) - rows.append(row) - - patches = pd.DataFrame(rows) - + raise IndexError("Could not assign fill and outline colors due to a mismatch in row numbers.") from e + + def _process_polygon(row: pd.Series, s: float) -> dict[str, Any]: + coords = np.array(row["geometry"].exterior.coords) + centroid = np.mean(coords, axis=0) + scaled_coords = (centroid + (coords - centroid) * s).tolist() + return {**row.to_dict(), "geometry": mplp.Polygon(scaled_coords, closed=True)} + + def _process_multipolygon(row: pd.Series, s: float) -> list[dict[str, Any]]: + mp = _make_patch_from_multipolygon(row["geometry"]) + row_dict = row.to_dict() + for m in mp: + _scale_pathpatch_around_centroid(m, s) + + return [{**row_dict, "geometry": m} for m in mp] + + def _process_point(row: pd.Series, s: float) -> dict[str, Any]: + return { + **row.to_dict(), + "geometry": mplp.Circle((row["geometry"].x, row["geometry"].y), radius=row["radius"] * s), + } + + def _create_patches(shapes_df: GeoDataFrame, fill_c: list[Any], outline_c: list[Any], s: float) -> pd.DataFrame: + rows = [] + is_multiple_shapes = len(shapes_df) > 1 + + for idx, row in shapes_df.iterrows(): + geom_type = row["geometry"].geom_type + processed_rows = [] + + if geom_type == "Polygon": + processed_rows.append(_process_polygon(row, s)) + elif geom_type == "MultiPolygon": + processed_rows.extend(_process_multipolygon(row, s)) + elif geom_type == "Point": + processed_rows.append(_process_point(row, s)) + + for processed_row in processed_rows: + _assign_fill_and_outline_to_row(fill_c, outline_c, processed_row, idx, is_multiple_shapes) + rows.append(processed_row) + + return pd.DataFrame(rows) + + patches = _create_patches(shapes_df, fill_c, outline_c, s) return PatchCollection( patches["geometry"].values.tolist(), snap=False, @@ -799,7 +823,8 @@ def _generate_base_categorial_color_mapping( color_source_vector: ArrayLike | pd.Series[CategoricalDtype], na_color: ColorLike, ) -> Mapping[str, str]: - if adata is not None and cluster_key in adata.uns: + if adata is not None and cluster_key in adata.uns and f"{cluster_key}_colors" in adata.uns: + # user probably used scanpy before to process the adata colors = adata.uns[f"{cluster_key}_colors"] categories = color_source_vector.categories.tolist() + ["NaN"] if "#" not in na_color: @@ -823,9 +848,9 @@ def _modify_categorical_color_mapping( modified_mapping = {key: mapping[key] for key in mapping if key in groups or key == "NaN"} elif len(palette) == len(groups) and isinstance(groups, list) and isinstance(palette, list): modified_mapping = dict(zip(groups, palette)) - else: raise ValueError(f"Expected palette to be of length `{len(groups)}`, found `{len(palette)}`.") + return modified_mapping @@ -841,7 +866,7 @@ def _get_default_categorial_color_mapping( palette = default_102 else: palette = ["grey" for _ in range(len_cat)] - logger.info("input has more than 103 categories. Uniform " "'grey' color will be used for all categories.") + logger.info("input has more than 103 categories. Uniform 'grey' color will be used for all categories.") return {cat: to_hex(to_rgba(col)[:3]) for cat, col in zip(color_source_vector.categories, palette[:len_cat])} @@ -872,54 +897,6 @@ def _get_categorical_color_mapping( return _modify_categorical_color_mapping(base_mapping, groups, palette) -def _get_palette( - categories: Sequence[Any], - adata: AnnData | None = None, - cluster_key: None | str = None, - palette: ListedColormap | str | list[str] | None = None, - alpha: float = 1.0, -) -> Mapping[str, str] | None: - palette = None if isinstance(palette, list) and palette[0] is None else palette - if adata is not None and palette is None: - try: - palette = adata.uns[f"{cluster_key}_colors"] # type: ignore[arg-type] - if len(palette) != len(categories): - raise ValueError( - f"Expected palette to be of length `{len(categories)}`, found `{len(palette)}`. " - + f"Removing the colors in `adata.uns` with `adata.uns.pop('{cluster_key}_colors')` may help." - ) - return {cat: to_hex(to_rgba(col)[:3]) for cat, col in zip(categories, palette)} - except KeyError as e: - logger.warning(e) - return None - - len_cat = len(categories) - - if palette is None: - if len_cat <= 20: - palette = default_20 - elif len_cat <= 28: - palette = default_28 - elif len_cat <= len(default_102): # 103 colors - palette = default_102 - else: - palette = ["grey" for _ in range(len_cat)] - logger.info("input has more than 103 categories. Uniform " "'grey' color will be used for all categories.") - return {cat: to_hex(to_rgba(col)[:3]) for cat, col in zip(categories, palette[:len_cat])} - - if isinstance(palette, str): - cmap = ListedColormap([palette]) - elif isinstance(palette, list): - cmap = ListedColormap(palette) - elif isinstance(palette, ListedColormap): - cmap = palette - else: - raise TypeError(f"Palette is {type(palette)} but should be string or list.") - palette = [to_hex(np.round(x, 5)) for x in cmap(np.linspace(0, 1, len_cat), alpha=alpha)] - - return dict(zip(categories, palette)) - - def _maybe_set_colors( source: AnnData, target: AnnData, key: str, palette: str | ListedColormap | Cycler | Sequence[Any] | None = None ) -> None: @@ -1087,34 +1064,6 @@ def save_fig(fig: Figure, path: str | Path, make_dir: bool = True, ext: str = "p fig.savefig(path, **kwargs) -def _get_cs_element_map( - element: str | Sequence[str] | None, - element_map: Mapping[str, Any], -) -> Mapping[str, str]: - """Get the mapping between the coordinate system and the class.""" - # from spatialdata.models import Image2DModel, Image3DModel, Labels2DModel, Labels3DModel, PointsModel, ShapesModel - element = list(element_map.keys())[0] if element is None else element - element = [element] if isinstance(element, str) else element - d = {} - for e in element: - cs = list(element_map[e].attrs["transform"].keys())[0] - d[cs] = e - # model = get_model(element_map["blobs_labels"]) - # if model in [Image2DModel, Image3DModel, Labels2DModel, Labels3DModel] - return d - - -def _multiscale_to_image(sdata: sd.SpatialData) -> sd.SpatialData: - if sdata.images is None: - raise ValueError("No images found in the SpatialData object.") - - for k, v in sdata.images.items(): - if isinstance(v, msi.multiscale_spatial_image.DataTree): - sdata.images[k] = Image2DModel.parse(v["scale0"].ds.to_array().squeeze(axis=0)) - - return sdata - - def _get_linear_colormap(colors: list[str], background: str) -> list[LinearSegmentedColormap]: return [LinearSegmentedColormap.from_list(c, [background, c], N=256) for c in colors] @@ -1126,62 +1075,6 @@ def _get_listed_colormap(color_dict: dict[str, str]) -> ListedColormap: return ListedColormap(["black"] + colors, N=len(colors) + 1) -def _translate_image( - image: DataArray, - translation: sd.transformations.transformations.Translation, -) -> DataArray: - shifts: dict[str, int] = {axis: int(translation.translation[idx]) for idx, axis in enumerate(translation.axes)} - img = image.values.copy() - # for yx images (important for rasterized MultiscaleImages as labels) - expanded_dims = False - if len(img.shape) == 2: - img = np.expand_dims(img, axis=0) - expanded_dims = True - - shifted_channels = [] - - # split channels, shift axes individually, them recombine - if len(img.shape) == 3: - for c in range(img.shape[0]): - channel = img[c, :, :] - - # iterates over [x, y] - for axis, shift in shifts.items(): - pad_x, pad_y = (0, 0), (0, 0) - if axis == "x" and shift > 0: - pad_x = (abs(shift), 0) - elif axis == "x" and shift < 0: - pad_x = (0, abs(shift)) - - if axis == "y" and shift > 0: - pad_y = (abs(shift), 0) - elif axis == "y" and shift < 0: - pad_y = (0, abs(shift)) - - channel = np.pad(channel, (pad_y, pad_x), mode="constant") - - shifted_channels.append(channel) - - if expanded_dims: - return Labels2DModel.parse( - np.array(shifted_channels[0]), - dims=["y", "x"], - transformations=image.attrs["transform"], - ) - return Image2DModel.parse( - np.array(shifted_channels), - dims=["c", "y", "x"], - transformations=image.attrs["transform"], - ) - - -def _convert_polygon_to_linestrings(polygon: Polygon) -> list[LineString]: - b = polygon.boundary.coords - linestrings = [LineString(b[k : k + 2]) for k in range(len(b) - 1)] - - return [list(ls.coords) for ls in linestrings] - - def _split_multipolygon_into_outer_and_inner(mp: shapely.MultiPolygon): # type: ignore # https://stackoverflow.com/a/21922058 From 7d8c2e8e21207977834f61ee6a6d9d0fae4ef30f Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 12:45:10 -0400 Subject: [PATCH 02/14] updated tests --- .../Shapes_can_render_multipolygons.png | Bin 10555 -> 9619 bytes tests/pl/test_render_shapes.py | 18 +++++++++--------- tests/pl/test_utils.py | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/_images/Shapes_can_render_multipolygons.png b/tests/_images/Shapes_can_render_multipolygons.png index fddad313d661527b89b498146b01eaf93d465223..40f798e1f524dc0ec1a1a840145c7ffcb56190f5 100644 GIT binary patch literal 9619 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 diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index 442fcb69..75841265 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -31,28 +31,28 @@ def test_plot_can_render_circles(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_shapes(element="blobs_circles").pl.show() def test_plot_can_render_circles_with_outline(self, sdata_blobs: SpatialData): - sdata_blobs.pl.render_shapes(element="blobs_circles", outline=True).pl.show() + sdata_blobs.pl.render_shapes(element="blobs_circles", outline_alpha=1).pl.show() def test_plot_can_render_circles_with_colored_outline(self, sdata_blobs: SpatialData): - sdata_blobs.pl.render_shapes(element="blobs_circles", outline=True, outline_color="red").pl.show() + sdata_blobs.pl.render_shapes(element="blobs_circles", outline_alpha=1, outline_color="red").pl.show() def test_plot_can_render_polygons(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_shapes(element="blobs_polygons").pl.show() def test_plot_can_render_polygons_with_outline(self, sdata_blobs: SpatialData): - sdata_blobs.pl.render_shapes(element="blobs_polygons", outline=True).pl.show() + sdata_blobs.pl.render_shapes(element="blobs_polygons", outline_alpha=1).pl.show() def test_plot_can_render_polygons_with_str_colored_outline(self, sdata_blobs: SpatialData): - sdata_blobs.pl.render_shapes(element="blobs_polygons", outline=True, outline_color="red").pl.show() + sdata_blobs.pl.render_shapes(element="blobs_polygons", outline_alpha=1, outline_color="red").pl.show() def test_plot_can_render_polygons_with_rgb_colored_outline(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_shapes( - element="blobs_polygons", outline=True, outline_color=(0.0, 0.0, 1.0, 1.0) + element="blobs_polygons", outline_alpha=1, outline_color=(0.0, 0.0, 1.0, 1.0) ).pl.show() def test_plot_can_render_polygons_with_rgba_colored_outline(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_shapes( - element="blobs_polygons", outline=True, outline_color=(0.0, 1.0, 0.0, 1.0) + element="blobs_polygons", outline_alpha=1, outline_color=(0.0, 1.0, 0.0, 1.0) ).pl.show() def test_plot_can_render_empty_geometry(self, sdata_blobs: SpatialData): @@ -60,10 +60,10 @@ def test_plot_can_render_empty_geometry(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_shapes().pl.show() def test_plot_can_render_circles_with_default_outline_width(self, sdata_blobs: SpatialData): - sdata_blobs.pl.render_shapes(element="blobs_circles", outline=True).pl.show() + sdata_blobs.pl.render_shapes(element="blobs_circles", outline_alpha=1).pl.show() def test_plot_can_render_circles_with_specified_outline_width(self, sdata_blobs: SpatialData): - sdata_blobs.pl.render_shapes(element="blobs_circles", outline=True, outline_width=3.0).pl.show() + sdata_blobs.pl.render_shapes(element="blobs_circles", outline_alpha=1, outline_width=3.0).pl.show() def test_plot_can_render_multipolygons(self): def _make_multi(): @@ -91,7 +91,7 @@ def _make_multi(): adata.obs.loc[:, "val"] = [0, 1, 2, 3] table = TableModel.parse(adata, region="p", region_key="region", instance_key="val") sdata["table"] = table - sdata.pl.render_shapes(color="val", outline=True, fill_alpha=0.3).pl.show() + sdata.pl.render_shapes(color="val", fill_alpha=0.3).pl.show() def test_plot_can_color_from_geodataframe(self, sdata_blobs: SpatialData): blob = deepcopy(sdata_blobs) diff --git a/tests/pl/test_utils.py b/tests/pl/test_utils.py index 498c738d..7f351a9c 100644 --- a/tests/pl/test_utils.py +++ b/tests/pl/test_utils.py @@ -40,7 +40,7 @@ class TestUtils(PlotTester, metaclass=PlotTesterMeta): ], ) def test_plot_set_outline_accepts_str_or_float_or_list_thereof(self, sdata_blobs: SpatialData, outline_color): - sdata_blobs.pl.render_shapes(element="blobs_polygons", outline=True, outline_color=outline_color).pl.show() + sdata_blobs.pl.render_shapes(element="blobs_polygons", outline_alpha=1, outline_color=outline_color).pl.show() @pytest.mark.parametrize( "colname", @@ -152,3 +152,4 @@ def test_utils_get_subplots_produces_correct_axs_layout(input_output): assert len_axs == len(axs.flatten()) assert axs_visible == [ax.axison for ax in axs.flatten()] + From 67f2cc931a6e09eb8229540c17ea48daabd26ea4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 16:45:30 +0000 Subject: [PATCH 03/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/pl/test_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/pl/test_utils.py b/tests/pl/test_utils.py index 7f351a9c..1527c9fa 100644 --- a/tests/pl/test_utils.py +++ b/tests/pl/test_utils.py @@ -152,4 +152,3 @@ def test_utils_get_subplots_produces_correct_axs_layout(input_output): assert len_axs == len(axs.flatten()) assert axs_visible == [ax.axison for ax in axs.flatten()] - From ab9b46fc487f57ebe5fa92f2de5ee11e37fad4ef Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 13:14:14 -0400 Subject: [PATCH 04/14] Removed partial fix for issue outside of scope of this PR --- src/spatialdata_plot/pl/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 5b899691..1d2a9d5a 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -823,8 +823,7 @@ def _generate_base_categorial_color_mapping( color_source_vector: ArrayLike | pd.Series[CategoricalDtype], na_color: ColorLike, ) -> Mapping[str, str]: - if adata is not None and cluster_key in adata.uns and f"{cluster_key}_colors" in adata.uns: - # user probably used scanpy before to process the adata + if adata is not None and cluster_key in adata.uns: colors = adata.uns[f"{cluster_key}_colors"] categories = color_source_vector.categories.tolist() + ["NaN"] if "#" not in na_color: From f65900078d3d2218fad58109eb57c98670dd46ae Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 14:23:50 -0400 Subject: [PATCH 05/14] simplified test --- src/spatialdata_plot/pl/utils.py | 2 +- tests/pl/test_render_labels.py | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 1d2a9d5a..5dfddb5c 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -762,7 +762,7 @@ def _map_color_seg( cell_id = np.array(cell_id) if color_vector is not None and isinstance(color_vector.dtype, pd.CategoricalDtype): # users wants to plot a categorical column - if isinstance(na_color, tuple) and len(na_color) == 4 and np.any(color_source_vector.isna()): + if np.any(color_source_vector.isna()): cell_id[color_source_vector.isna()] = 0 val_im: ArrayLike = map_array(seg, cell_id, color_vector.codes + 1) cols = colors.to_rgba_array(color_vector.categories) diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index 639c51d9..ab762489 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -149,12 +149,9 @@ def _make_tablemodel_with_categorical_labels(self, sdata_blobs, label): sdata_blobs_local = deepcopy(sdata_blobs) n_obs = len(get_element_instances(sdata_blobs_local[label])) - vals = np.arange(n_obs) - obs = pd.DataFrame({"a": vals, "b": vals + 0.3, "c": vals + 0.7}) - - adata = AnnData(vals.reshape(-1, 1), obs=obs) - adata.obs["instance_id"] = vals - adata.obs["category"] = list(["a", "b", "c"] * ((n_obs // 3) + 1))[:n_obs] + vals = np.arange(n_obs) + 1 + adata = AnnData(vals.reshape(-1, 1), obs=pd.DataFrame({"instance_id": vals})) + adata.obs["category"] = pd.Categorical(list(["a", "b", "c"] * ((n_obs // 3) + 1))[:n_obs]) adata.obs["region"] = label table = TableModel.parse( adata=adata, @@ -162,11 +159,9 @@ def _make_tablemodel_with_categorical_labels(self, sdata_blobs, label): instance_key="instance_id", region=label, ) - sdata_blobs_local["other_table"] = table - sdata_blobs_local["other_table"].obs["category"] = ( - sdata_blobs_local["other_table"].obs["category"].astype("category") - ) - sdata_blobs_local.pl.render_labels(label, color="category", outline_alpha=0.0, table="other_table").pl.show() + sdata_blobs_local.tables["other_table"] = table + + sdata_blobs_local.pl.render_labels(label, color="category", table="other_table", scale="scale0").pl.show() def test_plot_subset_categorical_label_maintains_order(self, sdata_blobs: SpatialData): max_col = sdata_blobs.table.to_df().idxmax(axis=1) From e1edaeea17176cae45b6ad98482830523dd1a466 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 14:30:16 -0400 Subject: [PATCH 06/14] added img from runner, renamed test --- ...abel_can_use_categorical_to_color_labels.png | Bin 0 -> 32283 bytes .../_images/Labels_label_categorical_color.png | Bin 37835 -> 0 bytes tests/pl/test_render_labels.py | 15 ++++++++------- 3 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 tests/_images/Labels_label_can_use_categorical_to_color_labels.png delete mode 100644 tests/_images/Labels_label_categorical_color.png diff --git a/tests/_images/Labels_label_can_use_categorical_to_color_labels.png b/tests/_images/Labels_label_can_use_categorical_to_color_labels.png new file mode 100644 index 0000000000000000000000000000000000000000..4e9bd5aece8c9844e71a09ff0fa40770578fd675 GIT binary patch literal 32283 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

Date: Wed, 28 Aug 2024 14:33:34 -0400 Subject: [PATCH 07/14] Now giving categories in test explicit order --- tests/pl/test_render_labels.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index b2b0bf99..c1fe4275 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -152,7 +152,11 @@ def _make_tablemodel_with_categorical_labels(self, sdata_blobs, label): n_obs = len(get_element_instances(sdata_blobs[label])) vals = np.arange(n_obs) + 1 adata = AnnData(vals.reshape(-1, 1), obs=pd.DataFrame({"instance_id": vals})) - adata.obs["category"] = pd.Categorical(list(["a", "b", "c"] * ((n_obs // 3) + 1))[:n_obs]) + adata.obs["category"] = pd.Categorical( + list(["a", "b", "c"] * ((n_obs // 3) + 1))[:n_obs], + categories=["a", "b", "c"], + ordered=True, + ) adata.obs["region"] = label table = TableModel.parse( adata=adata, From 271245023ffa49fbf5b75b0acded71df4907ff67 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 14:39:52 -0400 Subject: [PATCH 08/14] adjusted name of test --- ..._color_labels_by_categorical_variable.png} | Bin tests/pl/test_render_labels.py | 36 ++++++++++++++++++ 2 files changed, 36 insertions(+) rename tests/_images/{Labels_label_can_use_categorical_to_color_labels.png => Labels_can_color_labels_by_categorical_variable.png} (100%) diff --git a/tests/_images/Labels_label_can_use_categorical_to_color_labels.png b/tests/_images/Labels_can_color_labels_by_categorical_variable.png similarity index 100% rename from tests/_images/Labels_label_can_use_categorical_to_color_labels.png rename to tests/_images/Labels_can_color_labels_by_categorical_variable.png diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index c1fe4275..bf26dc98 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -82,6 +82,42 @@ def test_plot_can_stack_render_labels(self, sdata_blobs: SpatialData): def test_plot_can_color_labels_by_continuous_variable(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_labels("blobs_labels", color="channel_0_sum").pl.show() + @pytest.mark.parametrize( + "label", + [ + "blobs_labels", + "blobs_multiscale_labels", + ], + ) + def test_plot_can_color_labels_by_categorical_variable(self, sdata_blobs: SpatialData, label: str): + + def _make_tablemodel_with_categorical_labels(self, sdata_blobs, label): + + n_obs = len(get_element_instances(sdata_blobs[label])) + vals = np.arange(n_obs) + 1 + adata = AnnData(vals.reshape(-1, 1), obs=pd.DataFrame({"instance_id": vals})) + adata.obs["category"] = pd.Categorical( + list(["a", "b", "c"] * ((n_obs // 3) + 1))[:n_obs], + categories=["a", "b", "c"], + ordered=True, + ) + adata.obs["region"] = label + table = TableModel.parse( + adata=adata, + region_key="region", + instance_key="instance_id", + region=label, + ) + sdata_blobs.tables["other_table"] = table + + sdata_blobs.pl.render_labels(label, color="category", table="other_table", scale="scale0").pl.show() + sdata_blobs.pl.render_labels("blobs_labels", color="channel_0_sum").pl.show() + + # we're modifying the data here, so we need an independent copy + sdata_blobs_local = deepcopy(sdata_blobs) + + _make_tablemodel_with_categorical_labels(sdata_blobs_local, label) + def test_plot_two_calls_with_coloring_result_in_two_colorbars(self, sdata_blobs: SpatialData): # we're modifying the data here so we need an independent copy sdata_blobs_local = deepcopy(sdata_blobs) From f2be861059d79304ffcc5e75b47e83f890bc7733 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 14:44:54 -0400 Subject: [PATCH 09/14] removed duplicate of test --- tests/pl/test_render_labels.py | 36 +--------------------------------- 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index bf26dc98..d1010218 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -91,7 +91,7 @@ def test_plot_can_color_labels_by_continuous_variable(self, sdata_blobs: Spatial ) def test_plot_can_color_labels_by_categorical_variable(self, sdata_blobs: SpatialData, label: str): - def _make_tablemodel_with_categorical_labels(self, sdata_blobs, label): + def _make_tablemodel_with_categorical_labels(sdata_blobs, label): n_obs = len(get_element_instances(sdata_blobs[label])) vals = np.arange(n_obs) + 1 @@ -170,40 +170,6 @@ def test_can_plot_with_one_element_color_table(self, sdata_blobs: SpatialData): "blobs_multiscale_labels", color="channel_1_sum", table_name="multi_table" ).pl.show() - @pytest.mark.parametrize( - "label", - [ - "blobs_labels", - "blobs_multiscale_labels", - ], - ) - def test_plot_label_can_use_categorical_to_color_labels(self, sdata_blobs: SpatialData, label: str): - # we're modifying the data here, so we need an independent copy - sdata_blobs_local = deepcopy(sdata_blobs) - - self._make_tablemodel_with_categorical_labels(sdata_blobs_local, label) - - def _make_tablemodel_with_categorical_labels(self, sdata_blobs, label): - - n_obs = len(get_element_instances(sdata_blobs[label])) - vals = np.arange(n_obs) + 1 - adata = AnnData(vals.reshape(-1, 1), obs=pd.DataFrame({"instance_id": vals})) - adata.obs["category"] = pd.Categorical( - list(["a", "b", "c"] * ((n_obs // 3) + 1))[:n_obs], - categories=["a", "b", "c"], - ordered=True, - ) - adata.obs["region"] = label - table = TableModel.parse( - adata=adata, - region_key="region", - instance_key="instance_id", - region=label, - ) - sdata_blobs.tables["other_table"] = table - - sdata_blobs.pl.render_labels(label, color="category", table="other_table", scale="scale0").pl.show() - def test_plot_subset_categorical_label_maintains_order(self, sdata_blobs: SpatialData): max_col = sdata_blobs.table.to_df().idxmax(axis=1) max_col = pd.Categorical(max_col, categories=sdata_blobs.table.to_df().columns, ordered=True) From 3906ab56b35b5102f0fd8154d50ecc0ee11c861d Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 14:52:25 -0400 Subject: [PATCH 10/14] fixed test --- .github/workflows/test.yaml | 8 +------- tests/pl/test_render_labels.py | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 29af15df..0418a980 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -57,17 +57,11 @@ jobs: DISPLAY: :42 run: | pytest -v --cov --color=yes --cov-report=xml - # - name: Generate GH action "groundtruth" figures as artifacts, uncomment if needed - # if: always() - # uses: actions/upload-artifact@v3 - # with: - # name: groundtruth-figures - # path: /home/runner/work/spatialdata-plot/spatialdata-plot/tests/_images/* - name: Archive figures generated during testing if: always() uses: actions/upload-artifact@v3 with: - name: plotting-results + name: plotting-results-${{ matrix.os }} path: /home/runner/work/spatialdata-plot/spatialdata-plot/tests/figures/* - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 diff --git a/tests/pl/test_render_labels.py b/tests/pl/test_render_labels.py index d1010218..850d3bf1 100644 --- a/tests/pl/test_render_labels.py +++ b/tests/pl/test_render_labels.py @@ -111,7 +111,6 @@ def _make_tablemodel_with_categorical_labels(sdata_blobs, label): sdata_blobs.tables["other_table"] = table sdata_blobs.pl.render_labels(label, color="category", table="other_table", scale="scale0").pl.show() - sdata_blobs.pl.render_labels("blobs_labels", color="channel_0_sum").pl.show() # we're modifying the data here, so we need an independent copy sdata_blobs_local = deepcopy(sdata_blobs) From 936b0f9f10bbc2674ffd1b7d665d57048b9c11cc Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 15:02:29 -0400 Subject: [PATCH 11/14] made GH action for generating test plots more specific --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0418a980..a5c46660 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -61,7 +61,7 @@ jobs: if: always() uses: actions/upload-artifact@v3 with: - name: plotting-results-${{ matrix.os }} + name: visual_test_results_${{ matrix.os }}-python${{ matrix.python }} path: /home/runner/work/spatialdata-plot/spatialdata-plot/tests/figures/* - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 From 793337608a6c91bbe4f38e01ca24cf6793879e4f Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 19:56:04 +0000 Subject: [PATCH 12/14] removed duplicate imports --- src/spatialdata_plot/pl/utils.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 5dfddb5c..e1bc5a43 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -10,9 +10,7 @@ from typing import Any, Literal, Union import matplotlib -import matplotlib.patches -import matplotlib.patches as mpatches -import matplotlib.patches as mplp +import mpatches as mpatches import matplotlib.path as mpath import matplotlib.pyplot as plt import numpy as np @@ -232,7 +230,7 @@ def _sanitise_na_color(na_color: ColorLike | None) -> tuple[str, bool]: raise ValueError(f"Invalid na_color value: {na_color}") -def _get_centroid_of_pathpatch(pathpatch: matplotlib.patches.PathPatch) -> tuple[float, float]: +def _get_centroid_of_pathpatch(pathpatch: mpatches.PathPatch) -> tuple[float, float]: # Extract the vertices from the PathPatch path = pathpatch.get_path() vertices = path.vertices @@ -248,7 +246,7 @@ def _get_centroid_of_pathpatch(pathpatch: matplotlib.patches.PathPatch) -> tuple return centroid_x, centroid_y -def _scale_pathpatch_around_centroid(pathpatch: matplotlib.patches.PathPatch, scale_factor: float) -> None: +def _scale_pathpatch_around_centroid(pathpatch: mpatches.PathPatch, scale_factor: float) -> None: centroid = _get_centroid_of_pathpatch(pathpatch) vertices = pathpatch.get_path().vertices @@ -345,7 +343,7 @@ def _process_polygon(row: pd.Series, s: float) -> dict[str, Any]: coords = np.array(row["geometry"].exterior.coords) centroid = np.mean(coords, axis=0) scaled_coords = (centroid + (coords - centroid) * s).tolist() - return {**row.to_dict(), "geometry": mplp.Polygon(scaled_coords, closed=True)} + return {**row.to_dict(), "geometry": mpatches.Polygon(scaled_coords, closed=True)} def _process_multipolygon(row: pd.Series, s: float) -> list[dict[str, Any]]: mp = _make_patch_from_multipolygon(row["geometry"]) @@ -358,7 +356,7 @@ def _process_multipolygon(row: pd.Series, s: float) -> list[dict[str, Any]]: def _process_point(row: pd.Series, s: float) -> dict[str, Any]: return { **row.to_dict(), - "geometry": mplp.Circle((row["geometry"].x, row["geometry"].y), radius=row["radius"] * s), + "geometry": mpatches.Circle((row["geometry"].x, row["geometry"].y), radius=row["radius"] * s), } def _create_patches(shapes_df: GeoDataFrame, fill_c: list[Any], outline_c: list[Any], s: float) -> pd.DataFrame: From 98f9abb425e96a099c74bc861c0c965a2b448c74 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:57:03 +0000 Subject: [PATCH 13/14] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/spatialdata_plot/pl/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index e1bc5a43..81a09d25 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -10,9 +10,9 @@ from typing import Any, Literal, Union import matplotlib -import mpatches as mpatches import matplotlib.path as mpath import matplotlib.pyplot as plt +import mpatches as mpatches import numpy as np import pandas as pd import shapely From 25371d2ea28f6e23055701eaafecc4a0f100eac6 Mon Sep 17 00:00:00 2001 From: Tim Treis Date: Wed, 28 Aug 2024 19:59:50 +0000 Subject: [PATCH 14/14] fixed rename --- src/spatialdata_plot/pl/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index e1bc5a43..21359a76 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -10,7 +10,7 @@ from typing import Any, Literal, Union import matplotlib -import mpatches as mpatches +import matplotlib.patches as mpatches import matplotlib.path as mpath import matplotlib.pyplot as plt import numpy as np