Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Garden fix - kivy_garden package isn't a thing #43

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/kivy_garden/zbarcam/zbarcam.kv
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#:import XCamera kivy_garden.xcamera.XCamera
#:import is_android kivy_garden.zbarcam.utils.is_android
#:import XCamera kivy.garden.xcamera.XCamera
#:import is_android kivy.garden.zbarcam.utils.is_android
<ZBarCam>:
Widget:
# invert width/height on rotated Android
Expand Down
148 changes: 132 additions & 16 deletions src/kivy_garden/zbarcam/zbarcam.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,115 @@
import collections
import os
from collections import namedtuple
import queue
import threading

import PIL
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.logger import Logger
from kivy.properties import ListProperty
from kivy.uix.anchorlayout import AnchorLayout
from pyzbar import pyzbar

from .utils import fix_android_image

MODULE_DIRECTORY = os.path.dirname(os.path.realpath(__file__))


class ZBarDecoder:
def validate_code_types(self, code_types):
available_code_types = self.get_available_code_types()

if not all(
code_type in available_code_types
for code_type in code_types
):
raise ValueError(
f'Invalid code types: {code_types}. '
f'Available code types: {available_code_types}'
)


class PyZBarDecoder(ZBarDecoder):
@classmethod
def is_usable(cls):
try:
from pyzbar import pyzbar
cls.pyzbar = pyzbar
return True

except ImportError:
return False

def get_available_code_types(self):
return set(
code_type.lower()
for code_type in self.pyzbar.ZBarSymbol.__members__.keys()
)

def decode(self, image, code_types):
self.validate_code_types(code_types)
pyzbar_code_types = set(
getattr(self.pyzbar.ZBarSymbol, code_type.upper())
for code_type in code_types
)
return [
ZBarCam.Symbol(type=code.type.lower(), data=code.data)
for code in self.pyzbar.decode(
image,
symbols=pyzbar_code_types,
)
]


class ZBarLightDecoder(ZBarDecoder):
@classmethod
def is_usable(cls):
try:
import zbarlight
cls.zbarlight = zbarlight
return True

except ImportError:
return False

def get_available_code_types(self):
return set(
code_type.lower()
for code_type in self.zbarlight.Symbologies.keys()
)

def decode(self, image, code_types):
self.validate_code_types(code_types)
codes = self.zbarlight.scan_codes(code_types, image)

# zbarlight.scan_codes() returns None instead of []
if not codes:
return []

return [
ZBarCam.Symbol(type=None, data=code)
for code in codes
]


available_implementations = {
'pyzbar': PyZBarDecoder,
'zbarlight': ZBarLightDecoder,
}


for name, implementation in available_implementations.items():
if implementation.is_usable():
zbar_decoder = implementation()
Logger.info('ZBarCam: Using implementation %s', name)
break
else:
raise ImportError(
'No zbar implementation available '
f'(tried {", ".join(available_implementations.keys())})'
)


class ZBarCam(AnchorLayout):
"""
Widget that use the Camera and zbar to detect qrcode.
Expand All @@ -21,15 +118,19 @@ class ZBarCam(AnchorLayout):
resolution = ListProperty([640, 480])

symbols = ListProperty([])
Symbol = namedtuple('Symbol', ['type', 'data'])
Symbol = collections.namedtuple('Symbol', ['type', 'data'])
# checking all possible types by default
code_types = ListProperty(set(pyzbar.ZBarSymbol))
code_types = ListProperty(zbar_decoder.get_available_code_types())

def __init__(self, **kwargs):
# lazy loading the kv file rather than loading at module level,
# that way the `XCamera` import doesn't happen too early
Builder.load_file(os.path.join(MODULE_DIRECTORY, "zbarcam.kv"))
super().__init__(**kwargs)

self._decoding_frame = threading.Event()
self._symbols_queue = queue.Queue()

Clock.schedule_once(lambda dt: self._setup())

def _setup(self):
Expand All @@ -49,6 +150,7 @@ def _on_camera_ready(self, xcamera):
Starts binding when the `xcamera._camera` instance is ready.
"""
xcamera._camera.bind(on_texture=self._on_texture)
Clock.schedule_interval(self._update_symbols, 0)

def _remove_shoot_button(self):
"""
Expand All @@ -59,13 +161,32 @@ def _remove_shoot_button(self):
shoot_button = xcamera.children[0]
xcamera.remove_widget(shoot_button)

def _on_texture(self, instance):
self.symbols = self._detect_qrcode_frame(
texture=instance.texture, code_types=self.code_types)
def _on_texture(self, xcamera):
if not self._decoding_frame.is_set():
self._decoding_frame.set()
threading.Thread(
target=self._threaded_detect_qrcode_frame,
args=(
xcamera.texture,
xcamera.texture.pixels,
self.code_types,
),
).start()

@classmethod
def _detect_qrcode_frame(cls, texture, code_types):
image_data = texture.pixels
def _threaded_detect_qrcode_frame(self, texture, pixels, code_types):
self._symbols_queue.put(
self._detect_qrcode_frame(texture, code_types, pixels)
)
self._decoding_frame.clear()

def _update_symbols(self, *args):
try:
self.symbols = self._symbols_queue.get_nowait()
except queue.Empty:
return

def _detect_qrcode_frame(self, texture, code_types, pixels=None):
image_data = pixels or texture.pixels # Use pixels kwarg for threading
size = texture.size
# Fix for mode mismatch between texture.colorfmt and data returned by
# texture.pixels. texture.pixels always returns RGBA, so that should
Expand All @@ -74,12 +195,7 @@ def _detect_qrcode_frame(cls, texture, code_types):
pil_image = PIL.Image.frombytes(mode='RGBA', size=size,
data=image_data)
pil_image = fix_android_image(pil_image)
symbols = []
codes = pyzbar.decode(pil_image, symbols=code_types)
for code in codes:
symbol = ZBarCam.Symbol(type=code.type, data=code.data)
symbols.append(symbol)
return symbols
return zbar_decoder.decode(pil_image, code_types)

@property
def xcamera(self):
Expand Down
1 change: 1 addition & 0 deletions tests/kivy_garden/zbarcam/test_zbarcam.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from kivy.base import EventLoop
from kivy.core.image import Image

from kivy_garden.zbarcam import ZBarCam

FIXTURE_DIR = os.path.join(
Expand Down