From 2ae699b3909a4b3af2c753a36d2773a0f3a71417 Mon Sep 17 00:00:00 2001 From: Robin Cole Date: Fri, 24 Jan 2020 08:22:17 +0000 Subject: [PATCH 1/2] Adds save_image --- .../components/sighthound/image_processing.py | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sighthound/image_processing.py b/homeassistant/components/sighthound/image_processing.py index 175b1edc4c68ed..be02cd63283eb3 100644 --- a/homeassistant/components/sighthound/image_processing.py +++ b/homeassistant/components/sighthound/image_processing.py @@ -1,6 +1,9 @@ """Person detection using Sighthound cloud service.""" +import io import logging +import os +from PIL import Image, ImageDraw import simplehound.core as hound import voluptuous as vol @@ -14,6 +17,7 @@ from homeassistant.const import ATTR_ENTITY_ID, CONF_API_KEY from homeassistant.core import split_entity_id import homeassistant.helpers.config_validation as cv +from homeassistant.util.pil import draw_box _LOGGER = logging.getLogger(__name__) @@ -22,6 +26,7 @@ ATTR_BOUNDING_BOX = "bounding_box" ATTR_PEOPLE = "people" CONF_ACCOUNT_TYPE = "account_type" +CONF_SAVE_FILE_FOLDER = "save_file_folder" DEV = "dev" PROD = "prod" @@ -29,6 +34,7 @@ { vol.Required(CONF_API_KEY): cv.string, vol.Optional(CONF_ACCOUNT_TYPE, default=DEV): vol.In([DEV, PROD]), + vol.Optional(CONF_SAVE_FILE_FOLDER): cv.isdir, } ) @@ -45,10 +51,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Sighthound error %s setup aborted", exc) return + save_file_folder = config.get(CONF_SAVE_FILE_FOLDER) + if save_file_folder: + save_file_folder = os.path.join(save_file_folder, "") # If no trailing / add it + entities = [] for camera in config[CONF_SOURCE]: sighthound = SighthoundEntity( - api, camera[CONF_ENTITY_ID], camera.get(CONF_NAME) + api, camera[CONF_ENTITY_ID], camera.get(CONF_NAME), save_file_folder ) entities.append(sighthound) add_entities(entities) @@ -57,7 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class SighthoundEntity(ImageProcessingEntity): """Create a sighthound entity.""" - def __init__(self, api, camera_entity, name): + def __init__(self, api, camera_entity, name, save_file_folder): """Init.""" self._api = api self._camera = camera_entity @@ -69,6 +79,8 @@ def __init__(self, api, camera_entity, name): self._state = None self._image_width = None self._image_height = None + if save_file_folder: + self._save_file_folder = save_file_folder def process_image(self, image): """Process an image.""" @@ -81,6 +93,8 @@ def process_image(self, image): self._image_height = metadata["image_height"] for person in people: self.fire_person_detected_event(person) + if hasattr(self, "_save_file_folder") and self._state > 0: + self.save_image(image, people, self._save_file_folder) def fire_person_detected_event(self, person): """Send event with detected total_persons.""" @@ -94,6 +108,21 @@ def fire_person_detected_event(self, person): }, ) + def save_image(self, image, people, directory): + """Save a timestamped image with bounding boxes around targets.""" + + img = Image.open(io.BytesIO(bytearray(image))).convert("RGB") + draw = ImageDraw.Draw(img) + + for person in people: + box = hound.bbox_to_tf_style( + person["boundingBox"], self._image_width, self._image_height + ) + draw_box(draw, box, self._image_width, self._image_height) + + latest_save_path = directory + "{}_latest.jpg".format(self._name) + img.save(latest_save_path) + @property def camera_entity(self): """Return camera entity id from process pictures.""" From b31d059f7b59570581588ca623d49d99700dfca5 Mon Sep 17 00:00:00 2001 From: Robin Cole Date: Fri, 14 Feb 2020 07:08:55 +0000 Subject: [PATCH 2/2] Update test_image_processing.py --- .../sighthound/test_image_processing.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/components/sighthound/test_image_processing.py b/tests/components/sighthound/test_image_processing.py index 4548a3a6a3583d..fb82cfd82a4a61 100644 --- a/tests/components/sighthound/test_image_processing.py +++ b/tests/components/sighthound/test_image_processing.py @@ -1,4 +1,5 @@ """Tests for the Sighthound integration.""" +import os from unittest.mock import patch import pytest @@ -10,6 +11,8 @@ from homeassistant.core import callback from homeassistant.setup import async_setup_component +TEST_DIR = os.path.join(os.path.dirname(__file__)) + VALID_CONFIG = { ip.DOMAIN: { "platform": "sighthound", @@ -91,3 +94,18 @@ def capture_person_event(event): state = hass.states.get(VALID_ENTITY_ID) assert state.state == "2" assert len(person_events) == 2 + + +async def test_save_image(hass, mock_image, mock_detections): + """Save a processed image.""" + VALID_CONFIG.update({sh.CONF_SAVE_FILE_FOLDER: TEST_DIR}) + await async_setup_component(hass, ip.DOMAIN, VALID_CONFIG) + assert hass.states.get(VALID_ENTITY_ID) + + data = {ATTR_ENTITY_ID: VALID_ENTITY_ID} + with patch("PIL.Image.Image.save") as mock_save: + 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 == "2" + mock_save.assert_called_with("test.jpg")