{ "cells": [ { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. _tutorial:\n", "\n", "Tutorial\n", "********\n", "This tutorial will introduce you to the basics of using ``pymaid``. This is not supposed to be comprehensive but rather to give you a flavor of how things work. For inspiriation, explore the :ref:`example gallery ` and for detailed explanations have a look at the :ref:`API documentation `.\n", "\n", "Before we get started: ``pymaid`` is built on top of `navis `_ and you will note that we are using many of its functions throughout this tutorial and the examples. I highly recommend you familiarize yourself with it!" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Connecting to your Catmaid Server\n", "=================================\n", "At the beginning of each session, you have to initialise a connection to your CATMAID server. These connections are represented by :class:`~pymaid.CatmaidInstance` which holds the URL and your credentials.\n", "\n", "You will find that all functions that fetch data accept such connections as ``remote_instance`` parameters. In most situations, however, you won't use that parameter. That's because once you have initialized a connection it becomes the \"global default\" connection and ``pymaid`` will use that connection unless told otherwise. Sounds complicated but is actually straight forward - let's look at some code:\n", "\n", "First things first: import ``pymaid!``/" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import pymaid" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Now there are two ways to initialize a connection. The first one is to simply create an instance of :class:`~pymaid.CatmaidInstance` with your credentials:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO : Global CATMAID instance set. Caching is ON. (pymaid)\n" ] } ], "source": [ "rm = pymaid.CatmaidInstance(server='https://www.your.catmaid-server.org',\n", " api_token='YOURTOKEN',\n", " http_user='user', # omit if not required\n", " http_password='pw') # omit if not required" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "``HTTP_USER`` and ``HTTP_PASSWORD`` are credentials you might use before even getting to the CATMAID landing page, **not** your CATMAID credentials. If your server does not require them, just leave them blank (``''``). Instead of your CATMAID credentials, you use an API ``TOKEN``. See `here `_ how to generate yours.\n", "\n", "The above way of making a connection works perfectly fine but it can get tedious to always type in (or copy-paste) your credentials. It also makes you vulnerable to accidentally reveal your credentials when sharing code with others. Not good! \n", "\n", "Hence this second, much more convenient and much safer way of storing credentials: as environment variables. Store your credentials as ``CATMAID_SERVER_URL``, ``CATMAID_API_TOKEN``, ``CATMAID_HTTP_USER`` and ``CATMAID_HTTP_PASSWORD`` and then you can do this instead: " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO : Global CATMAID instance set. Caching is ON. (pymaid)\n" ] } ], "source": [ "rm = pymaid.connect_catmaid()" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Much easier and cleaner, isn't it? The way to set up environment variables depends a bit on your operating system and what terminal (e.g. bash or zsh) you are using. I recommend you Google it if you are unsure but here are two articles that do a decent job describing it:\n", "\n", "- `MacOS `_\n", "- `Linux `_\n", "\n", "``rm`` now holds your credentials and mediates fetching data from your server. This :class:`~pymaid.CatmaidInstance` has a few additional perks that we won't cover just yet - check out the API docs and the examples for :class:`~pymaid.CatmaidInstance`' to learn, for example, how to access different projects on your server or limit the number of parallel queries. \n", "\n", "Fetching data\n", "=============\n", "Now that we have set up the connection, we can query the server for data. The examples below are by no means comprehensive! Have a look at all the functions for :ref:`fetching data `. Most of these functions will follow a ``pymaid.get_{data}`` naming convention:" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "For starters, let's get a list of skeleton IDs from a single annotation:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[6170457, 483289, 23829, 6698696]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "skids = pymaid.get_skids_by_annotation('glomerulus DL4')\n", "skids" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Your Catmaid server will provide most data as simple list or dictionary, like the list of skeleton IDs above. Whenever this makes sense, ``pymaid`` will convert that data to a ``pandas.DataFrame``.\n", "\n", "`pandas `_ is **the** data science library for Python and will help you analyze and visualize your data. **I highly recommend familiarizing yourself with pandas!** There are plenty of good tutorials out there but pandas' own `10 Minutes to pandas `_ is a good place to start. " ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "As example, let's get the treenode table for one of the above neurons:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO : Retrieving 1 node table(s)... (pymaid)\n", "INFO : 7061 nodes retrieved. Creating table... (pymaid)\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HBox(children=(FloatProgress(value=0.0, description='Creating table', max=1.0, style=ProgressStyle(description\u2026" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
node_idparent_node_idconfidencexyzradiuscreatorlast_editedskeleton_idreviewerstags
03204804663373873550476221645345160-1vallas2020-07-15 16:55:44+00:006170457NaNNaN
16337387332048044550474821639245120-1helmickl2020-07-15 16:55:44+00:006170457NaNNaN
23204778563373858550379821800946040-1vallas2020-07-15 16:55:07+00:006170457NaNNaN
36337385832047777550382021804346080-1helmickl2020-07-15 16:55:07+00:006170457NaNNaN
43204242763373515551329822221741240-1vallas2020-07-15 16:36:29+00:006170457NaNNaN
\n", "
" ], "text/plain": [ " node_id parent_node_id confidence x y z radius creator \\\n", "0 32048046 63373873 5 504762 216453 45160 -1 vallas \n", "1 63373873 32048044 5 504748 216392 45120 -1 helmickl \n", "2 32047785 63373858 5 503798 218009 46040 -1 vallas \n", "3 63373858 32047777 5 503820 218043 46080 -1 helmickl \n", "4 32042427 63373515 5 513298 222217 41240 -1 vallas \n", "\n", " last_edited skeleton_id reviewers tags \n", "0 2020-07-15 16:55:44+00:00 6170457 NaN NaN \n", "1 2020-07-15 16:55:44+00:00 6170457 NaN NaN \n", "2 2020-07-15 16:55:07+00:00 6170457 NaN NaN \n", "3 2020-07-15 16:55:07+00:00 6170457 NaN NaN \n", "4 2020-07-15 16:36:29+00:00 6170457 NaN NaN " ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "tn_table = pymaid.get_node_table(skids[0])\n", "tn_table.head()" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Neurons\n", "=======\n", "Catmaid neurons are complex structures: they consist of (tree)nodes and connectors, have names, annotations, tags, review status and so on. We could query our Catmaid server for all these data separately and manage it using lists or dictionaries... a pain in the neck, trust me.\n", "\n", "``pymaid`` to the rescue! :class:`~pymaid.CatmaidNeuron` and their collections, :class:`~pymaid.CatmaidNeuronList`, make managing and accessing neuron data a breeze as you will see below.\n", "\n", "As of pymaid version ``2.0.0``, ``CatmaidNeuron/Lists`` are subclasses of `navis `_ ``TreeNeurons`` and ``NeuronList``, respectively. This means we can plug and play with all navis functions." ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Fetching CatmaidNeurons\n", "-----------------------\n", "\n", "There are two functions that will return ``CatmaidNeuron/Lists``: :func:`pymaid.get_neuron` and :func:`pymaid.find_neurons`. The latter is a bit more sophisticated and allows intersections of search criteria - we'll cover it elsewhere. For starters, let's get two neurons by their skeleton IDs:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HBox(children=(HTML(value='Fetch neurons'), FloatProgress(value=0.0, max=2.0), HTML(value='')))" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HBox(children=(HTML(value='Make nrn'), FloatProgress(value=0.0, max=2.0), HTML(value='')))" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "neuron_list = pymaid.get_neuron(['57311', '27295'])" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "``neuron_list`` is a :class:`~pymaid.CatmaidNeuronList` containing our two neurons. Calling it will give us a small summary:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
typenameskeleton_idn_nodesn_connectorsn_branchesn_leafscable_lengthsomaunits
0CatmaidNeuronUniglomerular mALT DA1 lPN 57312 LK5731155434632112191364103.25[3059181]1 nanometer
1CatmaidNeuronUniglomerular mALT DA1 lPN 27296 BH27295102165073043141806000.25[3005291]1 nanometer
\n", "
" ], "text/plain": [ " of 2 neurons\n", " type name skeleton_id n_nodes \\\n", "0 CatmaidNeuron Uniglomerular mALT DA1 lPN 57312 LK 57311 5543 \n", "1 CatmaidNeuron Uniglomerular mALT DA1 lPN 27296 BH 27295 10216 \n", "\n", " n_connectors n_branches n_leafs cable_length soma units \n", "0 463 211 219 1364103.25 [3059181] 1 nanometer \n", "1 507 304 314 1806000.25 [3005291] 1 nanometer " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "neuron_list" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "You can access individual :class:`~pymaid.CatmaidNeuron` within the :class:`~pymaid.CatmaidNeuronList` by various means. See example gallery for an in-depth explanation. For now, the easiest is to just index the :class:`~pymaid.CatmaidNeuronList` like you would a ``list``:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
typeCatmaidNeuron
nameUniglomerular mALT DA1 lPN 57312 LK
id57311
n_nodes5543
n_connectors463
n_branches211
n_leafs219
cable_length1.3641e+06
soma[3059181]
units1 nanometer
\n", "
" ], "text/plain": [ "type CatmaidNeuron\n", "name Uniglomerular mALT DA1 lPN 57312 LK\n", "id 57311\n", "n_nodes 5543\n", "n_connectors 463\n", "n_branches 211\n", "n_leafs 219\n", "cable_length 1.3641e+06\n", "soma [3059181]\n", "units 1 nanometer\n", "dtype: object" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "neuron_list[0]" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Accessing neuron data\n", "---------------------\n", "\n", "Treenodes, connectors, etc. are stored as *attributes* of ``CatmaidNeuron/Lists``:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
node_idparent_idcreator_idxyzradiusconfidencetype
0120497712049764438669.71875140775.484375196840.0-1.05slab
1120497812049774438669.53125140795.328125196880.0-1.05slab
286128586127925488491.90625183344.390625139640.0-1.05slab
386095086094925489096.68750170746.453125149040.0-1.05slab
486266286264925464031.31250143550.625000173320.0-1.05slab
\n", "
" ], "text/plain": [ " node_id parent_id creator_id x y z \\\n", "0 1204977 1204976 4 438669.71875 140775.484375 196840.0 \n", "1 1204978 1204977 4 438669.53125 140795.328125 196880.0 \n", "2 861285 861279 25 488491.90625 183344.390625 139640.0 \n", "3 860950 860949 25 489096.68750 170746.453125 149040.0 \n", "4 862662 862649 25 464031.31250 143550.625000 173320.0 \n", "\n", " radius confidence type \n", "0 -1.0 5 slab \n", "1 -1.0 5 slab \n", "2 -1.0 5 slab \n", "3 -1.0 5 slab \n", "4 -1.0 5 slab " ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "n = neuron_list[0]\n", "\n", "# .nodes is a pandas DataFrame and we can use .head() to show the first couple entries\n", "n.nodes.head()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
node_idconnector_idtypexyz
0120551421639790437151.25000145751.25000205040.0
1120739428840581449176.09375150388.56250216920.0
2120704428821750446111.00000152505.00000215400.0
3540060954006080340840.62500166648.68750168240.0
4540075854007570347488.00000165468.09375160680.0
\n", "
" ], "text/plain": [ " node_id connector_id type x y z\n", "0 1205514 2163979 0 437151.25000 145751.25000 205040.0\n", "1 1207394 2884058 1 449176.09375 150388.56250 216920.0\n", "2 1207044 2882175 0 446111.00000 152505.00000 215400.0\n", "3 5400609 5400608 0 340840.62500 166648.68750 168240.0\n", "4 5400758 5400757 0 347488.00000 165468.09375 160680.0" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "n.connectors.head()" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'57311'" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "n.skeleton_id" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Almost all *attributes* exist in both :class:`~pymaid.CatmaidNeuron` and :class:`~pymaid.CatmaidNeuronList`. In the latter case, you will simply get that attribute over all neurons in the ``CatmaidNeuronList``." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array(['57311', '27295'], dtype='\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
typeCatmaidNeuron
nameUniglomerular mALT DA1 lPN 57312 LK
id57311
n_nodes5543
n_connectors463
n_branches211
n_leafs219
cable_length1.3641e+06
soma[3059181]
units1 nanometer
\n", "" ], "text/plain": [ "type CatmaidNeuron\n", "name Uniglomerular mALT DA1 lPN 57312 LK\n", "id 57311\n", "n_nodes 5543\n", "n_connectors 463\n", "n_branches 211\n", "n_leafs 219\n", "cable_length 1.3641e+06\n", "soma [3059181]\n", "units 1 nanometer\n", "dtype: object" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "neuron_list[0]" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "That's because we are currently lacking that data. It will be retrieved/computed on-demand upon first **explicit** request:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HBox(children=(HTML(value='Rev. status'), FloatProgress(value=0.0, max=1.0), HTML(value='')))" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "79" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "neuron_list[0].review_status" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Now ``review_status`` is known for the first neuron but not the second:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
typenameskeleton_idn_nodesn_connectorsn_branchesn_leafscable_lengthsomaunits
0CatmaidNeuronUniglomerular mALT DA1 lPN 57312 LK5731155434632112191364103.25[3059181]1 nanometer
1CatmaidNeuronUniglomerular mALT DA1 lPN 27296 BH27295102165073043141806000.25[3005291]1 nanometer
\n", "
" ], "text/plain": [ " of 2 neurons\n", " type name skeleton_id n_nodes \\\n", "0 CatmaidNeuron Uniglomerular mALT DA1 lPN 57312 LK 57311 5543 \n", "1 CatmaidNeuron Uniglomerular mALT DA1 lPN 27296 BH 27295 10216 \n", "\n", " n_connectors n_branches n_leafs cable_length soma units \n", "0 463 211 219 1364103.25 [3059181] 1 nanometer \n", "1 507 304 314 1806000.25 [3005291] 1 nanometer " ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "neuron_list" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "*Attributes* are **not** automatically updated once they have been retrieved. You can force an update by calling the according ``get_...`` *method*:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "neuron_list[0].get_review()" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Most of these *methods* and *attributes* work on both, :class:`~pymaid.CatmaidNeuron` and :class:`~pymaid.CatmaidNeuron`:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "text/plain": [ "HBox(children=(FloatProgress(value=0.0, description='Rev. status', max=1.0, style=ProgressStyle(description_wi\u2026" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "array([79, 77])" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "neuron_list.review_status" ] }, { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "For more advanced stuff (plotting, pruning, cutting, etc.) have a look at the :ref:`example gallery `." ] } ], "metadata": { "celltoolbar": "Raw Cell Format", "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.5" } }, "nbformat": 4, "nbformat_minor": 2 }