{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Welcome to the Petabytes-3 WWT vizualization tutorial!\n", "\n", "## Nov 2019; Cambridge, MA, USA\n", "\n", "*Please note: these notebooks are preserved for posterity but not kept up-to-date, so you might run into issues with older notebooks.*\n", "\n", "Here are some helpful links:\n", "\n", "- [The tutorial landing page](https://wwt-forum.org/t/researcher-workshop-wwt-petabytes-to-science-3-boston-usa-2019-nov-8/86/2)\n", "- [The pywwt documentation](https://pywwt.readthedocs.io/)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Activity 0: Starting up pywwt\n", "\n", "The first thing to do is to open up a pywwt window in this notebook session. As is generally the case, we have to start with some Python imports:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from pywwt.jupyter import WWTJupyterWidget" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll also set up a utility function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def datapath(*args):\n", " from os.path import join\n", " return join('..', 'data', *args)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, create a widget and display it inline. (That's why the final line is a bare `wwt`.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt = WWTJupyterWidget()\n", "wwt" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "*It might look like you got a black screen but it's probably OK.* Click your mouse in the widget view and drag to either side. You should see the Milky Way scroll into view! Check with a neighbor if you're concerned that things are not working.\n", "\n", "Next, we will move the WWT view to a new window pane, which makes it much easier to interact with the visualization while writing code:\n", "\n", "1. Move your mouse *to the left side of the WWT widget area* and right-click. You should get a tall pop-up menu.\n", "2. Choose \"Create New View For Output\". A new WWT window should open up.\n", "3. Still in this notebook pane, left-click on the blue bar next to the WWT widget area. It should hide.\n", "4. If you like, click-and-drag the notebook tab that says \"Output View\" to move it to a different area of the screen. I like it at the top.\n", "\n", "![Right click and select \"Create New View for Output\"](../data/separate-pane-instructions.jpg)\n", "\n", "Stop here so we can bring it back together and introduce the first activity.\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Activity 1: Plotting and exploring tabular data\n", "\n", "In this notebook, we will visualize the table of confirmed exoplanets from the [NASA Exoplanet Archive](https://exoplanetarchive.ipac.caltech.edu/index.html) (as of 30th September 2019), and we will take a look at some of the more advanced features of tabular data visualization." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Loading the tabular data\n", "\n", "We start off by loading the table using the [astropy.table](https://docs.astropy.org/en/stable/table/) module:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from astropy.table import Table\n", "exoplanets = Table.read(datapath('planets_2019.09.30_16.25.03.votable'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's a helpful technique: take a look at the first 5 rows of the table to get a sense of what the data look like." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "exoplanets[:5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How many rows are there in total?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "len(exoplanets)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Adding a tabular data layer\n", "\n", "WWT has a model of different \"layers\" that add data to the view. Once we have an Astropy data table, adding it to the view is easy. We start off by calling `add_table_layer` to create the layer:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer = wwt.layers.add_table_layer(exoplanets)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By default the points are quite small, so we can enlarge them:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.size_scale = 50" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You should now see points appear in the widget above. Have a look around and see what the distribution of these confirmed exoplanets is on the sky. For example, you can use the following to point the view at the Kepler field:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from astropy import units as u\n", "from astropy.coordinates import SkyCoord\n", "wwt.center_on_coordinates(SkyCoord(76.32, 13.5, unit='deg', frame='galactic'), fov=20 * u.deg)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Color-coding and scaling of points by attributes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All the points are currently white, but you can also choose to color code points by one of the other attributes. For example, we can color code the points by effective temperature of the parent star:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.cmap_att = 'st_teff'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ``cmap_vmin`` and ``cmap_vmax`` properties can be used to specify which values should be used as the extremes for the colorbar:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.cmap_vmin = 5000\n", "layer.cmap_vmax = 8000" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and finally the colormap can be set using the ``cmap`` attribute, which takes either the name of a [Matplotlib colormap](https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html) or a Matplotlib colormap object:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.cmap = 'RdYlBu'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also scale the size of the points by one of the attributes — for example by the mass of the planet, and similarly we can set lower and upper values for the scaling:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.size_att = 'pl_bmassj'\n", "layer.size_vmin = 0\n", "layer.size_vmax = 30" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visualizing the data in 3D" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Luckily, the exoplanet catalog contains *Gaia* distances to most of the planetary systems, so we can now tell the layer about the third dimension:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.alt_att = 'gaia_dist'\n", "layer.alt_type = 'distance'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's an important step. WWT includes a 3D mode that lets us explore this data table in, well, 3D:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.set_view('solar system')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Start zooming out, with a two finger scroll, using your mouse wheel, or by pressing 'x'. It will take a while, but eventually you'll see 3D motion of the points from our data table. Some of the larger points dominate the view, so we can turn off the scaling by size:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.size_att = ''" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Keep on zooming out until you can see (an artist’s rendering of) the plane of the Milky Way. At this point, if your orient the view in the right 3D direction, you can see a cone of systems which corresponds to those discovered in the original Kepler field!\n", "\n", "**Note**: in Firefox, this stage can briefly slow down your browser a lot. Let WWT grind through for a minute and it will become smooth again.\n", "\n", "You may notice that some points appear and disappear as you rotate around. To avoid this effect, set the following option:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.far_side_visible = True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When this option is ``False`` (the default), points on the other side of the Sun (or the reference object being used for plotting) are hidden — this is useful when plotting on e.g. planets and the Sun, but not when looking at this kind of data.\n", "\n", "When you're done playing around with this view, reset the widget so that we can begin the next stage of the tutorial:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.set_view('sky')\n", "wwt.reset()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tabular data with a time-series axis\n", "\n", "We like to say that WWT is a \"4D\" engine because it also knows about the passage of time. In this section, we'll demonstrate this functionality using a table of GRB observations.\n", "\n", "First, we'll need some imports:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from astropy.coordinates import SkyCoord\n", "from astropy.table import Table\n", "from astropy.time import Time, TimeDelta\n", "from astropy import units as u\n", "from datetime import datetime" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we'll load up a table of GRB data. As before, we'll print out the first few rows to get a sense of the data structure. In this case it's pretty simple." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bursts = Table.read(datapath('grb_table_lite.ecsv'), format='ascii.ecsv')\n", "bursts[:5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The WWT engine doesn't know enough to know that the Julian Date values above are actually dates (and not just some random floating-point measurements), so we convert them to the ISO8601 format:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bursts['dates_ISOT'] = Time(bursts['dates_JD'], format='jd').isot\n", "bursts[:5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As before, let's create a table layer:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer = wwt.layers.add_table_layer(table=bursts, frame='Sky', lon_att='ra', lat_att='dec', size_scale=200)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's tell the engine that we want to pay attentio to the time axis of the table. **This will make all of the points disappear**, since the engine's clock is synchronized to your computer — it's plotting data for the year 2019, and all of our GRBs are much older than that." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.time_series = True\n", "layer.time_att = 'dates_ISOT'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can check the latter assertion using the `get_current_time()` method:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.get_current_time()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's demonstrate that things are actually working by centering on the first GRB and setting the time to just before it goes off." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "first_grb = bursts[0]\n", "\n", "wwt.center_on_coordinates(SkyCoord(first_grb['ra'], first_grb['dec'], unit=u.deg))\n", "wwt.set_current_time(Time(first_grb['dates_ISOT'], format='isot') - TimeDelta(3 * u.second))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You should see a white dot appear in the screen center after three seconds!\n", "\n", "The WWT timeseries support has the concept of a \"decay time\". Points that appear on the screen slowly fade over the decay timescale — according to the WWT simulation clock, that is, not wallclock time. The default is 16 days. That's a bit short for visualizing GRBs, so let's make it longer:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.time_decay = 1 * u.yr" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's speed time up to see the GRBs pop off over time. If you find the right patch of the sky (namely, the ecliptic), you might also see the Sun and planets moving around at enhanced speed." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.play_time(rate=3e6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once you've had your fill, reset the widget again:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.play_time() # this resets the clock to run at 1 second per second\n", "wwt.reset()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Stop here so that we can bring it back together, ask questions, and discuss how things went.\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Activity 2: Displaying and Manipulating Image Data\n", "\n", "In this activity, we will visualize a WISE 12µm image towards the [Westerhout 5 star forming region](https://en.wikipedia.org/wiki/Westerhout_5), and we will take a look at some of the advanced visualization options.\n", "\n", "Images, like data tables, are represented in WWT as \"layers\" that can be added to the view. With a standard FITS file, all you need to do is provide a pathname:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer = wwt.layers.add_image_layer(datapath('w5.fits'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The viewer will automatically center and zoom to the image you've loaded. You may get a warning from the `reproject` module; this can safely be ignored.\n", "\n", "\"Printing\" the following variable will create a set of widgets that let you adjust how the data are visualized:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.controls" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The image color scaling is controlled by the sliders in the \"Fine min/max\" row; the \"Coarse min/max\" boxes control the bounds that are placed on the range of those sliders.\n", "\n", "You should try sliding the image opacity back and forth to check the agreement between the morphology of the W5 image and the WWT all-sky map.\n", "\n", "All of the parameters that are controlled by the widgets above can be manipulated programmatically as well. Let's set a bunch of them at once:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layer.cmap = 'plasma'\n", "layer.vmin = 400\n", "layer.vmax = 1000\n", "layer.stretch = 'sqrt'\n", "layer.opacity = 0.9" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the settings in the widgets adjusted automatically to match what you entered. Fancy!\n", "\n", "After you're done playing around, let's reset the WWT widget as usual:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.reset()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's pause for the next segment.\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Activity 3: pywwt as Part of an Ecosystem\n", "\n", "Because pywwt is a Python module, not a standalone application, it gains a lot of power by being able to integrate with other components of the modern, Web-oriented astronomical software ecosystem.\n", "\n", "## Loading data from remote sources\n", "\n", "For instance, it is easy to use the Python module [astroquery](https://astroquery.readthedocs.io/en/latest/) to load in data directly from archive queries, without the requirement to save any files locally. Let's fetch 2MASS Ks-band images of the field of supernova 2011fe:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from astroquery.skyview import SkyView\n", "\n", "img_list = SkyView.get_images(position='SN 2011FE', survey='2MASS-K', pixels=500) # you can adjust the size if you want\n", "assert len(img_list) == 1 # there's only one matching item in this example\n", "twomass_img = img_list[0]\n", "twomass_img.info()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can import it into WWT in the usual way:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "twomass_layer = wwt.layers.add_image_layer(twomass_img)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once again you should see the view automatically center on your image. Let's adjust the background imagery to be more relevant:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.background = wwt.imagery.ir.twomass\n", "wwt.foreground_opacity = 0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We also provide interactive controls to let you adjust the parameters of the contextual imagery that's being shown. Try choosing different sets of all-sky imagery and adjusting the blend between them:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.layer_controls" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here are some settings that we like:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.background = wwt.imagery.visible.sdss\n", "wwt.foreground = wwt.imagery.gamma.fermi\n", "wwt.foreground_opacity = .7" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we'll load up another image of the same field that came from *Swift*, this time stored as a local file as in our previous activity:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swift_layer = wwt.layers.add_image_layer(datapath('m101_swiftx.fits'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Create controls to adjust all of the visualization parameters. If you want to go wild, you can overlay data from four different wavelengths in this one view!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt.layer_controls" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "twomass_layer.controls" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "swift_layer.controls" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Exporting to standalone HTML\n", "\n", "Pywwt, like all Jupyter widgets, is at its heart web-based. We've been working on building the technology to extract the visualizations that you create from Jupyter into standalone web assets that can be included anywhere that you can host HTML + JavaScript + CSS. In particular, we aim to support these assets as [interactive figures](https://journals.aas.org/graphics-guide/#interactive_figures) in the AAS journals.\n", "\n", "Exporting everything is easy, but due to an unfortunate bug in our JupyterLab support, it doesn't work reliably when you separate out the WWT window as have done in this tutorial. So, let's create a secondary WWT window:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt2 = WWTJupyterWidget()\n", "wwt2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For what it's worth, this does demonstrate that you can create multiple WWT widgets in the same notebook!\n", "\n", "Now, let's repopulate it with our images:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "twomass_layer2 = wwt2.layers.add_image_layer(twomass_img)\n", "swift_layer2 = wwt2.layers.add_image_layer(datapath('m101_swiftx.fits'), opacity=0.5, cmap='plasma')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You could tweak the appearance here by inserting cells to create control widgets with `wwt2.layer_controls`, `twomass_layer2.controls`, and `swift_layer2.controls`.\n", "\n", "Once we've jumped through this hoop, exporting the HTML is this easy:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "wwt2.save_as_html_bundle('figure.zip')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You should see a file named `figure.zip` appear in the folder view sidebar to the left of this notebook. (It might take a few seconds to appear.) You can right-click that file and select \"Download\" to save the Zip bundle to your local machine.\n", "\n", "You might want to open up this Zip file and inspect its contents. You will see that it contains an `index.html` file, some JavaScript, and a `data` directory containing two FITS files. These correspond to the images that you just loaded into your view.\n", "\n", "If you have [Node.js](https://nodejs.org/en/) installed on your computer, you can view your exported HTML in a straightforward way:\n", "\n", "1. Unpack the Zip file\n", "2. In a terminal, `cd` to the unpacked directory (the one containing `index.html`).\n", "3. Run: `npx httpserver 12345`\n", "4. Open a browser to the URL printed by the program.\n", "\n", "**Note**: alas, we *also* have a race condition bug that sometimes pops up here. You might need to reload the page for the images to appear.\n", "\n", "Other one-liner web servers might work, but not all of them serve file with the correct `Content-Type` headers, which is needed for everything to work here.\n", "\n", "Let's pause here again.\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Activity 4: Free Exploration!\n", "\n", "We will end with some time for free exploration! Here are some pointers that we hope are useful.\n", "\n", "## Have your own data?\n", "\n", "If you have your own data that you would like to explore, you can upload the files to the BinderHub server so that they can be accessed by the cloud-based notebooks. Just click the underlined-up-arrow icon in the Folder panel to the left of this notebook.\n", "\n", "## Don't have your own data?\n", "\n", "If not, take a look at the [Astroquery Gallery of Queries](https://astroquery.readthedocs.io/en/latest/gallery.html) for some queries that you can get started with.\n", "\n", "## Documentation\n", "\n", "Some of this documentation might be helpful:\n", "\n", "- [pywwt](https://pywwt.readthedocs.io/)\n", "- [astroquery](https://astroquery.readthedocs.io/)\n", "- [astropy](https://docs.astropy.org/en/stable/)\n", "\n", "## Data too big?\n", "\n", "We've used the cloud-based notebooks for convenience, but installing pywwt locally is not that hard. The recommended mechanism is through Anaconda. In a temporary environment, run:\n", "\n", "```\n", "conda install -c conda-forge pywwt\n", "```\n", "\n", "In this case it may be easier to use plain Jupyter notebooks, rather than Jupyter Lab, since setting up the Jupyter Lab widget system can sometimes be finicky.\n", "\n", "---" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# What's Next?\n", "\n", "Want to get more involved in the AAS WorldWide Telescope community? Here are some next steps you can take:\n", "\n", "- Sign up for our mailing list: \n", "- Join our discussion forum: \n", "- Visit the WWT Contributor Hub: \n", "- Follow us on social media:\n", " - [@wwtelescope](https://twitter.com/wwtelescope) on Twitter\n", " - [AASWorldWideTelescope](http://www.youtube.com/c/AASWorldWideTelescope) on YouTube\n", " - [@wwtelescope](http://www.youtube.com/c/AASWorldWideTelescope) on Facebook\n", "- We have [other bonus links on the tutorial thread](https://wwt-forum.org/t/researcher-workshop-wwt-adass29-groningen-netherlands-2019-october-6/70/3)\n", "\n", "We'll have plenty of activities at [AAS 235](http://www.youtube.com/c/AASWorldWideTelescope) if you'll be in Hawai'i in January!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Credits\n", "\n", "The examples in this notebook were prepared by:\n", "\n", "- O. Justin Otor\n", "- Thomas Robitaille\n", "- Peter K. G. Williams\n", "\n", "With thanks to Mark SubbaRao for creating the first version of the GRB example notebook." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.6" } }, "nbformat": 4, "nbformat_minor": 4 }