Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Turn ContourSet into a standard Collection artist. #25247

Merged
merged 2 commits into from Jun 28, 2023

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Feb 17, 2023

PR Summary

Closes (implements) #25128. Goes on top of #25138.

Keep (some) backcompat by making access to ContourSet.collection trigger
the self-replacement of the ContourSet by the old-style list of
PathCollections.

The baseline images are slighly shifted, but the new images actually
look more correct; see e.g. contour_corner_mask_False.png where the old
("-expected.png") implementation would white out some extra L-shaped
areas between masked regions (particularly visible in the diff image).

old:
contour_corner_mask_False-expected
new:
contour_corner_mask_False
diff:
contour_corner_mask_False-failed-diff

I suspect that many of the other diffs are due to tiny differences in rendering between drawing a single Path object with multiple blocks separated by MOVETO, and multiple separate Path objects (#25263 is one possible cause, though likely not the only one). I've checked all the diffs where I bumped the tol and at least they look reasonable, even though it may be possible to further reduce them by careful investigation. OTOH we can also accept the diffs and just regen the baselines.

PR Checklist

Documentation and Tests

  • Has pytest style unit tests (and pytest passes)
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • New plotting related features are documented with examples.

Release Notes

  • New features are marked with a .. versionadded:: directive in the docstring and documented in doc/users/next_whats_new/
  • API changes are marked with a .. versionchanged:: directive in the docstring and documented in doc/api/next_api_changes/
  • Release notes conform with instructions in next_whats_new/README.rst or next_api_changes/README.rst

Copy link
Contributor

@greglucas greglucas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice! I like that this can be a single collection without having to remember to go into the ContourSet.collections attribute. I tested all of the contour examples which all produced what I would expect with zoom/pan etc as well.

I can't say I followed everything in this, but logically it made sense and your comments were helpful along the way. I think a lot of the complexity is due to the complexity of the current ContourSet class itself and not really that you added a lot of extra complexity with this PR.

doc/api/next_api_changes/deprecations/25138-AL.rst Outdated Show resolved Hide resolved
Comment on lines 2183 to 2179
raise ValueError("Argument must be an image, collection, or "
"ContourSet in this Axes")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to call out ContourSet anymore since it is a Collection.

Suggested change
raise ValueError("Argument must be an image, collection, or "
"ContourSet in this Axes")
raise ValueError("Argument must be an image or collection "
"in this Axes")

Comment on lines 88 to 90
lws = np.broadcast_to(CS.get_linewidth(), len(levels)).copy()
lws[6] = 4
CS.set_linewidth(lws)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a case where having a single collection is not quite as nice. I'm not sure how much this is done in practice though. Possibly users wanting to set the dashed negative linestyles and solid positive linestyles manually themselves?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 88 simplification:
lws = np.resize(CS.get_linewidth(), len(levels))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, good catch.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or even lws = np.ones(len(levels)) * 2? Its not like the linewidths are a mystery to the user.


for c in cset2.collections:
c.set_linestyle('solid')
cset2.set_linestyle('solid')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here this is much nicer :)

@@ -14,6 +14,17 @@
import pytest


def _maybe_split_collections(do_split):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment here noting that this is a helper to test the 3.8 deprecations and that we should remove it after the deprecation lapses.

@@ -1240,11 +1309,10 @@ def _process_linewidths(self):
linewidths = linewidths * nreps
if len(linewidths) > Nlev:
linewidths = linewidths[:Nlev]
tlinewidths = [(w,) for w in linewidths]
tlinewidths = [w for w in linewidths]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're guaranteed a list of linewidths from both paths above?

Suggested change
tlinewidths = [w for w in linewidths]
tlinewidths = linewidths

@anntzer
Copy link
Contributor Author

anntzer commented Mar 17, 2023

Thanks, handled all comments.

@jklymak
Copy link
Member

jklymak commented Mar 28, 2023

@ianthomas23 did you have a chance to look at this? Thanks!

@ianthomas23
Copy link
Member

@ianthomas23 did you have a chance to look at this? Thanks!

Not yet, but I will do. Thanks for the reminder.

Copy link
Member

@ianthomas23 ianthomas23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

Noting that with the change of behaviour of .collections no longer returning information about the relationship between filled polygons and their holes, we will find out who has been using mpl to obtain such information rather than just render filled contours.

@WeatherGod
Copy link
Member

WeatherGod commented Mar 30, 2023 via email

@greglucas
Copy link
Contributor

It might be worth checking with downstream libraries as well? I just ran this PR with Cartopy and there is one failure with contour labeling, which I think should be addressable on the Cartopy-side since we override that method with some path manipulations.
Cartopy: https://github.com/SciTools/cartopy/blob/main/lib/cartopy/mpl/contour.py

Possibly check with Astropy? (looking at this code, this update may actually help them to only have one collection at a time going through their transform stack and avoid this utility function altogether): https://github.com/astropy/astropy/blob/8010bcb486087b1d3073a4f69f300f75d784d3e1/astropy/visualization/wcsaxes/utils.py#L118

@anntzer
Copy link
Contributor Author

anntzer commented Mar 30, 2023

I'll try to go through the baseline images changes again.
@dstansby I think(?) you're involved with astropy; can you check whether this PR will cause problems for them?

@anntzer
Copy link
Contributor Author

anntzer commented Jun 15, 2023

Thanks. Pushed the change, as well as @efiring's suggestion, will work on the api change note later.

@tacaswell
Copy link
Member

I am going to merge this as-is and open a new issue to expand the API change notes on this to more thoroughly explain how to update the styling on a specific contour in the set.

@rcomer
Copy link
Member

rcomer commented Jul 7, 2023

This has caused an image comparison failure in Cartopy, where the inline=True option for ax.clabel is no longer working. See SciTools/cartopy#2207.

@jklymak
Copy link
Member

jklymak commented Jul 7, 2023

Can you make an issue with just Matplotlib? Seems crazy we have no tests of inline clabels.

@rcomer
Copy link
Member

rcomer commented Jul 7, 2023

I tried running the Contour Label Demo in the same environment, and that looks fine. So I'm guessing the problem has something to do with transforms, which I've never done anything with directly. So not sure where to start with making a mpl-only example.

@jklymak
Copy link
Member

jklymak commented Jul 7, 2023

Does it barf on log-log axes?

@rcomer
Copy link
Member

rcomer commented Jul 7, 2023

Ah, I just realised Cartopy has its own clabel. I missed that before because I only looked on GeoAxes.
https://github.com/SciTools/cartopy/blob/c4858e4963fd8a11fe227700a4b97043f69986ab/lib/cartopy/mpl/contour.py#L22

Edit: and now I also see that @greglucas discussed this above 🤦‍♀️

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this change broke my "contours to shapefile" code, because the temporarily retained functionality of CountourSet.collections actually does not behave as it did before matplotlib 3.8.0. Somehow it is causing my final polygon set to become a single polygon, so that every contour has a connecting segment to the next contour in the set. So I was forced to update my code to the new definition of CountourSet, which I guess isn't a terrible thing, but effectively there is no "deprecation period"...it was immediate for me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mstoelinga, please try our main branch and see if this is fixed for you. This was a mistake, and should be fixed in 3.8.1

Copy link
Member

@rcomer rcomer Oct 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the collections attribute actually couldn’t be fully backwards compatible (from this comment)

# On access, make oneself invisible and instead add the old-style collections
# (one PathCollection per level). We do not try to further split contours into
# connected components as we already lost track of what pairs of contours need
# to be considered as single units to draw filled regions with holes.

There is now only one path in each collection, whereas there used to be multiple.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"contours to shapefile" code

This sounds like something that could use contourpy directly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very much so. I need to write up some docs on how to convert ContourPy outputs into other formats such as Shapely.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also looking for a solution to convert the ContourSet to shapefile. The code below does not work anymore.

from shapely.geometry import LineString

contour = ax.conout(...)
levels = contour.levels
shps = []
lvls = []
for l, c in zip(levels, contour):
    for ps in c.get_paths():
        shps.append(LineString(ps.vertices))
        lvls.append(l)
shp = gpd.GeoDataFrame({'level':lvls}, geometry=shps, )

Any quick fix? Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rcomer thanks. Are the plotted contours identical to what ContourPy generates?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ougx Matplotlib uses ContourPy's contours.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API: changes API: consistency Release critical For bugs that make the library unusable (segfaults, incorrect plots, etc) and major regressions. status: needs revision topic: contour
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet