##// END OF EJS Templates
Use svg by default due to lack of Ghostscript by default on Windows
blink1073 -
Show More
@@ -1,367 +1,367 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 ===========
3 ===========
4 octavemagic
4 octavemagic
5 ===========
5 ===========
6
6
7 Magics for interacting with Octave via oct2py.
7 Magics for interacting with Octave via oct2py.
8
8
9 .. note::
9 .. note::
10
10
11 The ``oct2py`` module needs to be installed separately and
11 The ``oct2py`` module needs to be installed separately and
12 can be obtained using ``easy_install`` or ``pip``.
12 can be obtained using ``easy_install`` or ``pip``.
13
13
14 Usage
14 Usage
15 =====
15 =====
16
16
17 ``%octave``
17 ``%octave``
18
18
19 {OCTAVE_DOC}
19 {OCTAVE_DOC}
20
20
21 ``%octave_push``
21 ``%octave_push``
22
22
23 {OCTAVE_PUSH_DOC}
23 {OCTAVE_PUSH_DOC}
24
24
25 ``%octave_pull``
25 ``%octave_pull``
26
26
27 {OCTAVE_PULL_DOC}
27 {OCTAVE_PULL_DOC}
28
28
29 """
29 """
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Copyright (C) 2012 The IPython Development Team
32 # Copyright (C) 2012 The IPython Development Team
33 #
33 #
34 # Distributed under the terms of the BSD License. The full license is in
34 # Distributed under the terms of the BSD License. The full license is in
35 # the file COPYING, distributed as part of this software.
35 # the file COPYING, distributed as part of this software.
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38 import tempfile
38 import tempfile
39 from glob import glob
39 from glob import glob
40 from shutil import rmtree
40 from shutil import rmtree
41
41
42 import numpy as np
42 import numpy as np
43 import oct2py
43 import oct2py
44 from xml.dom import minidom
44 from xml.dom import minidom
45
45
46 from IPython.core.displaypub import publish_display_data
46 from IPython.core.displaypub import publish_display_data
47 from IPython.core.magic import (Magics, magics_class, line_magic,
47 from IPython.core.magic import (Magics, magics_class, line_magic,
48 line_cell_magic, needs_local_scope)
48 line_cell_magic, needs_local_scope)
49 from IPython.testing.skipdoctest import skip_doctest
49 from IPython.testing.skipdoctest import skip_doctest
50 from IPython.core.magic_arguments import (
50 from IPython.core.magic_arguments import (
51 argument, magic_arguments, parse_argstring
51 argument, magic_arguments, parse_argstring
52 )
52 )
53 from IPython.utils.py3compat import unicode_to_str
53 from IPython.utils.py3compat import unicode_to_str
54
54
55 class OctaveMagicError(oct2py.Oct2PyError):
55 class OctaveMagicError(oct2py.Oct2PyError):
56 pass
56 pass
57
57
58 _mimetypes = {'png' : 'image/png',
58 _mimetypes = {'png' : 'image/png',
59 'svg' : 'image/svg+xml',
59 'svg' : 'image/svg+xml',
60 'jpg' : 'image/jpeg',
60 'jpg' : 'image/jpeg',
61 'jpeg': 'image/jpeg'}
61 'jpeg': 'image/jpeg'}
62
62
63 @magics_class
63 @magics_class
64 class OctaveMagics(Magics):
64 class OctaveMagics(Magics):
65 """A set of magics useful for interactive work with Octave via oct2py.
65 """A set of magics useful for interactive work with Octave via oct2py.
66 """
66 """
67 def __init__(self, shell):
67 def __init__(self, shell):
68 """
68 """
69 Parameters
69 Parameters
70 ----------
70 ----------
71 shell : IPython shell
71 shell : IPython shell
72
72
73 """
73 """
74 super(OctaveMagics, self).__init__(shell)
74 super(OctaveMagics, self).__init__(shell)
75 self._oct = oct2py.Oct2Py()
75 self._oct = oct2py.Oct2Py()
76 self._plot_format = 'png'
76 self._plot_format = 'svg'
77
77
78 # Allow publish_display_data to be overridden for
78 # Allow publish_display_data to be overridden for
79 # testing purposes.
79 # testing purposes.
80 self._publish_display_data = publish_display_data
80 self._publish_display_data = publish_display_data
81
81
82
82
83 def _fix_gnuplot_svg_size(self, image, size=None):
83 def _fix_gnuplot_svg_size(self, image, size=None):
84 """
84 """
85 GnuPlot SVGs do not have height/width attributes. Set
85 GnuPlot SVGs do not have height/width attributes. Set
86 these to be the same as the viewBox, so that the browser
86 these to be the same as the viewBox, so that the browser
87 scales the image correctly.
87 scales the image correctly.
88
88
89 Parameters
89 Parameters
90 ----------
90 ----------
91 image : str
91 image : str
92 SVG data.
92 SVG data.
93 size : tuple of int
93 size : tuple of int
94 Image width, height.
94 Image width, height.
95
95
96 """
96 """
97 (svg,) = minidom.parseString(image).getElementsByTagName('svg')
97 (svg,) = minidom.parseString(image).getElementsByTagName('svg')
98 viewbox = svg.getAttribute('viewBox').split(' ')
98 viewbox = svg.getAttribute('viewBox').split(' ')
99
99
100 if size is not None:
100 if size is not None:
101 width, height = size
101 width, height = size
102 else:
102 else:
103 width, height = viewbox[2:]
103 width, height = viewbox[2:]
104
104
105 svg.setAttribute('width', '%dpx' % width)
105 svg.setAttribute('width', '%dpx' % width)
106 svg.setAttribute('height', '%dpx' % height)
106 svg.setAttribute('height', '%dpx' % height)
107 return svg.toxml()
107 return svg.toxml()
108
108
109
109
110 @skip_doctest
110 @skip_doctest
111 @line_magic
111 @line_magic
112 def octave_push(self, line):
112 def octave_push(self, line):
113 '''
113 '''
114 Line-level magic that pushes a variable to Octave.
114 Line-level magic that pushes a variable to Octave.
115
115
116 `line` should be made up of whitespace separated variable names in the
116 `line` should be made up of whitespace separated variable names in the
117 IPython namespace::
117 IPython namespace::
118
118
119 In [7]: import numpy as np
119 In [7]: import numpy as np
120
120
121 In [8]: X = np.arange(5)
121 In [8]: X = np.arange(5)
122
122
123 In [9]: X.mean()
123 In [9]: X.mean()
124 Out[9]: 2.0
124 Out[9]: 2.0
125
125
126 In [10]: %octave_push X
126 In [10]: %octave_push X
127
127
128 In [11]: %octave mean(X)
128 In [11]: %octave mean(X)
129 Out[11]: 2.0
129 Out[11]: 2.0
130
130
131 '''
131 '''
132 inputs = line.split(' ')
132 inputs = line.split(' ')
133 for input in inputs:
133 for input in inputs:
134 input = unicode_to_str(input)
134 input = unicode_to_str(input)
135 self._oct.put(input, self.shell.user_ns[input])
135 self._oct.put(input, self.shell.user_ns[input])
136
136
137
137
138 @skip_doctest
138 @skip_doctest
139 @line_magic
139 @line_magic
140 def octave_pull(self, line):
140 def octave_pull(self, line):
141 '''
141 '''
142 Line-level magic that pulls a variable from Octave.
142 Line-level magic that pulls a variable from Octave.
143
143
144 In [18]: _ = %octave x = [1 2; 3 4]; y = 'hello'
144 In [18]: _ = %octave x = [1 2; 3 4]; y = 'hello'
145
145
146 In [19]: %octave_pull x y
146 In [19]: %octave_pull x y
147
147
148 In [20]: x
148 In [20]: x
149 Out[20]:
149 Out[20]:
150 array([[ 1., 2.],
150 array([[ 1., 2.],
151 [ 3., 4.]])
151 [ 3., 4.]])
152
152
153 In [21]: y
153 In [21]: y
154 Out[21]: 'hello'
154 Out[21]: 'hello'
155
155
156 '''
156 '''
157 outputs = line.split(' ')
157 outputs = line.split(' ')
158 for output in outputs:
158 for output in outputs:
159 output = unicode_to_str(output)
159 output = unicode_to_str(output)
160 self.shell.push({output: self._oct.get(output)})
160 self.shell.push({output: self._oct.get(output)})
161
161
162
162
163 @skip_doctest
163 @skip_doctest
164 @magic_arguments()
164 @magic_arguments()
165 @argument(
165 @argument(
166 '-i', '--input', action='append',
166 '-i', '--input', action='append',
167 help='Names of input variables to be pushed to Octave. Multiple names '
167 help='Names of input variables to be pushed to Octave. Multiple names '
168 'can be passed, separated by commas with no whitespace.'
168 'can be passed, separated by commas with no whitespace.'
169 )
169 )
170 @argument(
170 @argument(
171 '-o', '--output', action='append',
171 '-o', '--output', action='append',
172 help='Names of variables to be pulled from Octave after executing cell '
172 help='Names of variables to be pulled from Octave after executing cell '
173 'body. Multiple names can be passed, separated by commas with no '
173 'body. Multiple names can be passed, separated by commas with no '
174 'whitespace.'
174 'whitespace.'
175 )
175 )
176 @argument(
176 @argument(
177 '-s', '--size', action='store',
177 '-s', '--size', action='store',
178 help='Pixel size of plots, "width,height". Default is "-s 400,250".'
178 help='Pixel size of plots, "width,height". Default is "-s 400,250".'
179 )
179 )
180 @argument(
180 @argument(
181 '-f', '--format', action='store',
181 '-f', '--format', action='store',
182 help='Plot format (png, svg or jpg).'
182 help='Plot format (png, svg or jpg).'
183 )
183 )
184
184
185 @needs_local_scope
185 @needs_local_scope
186 @argument(
186 @argument(
187 'code',
187 'code',
188 nargs='*',
188 nargs='*',
189 )
189 )
190 @line_cell_magic
190 @line_cell_magic
191 def octave(self, line, cell=None, local_ns=None):
191 def octave(self, line, cell=None, local_ns=None):
192 '''
192 '''
193 Execute code in Octave, and pull some of the results back into the
193 Execute code in Octave, and pull some of the results back into the
194 Python namespace.
194 Python namespace.
195
195
196 In [9]: %octave X = [1 2; 3 4]; mean(X)
196 In [9]: %octave X = [1 2; 3 4]; mean(X)
197 Out[9]: array([[ 2., 3.]])
197 Out[9]: array([[ 2., 3.]])
198
198
199 As a cell, this will run a block of Octave code, without returning any
199 As a cell, this will run a block of Octave code, without returning any
200 value::
200 value::
201
201
202 In [10]: %%octave
202 In [10]: %%octave
203 ....: p = [-2, -1, 0, 1, 2]
203 ....: p = [-2, -1, 0, 1, 2]
204 ....: polyout(p, 'x')
204 ....: polyout(p, 'x')
205
205
206 -2*x^4 - 1*x^3 + 0*x^2 + 1*x^1 + 2
206 -2*x^4 - 1*x^3 + 0*x^2 + 1*x^1 + 2
207
207
208 In the notebook, plots are published as the output of the cell, e.g.
208 In the notebook, plots are published as the output of the cell, e.g.
209
209
210 %octave plot([1 2 3], [4 5 6])
210 %octave plot([1 2 3], [4 5 6])
211
211
212 will create a line plot.
212 will create a line plot.
213
213
214 Objects can be passed back and forth between Octave and IPython via the
214 Objects can be passed back and forth between Octave and IPython via the
215 -i and -o flags in line::
215 -i and -o flags in line::
216
216
217 In [14]: Z = np.array([1, 4, 5, 10])
217 In [14]: Z = np.array([1, 4, 5, 10])
218
218
219 In [15]: %octave -i Z mean(Z)
219 In [15]: %octave -i Z mean(Z)
220 Out[15]: array([ 5.])
220 Out[15]: array([ 5.])
221
221
222
222
223 In [16]: %octave -o W W = Z * mean(Z)
223 In [16]: %octave -o W W = Z * mean(Z)
224 Out[16]: array([ 5., 20., 25., 50.])
224 Out[16]: array([ 5., 20., 25., 50.])
225
225
226 In [17]: W
226 In [17]: W
227 Out[17]: array([ 5., 20., 25., 50.])
227 Out[17]: array([ 5., 20., 25., 50.])
228
228
229 The size and format of output plots can be specified::
229 The size and format of output plots can be specified::
230
230
231 In [18]: %%octave -s 600,800 -f svg
231 In [18]: %%octave -s 600,800 -f svg
232 ...: plot([1, 2, 3]);
232 ...: plot([1, 2, 3]);
233
233
234 '''
234 '''
235 args = parse_argstring(self.octave, line)
235 args = parse_argstring(self.octave, line)
236
236
237 # arguments 'code' in line are prepended to the cell lines
237 # arguments 'code' in line are prepended to the cell lines
238 if cell is None:
238 if cell is None:
239 code = ''
239 code = ''
240 return_output = True
240 return_output = True
241 else:
241 else:
242 code = cell
242 code = cell
243 return_output = False
243 return_output = False
244
244
245 code = ' '.join(args.code) + code
245 code = ' '.join(args.code) + code
246
246
247 # if there is no local namespace then default to an empty dict
247 # if there is no local namespace then default to an empty dict
248 if local_ns is None:
248 if local_ns is None:
249 local_ns = {}
249 local_ns = {}
250
250
251 if args.input:
251 if args.input:
252 for input in ','.join(args.input).split(','):
252 for input in ','.join(args.input).split(','):
253 input = unicode_to_str(input)
253 input = unicode_to_str(input)
254 try:
254 try:
255 val = local_ns[input]
255 val = local_ns[input]
256 except KeyError:
256 except KeyError:
257 val = self.shell.user_ns[input]
257 val = self.shell.user_ns[input]
258 self._oct.put(input, val)
258 self._oct.put(input, val)
259
259
260 # generate plots in a temporary directory
260 # generate plots in a temporary directory
261 plot_dir = tempfile.mkdtemp()
261 plot_dir = tempfile.mkdtemp()
262 if args.size is not None:
262 if args.size is not None:
263 size = args.size
263 size = args.size
264 else:
264 else:
265 size = '400,240'
265 size = '400,240'
266
266
267 if args.format is not None:
267 if args.format is not None:
268 plot_format = args.format
268 plot_format = args.format
269 else:
269 else:
270 plot_format = 'png'
270 plot_format = 'png'
271
271
272 pre_call = '''
272 pre_call = '''
273 global __ipy_figures = [];
273 global __ipy_figures = [];
274 page_screen_output(0);
274 page_screen_output(0);
275
275
276 function fig_create(src, event)
276 function fig_create(src, event)
277 global __ipy_figures;
277 global __ipy_figures;
278 __ipy_figures(size(__ipy_figures) + 1) = src;
278 __ipy_figures(size(__ipy_figures) + 1) = src;
279 set(src, "visible", "off");
279 set(src, "visible", "off");
280 end
280 end
281
281
282 set(0, 'DefaultFigureCreateFcn', @fig_create);
282 set(0, 'DefaultFigureCreateFcn', @fig_create);
283
283
284 close all;
284 close all;
285 clear ans;
285 clear ans;
286
286
287 # ___<end_pre_call>___ #
287 # ___<end_pre_call>___ #
288 '''
288 '''
289
289
290 post_call = '''
290 post_call = '''
291 # ___<start_post_call>___ #
291 # ___<start_post_call>___ #
292
292
293 # Save output of the last execution
293 # Save output of the last execution
294 if exist("ans") == 1
294 if exist("ans") == 1
295 _ = ans;
295 _ = ans;
296 else
296 else
297 _ = nan;
297 _ = nan;
298 end
298 end
299
299
300 for f = __ipy_figures
300 for f = __ipy_figures
301 outfile = sprintf('%(plot_dir)s/__ipy_oct_fig_%%03d.png', f);
301 outfile = sprintf('%(plot_dir)s/__ipy_oct_fig_%%03d.png', f);
302 try
302 try
303 print(f, outfile, '-d%(plot_format)s', '-tight', '-S%(size)s');
303 print(f, outfile, '-d%(plot_format)s', '-tight', '-S%(size)s');
304 end
304 end
305 end
305 end
306
306
307 ''' % locals()
307 ''' % locals()
308
308
309 code = ' '.join((pre_call, code, post_call))
309 code = ' '.join((pre_call, code, post_call))
310 try:
310 try:
311 text_output = self._oct.run(code, verbose=False)
311 text_output = self._oct.run(code, verbose=False)
312 except (oct2py.Oct2PyError) as exception:
312 except (oct2py.Oct2PyError) as exception:
313 msg = exception.message
313 msg = exception.message
314 msg = msg.split('# ___<end_pre_call>___ #')[1]
314 msg = msg.split('# ___<end_pre_call>___ #')[1]
315 msg = msg.split('# ___<start_post_call>___ #')[0]
315 msg = msg.split('# ___<start_post_call>___ #')[0]
316 raise OctaveMagicError('Octave could not complete execution. '
316 raise OctaveMagicError('Octave could not complete execution. '
317 'Traceback (currently broken in oct2py): %s'
317 'Traceback (currently broken in oct2py): %s'
318 % msg)
318 % msg)
319
319
320 key = 'OctaveMagic.Octave'
320 key = 'OctaveMagic.Octave'
321 display_data = []
321 display_data = []
322
322
323 # Publish text output
323 # Publish text output
324 if text_output:
324 if text_output:
325 display_data.append((key, {'text/plain': text_output}))
325 display_data.append((key, {'text/plain': text_output}))
326
326
327 # Publish images
327 # Publish images
328 images = [open(imgfile, 'rb').read() for imgfile in \
328 images = [open(imgfile, 'rb').read() for imgfile in \
329 glob("%s/*" % plot_dir)]
329 glob("%s/*" % plot_dir)]
330 rmtree(plot_dir)
330 rmtree(plot_dir)
331
331
332 plot_mime_type = _mimetypes.get(plot_format, 'image/png')
332 plot_mime_type = _mimetypes.get(plot_format, 'image/png')
333 width, height = [int(s) for s in size.split(',')]
333 width, height = [int(s) for s in size.split(',')]
334 for image in images:
334 for image in images:
335 if plot_format == 'svg':
335 if plot_format == 'svg':
336 image = self._fix_gnuplot_svg_size(image, size=(width, height))
336 image = self._fix_gnuplot_svg_size(image, size=(width, height))
337 display_data.append((key, {plot_mime_type: image}))
337 display_data.append((key, {plot_mime_type: image}))
338
338
339 if args.output:
339 if args.output:
340 for output in ','.join(args.output).split(','):
340 for output in ','.join(args.output).split(','):
341 output = unicode_to_str(output)
341 output = unicode_to_str(output)
342 self.shell.push({output: self._oct.get(output)})
342 self.shell.push({output: self._oct.get(output)})
343
343
344 for source, data in display_data:
344 for source, data in display_data:
345 self._publish_display_data(source, data)
345 self._publish_display_data(source, data)
346
346
347 if return_output:
347 if return_output:
348 ans = self._oct.get('_')
348 ans = self._oct.get('_')
349
349
350 # Unfortunately, Octave doesn't have a "None" object,
350 # Unfortunately, Octave doesn't have a "None" object,
351 # so we can't return any NaN outputs
351 # so we can't return any NaN outputs
352 if np.isscalar(ans) and np.isnan(ans):
352 if np.isscalar(ans) and np.isnan(ans):
353 ans = None
353 ans = None
354
354
355 return ans
355 return ans
356
356
357
357
358 __doc__ = __doc__.format(
358 __doc__ = __doc__.format(
359 OCTAVE_DOC = ' '*8 + OctaveMagics.octave.__doc__,
359 OCTAVE_DOC = ' '*8 + OctaveMagics.octave.__doc__,
360 OCTAVE_PUSH_DOC = ' '*8 + OctaveMagics.octave_push.__doc__,
360 OCTAVE_PUSH_DOC = ' '*8 + OctaveMagics.octave_push.__doc__,
361 OCTAVE_PULL_DOC = ' '*8 + OctaveMagics.octave_pull.__doc__
361 OCTAVE_PULL_DOC = ' '*8 + OctaveMagics.octave_pull.__doc__
362 )
362 )
363
363
364
364
365 def load_ipython_extension(ip):
365 def load_ipython_extension(ip):
366 """Load the extension in IPython."""
366 """Load the extension in IPython."""
367 ip.register_magics(OctaveMagics)
367 ip.register_magics(OctaveMagics)
General Comments 0
You need to be logged in to leave comments. Login now