From 1d6794b6bb353f35f292c9a98ed8011209c9e3ac Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 07:54:48 +0200 Subject: [PATCH 01/17] order projection inference --- ultraplot/internals/inputs.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ultraplot/internals/inputs.py b/ultraplot/internals/inputs.py index ef3fe387..a9013097 100644 --- a/ultraplot/internals/inputs.py +++ b/ultraplot/internals/inputs.py @@ -341,10 +341,13 @@ def _preprocess_or_redirect(self, *args, **kwargs): if kwargs.get("latlon", None) is None: kwargs["latlon"] = True if self._name == "cartopy" and name in CARTOPY_FUNCS: - if kwargs.get("transform", None) is None: - kwargs["transform"] = PlateCarree() - else: - kwargs["transform"] = Proj(kwargs["transform"]) + # Check if an input transform is given + # else default to axis projection or + # PlateCarree if no projection is set + input_transform = kwargs.get("transform", None) + kwargs["transform"] = _not_none( + input_transform, self.projection, PlateCarree() + ) # Process data args # NOTE: Raises error if there are more args than keys From fb7e92a51ef185dd88eb4bde21c0b6b0a8b55a42 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 08:01:23 +0200 Subject: [PATCH 02/17] convert transform --- ultraplot/internals/inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultraplot/internals/inputs.py b/ultraplot/internals/inputs.py index a9013097..7e31619e 100644 --- a/ultraplot/internals/inputs.py +++ b/ultraplot/internals/inputs.py @@ -346,7 +346,7 @@ def _preprocess_or_redirect(self, *args, **kwargs): # PlateCarree if no projection is set input_transform = kwargs.get("transform", None) kwargs["transform"] = _not_none( - input_transform, self.projection, PlateCarree() + Proj(input_transform), self.projection, PlateCarree() ) # Process data args From 9acdd026a0747b682f8ae6a2d6d2a7989a5afa65 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 08:10:50 +0200 Subject: [PATCH 03/17] change logic --- ultraplot/internals/inputs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ultraplot/internals/inputs.py b/ultraplot/internals/inputs.py index 7e31619e..23b68fc2 100644 --- a/ultraplot/internals/inputs.py +++ b/ultraplot/internals/inputs.py @@ -345,9 +345,10 @@ def _preprocess_or_redirect(self, *args, **kwargs): # else default to axis projection or # PlateCarree if no projection is set input_transform = kwargs.get("transform", None) - kwargs["transform"] = _not_none( - Proj(input_transform), self.projection, PlateCarree() - ) + if input_transform is not None: + kwargs["transform"] = Proj(input_transform) + else: + kwargs["transform"] = _not_none(self.projection, PlateCarree()) # Process data args # NOTE: Raises error if there are more args than keys From b0bbebd2f5e89aa0134e734038a89bba19599b26 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 08:54:14 +0200 Subject: [PATCH 04/17] add unittest --- ultraplot/tests/test_inputs.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/ultraplot/tests/test_inputs.py b/ultraplot/tests/test_inputs.py index 00ad9be9..a39aeecf 100644 --- a/ultraplot/tests/test_inputs.py +++ b/ultraplot/tests/test_inputs.py @@ -1,5 +1,5 @@ import ultraplot as uplt, pytest, numpy as np -from unittest.mock import Mock +from unittest.mock import Mock, patch @pytest.mark.parametrize( @@ -33,3 +33,25 @@ def tripcolor(self, tri, z, extra=None, kw=None): # Test that the decorator preserves the function name assert decorated.__name__ == "tripcolor" + + +@pytest.mark.parametrize("transform", [[], None, uplt.constructor.Proj("merc")]) +def test_projection_set_correctly(rng, transform): + + fig, ax = uplt.subplots(proj="merc") + fig.canvas.draw() + data = rng.random((10, 10)) + + with patch.object(ax, "imshow", wraps=ax.imshow) as mock_imshow: + # Call imshow with some dummy data + settings = {} + if transform != []: + settings["transform"] = transform + ax.imshow(data, **settings) + + # Assert that the transform keyword argument was set correctly + mock_imshow.assert_called_once() + _, kwargs = mock_imshow.call_args + assert "transform" in kwargs, "The 'transform' keyword argument is missing." + expectation = ax.projection if transform == [] else transform + assert kwargs["transform"] == expectation From e20329aa979247e05057eef3dda11e9cf002296f Mon Sep 17 00:00:00 2001 From: Casper van Elteren Date: Wed, 27 Aug 2025 09:42:47 +0200 Subject: [PATCH 05/17] Update ultraplot/tests/test_inputs.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ultraplot/tests/test_inputs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ultraplot/tests/test_inputs.py b/ultraplot/tests/test_inputs.py index a39aeecf..8f8d6914 100644 --- a/ultraplot/tests/test_inputs.py +++ b/ultraplot/tests/test_inputs.py @@ -53,5 +53,8 @@ def test_projection_set_correctly(rng, transform): mock_imshow.assert_called_once() _, kwargs = mock_imshow.call_args assert "transform" in kwargs, "The 'transform' keyword argument is missing." - expectation = ax.projection if transform == [] else transform + if transform == []: + expectation = ax.projection if ax.projection is not None else PlateCarree() + else: + expectation = transform assert kwargs["transform"] == expectation From d95a90fbf382747b52fee7f689656285c8e2efe7 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 13:21:35 +0200 Subject: [PATCH 06/17] rm projection for non imshow --- ultraplot/internals/inputs.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ultraplot/internals/inputs.py b/ultraplot/internals/inputs.py index 23b68fc2..3cf5e81a 100644 --- a/ultraplot/internals/inputs.py +++ b/ultraplot/internals/inputs.py @@ -348,7 +348,16 @@ def _preprocess_or_redirect(self, *args, **kwargs): if input_transform is not None: kwargs["transform"] = Proj(input_transform) else: - kwargs["transform"] = _not_none(self.projection, PlateCarree()) + # add projection for imshow as it + # cannot be projected on particular + # locations + options = [ + self.projection if name == "imshow" else None, + PlateCarree(), + ] + kwargs["transform"] = _not_none( + *options, + ) # Process data args # NOTE: Raises error if there are more args than keys From 9bc64e3007fa735b791e97164c5cd87fd8a79035 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 13:35:48 +0200 Subject: [PATCH 07/17] update test --- ultraplot/tests/test_inputs.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ultraplot/tests/test_inputs.py b/ultraplot/tests/test_inputs.py index 8f8d6914..10db9fc3 100644 --- a/ultraplot/tests/test_inputs.py +++ b/ultraplot/tests/test_inputs.py @@ -35,7 +35,7 @@ def tripcolor(self, tri, z, extra=None, kw=None): assert decorated.__name__ == "tripcolor" -@pytest.mark.parametrize("transform", [[], None, uplt.constructor.Proj("merc")]) +@pytest.mark.parametrize("transform", [None, uplt.constructor.Proj("merc")]) def test_projection_set_correctly(rng, transform): fig, ax = uplt.subplots(proj="merc") @@ -44,17 +44,16 @@ def test_projection_set_correctly(rng, transform): with patch.object(ax, "imshow", wraps=ax.imshow) as mock_imshow: # Call imshow with some dummy data - settings = {} - if transform != []: - settings["transform"] = transform + settings = dict(transform=transform) ax.imshow(data, **settings) # Assert that the transform keyword argument was set correctly mock_imshow.assert_called_once() _, kwargs = mock_imshow.call_args assert "transform" in kwargs, "The 'transform' keyword argument is missing." - if transform == []: - expectation = ax.projection if ax.projection is not None else PlateCarree() - else: - expectation = transform + expectation = ( + ax.projection + if ax.projection is not None + else uplt.axes.geo.ccrs.PlateCarree() + ) assert kwargs["transform"] == expectation From 7b54ec4fcab5689b44190bfb66257404bb2ac8a7 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 13:45:05 +0200 Subject: [PATCH 08/17] show passing of params --- ultraplot/tests/test_inputs.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ultraplot/tests/test_inputs.py b/ultraplot/tests/test_inputs.py index 10db9fc3..80d42dc0 100644 --- a/ultraplot/tests/test_inputs.py +++ b/ultraplot/tests/test_inputs.py @@ -51,9 +51,6 @@ def test_projection_set_correctly(rng, transform): mock_imshow.assert_called_once() _, kwargs = mock_imshow.call_args assert "transform" in kwargs, "The 'transform' keyword argument is missing." - expectation = ( - ax.projection - if ax.projection is not None - else uplt.axes.geo.ccrs.PlateCarree() - ) - assert kwargs["transform"] == expectation + assert ( + kwargs["transform"] == transform + ), f"Expected transform to be {expectation}, got {kwargs['transform']}" From 7047cadfccbfbb426fec4a17bba1d17cd914c635 Mon Sep 17 00:00:00 2001 From: Casper van Elteren Date: Wed, 27 Aug 2025 13:52:06 +0200 Subject: [PATCH 09/17] Update ultraplot/tests/test_inputs.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ultraplot/tests/test_inputs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultraplot/tests/test_inputs.py b/ultraplot/tests/test_inputs.py index 80d42dc0..5b46c68f 100644 --- a/ultraplot/tests/test_inputs.py +++ b/ultraplot/tests/test_inputs.py @@ -52,5 +52,4 @@ def test_projection_set_correctly(rng, transform): _, kwargs = mock_imshow.call_args assert "transform" in kwargs, "The 'transform' keyword argument is missing." assert ( - kwargs["transform"] == transform - ), f"Expected transform to be {expectation}, got {kwargs['transform']}" + ), f"Expected transform to be {transform}, got {kwargs['transform']}" From 82ad5b5017c0bbb7da576bf9d29f7479353198f5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:55:42 +0000 Subject: [PATCH 10/17] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ultraplot/tests/test_inputs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ultraplot/tests/test_inputs.py b/ultraplot/tests/test_inputs.py index 5b46c68f..40571497 100644 --- a/ultraplot/tests/test_inputs.py +++ b/ultraplot/tests/test_inputs.py @@ -51,5 +51,4 @@ def test_projection_set_correctly(rng, transform): mock_imshow.assert_called_once() _, kwargs = mock_imshow.call_args assert "transform" in kwargs, "The 'transform' keyword argument is missing." - assert ( - ), f"Expected transform to be {transform}, got {kwargs['transform']}" + assert (), f"Expected transform to be {transform}, got {kwargs['transform']}" From 8736afede7fc894178345271b70235d457fd4af8 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 14:06:59 +0200 Subject: [PATCH 11/17] type check for test --- ultraplot/tests/test_inputs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ultraplot/tests/test_inputs.py b/ultraplot/tests/test_inputs.py index 40571497..8e78a999 100644 --- a/ultraplot/tests/test_inputs.py +++ b/ultraplot/tests/test_inputs.py @@ -51,4 +51,6 @@ def test_projection_set_correctly(rng, transform): mock_imshow.assert_called_once() _, kwargs = mock_imshow.call_args assert "transform" in kwargs, "The 'transform' keyword argument is missing." - assert (), f"Expected transform to be {transform}, got {kwargs['transform']}" + assert ( + kwargs["transform"] is transform + ), f"Expected transform to be {transform}, got {kwargs['transform']}" From 30e509fd2b470f303438c2eb240ec449a665bb7f Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 15:26:31 +0200 Subject: [PATCH 12/17] update tests --- ultraplot/tests/test_geographic.py | 17 +++++++++++++++++ ultraplot/tests/test_inputs.py | 1 + 2 files changed, 18 insertions(+) diff --git a/ultraplot/tests/test_geographic.py b/ultraplot/tests/test_geographic.py index de62695d..cbb3f311 100644 --- a/ultraplot/tests/test_geographic.py +++ b/ultraplot/tests/test_geographic.py @@ -824,6 +824,7 @@ def are_labels_on(ax, which=("top", "bottom", "right", "left")) -> tuple[bool]: expectation = expectations[axi.number - 1] for i, j in zip(state, expectation): assert i == j + uplt.show(block=1) return fig @@ -879,3 +880,19 @@ def test_dms_used_for_mercator(): assert a == expectation assert b == expectation return fig + + +@pytest.mark.mpl_image_compare +def test_imshow_with_and_without_transform(rng): + data = rng.random((100, 100)) + fig, ax = uplt.subplots(ncols=3, proj="lcc", share=0) + ax.format(land=True, labels=True) + ax[:2].format( + latlim=(-10, 10), + lonlim=(-10, 10), + ) + ax[0].imshow(data, transform=ax[0].projection) + ax[1].imshow(data, transform=None) + ax[2].imshow(data, transform=uplt.axes.geo.ccrs.PlateCarree()) + uplt.show(block=1) + return fig diff --git a/ultraplot/tests/test_inputs.py b/ultraplot/tests/test_inputs.py index 8e78a999..a12959da 100644 --- a/ultraplot/tests/test_inputs.py +++ b/ultraplot/tests/test_inputs.py @@ -54,3 +54,4 @@ def test_projection_set_correctly(rng, transform): assert ( kwargs["transform"] is transform ), f"Expected transform to be {transform}, got {kwargs['transform']}" + uplt.close(fig) From 4e3c79cc7a3a8423cb0b80b7d62776518d68d52b Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 17:03:38 +0200 Subject: [PATCH 13/17] add visual test --- ultraplot/tests/test_geographic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultraplot/tests/test_geographic.py b/ultraplot/tests/test_geographic.py index cbb3f311..90062ef4 100644 --- a/ultraplot/tests/test_geographic.py +++ b/ultraplot/tests/test_geographic.py @@ -894,5 +894,5 @@ def test_imshow_with_and_without_transform(rng): ax[0].imshow(data, transform=ax[0].projection) ax[1].imshow(data, transform=None) ax[2].imshow(data, transform=uplt.axes.geo.ccrs.PlateCarree()) - uplt.show(block=1) + ax.format(title=["lcc", "No transform", "PlateCarree"]) return fig From a75fab4a797b60728f5a76a9111cfd4fdd50d39a Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 17:04:42 +0200 Subject: [PATCH 14/17] capitalize str --- ultraplot/tests/test_geographic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultraplot/tests/test_geographic.py b/ultraplot/tests/test_geographic.py index 90062ef4..947cc908 100644 --- a/ultraplot/tests/test_geographic.py +++ b/ultraplot/tests/test_geographic.py @@ -894,5 +894,5 @@ def test_imshow_with_and_without_transform(rng): ax[0].imshow(data, transform=ax[0].projection) ax[1].imshow(data, transform=None) ax[2].imshow(data, transform=uplt.axes.geo.ccrs.PlateCarree()) - ax.format(title=["lcc", "No transform", "PlateCarree"]) + ax.format(title=["LCC", "No transform", "PlateCarree"]) return fig From 02c92feb6148910bf085222166513c4d43ce8c3e Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Wed, 27 Aug 2025 17:10:36 +0200 Subject: [PATCH 15/17] restore inputs --- ultraplot/internals/inputs.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/ultraplot/internals/inputs.py b/ultraplot/internals/inputs.py index 3cf5e81a..ef3fe387 100644 --- a/ultraplot/internals/inputs.py +++ b/ultraplot/internals/inputs.py @@ -341,23 +341,10 @@ def _preprocess_or_redirect(self, *args, **kwargs): if kwargs.get("latlon", None) is None: kwargs["latlon"] = True if self._name == "cartopy" and name in CARTOPY_FUNCS: - # Check if an input transform is given - # else default to axis projection or - # PlateCarree if no projection is set - input_transform = kwargs.get("transform", None) - if input_transform is not None: - kwargs["transform"] = Proj(input_transform) + if kwargs.get("transform", None) is None: + kwargs["transform"] = PlateCarree() else: - # add projection for imshow as it - # cannot be projected on particular - # locations - options = [ - self.projection if name == "imshow" else None, - PlateCarree(), - ] - kwargs["transform"] = _not_none( - *options, - ) + kwargs["transform"] = Proj(kwargs["transform"]) # Process data args # NOTE: Raises error if there are more args than keys From f8a88ee1dd104aefef0662b94f9a67cdb908e08d Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Thu, 28 Aug 2025 08:50:39 +0200 Subject: [PATCH 16/17] remove debug --- ultraplot/tests/test_geographic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ultraplot/tests/test_geographic.py b/ultraplot/tests/test_geographic.py index 947cc908..4b95a938 100644 --- a/ultraplot/tests/test_geographic.py +++ b/ultraplot/tests/test_geographic.py @@ -824,7 +824,6 @@ def are_labels_on(ax, which=("top", "bottom", "right", "left")) -> tuple[bool]: expectation = expectations[axi.number - 1] for i, j in zip(state, expectation): assert i == j - uplt.show(block=1) return fig From 6ddd589abaa6775256189ac4d8110aa466c693ab Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Thu, 28 Aug 2025 09:20:41 +0200 Subject: [PATCH 17/17] add warning --- docs/projections.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/projections.py b/docs/projections.py index 7a139cad..6584f9db 100644 --- a/docs/projections.py +++ b/docs/projections.py @@ -126,7 +126,13 @@ # has its own :meth:`~ultraplot.axes.GeoAxes.format` command. :meth:`ultraplot.axes.GeoAxes.format` # facilitates :ref:`geographic-specific modifications ` like meridional # and parallel gridlines and land mass outlines. The syntax is very similar to -# :func:`ultraplot.axes.CartesianAxes.format`. Note that the `proj` keyword and several of +# :func:`ultraplot.axes.CartesianAxes.format`. +# .. important:: +# The internal reference system used for plotting in ultraplot is **PlateCarree**. +# External libraries, such as `contextily`, may use different internal reference systems, +# such as **EPSG:3857** (Web Mercator). When interfacing with such libraries, it is important +# to provide the appropriate `transform` parameter to ensure proper alignment between coordinate systems. +# Note that the `proj` keyword and several of # the :func:`~ultraplot.axes.GeoAxes.format` keywords are inspired by the basemap API. # In the below example, we create and format a very simple geographic plot. @@ -217,7 +223,7 @@ # when they are omitted (e.g., ``lon0=0`` as the default for most projections). # # .. warning:: -# The `basemap`_ package is now being actively maintained again witha short hiatus for a few years. We originally +# The `basemap`_ package is now being actively maintained again with a short hiatus for a few years. We originally # included basemap support because its gridline labeling was more powerful # than cartopy gridline labeling. While cartopy gridline labeling has # significantly improved since version 0.18, UltraPlot continues to support