From 519bab5d97753f86743dbad60e489dd1237aa496 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Mon, 7 Nov 2016 10:56:08 +0000 Subject: [PATCH 01/13] [#26] First pass at supporting 2.7.x, 3.4 and 3.5 --- gouda/engines/accusoft.py | 2 +- gouda/engines/dtk.py | 3 +-- gouda/engines/inlite.py | 1 - gouda/engines/libdmtx.py | 2 -- gouda/engines/options.py | 2 +- gouda/engines/softek.py | 2 +- gouda/engines/zbar_engine.py | 2 +- gouda/scripts/decode_barcodes.py | 4 ++-- gouda/strategies/resize.py | 2 +- gouda/strategies/roi/decode.py | 2 ++ gouda/strategies/roi/detect.py | 7 ++++++- gouda/strategies/roi/rect.py | 2 ++ gouda/strategies/roi/roi.py | 6 +++++- gouda/tests/test_decode_barcodes.py | 26 +++++++++++++------------- gouda/tests/test_engines.py | 8 ++++---- gouda/tests/test_rect.py | 2 +- gouda/tests/test_strategies.py | 4 ++-- gouda/util.py | 2 +- requirements.txt => requirements.pip | 4 ++-- setup.py | 13 +++++++++---- 20 files changed, 55 insertions(+), 41 deletions(-) rename requirements.txt => requirements.pip (57%) diff --git a/gouda/engines/accusoft.py b/gouda/engines/accusoft.py index 2c29bef..81e2887 100644 --- a/gouda/engines/accusoft.py +++ b/gouda/engines/accusoft.py @@ -59,7 +59,7 @@ def decode_file(self, path): # TODO LH Who owns dib? res = windll.kernel32.GlobalFree(dib) if res: - print('Error freeing global handle [{0}]'.format( + debug_print('Error freeing global handle [{0}]'.format( win32api.GetLastError() )) self.ie.FileName = '' diff --git a/gouda/engines/dtk.py b/gouda/engines/dtk.py index 37f1c17..090e193 100644 --- a/gouda/engines/dtk.py +++ b/gouda/engines/dtk.py @@ -1,4 +1,3 @@ - import os import tempfile @@ -80,7 +79,7 @@ def available(cls): def decode_file(self, path): self.d.ReadFromFile(str(path)) barcodes = [None] * self.d.Barcodes.Count - for i in xrange(0, self.d.Barcodes.Count): + for i in range(0, self.d.Barcodes.Count): b = self.d.Barcodes.Item(i) barcodes[i] = Barcode(self.types.get(b.Type, 'Unknown'), b.BarcodeString) diff --git a/gouda/engines/inlite.py b/gouda/engines/inlite.py index 2c4f375..a0d643b 100644 --- a/gouda/engines/inlite.py +++ b/gouda/engines/inlite.py @@ -1,4 +1,3 @@ - import os import tempfile diff --git a/gouda/engines/libdmtx.py b/gouda/engines/libdmtx.py index bcf9f58..574c163 100644 --- a/gouda/engines/libdmtx.py +++ b/gouda/engines/libdmtx.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import subprocess import tempfile diff --git a/gouda/engines/options.py b/gouda/engines/options.py index 4feb783..8494d21 100644 --- a/gouda/engines/options.py +++ b/gouda/engines/options.py @@ -15,7 +15,7 @@ def engine_options(): 'zxing': ZxingEngine, } - options = {k: v for k, v in options.iteritems() if v.available()} + options = {k: v for k, v in options.items() if v.available()} if AccusoftEngine.available(): options.update({ diff --git a/gouda/engines/softek.py b/gouda/engines/softek.py index 27f72ec..d4ae110 100644 --- a/gouda/engines/softek.py +++ b/gouda/engines/softek.py @@ -178,7 +178,7 @@ def available(cls): def decode_file(self, path): self.d.ScanBarCode(str(path)) barcodes = [None] * self.d.BarCodeCount - for i in xrange(0, self.d.BarCodeCount): + for i in range(0, self.d.BarCodeCount): barcodes[i] = Barcode(str(self.d.BarStringType(1+i)), self.d.BarString(1+i)) return barcodes diff --git a/gouda/engines/zbar_engine.py b/gouda/engines/zbar_engine.py index f28e0b2..c88c958 100644 --- a/gouda/engines/zbar_engine.py +++ b/gouda/engines/zbar_engine.py @@ -40,4 +40,4 @@ def __call__(self, img): img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) image = zbar.Image(width, height, 'Y800', img.tostring()) scanner.scan(image) - return [Barcode(str(s.type), unicode(s.data, 'utf8')) for s in image] + return [Barcode(s.type, s.data) for s in image] diff --git a/gouda/scripts/decode_barcodes.py b/gouda/scripts/decode_barcodes.py index 706b8b5..a78dd74 100755 --- a/gouda/scripts/decode_barcodes.py +++ b/gouda/scripts/decode_barcodes.py @@ -122,7 +122,7 @@ def _destination(self, path): destination = path if self.avoid_collisions: while destination.is_file(): - fname = u'{0}-{1}{2}'.format( + fname = '{0}-{1}{2}'.format( path.stem, next(self.suffix[path.name]), path.suffix @@ -146,7 +146,7 @@ def result(self, path, result): # to new destinations. first_destination = None for value in values: - dest = path.with_name(u'{0}{1}'.format(value, path.suffix)) + dest = path.with_name('{0}{1}'.format(value, path.suffix)) dest = self._destination(dest) source = first_destination if first_destination else path rename = not bool(first_destination) diff --git a/gouda/strategies/resize.py b/gouda/strategies/resize.py index 45568bd..beef085 100644 --- a/gouda/strategies/resize.py +++ b/gouda/strategies/resize.py @@ -17,7 +17,7 @@ def resize(img, engine): for sharpening in (0, 1, 2): if sharpening > 0: img = _unsharpmask(img) - for f in [round(x * 0.01, 2) for x in xrange(100, 0, -5)]: + for f in [round(x * 0.01, 2) for x in range(100, 0, -5)]: msg = 'resize: scaling factor [{0}] sharpening [{1}]' msg = msg.format(f, sharpening) debug_print(msg) diff --git a/gouda/strategies/roi/decode.py b/gouda/strategies/roi/decode.py index ce258ad..b4f54c7 100644 --- a/gouda/strategies/roi/decode.py +++ b/gouda/strategies/roi/decode.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import cv2 import numpy as np diff --git a/gouda/strategies/roi/detect.py b/gouda/strategies/roi/detect.py index cb4d898..b08925d 100644 --- a/gouda/strategies/roi/detect.py +++ b/gouda/strategies/roi/detect.py @@ -6,6 +6,7 @@ from .rect import Rect from gouda.util import debug_print +# debug_print = print class Detector(object): """Detects candidate barcode areas in an image @@ -89,9 +90,11 @@ def _compute_candidates(self): debug_print('Contours') cont_img = closing.copy() + # Taking only the last two elements of the sequence returned by + # findContours makes the code tolerant of OpenCV 2.4.x and 3.x contours, hierarchy = cv2.findContours( cont_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE - ) + )[-2:] working_images = { 'resized': resized, @@ -102,7 +105,9 @@ def _compute_candidates(self): 'closing': closing, } + candidates = [cv2.boundingRect(c) for c in contours] + candidates = [ Rect(x, y, width, height) for x, y, width, height in candidates ] diff --git a/gouda/strategies/roi/rect.py b/gouda/strategies/roi/rect.py index 00f1997..f42b042 100644 --- a/gouda/strategies/roi/rect.py +++ b/gouda/strategies/roi/rect.py @@ -1,3 +1,5 @@ +from __future__ import division + import collections diff --git a/gouda/strategies/roi/roi.py b/gouda/strategies/roi/roi.py index d727206..006bb73 100644 --- a/gouda/strategies/roi/roi.py +++ b/gouda/strategies/roi/roi.py @@ -1,8 +1,12 @@ +from __future__ import print_function + from .decode import Decoder from .detect import Detector from .filter import AreaFilter +from pprint import pprint + def roi(img, engine): # Regions of the image that might contain barcodes @@ -21,4 +25,4 @@ def roi(img, engine): if barcode.data not in res: res[barcode.data] = barcode - return ('roi', res.values()) if res else None + return ('roi', list(res.values())) diff --git a/gouda/tests/test_decode_barcodes.py b/gouda/tests/test_decode_barcodes.py index 20794b8..f74d487 100644 --- a/gouda/tests/test_decode_barcodes.py +++ b/gouda/tests/test_decode_barcodes.py @@ -6,7 +6,7 @@ from gouda.engines import ZbarEngine from gouda.scripts.decode_barcodes import main -from utils import temp_directory_with_files +from .utils import temp_directory_with_files TESTDATA = Path(__file__).parent.joinpath('test_data') @@ -17,7 +17,7 @@ class TestRename(unittest.TestCase): def test_rename(self): "File is renamed with value of barcode" with temp_directory_with_files(TESTDATA.joinpath('code128.png')) as tempdir: - main(['zbar', '--action=rename', unicode(tempdir)]) + main(['zbar', '--action=rename', str(tempdir)]) self.assertEqual( ['Stegosaurus.png'], [path.name for path in sorted(tempdir.iterdir())] @@ -26,7 +26,7 @@ def test_rename(self): def test_rename_multiple(self): "File with multiple barcodes results in renamed / copied to three files" with temp_directory_with_files(TESTDATA.joinpath('BM001128287.jpg')) as tempdir: - main(['zbar', '--action=rename', unicode(tempdir)]) + main(['zbar', '--action=rename', str(tempdir)]) self.assertEqual( ['BM001128286.jpg', 'BM001128287.jpg', 'BM001128288.jpg'], [path.name for path in sorted(tempdir.iterdir())] @@ -36,16 +36,16 @@ def test_rename_with_collisions(self): "Files with same barcode values results in just a single rename" with temp_directory_with_files(TESTDATA.joinpath('code128.png')) as tempdir: shutil.copy( - unicode(TESTDATA.joinpath('code128.png')), - unicode(tempdir.joinpath('first copy.png')) + str(TESTDATA.joinpath('code128.png')), + str(tempdir.joinpath('first copy.png')) ) shutil.copy( - unicode(TESTDATA.joinpath('code128.png')), - unicode(tempdir.joinpath('second copy.png')) + str(TESTDATA.joinpath('code128.png')), + str(tempdir.joinpath('second copy.png')) ) - main(['zbar', '--action=rename', unicode(tempdir)]) + main(['zbar', '--action=rename', str(tempdir)]) self.assertEqual( ['Stegosaurus.png', 'first copy.png', 'second copy.png'], [path.name for path in sorted(tempdir.iterdir(), key=lambda p: p.name)] @@ -55,16 +55,16 @@ def test_rename_avoid_collisions(self): "Files with same barcode values results in new files with suffixes" with temp_directory_with_files(TESTDATA.joinpath('code128.png')) as tempdir: shutil.copy( - unicode(TESTDATA.joinpath('code128.png')), - unicode(tempdir.joinpath('first copy.png')) + str(TESTDATA.joinpath('code128.png')), + str(tempdir.joinpath('first copy.png')) ) shutil.copy( - unicode(TESTDATA.joinpath('code128.png')), - unicode(tempdir.joinpath('second copy.png')) + str(TESTDATA.joinpath('code128.png')), + str(tempdir.joinpath('second copy.png')) ) - main(['zbar', '--action=rename', unicode(tempdir), '--avoid-collisions']) + main(['zbar', '--action=rename', str(tempdir), '--avoid-collisions']) print([path.name for path in sorted(tempdir.iterdir())]) self.assertEqual( ['Stegosaurus-1.png', 'Stegosaurus-2.png', 'Stegosaurus.png'], diff --git a/gouda/tests/test_engines.py b/gouda/tests/test_engines.py index a07963e..65db72a 100644 --- a/gouda/tests/test_engines.py +++ b/gouda/tests/test_engines.py @@ -27,25 +27,25 @@ class TestEngine(unittest.TestCase): NOBARCODE = cv2.imread(str(TESTDATA / 'nobarcode.png')) def _test_1d(self, engine, type='CODE128'): - expected = [Barcode(type=type, data='Stegosaurus')] + expected = [Barcode(type=type, data=b'Stegosaurus')] res = engine(self.CODE128) self.assertEqual(expected, res) self.assertEqual([], engine(self.NOBARCODE)) def _test_dm(self, engine, type='Data Matrix'): - expected = [Barcode(type=type, data=u'Triceratops')] + expected = [Barcode(type=type, data=b'Triceratops')] res = engine(self.DATAMATRIX) self.assertEqual(expected, res) self.assertEqual([], engine(self.NOBARCODE)) def _test_qr(self, engine, type='QR Code'): - expected = [Barcode(type=type, data=u'Thalassiodracon')] + expected = [Barcode(type=type, data=b'Thalassiodracon')] res = engine(self.QRCODE) self.assertEqual(expected, res) self.assertEqual([], engine(self.NOBARCODE)) def _test_pdf417(self, engine, type='PDF 417'): - expected = [Barcode(type=type, data=u'Metasequoia')] + expected = [Barcode(type=type, data=b'Metasequoia')] res = engine(self.PDF417) self.assertEqual(expected, res) self.assertEqual([], engine(self.NOBARCODE)) diff --git a/gouda/tests/test_rect.py b/gouda/tests/test_rect.py index c11e21b..5674283 100644 --- a/gouda/tests/test_rect.py +++ b/gouda/tests/test_rect.py @@ -41,7 +41,7 @@ def test_bottomright(self): self.assertEqual(Point(2, 4), self.R.bottomright) def test_centre(self): - self.assertEqual(Point(1, 2), self.R.centre) + self.assertEqual(Point(1.0, 2.5), self.R.centre) def test_comparison(self): a = Rect(0, 1, 2, 3) diff --git a/gouda/tests/test_strategies.py b/gouda/tests/test_strategies.py index 858d20d..8bdef1b 100644 --- a/gouda/tests/test_strategies.py +++ b/gouda/tests/test_strategies.py @@ -37,14 +37,14 @@ def _test(self, file, engine, strategy, expected): return method, barcodes def _test_1d(self, strategy): - expected = ['BM001128286', 'BM001128287', 'BM001128288'] + expected = [b'BM001128286', b'BM001128287', b'BM001128288'] return self._test('BM001128287.jpg', self.ONED_ENGINE, strategy, expected) def _test_dm(self, strategy): - expected = ['1265025'] + expected = [b'1265025'] return self._test('BMNHE_1265025.jpg', self.DM_ENGINE, strategy, diff --git a/gouda/util.py b/gouda/util.py index 534b485..4f2eda6 100644 --- a/gouda/util.py +++ b/gouda/util.py @@ -7,7 +7,7 @@ import cv2 try: - from _winreg import OpenKey, HKEY_LOCAL_MACHINE + from winreg import OpenKey, HKEY_LOCAL_MACHINE except ImportError: OpenKey = HKEY_LOCAL_MACHINE = None diff --git a/requirements.txt b/requirements.pip similarity index 57% rename from requirements.txt rename to requirements.pip index a737c7e..0a2a7b6 100644 --- a/requirements.txt +++ b/requirements.pip @@ -1,5 +1,5 @@ -pathlib>=1.0 -pylibdmtx>=0.1.1 +pathlib>=1.0; python_version == '2.7' +pylibdmtx>=0.1.2 numpy>=1.8.2 # Packages required for testing diff --git a/setup.py b/setup.py index f969ef3..bf16db0 100755 --- a/setup.py +++ b/setup.py @@ -30,10 +30,12 @@ }, 'install_requires': [ # TODO How to specify OpenCV? 'cv2>=2.4.8,<3', - 'pathlib>=1.0.1', 'pylibdmtx>=0.1.1', 'numpy>=1.8.2', ], + 'extras_require': { + ':python_version=="2.7"': ['pathlib>=1.0.1'], + }, 'tests_require': [ 'nose>=1.3.4', ], @@ -43,7 +45,10 @@ 'Topic :: Utilities', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', - ] + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ], } @@ -52,7 +57,7 @@ def setuptools_setup(): setup(**setup_data) -if (2, 7) == sys.version_info[:2]: +if (2, 7) == sys.version_info[:2] or (3, 4) <= sys.version_info: setuptools_setup() else: - sys.exit('Only Python 2.7 is supported') + sys.exit('Python versions 2.7 and >= 3.4 are supported') From 6e48cc0402586c20c4189b7e2ab8808b25ef2b3c Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Mon, 7 Nov 2016 10:56:27 +0000 Subject: [PATCH 02/13] [#26] bump version --- gouda/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gouda/__init__.py b/gouda/__init__.py index c3bb296..1c98a23 100644 --- a/gouda/__init__.py +++ b/gouda/__init__.py @@ -1 +1 @@ -__version__ = '0.1.8' +__version__ = '0.1.9' From a05b1273b3cf60f36d6d26e18e211bb85acc94fe Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Mon, 7 Nov 2016 11:07:09 +0000 Subject: [PATCH 03/13] [#26] CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0ca3fe..1c86bc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v0.1.9 +- #26 Python 3 support + # v0.1.8 - #24 Support pylibdmtx From 3a5cdc11a1dc00bf919a1f9471e84ad4aa68b628 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Mon, 7 Nov 2016 11:07:35 +0000 Subject: [PATCH 04/13] [#26] Updates --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 0024ff4..dc76b6c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,5 @@ dist/ .DS_Store .coverage htmlcov -setup.cfg -gouda/config_dev.py cover - +.tox From c7f69c61e6e54060beb0d7e796b6edc29327f282 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Mon, 7 Nov 2016 11:09:03 +0000 Subject: [PATCH 05/13] Initial commit --- tox.ini | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tox.ini diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..26e5577 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] +envlist = py27,py34,py35 + +[testenv] +sitepackages = True +deps = -rrequirements.pip +# Do not call nosetests here - it may will run the-system wide nosetests, which +# will be unaware of the created virtual environment +commands = python -m nose --verbose --with-coverage --cover-inclusive --cover-tests --cover-package=gouda gouda From 52e6f15c48eb9458286f3e08a25ea430d1662b86 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Mon, 7 Nov 2016 11:12:22 +0000 Subject: [PATCH 06/13] [#26] Update for requirements.pip --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d24fbcb..0253069 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ virtualenv: install: - sudo apt-get -qq update - sudo apt-get install -y --fix-missing libdmtx0a python-opencv - - pip install -r requirements.txt + - pip install -r requirements.pip script: - nosetests From 566d1a399b6f7668dc9cb7ab1b53a3bae3501f8b Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Mon, 7 Nov 2016 11:16:54 +0000 Subject: [PATCH 07/13] [#26] tox --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0253069..cd89614 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,13 +10,15 @@ python: virtualenv: system_site_packages: true -install: +before_install: - sudo apt-get -qq update - - sudo apt-get install -y --fix-missing libdmtx0a python-opencv - - pip install -r requirements.pip + - sudo apt-get install -y libdmtx0a python-opencv + +install: + - pip install tox-travis coveralls script: -- nosetests + - tox after_success: - coveralls From 823c822263c6c904a67bdc1fefb0909623a53717 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Wed, 9 Nov 2016 20:09:21 +0000 Subject: [PATCH 08/13] Merge master --- gouda/engines/zbar.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 gouda/engines/zbar.py diff --git a/gouda/engines/zbar.py b/gouda/engines/zbar.py new file mode 100644 index 0000000..a3f05f0 --- /dev/null +++ b/gouda/engines/zbar.py @@ -0,0 +1,34 @@ +# This file is not called zbar.py to avoid collision with the zbar package + +from gouda.barcode import Barcode +from gouda.gouda_error import GoudaError +from gouda.util import debug_print + +from PIL import Image + +try: + from pyzbar import pyzbar +except ImportError: + pyzbar = None + + +class ZbarEngine(object): + """Decode using the zbar library + + http://sourceforge.net/projects/zbar/ + https://pypi.python.org/pypi/pyzbar + """ + def __init__(self): + if not self.available(): + raise GoudaError('zbar unavailable') + + @classmethod + def available(cls): + return pyzbar is not None + + def decode_file(self, path): + return self(Image.open(str(path))) + + def __call__(self, img): + # Decode barcodes in img using pyzbar + return [Barcode(r.type, r.data) for r in pyzbar.decode(img)] From cae7924054e1492368e4be8928b44c2529d56993 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Wed, 9 Nov 2016 20:14:04 +0000 Subject: [PATCH 09/13] Merge master --- .travis.yml | 7 +++--- CHANGELOG.md | 5 +++- MANIFEST.in | 3 ++- README.md | 4 +-- build.sh | 2 +- decode_barcodes.spec | 26 +++++++++---------- gouda/__init__.py | 2 +- gouda/engines/__init__.py | 2 +- gouda/engines/libdmtx.py | 4 +-- gouda/engines/zbar_engine.py | 43 -------------------------------- gouda/scripts/decode_barcodes.py | 2 +- gouda/strategies/roi/roi.py | 2 +- gouda/util.py | 6 +++-- requirements.pip | 3 ++- setup.py | 3 ++- 15 files changed, 38 insertions(+), 76 deletions(-) delete mode 100644 gouda/engines/zbar_engine.py diff --git a/.travis.yml b/.travis.yml index cd89614..74c7421 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,13 +6,12 @@ sudo: python: - "2.7" - -virtualenv: - system_site_packages: true + - "3.4" + - "3.5" before_install: - sudo apt-get -qq update - - sudo apt-get install -y libdmtx0a python-opencv + - sudo apt-get install -y libdmtx0a libzbar0 python-opencv install: - pip install tox-travis coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c86bc8..b44df8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ -# v0.1.9 +# v0.1.10 - #26 Python 3 support +# v0.1.9 +- #27 Support pyzbar + # v0.1.8 - #24 Support pylibdmtx diff --git a/MANIFEST.in b/MANIFEST.in index cd664b0..9bc3e07 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include *.py -include requirements.txt +include requirements.pip +include gouda/tests/test_data/*png diff --git a/README.md b/README.md index 593fee5..0acd7f9 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ The easiest way is to install the current Python 2.7 release of conda update --all pip install --upgrade pip python \Scripts\pywin32_postinstall.py -install - pip install -r requirements.txt + pip install -r requirements.pip ## Install [OpenCV](http://www.opencv.org/) ### Linux @@ -98,7 +98,7 @@ Windows only. Download and install their [SDK](http://www.inliteresearch.com/). ### libdmtx The [pylibdmtx](https://pypi.python.org/pypi/pylibdmtx/) Python package is -a dependency of `gouda` and is listed in `requirements.txt`. +a dependency of `gouda` and is listed in `requirements.pip`. The `libdmtx` `DLL`s are included with the Windows Python wheel builds of `pylibdmtx`. diff --git a/build.sh b/build.sh index 8a91d66..caf219e 100755 --- a/build.sh +++ b/build.sh @@ -15,5 +15,5 @@ echo Tests nosetests --with-coverage --cover-html --cover-inclusive --cover-erase --cover-tests --cover-package=gouda echo Build -./setup.py sdist +./setup.py bdist_wheel pyinstaller --onefile --clean decode_barcodes.spec diff --git a/decode_barcodes.spec b/decode_barcodes.spec index fe0762e..e73dc9a 100644 --- a/decode_barcodes.spec +++ b/decode_barcodes.spec @@ -1,9 +1,13 @@ -# For PyInstaller build on Mac +# For PyInstaller build import sys from pathlib import Path +from pylibdmtx import pylibdmtx +from pyzbar import pyzbar + + block_cipher = None a = Analysis(['gouda/scripts/decode_barcodes.py'], @@ -18,13 +22,14 @@ a = Analysis(['gouda/scripts/decode_barcodes.py'], win_private_assemblies=False, cipher=block_cipher) -if 'darwin' == sys.platform: - # libdmtx dylib is not detected because it is loaded by a ctypes call in - # pylibdmtx - a.binaries += TOC([ - ('libdmtx.dylib', '/usr/local/Cellar/libdmtx/0.7.4/lib/libdmtx.dylib', 'BINARY'), - ]) +# dylibs not detected because they are loaded by ctypes +a.binaries += TOC([ + (Path(dep._name).name, dep._name, 'BINARY') + for dep in pylibdmtx.EXTERNAL_DEPENDENCIES + pyzbar.EXTERNAL_DEPENDENCIES +]) + +if 'darwin' == sys.platform: # PyInstaller does not detect some dylibs, in some cases (I think) because they # are symlinked. # See Stack Overflow post http://stackoverflow.com/a/17595149 for example @@ -56,13 +61,6 @@ if 'darwin' == sys.platform: a.binaries += TOC([ (lib, str(LIB.joinpath(lib).resolve()), 'BINARY') for lib in MISSING_DYLIBS ]) -elif 'win32' == sys.platform: - # libdmtx dylib is not detected because it is loaded by a ctypes call in - # pylibdmtx - fname = 'libdmtx-{0}.dll'.format('64' if sys.maxsize > 2**32 else '32') - a.binaries += TOC([ - (fname, str(Path(sys.argv[0]).parent.parent.joinpath(fname)), 'BINARY'), - ]) pyz = PYZ(a.pure, a.zipped_data, diff --git a/gouda/__init__.py b/gouda/__init__.py index 1c98a23..850505a 100644 --- a/gouda/__init__.py +++ b/gouda/__init__.py @@ -1 +1 @@ -__version__ = '0.1.9' +__version__ = '0.1.10' diff --git a/gouda/engines/__init__.py b/gouda/engines/__init__.py index ea66041..a2ac8c7 100644 --- a/gouda/engines/__init__.py +++ b/gouda/engines/__init__.py @@ -10,5 +10,5 @@ from .inlite import InliteEngine from .softek import SoftekEngine from .stecos import StecosEngine -from .zbar_engine import ZbarEngine +from .zbar import ZbarEngine from .zxing import ZxingEngine diff --git a/gouda/engines/libdmtx.py b/gouda/engines/libdmtx.py index 574c163..caf2b2d 100644 --- a/gouda/engines/libdmtx.py +++ b/gouda/engines/libdmtx.py @@ -1,7 +1,7 @@ import subprocess import tempfile -import cv2 +from PIL import Image from gouda import config from gouda.barcode import Barcode @@ -31,7 +31,7 @@ def available(cls): return pylibdmtx is not None def decode_file(self, path): - return self(cv2.imread(str(path))) + return self(Image.open(str(path))) def __call__(self, img): res = pylibdmtx.decode( diff --git a/gouda/engines/zbar_engine.py b/gouda/engines/zbar_engine.py deleted file mode 100644 index c88c958..0000000 --- a/gouda/engines/zbar_engine.py +++ /dev/null @@ -1,43 +0,0 @@ -# This file is not called zbar.py to avoid collision with the zbar package - -import cv2 - -from gouda.barcode import Barcode -from gouda.gouda_error import GoudaError -from gouda.util import debug_print - -try: - import zbar -except ImportError: - zbar = None - - -class ZbarEngine(object): - """Decode using the zbar library - - http://sourceforge.net/projects/zbar/ - https://pypi.python.org/pypi/zbar - """ - def __init__(self): - if not self.available(): - raise GoudaError('zbar unavailable') - - @classmethod - def available(cls): - return zbar is not None - - def decode_file(self, path): - return self(cv2.imread(str(path), cv2.IMREAD_GRAYSCALE)) - - def __call__(self, img): - # Decode barcodes in img using zbar - # https://github.com/ZBar/ZBar/blob/master/python/README - # https://github.com/herbyme/zbar/blob/master/python/examples/scan_image.py - scanner = zbar.ImageScanner() - height, width = img.shape[:2] - if 'uint8' != img.dtype or 2 != len(img.shape): - debug_print('Convert to greyscale for zbar') - img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - image = zbar.Image(width, height, 'Y800', img.tostring()) - scanner.scan(image) - return [Barcode(s.type, s.data) for s in image] diff --git a/gouda/scripts/decode_barcodes.py b/gouda/scripts/decode_barcodes.py index a78dd74..190934c 100755 --- a/gouda/scripts/decode_barcodes.py +++ b/gouda/scripts/decode_barcodes.py @@ -26,7 +26,7 @@ def decode(paths, strategies, engine, visitors, read_greyscale): """Finds and decodes barcodes in images given in pathss """ - for p in paths: + for p in sorted(paths): if p.is_dir(): # Descend into directory decode(p.iterdir(), strategies, engine, visitors, read_greyscale) diff --git a/gouda/strategies/roi/roi.py b/gouda/strategies/roi/roi.py index 006bb73..92e3dc5 100644 --- a/gouda/strategies/roi/roi.py +++ b/gouda/strategies/roi/roi.py @@ -25,4 +25,4 @@ def roi(img, engine): if barcode.data not in res: res[barcode.data] = barcode - return ('roi', list(res.values())) + return ('roi', list(res.values())) if res else None diff --git a/gouda/util.py b/gouda/util.py index 4f2eda6..3597be3 100644 --- a/gouda/util.py +++ b/gouda/util.py @@ -22,8 +22,10 @@ def debug_print(*args, **kwargs): def read_image(path, greyscale): - flags = cv2.IMREAD_GRAYSCALE if greyscale else cv2.IMREAD_UNCHANGED - return cv2.imread(str(path), flags) + if greyscale: + return cv2.imread(str(path), cv2.IMREAD_GRAYSCALE) + else: + return cv2.imread(str(path)) def expand_wildcard(args): diff --git a/requirements.pip b/requirements.pip index 0a2a7b6..490e181 100644 --- a/requirements.pip +++ b/requirements.pip @@ -1,5 +1,6 @@ pathlib>=1.0; python_version == '2.7' -pylibdmtx>=0.1.2 +pylibdmtx==0.1.4 +pyzbar==0.1.2 numpy>=1.8.2 # Packages required for testing diff --git a/setup.py b/setup.py index bf16db0..0d82208 100755 --- a/setup.py +++ b/setup.py @@ -30,7 +30,8 @@ }, 'install_requires': [ # TODO How to specify OpenCV? 'cv2>=2.4.8,<3', - 'pylibdmtx>=0.1.1', + 'pylibdmtx>=0.1.4', + 'pyzbar>=0.1.2', 'numpy>=1.8.2', ], 'extras_require': { From 30c9d424c8532554798383cab29ca593dc320d15 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Thu, 10 Nov 2016 05:06:36 +0000 Subject: [PATCH 10/13] Decode for regex --- gouda/scripts/decode_barcodes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gouda/scripts/decode_barcodes.py b/gouda/scripts/decode_barcodes.py index 190934c..6188932 100755 --- a/gouda/scripts/decode_barcodes.py +++ b/gouda/scripts/decode_barcodes.py @@ -138,7 +138,9 @@ def result(self, path, result): print(' No barcodes') else: # TODO How best to sanitize filenames? - values = [re.sub('[^a-zA-Z0-9_-]', '_', b.data) for b in barcodes] + values = [ + re.sub('[^a-zA-Z0-9_-]', '_', b.data.decode()) for b in barcodes + ] # The first time round the loop, the file will be renamed and # first_destination set to the new filename. From e4274735c0491e4d1cc9a9dd30581efbf4cf5b7a Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Thu, 10 Nov 2016 11:33:41 +0000 Subject: [PATCH 11/13] Updates --- requirements.pip | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/requirements.pip b/requirements.pip index 490e181..58467cf 100644 --- a/requirements.pip +++ b/requirements.pip @@ -1,7 +1,9 @@ -pathlib>=1.0; python_version == '2.7' +# TODO How to specify OpenCV? 'cv2>=2.4.8' +pathlib==1.0.1; python_version == '2.7' pylibdmtx==0.1.4 pyzbar==0.1.2 -numpy>=1.8.2 +Pillow==3.4.2 +numpy==1.11.2 # Packages required for testing coveralls>=1.1 From 6f660f78effaf1db4f176d3d517cf57d82b57f50 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Fri, 11 Nov 2016 15:53:44 +0000 Subject: [PATCH 12/13] Update dependencies --- setup.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 0d82208..b1ef16b 100755 --- a/setup.py +++ b/setup.py @@ -29,7 +29,8 @@ ['{0}=gouda.scripts.{0}:main'.format(script) for script in SCRIPTS], }, 'install_requires': [ - # TODO How to specify OpenCV? 'cv2>=2.4.8,<3', + # TODO How to specify OpenCV? 'cv2>=2.4.8' + 'Pillow>=3.2.0', 'pylibdmtx>=0.1.4', 'pyzbar>=0.1.2', 'numpy>=1.8.2', @@ -37,9 +38,6 @@ 'extras_require': { ':python_version=="2.7"': ['pathlib>=1.0.1'], }, - 'tests_require': [ - 'nose>=1.3.4', - ], 'classifiers': [ 'Development Status :: 4 - Beta', 'License :: OSI Approved :: MIT License', From dc58462e5cad5153924a6b2387e997933846cde2 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Fri, 11 Nov 2016 16:00:22 +0000 Subject: [PATCH 13/13] Back to 2.7 only --- .travis.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 74c7421..d6626fd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,18 +6,17 @@ sudo: python: - "2.7" - - "3.4" - - "3.5" -before_install: - - sudo apt-get -qq update - - sudo apt-get install -y libdmtx0a libzbar0 python-opencv +virtualenv: + system_site_packages: true install: - - pip install tox-travis coveralls + - sudo apt-get -qq update + - sudo apt-get install -y --fix-missing libdmtx0a libzbar0 python-opencv + - pip install -r requirements.pip script: - - tox + - python -m nose --verbose --with-coverage --cover-inclusive --cover-tests --cover-package=gouda gouda after_success: - coveralls