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