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