From 6f41196763a97f5f5a64f9f78f2cb29bf3d26cda Mon Sep 17 00:00:00 2001 From: James King Date: Mon, 23 Nov 2015 14:30:01 -0700 Subject: [PATCH] Now with support for Python 3+ --- .gitignore | 3 + src/bindings/python/openalpr/__init__.py | 7 +- src/bindings/python/openalpr/openalpr.py | 127 +++++++++++++++++++++-- 3 files changed, 126 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 85221c7..1e3f1c8 100644 --- a/.gitignore +++ b/.gitignore @@ -42,5 +42,8 @@ src.sln Debug/ Release/ +# JetBrains IDEA / PyCharm +.idea/ + cmake_install.cmake diff --git a/src/bindings/python/openalpr/__init__.py b/src/bindings/python/openalpr/__init__.py index 2871d69..8263252 100644 --- a/src/bindings/python/openalpr/__init__.py +++ b/src/bindings/python/openalpr/__init__.py @@ -1 +1,6 @@ -from openalpr import Alpr +import sys as _sys + +if _sys.version_info.major >= 3: + from .openalpr import Alpr +else: + from openalpr import Alpr \ No newline at end of file diff --git a/src/bindings/python/openalpr/openalpr.py b/src/bindings/python/openalpr/openalpr.py index a22969d..cd0162a 100644 --- a/src/bindings/python/openalpr/openalpr.py +++ b/src/bindings/python/openalpr/openalpr.py @@ -2,17 +2,60 @@ import ctypes import json import platform +# We need to do things slightly differently for Python 2 vs. 3 +# ... because the way str/unicode have changed to bytes/str +if platform.python_version_tuple()[0] == '2': + # Using Python 2 + bytes = str + _PYTHON_3 = False +else: + # Assume using Python 3+ + unicode = str + _PYTHON_3 = True + +def _convert_to_charp(string): + # Prepares function input for use in c-functions as char* + if type(string) == unicode: + return string.encode("UTF-8") + elif type(string) == bytes: + return string + else: + raise TypeError("Expected unicode string values or ascii/bytes values. Got: %r" % type(string)) + +def _convert_from_charp(charp): + # Prepares char* output from c-functions into Python strings + if _PYTHON_3 and type(charp) == bytes: + return charp.decode("UTF-8") + else: + return charp + class Alpr(): def __init__(self, country, config_file, runtime_dir): + """ + Initializes an OpenALPR instance in memory. + :param country: The default region for license plates. E.g., "us" or "eu" + :param config_file: The path to the OpenALPR config file + :param runtime_dir: The path to the OpenALPR runtime data directory + :return: An OpenALPR instance + """ + country = _convert_to_charp(country) + config_file = _convert_to_charp(config_file) + runtime_dir = _convert_to_charp(runtime_dir) + try: # Load the .dll for Windows and the .so for Unix-based - if platform.system().lower().find("windows") != -1: - self._openalprpy_lib = ctypes.cdll.LoadLibrary("openalprpy.dll") - elif platform.system().lower().find("darwin") != -1: - self._openalprpy_lib = ctypes.cdll.LoadLibrary("libopenalprpy.dylib") - else: - self._openalprpy_lib = ctypes.cdll.LoadLibrary("libopenalprpy.so") - + if platform.system().lower().find("windows") != -1: + self._openalprpy_lib = ctypes.cdll.LoadLibrary("openalprpy.dll") + elif platform.system().lower().find("darwin") != -1: + self._openalprpy_lib = ctypes.cdll.LoadLibrary("libopenalprpy.dylib") + else: + self._openalprpy_lib = ctypes.cdll.LoadLibrary("libopenalprpy.so") + except OSError as e: + nex = OSError("Unable to locate the OpenALPR library. Please make sure that OpenALPR is properly " + "installed on your system and that the libraries are in the appropriate paths.") + if _PYTHON_3: + nex.__cause__ = e; + raise nex self._initialize_func = self._openalprpy_lib.initialize self._initialize_func.restype = ctypes.c_void_p @@ -54,44 +97,108 @@ class Alpr(): def unload(self): + """ + Unloads OpenALPR from memory. + + :return: None + """ self._openalprpy_lib.dispose(self.alpr_pointer) def is_loaded(self): + """ + Checks if OpenALPR is loaded. + + :return: A bool representing if OpenALPR is loaded or not + """ return self._is_loaded_func(self.alpr_pointer) def recognize_file(self, file_path): + """ + This causes OpenALPR to attempt to recognize an image by opening a file on + disk. + + :param file_path: The path to the image that will be analyzed + :return: An OpenALPR analysis in the form of a response dictionary + """ + file_path = _convert_to_charp(file_path) ptr = self._recognize_file_func(self.alpr_pointer, file_path) json_data = ctypes.cast(ptr, ctypes.c_char_p).value + json_data = _convert_from_charp(json_data) response_obj = json.loads(json_data) self._free_json_mem_func(ctypes.c_void_p(ptr)) - return response_obj def recognize_array(self, byte_array): + """ + This causes OpenALPR to attempt to recognize an image passed in as a byte array. + :param byte_array: This should be a string (Python 2) or a bytes object (Python 3) + :return: An OpenALPR analysis in the form of a response dictionary + """ + if type(byte_array) != bytes: + raise TypeError("Expected a byte array (string in Python 2, bytes in Python 3)") pb = ctypes.cast(byte_array, ctypes.POINTER(ctypes.c_ubyte)) ptr = self._recognize_array_func(self.alpr_pointer, pb, len(byte_array)) json_data = ctypes.cast(ptr, ctypes.c_char_p).value + json_data = _convert_from_charp(json_data) response_obj = json.loads(json_data) self._free_json_mem_func(ctypes.c_void_p(ptr)) - return response_obj def get_version(self): + """ + This gets the version of OpenALPR + + :return: Version information + """ ptr = self._get_version_func(self.alpr_pointer) version_number = ctypes.cast(ptr, ctypes.c_char_p).value + version_number = _convert_from_charp(version_number) self._free_json_mem_func(ctypes.c_void_p(ptr)) - return version_number def set_top_n(self, topn): + """ + Sets the number of returned results when analyzing an image. For example, + setting topn = 5 returns the top 5 results. + + :param topn: An integer that represents the number of returned results. + :return: None + """ self._set_top_n_func(self.alpr_pointer, topn) def set_default_region(self, region): + """ + This sets the default region for detecting license plates. For example, + setting region to "us" for United States or "eu" for Europe. + + :param region: A unicode/ascii string (Python 2/3) or bytes array (Python 3) + :return: None + """ + region = _convert_to_charp(region) self._set_default_region_func(self.alpr_pointer, region) def set_detect_region(self, enabled): + """ + This allows OpenALPR to attempt to detect the region of a license plate + automatically. By default this is disabled, but you can enable it here. + + :param enabled: A boolean representing whether or not auto-detection is enabled + :return: None + """ self._set_detect_region_func(self.alpr_pointer, enabled) + def __del__(self): + if self.is_loaded(): + self.unload() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.is_loaded(): + self.unload() + +