Showing posts with label Jupyter Notebook. Show all posts
Showing posts with label Jupyter Notebook. Show all posts

Thursday, 17 December 2020

Webcam capture with ffmpeg and OpenCV from Jupyter Notebook

I want to share here my experience with using OpenCV and ffmpeg to capture a webcam output.


Setup:
  • Jupyter notebook running in jupyter-lab
  • Ubuntu 20.04
  • USB web camera
Goal:
  • Capture and display frames from the webcam

OpenCV: Video I/O with OpenCV Overview says that OpenCV: cv::VideoCapture Class calls video I/O backends (APIs) depending on which one is available.

To find out what backends (VideoCaptureAPIs) are available we can use the following code:

import cv2

# cv2a.videoio_registry.getBackends() returns list of all available backends.
availableBackends = [cv2.videoio_registry.getBackendName(b) for b in cv2.videoio_registry.getBackends()]
print(availableBackends)

# Returns list of available backends which works via cv::VideoCapture(int index)
availableCameraBackends = [cv2.videoio_registry.getBackendName(b) for b in cv2.videoio_registry.getCameraBackends()]
print(availableBackends)

The output in my case was: 

['FFMPEG', 'GSTREAMER', 'CV_IMAGES', 'CV_MJPEG']
['FFMPEG', 'GSTREAMER', 'CV_IMAGES', 'CV_MJPEG']

Let's see what is each of these backends:

• FFMPEG is a multimedia framework which can record, convert and stream audio and video.

It contains libavcodec, libavutil, libavformat, libavfilter, libavdevice, libswscale and libswresample which can be used by applications. As well as ffmpeg, ffplay and ffprobe which can be used by end users for transcoding and playing.

• GSTREAMER is a pipeline-based multimedia framework with similar capabilities as ffmpeg.

• CV_IMAGES -  OpenCV Image Sequence (e.g. img_%02d.jpg). Matches cv2.CAP_IMAGES API ID.

• CV_MJPEG - Built-in OpenCV MotionJPEG codec (used for reading video files). Matches cv2.CAP_OPENCV_MJPEG video capture API.

I was surprised to see GSTREAMER listed above as VideoCaptureAPIs documentation says

Backends are available only if they have been built with your OpenCV binaries. 

...and OpenCV package installed in my environment was built only with FFMPEG support:

>>> import cv2
>>> cv2.getBuildInformation()
...
Video I/O:\n    DC1394:                      NO\n    FFMPEG:                      YES\n      avcodec:                   YES (58.35.100)\n      avformat:                  YES (58.20.100)\n      avutil:                    YES (56.22.100)\n      swscale:                   YES (5.3.100)\n      avresample:                YES (4.0.0)\n\n  
...

...which can also be verifed by looking the cmake config in the repository (opencv-feedstock/build.sh at master · conda-forge/opencv-feedstock):

-DWITH_FFMPEG=1     \
-DWITH_GSTREAMER=0  \

Although my conda environment contained all relevant packages:

(my-env) $ conda list | grep 'opencv\|ffmpeg\|gstreamer'
ffmpeg                    4.1.3                h167e202_0    conda-forge
gstreamer                 1.14.5               h36ae1b5_2    conda-forge
opencv                    4.1.0            py36h79d2e43_1    conda-forge

...it is important to know that having ffmpeg and gstreamer packages installed means only that we have their binaries installed (executables and .so libraries) but not Python bindings (modules) or their OpenCV plugins. We are able to launch these applications from terminal but can't import them in Python code.

I tried to force using FFMPEG:

import cv2

deviceId = "/dev/video0"

# videoCaptureApi = cv2.CAP_ANY       # autodetect default API
videoCaptureApi = cv2.CAP_FFMPEG
# videoCaptureApi = cv2.CAP_GSTREAMER 
cap = cv2.VideoCapture("/dev/video2", videoCaptureApi)

cap = cv2.VideoCapture(deviceId)
cap.open(deviceId)
if not cap.isOpened():
    raise RuntimeError("ERROR! Unable to open camera")

try:
    while True:
        ret, frame = cap.read()
        cv2.imshow('frame', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
finally:        
    cap.release()
    cv2.destroyAllWindows()

...but cell execution would fail with:

RuntimeError: ERROR! Unable to open camera

I checked ($ v4l2-ctl --list-devices) - my webcam was indeed with index 2. As this was failing at the very beginning I decided to open python interpreter console and debug there only the isolated code snippet which opens the camera:

(my-env) $ export OPENCV_LOG_LEVEL=DEBUG; export OPENCV_VIDEOIO_DEBUG=1

(my-env) $ python 
Python 3.6.6 | packaged by conda-forge | (default, Oct 12 2018, 14:43:46) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> cap = cv2.VideoCapture("/dev/video2", cv2.CAP_FFMPEG)
[ WARN:0] VIDEOIO(FFMPEG): trying capture filename='/dev/video2' ...
[ WARN:0] VIDEOIO(FFMPEG): can't create capture

I also tried to force using Gstreamer to no avail (which was expected):

>>> cap = cv2.VideoCapture("/dev/video2", cv2.CAP_GSTREAMER)
[ WARN:0] VIDEOIO(GSTREAMER): trying capture filename='/dev/video2' ...
[ INFO:0] VideoIO pluigin (GSTREAMER): glob is 'libopencv_videoio_gstreamer*.so', 1 location(s)
[ INFO:0]     - /home/bojan/anaconda3/envs/my-env/lib/python3.6/site-packages/../..: 0
[ INFO:0] Found 0 plugin(s) for GSTREAMER
[ WARN:0] VIDEOIO(GSTREAMER): backend is not available (plugin is missing, or can't be loaded due dependencies or it is not compatible)

Indeed ~/anaconda3/envs/my-env/lib did not contain ffmpeg plugin (libopencv_videoio_ffmpeg*.so files) or Gstreamer plugin (libopencv_videoio_gstreamer*.so files).

These plugins are installed only if OpenCV is build with following CMake options:

- DWITH_FFMPEG=1     \
-DVIDEOIO_PLUGIN_LIST=ffmpeg

...or (for Gstreamer):

-DWITH_GSTREAMER=1 \
-DVIDEOIO_PLUGIN_LIST=gstreamer \

...and apart from WITH_FFMPEG no other were used in the cmake config that was used to build OpenCV package installed in my environment.

As I didn't want to compile OpenCV myself but to achieve my goal with what I have I decided to see if I can run ffmpg process to stream camera output into a pipe and then read the binary information from it and convert it into frames:

import os
import tempfile
import subprocess
import cv2
import numpy as np

# To get this path execute:
#    $ which ffmpeg
FFMPEG_BIN = '/home/bojan/anaconda3/envs/my-env/bin/ffmpeg'


# To find allowed formats for the specific camera:
#    $ ffmpeg -f v4l2 -list_formats all -i /dev/video3
#    ...
#    [video4linux2,v4l2 @ 0x5608ac90af40] Raw: yuyv422: YUYV 4:2:2: 640x480 1280x720 960x544 800x448 640x360 424x240 352x288 320x240 800x600 176x144 160x120 1280x800
#    ...

def run_ffmpeg(fifo_path):
    ffmpg_cmd = [
        FFMPEG_BIN,
        '-i', '/dev/video2',
        '-video_size', '640x480',
        '-pix_fmt', 'bgr24',        # opencv requires bgr24 pixel format
        '-vcodec', 'rawvideo',
        '-an','-sn',                # disable audio processing
        '-f', 'image2pipe',
        '-',                        # output to go to stdout
    ]
    return subprocess.Popen(ffmpg_cmd, stdout = subprocess.PIPE, bufsize=10**8)

def run_cv_window(process):
    while True:
        # read frame-by-frame
        raw_image = process.stdout.read(640*480*3)
        if raw_image == b'':
            raise RuntimeError("Empty pipe")
        
        # transform the bytes read into a numpy array
        frame =  np.frombuffer(raw_image, dtype='uint8')
        frame = frame.reshape((480,640,3)) # height, width, channels
        if frame is not None:
            cv2.imshow('Video', frame)
        
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
        process.stdout.flush()
    
    cv2.destroyAllWindows()
    process.terminate()
    print(process.poll())

def run():
    ffmpeg_process = run_ffmpeg()
    run_cv_window(ffmpeg_process)

run()

Et voila! I got the camera capture from Python notebook thanks to ffmpeg and OpenCV.



Sunday, 4 October 2020

Introduction to JupyterLab

JupyterLab is a new interface of Jupyter Notebook server. It is a web-based interactive development environment for Jupyter notebooks, code, and data.


Installation via Anaconda


env.yml file should contain:

name: my-env # arbitrary name
...
dependencies:
  - jupyter=1.0.0
  - jupyterlab=0.34.9
...
  - python=3.6.6 # for running python kernels
...


Let's first check that python we'll be using is the right one:

(my-env) $ which python
/home/bojan/anaconda3/envs/my-env/bin/python

(my-env) $ python --version
Python 3.6.6

To launch jupyter lab:

(my-env) $ jupyter-lab
[I 07:37:01.479 LabApp] JupyterLab extension loaded from /home/bojan/anaconda3/envs/my-env/lib/python3.6/site-packages/jupyterlab
[I 07:37:01.479 LabApp] JupyterLab application directory is /home/bojan/anaconda3/envs/my-env/share/jupyter/lab
[I 07:37:01.483 LabApp] Serving notebooks from local directory: /home/bojan/dev/github/jupyterlab-demo
[I 07:37:01.484 LabApp] The Jupyter Notebook is running at:
[I 07:37:01.484 LabApp] http://localhost:8888/?token=22151a5520697ab97bfe48f44bcdd6248a0c1bd7bc5100ff
[I 07:37:01.484 LabApp]  or http://127.0.0.1:8888/?token=22151a5520697ab97bfe48f44bcdd6248a0c1bd7bc5100ff
[I 07:37:01.484 LabApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 07:37:01.491 LabApp] 
    
    To access the notebook, open this file in a browser:
        file:///home/bojan/.local/share/jupyter/runtime/nbserver-20890-open.html
    Or copy and paste one of these URLs:
        http://localhost:8888/?token=22151a5520697ab97bfe48f44bcdd6248a0c1bd7bc5100ff
     or http://127.0.0.1:8888/?token=22151a5520697ab97bfe48f44bcdd6248a0c1bd7bc5100ff
Opening in existing browser session.
[1004/073701.855894:ERROR:nacl_helper_linux.cc(308)] NaCl helper process running without a sandbox!
Most likely you need to configure your SUID sandbox correctly
[I 07:37:24.221 LabApp] Kernel started: 8144639c-36e9-41ec-8e0c-06b9c593a426
[I 07:37:24.780 LabApp] Build is up to date
[I 07:37:46.613 LabApp] 302 GET /?token=22151a5520697ab97bfe48f44bcdd6248a0c1bd7bc5100ff (127.0.0.1) 0.66ms
[I 07:37:53.685 LabApp] Starting buffering for 8144639c-36e9-41ec-8e0c-06b9c593a426:3d1e75cf-7a1f-4999-a91a-013da0761314
[I 07:37:55.524 LabApp] Build is up to date
[I 07:38:59.153 LabApp] Saving file at /Untitled.ipynb
...
[I 07:46:51.165 LabApp] Starting buffering for 8144639c-36e9-41ec-8e0c-06b9c593a426:84ef87af-47d6-473a-b8a6-cbe3f2c0da45
[I 07:46:53.082 LabApp] Build is up to date
[I 07:46:55.971 LabApp] Starting buffering for 8144639c-36e9-41ec-8e0c-06b9c593a426:58fc2ee2-42b3-408d-baa9-b9d3fc5ba29c
^C[I 07:47:02.242 LabApp] interrupted
Serving notebooks from local directory: /home/bojan/dev/github/jupyterlab-demo
1 active kernel
The Jupyter Notebook is running at:
http://localhost:8888/?token=22151a5520697ab97bfe48f44bcdd6248a0c1bd7bc5100ff
 or http://127.0.0.1:8888/?token=22151a5520697ab97bfe48f44bcdd6248a0c1bd7bc5100ff
Shutdown this notebook server (y/[n])? y
[C 07:47:03.991 LabApp] Shutdown confirmed
[I 07:47:03.992 LabApp] Shutting down 1 kernel
[I 07:47:04.294 LabApp] Kernel shutdown: 8144639c-36e9-41ec-8e0c-06b9c593a426

We can see that jupyterlab starts a local web server which listens on port 8888. This is a classical Jupyter Notebook server (that's why we needed to include jupyter in the environment packages). We can then access it from a browser with http://localhost:8888 or http://127.0.0.1:8888. 

jupyterlab is an extension of this server:

(my-env) $ jupyter serverextension list
config dir: /home/bojan/anaconda3/envs/my-env/etc/jupyter
    jupyterlab  enabled 
    - Validating...
      jupyterlab 0.34.9 OK

jupyter lab is accessible at /lab path so when we run it, it will automatically open http://localhost:8888/lab in a browser.

We can also run jupyter notebook:

(my-env) $ jupyter notebook
[I 08:05:37.951 NotebookApp] JupyterLab extension loaded from /home/bojan/anaconda3/envs/my-env/lib/python3.6/site-packages/jupyterlab
[I 08:05:37.951 NotebookApp] JupyterLab application directory is /home/bojan/anaconda3/envs/my-env/share/jupyter/lab
[I 08:05:37.954 NotebookApp] Serving notebooks from local directory: /home/bojan/dev/github/jupyterlab-demo
[I 08:05:37.954 NotebookApp] The Jupyter Notebook is running at:
[I 08:05:37.954 NotebookApp] http://localhost:8888/?token=90194de8634a4a8e3c0ee9d4ec760382b4aa545d7b9260f6
[I 08:05:37.954 NotebookApp]  or http://127.0.0.1:8888/?token=90194de8634a4a8e3c0ee9d4ec760382b4aa545d7b9260f6
[I 08:05:37.954 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 08:05:37.960 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///home/bojan/.local/share/jupyter/runtime/nbserver-24138-open.html
    Or copy and paste one of these URLs:
        http://localhost:8888/?token=90194de8634a4a8e3c0ee9d4ec760382b4aa545d7b9260f6
     or http://127.0.0.1:8888/?token=90194de8634a4a8e3c0ee9d4ec760382b4aa545d7b9260f6
Opening in existing browser session.
[1004/080538.264120:ERROR:nacl_helper_linux.cc(308)] NaCl helper process running without a sandbox!
Most likely you need to configure your SUID sandbox correctly
[I 08:05:49.878 NotebookApp] Kernel started: 3aa4ef5c-e260-40af-8e54-a4f509223a64
[I 08:05:50.492 NotebookApp] Build is up to date
[I 08:06:31.324 NotebookApp] Saving file at /Untitled.ipynb




...and manually go to /lab:





References:

Friday, 24 April 2020

How to install Jupyter Notebook on Ubuntu 18.04

Just wanted to share my experience with installing Jupyter Notebook on my Ubuntu 18.04 box.

$ cd my-project

$ virtualenv venv
Using base prefix '/usr'
New python executable in /home/bojan/dev/my-project/venv/bin/python3
Also creating executable in /home/bojan/dev/my-project/venv/bin/python
Installing setuptools, pip, wheel...
done.

$ ls
README.md  venv

$ source venv/bin/activate

(venv) $ ls
README.md  venv

(venv) $ which pip
/home/bojan/dev/my-project/venv/bin/pip

(venv) $ which pip3
/home/bojan/dev/my-project/venv/bin/pip3

(venv) $ pip --version
pip 20.0.2 from /home/bojan/dev/my-project/venv/lib/python3.6/site-packages/pip (python 3.6)

(venv) $ pip3 --version
pip 20.0.2 from /home/bojan/dev/my-project/venv/lib/python3.6/site-packages/pip (python 3.6)

(venv) $ pip install jupyter
Collecting jupyter
  Using cached jupyter-1.0.0-py2.py3-none-any.whl (2.7 kB)
Collecting jupyter-console
  Downloading jupyter_console-6.1.0-py2.py3-none-any.whl (21 kB)
Collecting ipywidgets
  Using cached ipywidgets-7.5.1-py2.py3-none-any.whl (121 kB)
Collecting ipykernelbojan@bobox:~/dev/my-project models
  Downloading ipykernel-5.2.1-py3-none-any.whl (118 kB)
Collecting notebook
  Downloading notebook-6.0.3-py3-none-any.whl (9.7 MB)
Collecting nbconvert
  Using cached nbconvert-5.6.1-py2.py3-none-any.whl (455 kB)
Collecting qtconsole
  Downloading qtconsole-4.7.3-py2.py3-none-any.whl (117 kB)
Collecting ipython
  Downloading ipython-7.13.0-py3-none-any.whl (780 kB)
Collecting pygments
  Downloading Pygments-2.6.1-py3-none-any.whl (914 kB)
Collecting jupyter-client
  Downloading jupyter_client-6.1.3-py3-none-any.whl (106 kB)
Collecting prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0
  Downloading prompt_toolkit-3.0.5-py3-none-any.whl (351 kB)
Collecting traitlets>=4.3.1
  Using cached traitlets-4.3.3-py2.py3-none-any.whl (75 kB)
Collecting nbformat>=4.2.0
  Downloading nbformat-5.0.6-py3-none-any.whl (170 kB)
Collecting widgetsnbextension~=3.5.0
  Using cached widgetsnbextension-3.5.1-py2.py3-none-any.whl (2.2 MB)
Collecting tornado>=4.2
  Downloading tornado-6.0.4.tar.gz (496 kB)
Processing /home/bojan/.cache/pip/wheels/1c/54/34/fd47cd9b308826cc4292b54449c1899a30251ef3b506bc91ea/prometheus_client-0.7.1-cp36-none-any.whl
Collecting pyzmq>=17
  Downloading pyzmq-19.0.0-cp36-cp36m-manylinux1_x86_64.whl (1.1 MB)
Collecting jinja2
  Downloading Jinja2-2.11.2-py2.py3-none-any.whl (125 kB)
Collecting Send2Trash
  Using cached Send2Trash-1.5.0-py3-none-any.whl (12 kB)
Collecting jupyter-core>=4.6.1
  Downloading jupyter_core-4.6.3-py2.py3-none-any.whl (83 kB)
Collecting ipython-genutils
  Using cached ipython_genutils-0.2.0-py2.py3-none-any.whl (26 kB)
Collecting terminado>=0.8.1
  Using cached terminado-0.8.3-py2.py3-none-any.whl (33 kB)
Collecting testpath
  Using cached testpath-0.4.4-py2.py3-none-any.whl (163 kB)
Collecting mistune<2,>=0.8.1
  Using cached mistune-0.8.4-py2.py3-none-any.whl (16 kB)
Collecting bleach
  Downloading bleach-3.1.4-py2.py3-none-any.whl (151 kB)
Collecting defusedxml
  Using cached defusedxml-0.6.0-py2.py3-none-any.whl (23 kB)
Collecting entrypoints>=0.2.2
  Using cached entrypoints-0.3-py2.py3-none-any.whl (11 kB)
Processing /home/bojan/.cache/pip/wheels/39/01/56/f1b08a6275acc59e846fa4c1e1b65dbc1919f20157d9e66c20/pandocfilters-1.4.2-cp36-none-any.whl
Collecting qtpy
  Downloading QtPy-1.9.0-py2.py3-none-any.whl (54 kB)
Requirement already satisfied: setuptools>=18.5 in ./venv/lib/python3.6/site-packages (from ipython->jupyter-console->jupyter) (46.1.3)
Collecting pexpect; sys_platform != "win32"
  Downloading pexpect-4.8.0-py2.py3-none-any.whl (59 kB)
Collecting decorator
  Downloading decorator-4.4.2-py2.py3-none-any.whl (9.2 kB)
Processing /home/bojan/.cache/pip/wheels/98/b0/dd/29e28ff615af3dda4c67cab719dd51357597eabff926976b45/backcall-0.1.0-cp36-none-any.whl
Collecting jedi>=0.10
  Downloading jedi-0.17.0-py2.py3-none-any.whl (1.1 MB)
Collecting pickleshare
  Using cached pickleshare-0.7.5-py2.py3-none-any.whl (6.9 kB)
Collecting python-dateutil>=2.1
  Using cached python_dateutil-2.8.1-py2.py3-none-any.whl (227 kB)
Collecting wcwidth
  Downloading wcwidth-0.1.9-py2.py3-none-any.whl (19 kB)
Collecting six
  Downloading six-1.14.0-py2.py3-none-any.whl (10 kB)
Collecting jsonschema!=2.5.0,>=2.4
  Using cached jsonschema-3.2.0-py2.py3-none-any.whl (56 kB)
Collecting MarkupSafe>=0.23
  Using cached MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl (27 kB)
Collecting ptyprocess; os_name != "nt"
  Using cached ptyprocess-0.6.0-py2.py3-none-any.whl (39 kB)
Collecting webencodings
  Using cached webencodings-0.5.1-py2.py3-none-any.whl (11 kB)
Collecting parso>=0.7.0
  Downloading parso-0.7.0-py2.py3-none-any.whl (100 kB)
Collecting pyrsistent>=0.14.0
  Downloading pyrsistent-0.16.0.tar.gz (108 kB)
Collecting importlib-metadata; python_version < "3.8"
  Downloading importlib_metadata-1.6.0-py2.py3-none-any.whl (30 kB)
Collecting attrs>=17.4.0
  Using cached attrs-19.3.0-py2.py3-none-any.whl (39 kB)
Collecting zipp>=0.5
  Downloading zipp-3.1.0-py3-none-any.whl (4.9 kB)
Building wheels for collected packages: tornado, pyrsistent
  Building wheel for tornado (setup.py) ... done
  Created wheel for tornado: filename=tornado-6.0.4-cp36-cp36m-linux_x86_64.whl size=427634 sha256=2889594a465ff46f8fd99501b165ee4f674855322f0fe1a1df215836d56e20a7
  Stored in directory: /home/bojan/.cache/pip/wheels/37/a7/db/2d592e44029ef817f3ef63ea991db34191cebaef087a96f505
  Building wheel for pyrsistent (setup.py) ... done
  Created wheel for pyrsistent: filename=pyrsistent-0.16.0-cp36-cp36m-linux_x86_64.whl size=97736 sha256=06c739603fe2717a15b5bb65e9e0d6cae6ca8de2f1d3ee4988966b6711ca9b89
  Stored in directory: /home/bojan/.cache/pip/wheels/d1/8a/1c/32ab9017418a2c64e4fbaf503c08648bed2f8eb311b869a464
Successfully built tornado pyrsistent
Installing collected packages: tornado, six, python-dateutil, pyzmq, decorator, ipython-genutils, traitlets, jupyter-core, jupyter-client, ptyprocess, pexpect, backcall, parso, jedi, wcwidth, prompt-toolkit, pygments, pickleshare, ipython, ipykernel, jupyter-console, pyrsistent, zipp, importlib-metadata, attrs, jsonschema, nbformat, prometheus-client, MarkupSafe, jinja2, testpath, mistune, webencodings, bleach, defusedxml, entrypoints, pandocfilters, nbconvert, Send2Trash, terminado, notebook, widgetsnbextension, ipywidgets, qtpy, qtconsole, jupyter
Successfully installed MarkupSafe-1.1.1 Send2Trash-1.5.0 attrs-19.3.0 backcall-0.1.0 bleach-3.1.4 decorator-4.4.2 defusedxml-0.6.0 entrypoints-0.3 importlib-metadata-1.6.0 ipykernel-5.2.1 ipython-7.13.0 ipython-genutils-0.2.0 ipywidgets-7.5.1 jedi-0.17.0 jinja2-2.11.2 jsonschema-3.2.0 jupyter-1.0.0 jupyter-client-6.1.3 jupyter-console-6.1.0 jupyter-core-4.6.3 mistune-0.8.4 nbconvert-5.6.1 nbformat-5.0.6 notebook-6.0.3 pandocfilters-1.4.2 parso-0.7.0 pexpect-4.8.0 pickleshare-0.7.5 prometheus-client-0.7.1 prompt-toolkit-3.0.5 ptyprocess-0.6.0 pygments-2.6.1 pyrsistent-0.16.0 python-dateutil-2.8.1 pyzmq-19.0.0 qtconsole-4.7.3 qtpy-1.9.0 six-1.14.0 terminado-0.8.3 testpath-0.4.4 tornado-6.0.4 traitlets-4.3.3 wcwidth-0.1.9 webencodings-0.5.1 widgetsnbextension-3.5.1 zipp-3.1.0

Verification


(venv) $ jupyter notebook
[I 01:18:31.669 NotebookApp] Serving notebooks from local directory: /home/bojan/dev/my-project
[I 01:18:31.669 NotebookApp] The Jupyter Notebook is running at:
[I 01:18:31.670 NotebookApp] http://localhost:8888/?token=c9c50e5b8874194d2aaf815a79e079dacdac8eace7691c60
[I 01:18:31.670 NotebookApp]  or http://127.0.0.1:8888/?token=c9c50e5b8874194d2aaf815a79e079dacdac8eace7691c60
[I 01:18:31.670 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 01:18:31.674 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///home/bojan/.local/share/jupyter/runtime/nbserver-29213-open.html
    Or copy and paste one of these URLs:
        http://localhost:8888/?token=c9c50e5b8874144d2aaf815a79e079dacdac8eace7691c60
     or http://127.0.0.1:8888/?token=c9c50e5b8874494d2aaf815a79e079dacdac8eace7691c60
Opening in existing browser session.


References: