{ "cells": [ { "cell_type": "markdown", "metadata": { "colab_type": "text", "execution": {}, "id": "view-in-github" }, "source": [ "\"Open   \"Open" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "# Bonus Tutorial: Deploying Neural Networks on the Web\n", "\n", "**By Neuromatch Academy**\n", "\n", "__Content creators:__ Sam Ray, Vladimir Haltakov, Konrad Kording\n", "\n", "__Production editors:__ Spiros Chavlis" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Tutorial Objectives\n", "\n", "In this tutorial, you will learn the basics of how to deploy your deep learning models as web applications using some modern frameworks and libraries. In this tutorial, you will learn to:\n", "\n", " - Serve web pages with Flask\n", " - Apply the MVVM design pattern to write maintainable code\n", " - Create an interactive UI for your service\n", " - Deploy your deep learning models as a REST API\n", " - Deploying your application on Heroku" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @markdown\n", "from IPython.display import IFrame\n", "from ipywidgets import widgets\n", "out = widgets.Output()\n", "with out:\n", " print(f\"If you want to download the slides: https://osf.io/download/p6wty/\")\n", " display(IFrame(src=f\"https://mfr.ca-1.osf.io/render?url=https://osf.io/p6wty/?direct%26mode=render%26action=download%26mode=render\", width=730, height=410))\n", "display(out)" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Setup\n", "\n", "Run the following cells to install and include important dependencies." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install dependencies\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Install dependencies\n", "!pip install --upgrade jupyter-client --quiet\n", "!pip install Flask-RESTful flasgger pyngrok --quiet" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Install and import feedback gadget\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Install and import feedback gadget\n", "\n", "!pip3 install vibecheck datatops --quiet\n", "\n", "from vibecheck import DatatopsContentReviewContainer\n", "def content_review(notebook_section: str):\n", " return DatatopsContentReviewContainer(\n", " \"\", # No text prompt\n", " notebook_section,\n", " {\n", " \"url\": \"https://pmyvdlilci.execute-api.us-east-1.amazonaws.com/klab\",\n", " \"name\": \"neuromatch_dl\",\n", " \"user_key\": \"f379rz8y\",\n", " },\n", " ).render()\n", "\n", "\n", "feedback_prefix = \"Bonus_DeplooyModels\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Modified functions from the `flask-ngrok` package which doesn't work with the latest version of `ngrok`\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Modified functions from the `flask-ngrok` package which doesn't work with the latest version of `ngrok`\n", "import time\n", "import json\n", "import atexit\n", "import requests\n", "import subprocess\n", "from threading import Timer\n", "\n", "\n", "def _run_ngrok(port):\n", " ngrok = subprocess.Popen([\"ngrok\", 'http', str(port)])\n", " atexit.register(ngrok.terminate)\n", " localhost_url = \"http://localhost:4040/api/tunnels\" # Url with tunnel details\n", " time.sleep(1)\n", " tunnel_url = requests.get(localhost_url).text # Get the tunnel information\n", " j = json.loads(tunnel_url)\n", "\n", " tunnel_url = j['tunnels'][0]['public_url'] # Do the parsing of the get\n", " return tunnel_url\n", "\n", "\n", "def start_ngrok(port):\n", " ngrok_address = _run_ngrok(port)\n", " print(f\" * Running on {ngrok_address}\")\n", " print(f\" * Traffic stats available on http://127.0.0.1:4040\")\n", "\n", "def run_with_ngrok(app):\n", " \"\"\"\n", " The provided Flask app will be securely exposed to the public internet\n", " via ngrok when run, and the its ngrok address will be printed to stdout\n", " :param app: a Flask application object\n", " :return: None\n", " \"\"\"\n", " old_run = app.run\n", "\n", " def new_run(*args, **kwargs):\n", " port = kwargs.get('port', 5000)\n", " thread = Timer(1, start_ngrok, args=(port,))\n", " thread.setDaemon(True)\n", " thread.start()\n", " old_run(*args, **kwargs)\n", " app.run = new_run" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Imports\n", "import io\n", "import platform\n", "from PIL import Image\n", "from urllib.request import urlopen\n", "\n", "import flasgger\n", "from flask_restful import Api\n", "from flask_restful import Resource, fields, marshal\n", "from flask import Flask, render_template_string, request, redirect\n", "\n", "import torch\n", "from torchvision import models\n", "import torchvision.transforms as transforms" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Setup ngrok\n", "\n", "In order to be able to access the web app running in the notebook, we need to use a service called `ngrok`. Since recently, `ngrok` requires the user to register for a free account and setup an authentication token. Follow the steps below to set it up.\n", "\n", "1. Go to [ngrok.com](https://ngrok.com/) and create a free account. Do not forget to verify your e-mail address right after!\n", "2. Go to [dashboard.ngrok.com/get-started/your-authtoken](https://dashboard.ngrok.com/get-started/your-authtoken) and copy your authtoken.\n", "3. Paste it in the cell below by replacing `YOUR_NGROK_AUTHTOKEN`, uncomment the last line and run it.\n", "\n", "You should see this output:\n", "\n", "```python\n", "Authtoken saved to configuration file: /root/.ngrok2/ngrok.yml\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Paste your ngrok authtoken below and run the cell\n", "\n", "## Uncomment the line below\n", "# !ngrok authtoken YOUR_NGROK_AUTHTOKEN" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "**Note:** If you want to delete at some point your account, visit this page [here](https://dashboard.ngrok.com/user/settings) and at the bottom of the page click on `Delete User`." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Section 1: Introduction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Video 1: Deploying Neural Networks on the Web\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 1: Deploying Neural Networks on the Web\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'yQtPGtz4jDI'), ('Bilibili', 'BV1754y1E7Qf')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Deploying_Neural_Networks_on_the_Web_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "We will start by building a simple web application in Flask, which we'll keep extending throughout the tutorial. In the end, you will have a web app where you can upload an image and have it classified automatically by a neural network model." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Section 2: Flask" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Flask is a web application micro-framework built with Python. Flask is popular because it's lightweight, easy to use, scalable, and has tons of great extensions. Nowadays, Flask is used for many different applications like web applications, REST APIs, socket-based services, and by companies like LinkedIn or Pinterest.\n", "\n", "In this section, you will learn to create simple Flask websites." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 2.1: Your First Flask App\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 2: Flask\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 2: Flask\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'uVqu-9IBIRg'), ('Bilibili', 'BV1sA411P7Rq')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Flask_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Creating a minimal Flask app is very simple. You need to create a `Flask` object and define the handler for the root URL returning the HTML response. You need to provide the applications module or package, but we can use `__name__` as a convenient shortcut.\n", "\n", "We need one small trick because the app will be running in a notebook. If you just run the app, it will be accessible at `http://127.0.0.1:5000`. The problem is that this is a local address to the server where the notebook is running, so you can't access it. This is where `ngrok` helps - it creates a tunnel from the notebook server to the outside world. Make sure you use the ngrok URL when testing your app.\n", "\n", "Uncommenting the `app.run()` command below, you will see this output:\n", "\n", "```\n", " * Serving Flask app \"__main__\" (lazy loading)\n", " * Environment: production\n", " WARNING: This is a development server. Do not use it in a production deployment.\n", " Use a production WSGI server instead.\n", " * Debug mode: off\n", " * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n", " * Running on https://XXX-XX-XX-XXX-XXX.ngrok.io\n", " * Traffic stats available on http://127.0.0.1:4040\n", "```\n", "\n", "The URL you have to visit is: `https://XXX-XX-XX-XXX-XXX.ngrok.io`\n", "\n", "
\n", "\n", "> **Note:** the call to `app.run()` will not return on its own. Make sure to stop the running cell when you want to move to the next one. You can do this by either clicking on the cell run/stop button or through _settings>Interrupt runtime_ or Ctrl+M+I.\n", "\n", "> **Warning:** if the \"visit site\" option is not working, check the URL. `ngrok` redirects some links from `https` to `http`, so you have to manually make the change!" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Create a Flask app object\n", "app = Flask(__name__)\n", "\n", "# Define a function to be called when the user accesses the root URL (/)\n", "# Handler\n", "@app.route(\"/\")\n", "def home():\n", " # Return a very simple HTML response\n", " return \"

Welcome to Neuromatch

\"\n", "\n", "\n", "# You need ngrok to expose the Flask app outside of the notebook\n", "run_with_ngrok(app)\n", "\n", "## Uncomment below to run the app\n", "# app.run()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 2.2: Using Jinja2 Templates" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 3: Jinja Templates\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 3: Jinja Templates\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'u25FfNIAKsg'), ('Bilibili', 'BV1Mb4y167eg')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Jinja_Templates_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "The default template engine used by Flask is Jinja2. Jinja2 offers features that help you write clean and reusable templates such as inheritance, humanizing, and formatting data (there's an extension for this), dividing components into sub-modules, etc.\n", "\n", "In this section, we are going to add Jinja2 templates to the app. WIth Jinja2 you can use variables and control flow commands, like ifs and loops in your HTML code. Then you can pass data from your Python code to the template when it is rendered.\n", "\n", "Let's first define the template of a simple web page showing some platform properties. We are going to loop over the `platform` dictionary containing property keys and their corresponding values:\n", "```\n", "{% for key, value in platform.items() %}\n", "```\n", "One row of the HTML table is created for every element of the dictionary. You can display the content of the variables like that: `{{ value }}`" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Jinja2 HTML template\n", "template_str = '''\n", "\n", " \n", "
\n", "

Platform Info

\n", " \n", " \n", " \n", " \n", " \n", "\n", " {% for key, value in platform.items() %}\n", " \n", " \n", " \n", " \n", " {% endfor %}\n", "\n", "
PropertyValue
{{ key }}{{ value }}
\n", "
\n", " \n", "\n", "'''" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "You can then render the template passing the platform properties that you can retrieve using the `platform` package." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "app = Flask(__name__)\n", "\n", "@app.route(\"/\")\n", "def home():\n", " # Get the platform properties as a dict\n", " properties = platform.uname()._asdict()\n", "\n", " # Render the Jinja2 template\n", " return render_template_string(template_str, platform=properties)\n", "\n", "\n", "run_with_ngrok(app)\n", "\n", "## Uncomment below to run the app\n", "# app.run()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 2.3: Apply the MVVM Design Pattern" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 4: Using the MVVM Design Pattern\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 4: Using the MVVM Design Pattern\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', '13bFN4L6c9I'), ('Bilibili', 'BV1YA411P766')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Using_the_MVVM_Design_Pattern_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Design patterns provide a way of writing reusable, adaptable, and extendable code. Design patterns are not libraries, but rather a set of best practices to follow when designing your software.\n", "\n", "Model View View-Model (MVVM) is a powerful design pattern commonly used in web applications (and other GUI applications).\n", "* **View** - this is that part of your code the user interacts with (the web page)\n", "* **Model** - this is the representation of the data that you want to interact with.\n", "* **View-Model** - this is the part that handles the application state and that transforms the data from the Model to a representation suitable for display and back.\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Let's implement the MVVM pattern in Flask. You will first create classes for each of the 3 parts of the MVVM pattern used to display information about 2D points." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "class PointModel:\n", " \"\"\"\n", " Simple point Model storing a 2D point\n", " \"\"\"\n", "\n", " # Initialize a 2D point\n", " def __init__(self, x, y):\n", " self.x = x\n", " self.y = y\n", "\n", "\n", "class PointView(Resource):\n", " \"\"\"\n", " Simple View displaying the information about a 2D point\n", " \"\"\"\n", "\n", " def get(self):\n", " point = PointViewModel.get_sample_data()\n", " return f\"Point: (x={point.x}, y={point.y})\"\n", "\n", "\n", "class PointViewModel:\n", " \"\"\"\n", " ViewModel - Simple ViewModel retrieving the data and passing it to the view\n", " \"\"\"\n", "\n", " # Create some sample data\n", " @classmethod\n", " def get_sample_data(cls):\n", " return PointModel(2, 5)\n", "\n", " # Register a handler for \"/\" in the API calling the PointView\n", " def setup(self, api):\n", " api.add_resource(PointView, '/')" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "You can now create your Flask app and use the `Api` object where you can register your ViewModel." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Create a Flask app\n", "app = Flask(__name__)\n", "\n", "# Create an Api object where different ViewModels can be registered\n", "api = Api(app)\n", "\n", "# Create a PointViewModel and register it to the API\n", "pvm = PointViewModel()\n", "pvm.setup(api)\n", "\n", "# Run with ngrok\n", "run_with_ngrok(app)\n", "\n", "## Uncomment below to run the app\n", "# app.run()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 2.4: Creating a REST API\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 5: REST API\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 5: REST API\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'cIjaEE6tKpk'), ('Bilibili', 'BV1A64y1z74c')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_REST_API_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "REST (Representational State Transfer) is a set of rules according to which APIs are designed to enable your service to interact with other services. If HTML pages are interfaces designed for humans, you can think about REST APIs as interfaces made for computers.\n", "\n", "A common way to implement a REST API is for your application to respond to certain requests by returning a JSON string containing the required data.\n", "\n", "Let's now create a new View and ViewModel that will provide the platform properties in JSON format." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "class PlatformView(Resource):\n", " \"\"\"\n", " New view displaying some Platform Properties\n", " \"\"\"\n", "\n", " def get(self):\n", " \"\"\"\n", " This examples uses PlatformView Resource\n", " It works also with swag_from, schemas and spec_dict\n", " ---\n", " responses:\n", " 200:\n", " description: A single Machine item\n", " schema:\n", " id: Machine\n", " properties:\n", " machine:\n", " type: string\n", " description: The type of the processor\n", " default: None\n", " node:\n", " type: string\n", " description: The name of the current virtual machine\n", " default: None\n", " processor:\n", " type: string\n", " description: The type of the processor arch\n", " default: None\n", " system:\n", " type: string\n", " description: The name of the user\n", " default: None\n", " \"\"\"\n", "\n", " # Specification of the returned data\n", " resource_fields = {\n", " 'system': fields.String,\n", " 'machine': fields.String,\n", " 'processor': fields.String,\n", " 'node': fields.String\n", " }\n", "\n", " # Serialize the data according to the specification\n", " return marshal(platform.uname()._asdict(), resource_fields)\n", "\n", "\n", "class PlatformViewModel:\n", " \"\"\"\n", " A simple ViewModel that displays the PlatformView at /platform\n", " \"\"\"\n", "\n", " def setup(self, api):\n", " api.add_resource(PlatformView, '/platform')" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Note the documentation in the `get` method. We can use the `flasgger` package to automatically create documentation of your REST API. You can access it at `/apidocs`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Create the Flask app and register the ViewModel\n", "app = Flask(__name__)\n", "api = Api(app)\n", "pvm = PlatformViewModel()\n", "pvm.setup(api)\n", "\n", "# Redirect / to /platform for convenience\n", "@app.route('/')\n", "def redirect_platform():\n", " return redirect(\"/platform\", code=302)\n", "\n", "\n", "# Register Swagger to create API documentation at /apidocs\n", "swg = flasgger.Swagger(app)\n", "\n", "# Run the app\n", "run_with_ngrok(app)\n", "\n", "## Uncomment below to run the app\n", "# app.run()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Section 3: Vue.js" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Video 6: Vue.js\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 6: Vue.js\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'PD6l9pkjw-c'), ('Bilibili', 'BV1Yv411K7GS')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Vue.js_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "We already talked about the MVVM pattern and implemented it using a back end framework - Flask. Applying the same pattern on the front end can also be beneficial when creating dynamic applications.\n", "\n", "Vue.js is a great front end library that implements the MVVM design pattern. It is widely used for creating user interfaces and single-page applications.\n", "\n", "In this section, you will learn how to implement a simple Vue.js front end that fetches data from the platform REST API we created in the previous section." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 3.1: Defining the Vue Template\n", "\n", "We define our HTML template similarly to Jinja. The big difference is that the Vue template is rendered dynamically after the page is loaded, while the Jinja templates are rendered in the backend before the page is served.\n", "\n", "Using variables is very similar to Jinja, but now you also have some JavaScript code handling the state of the application and the binding to the data. You can use the `axois` package to fetch data from our platform REST API, when Vue is initialized (mounted)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "vue_template = \"\"\"\n", "\n", " \n", " \n", " \n", " \n", " \n", "\n", "
\n", " \n", "
\n", "\n", " \n", "\n", " \n", "\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 3.2: Serving the Vue.js App\n", "\n", "You can now again run your Flask app and serve the Vue template from the root URL. We are still using Flask to implement our logic on the backend and provide the platform REST API.\n", "\n", "This way of doing things may seem more complicated, but it has the advantage that it is dynamic. In the next section, we will add some dynamic functionality." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Create the Flask app with the previously defined platform API\n", "app = Flask(__name__)\n", "api = Api(app)\n", "# Serve the Platform REST API\n", "pvm = PlatformViewModel()\n", "pvm.setup(api)\n", "swg = flasgger.Swagger(app)\n", "\n", "# Serve the Vue template page at /\n", "@app.route(\"/\")\n", "def home():\n", " return vue_template\n", "\n", "\n", "# Run the app\n", "run_with_ngrok(app)\n", "\n", "## Uncomment below to run the app\n", "# app.run()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Section 4: Model Presentation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Video 7: Deploying a PyTorch model\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 7: Deploying a PyTorch model\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', '6UCLk37XWDs'), ('Bilibili', 'BV1Zb4y1z7aT')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Deploying_a_PyTorch_model_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Now (finally) we have all the tools we need to deploy our neural network! We are going to use a pre-trained DenseNet mode. In the first step, we are going to create an API entry point that accepts an image as input and classifies it. After that, we will create a dynamic UI for easier interaction." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 4.1: Image Classification API" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 8: Classification with a Pre-trained Model\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 8: Classification with a Pre-trained Model\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'UGByJ-_0whk'), ('Bilibili', 'BV1Dq4y1n7ks')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Classification_with_a_Pretrained_Model_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "First, we need to load a pre-trained DenseNet trained on ImageNet. You can use `torchvision.models` to quickly get a pre-trained model for many popular neural network architectures." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Load a pre-trainied DenseNet model from torchvision.models\n", "model = models.densenet121(pretrained=True)\n", "\n", "# Switch the model to evaluation mode\n", "model.eval()\n", "\n", "# Load the class labels from a file\n", "class_labels_url = \"https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt\"\n", "class_labels = urlopen(class_labels_url).read().decode(\"utf-8\").split(\"\\n\")\n", "\n", "# Define the transformation of the input image\n", "transform = transforms.Compose([\n", " transforms.Resize(256),\n", " transforms.CenterCrop(224),\n", " transforms.ToTensor(),\n", " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n", "])" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Define a function to predict the most likely class using the model. Note that we need to pass a batch of images to the model. Since we have only 1, we can just use `unsqueeze(0)` to add an additional dimension." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "def predict(model, transform, image, class_labels):\n", " # Transform the image and convert it to a tensor\n", " image_tensor = transform(image).unsqueeze(0)\n", "\n", " # Pass the image through the model\n", " with torch.no_grad():\n", " output = model(image_tensor)\n", "\n", " # Select the class with the higherst probability and look up the name\n", " class_id = torch.argmax(output).item()\n", " class_name = class_labels[class_id]\n", "\n", " # Return the class name\n", " return class_name" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Let's test the `predict` function using an image of a [dog](https://unsplash.com/photos/2l0CWTpcChI/download?force=true&w=640)." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "# Load and display the image\n", "dog_image = Image.open(io.BytesIO(urlopen(\"https://unsplash.com/photos/2l0CWTpcChI/download?force=true&w=480\").read()))\n", "display(dog_image)\n", "\n", "# Classify the image\n", "display(predict(model, transform, dog_image, class_labels))" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 4.2: Create a Dynamic Application" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 9: Create a Dynamic Application\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 9: Create a Dynamic Application\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'DJsK2bc9wuk'), ('Bilibili', 'BV1Vy4y1L7V9')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Create_a_Dynamic_Application_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "We will now create a Flask app that receives an image at `/predict` and passes it through the model. We will also implement an interactive UI to upload the image and call the API.\n", "\n", "The UI consists of a file upload field, a classify button, and an image displaying the uploaded file." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "index_template = \"\"\"\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", "
\n", "
\n", "\n", " \n", " \n", "\n", "\"\"\"" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "The application has two entry points:\n", "\n", "* `/` - serve the Vue template with the interactive UI\n", "* `/predict` - a REST API classifying the image received as input" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "execution": {} }, "outputs": [], "source": [ "app = Flask(__name__)\n", "\n", "# Serve the Vue template with the interactive UI\n", "@app.route(\"/\")\n", "def home():\n", " return index_template\n", "\n", "\n", "# Classification API\n", "@app.route(\"/predict\", methods=['POST'])\n", "def predict_api():\n", " # Fetch the image from the request and convert it to a Pillow image\n", " image_file = request.files['file']\n", " image_bytes = image_file.read()\n", " image = Image.open(io.BytesIO(image_bytes))\n", "\n", " # Predict the class from the image\n", " class_name = predict(model, transform, image, class_labels)\n", "\n", " # Return the result\n", " return class_name\n", "\n", "\n", "# Run the app\n", "run_with_ngrok(app)\n", "\n", "## Uncomment below to run the app\n", "# app.run()" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Section 5: Deploy a Flask app on Heroku" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Video 10: Deploy on Heroku\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 10: Deploy on Heroku\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'kaf6z-tAxCY'), ('Bilibili', 'BV1oo4y1S77Z')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Deploy_on_Heroku_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Now you are going to deploy your application as a real web server outside of the notebook. We are going to use Heroku for this. Heroku is a PaaS (Platform-as-a-Service) that offers pre-configured environments so you can deploy an application easily and quickly. They also offer a free tier which is enough for deploying simple apps.\n", "\n", "But first, you need to test your application locally." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 5.1: Preparing Your Environment\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 11: Prepare Python Environment\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 11: Prepare Python Environment\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'IMd_sRm4fJM'), ('Bilibili', 'BV1bv411K7dP')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Prepare_Python_Environment_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "You need to do all the steps from here on on your own machine and not in the notebook. You need to make sure that you have Python 3 installed and some code editor (for example VS Code). You will also be using the terminal a lot in this section.\n", "\n", "First, you need to prepare your Python environment and install all required dependencies. You should first create an empty folder where you will store your application and do the following steps.\n", "\n", "**1. Create a new virtual environment**\n", "\n", "Run the following code in the terminal to create a new Python virtual environment:\n", "```\n", "python -m venv .venv\n", "```\n", "\n", "**2. Activate the virtual environment**\n", "\n", "Now, you need to activate the environment, which is a bit different on Linux/macOS and Windows.\n", "\n", "For Linux and macOS:\n", "```\n", "source .venv/bin/activate\n", "```\n", "\n", "For Windows:\n", "```\n", ".venv\\Scripts\\activate.bat\n", "```\n", "\n", "\n", "**3. Install dependencies**\n", "\n", "You need to install some packages that you will need using `pip`:\n", "```\n", "pip install flask Pillow gunicorn\n", "```\n", "\n", "> Note: the package `gunicorn` is a web server that is needed later when your code runs on Heroku.\n", "\n", "\n", "**4. Install PyTorch**\n", "\n", "Depending on your system, there are different ways to install `torch` and `torchvision`. Refer to the [Installation page](https://pytorch.org/get-started/locally/) for the exact command. We recommend using `pip`.\n", "\n", "On macOS and Windows for example this is straightforward:\n", "```\n", "pip install torch torchvision\n", "```\n", "\n", "> Note: avoid installing `torchaudio` since it is not needed, but may cause problems with the package size when deploying on Heroku later.\n" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 5.2: Create Your Application" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 12: Creating a Local Application\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 12: Creating a Local Application\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'fF1fmIXz5NQ'), ('Bilibili', 'BV1bM4y157xK')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Creating_a_Local_Application_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "You are now ready to create the files needed for your application. For now, you need just 2 files.\n", "\n", "**`app.py`**\n", "\n", "This is the main file of your application. Inside, you will put the code for running your PyTorch model as well as the code of your Flask application.\n", "\n", "\n", "```python\n", "import os\n", "import io\n", "import torch\n", "from urllib.request import urlopen\n", "from PIL import Image\n", "from torchvision import models\n", "import torchvision.transforms as transforms\n", "from flask import Flask, request, send_from_directory\n", "\n", "# Load a pre-trainied DenseNet model from torchvision.models\n", "model = models.densenet121(pretrained=True)\n", "model.eval()\n", "\n", "# Load the class labels from a file\n", "class_labels_url = (\n", " \"https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt\"\n", ")\n", "class_labels = urlopen(class_labels_url).read().decode(\"utf-8\").split(\"\\n\")\n", "\n", "# Define the transofrmation of the input image\n", "transform = transforms.Compose([\n", " transforms.Resize(256),\n", " transforms.CenterCrop(224),\n", " transforms.ToTensor(),\n", " transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n", "])\n", "\n", "def predict(model, transform, image, class_labels):\n", " # Transform the image and convert it to a tensor\n", " image_tensor = transform(image).unsqueeze(0)\n", "\n", " # Pass the image through the model\n", " with torch.no_grad():\n", " output = model(image_tensor)\n", "\n", " # Select the class with the higherst probability\n", " class_id = torch.argmax(output).item()\n", " class_name = class_labels[class_id]\n", " return class_name\n", "\n", "\n", "app = Flask(__name__)\n", "\n", "@app.route(\"/\")\n", "def home():\n", " return send_from_directory(\"static\", \"index.html\")\n", "\n", "\n", "@app.route(\"/predict\", methods=[\"POST\"])\n", "def predict_api():\n", " # Fetch the image from the request and convert it\n", " image_file = request.files[\"file\"]\n", " image_bytes = image_file.read()\n", " image = Image.open(io.BytesIO(image_bytes))\n", "\n", " # Predict the class from the image\n", " class_name = predict(model, transform, image, class_labels)\n", "\n", " # Write result as JSON\n", " return class_name\n", "\n", "\n", "# Run the app\n", "if __name__ == \"__main__\":\n", " app.run(debug=False, threaded=True, port=os.getenv(\"PORT\", 5000))\n", "```" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "**static/index.html**\n", "\n", "This file should contain the HTML code of your Vue template - exactly as it is.\n", "\n", "```html\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
\n", "
\n", "
\n", "\n", " \n", " \n", "\n", "```" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 5.3: Testing Your Application Locally" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "You are now ready to test your application. Once you have your environment set up correctly and your application files created you can just start it:\n", "\n", "```\n", "python app.py\n", "```\n", "\n", "You can now access your application at [http://127.0.0.1:5000](http://127.0.0.1:5000). No `ngrok` needed anymore!" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 5.4: Preparing for Deployment on Heroku" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 13: Preparing for Heroku\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 13: Preparing for Heroku\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'L2W0C7nMttI'), ('Bilibili', 'BV1Nq4y1Q71H')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Preparing_for_Heroku_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "Before we can deploy on Heroku there are a couple of things we need to prepare.\n", "\n", "**Create `Procfile`**\n", "\n", "Every application running on Heroku needs a `Procfile` where you need to specify how the app should be run. In our case, it is quite easy, because we can use `gunicorn` as a web server. Create a file named `Procfile` in the root folder of your application and put the following code inside:\n", "```\n", "web: gunicorn app:app\n", "```\n", "\n", "**Create `requirements.txt`**\n", "\n", "We also need to tell Heroku which Python packages need to be installed. Heroku uses the standard way of defining Python dependencies - a `requirements.txt` file. You can create it with the following command:\n", "```\n", "pip freeze > requirements.txt\n", "```\n", "\n", "**Fix the `torch` version**\n", "\n", "Now, this should be enough in theory, but we need one small change. The problem is that by default `torch` comes with both the CPU and GPU code, which creates a package that exceeds the maximum size limit on the Heroku free tier. Therefore we need to make sure that we only specify the CPU version of `torch`.\n", "\n", "You need to open the `requirements.txt` file and modify it as follows:\n", "1. Add the following line in the beginning, telling Heroku where to look for the packages `-f https://download.pytorch.org/whl/torch_stable.html\n", "`\n", "2. Find the line defining the torch dependency and change it to `torch==1.9.0+cpu`\n", "3. Do the same with `torchvision` by changing it to `torchvision==0.10.0+cpu\n", "`" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "## Section 5.5: Deploying on Heroku" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Video 14: Deploying on Heroku\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 14: Deploying on Heroku\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'Ni9YKotZUQk'), ('Bilibili', 'BV1P64y1z7cU')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Deploying_on_Heroku_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "You are now finally ready to deploy to Heroku! There are just a couple of steps needed.\n", "\n", "**1. Create a Heroku account**\n", "\n", "Create a free account on [Heroku](https://www.heroku.com/).\n", "\n", "\n", "**2. Install the Heroku CLI**\n", "\n", "Use [this guide](https://devcenter.heroku.com/articles/heroku-cli) to install the Heroku CLI for your system. After installation, you should be able to run the `heroku` command in your terminal.\n", "\n", "**3. Login to Heroku**\n", "\n", "Run the following command in the terminal and log in:\n", "```\n", "heroku login\n", "```\n", "\n", "**4. Create a new Heroku App**\n", "\n", "Run the following command to create a new application. When choosing the application name, you need to make sure that it doesn't exist yet. It may be a good idea to add your name to it (I chose `vladimir-classifier-app`).\n", "```\n", "heroku create \n", "```\n", "\n", "**5. Initialize a Git repository**\n", "\n", "The deployment on Heroku is done using `git`. If you don't have it installed already, check [this guide](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). You need to initialize a new repository, commit your files and push it to Heroku:\n", "\n", "```\n", "git init\n", "git add app.py Procfile requirements.txt static\n", "git commit -m \"Initial commit\"\n", "heroku git:remote -a \n", "git push heroku master\n", "```\n", "\n", "Your application will now be packaged and uploaded to Heroku. This may take a couple of minutes, but when done you will be able to access your application at `https://<application name>.herokuapp.com`.\n", "\n", "You can also go to your dashboard and see your application [here](https://dashboard.heroku.com/apps)." ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "---\n", "# Summary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Video 15: Summary\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "remove-input" ] }, "outputs": [], "source": [ "# @title Video 15: Summary\n", "from ipywidgets import widgets\n", "from IPython.display import YouTubeVideo\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "\n", "\n", "class PlayVideo(IFrame):\n", " def __init__(self, id, source, page=1, width=400, height=300, **kwargs):\n", " self.id = id\n", " if source == 'Bilibili':\n", " src = f'https://player.bilibili.com/player.html?bvid={id}&page={page}'\n", " elif source == 'Osf':\n", " src = f'https://mfr.ca-1.osf.io/render?url=https://osf.io/download/{id}/?direct%26mode=render'\n", " super(PlayVideo, self).__init__(src, width, height, **kwargs)\n", "\n", "\n", "def display_videos(video_ids, W=400, H=300, fs=1):\n", " tab_contents = []\n", " for i, video_id in enumerate(video_ids):\n", " out = widgets.Output()\n", " with out:\n", " if video_ids[i][0] == 'Youtube':\n", " video = YouTubeVideo(id=video_ids[i][1], width=W,\n", " height=H, fs=fs, rel=0)\n", " print(f'Video available at https://youtube.com/watch?v={video.id}')\n", " else:\n", " video = PlayVideo(id=video_ids[i][1], source=video_ids[i][0], width=W,\n", " height=H, fs=fs, autoplay=False)\n", " if video_ids[i][0] == 'Bilibili':\n", " print(f'Video available at https://www.bilibili.com/video/{video.id}')\n", " elif video_ids[i][0] == 'Osf':\n", " print(f'Video available at https://osf.io/{video.id}')\n", " display(video)\n", " tab_contents.append(out)\n", " return tab_contents\n", "\n", "\n", "video_ids = [('Youtube', 'pDLdNOuUtKk'), ('Bilibili', 'BV1Qg411L7RM')]\n", "tab_contents = display_videos(video_ids, W=730, H=410)\n", "tabs = widgets.Tab()\n", "tabs.children = tab_contents\n", "for i in range(len(tab_contents)):\n", " tabs.set_title(i, video_ids[i][0])\n", "display(tabs)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Submit your feedback\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "execution": {}, "tags": [ "hide-input" ] }, "outputs": [], "source": [ "# @title Submit your feedback\n", "content_review(f\"{feedback_prefix}_Summary_Video\")" ] }, { "cell_type": "markdown", "metadata": { "execution": {} }, "source": [ "In this tutorial you learned the basics of some modern tools for creating dynamic web applications and REST APIs. You also learned how you can deploy your neural network model as a web app.\n", "\n", "You can now build on top of that and create more sophisticated and awesome applications and make them available to millions of people!" ] } ], "metadata": { "colab": { "collapsed_sections": [], "include_colab_link": true, "name": "Bonus_Tutorial1", "provenance": [], "toc_visible": true }, "kernel": { "display_name": "Python 3", "language": "python", "name": "python3" }, "kernelspec": { "display_name": "Python 3", "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.13" } }, "nbformat": 4, "nbformat_minor": 0 }