Calysto / metakernel

Jupyter/IPython Kernel Tools

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to display a graphic

jlries61 opened this issue · comments

I have been developing a Metakernel based kernel for Salford Predictive Miner (https://www.salford-systems.com/products/spm) and while text output is displayed in a Jupyter notebook very nicely, attempts to display graphics give me something like this:

<Figure size 640x480 with 1 Axes>

...which is, of course, completely unhelpful.

The code to display the graphic looks like this:

    fig = plt.figure()
    varimp_series.plot.barh(title="Variable Importances", color="blue")
    plt.xlabel("Importance")
    plt.ylabel("Predictor Name")
    self.Display(fig)

I have written a version of handle_plot_settings as follows (copied from the Gnuplot kernel):

  def handle_plot_settings(self):
    """Handle the current plot settings"""
    settings = self.plot_settings
    if ('termspec' not in settings or not settings['termspec']):
      settings['termspec'] = ('pngcairo size 385, 256' ' font "Arial,10"')
    if ('format' not in settings or not settings['format']):
      settings['format'] = 'png'
    self.inline_plotting = settings['backend'] == 'inline'

This is invoked in do_execute_direct like so:

    if self._first:
      self._first = False
      self.handle_plot_settings()

The header of my kernel class looks like this:

class SPMKernel(ProcessMetaKernel):
  implementation = 'spm_kernel'
  implementation_version = __version__
  language = 'SPM'
  language_version = '8.3'
  language_info = {'name': 'str',
                   'codemirror_mode': 'shell',
                   'mimetype': 'text/x-sh',
                   'file_extension': '.cmd'}
  _first = True

Unfortunately, the documentation I have seen thus far isn't much help, but I know it can be done because other kernels do it. Any guidance I might get on this would be greatly appreciated.

The version of the Metakernel I am using is 0.20.14

Hi @jlries61, the display of a matplotlib figure is not natively built into the Jupyter Notebook or JupyterLab. If you just want a static png, you can follow the pattern here to convert the figure to HTML, which the Display function can accept.

Thanks, Steve, but it looks like I'm not quite there yet. I changed my code as follows:

    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi='figure')
    width = fig.get_figwidth()
    data = "<img src='data:image/png;base64,{0}' width={1}/>"
    data = data.format(b64encode(buf.getvalue()).decode('utf-8'), width)
    display(HTML(data))

And I get:

<IPython.core.display.HTML object>

Any ideas?

John

You need to use self.Display(HTML(data)) as the last line to use metakernel's display handler.

Edit: you need to call the parent class Display method.

@blink1073 The code now reads:

    fig = plt.figure()
    varimp_series.plot.barh(title="Variable Importances", color="blue")
    plt.xlabel("Importance")
    plt.ylabel("Predictor Name")
    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi='figure')
    width = fig.get_figwidth()
    data = "<img src='data:image/png;base64,{0}' width={1}/>"
    data = data.format(b64encode(buf.getvalue()).decode('utf-8'), width)
    self.Display(HTML(data))

And we now get no output at all, nor useful warnings in the log. Any further thoughts?

Sorry for the confusion, I meant that the last line should be super(MetaKernel, self).Display(data) to invoke the parent method.

Whoops, one edit: super(MetaKernel, self).Display(HTML(data))

@blink1073 That didn't work. My code now reads as follows:

    fig = plt.figure()
    varimp_series.plot.barh(title="Variable Importances", color="blue")
    plt.xlabel("Importance")
    plt.ylabel("Predictor Name")
    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi='figure')
    width = fig.get_figwidth()
    data = "<img src='data:image/png;base64,{0}' width={1}/>"
    data = data.format(b64encode(buf.getvalue()).decode('utf-8'), width)
    #self.Display(HTML(data))
    super(MetaKernel, self).Display(HTML(data))

And I get:

[MetaKernelApp] ERROR | Exception in message handler:
Traceback (most recent call last):
  File "/home/jries/.local/lib/python3.7/site-packages/ipykernel/kernelbase.py", line 267, in dispatch_shell
    yield gen.maybe_future(handler(stream, idents, msg))
  File "/home/jries/.local/lib/python3.7/site-packages/tornado/gen.py", line 1133, in run
    value = future.result()
  File "/home/jries/.local/lib/python3.7/site-packages/tornado/gen.py", line 326, in wrapper
    yielded = next(result)
  File "/home/jries/.local/lib/python3.7/site-packages/ipykernel/kernelbase.py", line 534, in execute_request
    user_expressions, allow_stdin,
  File "/home/jries/.local/lib/python3.7/site-packages/metakernel/_metakernel.py", line 357, in do_execute
    retval = self.do_execute_direct(code)
  File "/home/jries/.local/lib/python3.7/site-packages/spm_kernel/kernel.py", line 193, in do_execute_direct
    self.display_varimp(doc)
  File "/home/jries/.local/lib/python3.7/site-packages/spm_kernel/kernel.py", line 112, in display_varimp
    super(MetaKernel, self).Display(HTML(data))
AttributeError: 'super' object has no attribute 'Display'

Any further ideas?

Gah, my apologies, that should be super(<YourKernelClass>, self).Display(HTML(data)).

@blink1073 So I modify my code as follows:

    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi='figure')
    msg=Image(fig)
    width = fig.get_figwidth()
    data = "<img src='data:image/png;base64,{0}' width={1}/>"
    data = data.format(b64encode(buf.getvalue()).decode('utf-8'), width)
    super(SPMKernel, self).Display(HTML(data))

And we get no output. But the log looks like this:

[MetaKernelApp] Display Data
[MetaKernelApp] {'header': {'msg_id': '5a7d17ca-3e9577ba0eb7629721e1fd32', 'msg_type': 'execute_reply', 'username': 'jries', 'session': '3fe7fe67-3cc2fe643c47941011e929fd', 'date': datetime.datetime(2019, 5, 14, 17, 49, 44, 940431, tzinfo=datetime.timezone.utc), 'version': '5.3'}, 'msg_id': '5a7d17ca-3e9577ba0eb7629721e1fd32', 'msg_type': 'execute_reply', 'parent_header': {'msg_id': '48c81179a21843c38a635691c49bf0fc', 'username': 'username', 'session': '3d4c87e280c9495798150588fe094e10', 'msg_type': 'execute_request', 'version': '5.2', 'date': datetime.datetime(2019, 5, 14, 17, 49, 35, 176833, tzinfo=tzutc())}, 'content': {'status': 'ok', 'execution_count': 19, 'payload': [], 'user_expressions': {}}, 'metadata': {'started': datetime.datetime(2019, 5, 14, 17, 49, 40, 180709, tzinfo=datetime.timezone.utc)}, 'tracker': <zmq.sugar.tracker.MessageTracker object at 0x7fd5bc36d2e8>}

That code looks right to me. Are you able to share the full work in progress kernel? I'm having a hard time debugging without the full context.

spm_kernel.zip
Since SPM is proprietary, I am not at liberty to provide you a copy; but I can provide the full source code for the package as it stands (attached). The method under discussion is SPMKernel.display_varimp (starting at kernel.py:59).

Thank you once again for your help.

@blink1073 I removed some debug code from kernel.py and am resending the archive.
spm_kernel.zip

I think the only thing you're missing is properly setting the width in pixels (the width returned by get_width() is in inches). I tested locally by making this change:

diff --git a/metakernel_python/metakernel_python.py b/metakernel_python/metakernel_python.py
index 13ceb79..51ed99f 100644
--- a/metakernel_python/metakernel_python.py
+++ b/metakernel_python/metakernel_python.py
@@ -49,6 +49,22 @@ class MetaKernelPython(MetaKernel):
         return python_magic.env.get(name, None)

     def do_execute_direct(self, code):
+        import matplotlib.pyplot as plt
+        import io
+        from base64 import b64encode
+        from IPython.display import HTML
+
+        fig = plt.figure()
+        plt.plot([1,2,3])
+        plt.xlabel("Importance")
+        plt.ylabel("Predictor Name")
+        buf = io.BytesIO()
+        fig.savefig(buf, format='png', dpi='figure')
+        width = fig.get_figwidth() * fig.dpi
+        data = "<img src='data:image/png;base64,{0}' width={1}/>"
+        data = data.format(b64encode(buf.getvalue()).decode('utf-8'), width)
+        self.Display(HTML(data))
+
         python_magic = self.line_magics['python']
         return python_magic.eval(code.strip())

We have success! My code now reads as follows:

    varimp_series.plot.barh(title="Variable Importances", color="blue")
    plt.xlabel("Importance")
    plt.ylabel("Predictor Name")
    img = Image(data=fig)
    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi='figure')
    width = fig.get_figwidth() * fig.dpi
    data = "<img src='data:image/png;base64,{0}' width={1}/>"
    data = data.format(b64encode(buf.getvalue()).decode('utf-8'), width)
    super(SPMKernel, self).Display(HTML(data))

And the graphic will be attached here.
(PNG Image, 640 × 480 pixels)

@blink1073 My profuse thanks for your assistance.

Glad I could help!