From 08ef328ca833aec8f601414c3a18ba446f4d1c01 2011-08-11 21:17:01 From: Brian E. Granger Date: 2011-08-11 21:17:01 Subject: [PATCH] Finishing display system work. * Added image/jpeg MIME type to notebook format, the core display logic and the notebook. * Finished HTML, SVG, Image, Math, Javascript, JSON classes. --- diff --git a/IPython/core/display.py b/IPython/core/display.py index 08a83c9..859ec37 100644 --- a/IPython/core/display.py +++ b/IPython/core/display.py @@ -21,7 +21,7 @@ from .displaypub import ( publish_pretty, publish_html, publish_latex, publish_svg, publish_png, publish_json, - publish_javascript + publish_javascript, publish_jpeg ) #----------------------------------------------------------------------------- @@ -86,7 +86,7 @@ def display_html(*objs, **kwargs): Parameters ---------- objs : tuple of objects - The Python objects to display, or if raw=True raw html data to + The Python objects to display, or if raw=True raw HTML data to display. raw : bool Are the data objects raw data or Python objects that need to be @@ -140,6 +140,26 @@ def display_png(*objs, **kwargs): display(*objs, include=['text/plain','image/png']) +def display_jpeg(*objs, **kwargs): + """Display the JPEG representation of an object. + + Parameters + ---------- + objs : tuple of objects + The Python objects to display, or if raw=True raw JPEG data to + display. + raw : bool + Are the data objects raw data or Python objects that need to be + formatted before display? [default: False] + """ + raw = kwargs.pop('raw',False) + if raw: + for obj in objs: + publish_png(obj) + else: + display(*objs, include=['text/plain','image/jpeg']) + + def display_latex(*objs, **kwargs): """Display the LaTeX representation of an object. @@ -207,28 +227,49 @@ def display_javascript(*objs, **kwargs): class DisplayObject(object): """An object that wraps data to be displayed.""" - def __init__(self, data): - """Create a display object given raw data of a MIME type or a URL. + _read_flags = 'r' + + def __init__(self, data=None, url=None, filename=None): + """Create a display object given raw data. When this object is returned by an expression or passed to the display function, it will result in the data being displayed in the frontend. The MIME type of the data should match the subclasses used, so the Png subclass should be used for 'image/png' data. If the data is a URL, the data will first be downloaded - and then displayed. + and then displayed. If Parameters ---------- data : unicode, str or bytes The raw data or a URL to download the data from. + url : unicode + A URL to download the data from. + filename : unicode + Path to a local file to load the data from. """ - if data.startswith('http'): - import urllib2 - response = urllib2.urlopen(data) - self.data = response.read() + if data is not None and data.startswith('http'): + self.url = data + self.filename = None + self.data = None else: self.data = data - + self.url = url + self.filename = None if filename is None else unicode(filename) + self.reload() + + def reload(self): + """Reload the raw data from file or URL.""" + if self.filename is not None: + with open(self.filename, self._read_flags) as f: + self.data = f.read() + elif self.url is not None: + try: + import urllib2 + response = urllib2.urlopen(self.url) + self.data = response.read() + except: + self.data = None class Pretty(DisplayObject): @@ -236,39 +277,97 @@ class Pretty(DisplayObject): return self.data -class Html(DisplayObject): +class HTML(DisplayObject): def _repr_html_(self): return self.data -class Latex(DisplayObject): +class Math(DisplayObject): def _repr_latex_(self): return self.data -class Png(DisplayObject): - - def _repr_png_(self): - return self.data - - -class Svg(DisplayObject): +class SVG(DisplayObject): def _repr_svg_(self): return self.data -class Json(DisplayObject): +class JSON(DisplayObject): def _repr_json_(self): return self.data -class Javscript(DisplayObject): +class Javascript(DisplayObject): def _repr_javascript_(self): return self.data +class Image(DisplayObject): + + _read_flags = 'rb' + + def __init__(self, data=None, url=None, filename=None, format=u'png', embed=False): + """Create a display an PNG/JPEG image given raw data. + + When this object is returned by an expression or passed to the + display function, it will result in the image being displayed + in the frontend. + + Parameters + ---------- + data : unicode, str or bytes + The raw data or a URL to download the data from. + url : unicode + A URL to download the data from. + filename : unicode + Path to a local file to load the data from. + format : unicode + The format of the image data (png/jpeg/jpg). If a filename or URL is given + for format will be inferred from the filename extension. + embed : bool + Should the image data be embedded in the notebook using a data URI (True) + or be loaded using an tag. Set this to True if you want the image + to be viewable later with no internet connection. If a filename is given + embed is always set to True. + """ + if filename is not None: + ext = self._find_ext(filename) + elif url is not None: + ext = self._find_ext(url) + elif data.startswith('http'): + ext = self._find_ext(data) + else: + ext = None + if ext is not None: + if ext == u'jpg' or ext == u'jpeg': + format = u'jpeg' + if ext == u'png': + format = u'png' + self.format = unicode(format).lower() + self.embed = True if filename is not None else embed + super(Image, self).__init__(data=data, url=url, filename=filename) + + def reload(self): + """Reload the raw data from file or URL.""" + if self.embed: + super(Image,self).reload() + + def _repr_html_(self): + if not self.embed: + return u'' % self.url + + def _repr_png_(self): + if self.embed and self.format == u'png': + return self.data + + def _repr_jpeg_(self): + if self.embed and (self.format == u'jpeg' or self.format == u'jpg'): + return self.data + + def _find_ext(self, s): + return unicode(s.split('.')[-1].lower()) diff --git a/IPython/core/displaypub.py b/IPython/core/displaypub.py index 8fcc57c..256dd61 100644 --- a/IPython/core/displaypub.py +++ b/IPython/core/displaypub.py @@ -78,6 +78,7 @@ class DisplayPublisher(Configurable): * application/json * application/javascript * image/png + * image/jpeg * image/svg+xml Parameters @@ -118,6 +119,7 @@ def publish_display_data(source, data, metadata=None): * application/json * application/javascript * image/png + * image/jpeg * image/svg+xml Parameters @@ -166,12 +168,12 @@ def publish_pretty(data, metadata=None): def publish_html(data, metadata=None): - """Publish raw html data to all frontends. + """Publish raw HTML data to all frontends. Parameters ---------- data : unicode - The raw html data to publish. + The raw HTML data to publish. metadata : dict A dictionary for metadata related to the data. This can contain arbitrary key, value pairs that frontends can use to interpret @@ -185,12 +187,12 @@ def publish_html(data, metadata=None): def publish_latex(data, metadata=None): - """Publish raw latex data to all frontends. + """Publish raw LaTeX data to all frontends. Parameters ---------- data : unicode - The raw latex data to publish. + The raw LaTeX data to publish. metadata : dict A dictionary for metadata related to the data. This can contain arbitrary key, value pairs that frontends can use to interpret @@ -203,12 +205,12 @@ def publish_latex(data, metadata=None): ) def publish_png(data, metadata=None): - """Publish raw binary png data to all frontends. + """Publish raw binary PNG data to all frontends. Parameters ---------- data : str/bytes - The raw binary png data to publish. + The raw binary PNG data to publish. metadata : dict A dictionary for metadata related to the data. This can contain arbitrary key, value pairs that frontends can use to interpret @@ -220,13 +222,33 @@ def publish_png(data, metadata=None): metadata=metadata ) + +def publish_jpeg(data, metadata=None): + """Publish raw binary JPEG data to all frontends. + + Parameters + ---------- + data : str/bytes + The raw binary JPEG data to publish. + metadata : dict + A dictionary for metadata related to the data. This can contain + arbitrary key, value pairs that frontends can use to interpret + the data. + """ + publish_display_data( + u'IPython.core.displaypub.publish_jpeg', + {'image/jpeg':data}, + metadata=metadata + ) + + def publish_svg(data, metadata=None): - """Publish raw svg data to all frontends. + """Publish raw SVG data to all frontends. Parameters ---------- data : unicode - The raw svg data to publish. + The raw SVG data to publish. metadata : dict A dictionary for metadata related to the data. This can contain arbitrary key, value pairs that frontends can use to interpret @@ -239,12 +261,12 @@ def publish_svg(data, metadata=None): ) def publish_json(data, metadata=None): - """Publish raw json data to all frontends. + """Publish raw JSON data to all frontends. Parameters ---------- data : unicode - The raw json data to publish. + The raw JSON data to publish. metadata : dict A dictionary for metadata related to the data. This can contain arbitrary key, value pairs that frontends can use to interpret @@ -257,12 +279,12 @@ def publish_json(data, metadata=None): ) def publish_javascript(data, metadata=None): - """Publish raw javascript data to all frontends. + """Publish raw Javascript data to all frontends. Parameters ---------- data : unicode - The raw javascript data to publish. + The raw Javascript data to publish. metadata : dict A dictionary for metadata related to the data. This can contain arbitrary key, value pairs that frontends can use to interpret diff --git a/IPython/core/formatters.py b/IPython/core/formatters.py index 059a837..cdc3649 100644 --- a/IPython/core/formatters.py +++ b/IPython/core/formatters.py @@ -51,6 +51,7 @@ class DisplayFormatter(Configurable): HTMLFormatter, SVGFormatter, PNGFormatter, + JPEGFormatter, LatexFormatter, JSONFormatter, JavascriptFormatter @@ -74,6 +75,7 @@ class DisplayFormatter(Configurable): * application/json * application/javascript * image/png + * image/jpeg * image/svg+xml Parameters @@ -496,6 +498,22 @@ class PNGFormatter(BaseFormatter): print_method = ObjectName('_repr_png_') +class JPEGFormatter(BaseFormatter): + """A JPEG formatter. + + To define the callables that compute the JPEG representation of your + objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type` + or :meth:`for_type_by_name` methods to register functions that handle + this. + + The return value of this formatter should be raw JPEG data, *not* + base64 encoded. + """ + format_type = Unicode('image/jpeg') + + print_method = ObjectName('_repr_jpeg_') + + class LatexFormatter(BaseFormatter): """A LaTeX formatter. @@ -547,6 +565,7 @@ FormatterABC.register(PlainTextFormatter) FormatterABC.register(HTMLFormatter) FormatterABC.register(SVGFormatter) FormatterABC.register(PNGFormatter) +FormatterABC.register(JPEGFormatter) FormatterABC.register(LatexFormatter) FormatterABC.register(JSONFormatter) FormatterABC.register(JavascriptFormatter) @@ -565,6 +584,7 @@ def format_display_data(obj, include=None, exclude=None): * application/json * application/javascript * image/png + * image/jpeg * image/svg+xml Parameters @@ -596,3 +616,4 @@ def format_display_data(obj, include=None, exclude=None): include, exclude ) + diff --git a/IPython/core/magic.py b/IPython/core/magic.py index 33daef0..5431c17 100644 --- a/IPython/core/magic.py +++ b/IPython/core/magic.py @@ -2119,7 +2119,8 @@ Currently the magic system has the following functions:\n""" response = urllib2.urlopen(arg_s) content = response.read() else: - content = open(arg_s).read() + with open(arg_s) as f: + content = f.read() self.set_next_input(content) def _find_edit_target(self, args, opts, last_call): diff --git a/IPython/frontend/html/notebook/static/js/codecell.js b/IPython/frontend/html/notebook/static/js/codecell.js index 6b2c952..1ffc292 100644 --- a/IPython/frontend/html/notebook/static/js/codecell.js +++ b/IPython/frontend/html/notebook/static/js/codecell.js @@ -205,13 +205,15 @@ var IPython = (function (IPython) { CodeCell.prototype.append_pyerr = function (json) { var tb = json.traceback; - var s = ''; - var len = tb.length; - for (var i=0; i").addClass("output_area output_jpeg"); + toinsert.append($("").attr('src','data:image/jpeg;base64,'+jpeg)); + element.append(toinsert); + return element; + }; + + CodeCell.prototype.append_latex = function (latex, element) { // This method cannot do the typesetting because the latex first has to // be on the page. diff --git a/IPython/frontend/html/notebook/static/js/notebook.js b/IPython/frontend/html/notebook/static/js/notebook.js index 27e821d..f415dde 100644 --- a/IPython/frontend/html/notebook/static/js/notebook.js +++ b/IPython/frontend/html/notebook/static/js/notebook.js @@ -544,6 +544,9 @@ var IPython = (function (IPython) { if (data['image/png'] !== undefined) { json.png = data['image/png']; }; + if (data['image/jpeg'] !== undefined) { + json.jpeg = data['image/jpeg']; + }; if (data['text/latex'] !== undefined) { json.latex = data['text/latex']; }; diff --git a/IPython/nbformat/v2/nbbase.py b/IPython/nbformat/v2/nbbase.py index 021bc45..042c487 100644 --- a/IPython/nbformat/v2/nbbase.py +++ b/IPython/nbformat/v2/nbbase.py @@ -24,7 +24,7 @@ def from_dict(d): def new_output(output_type=None, output_text=None, output_png=None, output_html=None, output_svg=None, output_latex=None, output_json=None, - output_javascript=None, prompt_number=None): + output_javascript=None, output_jpeg=None, prompt_number=None): """Create a new code cell with input and output""" output = NotebookNode() if output_type is not None: @@ -33,6 +33,8 @@ def new_output(output_type=None, output_text=None, output_png=None, output.text = unicode(output_text) if output_png is not None: output.png = bytes(output_png) + if output_jpeg is not None: + output.jpeg = bytes(output_jpeg) if output_html is not None: output.html = unicode(output_html) if output_svg is not None: diff --git a/IPython/nbformat/v2/nbxml.py b/IPython/nbformat/v2/nbxml.py index bd1f878..4680dce 100644 --- a/IPython/nbformat/v2/nbxml.py +++ b/IPython/nbformat/v2/nbxml.py @@ -91,16 +91,17 @@ class XMLReader(NotebookReader): output_type = _get_text(output_e,'output_type') output_text = _get_text(output_e,'text') output_png = _get_binary(output_e,'png') + output_jpeg = _get_binary(output_e,'jpeg') output_svg = _get_text(output_e,'svg') output_html = _get_text(output_e,'html') output_latex = _get_text(output_e,'latex') output_json = _get_text(output_e,'json') output_javascript = _get_text(output_e,'javascript') output = new_output(output_type=output_type,output_png=output_png, - output_text=output_text,output_svg=output_svg, - output_html=output_html,output_latex=output_latex, - output_json=output_json,output_javascript=output_javascript, - prompt_number=out_prompt_number + output_text=output_text, output_svg=output_svg, + output_html=output_html, output_latex=output_latex, + output_json=output_json, output_javascript=output_javascript, + output_jpeg=output_jpeg, prompt_number=out_prompt_number ) outputs.append(output) cc = new_code_cell(input=input,prompt_number=prompt_number, @@ -147,6 +148,7 @@ class XMLWriter(NotebookWriter): _set_text(output,'output_type',output_e,'output_type') _set_text(output,'text',output_e,'text') _set_binary(output,'png',output_e,'png') + _set_binary(output,'jpeg',output_e,'jpeg') _set_text(output,'html',output_e,'html') _set_text(output,'svg',output_e,'svg') _set_text(output,'latex',output_e,'latex') diff --git a/IPython/nbformat/v2/rwbase.py b/IPython/nbformat/v2/rwbase.py index 0c5432e..db29a0d 100644 --- a/IPython/nbformat/v2/rwbase.py +++ b/IPython/nbformat/v2/rwbase.py @@ -8,6 +8,8 @@ def base64_decode(nb): if cell.cell_type == 'code': if 'png' in cell: cell.png = bytes(decodestring(cell.png)) + if 'jpeg' in cell: + cell.jpeg = bytes(decodestring(cell.jpeg)) return nb @@ -18,6 +20,8 @@ def base64_encode(nb): if cell.cell_type == 'code': if 'png' in cell: cell.png = unicode(encodestring(cell.png)) + if 'jpeg' in cell: + cell.jpeg = unicode(encodestring(cell.jpeg)) return nb diff --git a/IPython/nbformat/v2/tests/nbexamples.py b/IPython/nbformat/v2/tests/nbexamples.py index a4a83f9..4c331b0 100644 --- a/IPython/nbformat/v2/tests/nbexamples.py +++ b/IPython/nbformat/v2/tests/nbexamples.py @@ -39,6 +39,7 @@ ws.cells.append(new_code_cell( output_html=u'The HTML rep', output_latex=u'$a$', output_png=b'data', + output_jpeg=b'data', output_svg=u'', output_json=u'json data', output_javascript=u'var i=0;', @@ -49,6 +50,7 @@ ws.cells.append(new_code_cell( output_html=u'The HTML rep', output_latex=u'$a$', output_png=b'data', + output_jpeg=b'data', output_svg=u'', output_json=u'json data', output_javascript=u'var i=0;', diff --git a/IPython/zmq/displayhook.py b/IPython/zmq/displayhook.py index ba5c267..cc70604 100644 --- a/IPython/zmq/displayhook.py +++ b/IPython/zmq/displayhook.py @@ -27,10 +27,14 @@ class ZMQDisplayHook(object): self.parent_header = extract_header(parent) -def _encode_png(data): - pngdata = data.get('image/png') +def _encode_binary(format_dict): + pngdata = format_dict.get('image/png') if pngdata is not None: - data['image/png'] = encodestring(pngdata) + format_dict['image/png'] = encodestring(pngdata) + jpegdata = format_dict.get('image/jpeg') + if jpegdata is not None: + format_dict['image/jpeg'] = encodestring(jpegdata) + class ZMQShellDisplayHook(DisplayHook): """A displayhook subclass that publishes data using ZeroMQ. This is intended @@ -54,11 +58,11 @@ class ZMQShellDisplayHook(DisplayHook): self.msg['content']['execution_count'] = self.prompt_count def write_format_data(self, format_dict): - pngdata = format_dict.get('image/png') - _encode_png(format_dict) + _encode_binary(format_dict) self.msg['content']['data'] = format_dict def finish_displayhook(self): """Finish up all displayhook activities.""" self.session.send(self.pub_socket, self.msg) self.msg = None + diff --git a/IPython/zmq/zmqshell.py b/IPython/zmq/zmqshell.py index 218ebe8..e556610 100644 --- a/IPython/zmq/zmqshell.py +++ b/IPython/zmq/zmqshell.py @@ -33,7 +33,7 @@ from IPython.utils import io from IPython.utils.path import get_py_filename from IPython.utils.traitlets import Instance, Type, Dict, CBool from IPython.utils.warn import warn -from IPython.zmq.displayhook import ZMQShellDisplayHook, _encode_png +from IPython.zmq.displayhook import ZMQShellDisplayHook, _encode_binary from IPython.zmq.session import extract_header from session import Session @@ -65,7 +65,7 @@ class ZMQDisplayPublisher(DisplayPublisher): self._validate_data(source, data, metadata) content = {} content['source'] = source - _encode_png(data) + _encode_binary(data) content['data'] = data content['metadata'] = metadata self.session.send(