From 7534692b084e3b6005d1952c06121b3d9cc4c277 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Fri, 25 Oct 2024 12:58:10 +0200 Subject: [PATCH 01/11] points are transformed --- src/spatialdata_plot/pl/render.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 24df6f9b..06b65f06 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -24,6 +24,7 @@ from spatialdata.transformations import ( set_transformation, ) +from spatialdata.transformations.transformations import Identity from spatialdata_plot._logging import logger from spatialdata_plot.pl.render_params import ( @@ -469,7 +470,7 @@ def _render_points( if color_source_vector is None and render_params.transfunc is not None: color_vector = render_params.transfunc(color_vector) - _, trans_data = _prepare_transformation(sdata.points[element], coordinate_system, ax) + trans, trans_data = _prepare_transformation(sdata.points[element], coordinate_system, ax) norm = copy(render_params.cmap_params.norm) @@ -492,8 +493,16 @@ def _render_points( # use dpi/100 as a factor for cases where dpi!=100 px = int(np.round(np.sqrt(render_params.size) * (fig_params.fig.dpi / 100))) + # apply transformations + transformed_element = PointsModel.parse( + pd.DataFrame( + trans.transform_affine(sdata_filt.points[element]), columns=sdata_filt.points[element].columns + ), + transformations={coordinate_system: Identity()}, + ) + plot_width, plot_height, x_ext, y_ext, factor = _get_extent_and_range_for_datashader_canvas( - sdata_filt.points[element], coordinate_system, ax, fig_params + transformed_element, coordinate_system, ax, fig_params ) # use datashader for the visualization of points @@ -503,7 +512,7 @@ def _render_points( aggregate_with_reduction = None if col_for_color is not None and (render_params.groups is None or len(render_params.groups) > 1): if color_by_categorical: - agg = cvs.points(sdata_filt.points[element], "x", "y", agg=ds.by(col_for_color, ds.count())) + agg = cvs.points(transformed_element, "x", "y", agg=ds.by(col_for_color, ds.count())) else: reduction_name = render_params.ds_reduction if render_params.ds_reduction is not None else "sum" logger.info( @@ -511,12 +520,12 @@ def _render_points( "to the matplotlib result." ) agg = _datashader_aggregate_with_function( - render_params.ds_reduction, cvs, sdata_filt.points[element], col_for_color, "points" + render_params.ds_reduction, cvs, transformed_element, col_for_color, "points" ) # save min and max values for drawing the colorbar aggregate_with_reduction = (agg.min(), agg.max()) else: - agg = cvs.points(sdata_filt.points[element], "x", "y", agg=ds.count()) + agg = cvs.points(transformed_element, "x", "y", agg=ds.count()) if norm.vmin is not None or norm.vmax is not None: norm.vmin = np.min(agg) if norm.vmin is None else norm.vmin From 2612dd73632266b6e875fd4ac161bcaa504333c1 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Fri, 25 Oct 2024 13:01:19 +0200 Subject: [PATCH 02/11] add points test --- tests/pl/test_render_points.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/pl/test_render_points.py b/tests/pl/test_render_points.py index b2dc9179..9d3ab0a3 100644 --- a/tests/pl/test_render_points.py +++ b/tests/pl/test_render_points.py @@ -7,6 +7,7 @@ from anndata import AnnData from spatialdata import SpatialData, deepcopy from spatialdata.models import PointsModel, TableModel +from spatialdata.transformations import Scale import spatialdata_plot # noqa: F401 from tests.conftest import DPI, PlotTester, PlotTesterMeta @@ -173,3 +174,16 @@ def test_plot_mpl_and_datashader_point_sizes_agree_after_altered_dpi(self, sdata sdata_blobs.pl.render_points(element="blobs_points", size=400, color="blue").pl.render_points( element="blobs_points", size=400, color="yellow", method="datashader", alpha=0.8 ).pl.show(dpi=200) + + def test_plot_datashader_points_are_transformed(self): + sdata = SpatialData( + points={ + "points1": PointsModel.parse( + pd.DataFrame({"y": [0, 0, 10, 10, 4, 6, 4, 6], "x": [0, 10, 10, 0, 4, 6, 6, 4]}), + transformations={"global": Scale([2, 2], ("y", "x"))}, + ) + }, + ) + sdata.pl.render_points("points1", method="matplotlib", size=50, color="lightgrey").pl.render_points( + "points1", method="datashader", size=10, color="red" + ).pl.show() From b3f5e45880ea6748252115885c8bf048e0aa72b8 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Fri, 25 Oct 2024 13:48:26 +0200 Subject: [PATCH 03/11] handle point annotation --- src/spatialdata_plot/pl/render.py | 5 ++--- ...Points_datashader_points_are_transformed.png | Bin 0 -> 11243 bytes 2 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 tests/_images/Points_datashader_points_are_transformed.png diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 06b65f06..483d5b94 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -495,9 +495,8 @@ def _render_points( # apply transformations transformed_element = PointsModel.parse( - pd.DataFrame( - trans.transform_affine(sdata_filt.points[element]), columns=sdata_filt.points[element].columns - ), + trans.transform(sdata_filt.points[element][["x", "y"]]), + annotation=sdata_filt.points[element][sdata_filt.points[element].columns.drop(["x", "y"])], transformations={coordinate_system: Identity()}, ) diff --git a/tests/_images/Points_datashader_points_are_transformed.png b/tests/_images/Points_datashader_points_are_transformed.png new file mode 100644 index 0000000000000000000000000000000000000000..c26c62ce7c8e9c655a8e0c5f535361a60564ef6b GIT binary patch literal 11243 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 literal 0 HcmV?d00001 From 78840c73c16b8b27bbf7a95db6de448d4e1d5c53 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Tue, 26 Nov 2024 17:39:39 +0100 Subject: [PATCH 04/11] affine, scale, sequence work now for (multi)polygons with ds --- src/spatialdata_plot/pl/basic.py | 12 +++-- src/spatialdata_plot/pl/render.py | 76 +++++++++++++++++++++---------- src/spatialdata_plot/pl/utils.py | 20 +++++++- 3 files changed, 80 insertions(+), 28 deletions(-) diff --git a/src/spatialdata_plot/pl/basic.py b/src/spatialdata_plot/pl/basic.py index f3cc0fc5..79cef533 100644 --- a/src/spatialdata_plot/pl/basic.py +++ b/src/spatialdata_plot/pl/basic.py @@ -163,7 +163,7 @@ def render_shapes( palette: list[str] | str | None = None, na_color: ColorLike | None = "default", outline_width: float | int = 1.5, - outline_color: str | list[float] = "#000000ff", + outline_color: str | list[float] = "#000000", outline_alpha: float | int = 0.0, cmap: Colormap | str | None = None, norm: Normalize | None = None, @@ -209,9 +209,11 @@ def render_shapes( won't be shown. outline_width : float | int, default 1.5 Width of the border. - outline_color : str | list[float], default "#000000ff" - Color of the border. Can either be a named color ("red"), a hex representation ("#000000ff") or a list of - floats that represent RGB/RGBA values (1.0, 0.0, 0.0, 1.0). + outline_color : str | list[float], default "#000000" + Color of the border. Can either be a named color ("red"), a hex representation ("#000000") or a list of + floats that represent RGB/RGBA values (1.0, 0.0, 0.0, 1.0). If the hex representation includes alpha, e.g. + "#000000ff", the last two positions are ignored, since the alpha of the outlines is solely controlled by + `outline_alpha`. outline_alpha : float | int, default 0.0 Alpha value for the outline of shapes. Invisible by default. cmap : Colormap | str | None, optional @@ -995,6 +997,8 @@ def show( has_shapes=has_shapes and wants_shapes, elements=wanted_elements, ) + # TODO: fix get_extent??? + # extent = {"x": (188.62348968860152, 461.8520923943867), "y": (-446.7026437060826, -149.92618678876786)} cs_x_min, cs_x_max = extent["x"] cs_y_min, cs_y_max = extent["y"] diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 483d5b94..e0ef7744 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -20,11 +20,9 @@ from matplotlib.colors import ListedColormap, Normalize from scanpy._settings import settings as sc_settings from spatialdata import get_extent -from spatialdata.models import PointsModel, get_table_keys -from spatialdata.transformations import ( - set_transformation, -) -from spatialdata.transformations.transformations import Identity +from spatialdata.models import PointsModel, ShapesModel, get_table_keys +from spatialdata.transformations import get_transformation, set_transformation +from spatialdata.transformations.transformations import Identity, Sequence from spatialdata_plot._logging import logger from spatialdata_plot.pl.render_params import ( @@ -150,7 +148,7 @@ def _render_shapes( colorbar = False if col_for_color is None else legend_params.colorbar # Apply the transformation to the PatchCollection's paths - trans, _ = _prepare_transformation(sdata_filt.shapes[element], coordinate_system) + trans, trans_data = _prepare_transformation(sdata_filt.shapes[element], coordinate_system) shapes = gpd.GeoDataFrame(shapes, geometry="geometry") @@ -170,10 +168,29 @@ def _render_shapes( ) if method == "datashader": - trans += ax.transData + # trans += ax.transData # TODO: delete? + + # TODO: what about circles? some sort of ellipsis object? + # or: if necessary, switch to polygons somehow (logging!) + + # TODO: test all transformations (scale, affine, mapaxis, translocation) + # and of course sequence. What if scale in negative range? + + # apply transformations to element + element_trans = get_transformation(sdata_filt.shapes[element]) + if isinstance(element_trans, Sequence): + temp = element_trans.transformations.copy() + temp.reverse() + element_trans = Sequence(temp) + + tm = element_trans.to_affine_matrix(("x", "y"), ("x", "y"))[:2, :2] + flip_matrix = [[1, 0], [0, -1]] + # flip the elements along the x-axis, then apply the transformation and flip back + transformed_element = sdata_filt.shapes[element].transform(lambda x: x @ (flip_matrix @ tm @ flip_matrix)) + transformed_element = ShapesModel.parse(gpd.GeoDataFrame(geometry=transformed_element)) plot_width, plot_height, x_ext, y_ext, factor = _get_extent_and_range_for_datashader_canvas( - sdata_filt.shapes[element], coordinate_system, ax, fig_params + transformed_element, coordinate_system, ax, fig_params ) cvs = ds.Canvas(plot_width=plot_width, plot_height=plot_height, x_range=x_ext, y_range=y_ext) @@ -184,21 +201,17 @@ def _render_shapes( # Handle circles encoded as points with radius if is_point.any(): scale = shapes[is_point]["radius"] * render_params.scale - sdata_filt.shapes[element].loc[is_point, "geometry"] = _geometry[is_point].buffer(scale.to_numpy()) + transformed_element.loc[is_point, "geometry"] = _geometry[is_point].buffer(scale.to_numpy()) # in case we are coloring by a column in table - if col_for_color is not None and col_for_color not in sdata_filt.shapes[element].columns: - sdata_filt.shapes[element][col_for_color] = ( - color_vector if color_source_vector is None else color_source_vector - ) + if col_for_color is not None and col_for_color not in transformed_element.columns: + transformed_element[col_for_color] = color_vector if color_source_vector is None else color_source_vector # Render shapes with datashader color_by_categorical = col_for_color is not None and color_source_vector is not None aggregate_with_reduction = None if col_for_color is not None and (render_params.groups is None or len(render_params.groups) > 1): if color_by_categorical: - agg = cvs.polygons( - sdata_filt.shapes[element], geometry="geometry", agg=ds.by(col_for_color, ds.count()) - ) + agg = cvs.polygons(transformed_element, geometry="geometry", agg=ds.by(col_for_color, ds.count())) else: reduction_name = render_params.ds_reduction if render_params.ds_reduction is not None else "mean" logger.info( @@ -206,16 +219,16 @@ def _render_shapes( "to the matplotlib result." ) agg = _datashader_aggregate_with_function( - render_params.ds_reduction, cvs, sdata_filt.shapes[element], col_for_color, "shapes" + render_params.ds_reduction, cvs, transformed_element, col_for_color, "shapes" ) # save min and max values for drawing the colorbar aggregate_with_reduction = (agg.min(), agg.max()) else: - agg = cvs.polygons(sdata_filt.shapes[element], geometry="geometry", agg=ds.count()) + agg = cvs.polygons(transformed_element, geometry="geometry", agg=ds.count()) # render outlines if needed if (render_outlines := render_params.outline_alpha) > 0: agg_outlines = cvs.line( - sdata_filt.shapes[element], + transformed_element, geometry="geometry", line_width=render_params.outline_params.linewidth, ) @@ -289,13 +302,23 @@ def _render_shapes( rgba_image, trans_data = _create_image_from_datashader_result(ds_result, factor, ax) _cax = _ax_show_and_transform( - rgba_image, trans_data, ax, zorder=render_params.zorder, alpha=render_params.fill_alpha + rgba_image, + trans_data, + ax, + zorder=render_params.zorder, + alpha=render_params.fill_alpha, + extent=x_ext + y_ext, ) # render outline image if needed if render_outlines: rgba_image, trans_data = _create_image_from_datashader_result(ds_outlines, factor, ax) _ax_show_and_transform( - rgba_image, trans_data, ax, zorder=render_params.zorder, alpha=render_params.outline_alpha + rgba_image, + trans_data, + ax, + zorder=render_params.zorder, + alpha=render_params.outline_alpha, + extent=x_ext + y_ext, ) cax = None @@ -332,7 +355,7 @@ def _render_shapes( if not values_are_categorical: # If the user passed a Normalize object with vmin/vmax we'll use those, - # # if not we'll use the min/max of the color_vector + # if not we'll use the min/max of the color_vector _cax.set_clim( vmin=render_params.cmap_params.norm.vmin or min(color_vector), vmax=render_params.cmap_params.norm.vmax or max(color_vector), @@ -582,7 +605,14 @@ def _render_points( ) rgba_image, trans_data = _create_image_from_datashader_result(ds_result, factor, ax) - _ax_show_and_transform(rgba_image, trans_data, ax, zorder=render_params.zorder, alpha=render_params.alpha) + _ax_show_and_transform( + rgba_image, + trans_data, + ax, + zorder=render_params.zorder, + alpha=render_params.alpha, + extent=x_ext + y_ext, + ) cax = None if aggregate_with_reduction is not None: diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 77e73273..901b6439 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -1975,12 +1975,29 @@ def _ax_show_and_transform( alpha: float | None = None, cmap: ListedColormap | LinearSegmentedColormap | None = None, zorder: int = 0, + extent: list[float] | None = None, ) -> matplotlib.image.AxesImage: + # default extent in mpl: + image_extent = [-0.5, array.shape[1] - 0.5, array.shape[0] - 0.5, -0.5] + if extent is not None: + # make sure extent is [x_min, x_max, y_min, y_max] + if extent[3] < extent[2]: + extent[2], extent[3] = extent[3], extent[2] + if extent[0] < 0: + x_factor = array.shape[1] / (extent[1] - extent[0]) + image_extent[0] = image_extent[0] + (extent[0] * x_factor) + image_extent[1] = image_extent[1] + (extent[0] * x_factor) + if extent[2] < 0: + y_factor = array.shape[0] / (extent[3] - extent[2]) + image_extent[2] = image_extent[2] + (extent[2] * y_factor) + image_extent[3] = image_extent[3] + (extent[2] * y_factor) + if not cmap and alpha is not None: im = ax.imshow( array, alpha=alpha, zorder=zorder, + extent=tuple(image_extent), ) im.set_transform(trans_data) else: @@ -1988,6 +2005,7 @@ def _ax_show_and_transform( array, cmap=cmap, zorder=zorder, + extent=tuple(image_extent), ) im.set_transform(trans_data) return im @@ -2053,7 +2071,7 @@ def _get_extent_and_range_for_datashader_canvas( def _create_image_from_datashader_result( ds_result: ds.transfer_functions.Image, factor: float, ax: Axes -) -> tuple[MaskedArray[np.float64, Any], matplotlib.transforms.CompositeGenericTransform]: +) -> tuple[MaskedArray[np.float64, Any], matplotlib.transforms.Transform]: # create SpatialImage from datashader output to get it back to original size rgba_image_data = ds_result.to_numpy().base rgba_image_data = np.transpose(rgba_image_data, (2, 0, 1)) From c514cf14d9827c44044b82f9169b47c49ec04459 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:43:59 +0000 Subject: [PATCH 05/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/spatialdata_plot/pl/render.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 4667b19b..adfe236e 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -21,7 +21,6 @@ from spatialdata.models import PointsModel, ShapesModel, get_table_keys from spatialdata.transformations import get_transformation, set_transformation from spatialdata.transformations.transformations import Identity, Sequence - from xarray import DataTree from spatialdata_plot._logging import logger From bd7b251ecc72ebe1cfcc49a25b16fc4a635d8e7f Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Tue, 26 Nov 2024 19:16:45 +0100 Subject: [PATCH 06/11] transforming circles as shapes with datashader --- src/spatialdata_plot/pl/render.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index adfe236e..283606ee 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -169,8 +169,13 @@ def _render_shapes( if method == "datashader": # trans += ax.transData # TODO: delete? - # TODO: what about circles? some sort of ellipsis object? - # or: if necessary, switch to polygons somehow (logging!) + _geometry = shapes["geometry"] + is_point = _geometry.type == "Point" + + # Handle circles encoded as points with radius + if is_point.any(): + scale = shapes[is_point]["radius"] * render_params.scale + sdata_filt.shapes[element].loc[is_point, "geometry"] = _geometry[is_point].buffer(scale.to_numpy()) # TODO: test all transformations (scale, affine, mapaxis, translocation) # and of course sequence. What if scale in negative range? @@ -186,7 +191,9 @@ def _render_shapes( flip_matrix = [[1, 0], [0, -1]] # flip the elements along the x-axis, then apply the transformation and flip back transformed_element = sdata_filt.shapes[element].transform(lambda x: x @ (flip_matrix @ tm @ flip_matrix)) - transformed_element = ShapesModel.parse(gpd.GeoDataFrame(geometry=transformed_element)) + transformed_element = ShapesModel.parse( + gpd.GeoDataFrame(data=sdata_filt.shapes[element].drop("geometry", axis=1), geometry=transformed_element) + ) plot_width, plot_height, x_ext, y_ext, factor = _get_extent_and_range_for_datashader_canvas( transformed_element, coordinate_system, ax, fig_params @@ -194,14 +201,6 @@ def _render_shapes( cvs = ds.Canvas(plot_width=plot_width, plot_height=plot_height, x_range=x_ext, y_range=y_ext) - _geometry = shapes["geometry"] - is_point = _geometry.type == "Point" - - # Handle circles encoded as points with radius - if is_point.any(): - scale = shapes[is_point]["radius"] * render_params.scale - transformed_element.loc[is_point, "geometry"] = _geometry[is_point].buffer(scale.to_numpy()) - # in case we are coloring by a column in table if col_for_color is not None and col_for_color not in transformed_element.columns: transformed_element[col_for_color] = color_vector if color_source_vector is None else color_source_vector From 2b5012f12876fae9b3058cce30840b458ae57139 Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Mon, 2 Dec 2024 23:29:04 +0100 Subject: [PATCH 07/11] working version for all transformations --- src/spatialdata_plot/pl/render.py | 23 ++++++------------- src/spatialdata_plot/pl/utils.py | 37 ++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/spatialdata_plot/pl/render.py b/src/spatialdata_plot/pl/render.py index 283606ee..6ced5b27 100644 --- a/src/spatialdata_plot/pl/render.py +++ b/src/spatialdata_plot/pl/render.py @@ -20,7 +20,7 @@ from spatialdata import get_extent from spatialdata.models import PointsModel, ShapesModel, get_table_keys from spatialdata.transformations import get_transformation, set_transformation -from spatialdata.transformations.transformations import Identity, Sequence +from spatialdata.transformations.transformations import Identity from xarray import DataTree from spatialdata_plot._logging import logger @@ -43,6 +43,7 @@ _get_colors_for_categorical_obs, _get_extent_and_range_for_datashader_canvas, _get_linear_colormap, + _get_transformation_matrix_for_datashader, _is_coercable_to_float, _map_color_seg, _maybe_set_colors, @@ -167,8 +168,6 @@ def _render_shapes( ) if method == "datashader": - # trans += ax.transData # TODO: delete? - _geometry = shapes["geometry"] is_point = _geometry.type == "Point" @@ -177,20 +176,12 @@ def _render_shapes( scale = shapes[is_point]["radius"] * render_params.scale sdata_filt.shapes[element].loc[is_point, "geometry"] = _geometry[is_point].buffer(scale.to_numpy()) - # TODO: test all transformations (scale, affine, mapaxis, translocation) - # and of course sequence. What if scale in negative range? - - # apply transformations to element + # apply transformations to the individual points element_trans = get_transformation(sdata_filt.shapes[element]) - if isinstance(element_trans, Sequence): - temp = element_trans.transformations.copy() - temp.reverse() - element_trans = Sequence(temp) - - tm = element_trans.to_affine_matrix(("x", "y"), ("x", "y"))[:2, :2] - flip_matrix = [[1, 0], [0, -1]] - # flip the elements along the x-axis, then apply the transformation and flip back - transformed_element = sdata_filt.shapes[element].transform(lambda x: x @ (flip_matrix @ tm @ flip_matrix)) + tm = _get_transformation_matrix_for_datashader(element_trans) + transformed_element = sdata_filt.shapes[element].transform( + lambda x: (np.hstack([x, np.ones((x.shape[0], 1))]) @ tm)[:, :2] + ) transformed_element = ShapesModel.parse( gpd.GeoDataFrame(data=sdata_filt.shapes[element].drop("geometry", axis=1), geometry=transformed_element) ) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 19a416ad..2fe377cc 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -19,6 +19,7 @@ import matplotlib.transforms as mtransforms import numpy as np import numpy.ma as ma +import numpy.typing as npt import pandas as pd import shapely import spatialdata as sd @@ -58,8 +59,11 @@ from spatialdata._core.query.relational_query import _locate_value, _ValueOrigin from spatialdata._types import ArrayLike from spatialdata.models import Image2DModel, Labels2DModel, PointsModel, SpatialElement, get_model + +# from spatialdata.transformations.transformations import Scale +from spatialdata.transformations import Affine, Identity, MapAxis, Scale, Translation +from spatialdata.transformations import Sequence as SDSequence from spatialdata.transformations.operations import get_transformation -from spatialdata.transformations.transformations import Scale from xarray import DataArray, DataTree from spatialdata_plot._logging import logger @@ -2205,3 +2209,34 @@ def _prepare_transformation( trans_data = trans + ax.transData if ax is not None else None return trans, trans_data + + +def _get_datashader_trans_matrix_of_single_element( + trans: Identity | Scale | Affine | MapAxis | Translation, +) -> npt.NDArray[Any]: + flip_matrix = np.array([[1, 0, 0], [0, -1, 0], [0, 0, 1]]) + tm: npt.NDArray[Any] = trans.to_affine_matrix(("x", "y"), ("x", "y")) + + if isinstance(trans, Identity): + return np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + if isinstance(trans, (Scale | Affine)): + # idea: "flip the y-axis", apply transformation, flip back + flip_and_transform: npt.NDArray[Any] = flip_matrix @ tm @ flip_matrix + return flip_and_transform + if isinstance(trans, MapAxis): + # no flipping needed + return tm + # for a Translation, we need the transposed transformation matrix + return tm.T + + +def _get_transformation_matrix_for_datashader( + trans: Scale | Identity | Affine | MapAxis | Translation | SDSequence, +) -> npt.NDArray[Any]: + """Get the affine matrix needed to transform shapes for rendering with datashader.""" + if isinstance(trans, SDSequence): + tm = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) + for x in trans.transformations: + tm = tm @ _get_datashader_trans_matrix_of_single_element(x) + return tm + return _get_datashader_trans_matrix_of_single_element(trans) From 15b6d59a3267703fec1c985eca0ec9b9a85bc15a Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Tue, 3 Dec 2024 21:37:21 +0100 Subject: [PATCH 08/11] add tests --- ...points_transformed_ds_agrees_with_mpl.png} | Bin tests/pl/test_render_points.py | 29 +++++++- tests/pl/test_render_shapes.py | 70 ++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) rename tests/_images/{Points_datashader_points_are_transformed.png => Points_points_transformed_ds_agrees_with_mpl.png} (100%) diff --git a/tests/_images/Points_datashader_points_are_transformed.png b/tests/_images/Points_points_transformed_ds_agrees_with_mpl.png similarity index 100% rename from tests/_images/Points_datashader_points_are_transformed.png rename to tests/_images/Points_points_transformed_ds_agrees_with_mpl.png diff --git a/tests/pl/test_render_points.py b/tests/pl/test_render_points.py index 9d3ab0a3..e3e99099 100644 --- a/tests/pl/test_render_points.py +++ b/tests/pl/test_render_points.py @@ -1,3 +1,5 @@ +import math + import dask.dataframe import matplotlib import matplotlib.pyplot as plt @@ -7,7 +9,8 @@ from anndata import AnnData from spatialdata import SpatialData, deepcopy from spatialdata.models import PointsModel, TableModel -from spatialdata.transformations import Scale +from spatialdata.transformations import Affine, Identity, MapAxis, Scale, Sequence, Translation +from spatialdata.transformations._utils import _set_transformations import spatialdata_plot # noqa: F401 from tests.conftest import DPI, PlotTester, PlotTesterMeta @@ -175,7 +178,7 @@ def test_plot_mpl_and_datashader_point_sizes_agree_after_altered_dpi(self, sdata element="blobs_points", size=400, color="yellow", method="datashader", alpha=0.8 ).pl.show(dpi=200) - def test_plot_datashader_points_are_transformed(self): + def test_plot_points_transformed_ds_agrees_with_mpl(self): sdata = SpatialData( points={ "points1": PointsModel.parse( @@ -187,3 +190,25 @@ def test_plot_datashader_points_are_transformed(self): sdata.pl.render_points("points1", method="matplotlib", size=50, color="lightgrey").pl.render_points( "points1", method="datashader", size=10, color="red" ).pl.show() + + def test_plot_datashader_can_transform_points(self, sdata_blobs: SpatialData): + theta = math.pi / 1.7 + rotation = Affine( + [ + [math.cos(theta), -math.sin(theta), 0], + [math.sin(theta), math.cos(theta), 0], + [0, 0, 1], + ], + input_axes=("x", "y"), + output_axes=("x", "y"), + ) + + scale = Scale([-1.3, 1.8], axes=("x", "y")) + identity = Identity() + mapaxis = MapAxis({"x": "y", "y": "x"}) + translation = Translation([20, -65], ("x", "y")) + seq = Sequence([mapaxis, scale, identity, translation, rotation]) + + _set_transformations(sdata_blobs["blobs_points"], {"global": seq}) + + sdata_blobs.pl.render_points("blobs_points", method="datashader", color="black", size=5).pl.show() diff --git a/tests/pl/test_render_shapes.py b/tests/pl/test_render_shapes.py index ff86a4f0..d683189a 100644 --- a/tests/pl/test_render_shapes.py +++ b/tests/pl/test_render_shapes.py @@ -1,3 +1,5 @@ +import math + import anndata import geopandas as gpd import matplotlib @@ -10,6 +12,8 @@ from shapely.geometry import MultiPolygon, Point, Polygon from spatialdata import SpatialData, deepcopy from spatialdata.models import ShapesModel, TableModel +from spatialdata.transformations import Affine, Identity, MapAxis, Scale, Sequence, Translation +from spatialdata.transformations._utils import _set_transformations import spatialdata_plot # noqa: F401 from tests.conftest import DPI, PlotTester, PlotTesterMeta @@ -377,3 +381,69 @@ def test_plot_can_set_clims_clip(self, sdata_blobs: SpatialData): sdata_blobs.pl.render_shapes( "blobs_circles", color="dummy_gene_expression", norm=norm, table_name="new_table" ).pl.show() + + def test_plot_datashader_can_transform_polygons(self, sdata_blobs: SpatialData): + theta = math.pi / 1.7 + rotation = Affine( + [ + [math.cos(theta), -math.sin(theta), 0], + [math.sin(theta), math.cos(theta), 0], + [0, 0, 1], + ], + input_axes=("x", "y"), + output_axes=("x", "y"), + ) + + scale = Scale([-1.3, 1.8], axes=("x", "y")) + identity = Identity() + mapaxis = MapAxis({"x": "y", "y": "x"}) + translation = Translation([20, -65], ("x", "y")) + seq = Sequence([mapaxis, scale, identity, translation, rotation]) + + _set_transformations(sdata_blobs["blobs_polygons"], {"global": seq}) + + sdata_blobs.pl.render_shapes("blobs_polygons", method="datashader", outline_alpha=1.0).pl.show() + + def test_plot_datashader_can_transform_multipolygons(self, sdata_blobs: SpatialData): + theta = math.pi / 1.7 + rotation = Affine( + [ + [math.cos(theta), -math.sin(theta), 0], + [math.sin(theta), math.cos(theta), 0], + [0, 0, 1], + ], + input_axes=("x", "y"), + output_axes=("x", "y"), + ) + + scale = Scale([-1.3, 1.8], axes=("x", "y")) + identity = Identity() + mapaxis = MapAxis({"x": "y", "y": "x"}) + translation = Translation([20, -65], ("x", "y")) + seq = Sequence([mapaxis, scale, identity, translation, rotation]) + + _set_transformations(sdata_blobs["blobs_multipolygons"], {"global": seq}) + + sdata_blobs.pl.render_shapes("blobs_multipolygons", method="datashader", outline_alpha=1.0).pl.show() + + def test_plot_datashader_can_transform_circles(self, sdata_blobs: SpatialData): + theta = math.pi / 1.7 + rotation = Affine( + [ + [math.cos(theta), -math.sin(theta), 0], + [math.sin(theta), math.cos(theta), 0], + [0, 0, 1], + ], + input_axes=("x", "y"), + output_axes=("x", "y"), + ) + + scale = Scale([-1.3, 1.8], axes=("x", "y")) + identity = Identity() + mapaxis = MapAxis({"x": "y", "y": "x"}) + translation = Translation([20, -65], ("x", "y")) + seq = Sequence([mapaxis, scale, identity, translation, rotation]) + + _set_transformations(sdata_blobs["blobs_circles"], {"global": seq}) + + sdata_blobs.pl.render_shapes("blobs_circles", method="datashader", outline_alpha=1.0).pl.show() From b28e507d7712fe5d86656dfe3b4b40d2889ae08f Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Tue, 3 Dec 2024 21:46:24 +0100 Subject: [PATCH 09/11] update changelog, add test images --- CHANGELOG.md | 30 +++++++++++++++++- ...Points_datashader_can_transform_points.png | Bin 0 -> 26005 bytes ...hapes_datashader_can_transform_circles.png | Bin 0 -> 26128 bytes ...datashader_can_transform_multipolygons.png | Bin 0 -> 18214 bytes ...apes_datashader_can_transform_polygons.png | Bin 0 -> 22622 bytes 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 tests/_images/Points_datashader_can_transform_points.png create mode 100644 tests/_images/Shapes_datashader_can_transform_circles.png create mode 100644 tests/_images/Shapes_datashader_can_transform_multipolygons.png create mode 100644 tests/_images/Shapes_datashader_can_transform_polygons.png diff --git a/CHANGELOG.md b/CHANGELOG.md index bc823656..c079894f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,35 @@ and this project adheres to [Semantic Versioning][]. [keep a changelog]: https://keepachangelog.com/en/1.0.0/ [semantic versioning]: https://semver.org/spec/v2.0.0.html -## [0.2.7] - tbd +## [0.2.9] - tbd + +### Added + +- + +### Changed + +- + +### Fixed + +- Transformations of Points and Shapes are now applied before rendering with datashader (#378) + +## [0.2.8] - 2024-11-26 + +### Added + +- + +### Changed + +- + +### Fixed + +- + +## [0.2.7] - 2024-10-24 ### Added diff --git a/tests/_images/Points_datashader_can_transform_points.png b/tests/_images/Points_datashader_can_transform_points.png new file mode 100644 index 0000000000000000000000000000000000000000..201b2db198c6d2c3162a8c45875ffecd9e31b36f GIT binary patch literal 26005 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 literal 0 HcmV?d00001 diff --git a/tests/_images/Shapes_datashader_can_transform_circles.png b/tests/_images/Shapes_datashader_can_transform_circles.png new file mode 100644 index 0000000000000000000000000000000000000000..60cde0732ba5cffcbb69f84532e3d1783117286e GIT binary patch literal 26128 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 KIp@FXn|}b_nsW63 literal 0 HcmV?d00001 diff --git a/tests/_images/Shapes_datashader_can_transform_multipolygons.png b/tests/_images/Shapes_datashader_can_transform_multipolygons.png new file mode 100644 index 0000000000000000000000000000000000000000..09e56c6353c188876d03ab1793363196450c8c20 GIT binary patch literal 18214 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 b=7}rUp4%jK4&l$Mp&0Jkr~6df_Tv8q^rCsF literal 0 HcmV?d00001 diff --git a/tests/_images/Shapes_datashader_can_transform_polygons.png b/tests/_images/Shapes_datashader_can_transform_polygons.png new file mode 100644 index 0000000000000000000000000000000000000000..fb2552ff20fc06c0099a2c492d8361b67e22b54a GIT binary patch literal 22622 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 Date: Tue, 3 Dec 2024 20:52:25 +0000 Subject: [PATCH 10/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d7f0456..56806a9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ and this project adheres to [Semantic Versioning][]. ### Changed -- Support for `xarray.DataTree` (which moved from `datatree.DataTree`) (#380) +- Support for `xarray.DataTree` (which moved from `datatree.DataTree`) (#380) ### Fixed From 4016973c7512621a8a15357c8754aba0c043204f Mon Sep 17 00:00:00 2001 From: Sonja Stockhaus Date: Wed, 4 Dec 2024 16:53:47 +0100 Subject: [PATCH 11/11] remove comment, clean changelog --- CHANGELOG.md | 20 -------------------- src/spatialdata_plot/pl/basic.py | 2 -- 2 files changed, 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56806a9d..6973baa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,32 +10,16 @@ and this project adheres to [Semantic Versioning][]. ## [0.2.9] - tbd -### Added - -- - -### Changed - -- - ### Fixed - Transformations of Points and Shapes are now applied before rendering with datashader (#378) ## [0.2.8] - 2024-11-26 -### Added - -- - ### Changed - Support for `xarray.DataTree` (which moved from `datatree.DataTree`) (#380) -### Fixed - -- - ## [0.2.7] - 2024-10-24 ### Added @@ -68,10 +52,6 @@ and this project adheres to [Semantic Versioning][]. ## [0.2.5] - 2024-08-23 -### Added - -- - ### Changed - Replaced `outline` parameter in `render_labels` with alpha-based logic (#323) diff --git a/src/spatialdata_plot/pl/basic.py b/src/spatialdata_plot/pl/basic.py index bca2e6df..8513dba9 100644 --- a/src/spatialdata_plot/pl/basic.py +++ b/src/spatialdata_plot/pl/basic.py @@ -996,8 +996,6 @@ def show( has_shapes=has_shapes and wants_shapes, elements=wanted_elements, ) - # TODO: fix get_extent??? - # extent = {"x": (188.62348968860152, 461.8520923943867), "y": (-446.7026437060826, -149.92618678876786)} cs_x_min, cs_x_max = extent["x"] cs_y_min, cs_y_max = extent["y"]