Skip to content

Commit

Permalink
[#16] Tidying
Browse files Browse the repository at this point in the history
  • Loading branch information
quicklizard99 committed Mar 14, 2018
1 parent 1aa81f7 commit e8962e8
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 67 deletions.
10 changes: 5 additions & 5 deletions DEVELOPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pip install tox
If you use non-standard locations for your Python builds, make the interpreters available on the `PATH` before running `tox`.

```
PATH=~/local/python-2.7.13/bin:~/local/python-3.4.6/bin:~/local/python-3.5.3/bin:~/local/python-3.6.0/bin:$PATH
PATH=~/local/python-2.7.14/bin:~/local/python-3.4.7/bin:~/local/python-3.5.4/bin:~/local/python-3.6.3/bin:$PATH
tox
```

Expand All @@ -46,8 +46,8 @@ brew install pandoc

2. Build
Generate the `reStructuredText README.rst` from `README.md` and create
source and wheel builds. The `win32` and `win_amd64` will contain the
appropriate `zbar.dll` and its dependencies.
source and wheel builds. The `win32` and `win_amd64` wheels will
contain the appropriate `zbar.dll` and its dependencies.

Including just the DLLs we want is pain...

Expand All @@ -60,8 +60,8 @@ brew install pandoc
cat MANIFEST.in.all MANIFEST.in.win32 > MANIFEST.in
./setup.py bdist_wheel --plat-name=win32
# Remove build to prevent win32 DLLs from being included in win64 build
rm -rf build
# Remove these dirs to prevent win32 DLLs from being included in win64 build
rm -rf build pyzbar.egg-info
cat MANIFEST.in.all MANIFEST.in.win64 > MANIFEST.in
./setup.py bdist_wheel --plat-name=win_amd64
Expand Down
24 changes: 11 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,28 @@
[![Travis status](https://travis-ci.org/NaturalHistoryMuseum/pyzbar.svg?branch=master)](https://travis-ci.org/NaturalHistoryMuseum/pyzbar)
[![Coverage Status](https://coveralls.io/repos/github/NaturalHistoryMuseum/pyzbar/badge.svg?branch=master)](https://coveralls.io/github/NaturalHistoryMuseum/pyzbar?branch=master)

A `ctypes`-based Python wrapper around the [zbar](http://zbar.sourceforge.net/)
barcode reader.
Read one-dimensional barcodes and QR codes from Python 2 and 3 using the
[zbar](http://zbar.sourceforge.net/) library.

The
[zbar](https://sourceforge.net/p/zbar/code/ci/default/tree/python/)
wrapper is stuck in Python 2.x-land.
The [zbarlight](https://github.com/Polyconseil/zbarlight/) wrapper doesn't
The older [zbar](https://sourceforge.net/p/zbar/code/ci/default/tree/python/)
package is stuck in Python 2.x-land.
The [zbarlight](https://github.com/Polyconseil/zbarlight/) package doesn't
provide support for Windows and depends upon Pillow.
This `ctypes`-based wrapper brings `zbar` to Python 2.7 and to Python 3.4 or
greater.
This pure-Python `ctypes`-based package brings `zbar` to Python 2.7 and to
Python 3.4 or greater.

## Installation

The `zbar` `DLL`s are included with the Windows Python wheels.
On other operating systems, you will need to install the `zbar` shared library.

On Mac OS X:
Mac OS X:

```
brew install zbar
```

On Linux:
Linux:

```
sudo apt-get install libzbar0
Expand Down Expand Up @@ -90,9 +89,8 @@ Traceback (most recent call last):
pyzbar.pyzbar_error.PyZbarError: Unsupported bits-per-pixel [24]
```

`zbar`'s default behaviour is (I think) to decode all symbol types.
You can ask `zbar` to look for just your symbol types (I have no idea of the
effect of this on performance)
The default behaviour is to decode all symbol types. You can look for just your
symbol types

```
>>> from pyzbar.pyzbar import ZBarSymbol
Expand Down
95 changes: 58 additions & 37 deletions pyzbar/pyzbar.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import print_function

from operator import itemgetter
from collections import namedtuple
from contextlib import contextmanager
from ctypes import cast, c_void_p, string_at
from operator import itemgetter

from .pyzbar_error import PyZbarError
from .wrapper import (
Expand All @@ -18,7 +18,6 @@

__all__ = ['decode', 'EXTERNAL_DEPENDENCIES']

RANGEFN = getattr(globals(), 'xrange', range)

# A rectangle
Rect = namedtuple('Rect', ['left', 'top', 'width', 'height'])
Expand All @@ -32,16 +31,16 @@
'GRAY': 1497715271
}

RANGEFN = getattr(globals(), 'xrange', range)


@contextmanager
def zbar_image():
"""A context manager for `zbar_image`, created and destoyed by
`zbar_image_create` and `zbar_image_destroy`.
Args:
Yields:
zbar_image: The created image
POINTER(zbar_image): The created image
Raises:
PyZbarError: If the image could not be created.
Expand All @@ -61,10 +60,8 @@ def zbar_image_scanner():
"""A context manager for `zbar_image_scanner`, created and destroyed by
`zbar_image_scanner_create` and `zbar_image_scanner_destroy`.
Args:
Yields:
zbar_image_scanner: The created scanner
POINTER(zbar_image_scanner): The created scanner
Raises:
PyZbarError: If the decoder could not be created.
Expand All @@ -80,30 +77,74 @@ def zbar_image_scanner():


def bounding_box_of_locations(locations):
"""Computes bounding boxes from scan locations.
"""Computes a bounding box from scan locations.
Args:
locations: :obj:`list` of tuple (x, y) values.
locations: iterable of tuples of ints (x, y).
Returns:
:obj:`list` of :obj:`Rect`: Coordinates of the bounding boxes.
`Rect`: Coordinates of the bounding box.
"""
x_values = list(map(itemgetter(0), locations))
x_min, x_max = min(x_values), max(x_values)
y_values = list(map(itemgetter(1), locations))
y_min, y_max = min(y_values), max(y_values)
return (Rect(x_min, y_min, x_max - x_min, y_max - y_min))
return Rect(x_min, y_min, x_max - x_min, y_max - y_min)


def decode(image, symbols=None):
def symbols_for_image(image):
"""Generator of symbols.
Args:
image: `zbar_image`
Yields:
POINTER(zbar_symbol): Symbol
"""
symbol = zbar_image_first_symbol(image)
while symbol:
yield symbol
symbol = zbar_symbol_next(symbol)


def decode_symbols(symbols):
"""Generator of decoded symbol information.
Args:
image: iterable of instances of `POINTER(zbar_symbol)`
Yields:
Decoded: decoded symbol
"""
for symbol in symbols:
data = string_at(zbar_symbol_get_data(symbol))
# The 'type' int in a value in the ZBarSymbol enumeration
symbol_type = ZBarSymbol(symbol.contents.type).name
locations = [
(
zbar_symbol_get_loc_x(symbol, index),
zbar_symbol_get_loc_y(symbol, index)
)
for index in RANGEFN(zbar_symbol_get_loc_size(symbol))
]

yield Decoded(
data=data,
type=symbol_type,
rect=bounding_box_of_locations(locations),
)


def decode(image, symbols=None, scan_locations=False):
"""Decodes datamatrix barcodes in `image`.
Args:
image: `numpy.ndarray`, `PIL.Image` or tuple (pixels, width, height)
symbols (ZBarSymbol): the symbol types to decode; if `None`, use
`zbar`'s default behaviour, which (I think) is to decode all
symbol types.
symbols (ZBarSymbol): the symbol types to decode; if `None`, uses
`zbar`'s default behaviour, which is to decode all symbol types.
scan_locations (bool): If `True`, results will include scan
locations.
Returns:
:obj:`list` of :obj:`Decoded`: The values decoded from barcodes.
Expand Down Expand Up @@ -163,26 +204,6 @@ def decode(image, symbols=None):
if decoded < 0:
raise PyZbarError('Unsupported image format')
else:
symbol = zbar_image_first_symbol(img)
while symbol:
data = string_at(zbar_symbol_get_data(symbol))
symbol_type = ZBarSymbol(symbol.contents.value).name

loc = zbar_symbol_get_loc_size(symbol)
locations = [
(
zbar_symbol_get_loc_x(symbol, l),
zbar_symbol_get_loc_y(symbol, l)
)
for l in RANGEFN(loc)
]

results.append(Decoded(
data=data,
type=symbol_type,
rect=bounding_box_of_locations(locations)
))

symbol = zbar_symbol_next(symbol)
results.extend(decode_symbols(symbols_for_image(img)))

return results
2 changes: 0 additions & 2 deletions pyzbar/tests/test_read_zbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
else:
from io import StringIO


from PIL import Image

from pyzbar.scripts.read_zbar import main
Expand Down Expand Up @@ -37,7 +36,6 @@ def test_read_qrcode(self):

self.assertEqual(expected, stdout.getvalue().strip())


def test_read_code128(self):
"Read CODE 128 barcodes"
with capture_stdout() as stdout:
Expand Down
29 changes: 21 additions & 8 deletions pyzbar/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ class zbar_image(Structure):
pass


class zbar_symbol(Structure):
"""Opaque C++ class with private implementation
The first item in the structure is an integeger value in the ZBarSymbol
enumeration.
"""
_fields_ = [
('type', c_int),
]


# Globals populated in load_libzbar
LIBZBAR = None
"""ctypes.CDLL
Expand All @@ -89,6 +100,8 @@ class zbar_image(Structure):

def load_libzbar():
"""Loads the zbar shared library and its dependencies.
Populates the globals LIBZBAR and EXTERNAL_DEPENDENCIES.
"""
global LIBZBAR
global EXTERNAL_DEPENDENCIES
Expand Down Expand Up @@ -245,44 +258,44 @@ def zbar_function(fname, restype, *args):

zbar_image_first_symbol = zbar_function(
'zbar_image_first_symbol',
POINTER(c_int), # values in ZBarSymbol
POINTER(zbar_symbol),
POINTER(zbar_image)
)

zbar_symbol_get_data_length = zbar_function(
'zbar_symbol_get_data_length',
c_uint,
POINTER(c_int) # values in ZBarSymbol
POINTER(zbar_symbol)
)

zbar_symbol_get_data = zbar_function(
'zbar_symbol_get_data',
c_char_p,
POINTER(c_int) # values in ZBarSymbol
POINTER(zbar_symbol)
)

zbar_symbol_get_loc_size = zbar_function(
'zbar_symbol_get_loc_size',
c_uint,
POINTER(c_int) # values in ZBARSymbol
POINTER(zbar_symbol)
)

zbar_symbol_get_loc_x = zbar_function(
'zbar_symbol_get_loc_x',
c_int,
POINTER(c_int), # values in ZBARSymbol
POINTER(zbar_symbol),
c_uint
)

zbar_symbol_get_loc_y = zbar_function(
'zbar_symbol_get_loc_y',
c_int,
POINTER(c_int), # values in ZBARSymbol
POINTER(zbar_symbol),
c_uint
)

zbar_symbol_next = zbar_function(
'zbar_symbol_next',
POINTER(c_int),
POINTER(c_int) # values in ZBarSymbol
POINTER(zbar_symbol),
POINTER(zbar_symbol)
)
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ def readme():
'test_suite': 'pyzbar.tests',
'scripts': ['pyzbar/scripts/{0}.py'.format(script) for script in SCRIPTS],
'entry_points': {
'console_scripts':
['{0}=pyzbar.scripts.{0}:main'.format(script) for script in SCRIPTS],
'console_scripts': [
'{0}=pyzbar.scripts.{0}:main'.format(script) for script in SCRIPTS
],
},
'extras_require': {
':python_version=="2.7"': ['enum34>=1.1.6', 'pathlib>=1.0.1'],
Expand Down

0 comments on commit e8962e8

Please sign in to comment.