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

Implement image/png repr #343

Merged
merged 12 commits into from Sep 21, 2021
Merged

Conversation

martinRenou
Copy link
Member

@martinRenou martinRenou commented Sep 14, 2021

Follow-up of #59

Should fix #150, and fix #16

cc. @ianhi @SylvainCorlay @tacaswell

This seems to properly work in JupyterLab, but we need to push a fix to ipywidgets (and notebook) to have it working in classic Jupyter Notebook as well.

ipympl_lab

@github-actions
Copy link
Contributor

Binder 👈 Launch a binder notebook on branch martinRenou/ipympl/text-html

@@ -186,7 +188,11 @@ export class MPLCanvasModel extends widgets.DOMWidgetModel {
this.image.src = image_url;

// Tell Jupyter that the notebook contents must change.
this.send_message('ack');
if (!this.acknowledged_rendered) {
this.send_message('ack');
Copy link
Member Author

Choose a reason for hiding this comment

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

Why do we need a round-trip to the front-end??

Copy link
Member

Choose a reason for hiding this comment

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

It looks like we were trying to update the mime bundle with an "update display" message, but honnestly I don't remmember.

We should probably try to keep the PR as simple as possible for now...

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried implementing it without the round-trip but I wasn't able. I need to look deeper into that.

@SylvainCorlay SylvainCorlay mentioned this pull request Sep 14, 2021
This was referenced Sep 14, 2021
@martinRenou
Copy link
Member Author

IDK what Github uses for rendering Notebooks, but it seems to correctly handle a mix of jupyter-widgets mimetype and image/png mimetype:

github_render

nbviewer is not able to render it

nbviewer

@SylvainCorlay
Copy link
Member

nbviewer is not able to render it

Yes, that is expected as per our discussion on data type priority and the html renderer.

@ianhi
Copy link
Collaborator

ianhi commented Sep 14, 2021

Does this depend on a specific vesion of jupyterlab? On the binder link for this PR I still get the widget mimebundle taking precedence:

Peek 2021-06-30 14-08

@martinRenou
Copy link
Member Author

@ianhi I was not aware of this binder link for PRs, is this really using a dev installation of ipympl? I see ipympl in the environment.yml: https://github.com/matplotlib/ipympl/blob/master/environment.yml#L8

@martinRenou
Copy link
Member Author

Relies on jupyter/notebook#6181 and jupyter-widgets/ipywidgets#3280 for Jupyter Notebook

@ianhi
Copy link
Collaborator

ianhi commented Sep 15, 2021

@ianhi I was not aware of this binder link for PRs, is this really using a dev installation of ipympl? I see ipympl in the environment.yml: https://github.com/matplotlib/ipympl/blob/master/environment.yml#L8

:/ indeed it seems to be wrong. Ideally the bot that commented gives you a link to binder based on the PR in question.

@ianhi ianhi mentioned this pull request Sep 15, 2021
@ianhi
Copy link
Collaborator

ianhi commented Sep 15, 2021

Does this depend on a specific vesion of jupyterlab?

Testing locally it seems that this doesn't work on jlab 3.0.14 but does on 3.1.12 so something in rendering priorities or response to errors changed between 3.0 and 3.1?


potential problem:

If I understand correctly this approach works because jlab falls back on the lower priority mimetypes when there is an error loading the widget state. This error shows up because the full widget state wasn't saved, but if you check the Save widget state automatically option then the png is never showed and the frontend stalls out waiting for a draw message (it doesn't render until there is some back and forth with pyton) even though I think it should have all the information it needs to draw itself.

Peek 2021-09-15 12-15

(Somewhere I've written down why I think this is happening and how it might be fixed, I'll try to find that)

@martinRenou
Copy link
Member Author

This error shows up because the full widget state wasn't saved, but if you check the Save widget state automatically option then the png is never showed

Yes, that should be the intended behavior. We could fix this by including the png data as part of the widget model.

@martinRenou
Copy link
Member Author

nbviewer and nbconvert should take this into account if this gets merged: jupyter/nbconvert#1643

@martinRenou martinRenou marked this pull request as draft September 16, 2021 10:00
@ianhi
Copy link
Collaborator

ianhi commented Sep 16, 2021

ohhh one more thing about this approach. I think we will need to call update_display on every draw call (or more parsimoniously whenever the notebook is saved) because otherwise you don't capture any updates to the figure that happen after the initial display call (panning, zooming, adding elements, manipulating with sliders, etc)

Peek 2021-09-16 11-31

Can widgets listen to notebook save events? I think updating the embedded png on saving is more reasonable than on every draw in order to avoid the flickering you described.

@martinRenou
Copy link
Member Author

ohhh one more thing about this approach. I think we will need to call update_display on every draw call (or more parsimoniously whenever the notebook is saved) because otherwise you don't capture any updates to the figure that happen after the initial display call (panning, zooming, adding elements, manipulating with sliders, etc)

Yeah. Though update_display makes the widget flicker and it is quite annoying. We might be able to improve JupyterLab/Notebook to not have this flickering though.
Also this means sending the data twice for each update, once for the widget, and once for the image/png repr, so not really ideal.

Can widgets listen to notebook save events? I think updating the embedded png on saving is more reasonable than on every draw in order to avoid the flickering you described.

Unfortunately not on the Python side. Technically, the kernel doesn't even know what a Notebook is.

I'm tempted to say we could ignore this particular issue for now. As what we have is already quite a big improvement. And other Matplotlib backends are also not able to save the latest state of the plot without an update_display call.

@martinRenou
Copy link
Member Author

Now using a post_execute cell hook (similar to what the inline backend does), this allows getting rid of the roundtrip to the client and not having to use the update_display (removing the flickering).

This seems to work nicely, though there is still a bit of polishing to do.

@martinRenou martinRenou marked this pull request as ready for review September 21, 2021 09:33
@martinRenou
Copy link
Member Author

cc. @ianhi @SylvainCorlay @tacaswell

This seems to work nicely in JupyterLab now! It makes ipympl closer to the inline backend in behavior and implementation.

We can merge this PR now and not wait for notebook and nbconvert to update.

@martinRenou
Copy link
Member Author

ipympl

@SylvainCorlay
Copy link
Member

Let's get this in as it is a net improvement over the current situation, and tag a patch release of ipympl.

@martinRenou
Copy link
Member Author

Let's make a minor release instead, as the API changes a tiny bit: it's not plt.figure() which makes the display anymore, so wrapping it inside an Output widget doesn't work anymore.

@SylvainCorlay
Copy link
Member

Placing show in an output widget shoulds still work as expected though.

@SylvainCorlay SylvainCorlay merged commit 4faf35f into matplotlib:master Sep 21, 2021
@SylvainCorlay SylvainCorlay deleted the text-html branch September 21, 2021 12:49
@jklymak
Copy link
Member

jklymak commented Sep 21, 2021

Thanks for the persistence on this one! Sounds like it was tricky...

Comment on lines -86 to -95
# Figure width in pixels
pwidth = (self.canvas.figure.get_figwidth() *
self.canvas.figure.get_dpi())
# Scale size to match widget on HiDPI monitors.
if hasattr(self.canvas, 'device_pixel_ratio'): # Matplotlib 3.5+
width = pwidth / self.canvas.device_pixel_ratio
else:
width = pwidth / self.canvas._dpi_ratio
data = "<img src='data:image/png;base64,{0}' width={1}/>"
data = data.format(b64encode(buf.getvalue()).decode('utf-8'), width)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@martinRenou why is it ok to remove this dpi scaling? It seems like we will now have imperfect behavior on hi dpi displays.

Copy link
Member Author

Choose a reason for hiding this comment

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

This might have been a mistake, we should open an issue to track this

Copy link
Member

Choose a reason for hiding this comment

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

I'll do it, actually. If I understand correctly, the PNG doesn't get saved at high res.

Copy link
Member

Choose a reason for hiding this comment

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

Note: we should not determine the dpi based on the browser because it could differ between clients.
It should be a fixed value.

@ianhi
Copy link
Collaborator

ianhi commented Sep 22, 2021

Let's make a minor release instead, as the API changes a tiny bit: it's not plt.figure() which makes the display anymore, so wrapping it inside an Output widget doesn't work anymore.

I can see this making #290 harder because now figures aren't shown until the end of cell execution? Or maybe that can be made to work with an explicit call to plt.show in a notebook cell?

@SylvainCorlay
Copy link
Member

I can see this making #290 harder because now figures aren't shown until the end of cell execution? Or maybe that can be made to work with an explicit call to plt.show in a notebook cell?

Yes, I actually think that the interactive mode (plt.ion()) is somewhat intrinsically broken. I would prefer it to be off by default and explicit calls to plt.show() to be required...

@nvaytet
Copy link

nvaytet commented Jun 29, 2022

Hi, unfortunately, I am still seeing this error after re-opening the notebook.
Screenshot at 2022-06-29 16-31-42

The console log from Chrome:
Screenshot at 2022-06-29 16-33-54

Versions:

jupyter_client            7.3.1              pyhd8ed1ab_0    conda-forge
jupyter_core              4.10.0           py39hf3d152e_0    conda-forge
jupyter_server            1.17.0             pyhd8ed1ab_0    conda-forge
jupyterlab                3.4.2              pyhd8ed1ab_0    conda-forge
jupyterlab_pygments       0.2.2              pyhd8ed1ab_0    conda-forge
jupyterlab_server         2.14.0             pyhd8ed1ab_0    conda-forge
jupyterlab_widgets        1.1.0              pyhd8ed1ab_0    conda-forge

matplotlib                3.5.2            py39hf3d152e_0    conda-forge
matplotlib-base           3.5.2            py39h700656a_0    conda-forge
matplotlib-inline         0.1.3              pyhd8ed1ab_0    conda-forge

ipydatawidgets            4.3.1.post1        pyhc268e32_0    conda-forge
ipykernel                 6.13.0           py39hef51801_0    conda-forge
ipympl                    0.9.1              pyhd8ed1ab_0    conda-forge
ipython                   8.4.0            py39hf3d152e_0    conda-forge
ipython_genutils          0.2.0                      py_1    conda-forge
ipywidgets                7.7.0              pyhd8ed1ab_0    conda-forge

Thanks for any help!

@jklymak
Copy link
Member

jklymak commented Jun 29, 2022

@nvaytet, I'd open a new issue - but for what it is worth, I have the same problem as you....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

embed and close button Images not saved in *.ipynb
5 participants