##// END OF EJS Templates
Fix plotting.
Stefan van der Walt -
Show More
@@ -1,287 +1,298
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 ===========
3 ===========
4 octavemagic
4 octavemagic
5 ===========
5 ===========
6
6
7 Magic command interface for interactive work with Octave via oct2py
7 Magics for interacting with Octave via oct2py.
8
8
9 Usage
9 Usage
10 =====
10 =====
11
11
12 ``%octave``
12 ``%octave``
13
13
14 {OCTAVE_DOC}
14 {OCTAVE_DOC}
15
15
16 ``%octave_push``
16 ``%octave_push``
17
17
18 {OCTAVE_PUSH_DOC}
18 {OCTAVE_PUSH_DOC}
19
19
20 ``%octave_pull``
20 ``%octave_pull``
21
21
22 {OCTAVE_PULL_DOC}
22 {OCTAVE_PULL_DOC}
23
23
24 """
24 """
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Copyright (C) 2012 The IPython Development Team
27 # Copyright (C) 2012 The IPython Development Team
28 #
28 #
29 # Distributed under the terms of the BSD License. The full license is in
29 # Distributed under the terms of the BSD License. The full license is in
30 # the file COPYING, distributed as part of this software.
30 # the file COPYING, distributed as part of this software.
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 import sys
34 import tempfile
33 import tempfile
35 from glob import glob
34 from glob import glob
36 from shutil import rmtree
35 from shutil import rmtree
37 from getopt import getopt
38
36
39 import numpy as np
37 import numpy as np
40 import oct2py
38 import oct2py
41
39
42 from IPython.core.displaypub import publish_display_data
40 from IPython.core.displaypub import publish_display_data
43 from IPython.core.magic import (Magics, magics_class, cell_magic, line_magic,
41 from IPython.core.magic import (Magics, magics_class, line_magic,
44 line_cell_magic)
42 line_cell_magic)
45 from IPython.testing.skipdoctest import skip_doctest
43 from IPython.testing.skipdoctest import skip_doctest
46 from IPython.core.magic_arguments import (
44 from IPython.core.magic_arguments import (
47 argument, magic_arguments, parse_argstring
45 argument, magic_arguments, parse_argstring
48 )
46 )
49 from IPython.utils.py3compat import unicode_to_str
47 from IPython.utils.py3compat import unicode_to_str
50
48
51
49
52 class OctaveMagicError(oct2py.Oct2PyError):
50 class OctaveMagicError(oct2py.Oct2PyError):
53 pass
51 pass
54
52
55
53
56 @magics_class
54 @magics_class
57 class OctaveMagics(Magics):
55 class OctaveMagics(Magics):
58 """A set of magics useful for interactive work with Octave via oct2py.
56 """A set of magics useful for interactive work with Octave via oct2py.
59 """
57 """
60 def __init__(self, shell):
58 def __init__(self, shell):
61 """
59 """
62 Parameters
60 Parameters
63 ----------
61 ----------
64
62
65 shell : IPython shell
63 shell : IPython shell
66
64
67 """
65 """
68 super(OctaveMagics, self).__init__(shell)
66 super(OctaveMagics, self).__init__(shell)
69 self.oct = oct2py.Oct2Py()
67 self.oct = oct2py.Oct2Py()
70
68
71
69
72 @skip_doctest
70 @skip_doctest
73 @line_magic
71 @line_magic
74 def octave_push(self, line):
72 def octave_push(self, line):
75 '''
73 '''
76 Line-level magic that pushes a variable to Octave.
74 Line-level magic that pushes a variable to Octave.
77
75
78 `line` should be made up of whitespace separated variable names in the
76 `line` should be made up of whitespace separated variable names in the
79 IPython namespace::
77 IPython namespace::
80
78
81 In [7]: import numpy as np
79 In [7]: import numpy as np
82
80
83 In [8]: X = np.arange(5)
81 In [8]: X = np.arange(5)
84
82
85 In [9]: X.mean()
83 In [9]: X.mean()
86 Out[9]: 2.0
84 Out[9]: 2.0
87
85
88 In [10]: %octave_push X
86 In [10]: %octave_push X
89
87
90 In [11]: %octave mean(X)
88 In [11]: %octave mean(X)
91 Out[11]: 2.0
89 Out[11]: 2.0
92
90
93 '''
91 '''
94 inputs = line.split(' ')
92 inputs = line.split(' ')
95 for input in inputs:
93 for input in inputs:
96 self.oct.put(input, self.shell.user_ns[input])
94 self.oct.put(input, self.shell.user_ns[input])
97
95
98
96
99 @skip_doctest
97 @skip_doctest
100 @line_magic
98 @line_magic
101 def octave_pull(self, line):
99 def octave_pull(self, line):
102 '''
100 '''
103 Line-level magic that pulls a variable from Octave.
101 Line-level magic that pulls a variable from Octave.
104
102
105 In [18]: _ = %octave x = [1 2; 3 4]; y = 'hello'
103 In [18]: _ = %octave x = [1 2; 3 4]; y = 'hello'
106
104
107 In [19]: %octave_pull x y
105 In [19]: %octave_pull x y
108
106
109 In [20]: x
107 In [20]: x
110 Out[20]:
108 Out[20]:
111 array([[ 1., 2.],
109 array([[ 1., 2.],
112 [ 3., 4.]])
110 [ 3., 4.]])
113
111
114 In [21]: y
112 In [21]: y
115 Out[21]: 'hello'
113 Out[21]: 'hello'
116
114
117 '''
115 '''
118 outputs = line.split(' ')
116 outputs = line.split(' ')
119 for output in outputs:
117 for output in outputs:
118 output = unicode_to_str(output)
120 self.shell.push({output: self.oct.get(output)})
119 self.shell.push({output: self.oct.get(output)})
121
120
122
121
123 @skip_doctest
122 @skip_doctest
124 @magic_arguments()
123 @magic_arguments()
125 @argument(
124 @argument(
126 '-i', '--input', action='append',
125 '-i', '--input', action='append',
127 help='Names of input variables to be pushed to Octave. Multiple names can be passed, separated by commas with no whitespace.'
126 help='Names of input variables to be pushed to Octave. Multiple names can be passed, separated by commas with no whitespace.'
128 )
127 )
129 @argument(
128 @argument(
130 '-o', '--output', action='append',
129 '-o', '--output', action='append',
131 help='Names of variables to be pulled from Octave after executing cell body. Multiple names can be passed, separated by commas with no whitespace.'
130 help='Names of variables to be pulled from Octave after executing cell body. Multiple names can be passed, separated by commas with no whitespace.'
132 )
131 )
133 @argument(
132 @argument(
133 '-s', '--size', action='append',
134 help='Pixel size of plots. Default is "-s 400,250".'
135 )
136 @argument(
134 'code',
137 'code',
135 nargs='*',
138 nargs='*',
136 )
139 )
137 @line_cell_magic
140 @line_cell_magic
138 def octave(self, line, cell=None):
141 def octave(self, line, cell=None):
139 '''
142 '''
140 Execute code in Octave, and pull some of the results back into the
143 Execute code in Octave, and pull some of the results back into the
141 Python namespace.
144 Python namespace.
142
145
143 In [9]: %octave X = [1 2; 3 4]; mean(X)
146 In [9]: %octave X = [1 2; 3 4]; mean(X)
144 Out[9]: array([[ 2., 3.]])
147 Out[9]: array([[ 2., 3.]])
145
148
146 As a cell, this will run a block of Octave code, without returning any
149 As a cell, this will run a block of Octave code, without returning any
147 value::
150 value::
148
151
149 In [10]: %%octave
152 In [10]: %%octave
150 ....: p = [-2, -1, 0, 1, 2]
153 ....: p = [-2, -1, 0, 1, 2]
151 ....: polyout(p, 'x')
154 ....: polyout(p, 'x')
152
155
153 -2*x^4 - 1*x^3 + 0*x^2 + 1*x^1 + 2
156 -2*x^4 - 1*x^3 + 0*x^2 + 1*x^1 + 2
154
157
155 In the notebook, plots are published as the output of the cell, e.g.
158 In the notebook, plots are published as the output of the cell, e.g.
156
159
157 %octave plot([1 2 3], [4 5 6])
160 %octave plot([1 2 3], [4 5 6])
158
161
159 will create a line plot.
162 will create a line plot.
160
163
161 Objects can be passed back and forth between Octave and IPython via the
164 Objects can be passed back and forth between Octave and IPython via the
162 -i and -o flags in line::
165 -i and -o flags in line::
163
166
164 In [14]: Z = np.array([1, 4, 5, 10])
167 In [14]: Z = np.array([1, 4, 5, 10])
165
168
166 In [15]: %octave -i Z mean(Z)
169 In [15]: %octave -i Z mean(Z)
167 Out[15]: array([ 5.])
170 Out[15]: array([ 5.])
168
171
169
172
170 In [16]: %octave -o W W = Z * mean(Z)
173 In [16]: %octave -o W W = Z * mean(Z)
171 Out[16]: array([ 5., 20., 25., 50.])
174 Out[16]: array([ 5., 20., 25., 50.])
172
175
173 In [17]: W
176 In [17]: W
174 Out[17]: array([ 5., 20., 25., 50.])
177 Out[17]: array([ 5., 20., 25., 50.])
175
178
176 '''
179 '''
177 args = parse_argstring(self.octave, line)
180 args = parse_argstring(self.octave, line)
178
181
179 # arguments 'code' in line are prepended to the cell lines
182 # arguments 'code' in line are prepended to the cell lines
180 if not cell:
183 if not cell:
181 code = ''
184 code = ''
182 return_output = True
185 return_output = True
183 line_mode = True
186 line_mode = True
184 else:
187 else:
185 code = cell
188 code = cell
186 return_output = False
189 return_output = False
187 line_mode = False
190 line_mode = False
188
191
189 code = ' '.join(args.code) + code
192 code = ' '.join(args.code) + code
190
193
191 if args.input:
194 if args.input:
192 for input in ','.join(args.input).split(','):
195 for input in ','.join(args.input).split(','):
196 input = unicode_to_str(input)
193 self.oct.put(input, self.shell.user_ns[input])
197 self.oct.put(input, self.shell.user_ns[input])
194
198
195 # generate plots in a temporary directory
199 # generate plots in a temporary directory
196 plot_dir = tempfile.mkdtemp()
200 plot_dir = tempfile.mkdtemp()
201 if args.size is not None:
202 size = args.size[0]
203 else:
204 size = '400,240'
197
205
198 pre_call = '''
206 pre_call = '''
199 global __ipy_figures = [];
207 global __ipy_figures = [];
200 page_screen_output(0);
208 page_screen_output(0);
201
209
202 function fig_create(src, event)
210 function fig_create(src, event)
203 global __ipy_figures;
211 global __ipy_figures;
204 __ipy_figures(size(__ipy_figures) + 1) = src;
212 __ipy_figures(size(__ipy_figures) + 1) = src;
213 set(src, "visible", "off");
205 end
214 end
206
215
207 set(0, 'DefaultFigureCreateFcn', @fig_create);
216 set(0, 'DefaultFigureCreateFcn', @fig_create);
208
217
209 clear ans;
218 clear ans;
210 '''
219 '''
211
220
212 post_call = '''
221 post_call = '''
213
222
214 # Save output of the last execution
223 # Save output of the last execution
215 if exist("ans") == 1
224 if exist("ans") == 1
216 _ = ans;
225 _ = ans;
217 else
226 else
218 _ = nan;
227 _ = nan;
219 end
228 end
220
229
221 for f = __ipy_figures
230 for f = __ipy_figures
222 outfile = sprintf('%s/__ipy_oct_fig_%%03d.png', f)
231 outfile = sprintf('%(plot_dir)s/__ipy_oct_fig_%%03d.png', f);
223 print(f, outfile, '-dpng')
232 print(f, outfile, '-dpng', '-S%(size)s');
224 end
233 end
225
234
226 ''' % plot_dir
235 # close all;
236
237 ''' % {'plot_dir': plot_dir, 'size': size}
227
238
228 code = ' '.join((pre_call, code, post_call))
239 code = ' '.join((pre_call, code, post_call))
229 try:
240 try:
230 text_output = self.oct.run(code, verbose=False)
241 text_output = self.oct.run(code, verbose=False)
231 except (oct2py.Oct2PyError) as exception:
242 except (oct2py.Oct2PyError) as exception:
232 raise OctaveMagicError('Octave could not complete execution. '
243 raise OctaveMagicError('Octave could not complete execution. '
233 'Traceback (currently broken in oct2py): %s'
244 'Traceback (currently broken in oct2py): %s'
234 % exception.message)
245 % exception.message)
235
246
236 key = 'OctaveMagic.Octave'
247 key = 'OctaveMagic.Octave'
237 display_data = []
248 display_data = []
238
249
239 # Publish text output
250 # Publish text output
240 if text_output:
251 if text_output:
241 display_data.append((key, {'text/plain': text_output}))
252 display_data.append((key, {'text/plain': text_output}))
242
253
243 # Publish images
254 # Publish images
244 fmt = 'png'
255 fmt = 'png'
245 mimetypes = {'png' : 'image/png',
256 mimetypes = {'png' : 'image/png',
246 'svg' : 'image/svg+xml'}
257 'svg' : 'image/svg+xml'}
247 mime = mimetypes[fmt]
258 mime = mimetypes[fmt]
248
259
249 images = [open(imgfile, 'rb').read() for imgfile in \
260 images = [open(imgfile, 'rb').read() for imgfile in \
250 glob("%s/*.png" % plot_dir)]
261 glob("%s/*.png" % plot_dir)]
251 rmtree(plot_dir)
262 rmtree(plot_dir)
252
263
253 for image in images:
264 for image in images:
254 display_data.append((key, {mime: image}))
265 display_data.append((key, {mime: image}))
255
266
256 if args.output:
267 if args.output:
257 for output in ','.join(args.output).split(','):
268 for output in ','.join(args.output).split(','):
258 output = unicode_to_str(output)
269 output = unicode_to_str(output)
259 self.shell.push({output: self.oct.get(output)})
270 self.shell.push({output: self.oct.get(output)})
260
271
261 for tag, data in display_data:
272 for tag, data in display_data:
262 publish_display_data(tag, data)
273 publish_display_data(tag, data)
263
274
264 if return_output:
275 if return_output:
265 ans = self.oct.get('_')
276 ans = self.oct.get('_')
266
277
267 # Unfortunately, Octave doesn't have a "None" object,
278 # Unfortunately, Octave doesn't have a "None" object,
268 # so we can't return any NaN outputs
279 # so we can't return any NaN outputs
269 if np.isnan(np.nan):
280 if np.isscalar(ans) and np.isnan(ans):
270 ans = None
281 ans = None
271
282
272 return ans
283 return ans
273
284
274 __doc__ = __doc__.format(
285 __doc__ = __doc__.format(
275 OCTAVE_DOC = ' '*8 + OctaveMagics.octave.__doc__,
286 OCTAVE_DOC = ' '*8 + OctaveMagics.octave.__doc__,
276 OCTAVE_PUSH_DOC = ' '*8 + OctaveMagics.octave_push.__doc__,
287 OCTAVE_PUSH_DOC = ' '*8 + OctaveMagics.octave_push.__doc__,
277 OCTAVE_PULL_DOC = ' '*8 + OctaveMagics.octave_pull.__doc__
288 OCTAVE_PULL_DOC = ' '*8 + OctaveMagics.octave_pull.__doc__
278 )
289 )
279
290
280
291
281 _loaded = False
292 _loaded = False
282 def load_ipython_extension(ip):
293 def load_ipython_extension(ip):
283 """Load the extension in IPython."""
294 """Load the extension in IPython."""
284 global _loaded
295 global _loaded
285 if not _loaded:
296 if not _loaded:
286 ip.register_magics(OctaveMagics)
297 ip.register_magics(OctaveMagics)
287 _loaded = True
298 _loaded = True
General Comments 0
You need to be logged in to leave comments. Login now