Skip to content

Commit

Permalink
Merge pull request #25 from NaturalHistoryMuseum/feature/24-pylibdmtx
Browse files Browse the repository at this point in the history
Feature/24 pylibdmtx
  • Loading branch information
quicklizard99 authored Nov 4, 2016
2 parents ca5f273 + feafbf0 commit 80dc61d
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 155 deletions.
17 changes: 9 additions & 8 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
language: python
language:
- python

sudo:
- required

python:
- "2.7"

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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# v0.1.8
- #24 Support pylibdmtx

# v0.1.7
- #21 Build scripts to exit on failure
Expand Down
63 changes: 10 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,68 +97,25 @@ 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:
The [pylibdmtx](https://pypi.python.org/pypi/pylibdmtx/) Python package is
a dependency of `gouda` and is listed in `requirements.txt`.

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)"
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

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

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

Library

cd libdmtx
git checkout v0.7.4
./autogen.sh
./configure
make
make install

Python lib

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)
Install the `libdmtx` shared lib.

cd ../dmtx-utils
brew install imagemagick
git checkout v0.7.4
./autogen.sh
./configure
make
brew install libdmtx

### Softek
Linux, OS X and Windows.
Expand Down
17 changes: 15 additions & 2 deletions decode_barcodes.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -51,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, str(Path(sys.argv[0]).parent.parent.joinpath(fname)), 'BINARY'),
])


pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
Expand Down
89 changes: 11 additions & 78 deletions gouda/engines/libdmtx.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
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
from gouda.gouda_error import GoudaError
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')

Expand All @@ -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]
6 changes: 5 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
pathlib>=1.0
Pillow>=2.7.0
pylibdmtx>=0.1.1
numpy>=1.8.2

# Packages required for testing
coveralls>=1.1
nose>=1.3.4
66 changes: 53 additions & 13 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,57 @@
#!/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='[email protected]',
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 = ['decode_barcodes']

URL = 'https://github.com/NaturalHistoryMuseum/gouda/'


setup_data = {
'name': 'gouda',
'version': gouda.__version__,
'author': 'Lawrence Hudson',
'author_email': '[email protected]',
'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',
'pathlib>=1.0.1',
'pylibdmtx>=0.1.1',
'numpy>=1.8.2',
],
'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')

0 comments on commit 80dc61d

Please sign in to comment.