octavemagic.py
364 lines
| 9.7 KiB
| text/x-python
|
PythonLexer
|
r7378 | # -*- coding: utf-8 -*- | ||
""" | ||||
=========== | ||||
octavemagic | ||||
=========== | ||||
|
r7379 | Magics for interacting with Octave via oct2py. | ||
|
r7378 | |||
|
r7439 | .. note:: | ||
The ``oct2py`` module needs to be installed separately, and in turn depends | ||||
on ``h5py``. Both can be obtained using ``easy_install`` or ``pip``. | ||||
|
r7378 | Usage | ||
===== | ||||
``%octave`` | ||||
{OCTAVE_DOC} | ||||
``%octave_push`` | ||||
{OCTAVE_PUSH_DOC} | ||||
``%octave_pull`` | ||||
{OCTAVE_PULL_DOC} | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (C) 2012 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
import tempfile | ||||
from glob import glob | ||||
from shutil import rmtree | ||||
import numpy as np | ||||
import oct2py | ||||
|
r7381 | from xml.dom import minidom | ||
|
r7378 | |||
from IPython.core.displaypub import publish_display_data | ||||
|
r7379 | from IPython.core.magic import (Magics, magics_class, line_magic, | ||
|
r7378 | line_cell_magic) | ||
from IPython.testing.skipdoctest import skip_doctest | ||||
from IPython.core.magic_arguments import ( | ||||
argument, magic_arguments, parse_argstring | ||||
) | ||||
from IPython.utils.py3compat import unicode_to_str | ||||
class OctaveMagicError(oct2py.Oct2PyError): | ||||
pass | ||||
|
r7381 | _mimetypes = {'png' : 'image/png', | ||
'svg' : 'image/svg+xml', | ||||
'jpg' : 'image/jpeg', | ||||
'jpeg': 'image/jpeg'} | ||||
|
r7378 | |||
@magics_class | ||||
class OctaveMagics(Magics): | ||||
"""A set of magics useful for interactive work with Octave via oct2py. | ||||
""" | ||||
def __init__(self, shell): | ||||
""" | ||||
Parameters | ||||
---------- | ||||
shell : IPython shell | ||||
""" | ||||
super(OctaveMagics, self).__init__(shell) | ||||
|
r7381 | self._oct = oct2py.Oct2Py() | ||
self._plot_format = 'png' | ||||
|
r7383 | # Allow publish_display_data to be overridden for | ||
# testing purposes. | ||||
self._publish_display_data = publish_display_data | ||||
|
r7381 | |||
def _fix_gnuplot_svg_size(self, image, size=None): | ||||
""" | ||||
GnuPlot SVGs do not have height/width attributes. Set | ||||
these to be the same as the viewBox, so that the browser | ||||
scales the image correctly. | ||||
Parameters | ||||
---------- | ||||
image : str | ||||
SVG data. | ||||
size : tuple of int | ||||
Image width, height. | ||||
""" | ||||
(svg,) = minidom.parseString(image).getElementsByTagName('svg') | ||||
viewbox = svg.getAttribute('viewBox').split(' ') | ||||
if size is not None: | ||||
width, height = size | ||||
else: | ||||
width, height = viewbox[2:] | ||||
svg.setAttribute('width', '%dpx' % width) | ||||
svg.setAttribute('height', '%dpx' % height) | ||||
return svg.toxml() | ||||
|
r7378 | |||
@skip_doctest | ||||
@line_magic | ||||
def octave_push(self, line): | ||||
''' | ||||
Line-level magic that pushes a variable to Octave. | ||||
`line` should be made up of whitespace separated variable names in the | ||||
IPython namespace:: | ||||
In [7]: import numpy as np | ||||
In [8]: X = np.arange(5) | ||||
In [9]: X.mean() | ||||
Out[9]: 2.0 | ||||
In [10]: %octave_push X | ||||
In [11]: %octave mean(X) | ||||
Out[11]: 2.0 | ||||
''' | ||||
inputs = line.split(' ') | ||||
for input in inputs: | ||||
|
r7380 | input = unicode_to_str(input) | ||
|
r7381 | self._oct.put(input, self.shell.user_ns[input]) | ||
|
r7378 | |||
@skip_doctest | ||||
@line_magic | ||||
def octave_pull(self, line): | ||||
''' | ||||
Line-level magic that pulls a variable from Octave. | ||||
In [18]: _ = %octave x = [1 2; 3 4]; y = 'hello' | ||||
In [19]: %octave_pull x y | ||||
In [20]: x | ||||
Out[20]: | ||||
array([[ 1., 2.], | ||||
[ 3., 4.]]) | ||||
In [21]: y | ||||
Out[21]: 'hello' | ||||
''' | ||||
outputs = line.split(' ') | ||||
for output in outputs: | ||||
|
r7379 | output = unicode_to_str(output) | ||
|
r7381 | self.shell.push({output: self._oct.get(output)}) | ||
|
r7378 | |||
@skip_doctest | ||||
@magic_arguments() | ||||
@argument( | ||||
'-i', '--input', action='append', | ||||
|
r7380 | help='Names of input variables to be pushed to Octave. Multiple names ' | ||
'can be passed, separated by commas with no whitespace.' | ||||
|
r7378 | ) | ||
@argument( | ||||
'-o', '--output', action='append', | ||||
|
r7380 | help='Names of variables to be pulled from Octave after executing cell ' | ||
'body. Multiple names can be passed, separated by commas with no ' | ||||
'whitespace.' | ||||
|
r7378 | ) | ||
@argument( | ||||
|
r7439 | '-s', '--size', action='store', | ||
|
r7381 | help='Pixel size of plots, "width,height". Default is "-s 400,250".' | ||
) | ||||
@argument( | ||||
|
r7439 | '-f', '--format', action='store', | ||
|
r7381 | help='Plot format (png, svg or jpg).' | ||
|
r7379 | ) | ||
|
r7381 | |||
|
r7379 | @argument( | ||
|
r7378 | 'code', | ||
nargs='*', | ||||
) | ||||
@line_cell_magic | ||||
def octave(self, line, cell=None): | ||||
''' | ||||
Execute code in Octave, and pull some of the results back into the | ||||
Python namespace. | ||||
In [9]: %octave X = [1 2; 3 4]; mean(X) | ||||
Out[9]: array([[ 2., 3.]]) | ||||
As a cell, this will run a block of Octave code, without returning any | ||||
value:: | ||||
In [10]: %%octave | ||||
....: p = [-2, -1, 0, 1, 2] | ||||
....: polyout(p, 'x') | ||||
-2*x^4 - 1*x^3 + 0*x^2 + 1*x^1 + 2 | ||||
In the notebook, plots are published as the output of the cell, e.g. | ||||
%octave plot([1 2 3], [4 5 6]) | ||||
will create a line plot. | ||||
Objects can be passed back and forth between Octave and IPython via the | ||||
-i and -o flags in line:: | ||||
In [14]: Z = np.array([1, 4, 5, 10]) | ||||
In [15]: %octave -i Z mean(Z) | ||||
Out[15]: array([ 5.]) | ||||
In [16]: %octave -o W W = Z * mean(Z) | ||||
Out[16]: array([ 5., 20., 25., 50.]) | ||||
In [17]: W | ||||
Out[17]: array([ 5., 20., 25., 50.]) | ||||
|
r7381 | The size and format of output plots can be specified:: | ||
In [18]: %%octave -s 600,800 -f svg | ||||
...: plot([1, 2, 3]); | ||||
|
r7378 | ''' | ||
args = parse_argstring(self.octave, line) | ||||
# arguments 'code' in line are prepended to the cell lines | ||||
|
r7381 | if cell is None: | ||
|
r7378 | code = '' | ||
return_output = True | ||||
line_mode = True | ||||
else: | ||||
code = cell | ||||
return_output = False | ||||
line_mode = False | ||||
code = ' '.join(args.code) + code | ||||
if args.input: | ||||
for input in ','.join(args.input).split(','): | ||||
|
r7379 | input = unicode_to_str(input) | ||
|
r7381 | self._oct.put(input, self.shell.user_ns[input]) | ||
|
r7378 | |||
# generate plots in a temporary directory | ||||
plot_dir = tempfile.mkdtemp() | ||||
|
r7379 | if args.size is not None: | ||
|
r7439 | size = args.size | ||
|
r7379 | else: | ||
size = '400,240' | ||||
|
r7378 | |||
|
r7381 | if args.format is not None: | ||
|
r7439 | plot_format = args.format | ||
|
r7381 | else: | ||
plot_format = 'png' | ||||
|
r7378 | pre_call = ''' | ||
global __ipy_figures = []; | ||||
page_screen_output(0); | ||||
function fig_create(src, event) | ||||
global __ipy_figures; | ||||
|
r7382 | __ipy_figures(size(__ipy_figures) + 1) = src; | ||
|
r7379 | set(src, "visible", "off"); | ||
|
r7378 | end | ||
set(0, 'DefaultFigureCreateFcn', @fig_create); | ||||
|
r7380 | close all; | ||
|
r7378 | clear ans; | ||
|
r7384 | |||
# ___<end_pre_call>___ # | ||||
|
r7378 | ''' | ||
post_call = ''' | ||||
|
r7384 | # ___<start_post_call>___ # | ||
|
r7378 | |||
# Save output of the last execution | ||||
if exist("ans") == 1 | ||||
_ = ans; | ||||
else | ||||
_ = nan; | ||||
end | ||||
for f = __ipy_figures | ||||
|
r7380 | outfile = sprintf('%(plot_dir)s/__ipy_oct_fig_%%03d.png', f); | ||
try | ||||
|
r7381 | print(f, outfile, '-d%(plot_format)s', '-tight', '-S%(size)s'); | ||
|
r7380 | end | ||
|
r7378 | end | ||
|
r7439 | ''' % locals() | ||
|
r7378 | |||
code = ' '.join((pre_call, code, post_call)) | ||||
try: | ||||
|
r7381 | text_output = self._oct.run(code, verbose=False) | ||
|
r7378 | except (oct2py.Oct2PyError) as exception: | ||
|
r7384 | msg = exception.message | ||
msg = msg.split('# ___<end_pre_call>___ #')[1] | ||||
msg = msg.split('# ___<start_post_call>___ #')[0] | ||||
|
r7378 | raise OctaveMagicError('Octave could not complete execution. ' | ||
'Traceback (currently broken in oct2py): %s' | ||||
|
r7384 | % msg) | ||
|
r7378 | |||
key = 'OctaveMagic.Octave' | ||||
display_data = [] | ||||
# Publish text output | ||||
if text_output: | ||||
display_data.append((key, {'text/plain': text_output})) | ||||
# Publish images | ||||
images = [open(imgfile, 'rb').read() for imgfile in \ | ||||
|
r7381 | glob("%s/*" % plot_dir)] | ||
|
r7378 | rmtree(plot_dir) | ||
|
r7381 | plot_mime_type = _mimetypes.get(plot_format, 'image/png') | ||
width, height = [int(s) for s in size.split(',')] | ||||
|
r7378 | for image in images: | ||
|
r7381 | if plot_format == 'svg': | ||
image = self._fix_gnuplot_svg_size(image, size=(width, height)) | ||||
display_data.append((key, {plot_mime_type: image})) | ||||
|
r7378 | |||
if args.output: | ||||
for output in ','.join(args.output).split(','): | ||||
output = unicode_to_str(output) | ||||
|
r7381 | self.shell.push({output: self._oct.get(output)}) | ||
|
r7378 | |||
|
r7383 | for source, data in display_data: | ||
self._publish_display_data(source, data) | ||||
|
r7378 | |||
if return_output: | ||||
|
r7381 | ans = self._oct.get('_') | ||
|
r7378 | |||
# Unfortunately, Octave doesn't have a "None" object, | ||||
# so we can't return any NaN outputs | ||||
|
r7379 | if np.isscalar(ans) and np.isnan(ans): | ||
|
r7378 | ans = None | ||
return ans | ||||
|
r7381 | |||
|
r7378 | __doc__ = __doc__.format( | ||
OCTAVE_DOC = ' '*8 + OctaveMagics.octave.__doc__, | ||||
OCTAVE_PUSH_DOC = ' '*8 + OctaveMagics.octave_push.__doc__, | ||||
OCTAVE_PULL_DOC = ' '*8 + OctaveMagics.octave_pull.__doc__ | ||||
) | ||||
_loaded = False | ||||
def load_ipython_extension(ip): | ||||
"""Load the extension in IPython.""" | ||||
global _loaded | ||||
if not _loaded: | ||||
ip.register_magics(OctaveMagics) | ||||
_loaded = True | ||||