Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
fd685ba
Adds facebox-detect
robmarkcole May 6, 2018
4e6d676
Drop face_detect
robmarkcole May 7, 2018
e0608ef
Update and rename test_facebox_face_detect.py to test_facebox.py
robmarkcole May 7, 2018
8286f09
Adds matched_faces attr
robmarkcole May 7, 2018
38b9eed
Update test_facebox.py
robmarkcole May 7, 2018
26128d2
Amend get_matched_faces to take faces
robmarkcole May 7, 2018
afdab60
Update test_facebox.py
robmarkcole May 7, 2018
8e89ec3
Update test_facebox.py
robmarkcole May 7, 2018
faacce1
Update test_facebox.py
robmarkcole May 7, 2018
2568d34
Make mock_req.post
robmarkcole May 7, 2018
a3ee098
Update test_facebox.py
robmarkcole May 7, 2018
d9ddc6e
Update test_facebox.py
robmarkcole May 7, 2018
9bfe89a
Rename to facebox
robmarkcole May 8, 2018
714cfac
Merge remote-tracking branch 'home-assistant/dev' into facebox-detect
robmarkcole May 8, 2018
0288897
Merge remote-tracking branch 'origin/facebox-detect' into facebox-detect
robmarkcole May 8, 2018
07d6b27
Working test
robmarkcole May 8, 2018
3ce626d
Update test with matched_faces
robmarkcole May 8, 2018
8d39604
Update hitron_coda.py to fix login for Shaw modems (#14306)
mikedm139 May 8, 2018
1c14d22
Add sensors for BMW electric cars (#14293)
gerard33 May 8, 2018
673a237
Add more homematicip cloud components (#14084)
worm-ee May 8, 2018
c782381
add 2 devices (#14321)
m4dmin May 8, 2018
a012b81
Gogogate2 0.1.1 (#14294)
dlbroadfoot May 8, 2018
8145a8e
Snips: Added slot values for siteId and probability (#14315)
todschmidt May 8, 2018
753419d
Fix Insteon PLM coverage
balloob May 8, 2018
5537585
Add help for conversation/process service (#14323)
stephanerosi May 8, 2018
107cf82
Fix BOM weather '-' value (#14042)
nickw444 May 8, 2018
3afd19b
Make sure zwave nodes/entities enter the registry is proper state. (#…
andrey-git May 8, 2018
89d7896
Improving icloud device tracker (#14078)
evgeniy-khatko May 8, 2018
2e002e1
Adds useful attributes to RainMachine programs and zones (#14087)
bachya May 8, 2018
64b5739
Waze Travel Time: optional inclusive/exclusive filters (#14000)
mario-tux May 8, 2018
146fc59
Bump frontend to 20180509.0
balloob May 9, 2018
c038ede
Ignore NaN values for influxdb (#14347)
amelchio May 9, 2018
013d51e
Add zone 3 for Onkyo media player (#14295)
snikch May 9, 2018
50806d3
Passing test
robmarkcole May 9, 2018
39b7223
Merge remote-tracking branch 'home-assistant/dev' into facebox-detect
robmarkcole May 9, 2018
3be6ae6
Fix lints
robmarkcole May 9, 2018
6960ea1
Fix lints
robmarkcole May 9, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions homeassistant/components/image_processing/facebox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""
Component that will perform facial detection and identification via facebox.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/image_processing.facebox
"""
import base64
import logging

import requests
import voluptuous as vol

from homeassistant.core import split_entity_id
import homeassistant.helpers.config_validation as cv
from homeassistant.components.image_processing import (
PLATFORM_SCHEMA, ImageProcessingFaceEntity, CONF_SOURCE, CONF_ENTITY_ID,
CONF_NAME)
from homeassistant.const import (CONF_IP_ADDRESS, CONF_PORT)

_LOGGER = logging.getLogger(__name__)

CLASSIFIER = 'facebox'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_IP_ADDRESS): cv.string,
vol.Required(CONF_PORT): cv.port,
})


def encode_image(image):
"""base64 encode an image stream."""
base64_img = base64.b64encode(image).decode('ascii')
return {"base64": base64_img}


def get_matched_faces(faces):
"""Return the name and rounded confidence of matched faces."""
return {face['name']: round(face['confidence'], 2)
for face in faces if face['matched']}


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the classifier."""
entities = []
for camera in config[CONF_SOURCE]:
entities.append(FaceClassifyEntity(
config[CONF_IP_ADDRESS],
config[CONF_PORT],
camera[CONF_ENTITY_ID],
camera.get(CONF_NAME)
))
add_devices(entities)


class FaceClassifyEntity(ImageProcessingFaceEntity):
"""Perform a face classification."""

def __init__(self, ip, port, camera_entity, name=None):
"""Init with the API key and model id."""
super().__init__()
self._url = "http://{}:{}/{}/check".format(ip, port, CLASSIFIER)
self._camera = camera_entity
if name:
self._name = name
else:
camera_name = split_entity_id(camera_entity)[1]
self._name = "{} {}".format(
CLASSIFIER, camera_name)
self._matched = {}

def process_image(self, image):
"""Process an image."""
response = {}
try:
response = requests.post(
self._url,
json=encode_image(image),
timeout=9
).json()
except requests.exceptions.ConnectionError:
_LOGGER.error("ConnectionError: Is %s running?", CLASSIFIER)
response['success'] = False

if response['success']:
faces = response['faces']
total = response['facesCount']
self.process_faces(faces, total)
self._matched = get_matched_faces(faces)

else:
self.total_faces = None
self.faces = []
self._matched = {}

@property
def camera_entity(self):
"""Return camera entity id from process pictures."""
return self._camera

@property
def name(self):
"""Return the name of the sensor."""
return self._name

@property
def device_state_attributes(self):
"""Return the classifier attributes."""
return {
'matched_faces': self._matched,
}
73 changes: 73 additions & 0 deletions tests/components/image_processing/test_facebox.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""The tests for the facebox component."""
from unittest.mock import patch

import pytest
import requests_mock

from homeassistant.const import (
ATTR_ENTITY_ID, CONF_IP_ADDRESS, CONF_PORT)
from homeassistant.setup import async_setup_component
import homeassistant.components.image_processing as ip

MOCK_IP = '192.168.0.1'
MOCK_PORT = '8080'

MOCK_JSON = {"facesCount": 1,
"success": True,
"faces": [{'confidence': 0.5812028911604818,
'id': 'john.jpg',
'matched': True,
'name': 'John Lennon',
'rect': {'height': 75,
'left': 63,
'top': 262,
'width': 74}
}]
}

VALID_ENTITY_ID = 'image_processing.facebox_demo_camera'
VALID_CONFIG = {
ip.DOMAIN: {
'platform': 'facebox',
CONF_IP_ADDRESS: MOCK_IP,
CONF_PORT: MOCK_PORT,
ip.CONF_SOURCE: {
ip.CONF_ENTITY_ID: 'camera.demo_camera'}
},
'camera': {
'platform': 'demo'
}
}


@pytest.fixture
def mock_image():
"""Return a mock camera image."""
with patch('homeassistant.components.camera.demo.DemoCamera.camera_image',
return_value=b'Test') as image:
yield image


async def test_setup_platform(hass):
"""Setup platform with one entity."""
await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG)
assert hass.states.get(VALID_ENTITY_ID)


async def test_process_image(hass, mock_image):
"""Test processing of an image."""
await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG)
assert hass.states.get(VALID_ENTITY_ID)

with requests_mock.Mocker() as mock_req:
url = "http://{}:{}/facebox/check".format(MOCK_IP, MOCK_PORT)
mock_req.post(url, json=MOCK_JSON)
data = {ATTR_ENTITY_ID: VALID_ENTITY_ID}
await hass.services.async_call(ip.DOMAIN,
ip.SERVICE_SCAN,
service_data=data)
await hass.async_block_till_done()

state = hass.states.get(VALID_ENTITY_ID)
assert state.state == '1'
assert state.attributes.get('matched_faces') == {'John Lennon': 0.58}