#
#         Luxand FaceSDK Library
#
#  Copyright(c) 2023 Luxand, Inc.
#         http://www.luxand.com
#
#  Wrapper classes and functions for FaceSDK
#
###########################################

import ctypes, os, sys, inspect, functools
from ctypes import c_int, c_uint, c_ushort, c_float, c_char, c_longlong, c_double, c_ubyte, c_wchar, c_wchar_p, c_bool, c_void_p, c_char_p
from ctypes import POINTER, byref, create_string_buffer
from typing import List, Tuple
from . import const

python2 = sys.version_info.major == 2
windows = sys.platform == 'win32'
linux   = 'linux' in sys.platform
if windows:
	try:
		import win
		from win import HBITMAP
	except:
		from . import win
		from .win import HBITMAP

	create_camera_buffer = ctypes.create_unicode_buffer
	camera_char_p = ctypes.c_wchar_p
	process_str = lambda x: x
else:
	create_camera_buffer = lambda x: ctypes.create_string_buffer(x.encode('utf-8'))
	camera_char_p = ctypes.c_char_p
	process_str = lambda x: x.decode('utf-8')

# supported platforms returned by sys.platform attribute
FSDK_LIB = {
	'win32': 'win32/facesdk.dll',
	'win64': 'win64/facesdk.dll',
	'darwin': 'osx_x86_64/libfsdk.dylib',
	'linux32': 'linux32/libfsdk.so',
	'linux64': 'linux64/libfsdk.so',
	'linux32_arm': 'linux32_arm/libfsdk.so',
	'linux64_arm': 'linux64_arm/libfsdk.so',
}

platform = sys.platform
if platform == 'win32':
	if '64 bit' in sys.version:
		platform = 'win64'
elif 'linux' in platform:
	platform = 'linux64' if sys.maxsize > 2**32 else 'linux32'
	import platform as _platform
	if 'arm' in _platform.machine() or 'aarch' in _platform.machine():
		platform += '_arm'

if platform not in FSDK_LIB:
	raise Exception("Unsupported platform '%s' for FaceSDK.\nMake sure your platform is specified in FSDK_LIB dictionary." % sys.platform)
fsdkDll_file_name = os.path.join(os.path.split(__file__)[0], FSDK_LIB[platform])
if not os.path.isfile(fsdkDll_file_name):
	raise Exception("FaceSDK binary '%s' could not be found." % fsdkDll_file_name)

fsdkDll = ctypes.CDLL(fsdkDll_file_name)

ERROR_NAMES = {v: n for n, v in const.__dict__.items() if n.startswith('FSDKE_')}

# this exception class is available as FSDK.Exception
class FSDK_Exception(Exception):
	def __init__(self, func_name, error_id, desc=''):
		self.func_name, self.error_id = func_name, error_id
		msg = "%s -> %s (%s) %s" % (func_name, ERROR_NAMES.get(error_id, 'UNKNOWN ERROR'), error_id, ': ' + str(desc) if desc else '')
		if error_id == const.FSDKE_NOT_ACTIVATED:
			msg += '\nPlease run the License Key Wizard (Start - Luxand - FaceSDK - License Key Wizard)'
		super(FSDK_Exception, self).__init__(msg)

# exceptions generated by wrapper for particular FaceSDK error codes
class Failed(FSDK_Exception):                    """FSDKE_FAILED = -1"""
class NotActivated(FSDK_Exception):              """FSDKE_NOT_ACTIVATED = -2"""
class OutOfMemory(FSDK_Exception):               """FSDKE_OUT_OF_MEMORY = -3"""
class InvalidArgument(FSDK_Exception):           """FSDKE_INVALID_ARGUMENT = -4"""
class IOError(FSDK_Exception):                   """FSDKE_IO_ERROR = -5"""
class ImageTooSmall(FSDK_Exception):             """FSDKE_IMAGE_TOO_SMALL = -6"""
class FaceNotFound(FSDK_Exception):              """FSDKE_FACE_NOT_FOUND = -7"""
class InsufficientBufferSize(FSDK_Exception):    """FSDKE_INSUFFICIENT_BUFFER_SIZE = -8"""
class UnsupportedImageExtension(FSDK_Exception): """FSDKE_UNSUPPORTED_IMAGE_EXTENSION = -9"""
class CannotOpenFile(FSDK_Exception):            """FSDKE_CANNOT_OPEN_FILE = -10"""
class CannotCreateFile(FSDK_Exception):          """FSDKE_CANNOT_CREATE_FILE = -11"""
class BadFileFormat(FSDK_Exception):             """FSDKE_BAD_FILE_FORMAT = -12"""
class FileNotFound(FSDK_Exception):              """FSDKE_FILE_NOT_FOUND = -13"""
class ConnectionClosed(FSDK_Exception):          """FSDKE_CONNECTION_CLOSED = -14"""
class ConnectionFailed(FSDK_Exception):          """FSDKE_CONNECTION_FAILED = -15"""
class IPInitFailed(FSDK_Exception):              """FSDKE_IP_INIT_FAILED = -16"""
class NeedServerActivation(FSDK_Exception):      """FSDKE_NEED_SERVER_ACTIVATION = -17"""
class IdNotFound(FSDK_Exception):                """FSDKE_ID_NOT_FOUND = -18"""
class AttributeNotDetected(FSDK_Exception):      """FSDKE_ATTRIBUTE_NOT_DETECTED = -19"""
class InsufficientTrackerMemoryLimit(FSDK_Exception): """FSDKE_INSUFFICIENT_TRACKER_MEMORY_LIMIT = -20"""
class UnknownAttribute(FSDK_Exception):          """FSDKE_UNKNOWN_ATTRIBUTE = -21"""
class UnsupportedFileVersion(FSDK_Exception):    """FSDKE_UNSUPPORTED_FILE_VERSION = -22"""
class SyntaxError(FSDK_Exception):               """FSDKE_SYNTAX_ERROR = -23"""
class ParameterNotFound(FSDK_Exception):         """FSDKE_PARAMETER_NOT_FOUND = -24"""
class InvalidTemplate(FSDK_Exception):           """FSDKE_INVALID_TEMPLATE = -25"""
class UnsupportedTemplateVersion(FSDK_Exception):"""FSDKE_UNSUPPORTED_TEMPLATE_VERSION = -26"""
class CameraIndexDoesNotExist(FSDK_Exception):   """FSDKE_CAMERA_INDEX_DOES_NOT_EXIST = -27"""
class PlatformNotLicensed(FSDK_Exception):       """FSDKE_PLATFORM_NOT_LICENSED = -28"""
class TensorflowNotInitialized(FSDK_Exception):  """FSDKE_TENSORFLOW_NOT_INITIALIZED = -29"""
class PluginNotLoaded(FSDK_Exception):           """FSDKE_PLUGIN_NOT_LOADED = -30"""
class PluginNoPermission(FSDK_Exception):        """FSDKE_PLUGIN_NO_PERMISSION = -31"""
class FaceIdNotFound(FSDK_Exception):            """FSDKE_FACEID_NOT_FOUND = -32"""
class FaceImageNotFound(FSDK_Exception):         """FSDKE_FACEIMAGE_NOT_FOUND = -33"""

def value_to_str(val):
	val = str(val)
	return val.lower() if val in ('True', 'False') else val

def cstr(s):
	return create_string_buffer(s.encode('utf-8'))

# types used in FaceSDK

class Point(ctypes.Structure):
	_fields_ = ("x", c_int), ("y", c_int)
	__repr__ = __str__ = lambda p: 'Point(x=%i, y=%i)' % (p.x, p.y)

class Eyes(Point*2):
	""" An array of two 2D coordinates for eyes """
	if python2:
		_type_ = Point*2
		_length_ = ctypes.sizeof(_type_)
	__repr__ = __str__ = lambda p: 'Eyes(%s, %s)' % (p[0], p[1])

class FacePosition(ctypes.Structure):
	""" The structure to hold face position
		'xc' - X coordinate of face square center
		'yc' - Y coordinate of face square center
		'w' - the width of face square
		'angle' - the rotation angle of face in degrees
	"""
	_fields_ = ("xc", c_int), ("yc", c_int), ("w", c_int), ("_padding", c_int), ("angle", c_double)
	__repr__ = __str__ = lambda fp: 'FacePosition(xc=%i, yc=%i, w=%i, angle=%.1f)' % (fp.xc, fp.yc, fp.w, fp.angle)
	@property
	def rect(self):
		x, y, w = self.xc, self.yc, self.w//2
		return x-w, y-w, x+w, y+w

class ImageData(POINTER(c_ubyte)):
	""" Structure is used to store the pointer to image raw data.
		To access image data use [] operator. Slices are supported.
		Additional attributes in the structure:
			'width' - width of image in pixels
			'height' - height of image in pixels
			'scanLine' - the distance between adjacent rows in bytes
			'colorMode' - one of the constant values:
					FSDK.IMAGE_GRAYSCALE_8BIT - grayscale image
					FSDK.IMAGE_COLOR_24BIT - 24-bit color image (R, G, B order)
					FSDK.IMAGE_COLOR_32BIT - 32-bit color image with alpha channel (R, G, B, A order)
	"""
	_type_ = c_ubyte
	def __init__(self):
		super(ImageData, self).__init__()
		self.width = self.height = self.scanLine = self.colorMode = 0
	__repr__ = __str__ = lambda imd: 'ImageData(width=%i, height=%i, scanLine=%i, colorMode=%i)' % \
		(imd.width, imd.height, imd.scanLine, imd.colorMode)

class IDSimilarity(ctypes.Structure):
	_fields_ = ("ID", c_longlong), ("similarity", c_float)
	""" The structure stores the similarity of matching the person by ID
		'ID' - the person ID stored in the tracker
		'similarity' - the similarity of matching in the range [0..1]
	"""
	__repr__ = __str__ = lambda o: 'IDSimilarity(ID=%i, similarity=%.5f)' % (o.ID, o.similarity)


Features = Point*const.FSDK_FACIAL_FEATURE_COUNT
ConfidenceLevels = c_float*const.FSDK_FACIAL_FEATURE_COUNT
FaceTemplate = c_char*const.FSDK_FACE_TEMPLATE_SIZE
# the following code is needed for python2 compatibility
FaceTemplate.MatchFaces = FaceTemplate.Match = lambda self, face_template: FSDK.MatchFaces(self, face_template)
FaceTemplate.__name__ = 'FaceTemplate'
Features.__name__ = 'Features'


class Camera(ctypes.Structure):
	_fields_ = ("handle", c_int),  # FSDK camera handle

	def __init__(self, cameraName=None):  # cameraName is for windows only
		if cameraName is None:
			super(Camera, self).__init__(-1)
		elif isinstance(cameraName, str):
			cam = FSDK.OpenVideoCamera(cameraName)
			super(Camera, self).__init__(cam.handle)
			self.name, cam.handle = cameraName, -1
			if hasattr(cameraName, 'device_path'):
				self.device_path = cameraName.device_path
		else:
			raise InvalidArgument('Camera construction', const.FSDKE_INVALID_ARGUMENT, desc=str(cameraName))

	Close = __del__ = lambda self: FSDK.CloseVideoCamera(self)  # destructor

	if windows or linux:
		def Open(self, cameraName):
			self.Close()
			cam = FSDK.OpenVideoCamera(cameraName)
			self.cam, cam.handle = cam.handle, -1

	def GrabFrame(self): return FSDK.GrabFrame(self)


if windows or linux: # windows specific classes
	class VideoFormatInfo(ctypes.Structure):
		_fields_ = ("Width", c_int), ("Height", c_int), ("FormatIndex", c_int)
		__repr__ = __str__ = lambda x: 'VideoFormatInfo(Width=%i, Height=%i, FormatIndex=%i)' % (x.Width, x.Height, x.FormatIndex)


class Image(ctypes.Structure):
	""" A wrapper for FaceSDK HImage handle """
	_fields_ = ("handle", c_int),  # FSDK image handle (-1 means invalid or uninitialized handle)

	def __new__(cls, arg=None):
		if arg is None:
			return FSDK.CreateEmptyImage()
		if type(arg) is int:
			img = super(Image, cls).__new__(cls)
			img.handle = -1
			return img
		if type(arg) is str:
			return FSDK.LoadImageFromFile(arg)
		if windows and type(arg) is HBITMAP:
			return FSDK.LoadImageFromHBitmap(arg)
		raise InvalidArgument('Image construction', const.FSDKE_INVALID_ARGUMENT, desc=str(arg))

	def __init__(self, arg=None):
		super(Image, self).__init__(self.handle)

	def __del__(self):
		FSDK.FreeImage(self)
	Free = __del__

	def __repr__(self):
		return 'Image(handle=%x)' % self.handle
	__str__ = __repr__

	# swap FSDK handles of two images
	def swap(self, image):
		self.handle, image.handle = image.handle, self.handle
		return self

	width = property(lambda self: FSDK.GetImageWidth(self))
	height = property(lambda self: FSDK.GetImageHeight(self))
	# returns a tuple (width, height)
	size = property(lambda self: (self.width, self.height))

	@staticmethod
	def FromFile(fileName: str):  # -> Image
		return FSDK.LoadImageFromFile(fileName)

	@staticmethod
	def FromBuffer(buffer: bytes, width: int, height: int, scanLine: int, colorMode: int):  # -> Image
		return FSDK.LoadImageFromBuffer(buffer, width, height, scanLine, colorMode)
	FromBytes = FromBuffer

	def SaveToFile(self, fileName: str, quality: int = None):
		FSDK.SaveImageToFile(self, fileName, quality)

	####  Copy functions

	def Copy(self):  # -> Image
		return FSDK.CopyImage(self, Image())

	def CopyRect(self, x1: int, y1: int, x2: int, y2: int):  # -> Image
		return FSDK.CopyRect(self, x1, y1, x2, y2, Image())
	Crop = CopyRect

	def CopyRectReplicateBorder(self, x1: int, y1: int, x2: int, y2: int):  # -> Image
		return FSDK.CopyRectReplicateBorder(self, x1, y1, x2, y2, Image())
	CropReplicateBorder = CopyRectReplicateBorder

	def Mirror(self, useVerticalMirroringInsteadOfHorizontal: bool = False):  # -> Image
		return FSDK.MirrorImage(self, useVerticalMirroringInsteadOfHorizontal)

	def Resize(self, ratio: float):  # -> Image
		return FSDK.ResizeImage(self, ratio, Image())

	def ResizeXY(self, ratioX: float, ratioY: float):  # -> Image
		return FSDK.ResizeImageXY(self, ratioX, ratioY, Image())

	def Rotate90(self, multiplier: int = 1):  # -> Image
		return FSDK.RotateImage90(self, multiplier, Image())

	def Rotate(self, angle: float):  # -> Image
		return FSDK.RotateImage(self, angle, Image())

	def RotateCenter(self, angle: float, xc: float, yc: float):  # -> Image
		return FSDK.RotateImageCenter(self, angle, xc, yc, Image())

	#### Face detection functions

	def DetectEyes(self, facePosition: FacePosition = None) -> Eyes:
		""" If 'facePosition' is None the function detects a frontal face in an image and returns its eye centers as an
			instance of Eyes. The FSDK.FaceNotFound exception is raised If a face is not found.
			If 'facePosition' is defined the function detects and returns eye centers in an image region returned by
			FSDK.DetectFace or FSDK.DetectMultipleFaces.
		"""
		return FSDK.DetectEyes(self) if facePosition is None else FSDK.DetectEyesInRegion(self, facePosition)

	def DetectFace(self) -> FacePosition:
		return FSDK.DetectFace(self)

	def DetectMultipleFaces(self) -> List[FacePosition]:
		return FSDK.DetectMultipleFaces(self)

	def DetectFacialFeatures(self, facePosition: FacePosition = None, *, confidenceLevel = False) -> Features:
		""" If 'facePosition' is None the function detects a frontal face in an image and returns its facial features.
			If 'facePosition' is defined the function detects facial features of a specific face in an image returned
			by FSDK.DetectFace or FSDK.DetectMultipleFaces.
			If 'facePosition' is None and a face is not found, the FSDK.FaceNotFound exception is raised.
			If 'confidenceLevels' is True the returned 'Features' objects contains the 'confidenceLevels' attribute
			that holds confidence levels of each facial point.
		"""
		if facePosition is None:
			return FSDK.DetectFacialFeaturesEx(self) if confidenceLevel else FSDK.DetectFacialFeatures(self)
		else:
			return FSDK.DetectFacialFeaturesInRegionEx(self, facePosition) if confidenceLevel else FSDK.DetectFacialFeaturesInRegion(self, facePosition)

	def GetFaceTemplate(self, facePosition: FacePosition = None) -> FaceTemplate:
		""" Calls FSDK.GetFaceTemplate if facePosition is None.
			Calls FSDK.GetFaceTemplateInRegion if facePosition is defined.
		"""
		return FSDK.GetFaceTemplate(self) if facePosition is None else FSDK.GetFaceTemplateInRegion(self, facePosition)

	def DetectFacialAttributeUsingFeatures(self, facialFeatures: Features, attributeName: str, ret_dict: bool = False) -> str:
		return FSDK.DetectFacialAttributeUsingFeatures(self, facialFeatures, attributeName, ret_dict)

	# --- special functions to work with image data

	def ImageData(self) -> ImageData:
		return FSDK.GetImageData(self)

	if windows:
		def GetHBitmap(self) -> HBITMAP:
			return FSDK.SaveImageToHBitmap(self)

	def ToBuffer(self, colorMode: int) -> bytes:
		buffer = bytes(FSDK.GetImageBufferSize(self, colorMode))
		FSDK._SaveImageToBuffer(self, buffer, colorMode)
		return buffer
	ToBytes = ToBuffer


class Tracker(ctypes.Structure):
	_fields_ = ("handle", c_int),  # FSDK tracker handle (-1 means invalid or uninitialized handle)

	def __new__(cls, arg=None):
		if arg is None:
			return FSDK.CreateTracker()
		if type(arg) is int: 
			tracker = super(Tracker, cls).__new__(cls)
			tracker.handle = -1
			return tracker
		raise InvalidArgument('Tracker construction', const.FSDKE_INVALID_ARGUMENT, desc=str(arg))

	def __del__(self):
		FSDK.FreeTracker(self)
	Free = __del__

	__repr__ = __str__ = lambda x: 'Tracker(handle=%x)' % x.handle

	def Clear(self):
		FSDK.ClearTracker(self)
	
	def SetParameters(self, **kw):		
		if kw:
			FSDK.SetTrackerMultipleParameters(self, ';'.join('%s=%s' % (n, value_to_str(v)) for n, v in kw.items()))

	def SetParameter(self, paramName: str, paramValue):
		FSDK.SetTrackerParameter(self, paramName, value_to_str(paramValue))

	def SetMultipleParameters(self, params: str):
		FSDK.SetTrackerMultipleParameters(self, params)

	def GetParameter(self, parameterName: str)-> str:
		return FSDK.GetTrackerParameter(self, parameterName)

	def FeedFrame(self, cameraIdx: int, img: Image, maxIDs: int = 512) -> List[int]:
		return FSDK.FeedFrame(self, cameraIdx, img, maxIDs=maxIDs)

	def GetFacePosition(self, cameraIdx, ID):
		return FSDK.GetTrackerFacePosition(self, cameraIdx, ID)

	def GetFacialFeatures(self, cameraIdx, ID):
		return FSDK.GetTrackerFacialFeatures(self, cameraIdx, ID)		

	def GetEyes(self, cameraIdx, ID):
		return FSDK.GetTrackerEyes(self, cameraIdx, ID)

	def GetFacialAttribute(self, cameraIdx, ID, attributeName):
		return FSDK.GetTrackerFacialAttribute(self, cameraIdx, ID, attributeName)
	GetTrackerFacialAttribute = GetFacialAttribute  # for compatibility with previous versions

	def GetIDsCount(self) -> int:
		return FSDK.GetTrackerIDsCount(self)

	def GetAllIDs(self) -> List[int]:
		return FSDK.GetTrackerAllIDs(self)

	def GetFaceIDsForID(self, ID) -> List[int]:
		return FSDK.GetTrackerFaceIDsForID(self, ID)

	def GetIDByFaceID(self, FaceID) -> int:
		return FSDK.GetTrackerIDByFaceID(self, FaceID)

	def GetFaceTemplate(self, FaceID):
		return FSDK.GetTrackerFaceTemplate(self, FaceID)

	def AddFaceTemplate(self, ID, faceTemplate):
		return FSDK.AddTrackerFaceTemplate(self, ID, faceTemplate)

	def DeleteFace(self, FaceID):
		return FSDK.DeleteTrackerFace(self, FaceID)

	def GetFaceImage(self, FaceID):
		return FSDK.GetTrackerFaceImage(self, FaceID)

	def SetFaceImage(self, FaceID, image):
		return FSDK.SetTrackerFaceImage(self, FaceID, image)

	def DeleteFaceImage(self, FaceID):
		return FSDK.DeleteTrackerFaceImage(self, FaceID)

	def TrackerMatchFaces(self, faceTemplate, threshold, maxCount = 5):
		return FSDK.TrackerMatchFaces(self, faceTemplate, threshold, maxCount)

	def LockID(self, ID):
		FSDK.LockID(self, ID)

	def UnlockID(self, ID):
		FSDK.UnlockID(self, ID)

	def PurgeID(self, ID):
		FSDK.PurgeID(self, ID)

	def SetName(self, ID, name):
		FSDK.SetName(self, ID, name)

	def GetName(self, ID):
		return FSDK.GetName(self, ID)

	def GetAllNames(self, ID):
		return FSDK.GetAllNames(self, ID)

	def GetIDReassignment(self, ID):
		return FSDK.GetIDReassignment(self, ID)

	def GetSimilarIDCount(self, ID: int) -> int:
		return FSDK.GetSimilarIDCount(self, ID)

	def GetSimilarIDList(self, ID):
		return FSDK.GetSimilarIDList(self, ID)

	def SaveToFile(self, fileName: str):
		FSDK.SaveTrackerMemoryToFile(self, fileName)

	@staticmethod
	def FromFile(fileName: str):
		return FSDK.LoadTrackerMemoryFromFile(fileName)

	def GetMemory(self) -> bytes:
		""" Creates and returns a buffer of the tracker memory.
			Note that tracker parameters, along with its face tracking state, are not saved to buffer.
		"""
		buffer = bytes(FSDK.GetTrackerMemoryBufferSize(self))
		FSDK._SaveTrackerMemoryToBuffer(self, buffer)
		return buffer
	ToBytes = GetMemory

	@staticmethod
	def FromMemory(buffer: bytes):  # -> Tracker
		return FSDK.LoadTrackerMemoryFromBuffer(buffer)
	FromBytes = FromMemory

# base class for FSDK wrapper functions
class FSDK_Wrapper:
	# generate a dictionary that associates all error codes with their FSDK exceptions
	def get_all_fsdk_exceptions():
		import inspect
		ex = {name.upper(): cls for name, cls in sys.modules[__name__].__dict__.items()
				if inspect.isclass(cls) and issubclass(cls, FSDK_Exception) and cls is not FSDK_Exception
		}
		for id, name in ERROR_NAMES.items():
			if id:
				errname = name[6:].replace("_", "")
				if errname in ex:
					yield id, ex[errname]
				else:
					raise Exception("Failed to locate FSDK exception for error %s" % name)
				del ex[errname]
		if ex:
			raise Exception("Found exception class(es) without error code:\n%s" % '\n'.join('class ' + cls.__name__ for cls in ex.values()))
	FSDKErrors = dict(get_all_fsdk_exceptions())
	del get_all_fsdk_exceptions

	@staticmethod
	def disable_type_checking(self): pass
	@staticmethod
	def enable_type_checking(self): pass

	@staticmethod
	def prepare(dct):
		def get_key_wrapper(key, func):
			try:
				fsdkFunc = getattr(fsdkDll, key)
			except AttributeError as ex:
				if not hasattr(func, "__FSDK_ver__"):
					raise ex
				def missed_function(message):
					@functools.wraps(func)
					def error(*v, **kw):
						raise Exception(message)
					return error
				fsdkFunc = missed_function("The function '%s' requires FaceSDK version %s or later" % (key, func.__FSDK_ver__))

			comments = inspect.getcomments(func)
			if comments and comments.startswith("# class method:"):
				cls_name, func_name = comments.split()[3].split('.')
				func_name = func_name.split('(')[0]
				ref_func = getattr(getattr(sys.modules[__name__], cls_name), func_name)
				if not ref_func.__doc__:
					ref_func.__doc__ = func.__doc__

			@functools.wraps(func)
			def fsdk_wrapper(self, *arg, **kw):
				def fsdk_caller(*arg, **kw):
					self._lastErrorID = res = fsdkFunc(*arg)
					if res:
						err_desc = kw.get('err_desc', '') if kw else ''
						if inspect.isfunction(err_desc):
							err_desc = err_desc()
						self._lastError = FSDK_Wrapper.FSDKErrors.get(res, FSDK_Exception)(key, res, err_desc)
						if kw and kw.get('skip') in {res, all}:
							return None
						raise self._lastError
					self._lastError = None
					return True
				return func(fsdk_caller, *arg, **kw)
			return fsdk_wrapper

		# generate callable FSDK functions
		items = tuple((k,v) for k, v in dct.items() if inspect.isfunction(v) and not k.startswith('__'))
		for name, func in items:
			params = tuple(inspect.signature(func).parameters.keys())
			if params and params[0] == 'f':
				prefix = 'FSDK' if name[0] == '_' else 'FSDK_'
				dct[name] = get_key_wrapper(prefix + name, func)

		# copy constants from const.py module to this class
		dct.update((item, getattr(const, item)) for item in dir(const) if not item.startswith('__'))
		# copy constants from const.py module to this class with removed FSDK_, FSDKE_ and FSDKP_ prefixes
		dct.update((item[item.find('_')+1:], getattr(const, item)) for item in dir(const) if item.startswith('FSDK'))

		# copy all FSDK exception classes to the FSDK object
		for ex in FSDK_Wrapper.FSDKErrors.values():
			dct[ex.__name__] = ex

	Exception = FSDK_Exception
	Image, ImageData, Tracker, Camera = Image, ImageData, Tracker, Camera
	Point, FacePosition, Eyes = Point, FacePosition, Eyes
	FaceTemplate, Features = FaceTemplate, Features

	if windows:
		HBITMAP = win.HBITMAP
	if windows or linux:
		VideoFormatInfo = VideoFormatInfo

# decorator for new functions of new versions of FaceSDK
def FSDK_ver(ver):
	def wrapper(f):
		@functools.wraps(f)
		def caller(*args, **kw): return f(*args, **kw)
		setattr(caller, "__FSDK_ver__", str(ver))
		return caller
	return wrapper


# class for definition of FaceSDK wrapper functions
# noinspection PyCallingNonCallable
class FSDKLib(FSDK_Wrapper):
	_lastErrorID = const.FSDKE_OK
	_lastError = None  # the last FSDK_Exception object
	_char_buffer = (c_char*4096)()  # internal buffer used to exchange data with external __cdecl functions

	@classmethod
	def _receive_string(cls, func):
		while 1:
			try: 
				func(cls._char_buffer)
				return cls._char_buffer.value.decode('utf-8')
			except InsufficientBufferSize: 
				cls._char_buffer = (c_char*(2*len(cls._char_buffer)))()  # double the buffer size


	# ---  Initialization functions  --- #

	def ActivateLibrary(f, license_key: str):
		""" Activates the FaceSDK library.
			'license_key' is a string with activation key you received from Luxand, Inc.
			Raises FSDK.NotActivated exception if key is invalid or expired.
			This exception is also raised from all FSDK functions if library is not activated. """
		try:
			import sys
			FSDK.SetParameter("environment", f"python")
		except NotActivated:
			pass
		f(cstr(license_key))

	def GetHardware_ID(f) -> str:
		""" Generates and returns a Hardware ID code """
		return FSDKLib._receive_string(lambda buf: f(buf))

	def GetLicenseInfo(f) -> str:
		"""	Returns license information """
		return FSDKLib._receive_string(lambda buf: f(buf))

	@FSDK_ver("8.3")
	def GetVersionInfo(f) -> str:
		"""	Returns version information """
		buf = c_char_p()
		f(byref(buf))
		return buf.value.decode("utf-8")

	def GetNumThreads(f) -> int:
		""" Retrieves the number of processor cores used by FaceSDK. """
		num = c_int()
		f(byref(num))
		return num.value

	def SetNumThreads(f, num: int):
		""" Sets the number of processor cores to be used by FaceSDK.
			If you set the number of cores to 1, support for multiple cores will be disabled,
			and the SDK will use only a single processor core.
			'num' is the number of threads to be used by FaceSDK. The value is limited by actual number of CPU cores. """
		f(c_int(num))

	def Initialize(f, dataFilesPath=''):
		""" Initializes the FaceSDK library. Should be called before using of any face detection functions.
			'dataFilesPath' is a string specifying the path where facesdk.dll is stored.
			An empty string means the current directory. (Note: the parameter is not used since FaceSDK 1.8)
		"""
		f(cstr(dataFilesPath))

	def Finalize(f):
		""" Finalizes the FaceSDK library. Should be called when the application is exited. """
		f()

	# class method: Image.DetectFace
	def DetectFace(f, image: Image) -> FacePosition:
		""" Detects a frontal face in an image and returns the face position.
			If a face is not found, the FSDK.FaceNotFound exception is raised.
		"""
		face_pos = FacePosition()
		f(image, byref(face_pos))
		return face_pos

	# class method: Image.DetectMultipleFaces
	def DetectMultipleFaces(f, image: Image) -> List[FacePosition]:
		""" Detects multiple faces in an image and returns a list of face positions.
			Equivalent to: Image.DetectMultipleFaces()
		"""
		max_faces, cnt = (512, c_int())
		while 1:
			buf = (FacePosition*max_faces)()
			if not f(image, byref(cnt), buf, ctypes.sizeof(buf), skip=const.FSDKE_FACE_NOT_FOUND):
				return ()
			if cnt.value < max_faces:
				return buf[:cnt.value]
			max_faces *= 2

	# class method: Image.DetectFacialFeatures
	def DetectFacialFeatures(f, image: Image) -> Features:
		""" Detects a frontal face in an image and returns its facial features.
			If a face is not found, the FSDK.FaceNotFound exception is raised.
		"""
		ff = Features()
		f(image, byref(ff))
		return ff

	# class method: Image.DetectFacialFeatures
	def DetectFacialFeaturesInRegion(f, image: Image, facePosition: FacePosition) -> Features:
		""" Detects facial features of a specific face in an image returned by FSDK.DetectFace or FSDK.DetectMultipleFaces.
		"""
		ff = Features()
		f(image, byref(facePosition), byref(ff))
		return ff

	def DetectFacialFeaturesEx(f, image: Image) -> Features:
		""" Equivalent to FSDK.DetectFacialFeatures(image, confidenceLevels=True). """
		ff = Features()
		ff.confidenceLevels = ConfidenceLevels()
		f(image, byref(ff), byref(ff.confidenceLevels))
		return ff

	def DetectFacialFeaturesInRegionEx(f, image: Image, facePosition: FacePosition) -> Features:
		""" Equivalent to FSDK.DetectFacialFeaturesInRegion(image, facePosition, confidenceLevels=True). """
		ff = Features()
		ff.confidenceLevels = ConfidenceLevels()
		f(image, byref(facePosition), byref(ff), byref(ff.confidenceLevels))
		return ff

	# class method: Image.DetectEyes
	def DetectEyes(f, image: Image) -> Eyes:
		""" Detects a frontal face in an image and returns its eye centers as an instance of Eyes.
			If a face is not found, the FSDK.FaceNotFound exception is raised.
		"""
		eyes = Eyes()
		f(image, byref(eyes))
		return eyes

	# class method: Image.DetectEyes
	def DetectEyesInRegion(f, image: Image, facePosition: FacePosition) -> Eyes:
		""" Detects and returns eye centers in an image region returned by FSDK.DetectFace
			or FSDK.DetectMultipleFaces.
		"""
		eyes = Eyes()
		f(image, byref(facePosition), byref(eyes))
		return eyes

	def SetFaceDetectionParameters(f, handleArbitraryRotations: bool, determineFaceRotationAngle: bool, internalResizeWidth: int):
		""" Sets a number of face detection parameters to control the performance and reliability of face detector.
			'handleArbitraryRotations' extends default in-plane face rotation angle from -15..15 degrees to -30..30 degrees.
			'determineFaceRotationAngle' enables or disables the detection of in-plane face rotation angle.
			'internalResizeWidth' controls the detection speed by setting the size of the image the detection
			functions will work with. Choose higher value to increase detection quality, or lower value to improve
			the performance. The value should be in the range from 64 to 4096.
		"""
		f(c_bool(handleArbitraryRotations), c_bool(determineFaceRotationAngle), c_int(internalResizeWidth))

	def SetFaceDetectionThreshold(f, threshold: int):
		""" Sets a threshold value for face detection. The default value is 5. The lowest possible value is 1.
			The function allows adjusting the sensitivity of the detection. If the threshold value is set to a higher
			value, the detector will only recognize faces with sharp, clearly defined details, thus reducing the number
			of false positive detections. Setting the threshold lower allows detecting more faces with less clearly
			defined features at the expense of increased number of false positives.
		"""
		f(c_int(threshold))

	def GetDetectedFaceConfidence(f):
		i = c_int()
		f(byref(i))
		return i.value

	# ---  Image manipulation functions (also available in Image class)  --- #

	def CreateEmptyImage(f) -> Image:
		""" The factory of FaceSDK Image objects. You don't need to call this function directly if Image class is used. """
		img = Image(-1)
		f(byref(img))
		return img

	# class method: Image.FromFile(fileName: str)
	def LoadImageFromFile(self, fileName: str) -> Image:
		""" Loads the image from a file and returns Image object.
			'fileName' is the name of image file to be loaded. FaceSDK supports the JPG, PNG and BMP file formats.
			Equivalent to Image(fileName) or Image.FromFile(fileName)
		"""
		fsdk_func = self._LoadImageFromFileW if windows else self._LoadImageFromFile
		return fsdk_func(fileName)

	# class method: Image.SaveToFile(fileName: str, quality: int = None)
	def SaveImageToFile(self, image: Image, fileName: str, quality: int = None):
		""" Saves an image to a file. When saving to .jpg files, you can set the quality of JPEG compression using
			the FSDK.SetJpegCompressionQuality function or optional 'quality' parameter (the value from 0 to 100).
			Equivalent to image.SaveToFile(fileName, quality)
		"""
		fsdk_func = self._SaveImageToFileW if windows else self._SaveImageToFile
		fsdk_func(image, fileName, quality)

	if windows:  # specific functions for Windows
		def LoadImageFromHBitmap(f, bitmapHandle: HBITMAP) -> Image:
			""" Creates and returns the image from an HBITMAP object.
				Equivalent to Image(bitmapHandle)
			"""
			img = Image(-1)
			f(byref(img), bitmapHandle)
			return img

		# class method: Image.GetHBitmap() -> HBITMAP
		def SaveImageToHBitmap(f, image: Image) -> HBITMAP:
			""" Creates and returns the HBITMAP object from an image. """
			bitmap_handle = HBITMAP()
			f(image, byref(bitmap_handle))
			return bitmap_handle

		# Use FSDK.LoadImageFromFile function
		def _LoadImageFromFileW(f, fileName):
			img = Image(-1)
			f(byref(img), ctypes.create_unicode_buffer(fileName), err_desc=fileName)
			return img

		# Use FSDK.SaveImageToFile function
		def _SaveImageToFileW(f, image, fileName, quality=None):
			if quality is not None:
				FSDK.SetJpegCompressionQuality(quality)
			f(image, ctypes.create_unicode_buffer(fileName), err_desc=fileName)

	else: # not windows

		# Use FSDK.LoadImageFromFile function
		def _LoadImageFromFile(f, filename): 
			img = Image(-1); 
			f(byref(img), create_string_buffer(filename.encode('utf-8')))
			return img

		# Use FSDK.SaveImageToFile function
		def _SaveImageToFile(f, image, filename, quality=None): 
			if quality is not None:
				FSDK.SetJpegCompressionQuality(quality)
			f(image, create_string_buffer(filename.encode('utf-8')))

	# class method: Image.FromBuffer(buffer: bytes, width: int, height: int, scanLine: int, imageMode: int)
	def LoadImageFromBuffer(f, buffer: bytes, width: int, height: int, scanLine: int, imageMode: int) -> Image:
		""" Loads an image from a buffer. The function suggests that the image data is organized in a top-to-bottom order,
			and the distance between adjacent rows is 'scanLine' bytes (for example, in the 24-bit image, the 'scanLine'
			value might be 3*'width' bytes if there is no spacing between adjacent rows). The following image modes are
			supported:
				FSDK.IMAGE_GRAYSCALE_8BIT - grayscale image
				FSDK.IMAGE_COLOR_24BIT - 24-bit color image (R, G, B order)
				FSDK.IMAGE_COLOR_32BIT - 32-bit color image with alpha channel (R, G, B, A order)
		"""
		img = Image(-1)
		f(byref(img), buffer, c_int(width), c_int(height), c_int(scanLine), c_int(imageMode))
		return img

	def LoadImageFromJpegBuffer(f, buffer: bytes, bufferLength: int = None) -> Image:
		""" Loads an image from a buffer containing JPEG data.
			If 'bufferLength' is not defined, an entire 'buffer' data is used.
		"""
		img = Image(-1)
		f(byref(img), buffer, c_uint(bufferLength or len(buffer)))
		return img

	def LoadImageFromPngBuffer(f, buffer: bytes, bufferLength: int = None) -> Image:
		""" Loads an image from a buffer containing PNG data.
			If 'bufferLength' is not defined, an entire 'buffer' data is used.
		"""
		img = Image(-1)
		f(byref(img), buffer, c_uint(bufferLength or len(buffer)))
		return img

	def FreeImage(f, image: Image):
		""" Remove 'image' object. The function is called implicitly by Image class. """
		if image.handle != -1:
			f(image)
			image.handle = -1

	def GetImageBufferSize(f, image: Image, imageMode: int) -> int:
		""" Returns the size of the buffer required to store 'image'. """
		i = c_int()
		f(image, byref(i), c_int(imageMode))
		return i.value

	# class method: Image.ToBuffer(imageMode) -> bytes
	def _SaveImageToBuffer(f, image: Image, buffer: bytes, imageMode: int):
		""" Saves 'image' to a buffer in the desired image mode.
			Refer to the FSDK.LoadImageFromBuffer function description to read more about image modes.
		"""
		f(image, buffer, c_int(imageMode))

	def SetJpegCompressionQuality(f, quality: int):
		""" Sets the quality of the JPEG compression to use in the FSDK_SaveImageToFile function.
			'quality' is the quality of JPEG compression from 0 to 100.
		"""
		f(c_int(quality))

	# class method: Image.Copy() -> Image
	def CopyImage(f, sourceImage: Image, destImage: Image) -> Image:
		""" Creates and returns a new copy of source image. """
		f(sourceImage, destImage)
		return destImage

	# class method: Image.Resize(ratio: float) -> Image
	def ResizeImage(f, sourceImage: Image, ratio: float, destImage: Image) -> Image:
		""" Changes the size of an image.
			'ratio' is a factor by which the x and y dimensions of the source image are changed. A factor value greater
			than 1 corresponds to increasing the image size.
		"""
		f(sourceImage, c_double(ratio), destImage)
		return destImage

	# class method: Image.ResizeXY(ratioX: float, ratioY: float) -> Image
	def ResizeImageXY(f, sourceImage: Image, ratioX: float, ratioY: float, destImage: Image) -> Image:
		""" Changes the size of an image.
			'ratioX' and 'ratioY' are factors by which the x and y dimensions of the source image are changed.
			A factor value greater than 1 corresponds to increasing the image size in a corresponding dimension.
		"""
		f(sourceImage, c_double(ratioX), c_double(ratioY), destImage)
		return destImage

	# class method: Image.Rotate90(multiplier: int) -> Image
	def RotateImage90(f, sourceImage: Image, multiplier: int, destImage: Image) -> Image:
		""" Rotates the image by 90 or 180 degrees clockwise or counter-clockwise.
			'multiplier' is an integer multiplier of 90 degrees defining the rotation angle. Specify 1 for 90 degrees
			clockwise, 2 for 180 degrees clockwise; specify -1 for 90 degrees counterclockwise.
		"""
		f(sourceImage, c_int(multiplier), destImage)
		return destImage

	# class method: Image.Rotate(angle: float) -> Image
	def RotateImage(f, sourceImage: Image, angle: float, destImage: Image) -> Image:
		""" Rotates an image around its center.
			'angle' is rotation angle in degrees.
		"""
		f(sourceImage, c_double(angle), destImage)
		return destImage

	# class method: Image.RotateCenter(angle: float, xCenter: float, yCenter: float) -> Image
	def RotateImageCenter(f, sourceImage: Image, angle: float, xCenter: float, yCenter: float, destImage: Image) -> Image:
		""" Rotates an image around an arbitrary center.
			'angle' - rotation angle in degrees.
			'xCenter' - the X coordinate of the rotation center.
			'yCenter' - the Y coordinate of the rotation center.
		"""
		f(sourceImage, c_double(angle), c_double(xCenter), c_double(yCenter), destImage)
		return destImage

	# class method: Image.CopyRect(x1: int, y1: int, x2: int, y2: int) -> Image
	def CopyRect(f, sourceImage: Image, x1: int, y1: int, x2: int, y2: int, destImage: Image) -> Image:
		""" Creates a copy of a rectangular area of an image. If some apex of a rectangle is located outside the source
			image, rectangular areas that do not contain the source image will be black.
			x1 and y2 - the X and Y coordinates of the bottom left corner of the copied rectangle.
			x2 and y2 - the X and Y coordinates of the top right corner of the copied rectangle.
			Equivalent to: destImage = sourceImage.CopyRect(x1, y1, x2, y2)
		"""
		f(sourceImage, c_int(x1), c_int(y1), c_int(x2), c_int(y2), destImage)
		return destImage

	# class method: Image.CopyRectReplicateBorder(x1: int, y1: int, x2: int, y2: int) -> Image
	def CopyRectReplicateBorder(f, sourceImage: Image, x1: int, y1: int, x2: int, y2: int, destImage: Image) -> Image:
		""" Creates a copy of a rectangular area of an image and adds replicated border pixels. This function copies
			the source image to the destination image and fills pixels ("border") outside the copied area in the
			destination image with the values of the nearest source image pixels.
			Equivalent to: destImage = sourceImage.CopyRectReplicateBorder(x1, y1, x2, y2)
		"""
		f(sourceImage, c_int(x1), c_int(y1), c_int(x2), c_int(y2), destImage)
		return destImage

	# class method: Image.Mirror(useVerticalMirroringInsteadOfHorizontal: bool = False) -> Image
	def MirrorImage(f, image: Image, useVerticalMirroringInsteadOfHorizontal: bool = False) -> Image:
		""" Mirrors an image inplace. The function can mirror images horizontally or vertically.
			'UseVerticalMirroringInsteadOfHorizontal' - sets the mirror direction.
			Returns: self
		"""
		f(image, c_bool(useVerticalMirroringInsteadOfHorizontal))
		return image

	# class method: Image.width
	def GetImageWidth(f, image: Image) -> int:
		i = c_int()
		f(image, byref(i))
		return i.value

	# class method: Image.height
	def GetImageHeight(f, image: Image) -> int:
		i = c_int()
		f(image, byref(i))
		return i.value

	# class method: Image.ImageData() -> ImageData
	def GetImageData(f, image: Image) -> ImageData:
		""" Returns inplace image buffer as ImageData object """
		(buf, width, height, scanLine, colorMode) = (ImageData(), c_int(), c_int(), c_int(), c_int())
		f(image, byref(buf), byref(width), byref(height), byref(scanLine), byref(colorMode))
		buf.width, buf.height, buf.scanLine, buf.colorMode = width.value, height.value, scanLine.value, colorMode.value
		return buf

	# ---  Matching functions  --- #

	# class method: Image.GetFaceTemplate
	def GetFaceTemplate(f, image: Image) -> FaceTemplate:
		""" The function is used to extract a template from a facial image. It first detects a face, then detects its
			facial features and extracts the template. If there is more than one face in the image, the template is
			extracted for the face with the most clearly visible details. If there is no clearly visible face,
			the function raises FSDK.FaceNotFound exception. To set threshold determining the accepted quality for faces,
			use the FSDK.SetFaceDetectionThreshold function. If the face position or its features or eye centers are known,
			it is more efficient to use the FSDK.GetFaceTemplateInRegion or FSDK.GetFaceTemplateUsingEyes functions.
			To extract the template for a specific face, use the FSDK.GetFaceTemplateInRegion function.
			Equivalent to: image.GetFaceTemplate()
		"""
		faceTemplate = FaceTemplate()
		f(image, byref(faceTemplate))
		return faceTemplate

	def GetFaceTemplateInRegion(f, image: Image, facePosition: FacePosition) -> FaceTemplate:
		""" The function detects facial features in a specific region, extracts and returns a template. The face detection
			stage is not performed. This function can be useful if an approximate face size and position is	known, or
			to process a specific face returned by FSDK.DetectFace or FSDK.DetectMultipleFaces. It raises no exception
			if the face is not clearly visible. This is because it assumes that if face detection functions return a
			detected face position, the face is of sufficient quality. If facial features or eye centers are known,
			it is more efficient to use the FSDK.GetFaceTemplateUsingFeatures or FSDK.GetFaceTemplateUsingEyes function.
			Equivalent to: image.GetFaceTemplate(facePosition)
		"""
		faceTemplate = FaceTemplate()
		f(image, byref(facePosition), byref(faceTemplate))
		return faceTemplate

	def GetFaceTemplateUsingFeatures(f, image: Image, facialFeatures: Features) -> FaceTemplate:
		""" Extracts a face template using the detected facial feature coordinates.
			The function receives facial feature coordinates detected by the FSDK.DetectFacialFeatures or
			FSDK.DetectFacialFeaturesInRegion functions and extracts a face template. Face detection, facial feature
			detection, and eye centers detection are not performed. This function can be useful when facial features
			for a specific face are already detected. The function raises no exception if the face is not clearly visible,
			since it assumes that if the face and its facial features are already detected, the face is of sufficient quality.
		"""
		faceTemplate = FaceTemplate()
		f(image, facialFeatures, byref(faceTemplate))
		return faceTemplate

	def GetFaceTemplateUsingEyes(f, image: Image, eyeCoords: Eyes) -> FaceTemplate:
		""" The function receives eye centers coordinates detected by the FSDK.DetectFacialFeatures, FSDK.DetectFacialFeaturesInRegion,
			FSDK.DetectEyes, or FSDK.DetectEyesInRegion functions and extracts a face template. Face detection, facial
			feature detection, and eye centers detection are not performed. This function can be useful when facial
			features or eye centers for a specific face are already detected. The function raises no exception if the
			face is not clearly visible, since it assumes that if the face and its facial features or eye centers are
			already detected, the face is of sufficient quality. Note that the FSDK.GetFaceTemplate, FSDK.GetFaceTemplateInRegion
			and FSDK.GetFaceTemplateUsingFeatures functions return templates that could be matched with higher accuracy,
			so it is recommended to use these functions instead.
		"""
		faceTemplate = FaceTemplate()
		f(image, eyeCoords, byref(faceTemplate))
		return faceTemplate

	# class method: FaceTemplate.Match
	def MatchFaces(f, faceTemplate1: FaceTemplate, faceTemplate2: FaceTemplate) -> float:
		""" Match two face templates. The returned value determines the similarity of the faces.
			'faceTemplate1' and 'faceTemplate2' are templates of faces to be compared.
			Returns the similarity of the face templates.
			FSDK.UnsypportedTemplateVersion exception is raised if any of the templates is created with an unsupported
			version of FaceSDK.
			Equivalent to: faceTemplate1.Match(faceTemplate2)
		"""
		similarity = c_float()
		f(faceTemplate1, faceTemplate2, byref(similarity))
		return similarity.value

	def GetMatchingThresholdAtFAR(f, FARValue: float) -> float:
		""" The function returns the threshold value for similarity to determine if two matched templates belong to the
			same person at a given FAR (False Acceptance Rate) value. The FAR determines the acceptable error rate when
			two different people’s templates are mistakenly recognized as the same person. Decreasing FAR leads to an
			increase in FRR - i.e. with low FAR it becomes more probable that two templates from the same person will be
			determined as belonging to different people.
			'FARValue' - the desired FAR value. Varies from 0.0 (means 0%) to 1.0 (means 100%).
		"""
		threshold = c_float()
		f(c_float(FARValue), byref(threshold))
		return threshold.value

	def GetMatchingThresholdAtFRR(f, FRRValue: float) -> float:
		""" The function returns the threshold value for similarity to determine if two matched templates belong to the
			same person at a given FRR (False Rejection Rate) value. The FRR determines the acceptable error rate when
			two templates of the same person are identified as belonging to different people. Decreasing FRR leads to an
			increase in FAR - i.e. with low FRR it becomes more probable that two different people’s templates will be
			recognized as the same person.
			'FRRValue' - the desired FRR value. Varies from 0.0 (means 0%) to 1.0 (means 100%).
		"""
		threshold = c_float()
		f(c_float(FRRValue), byref(threshold))
		return threshold.value

	# ---  Webcam usage  --- #
	def InitializeCapturing(f):
		""" The function initializes the capturing process (but does not open a camera). It should be called in a
			certain thread that works with cameras. Note that on Windows platforms this function initializes COM in the
			thread; if you already initialized COM, you must not call this function, and you must not call
			FSDK_FinalizeCapturing.
		"""
		f()

	def FinalizeCapturing(f):
		""" The function finalizes the capturing process, initialized by the FSDK_InitializeCapturing function (and
			finalizes COM on Windows platforms). If you already finalized COM, you must not call this function.
		"""
		f()

	def SetHTTPProxy(f, serverNameOrIPAddress: str, port: int, userName: str, password: str):
		""" This function sets an HTTP proxy to be used with an IP camera. If a proxy is required, the function should
			be called before the FSDK.OpenIPVideoCamera function.
			'serverNameOrIPAddress' - proxy address.
			'port' - proxy port.
			'userName' - proxy username.
			'password' - proxy password.
		"""
		f(cstr(serverNameOrIPAddress), c_ushort(port), cstr(userName), cstr(password))

	def OpenIPVideoCamera(f, compression: int, URL: str, userName: str, password: str, timeoutSeconds: int) -> Camera:
		""" This function opens the IP camera at a given URL and returns its handle. You may call the FSDK.SetHTTPProxy
			function to set an HTTP proxy for accessing the camera.
			'compressionType' - the type of video stream (FSDK.MJPEG by default).
			'URL' - URL of the IP camera to be opened.
			'username' - IP camera access username.
			'password' - IP camera access password.
			'timeoutSeconds' - connection timeout in seconds.
			Returns the Camera object on success.
		"""
		cam = Camera()
		f(c_int(compression), cstr(URL), cstr(userName), cstr(password), c_int(timeoutSeconds), byref(cam))
		return cam

	# class method: Camera.Close()
	def CloseVideoCamera(f, camera: Camera):
		""" This function closes the camera, opened by the FSDK.OpenVideoCamera or FSDK.OpenIPVideoCamera functions.
		"""
		if camera.handle != -1:
			f(camera)
			camera.handle = -1

	# class method: Camera.GrabFrame() -> Image
	def GrabFrame(f, camera: Camera) -> Image:
		""" Retrieves the current frame from a web camera or an IP camera and stores the frame in the created Image.
			If a camera returns an image, mirrored horizontally (it depends on the camera settings), then you can mirror
			it by using FSDK.MirrorImage(image) or image.Mirror()
		"""
		im = Image(-1)
		f(camera, byref(im))
		return im

	if windows or linux:
		def SetCameraNaming(f, useDevicePathAsName):
			f(c_bool(useDevicePathAsName))

		class CameraName(str):
			def __new__(cls, name, devicePath):
				inst = super(FSDK.CameraName, cls).__new__(cls, name)
				inst.devicePath = devicePath
				return inst

		def ListCameraNames(self): 
			nl, pl = POINTER(camera_char_p)(), POINTER(camera_char_p)()
			n = self._GetCameraListEx(nl, pl)
			lst = [FSDK.CameraName(process_str(name), process_str(devicePath)) for name, devicePath in zip(nl[:n], pl[:n])]
			self._FreeCameraList(nl, n)
			return lst

		def _GetCameraList(f, nameList):
			n = c_int()
			f(byref(nameList), byref(n))
			return n.value

		def _GetCameraListEx(f, nameList, pathList):
			n = c_int()
			f(byref(nameList), byref(pathList), byref(n))
			return n.value

		def _FreeCameraList(f, cameraList, cameraCount):
			f(cameraList, cameraCount)

		def ListVideoFormats(self, cameraName):
			vfl = POINTER(VideoFormatInfo)()
			lst = [VideoFormatInfo(f.Width, f.Height, f.FormatIndex) for f in vfl[:self._GetVideoFormatList(cameraName, vfl)]]
			self._FreeVideoFormatList(vfl)
			return lst

		def _GetVideoFormatList(f, cameraName, videoFormatList):
			cnt = c_int()
			f(create_camera_buffer(cameraName), byref(videoFormatList), byref(cnt))
			return cnt.value

		def _FreeVideoFormatList(f, videoFormatList):
			f(videoFormatList)

		def SetVideoFormat(f, cameraName, videoFormat):
			f(create_camera_buffer(cameraName), videoFormat)

		# the function is available in Camera class as constructor and 'Open' function
		def OpenVideoCamera(f, cameraName):
			cam = Camera()
			f(create_camera_buffer(cameraName), byref(cam))
			return cam

	# ---  Tracker functions (also available in Tracker class)  --- #

	# class method: Tracker.__new__() -> Tracker
	def CreateTracker(f) -> Tracker:
		""" Create a new tracker. """
		tr = Tracker(-1)
		f(byref(tr))
		return tr

	# class method: Tracker.Free()
	def FreeTracker(f, tracker: Tracker):
		""" Deletes existing 'tracker'. """
		if tracker.handle != -1:
			f(tracker)
			tracker.handle = -1

	# class method: Tracker.Clear()
	def ClearTracker(f, tracker: Tracker):
		""" Clears the content of 'tracker', releasing all its memory. The parameters are reset to their default values,
			so if you just need to clear the tracker’s memory, consider setting the parameters with the
			FSDK.SetTrackerParameter or the FSDK.SetTrackerMultipleParameters function again.
		"""
		f(tracker)

	# class method: Tracker.SetParameter
	def SetTrackerParameter(f, tracker: Tracker, parameterName: str, parameterValue: str):
		""" Sets the parameter of a tracker.
			'parameterName' - name of the parameter to be set.
			'parameterValue' - value of the parameter.
		"""
		f(tracker, cstr(parameterName), cstr(value_to_str(parameterValue)), err_desc=lambda: "%s = %s" % (parameterName, parameterValue))

	# class method: Tracker.SetParameters(**kw)
	def SetTrackerMultipleParameters(f, tracker: Tracker, parameters: str):
		""" Sets multiple parameters of 'tracker'. The 'parameters' and their values are specified in the following format:
			"Parameter1=Value1[;Parameter2=Value2[;…]]"
		"""
		err_pos = c_int()
		f(tracker, cstr(parameters), byref(err_pos), err_desc=lambda: "in line %s at position %s" % (parameters, err_pos.value))

	# class method: Tracker.GetParameter(parameterName: str) -> str
	def GetTrackerParameter(f, tracker: Tracker, parameterName: str) -> str:
		""" Retrieves the value of 'tracker' parameter. """
		return FSDKLib._receive_string(lambda buf: f(tracker, cstr(parameterName), buf, c_longlong(len(buf)),
			err_desc=lambda: "parameterName = %s" % parameterName))

	# class method: Tracker.FeedFrame(cameraIdx: int, image: Image) -> List[int]
	def FeedFrame(self, tracker: Tracker, cameraIdx: int, image: Image, maxIDs: int = 512) -> List[int]:
		""" Processes a video frame according to tracker’s parameters, and returns the list of identifiers of the
			tracked faces.
			'cameraIdx' - index of the camera; should be equal to 0 in the current release.
			'image' - the Image of the video frame to process.
			'maxIDs' - the max count of faces tracked in the current frame.
			Returns: the list of identifiers of the tracked faces.
		"""
		(buf, faceCount) = ((c_longlong*maxIDs)(), c_longlong())
		self._FeedFrame(tracker, c_longlong(cameraIdx), image, byref(faceCount), buf, c_longlong(maxIDs*8))
		return buf[:faceCount.value]

	if True:  # use FeedFrame function
		def _FeedFrame(f, tracker, cameraIdx, image, faceCount, IDs, maxSizeInBytes):
			f(tracker, cameraIdx, image, faceCount, IDs, maxSizeInBytes)

	# class method: Tracker.GetEyes(cameraIdx: int, ID: int) -> Eyes
	def GetTrackerEyes(f, tracker: Tracker, cameraIdx: int, ID: int) -> Eyes:
		""" Retrieves the coordinates of the eye centers of a tracked face. The function accepts the identifier returned
			by FSDK.FeedFrame. This identifier should be passed to before the next call of FSDK.FeedFrame using the same
			tracker.
			For the function to return the eye center coordinates, at least one of the parameters 'DetectEyes',
			'DetectFacialFeatures', 'RecognizeFaces', 'DetectGender', 'DetectAge' or 'DetectExpression' must be set to true.
			'cameraIdx' - index of the camera; should be equal to 0 in the current release.
			'ID' - identifier of the subject returned by FSDK.FeedFrame, whose eye center coordinates will be received.
		"""
		eyes = Eyes()
		f(tracker, c_longlong(cameraIdx), c_longlong(ID), byref(eyes))
		return eyes

	# class method: Tracker.GetFacialFeatures(cameraIdx: int, ID: int) -> Features
	def GetTrackerFacialFeatures(f, tracker: Tracker, cameraIdx: int, ID: int) -> Features:
		""" Retrieves the coordinates of a tracked face’s features. The function accepts the identifier returned
			by FSDK.FeedFrame. This identifier should be passed to before the next call of FSDK.FeedFrame.
			For the function to return the facial feature coordinates, either of the parameters 'DetectFacialFeatures',
			'DetectGender', 'DetectAge' or 'DetectExpression' must be set to true.
			Equivalent to: tracker.GetFacialFeatures(cameraIdx, ID)
			'cameraIdx' - index of the camera; should be equal to 0 in the current release.
			'ID' - identifier of the subject returned by FSDK.FeedFrame, whose facial feature coordinates will be received.
		"""
		ff = Features()
		f(tracker, c_longlong(cameraIdx), c_longlong(ID), byref(ff))
		return ff

	# class method: Tracker.GetFacePosition(cameraIdx: int, ID: int) -> FacePosition
	def GetTrackerFacePosition(f, tracker: Tracker, cameraIdx: int, ID: int) -> FacePosition:
		""" Retrieves the position of a tracked face. The function accepts the identifier returned by FSDK.FeedFrame.
			'cameraIdx' - index of the camera; should be equal to 0 in the current release.
			'ID' - identifier of the subject returned by FSDK.FeedFrame, whose face position will be received.
			Equivalent to: tracker.GetFacePosition(cameraIdx, ID)
		"""
		fp = FacePosition()
		f(tracker, c_longlong(cameraIdx), c_longlong(ID), byref(fp))
		return fp

	# class method: Tracker.GetFacialAttribute(cameraIdx: int, ID: int, attributeName: str) -> str
	def GetTrackerFacialAttribute(f, tracker: Tracker, cameraIdx: int, ID: int, attributeName: str) -> str:
		""" Given an attribute of a tracked face, retrieves its Values and their Confidences. The function accepts the
			identifier returned by FSDK.FeedFrame. This identifier should be passed to FSDK.GetTackerFacialAttribute
			before the next call of FSDK.FeedFrame. The function allows for detecting gender when provided with the
			“Gender” attribute name, for detecting age when provided with the “Age” attribute name and for detecting
			expression when provided with the “Expression” attribute name.
			'cameraIdx' - index of the camera; should be equal to 0 in the current release.
			'ID' - identifier of a subject returned by FSDK.FeedFrame whose attribute will be retrieved.
			'attributeName' - name of the attribute.
			Raises:
				FSDK.IdNotFound exception if the specified ID was not returned by the previous FSDK.FeedFrame call.
				FSDK.AttributeNotDetected if the specified attribute was not detected on the previous FSDK.FeedFrame call.
				FSDK.UnknownAttribute if the specified attribute name is not supported.
		"""
		return FSDKLib._receive_string(lambda buf: f(tracker, c_longlong(cameraIdx), c_longlong(ID), cstr(attributeName),
			buf, c_longlong(len(buf)), err_desc=lambda: "ID = %s, attributeName = %s" % (ID, attributeName)))

	# class method: Tracker.GetIDsCount() -> int
	@FSDK_ver("8.3")
	def GetTrackerIDsCount(f, tracker: Tracker) -> int:
		""" Returns the number of persons known to the tracker """
		count = c_longlong()
		f(tracker, byref(count))
		return count.value

	# class method: Tracker.GetAllIDs() -> List[int]
	@FSDK_ver("8.3")
	def GetTrackerAllIDs(f, tracker: Tracker) -> List[int]:
		""" Returns all IDs of persons known to the tracker """
		lst = (c_longlong*FSDK.GetTrackerIDsCount(tracker))()
		f(tracker, lst, c_longlong(len(lst)*8))
		return lst

	@FSDK_ver("8.3")
	def GetTrackerFaceIDsCountForID(f, tracker: Tracker, ID) -> int:
		""" Returns the number of a person's faceIDs with given ID """
		count = c_longlong()
		f(tracker, c_longlong(ID), byref(count))
		return count.value

	# class method: Tracker.GetFaceIDsForID(ID) -> List[int]
	@FSDK_ver("8.3")
	def GetTrackerFaceIDsForID(f, tracker: Tracker, ID: int) -> List[int]:
		""" Returns all faceIDs of a person with given ID """
		count = FSDK.GetTrackerFaceIDsCountForID(tracker, ID)
		id_list = (count*c_longlong)()
		f(tracker, c_longlong(ID), byref(id_list), c_longlong(count*8))
		return id_list

	# class method: Tracker.GetIDByFaceID(ID) -> int
	@FSDK_ver("8.3")
	def GetTrackerIDByFaceID(f, tracker: Tracker, FaceID: int) -> int:
		""" Returns the person ID by its FaceID """
		ID = c_longlong()
		f(tracker, c_longlong(FaceID), byref(ID))
		return ID.value

	# class method: Tracker.GetFaceTemplate(faceID: int) -> FaceTemplate
	@FSDK_ver("8.3")
	def GetTrackerFaceTemplate(f, tracker: Tracker, faceID: int) -> FaceTemplate:
		""" Return face template of a face with given faceID 
			Raises FSDK.FaceIdNotFound if the specified faceID is not present in the tracker memory.
		"""
		face_template = FaceTemplate()
		f(tracker, c_longlong(faceID), byref(face_template), err_desc = lambda: "faceID = %s" % faceID)
		return face_template

	@FSDK_ver("8.3")
	def TrackerCreateID(f, tracker: Tracker, faceTemplate: FaceTemplate) -> Tuple[int]: # (ID, faceID)
		""" Creates new person with given faceTemplate and returns its ID and faceID """
		ID = c_longlong()
		faceID = c_longlong()
		f(tracker, faceTemplate, byref(ID), byref(faceID))
		return ID.value, faceID.value

	# class method: Tracker.AddFaceTemplate(ID: int, faceTemplate: FaceTemplate) -> int
	@FSDK_ver("8.3")
	def AddTrackerFaceTemplate(f, tracker: Tracker, ID: int, faceTemplate: FaceTemplate) -> int:
		""" Add new faceTemplate to the person and returns its new faceID
			Raises FSDK.FaceIdNotFound if the specified ID is not present in the tracker memory.
		"""
		faceID = c_longlong()
		f(tracker, c_longlong(ID), faceTemplate, byref(faceID), err_desc = lambda: "ID = %s" % ID)
		return faceID.value

	# class method: Tracker.DeleteFaceTemplate(faceID: int)
	@FSDK_ver("8.3")
	def DeleteTrackerFace(f, tracker: Tracker, faceID: int):
		""" Delete the face from tracker memory 
			Raises FSDK.FaceIdNotFound if the specified faceID is not present in the tracker memory.
		"""
		f(tracker, c_longlong(faceID), err_desc = lambda: "faceID = %s" % faceID)

	# class method: Tracker.GetFaceImage(faceID: int) -> Image
	@FSDK_ver("8.3")
	def GetTrackerFaceImage(f, tracker: Tracker, faceID: int) -> Image:
		""" Return face Image of a face with given faceID 
			Raises FSDK.FaceIdNotFound if the specified faceID is not present in the tracker memory.
		"""
		img = Image(-1)
		f(tracker, c_longlong(faceID), byref(img), err_desc = lambda: "faceID = %s" % faceID)
		return img

	# class method: Tracker.SetFaceImage(faceID: int, image: Image)
	@FSDK_ver("8.3")
	def SetTrackerFaceImage(f, tracker: Tracker, faceID: int, image: Image):
		""" Set the new face image for given faceID.
			The image must have 96x96 size in pixels.
			Raises FSDK.FaceIdNotFound if the specified faceID is not present in the tracker memory.
		"""
		f(tracker, c_longlong(faceID), image, err_desc = lambda: "faceID = %s" % faceID)
		return image

	# class method: Tracker.DeleteFaceImage(faceID: int)
	@FSDK_ver("8.3")
	def DeleteTrackerFaceImage(f, tracker: Tracker, faceID: int):
		""" Deletes the image of a face with given faceID from the tracker memory.
			Raises FSDK.FaceIdNotFound if the specified faceID is not present in the tracker memory.
		"""
		f(tracker, c_longlong(faceID), err_desc = lambda: "faceID = %s" % faceID)

	# class method: Tracker.MatchFaces(faceTemplate: FaceTemplate, threshold: float, maxCount: int = 5)
	@FSDK_ver("8.3")
	def TrackerMatchFaces(f, tracker: Tracker, faceTemplate: FaceTemplate, threshold: float, maxCount: int = 5) -> List[IDSimilarity]:
		""" The function compares the FaceTemplate with all templates stored in the tracker, 
			finds the IDs of individuals that match the Threshold, and writes them to a buffer IDSimilarity structures in 
			descending order of similarities, starting with the highest. The number of returned IDs is limited by maxCount value.
		"""
		buffer = (IDSimilarity * maxCount)()
		count = c_longlong()
		f(tracker, faceTemplate, c_float(threshold), byref(buffer), byref(count), c_longlong(ctypes.sizeof(buffer)))
		return buffer[:count.value]

	# class method: Tracker.LockID(ID: int)
	def LockID(f, tracker: Tracker, ID: int):
		""" Locks an identifier. When an identifier is locked, at least one facial appearance of an identifier will
			not be deleted during any possible purge. You should call this function before the FSDK.SetName function.
			The function has no effect on identifiers which were already tagged with a name. The call should be usually
			paired with FSDK.UnlockID call. When the user does not set a name to a locked identifier, unlocking it allows
			it to become purged if necessary for memory efficient memory use.
			You may call this function with any identifier regardless of when it was returned as long as it remains
			present in the tracker memory.
			Raises FSDK.IdNotFound if the specified ID is not present in the tracker memory.
		"""
		f(tracker, c_longlong(ID))

	# class method: Tracker.UnlockID(ID: int)
	def UnlockID(f, tracker: Tracker, ID: int):
		""" Unlocks the ID so it may be purged. You should call this function after the FSDK.LockID call. The function
			has no effect on identifiers which were already tagged with a name.
			Raises FSDK.IdNotFound if the specified ID is not present in the tracker memory.
		"""
		f(tracker, c_longlong(ID))

	# class method: Tracker.PurgeID(ID: int)
	def PurgeID(f, tracker: Tracker, ID: int):
		""" Removes all facial appearances of the ID from the tracker memory. You must call this function if there was
			a false acceptance or if you erroneously assigned equal names to different persons.
			Raises FSDK.IdNotFound if the specified ID is not present in the tracker memory.
		"""
		f(tracker, c_longlong(ID))

	# class method: Tracker.SetName(ID: int, name: str)
	def SetName(f, tracker: Tracker, ID: int, name: str):
		""" Sets the name of an identifier. To erase the name tag, specify an empty name string. When erasing the name
			tag because of a false acceptance, or because you erroneously assigned equal names to different persons,
			you must also call the FSDK.PurgeID function. The function will unlock the identifier if the name is
			successfully set.
			You may call this function with any identifier regardless of when it was returned, as long as it is present
			in the tracker memory.
			Raises FSDK.IdNotFound if the specified ID is not present in the tracker memory.
		"""
		f(tracker, c_longlong(ID), cstr(name))

	# class method: Tracker.GetName(ID: int) -> str
	def GetName(f, tracker: Tracker, ID: int) -> str:
		""" Returns the name the identifier has been tagged with. The function accepts any identifier regardless of
			when it was returned, as long as it is present in the tracker memory.
		"""
		return FSDKLib._receive_string(lambda buf: f(tracker, c_longlong(ID), buf, c_longlong(len(buf))))

	# class method: Tracker.GetIDReassignment(ID: int) -> int
	def GetIDReassignment(f, tracker: Tracker, ID: int) -> int:
		""" When provided with a subject’s ID received on earlier frames, returns the new subject’s ID if there was
			a merger. If an identifier was not merged, the function returns the same ID value in the output variable.
			Note that the function does not raise an exception if an identifier is not present in the tracker memory;
			instead, the same ID value is returned.
			Returns the reassigned value of an identifier.
		"""
		rID = c_longlong()
		f(tracker, c_longlong(ID), byref(rID))
		return rID.value

	# class method: Tracker.GetSimilarIDCount(ID: int) -> int
	def GetSimilarIDCount(f, tracker: Tracker, ID: int) -> int:
		""" Returns the number of identifiers that are similar to a given identifier. The function accepts the identifier
			returned by FSDK.FeedFrame and should be passed before the next call of FSDK.FeedFrame.
		"""
		count = c_longlong()
		f(tracker, c_longlong(ID), byref(count))
		return count.value

	# class method: Tracker.GetSimilarIDCount(ID: int) -> List[int]
	def GetSimilarIDList(f, tracker: Tracker, ID: int) -> List[int]:
		""" Returns the list of identifiers that are similar to a given identifier.
			Raises FSDK.IdNotFound if the specified ID is not present in the tracker memory.
		"""
		lst = (c_longlong*FSDK.GetSimilarIDCount(tracker, ID))()
		f(tracker, ID, lst, c_longlong(len(lst)*8))
		return lst

	# class method: Tracker.GetAllNames(ID: int) -> List[str]
	def GetAllNames(f, tracker: Tracker, ID: int) -> List[str]:
		""" The function returns all names that belong to a given identifier, and similar identifiers.
			You should call this function instead of FSDK.GetName whenever possible. Alternatively, you may implement
			the functionality of FSDK.GetAllNames, calling FSDK.GetName on the given identifier, then
			FSDK.GetSimilarIDCount and FSDK.GetSimilarIDList to get the list of similar identifiers, then finally call
			FSDK.GetName on that list.
			Raises FSDK.IdNotFound if the specified ID is not present in the tracker memory.
		"""
		return FSDKLib._receive_string(lambda buf: f(tracker, ID, buf, c_longlong(len(buf)))).split(';')

	# class method: Tracker.SaveToFile(fileName: str)
	def SaveTrackerMemoryToFile(f, tracker: Tracker, fileName: str):
		""" Saves the memory of a tracker to a file.
			Note that tracker parameters, along with its face tracking state, are not saved.
			Raises FSDK.IOError if an I/O error has occurred.
		"""
		f(tracker, cstr(fileName), err_desc=fileName)

	# class method: Tracker.FromFile(fileName: str) -> Tracker
	def LoadTrackerMemoryFromFile(f, fileName: str) -> Tracker:
		""" Loads the memory of a tracker from a file and returns a new tracker.
			Note that tracker parameters, along with its face tracking state, are not loaded.
			Raises:
				FSDK.BadFileFormat if the file has unsupported format.
				FSDK.UnsupportedFileVersion if the file was saved with Luxand FaceSDK of an unsupported version.
				FSDK.FileNotFound if there was an error opening the file.
				FSDK.IOError if an I/O error has occurred.
		"""
		tr = Tracker(-1)
		f(byref(tr), cstr(fileName), err_desc=fileName)
		return tr

	def GetTrackerMemoryBufferSize(f, tracker: Tracker) -> int:
		""" Returns the size of a buffer (in bytes) needed to save the memory of a tracker. """
		i = c_longlong()
		f(tracker, byref(i))
		return i.value

	def _SaveTrackerMemoryToBuffer(f, tracker: Tracker, buffer: bytes):
		""" Use Tracker.GetMemory() or Tracker.ToBytes() to retrieve tracker memory """
		f(tracker, buffer, c_longlong(len(buffer)))

	# class method: Tracker.FromBytes(buffer: bytes) -> Tracker
	def LoadTrackerMemoryFromBuffer(f, buffer: bytes) -> Tracker:
		""" Creates and returns a tracker loaded from 'buffer'.
			Note that tracker parameters, along with its face tracking state, are not loaded.
		"""
		tr = Tracker(-1)
		f(byref(tr), buffer)
		return tr

	# ---  Facial attributes  --- #

	# class method: Image.DetectFacialAttributeUsingFeatures(facialFeatures: Features, attributeName: str, ret_dict: bool = False) -> str
	def DetectFacialAttributeUsingFeatures(f, image: Image, facialFeatures: Features, attributeName: str, ret_dict: bool = False) -> str:
		""" Detects an attribute of a face, and returns the Values of a particular attribute, and Confidences in these
			Values. Each facial attribute has a number of Values, and each Value has an associated Confidence.
			A Value is a string, and a Confidence is a float from 0 to 1, which represents confidence level of this
			particular value of the attribute.
			The following attribute names are supported:
				"Liveness" - to get the liveness probability.
				"Gender" - to detect the gender of a face. The attribute has "Male" and "Female" values.
				"Age" - to detect the age of a face. The attribute has "Age" value.
				"Expression" - to detect the expression of a face. The attribute has "Smile" and "EyesOpen" values.
			The Values and their Confidences are returned in a string of the following format:
				"Value1=Confidence1[;Value2=Confidence2[;…]]". For example, when calling the function with the "Gender"
			attribute, the following string may be returned:
				"Male=0.95721;Female=0.04279”. It means that the subject has male gender with a confidence of 95.7%,
			and female gender with a confidence of 4.3%.
			When calling the function with the "Age" attribute, the following string may be returned: "Age=37".
			When calling the function with the "Expression" attribute, the following string may be returned:
				"Smile=0.987;EyesOpen=0.9952". It means that the subject smiles with a confidence of 98.7%, and the eyes
			are open with a confidence of 99.5%. You may use several attributes in a single function call separated
			by ";". For example, if AttributeName is "Gender; Age; Expression", the result may be the following:
				"Male=0.95721;Female=0.04279;Age=37;Smile=0.987;EyesOpen=0.995".
			You may use the FSDK.GetValueConfidence to parse the returned string and retrieve the Confidences for
			individual Values.
			'AttributeName' - name of the attribute. You may specify several attributes separated by ";".
			'ret_dict' - if True, the function parses the result string and returns a dictionary like this:
				{'Male': 0.95721, 'Female': 0.04279, 'Age': 37.0, 'Smile': 0.987, 'EyesOpen': 0.995}
			"""
		res = FSDKLib._receive_string(lambda buf: f(image, facialFeatures, cstr(attributeName), buf, c_longlong(len(buf))))
		if ret_dict:
			pairs = (values.split('=') for values in res.split(';'))
			res = {value: float(conf) for value, conf in pairs}
		return res

	def GetValueConfidence(f, attributeValues: str, value: str) -> float:
		""" Parses the string returned by FSDK.GetTrackerFacialAttribute or FSDK.DetectFacialAttributeUsingFeatures,
			and returns the Confidence in an individual Value.
			Example:
				attr_values = FSDK.DetectFacialAttributeUsingFeatures(features, "Gender")
				male_confidence = FSDK.GetValueConfidence(attr_values, "Male")
				female_confidence = FSDK.GetValueConfidence(attr_values, "Female")
		"""
		conf = c_float()
		f(cstr(attributeValues), cstr(value), byref(conf))
		return conf.value

	@FSDK_ver("7.2")
	def SetParameters(f, values='', **kwargs):
		(err_pos, parameters) = (c_int(), values.strip(';\n\t '))
		def chain():
			if parameters: yield parameters
			for n, v in kwargs.items():
				yield '%s=%s' % (n, value_to_str(v))
		parameters = ';'.join(chain())
		f(cstr(parameters), byref(err_pos), err_desc=lambda: "in line %s at position %s" % (parameters, err_pos.value))

	@FSDK_ver("7.2")
	def SetParameter(f, param_name, param_value):
		""" Sets a parameter for FaceSDK.
			'param_name' - name of the parameter to be set.
			'param_value' - value of the parameter.
		"""
		f(cstr(param_name), cstr(value_to_str(param_value)), err_desc=lambda: "%s = %s" % (param_name, param_value))

	# generate wrapper functions
	FSDK_Wrapper.prepare(locals())
	del FSDK_Wrapper.prepare

FSDK = FSDKLib()
