194 lines
6.1 KiB
Python
194 lines
6.1 KiB
Python
import platform
|
|
import unittest
|
|
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from unittest.mock import patch
|
|
except ImportError:
|
|
# Python 2
|
|
from mock import patch
|
|
|
|
import numpy as np
|
|
|
|
from PIL import Image
|
|
|
|
try:
|
|
import cv2
|
|
except ImportError:
|
|
cv2 = None
|
|
|
|
from pyzbar.pyzbar import (
|
|
decode, Decoded, Rect, ZBarSymbol, EXTERNAL_DEPENDENCIES
|
|
)
|
|
from pyzbar.pyzbar_error import PyZbarError
|
|
|
|
|
|
TESTDATA = Path(__file__).parent
|
|
|
|
|
|
class TestDecode(unittest.TestCase):
|
|
EXPECTED_CODE128 = [
|
|
Decoded(
|
|
data=b'Foramenifera',
|
|
type='CODE128',
|
|
rect=Rect(left=37, top=550, width=324, height=76),
|
|
polygon=[(37, 551), (37, 625), (361, 626), (361, 550)]
|
|
),
|
|
Decoded(
|
|
data=b'Rana temporaria',
|
|
type='CODE128',
|
|
rect=Rect(left=4, top=0, width=390, height=76),
|
|
polygon=[(4, 1), (4, 75), (394, 76), (394, 0)]
|
|
)
|
|
]
|
|
|
|
EXPECTED_QRCODE = [
|
|
Decoded(
|
|
b'Thalassiodracon',
|
|
type='QRCODE',
|
|
rect=Rect(left=27, top=27, width=145, height=145),
|
|
polygon=[(27, 27), (27, 172), (172, 172), (172, 27)]
|
|
)
|
|
]
|
|
|
|
# Two barcodes, both with same content
|
|
EXPECTED_QRCODE_ROTATED = [
|
|
Decoded(
|
|
data=b'Thalassiodracon',
|
|
type='QRCODE',
|
|
rect=Rect(left=173, top=10, width=205, height=205),
|
|
polygon=[(173, 113), (276, 215), (378, 113), (276, 10)]),
|
|
Decoded(
|
|
data=b'Thalassiodracon',
|
|
type='QRCODE',
|
|
rect=Rect(left=32, top=208, width=158, height=158),
|
|
polygon=[(32, 352), (177, 366), (190, 222), (46, 208)])
|
|
]
|
|
|
|
def setUp(self):
|
|
self.code128, self.qrcode, self.qrcode_rotated, self.empty = (
|
|
Image.open(str(TESTDATA.joinpath(fname)))
|
|
for fname in
|
|
('code128.png', 'qrcode.png', 'qrcode_rotated.png', 'empty.png')
|
|
)
|
|
self.maxDiff = None
|
|
|
|
def tearDown(self):
|
|
self.code128 = self.empty = self.qrcode = None
|
|
|
|
def test_decode_code128(self):
|
|
"Read both barcodes in `code128.png`"
|
|
res = decode(self.code128)
|
|
self.assertEqual(self.EXPECTED_CODE128, res)
|
|
|
|
def test_decode_qrcode(self):
|
|
"Read barcode in `qrcode.png`"
|
|
res = decode(self.qrcode)
|
|
self.assertEqual(self.EXPECTED_QRCODE, res)
|
|
|
|
def test_decode_qrcode_rotated(self):
|
|
"Read barcode in `qrcode_rotated.png`"
|
|
# Test computation of the polygon around the barcode
|
|
res = decode(self.qrcode_rotated)
|
|
self.assertEqual(self.EXPECTED_QRCODE_ROTATED, res)
|
|
|
|
def test_symbols(self):
|
|
"Read only qrcodes in `qrcode.png`"
|
|
res = decode(self.qrcode, symbols=[ZBarSymbol.QRCODE])
|
|
self.assertEqual(self.EXPECTED_QRCODE, res)
|
|
|
|
def test_symbols_not_present(self):
|
|
"Read only code128 in `qrcode.png`"
|
|
res = decode(self.qrcode, symbols=[ZBarSymbol.CODE128])
|
|
self.assertEqual([], res)
|
|
|
|
def test_decode_tuple(self):
|
|
"Read barcodes in pixels"
|
|
pixels = self.code128.copy().convert('L').tobytes()
|
|
width, height = self.code128.size
|
|
res = decode((pixels, width, height))
|
|
self.assertEqual(self.EXPECTED_CODE128, res)
|
|
|
|
def test_unsupported_bpp(self):
|
|
pixels = self.code128.tobytes()
|
|
width, height = self.code128.size
|
|
self.assertRaises(PyZbarError, decode, (pixels, width, height))
|
|
|
|
def test_empty(self):
|
|
"Do not show any output for an image that does not contain a barcode"
|
|
res = decode(self.empty)
|
|
self.assertEqual([], res)
|
|
|
|
def test_decode_numpy(self):
|
|
"Read image using Pillow and convert to numpy.ndarray"
|
|
res = decode(np.asarray(self.code128))
|
|
self.assertEqual(self.EXPECTED_CODE128, res)
|
|
|
|
@unittest.skipIf(cv2 is None, 'OpenCV not installed')
|
|
def test_decode_opencv(self):
|
|
"Read image using OpenCV"
|
|
res = decode(cv2.imread(str(TESTDATA.joinpath('code128.png'))))
|
|
self.assertEqual(self.EXPECTED_CODE128, res)
|
|
|
|
def test_external_dependencies(self):
|
|
"External dependencies"
|
|
if 'Windows' == platform.system():
|
|
self.assertEqual(2, len(EXTERNAL_DEPENDENCIES))
|
|
self.assertTrue(
|
|
any('libiconv' in d._name for d in EXTERNAL_DEPENDENCIES)
|
|
)
|
|
self.assertTrue(
|
|
any('libzbar' in d._name for d in EXTERNAL_DEPENDENCIES)
|
|
)
|
|
|
|
@patch('pyzbar.pyzbar.zbar_image_create')
|
|
def test_zbar_image_create_fail(self, zbar_image_create):
|
|
zbar_image_create.return_value = None
|
|
self.assertRaisesRegexp(
|
|
PyZbarError, 'Could not create zbar image', decode, self.code128
|
|
)
|
|
zbar_image_create.assert_called_once_with()
|
|
|
|
@patch('pyzbar.pyzbar.zbar_image_scanner_create')
|
|
def test_zbar_image_scanner_create_fail(self, zbar_image_scanner_create):
|
|
zbar_image_scanner_create.return_value = None
|
|
self.assertRaisesRegexp(
|
|
PyZbarError, 'Could not create image scanner', decode, self.code128
|
|
)
|
|
zbar_image_scanner_create.assert_called_once_with()
|
|
|
|
@patch('pyzbar.pyzbar.zbar_scan_image')
|
|
def test_zbar_scan_image_fail(self, zbar_scan_image):
|
|
zbar_scan_image.return_value = -1
|
|
self.assertRaisesRegexp(
|
|
PyZbarError, 'Unsupported image format', decode, self.code128
|
|
)
|
|
self.assertEqual(1, zbar_scan_image.call_count)
|
|
|
|
def test_unsupported_bits_per_pixel(self):
|
|
# 16 bits-per-pixel
|
|
data = (list(range(3 * 3 * 2)), 3, 3)
|
|
self.assertRaisesRegexp(
|
|
PyZbarError,
|
|
r'Unsupported bits-per-pixel \[16\]. Only \[8\] is supported.',
|
|
decode, data
|
|
)
|
|
self.assertRaises(PyZbarError, decode, data)
|
|
|
|
def test_inconsistent_dimensions(self):
|
|
# Ten bytes but width x height indicates nine bytes
|
|
data = (list(range(10)), 3, 3)
|
|
self.assertRaisesRegexp(
|
|
PyZbarError,
|
|
(
|
|
r'Inconsistent dimensions: image data of 10 bytes is not '
|
|
r'divisible by \(width x height = 9\)'
|
|
),
|
|
decode, data
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|