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