-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Turn ContourSet into a standard Collection artist. #25247
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
Conversation
895a60e
to
668aa49
Compare
There was a problem hiding this 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.
lib/matplotlib/axes/_base.py
Outdated
raise ValueError("Argument must be an image, collection, or " | ||
"ContourSet in this Axes") |
There was a problem hiding this comment.
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.
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") |
lws = np.broadcast_to(CS.get_linewidth(), len(levels)).copy() | ||
lws[6] = 4 | ||
CS.set_linewidth(lws) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, good catch.
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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.
lib/matplotlib/contour.py
Outdated
@@ -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] |
There was a problem hiding this comment.
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?
tlinewidths = [w for w in linewidths] | |
tlinewidths = linewidths |
Thanks, handled all comments. |
@ianthomas23 did you have a chance to look at this? Thanks! |
Not yet, but I will do. Thanks for the reminder. |
There was a problem hiding this 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.
Looking through this now. I need to double-check a couple of things in some
old code of mine.
I am a little concerned about setting some of these tolerances as high as
they are. I'm leaning towards regenerating the test images. I tend to treat
the tolerance parameter as a way to account for differences in things out
of our control like compilers and machine architecture. Meanwhile, this is
a change that is completely within our control and we need to judge if the
change is desirable.
…On Thu, Mar 30, 2023 at 5:27 AM Ian Thomas ***@***.***> wrote:
***@***.**** approved this pull request.
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.
—
Reply to this email directly, view it on GitHub
<#25247 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AACHF6GSOXWCEER2SU33XJDW6VGXTANCNFSM6AAAAAAU77GGSA>
.
You are receiving this because you are subscribed to this thread.Message
ID: ***@***.***>
|
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. 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 |
I'll try to go through the baseline images changes again. |
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. |
This has caused an image comparison failure in Cartopy, where the |
Can you make an issue with just Matplotlib? Seems crazy we have no tests of inline clabels. |
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. |
Does it barf on log-log axes? |
Ah, I just realised Cartopy has its own Edit: and now I also see that @greglucas discussed this above 🤦♀️ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
matplotlib/lib/matplotlib/contour.py
Lines 967 to 970 in 518b268
# 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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:



new:
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
pytest
passes)Release Notes
.. versionadded::
directive in the docstring and documented indoc/users/next_whats_new/
.. versionchanged::
directive in the docstring and documented indoc/api/next_api_changes/
next_whats_new/README.rst
ornext_api_changes/README.rst