├── .coveragerc
├── .coveralls.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── demo.py
├── devrequirements.txt
├── setup.py
└── vlogging
├── __init__.py
└── tests
├── basic.py
├── corner_cases.py
├── dummies
├── PIL.py
├── __init__.py
├── cv2.py
└── pylab.py
└── lenna.jpg
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit =
3 | */python?.?/*
4 | */lib-python/?.?/*.py
5 | */lib_pypy/*.py
6 | */site-packages/nose/*
7 | */unittest2/*
8 | */pyshared/*
9 | */dist-packages/*
10 | */tests/*
11 | */venv/*
12 | demo.py
13 | setup.py
14 |
15 | [report]
16 | exclude_lines =
17 | # Have to re-enable the standard pragma
18 | pragma: no cover
--------------------------------------------------------------------------------
/.coveralls.yml:
--------------------------------------------------------------------------------
1 | # .coveralls.yml
2 | repo_token: GzsXpWT6B1llGwECNpJAyzcpXB6KK89Sq
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | *.egg-info/
22 | .installed.cfg
23 | *.egg
24 |
25 | # PyInstaller
26 | # Usually these files are written by a python script from a template
27 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
28 | *.manifest
29 | *.spec
30 |
31 | # Installer logs
32 | pip-log.txt
33 | pip-delete-this-directory.txt
34 |
35 | # Unit test / coverage reports
36 | htmlcov/
37 | .tox/
38 | .coverage
39 | .cache
40 | nosetests.xml
41 | coverage.xml
42 |
43 | # Translations
44 | *.mo
45 | *.pot
46 |
47 | # Django stuff:
48 | *.log
49 |
50 | # Sphinx documentation
51 | docs/_build/
52 |
53 | # PyBuilder
54 | target/
55 |
56 | .DS_Store
57 | test.html
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | virtualenv:
3 | system_site_packages: true
4 |
5 | cache:
6 | apt: true
7 | directories:
8 | - /home/travis/virtualenv/python2.7/lib/python2.7/site-packages
9 |
10 | before_install:
11 | - sudo apt-get update
12 | - sudo apt-get install -qq zlib1g-dev build-essential python-numpy
13 | - wget http://prdownloads.sourceforge.net/libpng/libpng-1.5.12.tar.gz
14 | - tar xzf libpng-1.5.12.tar.gz
15 | - rm libpng-1.5.12.tar.gz
16 | - pushd libpng-1.5.12
17 | - ./configure --prefix=/usr/local
18 | - make
19 | - sudo make install
20 | - sudo ldconfig
21 | - popd
22 | - curl -sL https://github.com/Itseez/opencv/archive/2.4.9.zip > opencv.zip
23 | - unzip opencv.zip
24 | - rm opencv.zip
25 | - mkdir opencv-build
26 | - pushd opencv-build/
27 | - cmake -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_DOCS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_opencv_java=OFF -DBUILD_JASPER=ON -DWITH_JASPER=ON -DBUILD_ZLIB=ON -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=OFF -DWITH_OPENEXR=OFF -DBUILD_PNG=ON -DWITH_PNG=ON -DWITH_TIFF=ON -DBUILD_TIFF=ON -DWITH_WEBP=OFF -DWITH_JPEG=ON -DBUILD_JPEG=ON ../opencv-2.4.9/
28 | - sudo make install
29 | - popd
30 |
31 | python:
32 | - "2.7"
33 | # - "3.4"
34 | # - "3.3"
35 |
36 | install:
37 | - pip install -r devrequirements.txt
38 |
39 | # command to run tests
40 | script: nosetests -vs vlogging/tests/corner_cases.py vlogging/tests/basic.py --with-isolation --with-coverage --cover-erase --cover-package=.
41 |
42 | notifications:
43 | slack: unshredit:2nm3MUsOgq9Bz9bqDPAOPn7S
44 |
45 | after_success:
46 | - coveralls
47 |
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Dmitry Chaplinsky
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | visual-logging
2 | ==============
3 |
4 | A simple way to generate beautiful html logs with embedded images for CV purposes.
5 |
6 | visual-logging piggy backs on logging module and allows you to use sick power of logging to debug your computer vision application on a whole new level.
7 |
8 | Now you can add OpenCV images (well technically it's numpy arrays), PIL images and matplotlib graphs to your logs.
9 |
10 | You can read about it in details in a great blog post [visual-logging, my new favorite tool for debugging OpenCV and Python apps](http://www.pyimagesearch.com/2014/12/22/visual-logging-new-favorite-tool-debugging-opencv-python-apps/) written by Adrian Rosebrock
11 |
12 |
13 | [](https://travis-ci.org/dchaplinsky/visual-logging)
14 | [](https://coveralls.io/r/dchaplinsky/visual-logging)
15 |
16 | ## Installation
17 | ```pip install visual-logging```
18 |
19 | No extra dependencies
20 |
21 | ## Usage example (see demo.py)
22 | ```python
23 | from logging import FileHandler
24 | from vlogging import VisualRecord
25 |
26 | if __name__ == '__main__':
27 | import cv2
28 | from PIL import Image
29 | import numpy as np
30 | import matplotlib.pyplot as plt
31 |
32 | # evenly sampled time at 200ms intervals
33 | t = np.arange(0., 5., 0.2)
34 |
35 | fig1 = plt.figure()
36 | plt.plot(t, t, 'r--', t, t ** 2, 'bs', t, t ** 3, 'g^')
37 |
38 | cv_image = cv2.imread('lenna.jpg')
39 | pil_image = Image.open('lenna.jpg')
40 |
41 | import logging
42 | logger = logging.getLogger("demo")
43 | fh = FileHandler('test.html', mode="w")
44 |
45 | logger.setLevel(logging.DEBUG)
46 | logger.addHandler(fh)
47 |
48 | logger.debug(VisualRecord(
49 | "Hello from OpenCV", cv_image, "This is openCV image", fmt="png"))
50 |
51 | logger.info(VisualRecord(
52 | "Hello from PIL", pil_image, "This is PIL image", fmt="jpeg"))
53 |
54 | logger.info(VisualRecord(
55 | "Hello from pylab", fig1, "This is PyLab graph", fmt="png"))
56 |
57 | logger.warning(
58 | VisualRecord("Hello from all", [cv_image, pil_image, fig1],
59 | fmt="png"))
60 |
61 | ```
62 |
63 | You can check generated html [here](http://dchaplinsky.github.io/visual-logging/)
64 |
--------------------------------------------------------------------------------
/demo.py:
--------------------------------------------------------------------------------
1 | from logging import FileHandler
2 | from vlogging import VisualRecord
3 |
4 | if __name__ == '__main__':
5 | import cv2
6 | from PIL import Image
7 | import numpy as np
8 | import matplotlib.pyplot as plt
9 |
10 | # evenly sampled time at 200ms intervals
11 | t = np.arange(0., 5., 0.2)
12 |
13 | fig1 = plt.figure()
14 | plt.plot(t, t, 'r--', t, t ** 2, 'bs', t, t ** 3, 'g^')
15 |
16 | cv_image = cv2.imread('vlogging/tests/lenna.jpg')
17 | pil_image = Image.open('vlogging/tests/lenna.jpg')
18 |
19 | import logging
20 | logger = logging.getLogger("demo")
21 | fh = FileHandler('test.html', mode="w")
22 |
23 | logger.setLevel(logging.DEBUG)
24 | logger.addHandler(fh)
25 |
26 | logger.debug(VisualRecord(
27 | "Hello from OpenCV", cv_image, "This is openCV image", fmt="png"))
28 |
29 | logger.info(VisualRecord(
30 | "Hello from PIL", pil_image, "This is PIL image", fmt="jpeg"))
31 |
32 | logger.info(VisualRecord(
33 | "Hello from pylab", fig1, "This is PyLab graph", fmt="png"))
34 |
35 | logger.warning(
36 | VisualRecord("Hello from all", [cv_image, pil_image, fig1],
37 | fmt="png"))
38 |
--------------------------------------------------------------------------------
/devrequirements.txt:
--------------------------------------------------------------------------------
1 | matplotlib==1.4.1
2 | Pillow==2.5.1
3 | coveralls
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | from vlogging import __version__
3 |
4 | setup(
5 | name='visual-logging',
6 | version=__version__,
7 | url='https://github.com/dchaplinsky/visual-logging',
8 | license='MIT',
9 | author='Dmitry Chaplinsky',
10 | author_email='chaplinsky.dmitry@gmail.com',
11 | packages=["vlogging"],
12 | description="A simple way to generate beautiful html "
13 | "logs with embedded images for CV purposes.",
14 | platforms='any'
15 | )
16 |
--------------------------------------------------------------------------------
/vlogging/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | from io import BytesIO as StringIO
4 | from string import Template
5 | import base64
6 |
7 | __version__ = "1.0"
8 | renderers = []
9 |
10 | try:
11 | import cv2
12 | import numpy
13 |
14 | def render_opencv(img, fmt="png"):
15 | if not isinstance(img, numpy.ndarray):
16 | return None
17 |
18 | retval, buf = cv2.imencode(".%s" % fmt, img)
19 | if not retval:
20 | return None
21 |
22 | return buf, "image/%s" % fmt
23 |
24 | renderers.append(render_opencv)
25 | except ImportError:
26 | pass
27 |
28 | try:
29 | from PIL import Image
30 |
31 | def render_pil(img, fmt="png"):
32 | if not callable(getattr(img, "save", None)):
33 | return None
34 |
35 | output = StringIO()
36 | img.save(output, format=fmt)
37 | contents = output.getvalue()
38 | output.close()
39 |
40 | return contents, "image/%s" % fmt
41 |
42 | renderers.append(render_pil)
43 | except ImportError:
44 | pass
45 |
46 | try:
47 | import pylab
48 |
49 | def render_pylab(img, fmt="png"):
50 | if not callable(getattr(img, "savefig", None)):
51 | return None
52 |
53 | output = StringIO()
54 | img.savefig(output, format=fmt)
55 | contents = output.getvalue()
56 | output.close()
57 |
58 | return contents, "image/%s" % fmt
59 |
60 | renderers.append(render_pylab)
61 | except ImportError:
62 | pass
63 |
64 |
65 | class VisualRecord(object):
66 | def __init__(self, title="", imgs=None, footnotes="", fmt="png"):
67 | self.title = title
68 | self.fmt = fmt
69 |
70 | if imgs is None:
71 | imgs = []
72 |
73 | self.imgs = imgs
74 |
75 | if not isinstance(imgs, (list, tuple, set, frozenset)):
76 | self.imgs = [self.imgs]
77 |
78 | self.footnotes = footnotes
79 |
80 | def render_images(self):
81 | rendered = []
82 |
83 | for img in self.imgs:
84 | for renderer in renderers:
85 | # Trying renderers we have one by one
86 | res = renderer(img, self.fmt)
87 |
88 | if res is None:
89 | continue
90 | else:
91 | rendered.append(res)
92 | break
93 |
94 | return "".join(
95 | Template('').substitute({
96 | "data": base64.b64encode(data).decode(),
97 | "mime": mime
98 | }) for data, mime in rendered)
99 |
100 | def render_footnotes(self):
101 | if not self.footnotes:
102 | return ""
103 |
104 | return Template("
$footnotes").substitute({ 105 | "footnotes": self.footnotes 106 | }) 107 | 108 | def __str__(self): 109 | t = Template( 110 | """ 111 |
" in s) 21 | 22 | def test_all_renderers(self): 23 | self.assertEqual(len(vlogging.renderers), 3) 24 | 25 | def test_invalid_images(self): 26 | s = str(vlogging.VisualRecord( 27 | title="title", 28 | imgs="foobar", 29 | footnotes="footnotes")) 30 | 31 | self.assertTrue("title" in s) 32 | self.assertTrue("footnotes" in s) 33 | self.assertTrue("" in s) 34 | self.assertEqual(s.count("" in s) 44 | self.assertEqual(s.count("
" in s) 58 | self.assertTrue("image/png" in s) 59 | self.assertEqual(s.count("
" in s) 89 | self.assertEqual(s.count("
" in s) 121 | self.assertEqual(s.count("
" in s) 140 | self.assertEqual(s.count("