Show More
@@ -81,17 +81,7 b' def publish_display_data(data, metadata=None, source=None, *, transient=None, **' | |||||
81 | See the ``display_data`` message in the messaging documentation for |
|
81 | See the ``display_data`` message in the messaging documentation for | |
82 | more details about this message type. |
|
82 | more details about this message type. | |
83 |
|
83 | |||
84 | The following MIME types are currently implemented: |
|
84 | Keys of data and metadata can be any mime-type. | |
85 |
|
||||
86 | * text/plain |
|
|||
87 | * text/html |
|
|||
88 | * text/markdown |
|
|||
89 | * text/latex |
|
|||
90 | * application/json |
|
|||
91 | * application/javascript |
|
|||
92 | * image/png |
|
|||
93 | * image/jpeg |
|
|||
94 | * image/svg+xml |
|
|||
95 |
|
85 | |||
96 | Parameters |
|
86 | Parameters | |
97 | ---------- |
|
87 | ---------- | |
@@ -254,7 +244,8 b' def display(*objs, include=None, exclude=None, metadata=None, transient=None, di' | |||||
254 | - `_repr_svg_`: return raw SVG data as a string |
|
244 | - `_repr_svg_`: return raw SVG data as a string | |
255 | - `_repr_latex_`: return LaTeX commands in a string surrounded by "$". |
|
245 | - `_repr_latex_`: return LaTeX commands in a string surrounded by "$". | |
256 | - `_repr_mimebundle_`: return a full mimebundle containing the mapping |
|
246 | - `_repr_mimebundle_`: return a full mimebundle containing the mapping | |
257 | from all mimetypes to data |
|
247 | from all mimetypes to data. | |
|
248 | Use this for any mime-type not listed above. | |||
258 |
|
249 | |||
259 | When you are directly writing your own classes, you can adapt them for |
|
250 | When you are directly writing your own classes, you can adapt them for | |
260 | display in IPython by following the above approach. But in practice, you |
|
251 | display in IPython by following the above approach. But in practice, you | |
@@ -942,8 +933,7 b' def _pngxy(data):' | |||||
942 | """read the (width, height) from a PNG header""" |
|
933 | """read the (width, height) from a PNG header""" | |
943 | ihdr = data.index(b'IHDR') |
|
934 | ihdr = data.index(b'IHDR') | |
944 | # next 8 bytes are width/height |
|
935 | # next 8 bytes are width/height | |
945 |
|
|
936 | return struct.unpack('>ii', data[ihdr+4:ihdr+12]) | |
946 | return struct.unpack('>ii', w4h4) |
|
|||
947 |
|
937 | |||
948 | def _jpegxy(data): |
|
938 | def _jpegxy(data): | |
949 | """read the (width, height) from a JPEG header""" |
|
939 | """read the (width, height) from a JPEG header""" | |
@@ -964,17 +954,28 b' def _jpegxy(data):' | |||||
964 | h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9]) |
|
954 | h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9]) | |
965 | return w, h |
|
955 | return w, h | |
966 |
|
956 | |||
|
957 | def _gifxy(data): | |||
|
958 | """read the (width, height) from a GIF header""" | |||
|
959 | return struct.unpack('<HH', data[6:10]) | |||
|
960 | ||||
|
961 | ||||
967 | class Image(DisplayObject): |
|
962 | class Image(DisplayObject): | |
968 |
|
963 | |||
969 | _read_flags = 'rb' |
|
964 | _read_flags = 'rb' | |
970 | _FMT_JPEG = u'jpeg' |
|
965 | _FMT_JPEG = u'jpeg' | |
971 | _FMT_PNG = u'png' |
|
966 | _FMT_PNG = u'png' | |
972 | _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG] |
|
967 | _FMT_GIF = u'gif' | |
|
968 | _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF] | |||
|
969 | _MIMETYPES = { | |||
|
970 | _FMT_PNG: 'image/png', | |||
|
971 | _FMT_JPEG: 'image/jpeg', | |||
|
972 | _FMT_GIF: 'image/gif', | |||
|
973 | } | |||
973 |
|
974 | |||
974 | def __init__(self, data=None, url=None, filename=None, format=None, |
|
975 | def __init__(self, data=None, url=None, filename=None, format=None, | |
975 | embed=None, width=None, height=None, retina=False, |
|
976 | embed=None, width=None, height=None, retina=False, | |
976 | unconfined=False, metadata=None): |
|
977 | unconfined=False, metadata=None): | |
977 | """Create a PNG/JPEG image object given raw data. |
|
978 | """Create a PNG/JPEG/GIF image object given raw data. | |
978 |
|
979 | |||
979 | When this object is returned by an input cell or passed to the |
|
980 | When this object is returned by an input cell or passed to the | |
980 | display function, it will result in the image being displayed |
|
981 | display function, it will result in the image being displayed | |
@@ -992,7 +993,7 b' class Image(DisplayObject):' | |||||
992 | Path to a local file to load the data from. |
|
993 | Path to a local file to load the data from. | |
993 | Images from a file are always embedded. |
|
994 | Images from a file are always embedded. | |
994 | format : unicode |
|
995 | format : unicode | |
995 | The format of the image data (png/jpeg/jpg). If a filename or URL is given |
|
996 | The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given | |
996 | for format will be inferred from the filename extension. |
|
997 | for format will be inferred from the filename extension. | |
997 | embed : bool |
|
998 | embed : bool | |
998 | Should the image data be embedded using a data URI (True) or be |
|
999 | Should the image data be embedded using a data URI (True) or be | |
@@ -1054,6 +1055,8 b' class Image(DisplayObject):' | |||||
1054 | format = self._FMT_JPEG |
|
1055 | format = self._FMT_JPEG | |
1055 | if ext == u'png': |
|
1056 | if ext == u'png': | |
1056 | format = self._FMT_PNG |
|
1057 | format = self._FMT_PNG | |
|
1058 | if ext == u'gif': | |||
|
1059 | format = self._FMT_GIF | |||
1057 | else: |
|
1060 | else: | |
1058 | format = ext.lower() |
|
1061 | format = ext.lower() | |
1059 | elif isinstance(data, bytes): |
|
1062 | elif isinstance(data, bytes): | |
@@ -1064,7 +1067,7 b' class Image(DisplayObject):' | |||||
1064 |
|
1067 | |||
1065 | # failed to detect format, default png |
|
1068 | # failed to detect format, default png | |
1066 | if format is None: |
|
1069 | if format is None: | |
1067 |
format = |
|
1070 | format = self._FMT_PNG | |
1068 |
|
1071 | |||
1069 | if format.lower() == 'jpg': |
|
1072 | if format.lower() == 'jpg': | |
1070 | # jpg->jpeg |
|
1073 | # jpg->jpeg | |
@@ -1075,6 +1078,9 b' class Image(DisplayObject):' | |||||
1075 |
|
1078 | |||
1076 | if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS: |
|
1079 | if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS: | |
1077 | raise ValueError("Cannot embed the '%s' image format" % (self.format)) |
|
1080 | raise ValueError("Cannot embed the '%s' image format" % (self.format)) | |
|
1081 | if self.embed: | |||
|
1082 | self._mimetype = self._MIMETYPES.get(self.format) | |||
|
1083 | ||||
1078 | self.width = width |
|
1084 | self.width = width | |
1079 | self.height = height |
|
1085 | self.height = height | |
1080 | self.retina = retina |
|
1086 | self.retina = retina | |
@@ -1091,14 +1097,17 b' class Image(DisplayObject):' | |||||
1091 | if retina: |
|
1097 | if retina: | |
1092 | self._retina_shape() |
|
1098 | self._retina_shape() | |
1093 |
|
1099 | |||
|
1100 | ||||
1094 | def _retina_shape(self): |
|
1101 | def _retina_shape(self): | |
1095 | """load pixel-doubled width and height from image data""" |
|
1102 | """load pixel-doubled width and height from image data""" | |
1096 | if not self.embed: |
|
1103 | if not self.embed: | |
1097 | return |
|
1104 | return | |
1098 |
if self.format == |
|
1105 | if self.format == self._FMT_PNG: | |
1099 | w, h = _pngxy(self.data) |
|
1106 | w, h = _pngxy(self.data) | |
1100 |
elif self.format == |
|
1107 | elif self.format == self._FMT_JPEG: | |
1101 | w, h = _jpegxy(self.data) |
|
1108 | w, h = _jpegxy(self.data) | |
|
1109 | elif self.format == self._FMT_GIF: | |||
|
1110 | w, h = _gifxy(self.data) | |||
1102 | else: |
|
1111 | else: | |
1103 | # retina only supports png |
|
1112 | # retina only supports png | |
1104 | return |
|
1113 | return | |
@@ -1128,7 +1137,21 b' class Image(DisplayObject):' | |||||
1128 | klass=klass, |
|
1137 | klass=klass, | |
1129 | ) |
|
1138 | ) | |
1130 |
|
1139 | |||
1131 | def _data_and_metadata(self): |
|
1140 | def _repr_mimebundle_(self, include=None, exclude=None): | |
|
1141 | """Return the image as a mimebundle | |||
|
1142 | ||||
|
1143 | Any new mimetype support should be implemented here. | |||
|
1144 | """ | |||
|
1145 | if self.embed: | |||
|
1146 | mimetype = self._mimetype | |||
|
1147 | data, metadata = self._data_and_metadata(always_both=True) | |||
|
1148 | if metadata: | |||
|
1149 | metadata = {mimetype: metadata} | |||
|
1150 | return {mimetype: data}, metadata | |||
|
1151 | else: | |||
|
1152 | return {'text/html': self._repr_html_()} | |||
|
1153 | ||||
|
1154 | def _data_and_metadata(self, always_both=False): | |||
1132 | """shortcut for returning metadata with shape information, if defined""" |
|
1155 | """shortcut for returning metadata with shape information, if defined""" | |
1133 | b64_data = b2a_base64(self.data).decode('ascii') |
|
1156 | b64_data = b2a_base64(self.data).decode('ascii') | |
1134 | md = {} |
|
1157 | md = {} | |
@@ -1140,22 +1163,23 b' class Image(DisplayObject):' | |||||
1140 | md['height'] = self.height |
|
1163 | md['height'] = self.height | |
1141 | if self.unconfined: |
|
1164 | if self.unconfined: | |
1142 | md['unconfined'] = self.unconfined |
|
1165 | md['unconfined'] = self.unconfined | |
1143 | if md: |
|
1166 | if md or always_both: | |
1144 | return b64_data, md |
|
1167 | return b64_data, md | |
1145 | else: |
|
1168 | else: | |
1146 | return b64_data |
|
1169 | return b64_data | |
1147 |
|
1170 | |||
1148 | def _repr_png_(self): |
|
1171 | def _repr_png_(self): | |
1149 |
if self.embed and self.format == |
|
1172 | if self.embed and self.format == self._FMT_PNG: | |
1150 | return self._data_and_metadata() |
|
1173 | return self._data_and_metadata() | |
1151 |
|
1174 | |||
1152 | def _repr_jpeg_(self): |
|
1175 | def _repr_jpeg_(self): | |
1153 |
if self.embed and |
|
1176 | if self.embed and self.format == self._FMT_JPEG: | |
1154 | return self._data_and_metadata() |
|
1177 | return self._data_and_metadata() | |
1155 |
|
1178 | |||
1156 | def _find_ext(self, s): |
|
1179 | def _find_ext(self, s): | |
1157 | return s.split('.')[-1].lower() |
|
1180 | return s.split('.')[-1].lower() | |
1158 |
|
1181 | |||
|
1182 | ||||
1159 | class Video(DisplayObject): |
|
1183 | class Video(DisplayObject): | |
1160 |
|
1184 | |||
1161 | def __init__(self, data=None, url=None, filename=None, embed=False, mimetype=None): |
|
1185 | def __init__(self, data=None, url=None, filename=None, embed=False, mimetype=None): | |
@@ -1254,12 +1278,6 b' class Video(DisplayObject):' | |||||
1254 | # TODO |
|
1278 | # TODO | |
1255 | pass |
|
1279 | pass | |
1256 |
|
1280 | |||
1257 | def _repr_png_(self): |
|
|||
1258 | # TODO |
|
|||
1259 | pass |
|
|||
1260 | def _repr_jpeg_(self): |
|
|||
1261 | # TODO |
|
|||
1262 | pass |
|
|||
1263 |
|
1281 | |||
1264 | def clear_output(wait=False): |
|
1282 | def clear_output(wait=False): | |
1265 | """Clear the output of the current cell receiving output. |
|
1283 | """Clear the output of the current cell receiving output. |
@@ -967,10 +967,7 b' class MimeBundleFormatter(BaseFormatter):' | |||||
967 | method = get_real_method(obj, self.print_method) |
|
967 | method = get_real_method(obj, self.print_method) | |
968 |
|
968 | |||
969 | if method is not None: |
|
969 | if method is not None: | |
970 | d = {} |
|
970 | return method(include=include, exclude=exclude) | |
971 | d['include'] = include |
|
|||
972 | d['exclude'] = exclude |
|
|||
973 | return method(**d) |
|
|||
974 | return None |
|
971 | return None | |
975 | else: |
|
972 | else: | |
976 | return None |
|
973 | return None | |
@@ -996,19 +993,6 b' def format_display_data(obj, include=None, exclude=None):' | |||||
996 |
|
993 | |||
997 | By default all format types will be computed. |
|
994 | By default all format types will be computed. | |
998 |
|
995 | |||
999 | The following MIME types are currently implemented: |
|
|||
1000 |
|
||||
1001 | * text/plain |
|
|||
1002 | * text/html |
|
|||
1003 | * text/markdown |
|
|||
1004 | * text/latex |
|
|||
1005 | * application/json |
|
|||
1006 | * application/javascript |
|
|||
1007 | * application/pdf |
|
|||
1008 | * image/png |
|
|||
1009 | * image/jpeg |
|
|||
1010 | * image/svg+xml |
|
|||
1011 |
|
||||
1012 | Parameters |
|
996 | Parameters | |
1013 | ---------- |
|
997 | ---------- | |
1014 | obj : object |
|
998 | obj : object |
@@ -32,6 +32,15 b' def test_image_size():' | |||||
32 | nt.assert_equal(u'<img src="%s" class="unconfined"/>' % (thisurl), img._repr_html_()) |
|
32 | nt.assert_equal(u'<img src="%s" class="unconfined"/>' % (thisurl), img._repr_html_()) | |
33 |
|
33 | |||
34 |
|
34 | |||
|
35 | def test_image_mimes(): | |||
|
36 | fmt = get_ipython().display_formatter.format | |||
|
37 | for format in display.Image._ACCEPTABLE_EMBEDDINGS: | |||
|
38 | mime = display.Image._MIMETYPES[format] | |||
|
39 | img = display.Image(b'garbage', format=format) | |||
|
40 | data, metadata = fmt(img) | |||
|
41 | nt.assert_equal(sorted(data), sorted([mime, 'text/plain'])) | |||
|
42 | ||||
|
43 | ||||
35 | def test_geojson(): |
|
44 | def test_geojson(): | |
36 |
|
45 | |||
37 | gj = display.GeoJSON(data={ |
|
46 | gj = display.GeoJSON(data={ | |
@@ -77,7 +86,7 b' def test_base64image():' | |||||
77 | def test_image_filename_defaults(): |
|
86 | def test_image_filename_defaults(): | |
78 | '''test format constraint, and validity of jpeg and png''' |
|
87 | '''test format constraint, and validity of jpeg and png''' | |
79 | tpath = ipath.get_ipython_package_dir() |
|
88 | tpath = ipath.get_ipython_package_dir() | |
80 |
nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat. |
|
89 | nt.assert_raises(ValueError, display.Image, filename=os.path.join(tpath, 'testing/tests/badformat.zip'), | |
81 | embed=True) |
|
90 | embed=True) | |
82 | nt.assert_raises(ValueError, display.Image) |
|
91 | nt.assert_raises(ValueError, display.Image) | |
83 | nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True) |
|
92 | nt.assert_raises(ValueError, display.Image, data='this is not an image', format='badformat', embed=True) | |
@@ -361,3 +370,4 b' def test_display_handle():' | |||||
361 | }, |
|
370 | }, | |
362 | 'update': True, |
|
371 | 'update': True, | |
363 | }) |
|
372 | }) | |
|
373 |
@@ -30,7 +30,10 b' class RichOutput(object):' | |||||
30 | return data, self.metadata[mime] |
|
30 | return data, self.metadata[mime] | |
31 | else: |
|
31 | else: | |
32 | return data |
|
32 | return data | |
33 |
|
33 | |||
|
34 | def _repr_mimebundle_(self, include=None, exclude=None): | |||
|
35 | return self.data, self.metadata | |||
|
36 | ||||
34 | def _repr_html_(self): |
|
37 | def _repr_html_(self): | |
35 | return self._repr_mime_("text/html") |
|
38 | return self._repr_mime_("text/html") | |
36 |
|
39 | |||
@@ -162,5 +165,3 b' class capture_output(object):' | |||||
162 | if self.display and self.shell: |
|
165 | if self.display and self.shell: | |
163 | self.shell.display_pub = self.save_display_pub |
|
166 | self.shell.display_pub = self.save_display_pub | |
164 | sys.displayhook = self.save_display_hook |
|
167 | sys.displayhook = self.save_display_hook | |
165 |
|
||||
166 |
|
General Comments 0
You need to be logged in to leave comments.
Login now