##// END OF EJS Templates
ENH: add webp support to IPython.display.Image and complete identification of JPEG,PNG,GIF,WEBP image types by initial bytes
Wes Turner -
Show More
@@ -1,1294 +1,1342
1 1 # -*- coding: utf-8 -*-
2 2 """Top-level display functions for displaying object in different formats."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7
8 8 from binascii import b2a_base64, hexlify
9 9 import html
10 10 import json
11 11 import mimetypes
12 12 import os
13 13 import struct
14 14 import warnings
15 15 from copy import deepcopy
16 16 from os.path import splitext
17 17 from pathlib import Path, PurePath
18 18
19 19 from IPython.utils.py3compat import cast_unicode
20 20 from IPython.testing.skipdoctest import skip_doctest
21 21 from . import display_functions
22 22
23 23
24 24 __all__ = ['display_pretty', 'display_html', 'display_markdown',
25 'display_svg', 'display_png', 'display_jpeg', 'display_latex', 'display_json',
25 'display_svg', 'display_png', 'display_jpeg', 'display_webp',
26 'display_latex', 'display_json',
26 27 'display_javascript', 'display_pdf', 'DisplayObject', 'TextDisplayObject',
27 28 'Pretty', 'HTML', 'Markdown', 'Math', 'Latex', 'SVG', 'ProgressBar', 'JSON',
28 29 'GeoJSON', 'Javascript', 'Image', 'set_matplotlib_formats',
29 30 'set_matplotlib_close',
30 31 'Video']
31 32
32 33 _deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
33 34
34 35 __all__ = __all__ + _deprecated_names
35 36
36 37
37 38 # ----- warn to import from IPython.display -----
38 39
39 40 from warnings import warn
40 41
41 42
42 43 def __getattr__(name):
43 44 if name in _deprecated_names:
44 45 warn(
45 46 f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython.display",
46 47 DeprecationWarning,
47 48 stacklevel=2,
48 49 )
49 50 return getattr(display_functions, name)
50 51
51 52 if name in globals().keys():
52 53 return globals()[name]
53 54 else:
54 55 raise AttributeError(f"module {__name__} has no attribute {name}")
55 56
56 57
57 58 #-----------------------------------------------------------------------------
58 59 # utility functions
59 60 #-----------------------------------------------------------------------------
60 61
61 62 def _safe_exists(path):
62 63 """Check path, but don't let exceptions raise"""
63 64 try:
64 65 return os.path.exists(path)
65 66 except Exception:
66 67 return False
67 68
68 69
69 70 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
70 71 """internal implementation of all display_foo methods
71 72
72 73 Parameters
73 74 ----------
74 75 mimetype : str
75 76 The mimetype to be published (e.g. 'image/png')
76 77 *objs : object
77 78 The Python objects to display, or if raw=True raw text data to
78 79 display.
79 80 raw : bool
80 81 Are the data objects raw data or Python objects that need to be
81 82 formatted before display? [default: False]
82 83 metadata : dict (optional)
83 84 Metadata to be associated with the specific mimetype output.
84 85 """
85 86 if metadata:
86 87 metadata = {mimetype: metadata}
87 88 if raw:
88 89 # turn list of pngdata into list of { 'image/png': pngdata }
89 90 objs = [ {mimetype: obj} for obj in objs ]
90 91 display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype])
91 92
92 93 #-----------------------------------------------------------------------------
93 94 # Main functions
94 95 #-----------------------------------------------------------------------------
95 96
96 97
97 98 def display_pretty(*objs, **kwargs):
98 99 """Display the pretty (default) representation of an object.
99 100
100 101 Parameters
101 102 ----------
102 103 *objs : object
103 104 The Python objects to display, or if raw=True raw text data to
104 105 display.
105 106 raw : bool
106 107 Are the data objects raw data or Python objects that need to be
107 108 formatted before display? [default: False]
108 109 metadata : dict (optional)
109 110 Metadata to be associated with the specific mimetype output.
110 111 """
111 112 _display_mimetype('text/plain', objs, **kwargs)
112 113
113 114
114 115 def display_html(*objs, **kwargs):
115 116 """Display the HTML representation of an object.
116 117
117 118 Note: If raw=False and the object does not have a HTML
118 119 representation, no HTML will be shown.
119 120
120 121 Parameters
121 122 ----------
122 123 *objs : object
123 124 The Python objects to display, or if raw=True raw HTML data to
124 125 display.
125 126 raw : bool
126 127 Are the data objects raw data or Python objects that need to be
127 128 formatted before display? [default: False]
128 129 metadata : dict (optional)
129 130 Metadata to be associated with the specific mimetype output.
130 131 """
131 132 _display_mimetype('text/html', objs, **kwargs)
132 133
133 134
134 135 def display_markdown(*objs, **kwargs):
135 136 """Displays the Markdown representation of an object.
136 137
137 138 Parameters
138 139 ----------
139 140 *objs : object
140 141 The Python objects to display, or if raw=True raw markdown data to
141 142 display.
142 143 raw : bool
143 144 Are the data objects raw data or Python objects that need to be
144 145 formatted before display? [default: False]
145 146 metadata : dict (optional)
146 147 Metadata to be associated with the specific mimetype output.
147 148 """
148 149
149 150 _display_mimetype('text/markdown', objs, **kwargs)
150 151
151 152
152 153 def display_svg(*objs, **kwargs):
153 154 """Display the SVG representation of an object.
154 155
155 156 Parameters
156 157 ----------
157 158 *objs : object
158 159 The Python objects to display, or if raw=True raw svg data to
159 160 display.
160 161 raw : bool
161 162 Are the data objects raw data or Python objects that need to be
162 163 formatted before display? [default: False]
163 164 metadata : dict (optional)
164 165 Metadata to be associated with the specific mimetype output.
165 166 """
166 167 _display_mimetype('image/svg+xml', objs, **kwargs)
167 168
168 169
169 170 def display_png(*objs, **kwargs):
170 171 """Display the PNG representation of an object.
171 172
172 173 Parameters
173 174 ----------
174 175 *objs : object
175 176 The Python objects to display, or if raw=True raw png data to
176 177 display.
177 178 raw : bool
178 179 Are the data objects raw data or Python objects that need to be
179 180 formatted before display? [default: False]
180 181 metadata : dict (optional)
181 182 Metadata to be associated with the specific mimetype output.
182 183 """
183 184 _display_mimetype('image/png', objs, **kwargs)
184 185
185 186
186 187 def display_jpeg(*objs, **kwargs):
187 188 """Display the JPEG representation of an object.
188 189
189 190 Parameters
190 191 ----------
191 192 *objs : object
192 193 The Python objects to display, or if raw=True raw JPEG data to
193 194 display.
194 195 raw : bool
195 196 Are the data objects raw data or Python objects that need to be
196 197 formatted before display? [default: False]
197 198 metadata : dict (optional)
198 199 Metadata to be associated with the specific mimetype output.
199 200 """
200 201 _display_mimetype('image/jpeg', objs, **kwargs)
201 202
202 203
204 def display_webp(*objs, **kwargs):
205 """Display the WEBP representation of an object.
206
207 Parameters
208 ----------
209 *objs : object
210 The Python objects to display, or if raw=True raw JPEG data to
211 display.
212 raw : bool
213 Are the data objects raw data or Python objects that need to be
214 formatted before display? [default: False]
215 metadata : dict (optional)
216 Metadata to be associated with the specific mimetype output.
217 """
218 _display_mimetype('image/webp', objs, **kwargs)
219
220
203 221 def display_latex(*objs, **kwargs):
204 222 """Display the LaTeX representation of an object.
205 223
206 224 Parameters
207 225 ----------
208 226 *objs : object
209 227 The Python objects to display, or if raw=True raw latex data to
210 228 display.
211 229 raw : bool
212 230 Are the data objects raw data or Python objects that need to be
213 231 formatted before display? [default: False]
214 232 metadata : dict (optional)
215 233 Metadata to be associated with the specific mimetype output.
216 234 """
217 235 _display_mimetype('text/latex', objs, **kwargs)
218 236
219 237
220 238 def display_json(*objs, **kwargs):
221 239 """Display the JSON representation of an object.
222 240
223 241 Note that not many frontends support displaying JSON.
224 242
225 243 Parameters
226 244 ----------
227 245 *objs : object
228 246 The Python objects to display, or if raw=True raw json data to
229 247 display.
230 248 raw : bool
231 249 Are the data objects raw data or Python objects that need to be
232 250 formatted before display? [default: False]
233 251 metadata : dict (optional)
234 252 Metadata to be associated with the specific mimetype output.
235 253 """
236 254 _display_mimetype('application/json', objs, **kwargs)
237 255
238 256
239 257 def display_javascript(*objs, **kwargs):
240 258 """Display the Javascript representation of an object.
241 259
242 260 Parameters
243 261 ----------
244 262 *objs : object
245 263 The Python objects to display, or if raw=True raw javascript data to
246 264 display.
247 265 raw : bool
248 266 Are the data objects raw data or Python objects that need to be
249 267 formatted before display? [default: False]
250 268 metadata : dict (optional)
251 269 Metadata to be associated with the specific mimetype output.
252 270 """
253 271 _display_mimetype('application/javascript', objs, **kwargs)
254 272
255 273
256 274 def display_pdf(*objs, **kwargs):
257 275 """Display the PDF representation of an object.
258 276
259 277 Parameters
260 278 ----------
261 279 *objs : object
262 280 The Python objects to display, or if raw=True raw javascript data to
263 281 display.
264 282 raw : bool
265 283 Are the data objects raw data or Python objects that need to be
266 284 formatted before display? [default: False]
267 285 metadata : dict (optional)
268 286 Metadata to be associated with the specific mimetype output.
269 287 """
270 288 _display_mimetype('application/pdf', objs, **kwargs)
271 289
272 290
273 291 #-----------------------------------------------------------------------------
274 292 # Smart classes
275 293 #-----------------------------------------------------------------------------
276 294
277 295
278 296 class DisplayObject(object):
279 297 """An object that wraps data to be displayed."""
280 298
281 299 _read_flags = 'r'
282 300 _show_mem_addr = False
283 301 metadata = None
284 302
285 303 def __init__(self, data=None, url=None, filename=None, metadata=None):
286 304 """Create a display object given raw data.
287 305
288 306 When this object is returned by an expression or passed to the
289 307 display function, it will result in the data being displayed
290 308 in the frontend. The MIME type of the data should match the
291 309 subclasses used, so the Png subclass should be used for 'image/png'
292 310 data. If the data is a URL, the data will first be downloaded
293 311 and then displayed.
294 312
295 313 Parameters
296 314 ----------
297 315 data : unicode, str or bytes
298 316 The raw data or a URL or file to load the data from
299 317 url : unicode
300 318 A URL to download the data from.
301 319 filename : unicode
302 320 Path to a local file to load the data from.
303 321 metadata : dict
304 322 Dict of metadata associated to be the object when displayed
305 323 """
306 324 if isinstance(data, (Path, PurePath)):
307 325 data = str(data)
308 326
309 327 if data is not None and isinstance(data, str):
310 328 if data.startswith('http') and url is None:
311 329 url = data
312 330 filename = None
313 331 data = None
314 332 elif _safe_exists(data) and filename is None:
315 333 url = None
316 334 filename = data
317 335 data = None
318 336
319 337 self.url = url
320 338 self.filename = filename
321 339 # because of @data.setter methods in
322 340 # subclasses ensure url and filename are set
323 341 # before assigning to self.data
324 342 self.data = data
325 343
326 344 if metadata is not None:
327 345 self.metadata = metadata
328 346 elif self.metadata is None:
329 347 self.metadata = {}
330 348
331 349 self.reload()
332 350 self._check_data()
333 351
334 352 def __repr__(self):
335 353 if not self._show_mem_addr:
336 354 cls = self.__class__
337 355 r = "<%s.%s object>" % (cls.__module__, cls.__name__)
338 356 else:
339 357 r = super(DisplayObject, self).__repr__()
340 358 return r
341 359
342 360 def _check_data(self):
343 361 """Override in subclasses if there's something to check."""
344 362 pass
345 363
346 364 def _data_and_metadata(self):
347 365 """shortcut for returning metadata with shape information, if defined"""
348 366 if self.metadata:
349 367 return self.data, deepcopy(self.metadata)
350 368 else:
351 369 return self.data
352 370
353 371 def reload(self):
354 372 """Reload the raw data from file or URL."""
355 373 if self.filename is not None:
356 374 encoding = None if "b" in self._read_flags else "utf-8"
357 375 with open(self.filename, self._read_flags, encoding=encoding) as f:
358 376 self.data = f.read()
359 377 elif self.url is not None:
360 378 # Deferred import
361 379 from urllib.request import urlopen
362 380 response = urlopen(self.url)
363 381 data = response.read()
364 382 # extract encoding from header, if there is one:
365 383 encoding = None
366 384 if 'content-type' in response.headers:
367 385 for sub in response.headers['content-type'].split(';'):
368 386 sub = sub.strip()
369 387 if sub.startswith('charset'):
370 388 encoding = sub.split('=')[-1].strip()
371 389 break
372 390 if 'content-encoding' in response.headers:
373 391 # TODO: do deflate?
374 392 if 'gzip' in response.headers['content-encoding']:
375 393 import gzip
376 394 from io import BytesIO
377 395
378 396 # assume utf-8 if encoding is not specified
379 397 with gzip.open(
380 398 BytesIO(data), "rt", encoding=encoding or "utf-8"
381 399 ) as fp:
382 400 encoding = None
383 401 data = fp.read()
384 402
385 403 # decode data, if an encoding was specified
386 404 # We only touch self.data once since
387 405 # subclasses such as SVG have @data.setter methods
388 406 # that transform self.data into ... well svg.
389 407 if encoding:
390 408 self.data = data.decode(encoding, 'replace')
391 409 else:
392 410 self.data = data
393 411
394 412
395 413 class TextDisplayObject(DisplayObject):
396 414 """Create a text display object given raw data.
397 415
398 416 Parameters
399 417 ----------
400 418 data : str or unicode
401 419 The raw data or a URL or file to load the data from.
402 420 url : unicode
403 421 A URL to download the data from.
404 422 filename : unicode
405 423 Path to a local file to load the data from.
406 424 metadata : dict
407 425 Dict of metadata associated to be the object when displayed
408 426 """
409 427 def _check_data(self):
410 428 if self.data is not None and not isinstance(self.data, str):
411 429 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
412 430
413 431 class Pretty(TextDisplayObject):
414 432
415 433 def _repr_pretty_(self, pp, cycle):
416 434 return pp.text(self.data)
417 435
418 436
419 437 class HTML(TextDisplayObject):
420 438
421 439 def __init__(self, data=None, url=None, filename=None, metadata=None):
422 440 def warn():
423 441 if not data:
424 442 return False
425 443
426 444 #
427 445 # Avoid calling lower() on the entire data, because it could be a
428 446 # long string and we're only interested in its beginning and end.
429 447 #
430 448 prefix = data[:10].lower()
431 449 suffix = data[-10:].lower()
432 450 return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
433 451
434 452 if warn():
435 453 warnings.warn("Consider using IPython.display.IFrame instead")
436 454 super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
437 455
438 456 def _repr_html_(self):
439 457 return self._data_and_metadata()
440 458
441 459 def __html__(self):
442 460 """
443 461 This method exists to inform other HTML-using modules (e.g. Markupsafe,
444 462 htmltag, etc) that this object is HTML and does not need things like
445 463 special characters (<>&) escaped.
446 464 """
447 465 return self._repr_html_()
448 466
449 467
450 468 class Markdown(TextDisplayObject):
451 469
452 470 def _repr_markdown_(self):
453 471 return self._data_and_metadata()
454 472
455 473
456 474 class Math(TextDisplayObject):
457 475
458 476 def _repr_latex_(self):
459 477 s = r"$\displaystyle %s$" % self.data.strip('$')
460 478 if self.metadata:
461 479 return s, deepcopy(self.metadata)
462 480 else:
463 481 return s
464 482
465 483
466 484 class Latex(TextDisplayObject):
467 485
468 486 def _repr_latex_(self):
469 487 return self._data_and_metadata()
470 488
471 489
472 490 class SVG(DisplayObject):
473 491 """Embed an SVG into the display.
474 492
475 493 Note if you just want to view a svg image via a URL use `:class:Image` with
476 494 a url=URL keyword argument.
477 495 """
478 496
479 497 _read_flags = 'rb'
480 498 # wrap data in a property, which extracts the <svg> tag, discarding
481 499 # document headers
482 500 _data = None
483 501
484 502 @property
485 503 def data(self):
486 504 return self._data
487 505
488 506 @data.setter
489 507 def data(self, svg):
490 508 if svg is None:
491 509 self._data = None
492 510 return
493 511 # parse into dom object
494 512 from xml.dom import minidom
495 513 x = minidom.parseString(svg)
496 514 # get svg tag (should be 1)
497 515 found_svg = x.getElementsByTagName('svg')
498 516 if found_svg:
499 517 svg = found_svg[0].toxml()
500 518 else:
501 519 # fallback on the input, trust the user
502 520 # but this is probably an error.
503 521 pass
504 522 svg = cast_unicode(svg)
505 523 self._data = svg
506 524
507 525 def _repr_svg_(self):
508 526 return self._data_and_metadata()
509 527
510 528 class ProgressBar(DisplayObject):
511 529 """Progressbar supports displaying a progressbar like element
512 530 """
513 531 def __init__(self, total):
514 532 """Creates a new progressbar
515 533
516 534 Parameters
517 535 ----------
518 536 total : int
519 537 maximum size of the progressbar
520 538 """
521 539 self.total = total
522 540 self._progress = 0
523 541 self.html_width = '60ex'
524 542 self.text_width = 60
525 543 self._display_id = hexlify(os.urandom(8)).decode('ascii')
526 544
527 545 def __repr__(self):
528 546 fraction = self.progress / self.total
529 547 filled = '=' * int(fraction * self.text_width)
530 548 rest = ' ' * (self.text_width - len(filled))
531 549 return '[{}{}] {}/{}'.format(
532 550 filled, rest,
533 551 self.progress, self.total,
534 552 )
535 553
536 554 def _repr_html_(self):
537 555 return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
538 556 self.html_width, self.total, self.progress)
539 557
540 558 def display(self):
541 559 display_functions.display(self, display_id=self._display_id)
542 560
543 561 def update(self):
544 562 display_functions.display(self, display_id=self._display_id, update=True)
545 563
546 564 @property
547 565 def progress(self):
548 566 return self._progress
549 567
550 568 @progress.setter
551 569 def progress(self, value):
552 570 self._progress = value
553 571 self.update()
554 572
555 573 def __iter__(self):
556 574 self.display()
557 575 self._progress = -1 # First iteration is 0
558 576 return self
559 577
560 578 def __next__(self):
561 579 """Returns current value and increments display by one."""
562 580 self.progress += 1
563 581 if self.progress < self.total:
564 582 return self.progress
565 583 else:
566 584 raise StopIteration()
567 585
568 586 class JSON(DisplayObject):
569 587 """JSON expects a JSON-able dict or list
570 588
571 589 not an already-serialized JSON string.
572 590
573 591 Scalar types (None, number, string) are not allowed, only dict or list containers.
574 592 """
575 593 # wrap data in a property, which warns about passing already-serialized JSON
576 594 _data = None
577 595 def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
578 596 """Create a JSON display object given raw data.
579 597
580 598 Parameters
581 599 ----------
582 600 data : dict or list
583 601 JSON data to display. Not an already-serialized JSON string.
584 602 Scalar types (None, number, string) are not allowed, only dict
585 603 or list containers.
586 604 url : unicode
587 605 A URL to download the data from.
588 606 filename : unicode
589 607 Path to a local file to load the data from.
590 608 expanded : boolean
591 609 Metadata to control whether a JSON display component is expanded.
592 610 metadata : dict
593 611 Specify extra metadata to attach to the json display object.
594 612 root : str
595 613 The name of the root element of the JSON tree
596 614 """
597 615 self.metadata = {
598 616 'expanded': expanded,
599 617 'root': root,
600 618 }
601 619 if metadata:
602 620 self.metadata.update(metadata)
603 621 if kwargs:
604 622 self.metadata.update(kwargs)
605 623 super(JSON, self).__init__(data=data, url=url, filename=filename)
606 624
607 625 def _check_data(self):
608 626 if self.data is not None and not isinstance(self.data, (dict, list)):
609 627 raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
610 628
611 629 @property
612 630 def data(self):
613 631 return self._data
614 632
615 633 @data.setter
616 634 def data(self, data):
617 635 if isinstance(data, (Path, PurePath)):
618 636 data = str(data)
619 637
620 638 if isinstance(data, str):
621 639 if self.filename is None and self.url is None:
622 640 warnings.warn("JSON expects JSONable dict or list, not JSON strings")
623 641 data = json.loads(data)
624 642 self._data = data
625 643
626 644 def _data_and_metadata(self):
627 645 return self.data, self.metadata
628 646
629 647 def _repr_json_(self):
630 648 return self._data_and_metadata()
631 649
632 650
633 651 _css_t = """var link = document.createElement("link");
634 652 link.rel = "stylesheet";
635 653 link.type = "text/css";
636 654 link.href = "%s";
637 655 document.head.appendChild(link);
638 656 """
639 657
640 658 _lib_t1 = """new Promise(function(resolve, reject) {
641 659 var script = document.createElement("script");
642 660 script.onload = resolve;
643 661 script.onerror = reject;
644 662 script.src = "%s";
645 663 document.head.appendChild(script);
646 664 }).then(() => {
647 665 """
648 666
649 667 _lib_t2 = """
650 668 });"""
651 669
652 670 class GeoJSON(JSON):
653 671 """GeoJSON expects JSON-able dict
654 672
655 673 not an already-serialized JSON string.
656 674
657 675 Scalar types (None, number, string) are not allowed, only dict containers.
658 676 """
659 677
660 678 def __init__(self, *args, **kwargs):
661 679 """Create a GeoJSON display object given raw data.
662 680
663 681 Parameters
664 682 ----------
665 683 data : dict or list
666 684 VegaLite data. Not an already-serialized JSON string.
667 685 Scalar types (None, number, string) are not allowed, only dict
668 686 or list containers.
669 687 url_template : string
670 688 Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
671 689 layer_options : dict
672 690 Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
673 691 url : unicode
674 692 A URL to download the data from.
675 693 filename : unicode
676 694 Path to a local file to load the data from.
677 695 metadata : dict
678 696 Specify extra metadata to attach to the json display object.
679 697
680 698 Examples
681 699 --------
682 700 The following will display an interactive map of Mars with a point of
683 701 interest on frontend that do support GeoJSON display.
684 702
685 703 >>> from IPython.display import GeoJSON
686 704
687 705 >>> GeoJSON(data={
688 706 ... "type": "Feature",
689 707 ... "geometry": {
690 708 ... "type": "Point",
691 709 ... "coordinates": [-81.327, 296.038]
692 710 ... }
693 711 ... },
694 712 ... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
695 713 ... layer_options={
696 714 ... "basemap_id": "celestia_mars-shaded-16k_global",
697 715 ... "attribution" : "Celestia/praesepe",
698 716 ... "minZoom" : 0,
699 717 ... "maxZoom" : 18,
700 718 ... })
701 719 <IPython.core.display.GeoJSON object>
702 720
703 721 In the terminal IPython, you will only see the text representation of
704 722 the GeoJSON object.
705 723
706 724 """
707 725
708 726 super(GeoJSON, self).__init__(*args, **kwargs)
709 727
710 728
711 729 def _ipython_display_(self):
712 730 bundle = {
713 731 'application/geo+json': self.data,
714 732 'text/plain': '<IPython.display.GeoJSON object>'
715 733 }
716 734 metadata = {
717 735 'application/geo+json': self.metadata
718 736 }
719 737 display_functions.display(bundle, metadata=metadata, raw=True)
720 738
721 739 class Javascript(TextDisplayObject):
722 740
723 741 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
724 742 """Create a Javascript display object given raw data.
725 743
726 744 When this object is returned by an expression or passed to the
727 745 display function, it will result in the data being displayed
728 746 in the frontend. If the data is a URL, the data will first be
729 747 downloaded and then displayed.
730 748
731 749 In the Notebook, the containing element will be available as `element`,
732 750 and jQuery will be available. Content appended to `element` will be
733 751 visible in the output area.
734 752
735 753 Parameters
736 754 ----------
737 755 data : unicode, str or bytes
738 756 The Javascript source code or a URL to download it from.
739 757 url : unicode
740 758 A URL to download the data from.
741 759 filename : unicode
742 760 Path to a local file to load the data from.
743 761 lib : list or str
744 762 A sequence of Javascript library URLs to load asynchronously before
745 763 running the source code. The full URLs of the libraries should
746 764 be given. A single Javascript library URL can also be given as a
747 765 string.
748 766 css : list or str
749 767 A sequence of css files to load before running the source code.
750 768 The full URLs of the css files should be given. A single css URL
751 769 can also be given as a string.
752 770 """
753 771 if isinstance(lib, str):
754 772 lib = [lib]
755 773 elif lib is None:
756 774 lib = []
757 775 if isinstance(css, str):
758 776 css = [css]
759 777 elif css is None:
760 778 css = []
761 779 if not isinstance(lib, (list,tuple)):
762 780 raise TypeError('expected sequence, got: %r' % lib)
763 781 if not isinstance(css, (list,tuple)):
764 782 raise TypeError('expected sequence, got: %r' % css)
765 783 self.lib = lib
766 784 self.css = css
767 785 super(Javascript, self).__init__(data=data, url=url, filename=filename)
768 786
769 787 def _repr_javascript_(self):
770 788 r = ''
771 789 for c in self.css:
772 790 r += _css_t % c
773 791 for l in self.lib:
774 792 r += _lib_t1 % l
775 793 r += self.data
776 794 r += _lib_t2*len(self.lib)
777 795 return r
778 796
779 # constants for identifying png/jpeg data
797 # constants for identifying png/jpeg/gif/webp data
780 798 _PNG = b'\x89PNG\r\n\x1a\n'
781 799 _JPEG = b'\xff\xd8'
800 _GIF1 = b"GIF87a"
801 _GIF2 = b"GIF89a"
802 _WEBP = b'WEBP'
782 803
783 804 def _pngxy(data):
784 805 """read the (width, height) from a PNG header"""
785 806 ihdr = data.index(b'IHDR')
786 807 # next 8 bytes are width/height
787 808 return struct.unpack('>ii', data[ihdr+4:ihdr+12])
788 809
789 810 def _jpegxy(data):
790 811 """read the (width, height) from a JPEG header"""
791 812 # adapted from http://www.64lines.com/jpeg-width-height
792 813
793 814 idx = 4
794 815 while True:
795 816 block_size = struct.unpack('>H', data[idx:idx+2])[0]
796 817 idx = idx + block_size
797 818 if data[idx:idx+2] == b'\xFF\xC0':
798 819 # found Start of Frame
799 820 iSOF = idx
800 821 break
801 822 else:
802 823 # read another block
803 824 idx += 2
804 825
805 826 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
806 827 return w, h
807 828
808 829 def _gifxy(data):
809 830 """read the (width, height) from a GIF header"""
810 831 return struct.unpack('<HH', data[6:10])
811 832
833 def _webpxy(data):
834 """read the (width, height) from a WEBP header"""
835 if data[12:16] == b"VP8 ":
836 width, height = struct.unpack('<HH', data[24:30])
837 width = (width & 0x3fff)
838 height = (height & 0x3fff)
839 return (width, height)
840 elif data[12:16] == b"VP8L":
841 size_info = struct.unpack('<I', data[21:25])[0]
842 width = 1 + ((size_info & 0x3F) << 8) | (size_info >> 24)
843 height = 1 + ((((size_info >> 8) & 0xF) << 10) |
844 (((size_info >> 14) & 0x3FC) << 2) |
845 ((size_info >> 22) & 0x3))
846 return (width, height)
847 else:
848 raise ValueError("Not a valid WEBP header")
849
812 850
813 851 class Image(DisplayObject):
814 852
815 853 _read_flags = 'rb'
816 854 _FMT_JPEG = u'jpeg'
817 855 _FMT_PNG = u'png'
818 856 _FMT_GIF = u'gif'
819 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF]
857 _FMT_WEBP = u'webp'
858 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF, _FMT_WEBP]
820 859 _MIMETYPES = {
821 860 _FMT_PNG: 'image/png',
822 861 _FMT_JPEG: 'image/jpeg',
823 862 _FMT_GIF: 'image/gif',
863 _FMT_WEBP: 'image/webp',
824 864 }
825 865
826 866 def __init__(
827 867 self,
828 868 data=None,
829 869 url=None,
830 870 filename=None,
831 871 format=None,
832 872 embed=None,
833 873 width=None,
834 874 height=None,
835 875 retina=False,
836 876 unconfined=False,
837 877 metadata=None,
838 878 alt=None,
839 879 ):
840 880 """Create a PNG/JPEG/GIF image object given raw data.
841 881
842 882 When this object is returned by an input cell or passed to the
843 883 display function, it will result in the image being displayed
844 884 in the frontend.
845 885
846 886 Parameters
847 887 ----------
848 888 data : unicode, str or bytes
849 889 The raw image data or a URL or filename to load the data from.
850 890 This always results in embedded image data.
851 891
852 892 url : unicode
853 893 A URL to download the data from. If you specify `url=`,
854 894 the image data will not be embedded unless you also specify `embed=True`.
855 895
856 896 filename : unicode
857 897 Path to a local file to load the data from.
858 898 Images from a file are always embedded.
859 899
860 900 format : unicode
861 The format of the image data (png/jpeg/jpg/gif). If a filename or URL is given
901 The format of the image data (png/jpeg/jpg/gif/webp). If a filename or URL is given
862 902 for format will be inferred from the filename extension.
863 903
864 904 embed : bool
865 905 Should the image data be embedded using a data URI (True) or be
866 906 loaded using an <img> tag. Set this to True if you want the image
867 907 to be viewable later with no internet connection in the notebook.
868 908
869 909 Default is `True`, unless the keyword argument `url` is set, then
870 910 default value is `False`.
871 911
872 912 Note that QtConsole is not able to display images if `embed` is set to `False`
873 913
874 914 width : int
875 915 Width in pixels to which to constrain the image in html
876 916
877 917 height : int
878 918 Height in pixels to which to constrain the image in html
879 919
880 920 retina : bool
881 921 Automatically set the width and height to half of the measured
882 922 width and height.
883 923 This only works for embedded images because it reads the width/height
884 924 from image data.
885 925 For non-embedded images, you can just set the desired display width
886 926 and height directly.
887 927
888 928 unconfined : bool
889 929 Set unconfined=True to disable max-width confinement of the image.
890 930
891 931 metadata : dict
892 932 Specify extra metadata to attach to the image.
893 933
894 934 alt : unicode
895 935 Alternative text for the image, for use by screen readers.
896 936
897 937 Examples
898 938 --------
899 939 embedded image data, works in qtconsole and notebook
900 940 when passed positionally, the first arg can be any of raw image data,
901 941 a URL, or a filename from which to load image data.
902 942 The result is always embedding image data for inline images.
903 943
904 944 >>> Image('https://www.google.fr/images/srpr/logo3w.png') # doctest: +SKIP
905 945 <IPython.core.display.Image object>
906 946
907 947 >>> Image('/path/to/image.jpg')
908 948 <IPython.core.display.Image object>
909 949
910 950 >>> Image(b'RAW_PNG_DATA...')
911 951 <IPython.core.display.Image object>
912 952
913 953 Specifying Image(url=...) does not embed the image data,
914 954 it only generates ``<img>`` tag with a link to the source.
915 955 This will not work in the qtconsole or offline.
916 956
917 957 >>> Image(url='https://www.google.fr/images/srpr/logo3w.png')
918 958 <IPython.core.display.Image object>
919 959
920 960 """
921 961 if isinstance(data, (Path, PurePath)):
922 962 data = str(data)
923 963
924 964 if filename is not None:
925 965 ext = self._find_ext(filename)
926 966 elif url is not None:
927 967 ext = self._find_ext(url)
928 968 elif data is None:
929 969 raise ValueError("No image data found. Expecting filename, url, or data.")
930 970 elif isinstance(data, str) and (
931 971 data.startswith('http') or _safe_exists(data)
932 972 ):
933 973 ext = self._find_ext(data)
934 974 else:
935 975 ext = None
936 976
937 977 if format is None:
938 978 if ext is not None:
939 979 if ext == u'jpg' or ext == u'jpeg':
940 980 format = self._FMT_JPEG
941 981 elif ext == u'png':
942 982 format = self._FMT_PNG
943 983 elif ext == u'gif':
944 984 format = self._FMT_GIF
985 elif ext == u'webp':
986 format = self._FMT_WEBP
945 987 else:
946 988 format = ext.lower()
947 989 elif isinstance(data, bytes):
948 990 # infer image type from image data header,
949 991 # only if format has not been specified.
950 992 if data[:2] == _JPEG:
951 993 format = self._FMT_JPEG
994 elif data[:8] == _PNG:
995 format = self._FMT_PNG
996 elif data[8:12] == _WEBP:
997 format = self._FMT_WEBP
998 elif data[:6] == self._GIF1 or data[:6] == self._GIF2:
999 format = self._FMT_GIF
952 1000
953 1001 # failed to detect format, default png
954 1002 if format is None:
955 1003 format = self._FMT_PNG
956 1004
957 1005 if format.lower() == 'jpg':
958 1006 # jpg->jpeg
959 1007 format = self._FMT_JPEG
960 1008
961 1009 self.format = format.lower()
962 1010 self.embed = embed if embed is not None else (url is None)
963 1011
964 1012 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
965 1013 raise ValueError("Cannot embed the '%s' image format" % (self.format))
966 1014 if self.embed:
967 1015 self._mimetype = self._MIMETYPES.get(self.format)
968 1016
969 1017 self.width = width
970 1018 self.height = height
971 1019 self.retina = retina
972 1020 self.unconfined = unconfined
973 1021 self.alt = alt
974 1022 super(Image, self).__init__(data=data, url=url, filename=filename,
975 1023 metadata=metadata)
976 1024
977 1025 if self.width is None and self.metadata.get('width', {}):
978 1026 self.width = metadata['width']
979 1027
980 1028 if self.height is None and self.metadata.get('height', {}):
981 1029 self.height = metadata['height']
982 1030
983 1031 if self.alt is None and self.metadata.get("alt", {}):
984 1032 self.alt = metadata["alt"]
985 1033
986 1034 if retina:
987 1035 self._retina_shape()
988 1036
989 1037
990 1038 def _retina_shape(self):
991 1039 """load pixel-doubled width and height from image data"""
992 1040 if not self.embed:
993 1041 return
994 1042 if self.format == self._FMT_PNG:
995 1043 w, h = _pngxy(self.data)
996 1044 elif self.format == self._FMT_JPEG:
997 1045 w, h = _jpegxy(self.data)
998 1046 elif self.format == self._FMT_GIF:
999 1047 w, h = _gifxy(self.data)
1000 1048 else:
1001 1049 # retina only supports png
1002 1050 return
1003 1051 self.width = w // 2
1004 1052 self.height = h // 2
1005 1053
1006 1054 def reload(self):
1007 1055 """Reload the raw data from file or URL."""
1008 1056 if self.embed:
1009 1057 super(Image,self).reload()
1010 1058 if self.retina:
1011 1059 self._retina_shape()
1012 1060
1013 1061 def _repr_html_(self):
1014 1062 if not self.embed:
1015 1063 width = height = klass = alt = ""
1016 1064 if self.width:
1017 1065 width = ' width="%d"' % self.width
1018 1066 if self.height:
1019 1067 height = ' height="%d"' % self.height
1020 1068 if self.unconfined:
1021 1069 klass = ' class="unconfined"'
1022 1070 if self.alt:
1023 1071 alt = ' alt="%s"' % html.escape(self.alt)
1024 1072 return '<img src="{url}"{width}{height}{klass}{alt}/>'.format(
1025 1073 url=self.url,
1026 1074 width=width,
1027 1075 height=height,
1028 1076 klass=klass,
1029 1077 alt=alt,
1030 1078 )
1031 1079
1032 1080 def _repr_mimebundle_(self, include=None, exclude=None):
1033 1081 """Return the image as a mimebundle
1034 1082
1035 1083 Any new mimetype support should be implemented here.
1036 1084 """
1037 1085 if self.embed:
1038 1086 mimetype = self._mimetype
1039 1087 data, metadata = self._data_and_metadata(always_both=True)
1040 1088 if metadata:
1041 1089 metadata = {mimetype: metadata}
1042 1090 return {mimetype: data}, metadata
1043 1091 else:
1044 1092 return {'text/html': self._repr_html_()}
1045 1093
1046 1094 def _data_and_metadata(self, always_both=False):
1047 1095 """shortcut for returning metadata with shape information, if defined"""
1048 1096 try:
1049 1097 b64_data = b2a_base64(self.data, newline=False).decode("ascii")
1050 1098 except TypeError as e:
1051 1099 raise FileNotFoundError(
1052 1100 "No such file or directory: '%s'" % (self.data)) from e
1053 1101 md = {}
1054 1102 if self.metadata:
1055 1103 md.update(self.metadata)
1056 1104 if self.width:
1057 1105 md['width'] = self.width
1058 1106 if self.height:
1059 1107 md['height'] = self.height
1060 1108 if self.unconfined:
1061 1109 md['unconfined'] = self.unconfined
1062 1110 if self.alt:
1063 1111 md["alt"] = self.alt
1064 1112 if md or always_both:
1065 1113 return b64_data, md
1066 1114 else:
1067 1115 return b64_data
1068 1116
1069 1117 def _repr_png_(self):
1070 1118 if self.embed and self.format == self._FMT_PNG:
1071 1119 return self._data_and_metadata()
1072 1120
1073 1121 def _repr_jpeg_(self):
1074 1122 if self.embed and self.format == self._FMT_JPEG:
1075 1123 return self._data_and_metadata()
1076 1124
1077 1125 def _find_ext(self, s):
1078 1126 base, ext = splitext(s)
1079 1127
1080 1128 if not ext:
1081 1129 return base
1082 1130
1083 1131 # `splitext` includes leading period, so we skip it
1084 1132 return ext[1:].lower()
1085 1133
1086 1134
1087 1135 class Video(DisplayObject):
1088 1136
1089 1137 def __init__(self, data=None, url=None, filename=None, embed=False,
1090 1138 mimetype=None, width=None, height=None, html_attributes="controls"):
1091 1139 """Create a video object given raw data or an URL.
1092 1140
1093 1141 When this object is returned by an input cell or passed to the
1094 1142 display function, it will result in the video being displayed
1095 1143 in the frontend.
1096 1144
1097 1145 Parameters
1098 1146 ----------
1099 1147 data : unicode, str or bytes
1100 1148 The raw video data or a URL or filename to load the data from.
1101 1149 Raw data will require passing ``embed=True``.
1102 1150
1103 1151 url : unicode
1104 1152 A URL for the video. If you specify ``url=``,
1105 1153 the image data will not be embedded.
1106 1154
1107 1155 filename : unicode
1108 1156 Path to a local file containing the video.
1109 1157 Will be interpreted as a local URL unless ``embed=True``.
1110 1158
1111 1159 embed : bool
1112 1160 Should the video be embedded using a data URI (True) or be
1113 1161 loaded using a <video> tag (False).
1114 1162
1115 1163 Since videos are large, embedding them should be avoided, if possible.
1116 1164 You must confirm embedding as your intention by passing ``embed=True``.
1117 1165
1118 1166 Local files can be displayed with URLs without embedding the content, via::
1119 1167
1120 1168 Video('./video.mp4')
1121 1169
1122 1170 mimetype : unicode
1123 1171 Specify the mimetype for embedded videos.
1124 1172 Default will be guessed from file extension, if available.
1125 1173
1126 1174 width : int
1127 1175 Width in pixels to which to constrain the video in HTML.
1128 1176 If not supplied, defaults to the width of the video.
1129 1177
1130 1178 height : int
1131 1179 Height in pixels to which to constrain the video in html.
1132 1180 If not supplied, defaults to the height of the video.
1133 1181
1134 1182 html_attributes : str
1135 1183 Attributes for the HTML ``<video>`` block.
1136 1184 Default: ``"controls"`` to get video controls.
1137 1185 Other examples: ``"controls muted"`` for muted video with controls,
1138 1186 ``"loop autoplay"`` for looping autoplaying video without controls.
1139 1187
1140 1188 Examples
1141 1189 --------
1142 1190 ::
1143 1191
1144 1192 Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
1145 1193 Video('path/to/video.mp4')
1146 1194 Video('path/to/video.mp4', embed=True)
1147 1195 Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
1148 1196 Video(b'raw-videodata', embed=True)
1149 1197 """
1150 1198 if isinstance(data, (Path, PurePath)):
1151 1199 data = str(data)
1152 1200
1153 1201 if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
1154 1202 url = data
1155 1203 data = None
1156 1204 elif data is not None and os.path.exists(data):
1157 1205 filename = data
1158 1206 data = None
1159 1207
1160 1208 if data and not embed:
1161 1209 msg = ''.join([
1162 1210 "To embed videos, you must pass embed=True ",
1163 1211 "(this may make your notebook files huge)\n",
1164 1212 "Consider passing Video(url='...')",
1165 1213 ])
1166 1214 raise ValueError(msg)
1167 1215
1168 1216 self.mimetype = mimetype
1169 1217 self.embed = embed
1170 1218 self.width = width
1171 1219 self.height = height
1172 1220 self.html_attributes = html_attributes
1173 1221 super(Video, self).__init__(data=data, url=url, filename=filename)
1174 1222
1175 1223 def _repr_html_(self):
1176 1224 width = height = ''
1177 1225 if self.width:
1178 1226 width = ' width="%d"' % self.width
1179 1227 if self.height:
1180 1228 height = ' height="%d"' % self.height
1181 1229
1182 1230 # External URLs and potentially local files are not embedded into the
1183 1231 # notebook output.
1184 1232 if not self.embed:
1185 1233 url = self.url if self.url is not None else self.filename
1186 1234 output = """<video src="{0}" {1} {2} {3}>
1187 1235 Your browser does not support the <code>video</code> element.
1188 1236 </video>""".format(url, self.html_attributes, width, height)
1189 1237 return output
1190 1238
1191 1239 # Embedded videos are base64-encoded.
1192 1240 mimetype = self.mimetype
1193 1241 if self.filename is not None:
1194 1242 if not mimetype:
1195 1243 mimetype, _ = mimetypes.guess_type(self.filename)
1196 1244
1197 1245 with open(self.filename, 'rb') as f:
1198 1246 video = f.read()
1199 1247 else:
1200 1248 video = self.data
1201 1249 if isinstance(video, str):
1202 1250 # unicode input is already b64-encoded
1203 1251 b64_video = video
1204 1252 else:
1205 1253 b64_video = b2a_base64(video, newline=False).decode("ascii").rstrip()
1206 1254
1207 1255 output = """<video {0} {1} {2}>
1208 1256 <source src="data:{3};base64,{4}" type="{3}">
1209 1257 Your browser does not support the video tag.
1210 1258 </video>""".format(self.html_attributes, width, height, mimetype, b64_video)
1211 1259 return output
1212 1260
1213 1261 def reload(self):
1214 1262 # TODO
1215 1263 pass
1216 1264
1217 1265
1218 1266 @skip_doctest
1219 1267 def set_matplotlib_formats(*formats, **kwargs):
1220 1268 """
1221 1269 .. deprecated:: 7.23
1222 1270
1223 1271 use `matplotlib_inline.backend_inline.set_matplotlib_formats()`
1224 1272
1225 1273 Select figure formats for the inline backend. Optionally pass quality for JPEG.
1226 1274
1227 1275 For example, this enables PNG and JPEG output with a JPEG quality of 90%::
1228 1276
1229 1277 In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
1230 1278
1231 1279 To set this in your config files use the following::
1232 1280
1233 1281 c.InlineBackend.figure_formats = {'png', 'jpeg'}
1234 1282 c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
1235 1283
1236 1284 Parameters
1237 1285 ----------
1238 1286 *formats : strs
1239 1287 One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
1240 1288 **kwargs
1241 1289 Keyword args will be relayed to ``figure.canvas.print_figure``.
1242 1290 """
1243 1291 warnings.warn(
1244 1292 "`set_matplotlib_formats` is deprecated since IPython 7.23, directly "
1245 1293 "use `matplotlib_inline.backend_inline.set_matplotlib_formats()`",
1246 1294 DeprecationWarning,
1247 1295 stacklevel=2,
1248 1296 )
1249 1297
1250 1298 from matplotlib_inline.backend_inline import (
1251 1299 set_matplotlib_formats as set_matplotlib_formats_orig,
1252 1300 )
1253 1301
1254 1302 set_matplotlib_formats_orig(*formats, **kwargs)
1255 1303
1256 1304 @skip_doctest
1257 1305 def set_matplotlib_close(close=True):
1258 1306 """
1259 1307 .. deprecated:: 7.23
1260 1308
1261 1309 use `matplotlib_inline.backend_inline.set_matplotlib_close()`
1262 1310
1263 1311 Set whether the inline backend closes all figures automatically or not.
1264 1312
1265 1313 By default, the inline backend used in the IPython Notebook will close all
1266 1314 matplotlib figures automatically after each cell is run. This means that
1267 1315 plots in different cells won't interfere. Sometimes, you may want to make
1268 1316 a plot in one cell and then refine it in later cells. This can be accomplished
1269 1317 by::
1270 1318
1271 1319 In [1]: set_matplotlib_close(False)
1272 1320
1273 1321 To set this in your config files use the following::
1274 1322
1275 1323 c.InlineBackend.close_figures = False
1276 1324
1277 1325 Parameters
1278 1326 ----------
1279 1327 close : bool
1280 1328 Should all matplotlib figures be automatically closed after each cell is
1281 1329 run?
1282 1330 """
1283 1331 warnings.warn(
1284 1332 "`set_matplotlib_close` is deprecated since IPython 7.23, directly "
1285 1333 "use `matplotlib_inline.backend_inline.set_matplotlib_close()`",
1286 1334 DeprecationWarning,
1287 1335 stacklevel=2,
1288 1336 )
1289 1337
1290 1338 from matplotlib_inline.backend_inline import (
1291 1339 set_matplotlib_close as set_matplotlib_close_orig,
1292 1340 )
1293 1341
1294 1342 set_matplotlib_close_orig(close)
General Comments 0
You need to be logged in to leave comments. Login now