display.py 42 KB

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