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