From 9ba06de13cffcc5c244e0ad41a3f0eaa31218cd7 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Tue, 1 Nov 2016 20:08:48 +0000 Subject: [PATCH 1/7] [#24] Support pylibdmtx --- .travis.yml | 17 ++++---- CHANGELOG.md | 1 + README.md | 59 ++++---------------------- gouda/engines/libdmtx.py | 89 +++++----------------------------------- requirements.txt | 5 ++- 5 files changed, 34 insertions(+), 137 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5873ab6..d24fbcb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,8 @@ -language: python +language: + - python + +sudo: + - required python: - "2.7" @@ -6,16 +10,13 @@ python: virtualenv: system_site_packages: true -before_install: - - sudo apt-get update - install: - - sudo apt-get install --fix-missing python-opencv python-numpy python-zbar libdmtx-dev - - pip install -e 'git+git://libdmtx.git.sourceforge.net/gitroot/libdmtx/dmtx-wrappers#egg=pydmtx&subdirectory=python' + - sudo apt-get -qq update + - sudo apt-get install -y --fix-missing libdmtx0a python-opencv - pip install -r requirements.txt - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start" script: - nosetests +after_success: + - coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 59b27ce..d0ca3fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # v0.1.8 +- #24 Support pylibdmtx # v0.1.7 - #21 Build scripts to exit on failure diff --git a/README.md b/README.md index be10577..b8c1336 100644 --- a/README.md +++ b/README.md @@ -97,68 +97,27 @@ Windows only. Download and install their [SDK](http://www.inliteresearch.com/). ### libdmtx -#### Windows - -Install the appropriate build of `pydmtx` from -[dmtx-wrapper](https://github.com/NaturalHistoryMuseum/dmtx-wrappers/) - one of: - - pip install https://github.com/NaturalHistoryMuseum/dmtx-wrappers/releases/download/v0.7.4b1/pydmtx-0.7.4b1-cp27-none-win32.whl - pip install https://github.com/NaturalHistoryMuseum/dmtx-wrappers/releases/download/v0.7.4b1/pydmtx-0.7.4b1-cp27-none-win_amd64.whl - -Test - - python -c "import pydmtx; print(pydmtx)" - #### Linux -Library and utils - - sudo apt-get install libdmtx-dev libdmtx-utils - -Python lib +Install the `libdmtx` shared lib. - git clone git://libdmtx.git.sourceforge.net/gitroot/libdmtx/dmtx-wrappers - cd dmtx-wrappers/python - python setup.py install - python -c "import pydmtx; print(pydmtx)" - python test.py + sudo apt-get install libdmtx0a #### OS X -Source +Install the `libdmtx` shared lib. - git clone git://libdmtx.git.sourceforge.net/gitroot/libdmtx/libdmtx - git clone git://libdmtx.git.sourceforge.net/gitroot/libdmtx/dmtx-wrappers - git clone git://libdmtx.git.sourceforge.net/gitroot/libdmtx/dmtx-utils + brew install libdmtx -Library +### All OSes - cd libdmtx - git checkout v0.7.4 - ./autogen.sh - ./configure - make - make install +Install Python wrapper -Python lib + pip install pylibdmtx - cd ../dmtx-wrappers/ - ./autogen.sh - ./configure - make - - cd python - python setup.py install - python -c "import pydmtx; print(pydmtx)" - -Command-line tools (not used but might be useful to you) +Test - cd ../dmtx-utils - brew install imagemagick - git checkout v0.7.4 - ./autogen.sh - ./configure - make + python -c "import pylibdmtx; print(pylibdmtx)" ### Softek Linux, OS X and Windows. diff --git a/gouda/engines/libdmtx.py b/gouda/engines/libdmtx.py index 57e0fe3..bcf9f58 100644 --- a/gouda/engines/libdmtx.py +++ b/gouda/engines/libdmtx.py @@ -1,10 +1,9 @@ +from __future__ import absolute_import + import subprocess import tempfile import cv2 -import numpy as np - -from PIL import Image from gouda import config from gouda.barcode import Barcode @@ -12,20 +11,17 @@ from gouda.util import debug_print try: - from pydmtx import DataMatrix + from pylibdmtx import pylibdmtx except ImportError: - DataMatrix = None + pylibdmtx = None -# Two interfaces to libdmtx - WrapperLibDMTXEngine uses pydmtx -# SubprocessLibDMTXEngine uses the dmtxread command-line tool -# WrapperLibDMTXEngine preferred - both kept here for reference -class WrapperLibDMTXEngine(object): - """Decode Data Matrix barcodes using the libdmtx decoder via pydmtx +class LibDMTXEngine(object): + """Decode Data Matrix barcodes using the libdmtx decoder via pylibdmtx """ - def __init__(self, timeout_ms=300, max_count=1): + def __init__(self, timeout_ms=300, max_count=None): if not self.available(): raise GoudaError('libdmtx unavailable') @@ -34,77 +30,14 @@ def __init__(self, timeout_ms=300, max_count=1): @classmethod def available(cls): - return DataMatrix is not None + return pylibdmtx is not None def decode_file(self, path): return self(cv2.imread(str(path))) def __call__(self, img): - d = DataMatrix(timeout=self.timeout_ms, max_count=self.max_count) - - # PIL RGB image from BGR np.ndarray - # http://stackoverflow.com/a/4661652/1773758 - img = Image.fromarray(np.roll(img, 1, axis=-1)) - img = img.convert('RGB') - - res = d.decode(img.size[0], img.size[1], buffer(img.tobytes())) - - res = [None] * d.count() - for i in xrange(0, d.count()): - res[i] = Barcode('Data Matrix', d.message(1+i)) - return res - - -class SubprocessLibDMTXEngine(object): - """Decode Data Matrix barcodes using the libdmtx decoder via dmtxread - """ - - DTMXREAD = getattr(config, 'LIBDMTX_DTMXREAD', None) - - def __init__(self, timeout_ms=300): - if not self.available(): - raise GoudaError('libdmtx unavailable') - elif timeout_ms < 0: - raise GoudaError('Bad timeout [{0}]'.format(timeout_ms)) - else: - self.timeout_ms = timeout_ms - - @classmethod - def available(cls): - return cls.DTMXREAD is not None and cls.DTMXREAD.is_file() - - def decode_file(self, path): - dmtx = [ - str(self.DTMXREAD), - '--newline', - '--stop-after=1', - '--milliseconds={0}'.format(self.timeout_ms), - str(path), - ] - # dmtxread returns a non-zero exit code if no barcodes are - # found - this is bone-headed. - dmtxread = subprocess.Popen( - dmtx, - stdout=subprocess.PIPE, stderr=subprocess.PIPE + res = pylibdmtx.decode( + img, timeout=self.timeout_ms, max_count=self.max_count ) - stdoutdata, stderrdata = dmtxread.communicate() - if stderrdata: - raise ValueError(stderrdata) - elif stdoutdata: - # TODO Coordinates? - stdoutdata = stdoutdata.strip().split('\n') - return [Barcode('Data Matrix', l) for l in stdoutdata] - else: - return [] - - def __call__(self, img): - # Decode data matrix barcodes in img using dmtxread - with tempfile.NamedTemporaryFile(suffix='.png') as img_temp: - debug_print( - 'Writing temp file [{0}] for dmtxread'.format(img_temp.name) - ) - cv2.imwrite(img_temp.name, img) - return self.decode_file(img_temp.name) -# Use the more more elegant and flexible solution -LibDMTXEngine = WrapperLibDMTXEngine + return [Barcode('Data Matrix', r.data) for r in res] diff --git a/requirements.txt b/requirements.txt index bbeb909..9a8fbc1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ pathlib>=1.0 -Pillow>=2.7.0 numpy>=1.8.2 + +# Packages required for testing +coveralls>=1.1 +nose>=1.3.4 From b9727bd2c906351ae2683fab4a1b9b9d91956165 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Tue, 1 Nov 2016 20:09:23 +0000 Subject: [PATCH 2/7] [#24] improved setup spec as part of adding pylibdmtx --- setup.py | 65 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/setup.py b/setup.py index 46f8c55..25a958e 100755 --- a/setup.py +++ b/setup.py @@ -1,17 +1,56 @@ #!/usr/bin/env python -from setuptools import setup +import sys import gouda -setup( - name='gouda', - version=gouda.__version__, - description=gouda.__doc__, - author='Lawrence Hudson', - author_email='l.hudson@nhm.ac.uk', - packages=['gouda', 'gouda.engines', 'gouda.java', 'gouda.strategies', - 'gouda.strategies.roi'], - test_suite="gouda.tests", - scripts=['gouda/scripts/decode_barcodes.py'], - install_requires=[l.replace('==', '>=') for l in open('requirements.txt')], -) +SCRIPTS = ['read_barcodes'] + +URL = 'https://github.com/NaturalHistoryMuseum/gouda/' + + +setup_data = { + 'name': 'gouda', + 'version': gouda.__version__, + 'author': 'Lawrence Hudson', + 'author_email': 'l.hudson@nhm.ac.uk', + 'url': URL, + 'description': gouda.__doc__, + 'long_description': 'Visit {0} for more details.'.format(URL), + 'packages': [ + 'gouda', 'gouda.engines', 'gouda.java', 'gouda.strategies', + 'gouda.strategies.roi', 'gouda.tests', + ], + 'include_package_data': True, + 'test_suite': 'gouda.tests', + 'scripts': ['gouda/scripts/{0}.py'.format(script) for script in SCRIPTS], + 'entry_points': { + 'console_scripts': + ['{0}=gouda.scripts.{0}:main'.format(script) for script in SCRIPTS], + }, + 'install_requires': [ + # TODO How to specify OpenCV? 'cv2>=2.4.8,<3', + 'numpy>=1.8.2', + 'pathlib>=1.0.1', + ], + 'tests_require': [ + 'nose>=1.3.4', + ], + 'classifiers': [ + 'Development Status :: 4 - Beta', + 'License :: OSI Approved :: MIT License', + 'Topic :: Utilities', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + ] +} + + +def setuptools_setup(): + from setuptools import setup + setup(**setup_data) + + +if (2, 7) == sys.version_info[:2]: + setuptools_setup() +else: + sys.exit('Only Python 2.7 is supported') From 55070455b6fba2c279c9fc9efc88ff80ecce3ebd Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Wed, 2 Nov 2016 07:44:20 +0000 Subject: [PATCH 3/7] Script name --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 25a958e..1953355 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ import gouda -SCRIPTS = ['read_barcodes'] +SCRIPTS = ['decode_barcodes'] URL = 'https://github.com/NaturalHistoryMuseum/gouda/' From 0049e65920b3179d7f906c5da1b0eb5f277d0315 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Thu, 3 Nov 2016 15:55:59 +0000 Subject: [PATCH 4/7] libdmtx.dylib --- decode_barcodes.spec | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/decode_barcodes.spec b/decode_barcodes.spec index 339e9cf..a0bd6ac 100644 --- a/decode_barcodes.spec +++ b/decode_barcodes.spec @@ -19,11 +19,16 @@ a = Analysis(['gouda/scripts/decode_barcodes.py'], cipher=block_cipher) if 'darwin' == sys.platform: - # PyInstaller does not detect some dylibs, I think in some cases because they + # 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'), + ]) + + # 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 # of manipulating Analysis.binaries. - MISSING_DYLIBS = ( 'libpng16.16.dylib', 'libz.1.dylib', From 17eae5df80e548ef1e7ff2c229feb4ad28b7ca06 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Thu, 3 Nov 2016 18:51:02 +0000 Subject: [PATCH 5/7] Windows build --- decode_barcodes.spec | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/decode_barcodes.spec b/decode_barcodes.spec index a0bd6ac..0e7f4ed 100644 --- a/decode_barcodes.spec +++ b/decode_barcodes.spec @@ -56,6 +56,14 @@ 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, Path(sys.argv[0]).parent.parent.joinpath(fname), 'BINARY'), + ]) + pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) From cfbfac73ba99e6bf9e3f0bb7001d8a8aeadd0ca7 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Thu, 3 Nov 2016 19:09:36 +0000 Subject: [PATCH 6/7] Correct object type for TOC --- decode_barcodes.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decode_barcodes.spec b/decode_barcodes.spec index 0e7f4ed..fe0762e 100644 --- a/decode_barcodes.spec +++ b/decode_barcodes.spec @@ -61,7 +61,7 @@ elif 'win32' == sys.platform: # pylibdmtx fname = 'libdmtx-{0}.dll'.format('64' if sys.maxsize > 2**32 else '32') a.binaries += TOC([ - (fname, Path(sys.argv[0]).parent.parent.joinpath(fname), 'BINARY'), + (fname, str(Path(sys.argv[0]).parent.parent.joinpath(fname)), 'BINARY'), ]) From feafbf03956eafe0643a13b3799f7aa241d07590 Mon Sep 17 00:00:00 2001 From: Lawrence Hudson Date: Fri, 4 Nov 2016 13:34:26 +0000 Subject: [PATCH 7/7] Public release of pylibdmtx --- README.md | 18 ++++++++---------- requirements.txt | 1 + setup.py | 3 ++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b8c1336..593fee5 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,14 @@ 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`. + +The `libdmtx` `DLL`s are included with the Windows Python wheel builds +of `pylibdmtx`. +On other operating systems, you will need to install the `libdmtx` shared +library. + #### Linux Install the `libdmtx` shared lib. @@ -109,16 +117,6 @@ Install the `libdmtx` shared lib. brew install libdmtx -### All OSes - -Install Python wrapper - - pip install pylibdmtx - -Test - - python -c "import pylibdmtx; print(pylibdmtx)" - ### Softek Linux, OS X and Windows. Download and install their [SDK](http://www.bardecode.com/). diff --git a/requirements.txt b/requirements.txt index 9a8fbc1..a737c7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ pathlib>=1.0 +pylibdmtx>=0.1.1 numpy>=1.8.2 # Packages required for testing diff --git a/setup.py b/setup.py index 1953355..313cd21 100755 --- a/setup.py +++ b/setup.py @@ -29,8 +29,9 @@ }, 'install_requires': [ # TODO How to specify OpenCV? 'cv2>=2.4.8,<3', - 'numpy>=1.8.2', 'pathlib>=1.0.1', + 'pylibdmtx>=0.1.1', + 'numpy>=1.8.2', ], 'tests_require': [ 'nose>=1.3.4',