[Image_Processing][Breaking Change] Cleanup Base face class add support for microsoft face detect (#5802)
* [Image_Processing] Cleanup Base face class add support for microsoft face detect * fix lint * add unittest for micosoft detect * fix test
This commit is contained in:
parent
3f82ef64a1
commit
881d53339b
9 changed files with 400 additions and 54 deletions
|
@ -4,19 +4,19 @@ Support for the demo image processing.
|
||||||
For more details about this component, please refer to the documentation at
|
For more details about this component, please refer to the documentation at
|
||||||
https://home-assistant.io/components/demo/
|
https://home-assistant.io/components/demo/
|
||||||
"""
|
"""
|
||||||
|
from homeassistant.components.image_processing import ATTR_CONFIDENCE
|
||||||
from homeassistant.components.image_processing.openalpr_local import (
|
from homeassistant.components.image_processing.openalpr_local import (
|
||||||
ImageProcessingAlprEntity)
|
ImageProcessingAlprEntity)
|
||||||
from homeassistant.components.image_processing.microsoft_face_identify import (
|
from homeassistant.components.image_processing.microsoft_face_identify import (
|
||||||
ImageProcessingFaceIdentifyEntity)
|
ImageProcessingFaceEntity, ATTR_NAME, ATTR_AGE, ATTR_GENDER)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Setup the demo image_processing platform."""
|
"""Setup the demo image_processing platform."""
|
||||||
add_devices([
|
add_devices([
|
||||||
DemoImageProcessingAlpr('camera.demo_camera', "Demo Alpr"),
|
DemoImageProcessingAlpr('camera.demo_camera', "Demo Alpr"),
|
||||||
DemoImageProcessingFaceIdentify(
|
DemoImageProcessingFace(
|
||||||
'camera.demo_camera', "Demo Face Identify")
|
'camera.demo_camera', "Demo Face")
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ class DemoImageProcessingAlpr(ImageProcessingAlprEntity):
|
||||||
self.process_plates(demo_data, 1)
|
self.process_plates(demo_data, 1)
|
||||||
|
|
||||||
|
|
||||||
class DemoImageProcessingFaceIdentify(ImageProcessingFaceIdentifyEntity):
|
class DemoImageProcessingFace(ImageProcessingFaceEntity):
|
||||||
"""Demo face identify image processing entity."""
|
"""Demo face identify image processing entity."""
|
||||||
|
|
||||||
def __init__(self, camera_entity, name):
|
def __init__(self, camera_entity, name):
|
||||||
|
@ -84,10 +84,22 @@ class DemoImageProcessingFaceIdentify(ImageProcessingFaceIdentifyEntity):
|
||||||
|
|
||||||
def process_image(self, image):
|
def process_image(self, image):
|
||||||
"""Process image."""
|
"""Process image."""
|
||||||
demo_data = {
|
demo_data = [
|
||||||
'Hans': 98.34,
|
{
|
||||||
'Helena': 82.53,
|
ATTR_CONFIDENCE: 98.34,
|
||||||
'Luna': 62.53,
|
ATTR_NAME: 'Hans',
|
||||||
}
|
ATTR_AGE: 16.0,
|
||||||
|
ATTR_GENDER: 'male',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_NAME: 'Helena',
|
||||||
|
ATTR_AGE: 28.0,
|
||||||
|
ATTR_GENDER: 'female',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ATTR_CONFIDENCE: 62.53,
|
||||||
|
ATTR_NAME: 'Luna',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
self.process_faces(demo_data, 4)
|
self.process_faces(demo_data, 4)
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
"""
|
||||||
|
Component that will help set the microsoft face detect processing.
|
||||||
|
|
||||||
|
For more details about this component, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/image_processing.microsoft_face_detect/
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
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)
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
DEPENDENCIES = ['microsoft_face']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
EVENT_IDENTIFY_FACE = 'detect_face'
|
||||||
|
|
||||||
|
SUPPORTED_ATTRIBUTES = [
|
||||||
|
ATTR_AGE,
|
||||||
|
ATTR_GENDER,
|
||||||
|
ATTR_GLASSES
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF_ATTRIBUTES = 'attributes'
|
||||||
|
DEFAULT_ATTRIBUTES = [ATTR_AGE, ATTR_GENDER]
|
||||||
|
|
||||||
|
|
||||||
|
def validate_attributes(list_attributes):
|
||||||
|
"""Validate face attributes."""
|
||||||
|
for attr in list_attributes:
|
||||||
|
if attr not in SUPPORTED_ATTRIBUTES:
|
||||||
|
raise vol.Invalid("Invalid attribtue {0}".format(attr))
|
||||||
|
return list_attributes
|
||||||
|
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Optional(CONF_ATTRIBUTES, default=DEFAULT_ATTRIBUTES):
|
||||||
|
vol.All(cv.ensure_list, validate_attributes),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
|
"""Set up the microsoft face detection platform."""
|
||||||
|
api = hass.data[DATA_MICROSOFT_FACE]
|
||||||
|
attributes = config[CONF_ATTRIBUTES]
|
||||||
|
|
||||||
|
entities = []
|
||||||
|
for camera in config[CONF_SOURCE]:
|
||||||
|
entities.append(MicrosoftFaceDetectEntity(
|
||||||
|
camera[CONF_ENTITY_ID], api, attributes, camera.get(CONF_NAME)
|
||||||
|
))
|
||||||
|
|
||||||
|
yield from async_add_devices(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity):
|
||||||
|
"""Microsoft face api entity for identify."""
|
||||||
|
|
||||||
|
def __init__(self, camera_entity, api, attributes, name=None):
|
||||||
|
"""Initialize openalpr local api."""
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._api = api
|
||||||
|
self._camera = camera_entity
|
||||||
|
self._attributes = attributes
|
||||||
|
|
||||||
|
if name:
|
||||||
|
self._name = name
|
||||||
|
else:
|
||||||
|
self._name = "MicrosoftFace {0}".format(
|
||||||
|
split_entity_id(camera_entity)[1])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def camera_entity(self):
|
||||||
|
"""Return camera entity id from process pictures."""
|
||||||
|
return self._camera
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the entity."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_process_image(self, image):
|
||||||
|
"""Process image.
|
||||||
|
|
||||||
|
This method is a coroutine.
|
||||||
|
"""
|
||||||
|
face_data = None
|
||||||
|
try:
|
||||||
|
face_data = yield from self._api.call_api(
|
||||||
|
'post', 'detect', image, binary=True,
|
||||||
|
params={'returnFaceAttributes': ",".join(self._attributes)})
|
||||||
|
|
||||||
|
except HomeAssistantError as err:
|
||||||
|
_LOGGER.error("Can't process image on microsoft face: %s", err)
|
||||||
|
return
|
||||||
|
|
||||||
|
if face_data is None or len(face_data) < 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
faces = []
|
||||||
|
for face in face_data:
|
||||||
|
face_attr = {}
|
||||||
|
for attr in self._attributes:
|
||||||
|
if attr in face['faceAttributes']:
|
||||||
|
face_attr[attr] = face['faceAttributes'][attr]
|
||||||
|
|
||||||
|
if face_attr:
|
||||||
|
faces.append(face_attr)
|
||||||
|
|
||||||
|
self.async_process_faces(faces, len(face_data))
|
|
@ -23,11 +23,16 @@ DEPENDENCIES = ['microsoft_face']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
EVENT_IDENTIFY_FACE = 'identify_face'
|
EVENT_DETECT_FACE = 'image_processing.detect_face'
|
||||||
|
|
||||||
ATTR_NAME = 'name'
|
ATTR_NAME = 'name'
|
||||||
ATTR_TOTAL_FACES = 'total_faces'
|
ATTR_TOTAL_FACES = 'total_faces'
|
||||||
ATTR_KNOWN_FACES = 'known_faces'
|
ATTR_AGE = 'age'
|
||||||
|
ATTR_GENDER = 'gender'
|
||||||
|
ATTR_MOTION = 'motion'
|
||||||
|
ATTR_GLASSES = 'glasses'
|
||||||
|
ATTR_FACES = 'faces'
|
||||||
|
|
||||||
CONF_GROUP = 'group'
|
CONF_GROUP = 'group'
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
@ -52,71 +57,90 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
yield from async_add_devices(entities)
|
yield from async_add_devices(entities)
|
||||||
|
|
||||||
|
|
||||||
class ImageProcessingFaceIdentifyEntity(ImageProcessingEntity):
|
class ImageProcessingFaceEntity(ImageProcessingEntity):
|
||||||
"""Base entity class for face identify/verify image processing."""
|
"""Base entity class for face image processing."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initialize base face identify/verify entity."""
|
"""Initialize base face identify/verify entity."""
|
||||||
self.known_faces = {} # last scan data
|
self.faces = [] # last scan data
|
||||||
self.total_faces = 0 # face count
|
self.total_faces = 0 # face count
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the entity."""
|
"""Return the state of the entity."""
|
||||||
confidence = 0
|
confidence = 0
|
||||||
face_name = STATE_UNKNOWN
|
state = STATE_UNKNOWN
|
||||||
|
|
||||||
# search high verify face
|
# no confidence support
|
||||||
for i_name, i_co in self.known_faces.items():
|
if not self.confidence:
|
||||||
if i_co > confidence:
|
return self.total_faces
|
||||||
confidence = i_co
|
|
||||||
face_name = i_name
|
# search high confidence
|
||||||
return face_name
|
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
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
"""Return device specific state attributes."""
|
"""Return device specific state attributes."""
|
||||||
attr = {
|
attr = {
|
||||||
ATTR_KNOWN_FACES: self.known_faces,
|
ATTR_FACES: self.faces,
|
||||||
ATTR_TOTAL_FACES: self.total_faces,
|
ATTR_TOTAL_FACES: self.total_faces,
|
||||||
}
|
}
|
||||||
|
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
def process_faces(self, known, total):
|
def process_faces(self, faces, total):
|
||||||
"""Send event with detected faces and store data."""
|
"""Send event with detected faces and store data."""
|
||||||
run_callback_threadsafe(
|
run_callback_threadsafe(
|
||||||
self.hass.loop, self.async_process_faces, known, total
|
self.hass.loop, self.async_process_faces, faces, total
|
||||||
).result()
|
).result()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_process_faces(self, known, total):
|
def async_process_faces(self, faces, total):
|
||||||
"""Send event with detected faces and store data.
|
"""Send event with detected faces and store data.
|
||||||
|
|
||||||
known are a dict in follow format:
|
known are a dict in follow format:
|
||||||
{ 'name': confidence }
|
[
|
||||||
|
{
|
||||||
|
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.
|
This method must be run in the event loop.
|
||||||
"""
|
"""
|
||||||
detect = {name: confidence for name, confidence in known.items()
|
|
||||||
if confidence >= self.confidence}
|
|
||||||
|
|
||||||
# send events
|
# send events
|
||||||
for name, confidence in detect.items():
|
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.async_add_job(
|
||||||
self.hass.bus.async_fire, EVENT_IDENTIFY_FACE, {
|
self.hass.bus.async_fire, EVENT_DETECT_FACE, face
|
||||||
ATTR_NAME: name,
|
|
||||||
ATTR_ENTITY_ID: self.entity_id,
|
|
||||||
ATTR_CONFIDENCE: confidence,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# update entity store
|
# update entity store
|
||||||
self.known_faces = detect
|
self.faces = faces
|
||||||
self.total_faces = total
|
self.total_faces = total
|
||||||
|
|
||||||
|
|
||||||
class MicrosoftFaceIdentifyEntity(ImageProcessingFaceIdentifyEntity):
|
class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity):
|
||||||
"""Microsoft face api entity for identify."""
|
"""Microsoft face api entity for identify."""
|
||||||
|
|
||||||
def __init__(self, camera_entity, api, face_group, confidence, name=None):
|
def __init__(self, camera_entity, api, face_group, confidence, name=None):
|
||||||
|
@ -173,7 +197,7 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceIdentifyEntity):
|
||||||
return
|
return
|
||||||
|
|
||||||
# parse data
|
# parse data
|
||||||
knwon_faces = {}
|
knwon_faces = []
|
||||||
total = 0
|
total = 0
|
||||||
for face in detect:
|
for face in detect:
|
||||||
total += 1
|
total += 1
|
||||||
|
@ -187,7 +211,10 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceIdentifyEntity):
|
||||||
name = s_name
|
name = s_name
|
||||||
break
|
break
|
||||||
|
|
||||||
knwon_faces[name] = data['confidence'] * 100
|
knwon_faces.append({
|
||||||
|
ATTR_NAME: name,
|
||||||
|
ATTR_CONFIDENCE: data['confidence'] * 100,
|
||||||
|
})
|
||||||
|
|
||||||
# process data
|
# process data
|
||||||
self.async_process_faces(knwon_faces, total)
|
self.async_process_faces(knwon_faces, total)
|
||||||
|
|
|
@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
RE_ALPR_PLATE = re.compile(r"^plate\d*:")
|
RE_ALPR_PLATE = re.compile(r"^plate\d*:")
|
||||||
RE_ALPR_RESULT = re.compile(r"- (\w*)\s*confidence: (\d*.\d*)")
|
RE_ALPR_RESULT = re.compile(r"- (\w*)\s*confidence: (\d*.\d*)")
|
||||||
|
|
||||||
EVENT_FOUND_PLATE = 'found_plate'
|
EVENT_FOUND_PLATE = 'image_processing.found_plate'
|
||||||
|
|
||||||
ATTR_PLATE = 'plate'
|
ATTR_PLATE = 'plate'
|
||||||
ATTR_PLATES = 'plates'
|
ATTR_PLATES = 'plates'
|
||||||
|
|
|
@ -110,7 +110,7 @@ class TestImageProcessing(object):
|
||||||
|
|
||||||
|
|
||||||
class TestImageProcessingAlpr(object):
|
class TestImageProcessingAlpr(object):
|
||||||
"""Test class for image processing."""
|
"""Test class for alpr image processing."""
|
||||||
|
|
||||||
def setup_method(self):
|
def setup_method(self):
|
||||||
"""Setup things to be run when tests are started."""
|
"""Setup things to be run when tests are started."""
|
||||||
|
@ -142,7 +142,7 @@ class TestImageProcessingAlpr(object):
|
||||||
"""Mock event."""
|
"""Mock event."""
|
||||||
self.alpr_events.append(event)
|
self.alpr_events.append(event)
|
||||||
|
|
||||||
self.hass.bus.listen('found_plate', mock_alpr_event)
|
self.hass.bus.listen('image_processing.found_plate', mock_alpr_event)
|
||||||
|
|
||||||
def teardown_method(self):
|
def teardown_method(self):
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
|
@ -211,8 +211,8 @@ class TestImageProcessingAlpr(object):
|
||||||
assert event_data[0]['entity_id'] == 'image_processing.demo_alpr'
|
assert event_data[0]['entity_id'] == 'image_processing.demo_alpr'
|
||||||
|
|
||||||
|
|
||||||
class TestImageProcessingFaceIdentify(object):
|
class TestImageProcessingFace(object):
|
||||||
"""Test class for image processing."""
|
"""Test class for face image processing."""
|
||||||
|
|
||||||
def setup_method(self):
|
def setup_method(self):
|
||||||
"""Setup things to be run when tests are started."""
|
"""Setup things to be run when tests are started."""
|
||||||
|
@ -228,7 +228,7 @@ class TestImageProcessingFaceIdentify(object):
|
||||||
}
|
}
|
||||||
|
|
||||||
with patch('homeassistant.components.image_processing.demo.'
|
with patch('homeassistant.components.image_processing.demo.'
|
||||||
'DemoImageProcessingFaceIdentify.should_poll',
|
'DemoImageProcessingFace.should_poll',
|
||||||
new_callable=PropertyMock(return_value=False)):
|
new_callable=PropertyMock(return_value=False)):
|
||||||
setup_component(self.hass, ip.DOMAIN, config)
|
setup_component(self.hass, ip.DOMAIN, config)
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ class TestImageProcessingFaceIdentify(object):
|
||||||
"""Mock event."""
|
"""Mock event."""
|
||||||
self.face_events.append(event)
|
self.face_events.append(event)
|
||||||
|
|
||||||
self.hass.bus.listen('identify_face', mock_face_event)
|
self.hass.bus.listen('image_processing.detect_face', mock_face_event)
|
||||||
|
|
||||||
def teardown_method(self):
|
def teardown_method(self):
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
|
@ -254,10 +254,10 @@ class TestImageProcessingFaceIdentify(object):
|
||||||
"""Setup and scan a picture and test faces from event."""
|
"""Setup and scan a picture and test faces from event."""
|
||||||
aioclient_mock.get(self.url, content=b'image')
|
aioclient_mock.get(self.url, content=b'image')
|
||||||
|
|
||||||
ip.scan(self.hass, entity_id='image_processing.demo_face_identify')
|
ip.scan(self.hass, entity_id='image_processing.demo_face')
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
|
|
||||||
state = self.hass.states.get('image_processing.demo_face_identify')
|
state = self.hass.states.get('image_processing.demo_face')
|
||||||
|
|
||||||
assert len(self.face_events) == 2
|
assert len(self.face_events) == 2
|
||||||
assert state.state == 'Hans'
|
assert state.state == 'Hans'
|
||||||
|
@ -268,5 +268,31 @@ class TestImageProcessingFaceIdentify(object):
|
||||||
assert len(event_data) == 1
|
assert len(event_data) == 1
|
||||||
assert event_data[0]['name'] == 'Hans'
|
assert event_data[0]['name'] == 'Hans'
|
||||||
assert event_data[0]['confidence'] == 98.34
|
assert event_data[0]['confidence'] == 98.34
|
||||||
|
assert event_data[0]['gender'] == 'male'
|
||||||
assert event_data[0]['entity_id'] == \
|
assert event_data[0]['entity_id'] == \
|
||||||
'image_processing.demo_face_identify'
|
'image_processing.demo_face'
|
||||||
|
|
||||||
|
@patch('homeassistant.components.image_processing.demo.'
|
||||||
|
'DemoImageProcessingFace.confidence',
|
||||||
|
new_callable=PropertyMock(return_value=None))
|
||||||
|
def test_face_event_call_no_confidence(self, mock_confi, aioclient_mock):
|
||||||
|
"""Setup and scan a picture and test faces from event."""
|
||||||
|
aioclient_mock.get(self.url, content=b'image')
|
||||||
|
|
||||||
|
ip.scan(self.hass, entity_id='image_processing.demo_face')
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
state = self.hass.states.get('image_processing.demo_face')
|
||||||
|
|
||||||
|
assert len(self.face_events) == 3
|
||||||
|
assert state.state == '4'
|
||||||
|
assert state.attributes['total_faces'] == 4
|
||||||
|
|
||||||
|
event_data = [event.data for event in self.face_events if
|
||||||
|
event.data.get('name') == 'Hans']
|
||||||
|
assert len(event_data) == 1
|
||||||
|
assert event_data[0]['name'] == 'Hans'
|
||||||
|
assert event_data[0]['confidence'] == 98.34
|
||||||
|
assert event_data[0]['gender'] == 'male'
|
||||||
|
assert event_data[0]['entity_id'] == \
|
||||||
|
'image_processing.demo_face'
|
||||||
|
|
159
tests/components/image_processing/test_microsoft_face_detect.py
Normal file
159
tests/components/image_processing/test_microsoft_face_detect.py
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
"""The tests for the microsoft face detect platform."""
|
||||||
|
from unittest.mock import patch, PropertyMock
|
||||||
|
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.const import ATTR_ENTITY_PICTURE
|
||||||
|
from homeassistant.bootstrap import setup_component
|
||||||
|
import homeassistant.components.image_processing as ip
|
||||||
|
import homeassistant.components.microsoft_face as mf
|
||||||
|
|
||||||
|
from tests.common import (
|
||||||
|
get_test_home_assistant, assert_setup_component, load_fixture, mock_coro)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMicrosoftFaceDetectSetup(object):
|
||||||
|
"""Test class for image processing."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
"""Setup things to be run when tests are started."""
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
def teardown_method(self):
|
||||||
|
"""Stop everything that was started."""
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
@patch('homeassistant.components.microsoft_face.'
|
||||||
|
'MicrosoftFace.update_store', return_value=mock_coro()())
|
||||||
|
def test_setup_platform(self, store_mock):
|
||||||
|
"""Setup platform with one entity."""
|
||||||
|
config = {
|
||||||
|
ip.DOMAIN: {
|
||||||
|
'platform': 'microsoft_face_detect',
|
||||||
|
'source': {
|
||||||
|
'entity_id': 'camera.demo_camera'
|
||||||
|
},
|
||||||
|
'attributes': ['age', 'gender'],
|
||||||
|
},
|
||||||
|
'camera': {
|
||||||
|
'platform': 'demo'
|
||||||
|
},
|
||||||
|
mf.DOMAIN: {
|
||||||
|
'api_key': '12345678abcdef6',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with assert_setup_component(1, ip.DOMAIN):
|
||||||
|
setup_component(self.hass, ip.DOMAIN, config)
|
||||||
|
|
||||||
|
assert self.hass.states.get(
|
||||||
|
'image_processing.microsoftface_demo_camera')
|
||||||
|
|
||||||
|
@patch('homeassistant.components.microsoft_face.'
|
||||||
|
'MicrosoftFace.update_store', return_value=mock_coro()())
|
||||||
|
def test_setup_platform_name(self, store_mock):
|
||||||
|
"""Setup platform with one entity and set name."""
|
||||||
|
config = {
|
||||||
|
ip.DOMAIN: {
|
||||||
|
'platform': 'microsoft_face_detect',
|
||||||
|
'source': {
|
||||||
|
'entity_id': 'camera.demo_camera',
|
||||||
|
'name': 'test local'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'camera': {
|
||||||
|
'platform': 'demo'
|
||||||
|
},
|
||||||
|
mf.DOMAIN: {
|
||||||
|
'api_key': '12345678abcdef6',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with assert_setup_component(1, ip.DOMAIN):
|
||||||
|
setup_component(self.hass, ip.DOMAIN, config)
|
||||||
|
|
||||||
|
assert self.hass.states.get('image_processing.test_local')
|
||||||
|
|
||||||
|
|
||||||
|
class TestMicrosoftFaceDetect(object):
|
||||||
|
"""Test class for image processing."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
"""Setup things to be run when tests are started."""
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
self.config = {
|
||||||
|
ip.DOMAIN: {
|
||||||
|
'platform': 'microsoft_face_detect',
|
||||||
|
'source': {
|
||||||
|
'entity_id': 'camera.demo_camera',
|
||||||
|
'name': 'test local'
|
||||||
|
},
|
||||||
|
'attributes': ['age', 'gender'],
|
||||||
|
},
|
||||||
|
'camera': {
|
||||||
|
'platform': 'demo'
|
||||||
|
},
|
||||||
|
mf.DOMAIN: {
|
||||||
|
'api_key': '12345678abcdef6',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def teardown_method(self):
|
||||||
|
"""Stop everything that was started."""
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
@patch('homeassistant.components.image_processing.microsoft_face_detect.'
|
||||||
|
'MicrosoftFaceDetectEntity.should_poll',
|
||||||
|
new_callable=PropertyMock(return_value=False))
|
||||||
|
def test_ms_detect_process_image(self, poll_mock, aioclient_mock):
|
||||||
|
"""Setup and scan a picture and test plates from event."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
mf.FACE_API_URL.format("persongroups"),
|
||||||
|
text=load_fixture('microsoft_face_persongroups.json')
|
||||||
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
mf.FACE_API_URL.format("persongroups/test_group1/persons"),
|
||||||
|
text=load_fixture('microsoft_face_persons.json')
|
||||||
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
mf.FACE_API_URL.format("persongroups/test_group2/persons"),
|
||||||
|
text=load_fixture('microsoft_face_persons.json')
|
||||||
|
)
|
||||||
|
|
||||||
|
setup_component(self.hass, ip.DOMAIN, self.config)
|
||||||
|
|
||||||
|
state = self.hass.states.get('camera.demo_camera')
|
||||||
|
url = "{0}{1}".format(
|
||||||
|
self.hass.config.api.base_url,
|
||||||
|
state.attributes.get(ATTR_ENTITY_PICTURE))
|
||||||
|
|
||||||
|
face_events = []
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def mock_face_event(event):
|
||||||
|
"""Mock event."""
|
||||||
|
face_events.append(event)
|
||||||
|
|
||||||
|
self.hass.bus.listen('image_processing.detect_face', mock_face_event)
|
||||||
|
|
||||||
|
aioclient_mock.get(url, content=b'image')
|
||||||
|
|
||||||
|
aioclient_mock.post(
|
||||||
|
mf.FACE_API_URL.format("detect"),
|
||||||
|
text=load_fixture('microsoft_face_detect.json'),
|
||||||
|
params={'returnFaceAttributes': "age,gender"}
|
||||||
|
)
|
||||||
|
|
||||||
|
ip.scan(self.hass, entity_id='image_processing.test_local')
|
||||||
|
self.hass.block_till_done()
|
||||||
|
|
||||||
|
state = self.hass.states.get('image_processing.test_local')
|
||||||
|
|
||||||
|
assert len(face_events) == 1
|
||||||
|
assert state.attributes.get('total_faces') == 1
|
||||||
|
assert state.state == '1'
|
||||||
|
|
||||||
|
assert face_events[0].data['age'] == 71.0
|
||||||
|
assert face_events[0].data['gender'] == 'male'
|
||||||
|
assert face_events[0].data['entity_id'] == \
|
||||||
|
'image_processing.test_local'
|
|
@ -106,7 +106,7 @@ class TestMicrosoftFaceIdentify(object):
|
||||||
@patch('homeassistant.components.image_processing.microsoft_face_identify.'
|
@patch('homeassistant.components.image_processing.microsoft_face_identify.'
|
||||||
'MicrosoftFaceIdentifyEntity.should_poll',
|
'MicrosoftFaceIdentifyEntity.should_poll',
|
||||||
new_callable=PropertyMock(return_value=False))
|
new_callable=PropertyMock(return_value=False))
|
||||||
def test_openalpr_process_image(self, poll_mock, aioclient_mock):
|
def test_ms_identify_process_image(self, poll_mock, aioclient_mock):
|
||||||
"""Setup and scan a picture and test plates from event."""
|
"""Setup and scan a picture and test plates from event."""
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
mf.FACE_API_URL.format("persongroups"),
|
mf.FACE_API_URL.format("persongroups"),
|
||||||
|
@ -135,7 +135,7 @@ class TestMicrosoftFaceIdentify(object):
|
||||||
"""Mock event."""
|
"""Mock event."""
|
||||||
face_events.append(event)
|
face_events.append(event)
|
||||||
|
|
||||||
self.hass.bus.listen('identify_face', mock_face_event)
|
self.hass.bus.listen('image_processing.detect_face', mock_face_event)
|
||||||
|
|
||||||
aioclient_mock.get(url, content=b'image')
|
aioclient_mock.get(url, content=b'image')
|
||||||
|
|
||||||
|
|
|
@ -143,7 +143,7 @@ class TestOpenAlprCloud(object):
|
||||||
"""Mock event."""
|
"""Mock event."""
|
||||||
self.alpr_events.append(event)
|
self.alpr_events.append(event)
|
||||||
|
|
||||||
self.hass.bus.listen('found_plate', mock_alpr_event)
|
self.hass.bus.listen('image_processing.found_plate', mock_alpr_event)
|
||||||
|
|
||||||
self.params = {
|
self.params = {
|
||||||
'secret_key': "sk_abcxyz123456",
|
'secret_key': "sk_abcxyz123456",
|
||||||
|
|
|
@ -134,7 +134,7 @@ class TestOpenAlprLocal(object):
|
||||||
"""Mock event."""
|
"""Mock event."""
|
||||||
self.alpr_events.append(event)
|
self.alpr_events.append(event)
|
||||||
|
|
||||||
self.hass.bus.listen('found_plate', mock_alpr_event)
|
self.hass.bus.listen('image_processing.found_plate', mock_alpr_event)
|
||||||
|
|
||||||
def teardown_method(self):
|
def teardown_method(self):
|
||||||
"""Stop everything that was started."""
|
"""Stop everything that was started."""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue