Skip to content

Documentation and bug report: mplot3d and blitting #27830

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

Closed
Fcp-GitHub opened this issue Feb 28, 2024 · 7 comments
Closed

Documentation and bug report: mplot3d and blitting #27830

Fcp-GitHub opened this issue Feb 28, 2024 · 7 comments
Labels

Comments

@Fcp-GitHub
Copy link

Bug summary

Summary

Problem with 3d animation using FuncAnimation and the mplot3d toolkit. Maybe this problem comes from not enough clarifications in the documentation. Following this it is presented a simplified version of the program that led me to these conclusions.

Explanation

Looking through the documentation regarding animation and blitting, it is possible to see two different approaches:

  • The first one (see here) is presented at the first explanation page about creating animations with the animation module. Here it is explained that in the function passed to FuncAnimation there has to be some method that updates the data in each artist in order to generate the new frame. It is written that "the update function uses the set_* function for different artists to modify the data".
  • However, the second approach (presented here, where blitting is actually explained) tells the user to use the draw_artist() method in order to update/redraw efficiently the artists in the function of the render loop. I haven't found many low-level details (except some look directly at the source code) about the functioning of FuncAnimation or plt.show(), so I couldn't make real assumptions about their involvement in this.

Mixing the informations, I have encountered many problems that I will try to replicate and explain in the following code.

Code for reproduction

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Line3D
from matplotlib.animation import FuncAnimation

fig = plt.figure()
ax = fig.add_subplot(projection='3d', xlim=(0,100), ylim=(0,100), zlim=(0,100))

line = Line3D( [0], [0], [0], marker="o", markersize=15, color='r', animated=True)

def update(t):
    line.set_data_3d([t],[t],[t])
    ax.draw_artist(line)
    
    return line,

anim = FuncAnimation(fig=fig, func=update, frames=None, blit=True, cache_frame_data=False)

plt.show()

Actual outcome

File "C:\Users\user\Desktop...\axes3d_issue.py", line 13, in update
ax.draw_artist(line)
File "C:\Users\user\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\site-packages\matplotlib\axes_base.py", line 3080, in draw_artist
a.draw(self.figure.canvas.get_renderer())
File "C:\Users\user\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\site-packages\matplotlib\artist.py", line 72, in draw_wrapper
return draw(artist, renderer)
^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\user\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.12_qbz5n2kfra8p0\LocalCache\local-packages\Python312\site-packages\mpl_toolkits\mplot3d\art3d.py", line 270, in draw
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)
^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'M'

Expected outcome

The rendering of the image.

Additional information

Some considerations:

  1. Maybe the draw_artist() method should be used in combination with plt.pause() or by API developers, but neither is specified in the documentation. Removing the ax.draw_artist(line) (following thus the first approach described at the beginning) is indeed a solution to the problem.
  2. Using Visual Studio Code Data inspection debug tool along with debugpy, I've noticed that in the update() function the Line3D artist seems to lose all the references to the parent classes Axes3D and Figure, hence some error messages. However, trying to add something like line.axes = ax leads directly to an exception raised due to the next problem.
  3. It seems that without calling the 'Axes3D.draw()' method first, the projection matrix variable M of the same class is never updated from None to some value. Actually, calling the Axes3D.get_proj() method for M and computing the inverse matrix also for the invM attribute seems to work. This appears to be a flaw in every case, but maybe it corroborates the hypothesis of some kind of "reservedness" of the method for some special cases.
    If this is the case, it is not specified neither in the already cited documentation, neither in the (quite short, for some reason) mplot3d documentation (see here).

I think the documentation about this topic should be a bit clearer. Some more explanation about the inner working of some ubiquitous functions like plt.show() or the clear methods, another set of functions that is presentend unclearly, could help users understand and resolve their problems, along with using them more consciously.

Operating system

Microsoft Windows 11 Home

Matplotlib Version

3.8.2

Matplotlib Backend

TkAgg

Python version

3.12.2

Jupyter version

No response

Installation

pip

@WeatherGod
Copy link
Member

WeatherGod commented Feb 28, 2024 via email

@WeatherGod
Copy link
Member

WeatherGod commented Feb 28, 2024 via email

@timhoffm timhoffm added topic: mplot3d Documentation: examples files in galleries/examples labels Feb 28, 2024
@Fcp-GitHub
Copy link
Author

Unfortunately, I won't be able to test the new results for some days. However, if I remember correctly, something was off in this case too.
Actually, the problem was that I had added the ax.add_line() method in the initialization function of FuncAnimation and the initial frame of the artists remained even after data update. The artists were moving, but the initial frame was drawn again too every time.

Thank you for your answer.

@Fcp-GitHub
Copy link
Author

Ok, I have added the ax.add_line(line) method and it doesn't get better. Actually, the error message is pretty similar to the one at the top of the discussion.
Checking the source code again, it still seems to be an issue related to Axes3d not having a projection matrix soon enough because it's draw_artist() method calls immediately the artist's draw() one and thus never initiates its projection matrix ax.M (that is needed by the Line3D.draw() method).
Maybe the problem is that the draw_artist() method shouldn't be used within the FuncAnimation's update function. If this is the case, it should be explicited in the documentation. I've noticed problems also with clearing functions like: ax.clear()), fig.clear(), plt.clear(), etc. simply put in the update function.

@timhoffm
Copy link
Member

timhoffm commented Mar 5, 2024

If you use FuncAnimation, follow the basic guide here
In particular, create an initial figure with regular plotting methods - manually instantiating Artists and trying to wire them up correctly would be a hassle. Also, you don't need any draw_artist(); FuncAnimation takes care of this.

This works:

fig = plt.figure()
ax = fig.add_subplot(projection='3d', xlim=(0,100), ylim=(0,100), zlim=(0,100))
line, = ax.plot([0], [0], [0], marker="o", markersize=15, color='r')

def update(t):
    line.set_data_3d([t],[t],[t])
    return line,

anim = FuncAnimation(fig=fig, func=update, frames=100, blit=True, cache_frame_data=False)

timhoffm added a commit to timhoffm/matplotlib that referenced this issue Mar 5, 2024
- use `set_data_3d`
- cross-reference `Line2d` `set_data`, `set_xdata`, `set_ydata`
- Rewrite parts of the FuncAnimation description

Inspired through matplotlib#27830.
timhoffm added a commit to timhoffm/matplotlib that referenced this issue Mar 5, 2024
- use `set_data_3d`
- cross-reference `Line2d` `set_data`, `set_xdata`, `set_ydata`
- Rewrite parts of the FuncAnimation description

Inspired through matplotlib#27830.
@timhoffm
Copy link
Member

timhoffm commented Mar 5, 2024

#27867 fixes some general documentation issues uncovered above.

Still open: Better description of blitting in FuncAnimation. E.g.

  • do we need animated=True for the Artists or does FuncAnimation handle this (with and without intit_func)?
  • does blitting work at all? I don't see a difference in output or timing in my above code?

@Fcp-GitHub
Copy link
Author

Fcp-GitHub commented Mar 6, 2024

#27867 fixes some general documentation issues uncovered above.

Still open: Better description of blitting in FuncAnimation. E.g.

  • do we need animated=True for the Artists or does FuncAnimation handle this (with and without intit_func)?

  • does blitting work at all? I don't see a difference in output or timing in my above code?

Agreed. The animated=True seems actually a bit extra considering that we don't need to call functions such as draw_artist() that appear to be mandatory using animated reading here for example. However, if I remember correctly, in the source code FuncAnimation distinguishes between blitting switched on or off.
Maybe the problem with the documentation about blitting is that it isn't clearly subdivided between the parts talking about blitting with raw animations (using plt.clear()) and the ones talking about the module FuncAnimation. There should be clear partitioning between animations with helper classes/modules and raw animations.

I think there should also be some better documentation for mplot3d that now has just a list of examples in it. I know its usage should be similar to the 2D counterparts, but it has its peculiarities.

timhoffm added a commit to timhoffm/matplotlib that referenced this issue Mar 6, 2024
- use `set_data_3d`
- cross-reference `Line2d` `set_data`, `set_xdata`, `set_ydata`
- Rewrite parts of the FuncAnimation description

Inspired through matplotlib#27830.
Impaler343 pushed a commit to Impaler343/matplotlib that referenced this issue Mar 14, 2024
- use `set_data_3d`
- cross-reference `Line2d` `set_data`, `set_xdata`, `set_ydata`
- Rewrite parts of the FuncAnimation description

Inspired through matplotlib#27830.
Impaler343 added a commit to Impaler343/matplotlib that referenced this issue Mar 14, 2024
pin pytest

Deprecate plot_date

Correctly set temporary pdf/pgf backends

Calling `FigureCanvasPdf(figure)` will call `figure.set_canvas(self)`,
meaning the `cbook._setattr_cm` context manager that wraps this change
will see the _new_ canvas, and the old canvas isn't restored.

Instead, just pass the `backend` parameter, as `savefig` already knows
how to correctly save and restore the canvas if it needs to change
backends.

Fixes matplotlib#27865

Bump the actions group with 2 updates

Bumps the actions group with 2 updates: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) and [scientific-python/upload-nightly-action](https://github.com/scientific-python/upload-nightly-action).

Updates `pypa/gh-action-pypi-publish` from 1.8.11 to 1.8.12
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](pypa/gh-action-pypi-publish@2f6f737...e53eb8b)

Updates `scientific-python/upload-nightly-action` from 0.3.0 to 0.5.0
- [Release notes](https://github.com/scientific-python/upload-nightly-action/releases)
- [Commits](scientific-python/upload-nightly-action@6e9304f...b67d7fc)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
- dependency-name: scientific-python/upload-nightly-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>

Bump pydata-sphinx-theme to 0.15

Fix search button placement in navbar

Fix doc sidebar

Fix version switcher url for circleCI

Don't use custom CSS for announcement banner

Disable parallel build

Don't show doc source link

Add dtype/copy args to internal testing class

Use pybind11 string formatter for exception messages

This can eventually be replaced by `std::format` when we require C++20.

doc: add description of **kwargs usage to collections (matplotlib#27872)

* doc: add description of **kwargs usage to collections
* Update lib/matplotlib/collections.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

---------

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

TST: adding tests of current clear behavior on ticks

closes matplotlib#23839

DOC: update comments and docstrings

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

Update lib/matplotlib/tests/test_axes.py

BLD,Cygwin: Included Python.h first in src/_c_internal_utils.cpp

Python.h needs to be included before any system includes.  This is easiest if it is the first include.  Interestingly, pybind11/pybind11.h does not include Python, possibly depending on downstream projects including Python.h beforehand.

CI: Include Python.h first in _tkagg.cpp

Needs to be included before system headers to set visibility macros for pybind11

BLD: Include Python.h first in _backend_agg.cpp

BLD: Include Python.h before pybind11/pybind11.h

pybind11 assumes Python.h is included first.

CI: Specify python version for default pytest.

CI,Cygwin: Run pytest with python -m pytest

Cygwin pytest recently updated, and only installs pytest-3.9

CI,Cygwin: Revert use of alternatives to set pytest script.

Cygwin pytest only installs pytest-3.9.
I should possibly suggest using alternatives for that and pip.

CI,Cygwin: Avoid running pytest in root directory.

CI,Cygwin: Revert running pytest in different directory.

CI,Cygwin: Call pytest-3.9 explicitly.

BLD: Include Python.h first in src/_path.h

BLD,BUG: Remove Python.h includes immediately preceeding pybind11/pybind11.h includes

pybind11 seems decent about including Python.h before any system headers, once you chase three layers of includes in to see that.

FIX: handle nans in RGBA input with ScalarMappables

DOC: Update some animation related topics

- use `set_data_3d`
- cross-reference `Line2d` `set_data`, `set_xdata`, `set_ydata`
- Rewrite parts of the FuncAnimation description

Inspired through matplotlib#27830.

Revert "Merge branch 'matplotlib:main' into doc-change"

This reverts commit feeabe6, reversing
changes made to bcbc2ef.

Reapply "Merge branch 'matplotlib:main' into doc-change"

This reverts commit ed91cee.

Convert path extension to pybind11

Add a pybind11 type caster for agg::rect_d

Add a pybind11 type caster for agg::trans_affine

Add a pybind11 type caster for mpl::PathIterator

Add a pybind11 type caster for e_snap_mode

Add a pybind11 type caster for SketchParams

Optimize convert_polygon_vector a bit

FIX: don't copy twice on RGB input

Fix devdocs version switcher

Allow passing a transformation to secondary_[xy]axis

Add transform argument to secondary axes

Update _secax_docstring

Move new params to end of functions

Add input check to secondary axes

Add tests

Add examples

Move transform type checks and improve docs

Add type stubs

Update _secax_docstring

Move new params to end of functions

Add input check to secondary axes

Move transform type checks and improve docs

Fix rebase error

Fix stub for SecondaryAxis.__init__

Clarify example

Add default param to secax constructor

Fix stub for secax constructor

Simplify imports

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

Remove redundancy in docs

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

Fix typo

DOC: fix stray release note entry

BLD: Add a fallback URL for FreeType

FreeType is available from both Savannah and SourceForge, and sometimes
AppVeyor seems to have trouble downloading from Savannah, so perhaps
this fallback will help. We used to try both these URLs in the pre-Meson
build system.

DOC: State approximate documentation build time

Since the doc build takes very long, an order-of-magnitude estimate
is helpful to manage expectations.

I'm not wedded to the actual numbers, if somebody has better
suggestions. A quick test on an old i7 6700 (from 2015) yielded

- `O=-j1`: 23min
- `O=-j4`: 15min

Doc build on CI is around 18min.

Add BackendRegistry singleton class

Use backend_registry for name of singleton instance

Use class variables for immutable collections

Review comments

Update lib/matplotlib/backend_bases.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

Linting/mypy fixes

Remove INTERACTIVE_NON_WEB backend filter

Small changes from review

Use _api.caching_module_getattr for deprecated module-level attributes

Add docstrings

Add api changes and what new docs

Fix docs

Inline the deprecation function calls

Import BackendFilter and backend_registry into
matplotlib.backends.__init__.py

Mypy fixes

Remove unused _safe_pyplot_import

Remove unneeded type annotations

Make sure custom alpha param does not change 'none' colors in a list of colors (matplotlib#27845)

* fix issue 27839, make sure alpha param does not affect none colors
Apply review feedbacks

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Update lib/matplotlib/colors.py

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

---------

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

DOC: fix :mpltype:`color` role

`inline.interpreted()` already returns a list of nodes. We mustn't wrap
it in another list.

Use :mpltype:`color` for color types
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants