├── .coveragerc ├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG ├── CONTRIBUTING ├── INSTALL ├── LICENSE ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── build │ └── empty ├── requirements.txt └── source │ ├── _static │ └── empty │ ├── _templates │ └── empty │ ├── api.rst │ ├── api_app.rst │ ├── api_misc.rst │ ├── api_misc_canvas.rst │ ├── api_misc_colors.rst │ ├── api_misc_text.rst │ ├── api_prim.rst │ ├── api_src.rst │ ├── api_src_camera.rst │ ├── api_src_image.rst │ ├── api_src_video.rst │ ├── api_ui.rst │ ├── api_ui_trackbar.rst │ ├── api_ui_window.rst │ ├── api_ui_wm.rst │ ├── conf.py │ ├── index.rst │ ├── quickstart.rst │ └── rst_guide.rst ├── ocvproto ├── __init__.py ├── app │ ├── __init__.py │ ├── application.py │ ├── config.py │ └── keys.py ├── backend.py ├── exceptions.py ├── frame.py ├── misc │ ├── __init__.py │ ├── canvas.py │ ├── colors.py │ └── text.py ├── primitives │ ├── __init__.py │ └── legend.py ├── sources │ ├── __init__.py │ ├── base.py │ ├── camera.py │ ├── image.py │ └── video.py ├── toolbox.py └── ui │ ├── __init__.py │ ├── trackbars │ ├── __init__.py │ └── base.py │ ├── window.py │ └── wm.py ├── setup.cfg ├── setup.py ├── tests ├── conftest.py ├── static │ ├── empty │ ├── red.avi │ └── tiny.png ├── test_application.py ├── test_camera.py ├── test_config.py ├── test_image.py ├── test_misc.py ├── test_primitives.py ├── test_trackbar.py ├── test_video.py ├── test_window.py └── test_wm.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = ocvproto/* 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .pydevproject 3 | .idea 4 | .tox 5 | __pycache__ 6 | *.pyc 7 | *.pyo 8 | *.egg-info 9 | docs/_build/ 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | 3 | language: python 4 | 5 | sudo: false 6 | 7 | python: 8 | - 3.8 9 | - 3.7 10 | - 3.6 11 | 12 | install: 13 | - pip install pytest coverage coveralls six 14 | 15 | script: 16 | - coverage run --source=ocvproto setup.py test 17 | 18 | after_success: 19 | - coveralls 20 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | opencv-proto authors 2 | ==================== 3 | 4 | Created by Igor `idle sign` Starikov. 5 | 6 | 7 | Contributors 8 | ------------ 9 | 10 | Here could be your name. 11 | 12 | 13 | 14 | Translators 15 | ----------- 16 | 17 | Here could be your name. 18 | 19 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | opencv-proto changelog 2 | ====================== 3 | 4 | 5 | v0.3.0 [2020-04-24] 6 | ------------------- 7 | + Added 'Legend' into primitives subpackage. 8 | + Added .describe_properties() helper to adjust Video/Camera props with trackbars. 9 | + Added Frame.draw_rectangle(). 10 | + Added more color names. 11 | + Exposed more adjustable properties for Video and Camera. 12 | * Now can be installed with the thirdparties: pip install opencv-proto[all]. 13 | * Text.apply* renamed into .put_on* for clarity. 14 | 15 | 16 | v0.2.0 [2020-04-22] 17 | ------------------- 18 | + Added Frame abstraction. 19 | + Added helpers to save images. 20 | * Video.write() renamed into .dump() as less confusing. 21 | 22 | 23 | v0.1.0 [2020-04-19] 24 | ------------------- 25 | + Basic functionality. -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | opencv-proto contributing 2 | ========================= 3 | 4 | 5 | Submit issues 6 | ------------- 7 | 8 | If you spotted something weird in application behavior or want to propose a feature you are welcome. 9 | 10 | 11 | Write code 12 | ---------- 13 | If you are eager to participate in application development and to work on an existing issue (whether it should 14 | be a bugfix or a feature implementation), fork, write code, and make a pull request right from the forked project page. 15 | 16 | 17 | Spread the word 18 | --------------- 19 | 20 | If you have some tips and tricks or any other words that you think might be of interest for the others — publish it 21 | wherever you find convenient. 22 | 23 | 24 | See also: https://github.com/idlesign/opencv-proto 25 | 26 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | opencv-proto installation 2 | ========================= 3 | 4 | 5 | Python ``pip`` package is required to install ``opencv-proto``. 6 | 7 | 8 | From sources 9 | ------------ 10 | 11 | Use the following command line to install ``opencv-proto`` from sources directory (containing setup.py): 12 | 13 | pip install . 14 | 15 | or 16 | 17 | python setup.py install 18 | 19 | 20 | From PyPI 21 | --------- 22 | 23 | Alternatively you can install ``opencv-proto`` from PyPI: 24 | 25 | pip install opencv-proto 26 | 27 | or quick install with third-parties: 28 | 29 | pip install opencv-proto[all] 30 | 31 | 32 | Use `-U` flag for upgrade: 33 | 34 | pip install -U opencv-proto 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Igor `idle sign` Starikov 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the opencv-proto nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include CHANGELOG 3 | include INSTALL 4 | include LICENSE 5 | include README.rst 6 | 7 | include docs/Makefile 8 | recursive-include docs *.rst 9 | recursive-include docs *.py 10 | recursive-include tests * 11 | 12 | recursive-exclude * __pycache__ 13 | recursive-exclude * *.py[co] 14 | recursive-exclude * empty 15 | 16 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | opencv-proto 2 | ============ 3 | https://github.com/idlesign/opencv-proto 4 | 5 | |release| |lic| |ci| |coverage| 6 | 7 | .. |release| image:: https://img.shields.io/pypi/v/opencv-proto.svg 8 | :target: https://pypi.python.org/pypi/opencv-proto 9 | 10 | .. |lic| image:: https://img.shields.io/pypi/l/opencv-proto.svg 11 | :target: https://pypi.python.org/pypi/opencv-proto 12 | 13 | .. |ci| image:: https://img.shields.io/travis/idlesign/opencv-proto/master.svg 14 | :target: https://travis-ci.org/idlesign/opencv-proto 15 | 16 | .. |coverage| image:: https://img.shields.io/coveralls/idlesign/opencv-proto/master.svg 17 | :target: https://coveralls.io/r/idlesign/opencv-proto 18 | 19 | 20 | **Work in progress. Stay tuned.** 21 | 22 | Description 23 | ----------- 24 | 25 | *Allows fast prototyping in Python for OpenCV* 26 | 27 | Offers primitives and simplified interfaces to streamline prototypes construction in Python. 28 | 29 | Facilitates: 30 | 31 | * Windows construction and management 32 | * Trackbar construction 33 | * Configuration save/load (including trackbar values) 34 | * Key binding (e.g. for trackbar control, configuration save/load) 35 | * Video capturing and modification 36 | * Work with images 37 | * Work with text 38 | * Frames transformation 39 | 40 | 41 | Samples 42 | ------- 43 | 44 | Color Palette 45 | ~~~~~~~~~~~~~ 46 | 47 | Let's replace 37 lines of source code from `Trackbar as the Color Palette `_ 48 | tutorial with ``ocvproto``-based implementation: 49 | 50 | .. code-block:: python 51 | 52 | from ocvproto.toolbox import WindowManager, Canvas 53 | 54 | with WindowManager() as wm: 55 | rgb = wm.window.add_trackbar_group(['R', 'G', 'B'], max=255) 56 | for _ in wm.app.loop(): 57 | wm.set_frame(Canvas(512, 300, color=rgb)) 58 | 59 | 60 | Camera capture 61 | ~~~~~~~~~~~~~~ 62 | 63 | Now let's capture video camera stream into ``ocvproto.avi`` file, being able to adjust blur. 64 | 65 | Let's also setup config filepath (``ocvproto.json``) - this allows us to store current trackbar values 66 | (``s`` key) and load them (``r`` key). It is useful to restore settings between sessions. 67 | 68 | We bind ``z`` key to take camera shots. 69 | 70 | .. code-block:: python 71 | 72 | from ocvproto.toolbox import WindowManager, Camera 73 | 74 | with WindowManager() as wm: 75 | 76 | wm.app.set_config('ocvproto.json') 77 | 78 | blur = wm.window.add_trackbar_group(['x', 'y'], 'Blur', default=1) 79 | 80 | with Camera() as cam: 81 | wm.app.bind_key('z', cam.dump_image) 82 | 83 | for _ in wm.app.loop(): 84 | cam.read() 85 | cam.blur(blur) 86 | cam.dump() 87 | wm.set_frame(cam) 88 | 89 | 90 | Read the documentation. 91 | 92 | Requirements 93 | ------------ 94 | * Python 3.6+ 95 | * ``opencv-python`` (or variants) 96 | 97 | Quick install with third-parties: ``$ pip install opencv-proto[all]`` 98 | 99 | 100 | Documentation 101 | ------------- 102 | 103 | https://opencv-proto.readthedocs.org/ 104 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = opencv-proto 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | 22 | -------------------------------------------------------------------------------- /docs/build/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/opencv-proto/35bff19f71dc8823bc84cdb87e721a41aaf08a40/docs/build/empty -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # This can be used to describe dependencies. May be useful for Read The Docs. 2 | 3 | -------------------------------------------------------------------------------- /docs/source/_static/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/opencv-proto/35bff19f71dc8823bc84cdb87e721a41aaf08a40/docs/source/_static/empty -------------------------------------------------------------------------------- /docs/source/_templates/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/opencv-proto/35bff19f71dc8823bc84cdb87e721a41aaf08a40/docs/source/_templates/empty -------------------------------------------------------------------------------- /docs/source/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | api_app 8 | api_ui 9 | api_src 10 | api_misc 11 | api_prim 12 | -------------------------------------------------------------------------------- /docs/source/api_app.rst: -------------------------------------------------------------------------------- 1 | Application 2 | =========== 3 | 4 | .. automodule:: ocvproto.app.application 5 | :members: 6 | 7 | 8 | Config 9 | ------ 10 | 11 | .. automodule:: ocvproto.app.config 12 | :members: 13 | 14 | 15 | Keys 16 | ---- 17 | 18 | .. automodule:: ocvproto.app.keys 19 | :members: 20 | -------------------------------------------------------------------------------- /docs/source/api_misc.rst: -------------------------------------------------------------------------------- 1 | Miscellaneous 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | api_misc_canvas 8 | api_misc_colors 9 | api_misc_text 10 | -------------------------------------------------------------------------------- /docs/source/api_misc_canvas.rst: -------------------------------------------------------------------------------- 1 | Canvas 2 | ====== 3 | 4 | .. automodule:: ocvproto.misc.canvas 5 | :members: 6 | :inherited-members: 7 | -------------------------------------------------------------------------------- /docs/source/api_misc_colors.rst: -------------------------------------------------------------------------------- 1 | Colors 2 | ====== 3 | 4 | .. automodule:: ocvproto.misc.colors 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/api_misc_text.rst: -------------------------------------------------------------------------------- 1 | Text 2 | ==== 3 | 4 | .. automodule:: ocvproto.misc.text 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/api_prim.rst: -------------------------------------------------------------------------------- 1 | Primitives 2 | ========== 3 | 4 | 5 | Legend 6 | ------ 7 | 8 | .. automodule:: ocvproto.primitives.legend 9 | :members: 10 | :inherited-members: 11 | :show-inheritance: 12 | -------------------------------------------------------------------------------- /docs/source/api_src.rst: -------------------------------------------------------------------------------- 1 | Sources 2 | ======= 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | api_src_image 8 | api_src_camera 9 | api_src_video 10 | -------------------------------------------------------------------------------- /docs/source/api_src_camera.rst: -------------------------------------------------------------------------------- 1 | Camera 2 | ====== 3 | 4 | .. automodule:: ocvproto.sources.camera 5 | :members: 6 | :inherited-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/api_src_image.rst: -------------------------------------------------------------------------------- 1 | Image 2 | ===== 3 | 4 | .. automodule:: ocvproto.sources.image 5 | :members: 6 | :inherited-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/api_src_video.rst: -------------------------------------------------------------------------------- 1 | Video 2 | ===== 3 | 4 | .. automodule:: ocvproto.sources.video 5 | :members: 6 | :inherited-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/source/api_ui.rst: -------------------------------------------------------------------------------- 1 | User Interface 2 | ============== 3 | 4 | .. toctree:: 5 | :maxdepth: 3 6 | 7 | api_ui_wm 8 | api_ui_window 9 | api_ui_trackbar 10 | -------------------------------------------------------------------------------- /docs/source/api_ui_trackbar.rst: -------------------------------------------------------------------------------- 1 | Trackbar 2 | ======== 3 | 4 | .. automodule:: ocvproto.ui.trackbars.base 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/api_ui_window.rst: -------------------------------------------------------------------------------- 1 | Window 2 | ====== 3 | 4 | .. automodule:: ocvproto.ui.window 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/api_ui_wm.rst: -------------------------------------------------------------------------------- 1 | Windows Manager 2 | =============== 3 | 4 | .. automodule:: ocvproto.ui.wm 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # opencv-ocvproto documentation build configuration file. 4 | # 5 | # This file is execfile()d with the current directory set to its 6 | # containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | # If extensions (or modules to document with autodoc) are in another directory, 15 | # add these directories to sys.path here. If the directory is relative to the 16 | # documentation root, use os.path.abspath to make it absolute, like shown here. 17 | # 18 | import sys, os 19 | sys.path.insert(0, os.path.abspath('../../')) 20 | 21 | from ocvproto import VERSION_STR 22 | 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = ['sphinx.ext.autodoc'] 34 | 35 | # Instruct autoclass directive to document both class and __init__ docstrings. 36 | autoclass_content = 'both' 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix(es) of source filenames. 42 | # You can specify multiple suffix as a list of string: 43 | # 44 | # source_suffix = ['.rst', '.md'] 45 | source_suffix = '.rst' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = 'opencv-proto' 52 | copyright = '2020, Igor `idle sign` Starikov' 53 | author = 'Igor `idle sign` Starikov' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = VERSION_STR 61 | # The full version, including alpha/beta/rc tags. 62 | release = VERSION_STR 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This patterns also effect to html_static_path and html_extra_path 74 | exclude_patterns = ['rst_guide.rst'] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = 'sphinx' 78 | 79 | todo_include_todos = False 80 | 81 | 82 | # -- Options for HTML output ---------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = 'default' 88 | 89 | # Theme options are theme-specific and customize the look and feel of a theme 90 | # further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ['_static'] 99 | 100 | 101 | # -- Options for HTMLHelp output ------------------------------------------ 102 | 103 | # Output file base name for HTML help builder. 104 | htmlhelp_basename = 'opencv-protodoc' 105 | 106 | 107 | # -- Options for LaTeX output --------------------------------------------- 108 | 109 | latex_elements = { 110 | # The paper size ('letterpaper' or 'a4paper'). 111 | # 112 | # 'papersize': 'letterpaper', 113 | 114 | # The font size ('10pt', '11pt' or '12pt'). 115 | # 116 | # 'pointsize': '10pt', 117 | 118 | # Additional stuff for the LaTeX preamble. 119 | # 120 | # 'preamble': '', 121 | 122 | # Latex figure (float) alignment 123 | # 124 | # 'figure_align': 'htbp', 125 | } 126 | 127 | # Grouping the document tree into LaTeX files. List of tuples 128 | # (source start file, target name, title, 129 | # author, documentclass [howto, manual, or own class]). 130 | latex_documents = [ 131 | (master_doc, 'opencv-proto.tex', 'opencv-proto Documentation', 132 | 'Igor `idle sign` Starikov', 'manual'), 133 | ] 134 | 135 | 136 | # -- Options for manual page output --------------------------------------- 137 | 138 | # One entry per manual page. List of tuples 139 | # (source start file, name, description, authors, manual section). 140 | man_pages = [ 141 | (master_doc, 'opencv-proto', 'opencv-proto Documentation', 142 | [author], 1) 143 | ] 144 | 145 | 146 | # -- Options for Texinfo output ------------------------------------------- 147 | 148 | # Grouping the document tree into Texinfo files. List of tuples 149 | # (source start file, target name, title, author, 150 | # dir menu entry, description, category) 151 | texinfo_documents = [ 152 | (master_doc, 'opencv-proto', 'opencv-proto Documentation', 153 | author, 'opencv-proto', 'Allows fast prototyping in Python for OpenCV', 154 | 'Miscellaneous'), 155 | ] 156 | 157 | autodoc_mock_imports = ['cv2', 'colorhash', 'numpy'] 158 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | opencv-proto documentation 2 | ========================== 3 | https://github.com/idlesign/opencv-proto 4 | 5 | 6 | 7 | Description 8 | ----------- 9 | 10 | *Allows fast prototyping in Python for OpenCV* 11 | 12 | Offers primitives and simplified interfaces to streamline prototypes construction in Python. 13 | 14 | Facilitates: 15 | 16 | * Windows construction and management 17 | * Trackbar construction 18 | * Configuration save/load (including trackbar values) 19 | * Key binding (e.g. for trackbar control, configuration save/load) 20 | * Video capturing and modification 21 | * Work with images 22 | * Work with text 23 | * Frames transformation 24 | 25 | 26 | Requirements 27 | ------------ 28 | 29 | 1. Python 3.6+ 30 | 2. ``opencv-python`` (or variants) 31 | 32 | Quick install with third-parties: ``$ pip install opencv-proto[all]`` 33 | 34 | 35 | Table of Contents 36 | ----------------- 37 | 38 | .. toctree:: 39 | :maxdepth: 4 40 | 41 | quickstart 42 | api 43 | -------------------------------------------------------------------------------- /docs/source/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | 4 | Color Palette 5 | ~~~~~~~~~~~~~ 6 | 7 | Let's replace 37 lines of source code from `Trackbar as the Color Palette `_ 8 | tutorial with ``ocvproto``-based implementation: 9 | 10 | .. code-block:: python 11 | 12 | from ocvproto.toolbox import WindowManager, Canvas 13 | 14 | with WindowManager() as wm: 15 | # Window manager will create a default window for us if none provided. 16 | # Default window is available through .window property. 17 | 18 | # With the help of .add_trackbar_group() we create three trackbars, 19 | # to adjust our color values for R, G and B. Batch apply `max` value 20 | # to all three trackbars. 21 | rgb = wm.window.add_trackbar_group(['R', 'G', 'B'], max=255) 22 | 23 | # If one doesn't configure their own Application object, 24 | # Window manager will instantiate one automatically, using default settings. 25 | for _ in wm.app.loop(): 26 | # Most of the time we'll need a loop to process our frames. 27 | # Application object (available through .app property) offers us such a loop. 28 | 29 | # Lastly we create a canvas object using RGB value from trackbars, 30 | # and pass its' frame to .set_frame() shortcut. 31 | # That shortcut puts the frame into default window. 32 | wm.set_frame(Canvas(512, 300, color=rgb)) 33 | 34 | 35 | Camera capture 36 | ~~~~~~~~~~~~~~ 37 | 38 | Now let's capture video camera stream into ``ocvproto.avi`` file, being able to adjust blur. 39 | 40 | Let's also setup config filepath (``ocvproto.json``) - this allows us to store current trackbar values 41 | (``s`` key) and load them (``r`` key). It is useful to restore settings between sessions. 42 | 43 | We bind ``z`` key to take camera shots. 44 | 45 | .. code-block:: python 46 | 47 | from ocvproto.toolbox import WindowManager, Camera 48 | 49 | with WindowManager() as wm: 50 | 51 | # We instruct our application to store settings into file. 52 | wm.app.set_config('ocvproto.json') 53 | 54 | # We create two trackbars to adjust blur. 55 | blur = wm.window.add_trackbar_group(['x', 'y'], 'Blur', default=1) 56 | 57 | # We initiate default (first available) camera connection. 58 | with Camera() as cam: 59 | 60 | # Let's add trackbars for adjustable camera properties (contrast, brightness, etc.). 61 | wm.window.add_trackbar_group(cam.describe_properties(), default=1, max=255) 62 | 63 | # You can bind keys to actions. 64 | # Here we bind `z` to trigger .cam.dump_image() to take stills. 65 | wm.app.bind_key('z', cam.dump_image) 66 | 67 | for _ in wm.app.loop(): 68 | # Read a frame from camera, we'll work with. 69 | cam.read() 70 | 71 | # Now we blur that frame. 72 | cam.blur(blur) 73 | 74 | # And we dump the frame into the file. 75 | # If dumping parameters were not set up before 76 | # .dump() shortcut will use defaults 77 | # (e.g. `ocvproto.avi` name, XVID codec). 78 | cam.dump() 79 | 80 | # Show the frame. 81 | wm.set_frame(cam) 82 | 83 | -------------------------------------------------------------------------------- /docs/source/rst_guide.rst: -------------------------------------------------------------------------------- 1 | RST Quick guide 2 | =============== 3 | 4 | Online reStructuredText editor - http://rst.ninjs.org/ 5 | 6 | 7 | Main heading 8 | ============ 9 | 10 | 11 | Secondary heading 12 | ----------------- 13 | 14 | Minor heading 15 | ~~~~~~~~~~~~~ 16 | 17 | 18 | Typography 19 | ---------- 20 | 21 | **Bold** 22 | 23 | `Italic` 24 | 25 | ``Accent`` 26 | 27 | 28 | 29 | Blocks 30 | ------ 31 | 32 | Double colon to consider the following paragraphs preformatted:: 33 | 34 | This text is preformated. Can be used for code samples. 35 | 36 | 37 | .. code-block:: python 38 | 39 | # code-block accepts language name to highlight code 40 | # E.g.: python, html 41 | import this 42 | 43 | 44 | .. note:: 45 | 46 | This text will be rendered as a note block (usually green). 47 | 48 | 49 | .. warning:: 50 | 51 | This text will be rendered as a warning block (usually red). 52 | 53 | 54 | 55 | Lists 56 | ----- 57 | 58 | 1. Ordered item 1. 59 | 60 | Indent paragraph to make in belong to the above list item. 61 | 62 | 2. Ordered item 2. 63 | 64 | 65 | + Unordered item 1. 66 | + Unordered item . 67 | 68 | 69 | 70 | Links 71 | ----- 72 | 73 | :ref:`Documentation inner link label ` 74 | 75 | .. _some-marker: 76 | 77 | 78 | `Outer link label `_ 79 | 80 | Inline URLs are converted to links automatically: http://github.com/idlesign/makeapp/ 81 | 82 | 83 | Images 84 | ------ 85 | 86 | .. image:: path_to_image/image.png 87 | 88 | 89 | Automation 90 | ---------- 91 | 92 | http://sphinx-doc.org/ext/autodoc.html 93 | 94 | .. automodule:: my_module 95 | :members: 96 | 97 | .. autoclass:: my_module.MyClass 98 | :members: do_this, do_that 99 | :inherited-members: 100 | :undoc-members: 101 | :private-members: 102 | :special-members: 103 | :show-inheritance: 104 | 105 | -------------------------------------------------------------------------------- /ocvproto/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | VERSION = (0, 3, 0) 4 | """Application version number tuple.""" 5 | 6 | VERSION_STR = '.'.join(map(str, VERSION)) 7 | """Application version number string.""" -------------------------------------------------------------------------------- /ocvproto/app/__init__.py: -------------------------------------------------------------------------------- 1 | from .application import Application 2 | from .config import Config 3 | from .keys import Key 4 | -------------------------------------------------------------------------------- /ocvproto/app/application.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Callable, Union, Optional, Tuple, List, Dict 3 | 4 | from .config import Config 5 | from .keys import Key 6 | from ..backend import cv 7 | 8 | TypeKey = Union[str, int] 9 | TypeConfig = Optional[Union[str, Path]] 10 | 11 | 12 | class Application: 13 | """Represents ocvproto application.""" 14 | 15 | def __init__(self, *, config: TypeConfig = None): 16 | """ 17 | 18 | :param config: Configuration to be used. 19 | 20 | """ 21 | self._config: Optional[Config] = None 22 | self._key_map = {} 23 | self._loop_func = lambda: None 24 | self._hooks: Dict[str, List[Callable]] = { 25 | 'config_save': [], 26 | 'config_load': [], 27 | } 28 | self.set_config(config) 29 | 30 | def set_config(self, config: TypeConfig, keys: Tuple[TypeKey, TypeKey] = None): 31 | """Sets configuration from the app. 32 | 33 | :param config: Configuration object. 34 | :param keys: Keys tuple to save and load configuration. 35 | 36 | """ 37 | if config is None: 38 | return 39 | 40 | config = Config(config) 41 | 42 | self._config = config 43 | self._bind_config_keys(keys) 44 | self.config_load() 45 | 46 | def _bind_config_keys(self, keys: Tuple[TypeKey, TypeKey] = None): 47 | bind = self.bind_key 48 | save, load = keys or ('s', 'r') 49 | bind(save, self.config_save) 50 | bind(load, self.config_load) 51 | 52 | def config_save(self): 53 | """Saves current configuration to config file.""" 54 | config = self._config 55 | 56 | if config: 57 | for hook in self._hooks['config_save']: 58 | hook(config) 59 | config.save() 60 | 61 | def config_load(self): 62 | """Loads a configuration from config file.""" 63 | config = self._config 64 | 65 | if config: 66 | config.load() 67 | 68 | for hook in self._hooks['config_load']: 69 | hook(config) 70 | 71 | def hook_register(self, key: str, func: Callable): 72 | """Registers a hook. 73 | 74 | :param key: Hooks group key. 75 | :param func: Hook function to add to group. 76 | 77 | """ 78 | self._hooks[key].append(func) 79 | 80 | def bind_key(self, key: TypeKey, func: Callable): 81 | """Binds a key to a function. 82 | 83 | :param key: 84 | :param func: 85 | 86 | """ 87 | if not isinstance(key, int): 88 | key = ord(key) 89 | 90 | self._key_map[key] = func 91 | 92 | def set_loop_func(self, func: Callable): 93 | """Sets a function to perform in a main app loop.""" 94 | self._loop_func = func 95 | 96 | def loop(self): 97 | """Main application loop. 98 | Handles keys listening and issues a loop function (see .set_loop_func()). 99 | 100 | """ 101 | esc = Key.ESC 102 | key_map = self._key_map 103 | func = self._loop_func 104 | 105 | wait = cv.waitKey 106 | 107 | while True: 108 | 109 | yield 110 | 111 | func() 112 | 113 | key = wait(5) & 0xff 114 | 115 | if key == esc: 116 | break 117 | 118 | key_handler = key_map.get(key) 119 | if key_handler: 120 | key_handler() 121 | -------------------------------------------------------------------------------- /ocvproto/app/config.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from typing import Union, Any 4 | 5 | 6 | class Config: 7 | """Represent an interface to configuration file.""" 8 | 9 | def __init__(self, fpath: Union[str, Path]): 10 | """ 11 | 12 | :param fpath: Configuration file path. 13 | 14 | """ 15 | self._fpath = Path(fpath) 16 | self._data = { 17 | 'metadata': {}, 18 | 'data': {}, 19 | } 20 | 21 | def set_data(self, key: str, value: Any): 22 | """Places the data into config section denoted by key. 23 | 24 | :param key: 25 | :param value: 26 | 27 | """ 28 | self._data['data'][key] = value 29 | 30 | def get_data(self, key: str, default: Any = None): 31 | """Reads data from a config section denoted by key. 32 | 33 | :param key: 34 | :param default: Default values to return. 35 | 36 | """ 37 | return self._data['data'].get(key, default) 38 | 39 | def save(self): 40 | """Saves configuration to file.""" 41 | fpath = self._fpath 42 | 43 | with open(str(fpath), 'w') as f: 44 | f.write(json.dumps(self._data)) 45 | 46 | def load(self): 47 | """Loads configuration from file.""" 48 | fpath = self._fpath 49 | 50 | if not fpath.exists(): 51 | return 52 | 53 | with open(f'{fpath}') as f: 54 | self._data = json.loads(f.read()) 55 | -------------------------------------------------------------------------------- /ocvproto/app/keys.py: -------------------------------------------------------------------------------- 1 | 2 | class Key: 3 | """Keys registry. Can be used for binding actions.""" 4 | 5 | BACKSPACE = 8 6 | ENTER = 13 7 | ESC = 27 8 | SPACE = 32 9 | 10 | LEFT = 81 11 | UP = 82 12 | RIGHT = 83 13 | DW = 84 14 | 15 | HOME = 80 16 | PGUP = 85 17 | PGDW = 86 18 | END = 87 19 | -------------------------------------------------------------------------------- /ocvproto/backend.py: -------------------------------------------------------------------------------- 1 | import cv2 as cv # noqa 2 | import numpy as np # noqa 3 | -------------------------------------------------------------------------------- /ocvproto/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | class OcvprotoException(Exception): 3 | """Base ocvproto exception.""" 4 | 5 | 6 | class SourceError(OcvprotoException): 7 | """Error getting source,""" 8 | -------------------------------------------------------------------------------- /ocvproto/frame.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pathlib import Path 3 | from typing import Tuple, Union 4 | 5 | from .backend import cv, np 6 | from .misc.colors import TypeColor, to_rgb 7 | 8 | 9 | OcvFrame = np.core.multiarray.ndarray 10 | AnyFrame = Union[OcvFrame, 'Frame'] 11 | TypePoint = Tuple[int, int] 12 | 13 | 14 | class Frame: 15 | """Represents a frame.""" 16 | 17 | __slots__ = ['_frame'] 18 | 19 | def __init__(self, frame: OcvFrame = None): 20 | self._frame = frame 21 | 22 | @property 23 | def width(self) -> int: 24 | """Width""" 25 | return int(self.frame.shape[1]) 26 | 27 | @property 28 | def height(self) -> int: 29 | """Height""" 30 | return int(self.frame.shape[0]) 31 | 32 | @property 33 | def frame(self) -> OcvFrame: 34 | return self._frame 35 | 36 | def _set_frame(self, frame: OcvFrame) -> 'Frame': 37 | self._frame = frame 38 | return self 39 | 40 | def dump(self, fpath: Union[str, Path] = None): 41 | """Dumps frame into a file. 42 | 43 | :param fpath: Filepath to store image into. 44 | If not set, name is generated automatically. 45 | 46 | """ 47 | if not fpath: 48 | fpath = f'cov_{datetime.now()}.png' 49 | 50 | cv.imwrite(str(fpath), self.frame) 51 | 52 | def fill(self, color: TypeColor): 53 | """Fills the canvas with the given color. 54 | 55 | :param color: 56 | 57 | """ 58 | self.frame[:] = to_rgb(color) 59 | 60 | def resize(self, width: int, height: int) -> 'Frame': 61 | """Resizes the current frame inplace. 62 | 63 | :param width: 64 | :param height: 65 | 66 | """ 67 | return self._set_frame(cv.resize(self.frame, (width, height))) 68 | 69 | def absdiff(self, frame: AnyFrame) -> 'Frame': 70 | """Returns absolute difference between 71 | the current and a given frame as a new Source. 72 | 73 | :param frame: 74 | 75 | """ 76 | return Frame(cv.absdiff(self.frame, getattr(frame, 'frame', frame))) 77 | 78 | def make_gray(self) -> 'Frame': 79 | """Makes the current frame grayscale inplace.""" 80 | return self._set_frame(cv.cvtColor(self.frame, cv.COLOR_BGR2GRAY)) 81 | 82 | def make_rgb(self) -> 'Frame': 83 | """Makes the current frame RGB inplace.""" 84 | return self._set_frame(cv.cvtColor(self.frame, cv.COLOR_BGR2RGB)) 85 | 86 | def canny(self, thr_1: int, thr_2: int) -> 'Frame': 87 | """Applies Canny Edge Detection algorithm 88 | to the current frame inplace. 89 | 90 | :param thr_1: 91 | :param thr_2: 92 | 93 | """ 94 | return self._set_frame(cv.Canny(self.frame, thr_1, thr_2)) 95 | 96 | def dilate(self, element: AnyFrame, iterations: int = None) -> 'Frame': 97 | """Dilates the current frame inplace. 98 | 99 | :param element: 100 | :param iterations: 101 | 102 | """ 103 | return self._set_frame(cv.dilate(self.frame, getattr(element, 'frame', element), iterations=iterations)) 104 | 105 | def blur(self, ksize: Tuple[int, int]) -> 'Frame': 106 | """Blures the current frame inplace. 107 | 108 | :param ksize: Kernel size tuple (width, height) 109 | 110 | """ 111 | return self._set_frame(cv.blur(self.frame, ksize)) 112 | 113 | def draw_rectangle(self, *, pos: TypePoint, width: int, height: int, color: TypeColor = None): 114 | """Draws a rectangle. 115 | 116 | :param pos: Top left corner (x, y). 117 | :param width: 118 | :param height: 119 | :param color: 120 | 121 | """ 122 | x, y = pos 123 | return self._set_frame(cv.rectangle( 124 | self.frame, pos, (x + width, y + height), to_rgb(color or (255, 255, 255)), -1)) 125 | -------------------------------------------------------------------------------- /ocvproto/misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/opencv-proto/35bff19f71dc8823bc84cdb87e721a41aaf08a40/ocvproto/misc/__init__.py -------------------------------------------------------------------------------- /ocvproto/misc/canvas.py: -------------------------------------------------------------------------------- 1 | from .colors import TypeColor 2 | from ..backend import np 3 | from ..frame import Frame 4 | 5 | 6 | class Canvas(Frame): 7 | """Represents a canvas.""" 8 | 9 | __slots__ = ['_frame'] 10 | 11 | def __init__(self, width: int = 640, height: int = 480, *, channels: int = 3, color: TypeColor = None): 12 | """ 13 | 14 | :param width: 15 | :param height: 16 | :param channels: 17 | :param color: 18 | 19 | """ 20 | super().__init__(np.zeros((height, width, channels), np.uint8)) 21 | 22 | if color: 23 | self.fill(color) 24 | -------------------------------------------------------------------------------- /ocvproto/misc/colors.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Tuple 2 | 3 | TypeRgb = Tuple[int, int, int] 4 | TypeColor = Union[int, str, TypeRgb] 5 | 6 | COLORS = { 7 | 'white': (255, 255, 255), 8 | 'black': (0, 0, 0), 9 | 'red': (255, 0, 0), 10 | 'lime': (0, 255, 0), 11 | 'blue': (0, 0, 255), 12 | 'yellow': (255, 255, 0), 13 | 'cyan': (0, 255, 255), 14 | 'magenta': (255, 0, 255), 15 | 'silver': (192, 192, 192), 16 | 'gray': (128, 128, 128), 17 | 'maroon': (128, 0, 0), 18 | 'olive': (128, 128, 0), 19 | 'green': (0, 128, 0), 20 | 'purple': (128, 0, 128), 21 | 'teal': (0, 128, 128), 22 | 'navy': (0, 0, 128), 23 | 'brown': (165, 42, 42), 24 | 'orange': (255, 165, 0), 25 | 'gold': (255, 215, 0), 26 | 'khaki': (240, 230, 140), 27 | 'indigo': (75, 0, 130), 28 | 'violet': (238, 130, 238), 29 | 'pink': (255, 192, 203), 30 | 'beige': (245, 245, 220), 31 | 'wheat': (245, 222, 179), 32 | 'chocolate': (210, 105, 30), 33 | 'tan': (210, 180, 140), 34 | 'linen': (250, 240, 230), 35 | } 36 | """Color aliases to RGB tuples map.""" 37 | 38 | 39 | def to_rgb(value: TypeColor) -> Tuple[int, int, int]: 40 | """Translates the given color value to RGB tuple. 41 | 42 | :param value: 43 | 44 | """ 45 | if isinstance(value, tuple): 46 | return value 47 | 48 | if isinstance(value, int): 49 | value = (value, value, value) 50 | 51 | elif isinstance(value, str): 52 | value = COLORS[value] 53 | 54 | return value 55 | -------------------------------------------------------------------------------- /ocvproto/misc/text.py: -------------------------------------------------------------------------------- 1 | from .colors import to_rgb, TypeColor 2 | from ..backend import cv 3 | from ..frame import TypePoint 4 | from ..sources.base import AnyFrame 5 | 6 | 7 | class Text: 8 | """Represents a text that can be placed into a frame.""" 9 | 10 | # | FONT_ITALIC 11 | face_map = { 12 | 'small': cv.FONT_HERSHEY_PLAIN, 13 | 'normal': cv.FONT_HERSHEY_SIMPLEX, 14 | 'duplex': cv.FONT_HERSHEY_DUPLEX, 15 | 'triplex': cv.FONT_HERSHEY_TRIPLEX, 16 | 'complex': cv.FONT_HERSHEY_COMPLEX, 17 | 'complex-sm': cv.FONT_HERSHEY_COMPLEX_SMALL, 18 | 'hand': cv.FONT_HERSHEY_SCRIPT_SIMPLEX, 19 | 'hand-complex': cv.FONT_HERSHEY_SCRIPT_COMPLEX, 20 | } 21 | 22 | def __init__( 23 | self, 24 | val: str = None, 25 | *, 26 | face: str = None, 27 | scale: float = None, 28 | color: TypeColor = None, 29 | pos: TypePoint = None, 30 | weight: int = None 31 | ): 32 | """ 33 | 34 | :param val: Text value itself. 35 | :param face: Font face alias (see .face_map keys). Default: normal 36 | :param scale: Scale factor. Default: 1 37 | :param color: Color RGB tuple or alias (see `COLORS`). Default: white 38 | :param pos: Position tuple (x, y) in frame from top-left. Default: (20, 20) 39 | :param weight: Line thickness. Default: 1 40 | 41 | """ 42 | self._val = val or '' 43 | self._face = self.face_map.get(face, self.face_map['normal']) 44 | self._scale = scale or 1 45 | self._color = to_rgb(color or 'white') 46 | self._weight = weight or 1 47 | self._pos = pos or (20, 20) 48 | 49 | @classmethod 50 | def put_on_demo(cls, frame: AnyFrame, text: str = 'Test Text 1 2 3 4 5'): 51 | """Demonstrates available font faces applying all of them to the frame. 52 | 53 | :param frame: Frame to apply text to. 54 | :param text: Text value to on frame. 55 | 56 | """ 57 | for idx, face in enumerate(Text.face_map, 1): 58 | cls(f'{face}: {text}', face=face, pos=(20, idx * 35)).put_on(frame) 59 | 60 | def put_on(self, frame: AnyFrame, text: str = None, *, pos: TypePoint = None): 61 | """Applies text to the frame. 62 | 63 | :param frame: Frame to apply text to. 64 | :param text: Text value to set on frame. If not set, value from initializer is used. 65 | :param pos: Position tuple (x, y) in frame from top-left. Default: (20, 20) 66 | 67 | """ 68 | cv.putText( 69 | getattr(frame, 'frame', frame), 70 | text or self._val, 71 | pos or self._pos, 72 | self._face, 73 | self._scale, 74 | self._color, 75 | self._weight 76 | ) 77 | -------------------------------------------------------------------------------- /ocvproto/primitives/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/opencv-proto/35bff19f71dc8823bc84cdb87e721a41aaf08a40/ocvproto/primitives/__init__.py -------------------------------------------------------------------------------- /ocvproto/primitives/legend.py: -------------------------------------------------------------------------------- 1 | from typing import Sequence, Dict 2 | 3 | try: 4 | from colorhash import ColorHash 5 | 6 | except ImportError: # pragma: nocover 7 | raise ImportError('Legend requires `colorhash` library: pip install colorhash') 8 | 9 | from ..frame import Frame, TypePoint 10 | from ..misc.text import Text 11 | from ..misc.colors import TypeRgb 12 | 13 | 14 | class Legend: 15 | """Represents a color-legend for labels.""" 16 | 17 | def __init__( 18 | self, 19 | labels: Sequence[str], 20 | pos: TypePoint = None, 21 | width: int = None, 22 | gap: int = None 23 | ): 24 | """ 25 | 26 | :param labels: Strings to get colors for. 27 | :param pos: Position (x, y) to place top left legend corner. Default: (20, 20) 28 | :param width: Default: 250 29 | :param gap: Base gap (also a height for each color stripe). Default: 25 30 | 31 | """ 32 | self.labels: Dict[str, TypeRgb] = {label: ColorHash(label).rgb for label in labels} 33 | self._gap = gap or 25 34 | self._pos = pos or (20, 20) 35 | self._width = width or 250 36 | 37 | def put_on(self, frame: Frame, *, pos: TypePoint = None): 38 | """Applies the legend to the frame. 39 | 40 | :param frame: Frame to apply the legend to. 41 | :param pos: Position (x, y) to place top left legend corner. Default: (20, 20) 42 | 43 | """ 44 | gap = self._gap 45 | width = self._width 46 | pos = pos or self._pos 47 | 48 | text_left, text_top = pos 49 | 50 | for idx, (label, color) in enumerate(self.labels.items()): 51 | step = (idx * gap) + text_top 52 | 53 | frame.draw_rectangle( 54 | pos=(text_left, step), 55 | width=width, 56 | height=gap, 57 | color=color, 58 | ) 59 | 60 | # todo remove +17 hardcoded for the given text scale 61 | Text(label, scale=0.5, pos=(text_left + 5, step + 17)).put_on(frame) 62 | -------------------------------------------------------------------------------- /ocvproto/sources/__init__.py: -------------------------------------------------------------------------------- 1 | from .camera import Camera 2 | from .image import Image 3 | from .video import Video 4 | -------------------------------------------------------------------------------- /ocvproto/sources/base.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Union 3 | 4 | from ..frame import Frame, OcvFrame, AnyFrame 5 | 6 | 7 | class Source(Frame): 8 | """Basic source.""" 9 | 10 | def __init__(self, src: Union[int, str, AnyFrame] = None): 11 | """ 12 | 13 | :param src: Source id (int), path (str) or frame (np array) 14 | 15 | """ 16 | frame = None 17 | 18 | if src is not None: 19 | if not isinstance(src, (str, int, Path)): 20 | frame, src = src, '' 21 | 22 | elif isinstance(src, Path): 23 | src = str(src) 24 | 25 | super().__init__(frame) 26 | 27 | if src is not 0: 28 | src = src or '' 29 | 30 | self._src = src 31 | 32 | @property 33 | def frame(self) -> OcvFrame: 34 | """Current frame.""" 35 | frame = self._frame 36 | 37 | if frame is None: 38 | frame = self.read()._frame 39 | 40 | return frame 41 | 42 | def read(self) -> 'Source': # pragma: nocover 43 | """Read and return current frame.""" 44 | raise NotImplementedError 45 | -------------------------------------------------------------------------------- /ocvproto/sources/camera.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from .video import Video, Property 4 | from ..backend import cv 5 | 6 | 7 | class Camera(Video): 8 | """Represents a camera device.""" 9 | 10 | def __init__(self, src: Union[int, str] = 0): 11 | """ 12 | 13 | :param src: Device path (str) or id (int). Default 0. 14 | 15 | E.g.: 16 | * '/dev/video0' 17 | * 0 18 | * 1 19 | 20 | """ 21 | super().__init__(src) 22 | 23 | brightness = Property(cv.CAP_PROP_BRIGHTNESS) 24 | """Brightness""" 25 | 26 | contrast = Property(cv.CAP_PROP_CONTRAST) 27 | """Contrast""" 28 | 29 | saturation = Property(cv.CAP_PROP_SATURATION) 30 | """Saturation""" 31 | 32 | hue = Property(cv.CAP_PROP_HUE) 33 | """Hue""" 34 | 35 | gain = Property(cv.CAP_PROP_GAIN) 36 | """Gain""" 37 | 38 | exposure = Property(cv.CAP_PROP_EXPOSURE) 39 | """Exposure""" 40 | -------------------------------------------------------------------------------- /ocvproto/sources/image.py: -------------------------------------------------------------------------------- 1 | from .base import Source 2 | from ..backend import cv 3 | from ..exceptions import SourceError 4 | 5 | 6 | class Image(Source): 7 | """Represents an image.""" 8 | 9 | def read(self) -> 'Image': 10 | frame = cv.imread(self._src) 11 | 12 | if frame is None: 13 | raise SourceError(f"Unable to load image '{self._src}'.") 14 | 15 | return self._set_frame(frame) 16 | -------------------------------------------------------------------------------- /ocvproto/sources/video.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | from pathlib import Path 3 | from typing import Union, Dict, Any 4 | 5 | from .base import Source, OcvFrame 6 | from .image import Image 7 | from ..backend import cv 8 | from ..exceptions import SourceError 9 | from ..frame import AnyFrame 10 | 11 | 12 | class Property: 13 | """Represents a capture video property with restrictions.""" 14 | 15 | def __init__(self, cv_prop: int, *, max: int = None): 16 | self._cv_prop = cv_prop 17 | self.max = max 18 | 19 | def __get__(self, instance, cls): 20 | return instance._cap.get(self._cv_prop) 21 | 22 | def __set__(self, instance, value): 23 | instance._cap.set(self._cv_prop, value) 24 | 25 | 26 | class Video(Source): 27 | """Represents a video.""" 28 | 29 | def __init__(self, src: Union[str, OcvFrame]): 30 | super().__init__(src) 31 | self._cap = None 32 | self._writer = None 33 | 34 | def __enter__(self) -> 'Video': 35 | self._cap = cv.VideoCapture(self._src) 36 | return self 37 | 38 | def __exit__(self, exc_type, exc_val, exc_tb): 39 | self._cap.release() 40 | 41 | writer = self._writer 42 | 43 | if writer is not None: 44 | writer.release() 45 | 46 | def get_image(self) -> Image: 47 | """Returns image object from the current frame.""" 48 | return Image(self.frame) 49 | 50 | def dump_image(self, fpath: Union[str, Path] = None): 51 | """Dumps the image into a file. 52 | 53 | :param fpath: Filepath to store image into. 54 | If not set, name is generated automatically. 55 | 56 | """ 57 | self.get_image().dump(fpath) 58 | 59 | sharpness = Property(cv.CAP_PROP_SHARPNESS) 60 | """Sharpness""" 61 | 62 | gamma = Property(cv.CAP_PROP_GAMMA) 63 | """Gamma""" 64 | 65 | focus = Property(cv.CAP_PROP_FOCUS) 66 | """Focus""" 67 | 68 | zoom = Property(cv.CAP_PROP_ZOOM) 69 | """Zoom""" 70 | 71 | width = Property(cv.CAP_PROP_FRAME_WIDTH, max=4096) 72 | """Width""" 73 | 74 | height = Property(cv.CAP_PROP_FRAME_HEIGHT, max=3072) 75 | """Height""" 76 | 77 | fps = Property(cv.CAP_PROP_FPS, max=60) 78 | """FPS""" 79 | 80 | @property 81 | def codec(self) -> str: 82 | """FOURCC codec alias.""" 83 | val = int(self._cap.get(cv.CAP_PROP_FOURCC)) 84 | return ''.join([chr((val >> 8 * i) & 0xFF) for i in range(4)]) 85 | 86 | def set_property(self, name: str, value: int): 87 | """Helper method to set property value. 88 | 89 | :param name: Property name. 90 | :param value: 91 | 92 | """ 93 | setattr(self, name, value) 94 | 95 | def describe_properties(self) -> Dict[str, Any]: 96 | """Returns descriptions for CV properties found 97 | in the class of this object and its bases. 98 | 99 | One can initialize trackbars with these descriptions: 100 | see Window.add_trackbar_group() 101 | 102 | """ 103 | properties = {} 104 | 105 | cls = self.__class__ 106 | for cls in [cls] + list(cls.__bases__): 107 | 108 | for attr, val in cls.__dict__.items(): 109 | 110 | if isinstance(val, Property): 111 | properties[attr] = { 112 | 'default': getattr(self, attr), 113 | 'callback': partial(self.set_property, attr), 114 | 'max': val.max 115 | } 116 | 117 | return properties 118 | 119 | def dump_setup( 120 | self, 121 | fpath: Union[str, Path] = 'ocvproto.avi', 122 | *, 123 | width: int = None, 124 | height: int = None, 125 | fps: Union[int, float] = None, 126 | codec: str = 'XVID', 127 | ) -> cv.VideoWriter: 128 | """Configures write parameters. 129 | Returns opencv writer object. 130 | 131 | :param fpath: Filepath. 132 | :param width: 133 | :param height: 134 | :param fps: Frames per second. 135 | :param codec: FOURCC codec alias. 136 | 137 | """ 138 | if codec is None: 139 | codec = self.codec 140 | 141 | fcc = cv.VideoWriter_fourcc(*codec) 142 | 143 | writer = cv.VideoWriter( 144 | f'{fpath}', 145 | fcc, 146 | fps or int(self.fps), 147 | (width or int(self.width), height or int(self.height)) 148 | ) 149 | self._writer = writer 150 | return writer 151 | 152 | def dump(self, frame: AnyFrame = None): 153 | """Writes the current or the given frame. 154 | Automatically configures writer object is needed. 155 | 156 | """ 157 | if frame is None: 158 | frame = self.frame 159 | 160 | else: 161 | frame = getattr(frame, 'frame', frame) 162 | 163 | writer = self._writer 164 | if writer is None: 165 | writer = self.dump_setup() 166 | 167 | writer.write(frame) 168 | 169 | def read(self) -> 'Video': 170 | success, frame = self._cap.read() 171 | 172 | if not success: 173 | raise SourceError(f"Unable to read from '{self._src}'.") 174 | 175 | return self._set_frame(frame) 176 | -------------------------------------------------------------------------------- /ocvproto/toolbox.py: -------------------------------------------------------------------------------- 1 | from .app import Application, Key, Config # noqa 2 | from .frame import Frame # noqa 3 | from .misc.canvas import Canvas # noqa 4 | from .misc.colors import COLORS, to_rgb # noqa 5 | from .misc.text import Text # noqa 6 | from .sources import Video, Image, Camera # noqa 7 | from .ui import WindowManager, Window, Trackbar # noqa 8 | -------------------------------------------------------------------------------- /ocvproto/ui/__init__.py: -------------------------------------------------------------------------------- 1 | from .trackbars.base import Trackbar 2 | from .window import Window 3 | from .wm import WindowManager 4 | -------------------------------------------------------------------------------- /ocvproto/ui/trackbars/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import Trackbar 2 | -------------------------------------------------------------------------------- /ocvproto/ui/trackbars/base.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Callable 2 | 3 | from ...backend import cv 4 | 5 | TypeNumber = Union[int, float] 6 | 7 | 8 | def noop_callback(value: TypeNumber): # pragma: nocover 9 | return None 10 | 11 | 12 | class Trackbar: 13 | """Represents a trackbar.""" 14 | 15 | def __init__( 16 | self, 17 | name, 18 | *, 19 | max: TypeNumber = None, 20 | default: TypeNumber = None, 21 | callback: Callable = None, 22 | step: TypeNumber = None, 23 | keys: str = None 24 | ): 25 | """ 26 | 27 | :param name: Name to show in UI and address this in opencv api. 28 | :param max: Max value. Default: 100 29 | :param default: Default (current) value. Default: 0 30 | :param callback: Function to be called on trackbar value change through UI. 31 | :param step: Step to inc/dec trackbar value. Default: 1 32 | :param keys: Two-letter string to represent keys to inc and dec value. 33 | 34 | """ 35 | self.name = name 36 | 37 | self._default = default or 0 38 | self._max = max or 100 39 | 40 | self.keys = {} 41 | 42 | if keys: 43 | self.set_keys(keys) 44 | 45 | self.step = step or 1 46 | 47 | self.callback = callback or noop_callback 48 | self._window_name = None 49 | self._value = default 50 | 51 | def __float__(self): 52 | return float(self.value) 53 | 54 | def __index__(self): 55 | return int(self.value) 56 | 57 | def __int__(self): 58 | return int(self.value) 59 | 60 | def set_keys(self, keys: str): 61 | """Set keys to inc/dec trackbar value. 62 | 63 | :param keys: Two-letter string to represent keys to inc and dec value. 64 | 65 | """ 66 | assert len(keys) == 2, 'Trackbar `keys` param is expected to be 2 char length.' 67 | self.keys = {keys[0]: self.dec, keys[1]: self.inc} 68 | 69 | def bind(self, window_name: str): 70 | """Binds the trackabr to the given window. 71 | 72 | :param window_name: 73 | 74 | """ 75 | self._window_name = window_name 76 | cv.createTrackbar(self.name, window_name, int(self._default), self._max, self.onChange) 77 | 78 | def inc(self): 79 | """Increments the current value.""" 80 | self.value += self.step 81 | 82 | def dec(self): 83 | """Decrements the current value.""" 84 | self.value -= self.step 85 | 86 | def get_value(self) -> TypeNumber: 87 | """Force getting current value.""" 88 | return cv.getTrackbarPos(self.name, self._window_name) 89 | 90 | def _get_value(self) -> TypeNumber: 91 | return self._value or self._default 92 | 93 | def _set_value(self, val: TypeNumber): 94 | cv.setTrackbarPos(self.name, self._window_name, val) 95 | 96 | value = property(_get_value, _set_value) 97 | """Current trackbar value.""" 98 | 99 | def onChange(self, val: TypeNumber): 100 | """Issued on value change from UI.""" 101 | self.callback(val) 102 | self._value = val 103 | -------------------------------------------------------------------------------- /ocvproto/ui/window.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from operator import ior 3 | from typing import Union, Dict, List, Tuple 4 | 5 | from .trackbars import Trackbar 6 | from ..backend import cv 7 | from ..frame import Frame, AnyFrame 8 | 9 | WIN_COUNT = 0 10 | 11 | 12 | class Window: 13 | """Represents a window.""" 14 | 15 | def __init__(self, name: str = None): 16 | """ 17 | 18 | :param name: Window name. If not set, automatically generated. 19 | 20 | """ 21 | if not name: 22 | global WIN_COUNT 23 | WIN_COUNT += 1 24 | # Not a uuid to be friendly to Config save/load. 25 | name = f'{WIN_COUNT}' 26 | 27 | self.name = name 28 | self.trackbars = {} 29 | self.create() 30 | self._frame = None 31 | 32 | def create(self, *, autosize=True): 33 | """Creates a window. 34 | 35 | :param autosize: If try, window is automatically 36 | sized to a content. 37 | 38 | """ 39 | flags = [ 40 | cv.WINDOW_AUTOSIZE if autosize else cv.WINDOW_NORMAL, 41 | cv.WINDOW_KEEPRATIO 42 | ] 43 | 44 | cv.namedWindow(self.name, reduce(ior, flags)) 45 | 46 | def position(self, *, x: int, y: int): 47 | """Positions the window.""" 48 | cv.moveWindow(self.name, x, y) 49 | 50 | def resize(self, *, width: int, height: int): 51 | """Resizes the window. 52 | 53 | :param width: 54 | :param height: 55 | 56 | """ 57 | cv.resizeWindow(self.name, width, height) 58 | 59 | def add_trackbar(self, *trackbars): 60 | """Add the given trackbars to the window. 61 | 62 | :param trackbars: 63 | 64 | """ 65 | for trackbar in trackbars: 66 | self.trackbars[trackbar.name] = trackbar 67 | trackbar.bind(self.name) 68 | return self 69 | 70 | def add_trackbar_group( 71 | self, 72 | definitions: Union[int, Dict[str, dict], List[dict], List[str]], 73 | prefix: str = '', 74 | **common_kwargs 75 | ) -> Tuple: # not Tuple[Trackbar, ...] to satisfy typechecker, when passing trackbar as param to ocv functions. 76 | """A shortcut to batch create trackbars in a declarative way. 77 | 78 | :param definitions: Definitions to construct trackbars. 79 | 80 | * Integer: 81 | * 2 - create two trackbars with generated titles and default params. 82 | 83 | * List: 84 | * ['one', 'two', 'three'] - 85 | - create 3 trackbars with the given titles and default params. 86 | 87 | * [{'keys': 'kl'}, {}] - 88 | - create 2 trackbars with generated titles and default params. 89 | 90 | * Dictionary: 91 | * {'y': {'keys': 'kl'}, 'x': {'step': 20}} 92 | - create 2 trackbars with the given titles and params. 93 | 94 | 95 | :param prefix: Prefix to add to trackbars titles. 96 | 97 | :param common_kwargs: Common keyword arguments to pass to all trackbars. 98 | 99 | """ 100 | trackbars = [] 101 | 102 | if prefix: 103 | prefix = f'{prefix} ' 104 | 105 | if isinstance(definitions, int): 106 | # Just a number of trackbars to generate. 107 | definitions = {f'{idx+1}': {} for idx in range(definitions)} 108 | 109 | elif isinstance(definitions, list): 110 | definitions_ = {} 111 | 112 | for idx, definition in enumerate(definitions, 1): 113 | if isinstance(definition, dict): 114 | # A list of params. Generate names 115 | definitions_[f'{idx}'] = definition 116 | else: 117 | # A list of names. Generate params. 118 | definitions_[definition] = {} 119 | 120 | definitions = definitions_ 121 | 122 | for title, definition in definitions.items(): 123 | definition = definition or {} 124 | 125 | trackbar = Trackbar( 126 | f'{prefix}{title}', 127 | **{**common_kwargs, **definition} 128 | ) 129 | trackbars.append(trackbar) 130 | self.add_trackbar(trackbar) 131 | 132 | return tuple(trackbars) 133 | 134 | def set_frame(self, frame: AnyFrame): 135 | """Sets current frame for the window. 136 | 137 | :param frame: 138 | 139 | """ 140 | self._frame = frame 141 | 142 | def render(self): 143 | """Renders window contents.""" 144 | frame = self._frame 145 | if frame is not None: 146 | cv.imshow(self.name, getattr(frame, 'frame', frame)) 147 | -------------------------------------------------------------------------------- /ocvproto/ui/wm.py: -------------------------------------------------------------------------------- 1 | from typing import List, Generator, Tuple 2 | 3 | from .window import Window, Trackbar 4 | from ..app import Application, Config 5 | from ..backend import cv 6 | from ..frame import AnyFrame 7 | 8 | 9 | class WindowManager: 10 | """Manages windows.""" 11 | 12 | def __init__(self, windows: List[Window] = None, app: Application = None): 13 | """ 14 | 15 | :param windows: Windows to manage. If not set, 16 | one window is automatically constructed. 17 | 18 | :param app: ocvproto application object. Automatically constructed if not set. 19 | 20 | """ 21 | if app is None: 22 | app = Application() 23 | 24 | self.app = app 25 | 26 | if not windows: 27 | windows = [Window()] 28 | 29 | self._windows = windows 30 | 31 | self.bind_trackbar_keys() 32 | app.set_loop_func(self.render) 33 | self._hooks_bind() 34 | 35 | def _hooks_bind(self): 36 | app = self.app 37 | app.hook_register('config_save', self.config_update) 38 | app.hook_register('config_load', self.config_load) 39 | 40 | @property 41 | def window(self) -> Window: 42 | """Default window.""" 43 | return self._windows[0] 44 | 45 | def config_update(self, config: Config): 46 | """Updates data gathered from managed windows in the given config. 47 | 48 | :param config: 49 | 50 | """ 51 | data = {} 52 | 53 | for window, trackbar in self.iter_trackbars(): 54 | window_data = data.setdefault(window.name, {'trackvals': {}}) 55 | window_data['trackvals'][trackbar.name] = trackbar.value 56 | 57 | config.set_data('windows', data) 58 | 59 | def config_load(self, config: Config): 60 | """Updates managed windows using data from the given config. 61 | 62 | :param config: 63 | 64 | """ 65 | windows_data: dict = config.get_data('windows', {}) 66 | 67 | for window, trackbar in self.iter_trackbars(): 68 | window_data = windows_data.get(window.name) 69 | 70 | if window_data: 71 | trackbar_vals = window_data.get('trackvals') 72 | if trackbar_vals: 73 | trackbar_val = trackbar_vals.get(trackbar.name) 74 | if trackbar_val is not None: 75 | trackbar.value = trackbar_val 76 | 77 | def iter_trackbars(self) -> Generator[Tuple[Window, Trackbar], None, None]: 78 | """Generator yielding managed windows and trackbars.""" 79 | for window in self._windows: 80 | for trackbar in window.trackbars.values(): 81 | yield window, trackbar 82 | 83 | def __enter__(self): 84 | return self 85 | 86 | def __exit__(self, exc_type, exc_val, exc_tb): 87 | cv.destroyAllWindows() 88 | 89 | def set_frame(self, frame: AnyFrame): 90 | """Sets frame to be rendered in default window. 91 | 92 | :param frame: 93 | 94 | """ 95 | self.window.set_frame(frame) 96 | 97 | def render(self): 98 | """Renders managed windows.""" 99 | for window in self._windows: 100 | window.render() 101 | 102 | def bind_trackbar_keys(self): 103 | 104 | bind = self.app.bind_key 105 | 106 | for window, trackbar in self.iter_trackbars(): 107 | for key, func in trackbar.keys.items(): 108 | bind(key, func) 109 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | release = clean --all sdist bdist_wheel upload 3 | 4 | test = pytest 5 | 6 | [wheel] 7 | universal = 1 8 | 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import re 4 | 5 | from setuptools import setup, find_packages 6 | 7 | import sys 8 | 9 | PATH_BASE = os.path.dirname(__file__) 10 | 11 | 12 | def read_file(fpath): 13 | """Reads a file within package directories.""" 14 | with io.open(os.path.join(PATH_BASE, fpath)) as f: 15 | return f.read() 16 | 17 | 18 | def get_version(): 19 | """Returns version number, without module import (which can lead to ImportError 20 | if some dependencies are unavailable before install.""" 21 | contents = read_file(os.path.join('ocvproto', '__init__.py')) 22 | version = re.search('VERSION = \(([^)]+)\)', contents) 23 | version = version.group(1).replace(', ', '.').strip() 24 | return version 25 | 26 | 27 | DEPS = [ 28 | 'opencv-python', 29 | 'colorhash', 30 | ] 31 | 32 | setup( 33 | name='opencv-proto', 34 | version=get_version(), 35 | url='https://github.com/idlesign/opencv-proto', 36 | 37 | description='Allows fast prototyping in Python for OpenCV', 38 | long_description=read_file('README.rst'), 39 | license='BSD 3-Clause License', 40 | 41 | author='Igor `idle sign` Starikov', 42 | author_email='idlesign@yandex.ru', 43 | 44 | packages=find_packages(exclude=['tests']), 45 | include_package_data=True, 46 | zip_safe=False, 47 | 48 | install_requires=[], 49 | setup_requires=(['pytest-runner'] if 'test' in sys.argv else []), 50 | 51 | extras_require={ 52 | 'all': DEPS, 53 | }, 54 | 55 | test_suite='tests', 56 | tests_require=[ 57 | 'pytest', 58 | 'pytest-stub>=1.1.0', 59 | ] + DEPS, 60 | 61 | classifiers=[ 62 | # As in https://pypi.python.org/pypi?:action=list_classifiers 63 | 'Development Status :: 4 - Beta', 64 | 'Operating System :: OS Independent', 65 | 'Programming Language :: Python', 66 | 'Programming Language :: Python :: 3', 67 | 'Programming Language :: Python :: 3.6', 68 | 'Programming Language :: Python :: 3.7', 69 | 'Programming Language :: Python :: 3.8', 70 | 'License :: OSI Approved :: BSD License', 71 | ], 72 | ) 73 | 74 | 75 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pathlib import Path 3 | 4 | STUB = False 5 | 6 | if STUB: 7 | from pytest_stub.toolbox import stub_global 8 | 9 | stub_global({ 10 | 'cv2': '[mock_persist]', 11 | 'numpy': '[mock_persist]', 12 | }) 13 | 14 | 15 | @pytest.fixture 16 | def static_path(request): 17 | 18 | path = request.fspath 19 | 20 | def static_path_(fname): 21 | return Path(str(path)).parent / 'static' / fname 22 | 23 | return static_path_ 24 | -------------------------------------------------------------------------------- /tests/static/empty: -------------------------------------------------------------------------------- 1 | dummy 2 | -------------------------------------------------------------------------------- /tests/static/red.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/opencv-proto/35bff19f71dc8823bc84cdb87e721a41aaf08a40/tests/static/red.avi -------------------------------------------------------------------------------- /tests/static/tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idlesign/opencv-proto/35bff19f71dc8823bc84cdb87e721a41aaf08a40/tests/static/tiny.png -------------------------------------------------------------------------------- /tests/test_application.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from ocvproto.toolbox import Application, Key 4 | 5 | 6 | def test_application(tmpdir, monkeypatch): 7 | 8 | app1 = Application() # no config test 9 | app1.config_load() 10 | app1.config_save() 11 | 12 | app2 = Application(config=tmpdir / 'some.json') 13 | 14 | log = [] 15 | 16 | def hook_save(config): 17 | log.append('save') 18 | 19 | def hook_load(config): 20 | log.append('load') 21 | 22 | app2.hook_register('config_save', hook_save) 23 | app2.hook_register('config_load', hook_load) 24 | 25 | app2.config_save() 26 | app2.config_load() 27 | 28 | assert log == ['save', 'load'] 29 | 30 | log.clear() 31 | 32 | from ocvproto.backend import cv 33 | monkeypatch.setattr(cv, 'waitKey', MagicMock()) 34 | 35 | def loop_func(): 36 | log.append('triggered') 37 | len_log = len(log) 38 | 39 | if len_log == 1: 40 | cv.waitKey.return_value = 115 41 | 42 | elif len_log == 3: 43 | cv.waitKey.return_value = Key.ESC 44 | 45 | app2.set_loop_func(loop_func) 46 | 47 | for _ in app2.loop(): 48 | pass 49 | 50 | assert log == ['triggered', 'save', 'triggered'] 51 | -------------------------------------------------------------------------------- /tests/test_camera.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ocvproto.exceptions import SourceError 4 | from ocvproto.toolbox import Camera 5 | 6 | 7 | def test_image(static_path): 8 | 9 | with pytest.raises(SourceError): 10 | with Camera() as cam: 11 | cam.read() 12 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | from ocvproto.toolbox import Config 2 | 3 | 4 | def test_config(tmpdir): 5 | 6 | fpath = tmpdir / 'some.json' 7 | 8 | conf = Config(fpath) 9 | conf.load() 10 | 11 | assert conf.get_data('key', 'some') == 'some' 12 | 13 | conf.set_data('key', 'val') 14 | conf.save() 15 | 16 | conf = Config(fpath) 17 | conf.load() 18 | assert conf.get_data('key') == 'val' 19 | -------------------------------------------------------------------------------- /tests/test_image.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ocvproto.exceptions import SourceError 4 | from ocvproto.toolbox import Image 5 | 6 | 7 | def test_image(static_path): 8 | img = Image(static_path('tiny.png')) 9 | assert img.height == 3 10 | assert img.width == 2 11 | 12 | with pytest.raises(SourceError): 13 | Image(static_path('empty')).read() 14 | -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | from ocvproto.toolbox import to_rgb, Canvas, Text 2 | 3 | 4 | def test_to_rgb(): 5 | 6 | assert to_rgb((0, 0, 0)) == (0, 0, 0) 7 | assert to_rgb('black') == (0, 0, 0) 8 | assert to_rgb(0) == (0, 0, 0) 9 | 10 | 11 | def test_canvas(): 12 | canvas = Canvas(color='red') 13 | assert canvas.height == 480 14 | 15 | 16 | def test_text(): 17 | canvas = Canvas() 18 | 19 | text = Text() 20 | text.put_on(canvas) 21 | text.put_on_demo(canvas) 22 | 23 | -------------------------------------------------------------------------------- /tests/test_primitives.py: -------------------------------------------------------------------------------- 1 | from ocvproto.primitives.legend import Legend 2 | from ocvproto.toolbox import Canvas 3 | 4 | 5 | def test_primitives(tmpdir): 6 | legend = Legend(['a', 'b', 'c']) 7 | legend.put_on(Canvas()) 8 | 9 | assert legend.labels == {'a': (119, 134, 45), 'b': (83, 102, 172), 'c': (45, 197, 210)} 10 | -------------------------------------------------------------------------------- /tests/test_trackbar.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | import pytest 4 | 5 | from ocvproto.toolbox import Window, Trackbar 6 | 7 | 8 | @pytest.mark.skipif('TRAVIS' in environ, reason='UI backend may be unavailable') 9 | def test_trackbar(static_path): 10 | 11 | win = Window() 12 | win.create() 13 | 14 | log = [] 15 | 16 | def call(val): 17 | log.append(val) 18 | 19 | tb = Trackbar('some', default=3, max=15, step=2, callback=call, keys='[]') 20 | tb.bind(win.name) 21 | assert int(tb) == 3 22 | assert float(tb) == 3 23 | assert bin(tb) == '0b11' 24 | 25 | tb.inc() 26 | assert tb.get_value() == 5 27 | 28 | tb.dec() 29 | assert tb.get_value() == 3 30 | 31 | assert log == [5, 3] 32 | -------------------------------------------------------------------------------- /tests/test_video.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ocvproto.exceptions import SourceError 4 | from ocvproto.toolbox import Video, Image 5 | 6 | 7 | def test_image(static_path, tmpdir): 8 | 9 | with pytest.raises(SourceError): 10 | with Video(static_path('empty')) as video: 11 | video.read() 12 | 13 | fpath_img = str(tmpdir / 'dumped.png') 14 | fpath_dump = str(tmpdir / 'dumped.avi') 15 | 16 | with Video(static_path('red.avi')) as video: 17 | 18 | assert video.height == 240 19 | assert video.width == 320 20 | assert video.fps == 25 21 | assert video.codec == 'FMP4' 22 | 23 | props = video.describe_properties() 24 | 25 | prop_fps = props['fps'] 26 | assert prop_fps['default'] == 25 27 | assert prop_fps['max'] == 60 28 | prop_fps['callback'](60) 29 | 30 | video.dump_image(fpath_img) 31 | 32 | img = Image(fpath_img) 33 | assert img.height == 240 34 | 35 | video.dump_setup(fpath_dump) 36 | video.dump() 37 | video.dump(video) 38 | 39 | with Video(fpath_dump) as video: 40 | assert video.height == 240 41 | assert video.codec == 'XVID' 42 | -------------------------------------------------------------------------------- /tests/test_window.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | import pytest 4 | 5 | from ocvproto.toolbox import Window 6 | 7 | 8 | @pytest.mark.skipif('TRAVIS' in environ, reason='UI backend may be unavailable') 9 | def test_window(): 10 | win = Window() 11 | win.create(autosize=False) 12 | win.position(x=10, y=20) 13 | win.resize(width=50, height=50) 14 | -------------------------------------------------------------------------------- /tests/test_wm.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | import pytest 4 | 5 | from ocvproto.toolbox import WindowManager, Window, Application, Canvas, Trackbar 6 | 7 | 8 | @pytest.mark.skipif('TRAVIS' in environ, reason='UI backend may be unavailable') 9 | def test_wm(tmpdir): 10 | 11 | with WindowManager() as wm: 12 | 13 | app = wm.app 14 | assert isinstance(app, Application) 15 | app.set_config(tmpdir / 'wm_conf.json') 16 | 17 | win = wm.window 18 | assert isinstance(win, Window) 19 | 20 | a, b = win.add_trackbar_group(2, prefix='common') 21 | a.set_keys('[]') 22 | assert isinstance(a, Trackbar) 23 | assert isinstance(b, Trackbar) 24 | assert a.name == 'common 1' 25 | assert b.name == 'common 2' 26 | 27 | c, d = win.add_trackbar_group(['x', {'default': 20}]) 28 | assert isinstance(c, Trackbar) 29 | assert isinstance(d, Trackbar) 30 | assert c.name == 'x' 31 | assert d.name == '2' 32 | 33 | wm.bind_trackbar_keys() 34 | 35 | app.config_save() 36 | app.config_load() 37 | 38 | canvas = Canvas() 39 | wm.set_frame(canvas) 40 | wm.render() 41 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # See http://tox.readthedocs.org/en/latest/examples.html for samples. 2 | [tox] 3 | envlist = 4 | py{36,37,38} 5 | 6 | skip_missing_interpreters = True 7 | 8 | install_command = pip install {opts} {packages} 9 | 10 | [testenv] 11 | commands = 12 | python setup.py test 13 | 14 | deps = 15 | 16 | --------------------------------------------------------------------------------