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