From b9e893184ad2db3b36872de40f28d13843fb0086 Mon Sep 17 00:00:00 2001 From: Robin Date: Sat, 5 May 2018 15:57:53 +0100 Subject: [PATCH] Refactor ImageProcessingFaceEntity (#14296) * Refactor ImageProcessingFaceEntity * Replace STATE_UNKNOWN with None --- .../components/image_processing/__init__.py | 98 ++++++++++++++++ .../components/image_processing/demo.py | 7 +- .../image_processing/dlib_face_detect.py | 4 +- .../image_processing/dlib_face_identify.py | 5 +- .../image_processing/microsoft_face_detect.py | 5 +- .../microsoft_face_identify.py | 105 +----------------- 6 files changed, 110 insertions(+), 114 deletions(-) diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index f0cb3a66d52..c6100ff701d 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -10,6 +10,7 @@ import logging import voluptuous as vol +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.const import ( ATTR_ENTITY_ID, CONF_NAME, CONF_ENTITY_ID) @@ -17,6 +18,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.util.async_ import run_callback_threadsafe _LOGGER = logging.getLogger(__name__) @@ -33,7 +35,16 @@ DEVICE_CLASSES = [ SERVICE_SCAN = 'scan' +EVENT_DETECT_FACE = 'image_processing.detect_face' + +ATTR_AGE = 'age' ATTR_CONFIDENCE = 'confidence' +ATTR_FACES = 'faces' +ATTR_GENDER = 'gender' +ATTR_GLASSES = 'glasses' +ATTR_NAME = 'name' +ATTR_MOTION = 'motion' +ATTR_TOTAL_FACES = 'total_faces' CONF_SOURCE = 'source' CONF_CONFIDENCE = 'confidence' @@ -133,3 +144,90 @@ class ImageProcessingEntity(Entity): # process image data yield from self.async_process_image(image.content) + + +class ImageProcessingFaceEntity(ImageProcessingEntity): + """Base entity class for face image processing.""" + + def __init__(self): + """Initialize base face identify/verify entity.""" + self.faces = [] + self.total_faces = 0 + + @property + def state(self): + """Return the state of the entity.""" + confidence = 0 + state = None + + # No confidence support + if not self.confidence: + return self.total_faces + + # Search high confidence + for face in self.faces: + if ATTR_CONFIDENCE not in face: + continue + + f_co = face[ATTR_CONFIDENCE] + if f_co > confidence: + confidence = f_co + for attr in [ATTR_NAME, ATTR_MOTION]: + if attr in face: + state = face[attr] + break + + return state + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return 'face' + + @property + def state_attributes(self): + """Return device specific state attributes.""" + attr = { + ATTR_FACES: self.faces, + ATTR_TOTAL_FACES: self.total_faces, + } + + return attr + + def process_faces(self, faces, total): + """Send event with detected faces and store data.""" + run_callback_threadsafe( + self.hass.loop, self.async_process_faces, faces, total).result() + + @callback + def async_process_faces(self, faces, total): + """Send event with detected faces and store data. + + known are a dict in follow format: + [ + { + ATTR_CONFIDENCE: 80, + ATTR_NAME: 'Name', + ATTR_AGE: 12.0, + ATTR_GENDER: 'man', + ATTR_MOTION: 'smile', + ATTR_GLASSES: 'sunglasses' + }, + ] + + This method must be run in the event loop. + """ + # Send events + for face in faces: + if ATTR_CONFIDENCE in face and self.confidence: + if face[ATTR_CONFIDENCE] < self.confidence: + continue + + face.update({ATTR_ENTITY_ID: self.entity_id}) + self.hass.async_add_job( + self.hass.bus.async_fire, EVENT_DETECT_FACE, face + ) + + # Update entity store + self.faces = faces + self.total_faces = total diff --git a/homeassistant/components/image_processing/demo.py b/homeassistant/components/image_processing/demo.py index 788d12520f5..e225113b5b1 100644 --- a/homeassistant/components/image_processing/demo.py +++ b/homeassistant/components/image_processing/demo.py @@ -4,11 +4,12 @@ Support for the demo image processing. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/demo/ """ -from homeassistant.components.image_processing import ATTR_CONFIDENCE +from homeassistant.components.image_processing import ( + ImageProcessingFaceEntity, ATTR_CONFIDENCE, ATTR_NAME, ATTR_AGE, + ATTR_GENDER + ) from homeassistant.components.image_processing.openalpr_local import ( ImageProcessingAlprEntity) -from homeassistant.components.image_processing.microsoft_face_identify import ( - ImageProcessingFaceEntity, ATTR_NAME, ATTR_AGE, ATTR_GENDER) def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/image_processing/dlib_face_detect.py b/homeassistant/components/image_processing/dlib_face_detect.py index 65705feb7f7..d4a20da253c 100644 --- a/homeassistant/components/image_processing/dlib_face_detect.py +++ b/homeassistant/components/image_processing/dlib_face_detect.py @@ -11,9 +11,7 @@ from homeassistant.core import split_entity_id # pylint: disable=unused-import from homeassistant.components.image_processing import PLATFORM_SCHEMA # noqa from homeassistant.components.image_processing import ( - CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) -from homeassistant.components.image_processing.microsoft_face_identify import ( - ImageProcessingFaceEntity) + ImageProcessingFaceEntity, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) REQUIREMENTS = ['face_recognition==1.0.0'] diff --git a/homeassistant/components/image_processing/dlib_face_identify.py b/homeassistant/components/image_processing/dlib_face_identify.py index 22594aa2547..bf34eb4c2da 100644 --- a/homeassistant/components/image_processing/dlib_face_identify.py +++ b/homeassistant/components/image_processing/dlib_face_identify.py @@ -11,9 +11,8 @@ import voluptuous as vol from homeassistant.core import split_entity_id from homeassistant.components.image_processing import ( - PLATFORM_SCHEMA, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) -from homeassistant.components.image_processing.microsoft_face_identify import ( - ImageProcessingFaceEntity) + ImageProcessingFaceEntity, PLATFORM_SCHEMA, CONF_SOURCE, CONF_ENTITY_ID, + CONF_NAME) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['face_recognition==1.0.0'] diff --git a/homeassistant/components/image_processing/microsoft_face_detect.py b/homeassistant/components/image_processing/microsoft_face_detect.py index 6770ff1bdf6..cd1e341a218 100644 --- a/homeassistant/components/image_processing/microsoft_face_detect.py +++ b/homeassistant/components/image_processing/microsoft_face_detect.py @@ -13,9 +13,8 @@ from homeassistant.core import split_entity_id from homeassistant.exceptions import HomeAssistantError from homeassistant.components.microsoft_face import DATA_MICROSOFT_FACE from homeassistant.components.image_processing import ( - PLATFORM_SCHEMA, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) -from homeassistant.components.image_processing.microsoft_face_identify import ( - ImageProcessingFaceEntity, ATTR_GENDER, ATTR_AGE, ATTR_GLASSES) + PLATFORM_SCHEMA, ImageProcessingFaceEntity, ATTR_AGE, ATTR_GENDER, + ATTR_GLASSES, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME) import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['microsoft_face'] diff --git a/homeassistant/components/image_processing/microsoft_face_identify.py b/homeassistant/components/image_processing/microsoft_face_identify.py index 51f1cd42f47..32f02e1820e 100644 --- a/homeassistant/components/image_processing/microsoft_face_identify.py +++ b/homeassistant/components/image_processing/microsoft_face_identify.py @@ -9,30 +9,18 @@ import logging import voluptuous as vol -from homeassistant.core import split_entity_id, callback -from homeassistant.const import STATE_UNKNOWN +from homeassistant.core import split_entity_id from homeassistant.exceptions import HomeAssistantError from homeassistant.components.microsoft_face import DATA_MICROSOFT_FACE from homeassistant.components.image_processing import ( - PLATFORM_SCHEMA, ImageProcessingEntity, CONF_CONFIDENCE, CONF_SOURCE, - CONF_ENTITY_ID, CONF_NAME, ATTR_ENTITY_ID, ATTR_CONFIDENCE) + PLATFORM_SCHEMA, ImageProcessingFaceEntity, ATTR_NAME, + CONF_CONFIDENCE, CONF_SOURCE, CONF_ENTITY_ID, CONF_NAME, ATTR_CONFIDENCE) import homeassistant.helpers.config_validation as cv -from homeassistant.util.async_ import run_callback_threadsafe DEPENDENCIES = ['microsoft_face'] _LOGGER = logging.getLogger(__name__) -EVENT_DETECT_FACE = 'image_processing.detect_face' - -ATTR_NAME = 'name' -ATTR_TOTAL_FACES = 'total_faces' -ATTR_AGE = 'age' -ATTR_GENDER = 'gender' -ATTR_MOTION = 'motion' -ATTR_GLASSES = 'glasses' -ATTR_FACES = 'faces' - CONF_GROUP = 'group' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -57,93 +45,6 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): async_add_devices(entities) -class ImageProcessingFaceEntity(ImageProcessingEntity): - """Base entity class for face image processing.""" - - def __init__(self): - """Initialize base face identify/verify entity.""" - self.faces = [] - self.total_faces = 0 - - @property - def state(self): - """Return the state of the entity.""" - confidence = 0 - state = STATE_UNKNOWN - - # No confidence support - if not self.confidence: - return self.total_faces - - # Search high confidence - for face in self.faces: - if ATTR_CONFIDENCE not in face: - continue - - f_co = face[ATTR_CONFIDENCE] - if f_co > confidence: - confidence = f_co - for attr in [ATTR_NAME, ATTR_MOTION]: - if attr in face: - state = face[attr] - break - - return state - - @property - def device_class(self): - """Return the class of this device, from component DEVICE_CLASSES.""" - return 'face' - - @property - def state_attributes(self): - """Return device specific state attributes.""" - attr = { - ATTR_FACES: self.faces, - ATTR_TOTAL_FACES: self.total_faces, - } - - return attr - - def process_faces(self, faces, total): - """Send event with detected faces and store data.""" - run_callback_threadsafe( - self.hass.loop, self.async_process_faces, faces, total).result() - - @callback - def async_process_faces(self, faces, total): - """Send event with detected faces and store data. - - known are a dict in follow format: - [ - { - ATTR_CONFIDENCE: 80, - ATTR_NAME: 'Name', - ATTR_AGE: 12.0, - ATTR_GENDER: 'man', - ATTR_MOTION: 'smile', - ATTR_GLASSES: 'sunglasses' - }, - ] - - This method must be run in the event loop. - """ - # Send events - for face in faces: - if ATTR_CONFIDENCE in face and self.confidence: - if face[ATTR_CONFIDENCE] < self.confidence: - continue - - face.update({ATTR_ENTITY_ID: self.entity_id}) - self.hass.async_add_job( - self.hass.bus.async_fire, EVENT_DETECT_FACE, face - ) - - # Update entity store - self.faces = faces - self.total_faces = total - - class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity): """Representation of the Microsoft Face API entity for identify."""