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