From b2864114a948f4c13e30547e78838d8b281c11f9 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sun, 25 Aug 2019 23:05:42 -0400 Subject: [PATCH 01/15] Add support for doods --- homeassistant/components/doods/__init__.py | 1 + .../components/doods/image_processing.py | 417 ++++++++++++++++++ homeassistant/components/doods/manifest.json | 8 + 3 files changed, 426 insertions(+) create mode 100644 homeassistant/components/doods/__init__.py create mode 100644 homeassistant/components/doods/image_processing.py create mode 100644 homeassistant/components/doods/manifest.json diff --git a/homeassistant/components/doods/__init__.py b/homeassistant/components/doods/__init__.py new file mode 100644 index 00000000000000..b6edb9be87bda0 --- /dev/null +++ b/homeassistant/components/doods/__init__.py @@ -0,0 +1 @@ +"""The doods component.""" diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py new file mode 100644 index 00000000000000..ed8558a2559f56 --- /dev/null +++ b/homeassistant/components/doods/image_processing.py @@ -0,0 +1,417 @@ +"""Support for the DOODS service.""" +import logging +import os +import sys +import requests +import base64 +import time +import voluptuous as vol + +from homeassistant.components.image_processing import ( + CONF_CONFIDENCE, + CONF_ENTITY_ID, + CONF_NAME, + CONF_SOURCE, + PLATFORM_SCHEMA, + ImageProcessingEntity, +) +from homeassistant.const import ( + HTTP_BAD_REQUEST, + HTTP_OK, + HTTP_UNAUTHORIZED, +) +from homeassistant.core import split_entity_id +from homeassistant.helpers import template +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +ATTR_MATCHES = "matches" +ATTR_SUMMARY = "summary" +ATTR_TOTAL_MATCHES = "total_matches" + +CONF_URL = "url" +CONF_AUTH_KEY = "auth_key" +CONF_DETECTOR = "detector" +CONF_LABELS = "labels" +CONF_AREA = "area" +CONF_TOP = "top" +CONF_BOTTOM = "bottom" +CONF_RIGHT = "right" +CONF_LEFT = "left" +CONF_FILE_OUT = "file_out" + +AREA_SCHEMA = vol.Schema( + { + vol.Optional(CONF_BOTTOM, default=1): cv.small_float, + vol.Optional(CONF_LEFT, default=0): cv.small_float, + vol.Optional(CONF_RIGHT, default=1): cv.small_float, + vol.Optional(CONF_TOP, default=0): cv.small_float, + } +) + +LABEL_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_AREA): AREA_SCHEMA, + vol.Optional(CONF_CONFIDENCE): vol.Range(min=0, max=100), + } +) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_URL): cv.string, + vol.Required(CONF_DETECTOR): cv.string, + vol.Optional(CONF_AUTH_KEY, default=""): cv.string, + vol.Optional(CONF_FILE_OUT, default=[]): vol.All(cv.ensure_list, [cv.template]), + vol.Optional(CONF_CONFIDENCE, default=0.0): vol.Range(min=0, max=100), + vol.Optional(CONF_LABELS, default=[]): vol.All( + cv.ensure_list, [vol.Any(cv.string, LABEL_SCHEMA)] + ), + vol.Optional(CONF_AREA): AREA_SCHEMA, + } +) + + +def get_detectors(url, auth_key): + """Check the health and return its id if healthy.""" + kwargs = {} + if auth_key: + kwargs['headers'] = {'doods-auth-key': auth_key} + try: + response = requests.get( + url + "/detectors", + **kwargs + ) + if response.status_code == HTTP_UNAUTHORIZED: + _LOGGER.error("AuthenticationError on %s", CLASSIFIER) + return None + if response.status_code == HTTP_OK: + return response.json() + except requests.exceptions.ConnectionError: + _LOGGER.error("ConnectionError: Is %s running?", CLASSIFIER) + return None + + +def detect(url, auth_key, image, dconfig): + """Post an image to the detector.""" + kwargs = {} + if auth_key: + kwargs['headers'] = {'doods-auth-key': auth_key} + try: + response = requests.post( + url + "/detect", + json={"data": encode_image(image), "detect": dconfig}, + **kwargs + ) + if response.status_code == HTTP_UNAUTHORIZED: + _LOGGER.error("AuthenticationError on %s", CLASSIFIER) + return None + if response.status_code == HTTP_OK: + return response.json() + except requests.exceptions.ConnectionError: + _LOGGER.error("ConnectionError: Is %s running?", CLASSIFIER) + return None + + +def encode_image(image): + """base64 encode an image stream.""" + base64_img = base64.b64encode(image).decode('ascii') + return base64_img + + +def draw_box(draw, box, img_width, img_height, text="", color=(255, 255, 0)): + """Draw bounding box on image.""" + ymin, xmin, ymax, xmax = box + (left, right, top, bottom) = ( + xmin * img_width, + xmax * img_width, + ymin * img_height, + ymax * img_height, + ) + draw.line( + [(left, top), (left, bottom), (right, bottom), (right, top), (left, top)], + width=5, + fill=color, + ) + if text: + draw.text((left, abs(top - 15)), text, fill=color) + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Set up the Doods client.""" + url = config[CONF_URL] + auth_key = config[CONF_AUTH_KEY] + detector_name = config[CONF_DETECTOR] + response = get_detectors(url, auth_key) + if not isinstance(response, dict): + _LOGGER.warning("Could not connect to doods server: %s", url) + return + + detector = {} + for d in response["detectors"]: + if d["name"] == detector_name: + detector = d + break + + if not detector: + _LOGGER.warning( + "Detector %s is not supported by doods server %s", detector_name, url) + return + + entities = [] + for camera in config[CONF_SOURCE]: + entities.append( + Doods( + hass, + camera[CONF_ENTITY_ID], + camera.get(CONF_NAME), + url, + auth_key, + detector, + config, + ) + ) + add_entities(entities) + + +class Doods(ImageProcessingEntity): + """Doods image processing service client.""" + + def __init__( + self, + hass, + camera_entity, + name, + url, + auth_key, + detector, + config, + ): + """Initialize the DOODS entity.""" + self.hass = hass + self._camera_entity = camera_entity + if name: + self._name = name + else: + self._name = "Doods {0}".format(split_entity_id(camera_entity)[1]) + self._url = url + self._auth_key = auth_key + self._file_out = config.get(CONF_FILE_OUT) + + # detector config and aspect ratio + if detector["width"] and detector["height"]: + self._width = detector["width"] + self._height = detector["height"] + self._aspect = self._width / self._height + + # the base confidence + dconfig = {} + confidence = config.get(CONF_CONFIDENCE) + if not confidence: + confidence = 0 + + # handle labels and specific detection areas + labels = config.get(CONF_LABELS) + self._label_areas = {} + for label in labels: + if isinstance(label, dict): + label_name = label.get(CONF_NAME) + if label_name not in detector["labels"] and label_name != "*": + _LOGGER.warning( + "Detector does not support label %s", label_name) + continue + + # Label Confidence + label_confidence = label.get(CONF_CONFIDENCE) + if not label_confidence: + label_confidence = confidence + if label_name not in dconfig or dconfig[label_name] > label_confidence: + dconfig[label_name] = label_confidence + + # Label area + label_area = label.get(CONF_AREA) + self._label_areas[label_name] = [0, 0, 1, 1] + if label_area: + self._label_areas[label_name] = [ + label_area.get(CONF_TOP), + label_area.get(CONF_LEFT), + label_area.get(CONF_BOTTOM), + label_area.get(CONF_RIGHT), + ] + else: + if label not in detector["labels"] and label != "*": + _LOGGER.warning( + "Detector does not support label %s", label) + continue + self._label_areas[label] = [0, 0, 1, 1] + if label not in dconfig or dconfig[label] > confidence: + dconfig[label] = confidence + + if not dconfig: + dconfig["*"] = confidence + + # Handle global detection area + self._area = [0, 0, 1, 1] + area_config = config.get(CONF_AREA) + if area_config: + self._area = [ + area_config.get(CONF_TOP), + area_config.get(CONF_LEFT), + area_config.get(CONF_BOTTOM), + area_config.get(CONF_RIGHT), + ] + + template.attach(hass, self._file_out) + + self._dconfig = dconfig + self._matches = {} + self._total_matches = 0 + self._last_image = None + + @property + def camera_entity(self): + """Return camera entity id from process pictures.""" + return self._camera_entity + + @property + def name(self): + """Return the name of the image processor.""" + return self._name + + @property + def state(self): + """Return the state of the entity.""" + return self._total_matches + + @property + def device_state_attributes(self): + """Return device specific state attributes.""" + return { + ATTR_MATCHES: self._matches, + ATTR_SUMMARY: { + label: len(values) for label, values in self._matches.items() + }, + ATTR_TOTAL_MATCHES: self._total_matches, + } + + def _save_image(self, image, matches, paths): + from PIL import Image, ImageDraw + import io + + img = Image.open(io.BytesIO(bytearray(image))).convert("RGB") + img_width, img_height = img.size + draw = ImageDraw.Draw(img) + + # Draw custom global region/area + if self._area != [0, 0, 1, 1]: + draw_box( + draw, self._area, img_width, img_height, "Detection Area", ( + 0, 255, 255) + ) + + for label, values in matches.items(): + + # Draw custom label regions/areas + if label in self._label_areas and self._label_areas[label] != [ + 0, + 0, + 1, + 1, + ]: + box_label = "{} Detection Area".format(label.capitalize()) + draw_box( + draw, + self._label_areas[label], + img_width, + img_height, + box_label, + (0, 255, 0), + ) + + # Draw detected objects + for instance in values: + box_label = "{0} {1:.1f}%".format(label, instance["score"]) + # Already scaled, use 1 for width and height + draw_box(draw, instance["box"], img_width, + img_height, box_label, (255, 255, 0)) + + for path in paths: + _LOGGER.info("Saving results image to %s", path) + img.save(path) + + def process_image(self, image): + """Process the image.""" + + from PIL import Image + import io + img = Image.open(io.BytesIO(bytearray(image))) + img_width, img_height = img.size + + if self._aspect and abs((img_width/img_height) - self._aspect) > 0.1: + _LOGGER.warn("The image aspect: %s and the detector aspect: %s differ by more than 0.1", + (img_width/img_height), self._aspect) + + # Run detection + start = time.time() + dconfig = {} + response = detect(self._url, self._auth_key, image, self._dconfig) + _LOGGER.info("doods detect: %s response: %s duration: %s", + self._dconfig, response, time.time()-start) + + matches = {} + total_matches = 0 + + if response: + # Was there an error + if "error" in response: + _LOGGER.error(response["error"]) + else: + for d in response["detections"]: + score = d["confidence"] + boxes = [d["top"], d["left"], d["bottom"], d["right"]] + label = d["label"] + + # Exclude unlisted labels + if "*" not in self._dconfig and label not in self._dconfig: + continue + + # Exclude matches outside global area definition + if ( + boxes[0] < self._area[0] * img_height + or boxes[1] < self._area[1] * img_width + or boxes[2] > self._area[2] * img_height + or boxes[3] > self._area[3] * img_width + ): + continue + + # Exclude matches outside label specific area definition + if self._label_areas and ( + boxes[0] < self._label_areas[label][0] * img_height + or boxes[1] < self._label_areas[label][1] * img_width + or boxes[2] > self._label_areas[label][2] * img_height + or boxes[3] > self._label_areas[label][3] * img_width + ): + continue + + if label not in matches.keys(): + matches[label] = [] + matches[label].append( + {"score": float(score), "box": boxes}) + total_matches += 1 + + # Save Images + if total_matches and self._file_out: + paths = [] + for path_template in self._file_out: + if isinstance(path_template, template.Template): + paths.append( + path_template.render( + camera_entity=self._camera_entity) + ) + else: + paths.append(path_template) + self._save_image(image, matches, paths) + + self._matches = matches + self._total_matches = total_matches diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json new file mode 100644 index 00000000000000..39512aff0dd199 --- /dev/null +++ b/homeassistant/components/doods/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "doods", + "name": "DOODS - Distributed Outside Object Detection Service", + "documentation": "https://www.home-assistant.io/components/doods", + "requirements": [], + "dependencies": [], + "codeowners": [] +} \ No newline at end of file From ea702f37d203f9096f125a1f362bcc893b04da90 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 27 Aug 2019 00:07:59 -0400 Subject: [PATCH 02/15] Move connection to external module --- .../components/doods/image_processing.py | 63 +++---------------- homeassistant/components/doods/manifest.json | 4 +- 2 files changed, 11 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index ed8558a2559f56..aaff9a0852fb62 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -6,6 +6,7 @@ import base64 import time import voluptuous as vol +from pydoods import PyDOODS from homeassistant.components.image_processing import ( CONF_CONFIDENCE, @@ -73,53 +74,6 @@ ) -def get_detectors(url, auth_key): - """Check the health and return its id if healthy.""" - kwargs = {} - if auth_key: - kwargs['headers'] = {'doods-auth-key': auth_key} - try: - response = requests.get( - url + "/detectors", - **kwargs - ) - if response.status_code == HTTP_UNAUTHORIZED: - _LOGGER.error("AuthenticationError on %s", CLASSIFIER) - return None - if response.status_code == HTTP_OK: - return response.json() - except requests.exceptions.ConnectionError: - _LOGGER.error("ConnectionError: Is %s running?", CLASSIFIER) - return None - - -def detect(url, auth_key, image, dconfig): - """Post an image to the detector.""" - kwargs = {} - if auth_key: - kwargs['headers'] = {'doods-auth-key': auth_key} - try: - response = requests.post( - url + "/detect", - json={"data": encode_image(image), "detect": dconfig}, - **kwargs - ) - if response.status_code == HTTP_UNAUTHORIZED: - _LOGGER.error("AuthenticationError on %s", CLASSIFIER) - return None - if response.status_code == HTTP_OK: - return response.json() - except requests.exceptions.ConnectionError: - _LOGGER.error("ConnectionError: Is %s running?", CLASSIFIER) - return None - - -def encode_image(image): - """base64 encode an image stream.""" - base64_img = base64.b64encode(image).decode('ascii') - return base64_img - - def draw_box(draw, box, img_width, img_height, text="", color=(255, 255, 0)): """Draw bounding box on image.""" ymin, xmin, ymax, xmax = box @@ -143,7 +97,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): url = config[CONF_URL] auth_key = config[CONF_AUTH_KEY] detector_name = config[CONF_DETECTOR] - response = get_detectors(url, auth_key) + + doods = PyDOODS(url, auth_key) + response = doods.get_detectors() if not isinstance(response, dict): _LOGGER.warning("Could not connect to doods server: %s", url) return @@ -166,8 +122,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hass, camera[CONF_ENTITY_ID], camera.get(CONF_NAME), - url, - auth_key, + doods, detector, config, ) @@ -183,8 +138,7 @@ def __init__( hass, camera_entity, name, - url, - auth_key, + doods, detector, config, ): @@ -195,8 +149,7 @@ def __init__( self._name = name else: self._name = "Doods {0}".format(split_entity_id(camera_entity)[1]) - self._url = url - self._auth_key = auth_key + self._doods = doods self._file_out = config.get(CONF_FILE_OUT) # detector config and aspect ratio @@ -355,7 +308,7 @@ def process_image(self, image): # Run detection start = time.time() dconfig = {} - response = detect(self._url, self._auth_key, image, self._dconfig) + response = self._doods.detect(image, self._dconfig) _LOGGER.info("doods detect: %s response: %s duration: %s", self._dconfig, response, time.time()-start) diff --git a/homeassistant/components/doods/manifest.json b/homeassistant/components/doods/manifest.json index 39512aff0dd199..3e1ce22a230b03 100644 --- a/homeassistant/components/doods/manifest.json +++ b/homeassistant/components/doods/manifest.json @@ -2,7 +2,9 @@ "domain": "doods", "name": "DOODS - Distributed Outside Object Detection Service", "documentation": "https://www.home-assistant.io/components/doods", - "requirements": [], + "requirements": [ + "pydoods==1.0.1" + ], "dependencies": [], "codeowners": [] } \ No newline at end of file From 9d07ba764519dc84a851bac99e2f274a0063cdb3 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 27 Aug 2019 08:58:48 -0400 Subject: [PATCH 03/15] Fix for CI --- .coveragerc | 1 + .../components/doods/image_processing.py | 69 +++++++++---------- requirements_all.txt | 3 + 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/.coveragerc b/.coveragerc index 1d861d69c1dfe1..2bebc4505594f3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -141,6 +141,7 @@ omit = homeassistant/components/dlna_dmr/media_player.py homeassistant/components/dnsip/sensor.py homeassistant/components/dominos/* + homeassistant/components/doods/* homeassistant/components/doorbird/* homeassistant/components/dovado/* homeassistant/components/downloader/* diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index aaff9a0852fb62..d23b35101e2cca 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -16,11 +16,7 @@ PLATFORM_SCHEMA, ImageProcessingEntity, ) -from homeassistant.const import ( - HTTP_BAD_REQUEST, - HTTP_OK, - HTTP_UNAUTHORIZED, -) +from homeassistant.const import HTTP_BAD_REQUEST, HTTP_OK, HTTP_UNAUTHORIZED from homeassistant.core import split_entity_id from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv @@ -112,7 +108,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if not detector: _LOGGER.warning( - "Detector %s is not supported by doods server %s", detector_name, url) + "Detector %s is not supported by doods server %s", detector_name, url + ) return entities = [] @@ -133,15 +130,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class Doods(ImageProcessingEntity): """Doods image processing service client.""" - def __init__( - self, - hass, - camera_entity, - name, - doods, - detector, - config, - ): + def __init__(self, hass, camera_entity, name, doods, detector, config): """Initialize the DOODS entity.""" self.hass = hass self._camera_entity = camera_entity @@ -171,8 +160,7 @@ def __init__( if isinstance(label, dict): label_name = label.get(CONF_NAME) if label_name not in detector["labels"] and label_name != "*": - _LOGGER.warning( - "Detector does not support label %s", label_name) + _LOGGER.warning("Detector does not support label %s", label_name) continue # Label Confidence @@ -194,8 +182,7 @@ def __init__( ] else: if label not in detector["labels"] and label != "*": - _LOGGER.warning( - "Detector does not support label %s", label) + _LOGGER.warning("Detector does not support label %s", label) continue self._label_areas[label] = [0, 0, 1, 1] if label not in dconfig or dconfig[label] > confidence: @@ -259,19 +246,13 @@ def _save_image(self, image, matches, paths): # Draw custom global region/area if self._area != [0, 0, 1, 1]: draw_box( - draw, self._area, img_width, img_height, "Detection Area", ( - 0, 255, 255) + draw, self._area, img_width, img_height, "Detection Area", (0, 255, 255) ) for label, values in matches.items(): # Draw custom label regions/areas - if label in self._label_areas and self._label_areas[label] != [ - 0, - 0, - 1, - 1, - ]: + if label in self._label_areas and self._label_areas[label] != [0, 0, 1, 1]: box_label = "{} Detection Area".format(label.capitalize()) draw_box( draw, @@ -286,8 +267,14 @@ def _save_image(self, image, matches, paths): for instance in values: box_label = "{0} {1:.1f}%".format(label, instance["score"]) # Already scaled, use 1 for width and height - draw_box(draw, instance["box"], img_width, - img_height, box_label, (255, 255, 0)) + draw_box( + draw, + instance["box"], + img_width, + img_height, + box_label, + (255, 255, 0), + ) for path in paths: _LOGGER.info("Saving results image to %s", path) @@ -298,19 +285,27 @@ def process_image(self, image): from PIL import Image import io + img = Image.open(io.BytesIO(bytearray(image))) img_width, img_height = img.size - if self._aspect and abs((img_width/img_height) - self._aspect) > 0.1: - _LOGGER.warn("The image aspect: %s and the detector aspect: %s differ by more than 0.1", - (img_width/img_height), self._aspect) + if self._aspect and abs((img_width / img_height) - self._aspect) > 0.1: + _LOGGER.warn( + "The image aspect: %s and the detector aspect: %s differ by more than 0.1", + (img_width / img_height), + self._aspect, + ) # Run detection start = time.time() dconfig = {} response = self._doods.detect(image, self._dconfig) - _LOGGER.info("doods detect: %s response: %s duration: %s", - self._dconfig, response, time.time()-start) + _LOGGER.info( + "doods detect: %s response: %s duration: %s", + self._dconfig, + response, + time.time() - start, + ) matches = {} total_matches = 0 @@ -349,8 +344,7 @@ def process_image(self, image): if label not in matches.keys(): matches[label] = [] - matches[label].append( - {"score": float(score), "box": boxes}) + matches[label].append({"score": float(score), "box": boxes}) total_matches += 1 # Save Images @@ -360,7 +354,8 @@ def process_image(self, image): if isinstance(path_template, template.Template): paths.append( path_template.render( - camera_entity=self._camera_entity) + camera_entity=self._camera_entity + ) ) else: paths.append(path_template) diff --git a/requirements_all.txt b/requirements_all.txt index e21dca1020f4c9..5f771b942f3a86 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1112,6 +1112,9 @@ pydelijn==0.5.1 # homeassistant.components.zwave pydispatcher==2.0.5 +# homeassistant.components.doods +pydoods==1.0.1 + # homeassistant.components.android_ip_webcam pydroid-ipcam==0.8 From 97adf895838a00b5534c0f488ca9de1328a9f81f Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 27 Aug 2019 09:08:20 -0400 Subject: [PATCH 04/15] Another update for CI --- .../components/doods/image_processing.py | 67 +++++++++---------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index d23b35101e2cca..98db878db128da 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -1,9 +1,5 @@ """Support for the DOODS service.""" import logging -import os -import sys -import requests -import base64 import time import voluptuous as vol from pydoods import PyDOODS @@ -16,7 +12,6 @@ PLATFORM_SCHEMA, ImageProcessingEntity, ) -from homeassistant.const import HTTP_BAD_REQUEST, HTTP_OK, HTTP_UNAUTHORIZED from homeassistant.core import split_entity_id from homeassistant.helpers import template import homeassistant.helpers.config_validation as cv @@ -108,8 +103,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if not detector: _LOGGER.warning( - "Detector %s is not supported by doods server %s", detector_name, url - ) + "Detector %s is not supported by doods server %s", detector_name, url) return entities = [] @@ -130,7 +124,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class Doods(ImageProcessingEntity): """Doods image processing service client.""" - def __init__(self, hass, camera_entity, name, doods, detector, config): + def __init__( + self, + hass, + camera_entity, + name, + doods, + detector, + config, + ): """Initialize the DOODS entity.""" self.hass = hass self._camera_entity = camera_entity @@ -160,7 +162,8 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): if isinstance(label, dict): label_name = label.get(CONF_NAME) if label_name not in detector["labels"] and label_name != "*": - _LOGGER.warning("Detector does not support label %s", label_name) + _LOGGER.warning( + "Detector does not support label %s", label_name) continue # Label Confidence @@ -182,7 +185,8 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): ] else: if label not in detector["labels"] and label != "*": - _LOGGER.warning("Detector does not support label %s", label) + _LOGGER.warning( + "Detector does not support label %s", label) continue self._label_areas[label] = [0, 0, 1, 1] if label not in dconfig or dconfig[label] > confidence: @@ -246,13 +250,19 @@ def _save_image(self, image, matches, paths): # Draw custom global region/area if self._area != [0, 0, 1, 1]: draw_box( - draw, self._area, img_width, img_height, "Detection Area", (0, 255, 255) + draw, self._area, img_width, img_height, "Detection Area", ( + 0, 255, 255) ) for label, values in matches.items(): # Draw custom label regions/areas - if label in self._label_areas and self._label_areas[label] != [0, 0, 1, 1]: + if label in self._label_areas and self._label_areas[label] != [ + 0, + 0, + 1, + 1, + ]: box_label = "{} Detection Area".format(label.capitalize()) draw_box( draw, @@ -267,14 +277,8 @@ def _save_image(self, image, matches, paths): for instance in values: box_label = "{0} {1:.1f}%".format(label, instance["score"]) # Already scaled, use 1 for width and height - draw_box( - draw, - instance["box"], - img_width, - img_height, - box_label, - (255, 255, 0), - ) + draw_box(draw, instance["box"], img_width, + img_height, box_label, (255, 255, 0)) for path in paths: _LOGGER.info("Saving results image to %s", path) @@ -285,27 +289,18 @@ def process_image(self, image): from PIL import Image import io - img = Image.open(io.BytesIO(bytearray(image))) img_width, img_height = img.size if self._aspect and abs((img_width / img_height) - self._aspect) > 0.1: - _LOGGER.warn( - "The image aspect: %s and the detector aspect: %s differ by more than 0.1", - (img_width / img_height), - self._aspect, - ) + _LOGGER.warn("The image aspect: %s and the detector aspect: %s differ by more than 0.1", + (img_width / img_height), self._aspect) # Run detection start = time.time() - dconfig = {} response = self._doods.detect(image, self._dconfig) - _LOGGER.info( - "doods detect: %s response: %s duration: %s", - self._dconfig, - response, - time.time() - start, - ) + _LOGGER.info("doods detect: %s response: %s duration: %s", + self._dconfig, response, time.time() - start) matches = {} total_matches = 0 @@ -344,7 +339,8 @@ def process_image(self, image): if label not in matches.keys(): matches[label] = [] - matches[label].append({"score": float(score), "box": boxes}) + matches[label].append( + {"score": float(score), "box": boxes}) total_matches += 1 # Save Images @@ -354,8 +350,7 @@ def process_image(self, image): if isinstance(path_template, template.Template): paths.append( path_template.render( - camera_entity=self._camera_entity - ) + camera_entity=self._camera_entity) ) else: paths.append(path_template) From 271b68551ec968e2b28487522155e936841337c2 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 27 Aug 2019 12:24:09 -0400 Subject: [PATCH 05/15] Reformatted via black --- .../components/doods/image_processing.py | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 98db878db128da..8ba48eaf8c99ed 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -103,7 +103,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if not detector: _LOGGER.warning( - "Detector %s is not supported by doods server %s", detector_name, url) + "Detector %s is not supported by doods server %s", detector_name, url + ) return entities = [] @@ -124,15 +125,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class Doods(ImageProcessingEntity): """Doods image processing service client.""" - def __init__( - self, - hass, - camera_entity, - name, - doods, - detector, - config, - ): + def __init__(self, hass, camera_entity, name, doods, detector, config): """Initialize the DOODS entity.""" self.hass = hass self._camera_entity = camera_entity @@ -162,8 +155,7 @@ def __init__( if isinstance(label, dict): label_name = label.get(CONF_NAME) if label_name not in detector["labels"] and label_name != "*": - _LOGGER.warning( - "Detector does not support label %s", label_name) + _LOGGER.warning("Detector does not support label %s", label_name) continue # Label Confidence @@ -185,8 +177,7 @@ def __init__( ] else: if label not in detector["labels"] and label != "*": - _LOGGER.warning( - "Detector does not support label %s", label) + _LOGGER.warning("Detector does not support label %s", label) continue self._label_areas[label] = [0, 0, 1, 1] if label not in dconfig or dconfig[label] > confidence: @@ -250,19 +241,13 @@ def _save_image(self, image, matches, paths): # Draw custom global region/area if self._area != [0, 0, 1, 1]: draw_box( - draw, self._area, img_width, img_height, "Detection Area", ( - 0, 255, 255) + draw, self._area, img_width, img_height, "Detection Area", (0, 255, 255) ) for label, values in matches.items(): # Draw custom label regions/areas - if label in self._label_areas and self._label_areas[label] != [ - 0, - 0, - 1, - 1, - ]: + if label in self._label_areas and self._label_areas[label] != [0, 0, 1, 1]: box_label = "{} Detection Area".format(label.capitalize()) draw_box( draw, @@ -277,8 +262,14 @@ def _save_image(self, image, matches, paths): for instance in values: box_label = "{0} {1:.1f}%".format(label, instance["score"]) # Already scaled, use 1 for width and height - draw_box(draw, instance["box"], img_width, - img_height, box_label, (255, 255, 0)) + draw_box( + draw, + instance["box"], + img_width, + img_height, + box_label, + (255, 255, 0), + ) for path in paths: _LOGGER.info("Saving results image to %s", path) @@ -289,18 +280,26 @@ def process_image(self, image): from PIL import Image import io + img = Image.open(io.BytesIO(bytearray(image))) img_width, img_height = img.size if self._aspect and abs((img_width / img_height) - self._aspect) > 0.1: - _LOGGER.warn("The image aspect: %s and the detector aspect: %s differ by more than 0.1", - (img_width / img_height), self._aspect) + _LOGGER.warn( + "The image aspect: %s and the detector aspect: %s differ by more than 0.1", + (img_width / img_height), + self._aspect, + ) # Run detection start = time.time() response = self._doods.detect(image, self._dconfig) - _LOGGER.info("doods detect: %s response: %s duration: %s", - self._dconfig, response, time.time() - start) + _LOGGER.info( + "doods detect: %s response: %s duration: %s", + self._dconfig, + response, + time.time() - start, + ) matches = {} total_matches = 0 @@ -339,8 +338,7 @@ def process_image(self, image): if label not in matches.keys(): matches[label] = [] - matches[label].append( - {"score": float(score), "box": boxes}) + matches[label].append({"score": float(score), "box": boxes}) total_matches += 1 # Save Images @@ -350,7 +348,8 @@ def process_image(self, image): if isinstance(path_template, template.Template): paths.append( path_template.render( - camera_entity=self._camera_entity) + camera_entity=self._camera_entity + ) ) else: paths.append(path_template) From a4ce683e21f6aafc53649972646c5098176e56b7 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 27 Aug 2019 22:07:01 -0400 Subject: [PATCH 06/15] Updated linting stuff --- .../components/doods/image_processing.py | 115 ++++++++++-------- 1 file changed, 61 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 8ba48eaf8c99ed..63fa5389ce74df 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -96,9 +96,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return detector = {} - for d in response["detectors"]: - if d["name"] == detector_name: - detector = d + for server_detector in response["detectors"]: + if server_detector["name"] == detector_name: + detector = server_detector break if not detector: @@ -285,7 +285,7 @@ def process_image(self, image): img_width, img_height = img.size if self._aspect and abs((img_width / img_height) - self._aspect) > 0.1: - _LOGGER.warn( + _LOGGER.warning( "The image aspect: %s and the detector aspect: %s differ by more than 0.1", (img_width / img_height), self._aspect, @@ -301,59 +301,66 @@ def process_image(self, image): time.time() - start, ) + if not response: + self._matches = {} + self._total_matches = 0 + return + matches = {} total_matches = 0 - if response: - # Was there an error - if "error" in response: - _LOGGER.error(response["error"]) - else: - for d in response["detections"]: - score = d["confidence"] - boxes = [d["top"], d["left"], d["bottom"], d["right"]] - label = d["label"] - - # Exclude unlisted labels - if "*" not in self._dconfig and label not in self._dconfig: - continue - - # Exclude matches outside global area definition - if ( - boxes[0] < self._area[0] * img_height - or boxes[1] < self._area[1] * img_width - or boxes[2] > self._area[2] * img_height - or boxes[3] > self._area[3] * img_width - ): - continue - - # Exclude matches outside label specific area definition - if self._label_areas and ( - boxes[0] < self._label_areas[label][0] * img_height - or boxes[1] < self._label_areas[label][1] * img_width - or boxes[2] > self._label_areas[label][2] * img_height - or boxes[3] > self._label_areas[label][3] * img_width - ): - continue - - if label not in matches.keys(): - matches[label] = [] - matches[label].append({"score": float(score), "box": boxes}) - total_matches += 1 - - # Save Images - if total_matches and self._file_out: - paths = [] - for path_template in self._file_out: - if isinstance(path_template, template.Template): - paths.append( - path_template.render( - camera_entity=self._camera_entity - ) - ) - else: - paths.append(path_template) - self._save_image(image, matches, paths) + # Was there an error + if "error" in response: + _LOGGER.error(response["error"]) + else: + for detection in response["detections"]: + score = detection["confidence"] + boxes = [ + detection["top"], + detection["left"], + detection["bottom"], + detection["right"], + ] + label = detection["label"] + + # Exclude unlisted labels + if "*" not in self._dconfig and label not in self._dconfig: + continue + + # Exclude matches outside global area definition + if ( + boxes[0] < self._area[0] * img_height + or boxes[1] < self._area[1] * img_width + or boxes[2] > self._area[2] * img_height + or boxes[3] > self._area[3] * img_width + ): + continue + + # Exclude matches outside label specific area definition + if self._label_areas and ( + boxes[0] < self._label_areas[label][0] * img_height + or boxes[1] < self._label_areas[label][1] * img_width + or boxes[2] > self._label_areas[label][2] * img_height + or boxes[3] > self._label_areas[label][3] * img_width + ): + continue + + if label not in matches.keys(): + matches[label] = [] + matches[label].append({"score": float(score), "box": boxes}) + total_matches += 1 + + # Save Images + if total_matches and self._file_out: + paths = [] + for path_template in self._file_out: + if isinstance(path_template, template.Template): + paths.append( + path_template.render(camera_entity=self._camera_entity) + ) + else: + paths.append(path_template) + self._save_image(image, matches, paths) self._matches = matches self._total_matches = total_matches From 0e52f3b6edf87a090a162f0c8f3403b72f9a5128 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 31 Aug 2019 08:15:51 -0400 Subject: [PATCH 07/15] Updated per code review --- .../components/doods/image_processing.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 63fa5389ce74df..780b96184a8c6c 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -1,7 +1,10 @@ """Support for the DOODS service.""" +import io import logging import time import voluptuous as vol + +from PIL import Image, ImageDraw from pydoods import PyDOODS from homeassistant.components.image_processing import ( @@ -132,11 +135,15 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): if name: self._name = name else: - self._name = "Doods {0}".format(split_entity_id(camera_entity)[1]) + name = split_entity_id(camera_entity)[1] + self._name = f"Doods {name}" self._doods = doods self._file_out = config.get(CONF_FILE_OUT) # detector config and aspect ratio + self._width = None + self._height = None + self._aspect = None if detector["width"] and detector["height"]: self._width = detector["width"] self._height = detector["height"] @@ -145,8 +152,6 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): # the base confidence dconfig = {} confidence = config.get(CONF_CONFIDENCE) - if not confidence: - confidence = 0 # handle labels and specific detection areas labels = config.get(CONF_LABELS) @@ -231,9 +236,6 @@ def device_state_attributes(self): } def _save_image(self, image, matches, paths): - from PIL import Image, ImageDraw - import io - img = Image.open(io.BytesIO(bytearray(image))).convert("RGB") img_width, img_height = img.size draw = ImageDraw.Draw(img) @@ -285,7 +287,7 @@ def process_image(self, image): img_width, img_height = img.size if self._aspect and abs((img_width / img_height) - self._aspect) > 0.1: - _LOGGER.warning( + _LOGGER.debug( "The image aspect: %s and the detector aspect: %s differ by more than 0.1", (img_width / img_height), self._aspect, @@ -345,7 +347,7 @@ def process_image(self, image): ): continue - if label not in matches.keys(): + if label not in matches: matches[label] = [] matches[label].append({"score": float(score), "box": boxes}) total_matches += 1 From e0bcb4053eccc1144fc7e88b09ccd2601716a723 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 31 Aug 2019 08:32:38 -0400 Subject: [PATCH 08/15] Removed none check for something with a default --- homeassistant/components/doods/image_processing.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 780b96184a8c6c..a3e33a574fe991 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -165,8 +165,6 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): # Label Confidence label_confidence = label.get(CONF_CONFIDENCE) - if not label_confidence: - label_confidence = confidence if label_name not in dconfig or dconfig[label_name] > label_confidence: dconfig[label_name] = label_confidence From cbc506b337719c9cec128c9772004ca71544a858 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 31 Aug 2019 09:43:14 -0400 Subject: [PATCH 09/15] Updated config parsing --- .../components/doods/image_processing.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index a3e33a574fe991..6070e16054d7fd 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -2,8 +2,8 @@ import io import logging import time -import voluptuous as vol +import voluptuous as vol from PIL import Image, ImageDraw from pydoods import PyDOODS @@ -49,7 +49,7 @@ { vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_AREA): AREA_SCHEMA, - vol.Optional(CONF_CONFIDENCE): vol.Range(min=0, max=100), + vol.Optional(CONF_CONFIDENCE, default=0.0): vol.Range(min=0, max=100), } ) @@ -138,7 +138,7 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): name = split_entity_id(camera_entity)[1] self._name = f"Doods {name}" self._doods = doods - self._file_out = config.get(CONF_FILE_OUT) + self._file_out = config[CONF_FILE_OUT] # detector config and aspect ratio self._width = None @@ -151,20 +151,20 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): # the base confidence dconfig = {} - confidence = config.get(CONF_CONFIDENCE) + confidence = config[CONF_CONFIDENCE] # handle labels and specific detection areas - labels = config.get(CONF_LABELS) + labels = config[CONF_LABELS] self._label_areas = {} for label in labels: if isinstance(label, dict): - label_name = label.get(CONF_NAME) + label_name = label[CONF_NAME] if label_name not in detector["labels"] and label_name != "*": _LOGGER.warning("Detector does not support label %s", label_name) continue # Label Confidence - label_confidence = label.get(CONF_CONFIDENCE) + label_confidence = label[CONF_CONFIDENCE] if label_name not in dconfig or dconfig[label_name] > label_confidence: dconfig[label_name] = label_confidence @@ -173,10 +173,10 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): self._label_areas[label_name] = [0, 0, 1, 1] if label_area: self._label_areas[label_name] = [ - label_area.get(CONF_TOP), - label_area.get(CONF_LEFT), - label_area.get(CONF_BOTTOM), - label_area.get(CONF_RIGHT), + label_area[CONF_TOP], + label_area[CONF_LEFT], + label_area[CONF_BOTTOM], + label_area[CONF_RIGHT], ] else: if label not in detector["labels"] and label != "*": @@ -194,10 +194,10 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): area_config = config.get(CONF_AREA) if area_config: self._area = [ - area_config.get(CONF_TOP), - area_config.get(CONF_LEFT), - area_config.get(CONF_BOTTOM), - area_config.get(CONF_RIGHT), + area_config[CONF_TOP], + area_config[CONF_LEFT], + area_config[CONF_BOTTOM], + area_config[CONF_RIGHT], ] template.attach(hass, self._file_out) From 6c454ece59e73a3ef0fc793ea3580a0578155f3e Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 31 Aug 2019 09:48:36 -0400 Subject: [PATCH 10/15] Updated if statements, need to disable lint check --- .../components/doods/image_processing.py | 111 +++++++++--------- 1 file changed, 54 insertions(+), 57 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 6070e16054d7fd..93fb0a5a5f47c1 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -301,66 +301,63 @@ def process_image(self, image): time.time() - start, ) - if not response: - self._matches = {} - self._total_matches = 0 - return - matches = {} total_matches = 0 - # Was there an error - if "error" in response: - _LOGGER.error(response["error"]) - else: - for detection in response["detections"]: - score = detection["confidence"] - boxes = [ - detection["top"], - detection["left"], - detection["bottom"], - detection["right"], - ] - label = detection["label"] - - # Exclude unlisted labels - if "*" not in self._dconfig and label not in self._dconfig: - continue - - # Exclude matches outside global area definition - if ( - boxes[0] < self._area[0] * img_height - or boxes[1] < self._area[1] * img_width - or boxes[2] > self._area[2] * img_height - or boxes[3] > self._area[3] * img_width - ): - continue - - # Exclude matches outside label specific area definition - if self._label_areas and ( - boxes[0] < self._label_areas[label][0] * img_height - or boxes[1] < self._label_areas[label][1] * img_width - or boxes[2] > self._label_areas[label][2] * img_height - or boxes[3] > self._label_areas[label][3] * img_width - ): - continue - - if label not in matches: - matches[label] = [] - matches[label].append({"score": float(score), "box": boxes}) - total_matches += 1 - - # Save Images - if total_matches and self._file_out: - paths = [] - for path_template in self._file_out: - if isinstance(path_template, template.Template): - paths.append( - path_template.render(camera_entity=self._camera_entity) - ) - else: - paths.append(path_template) - self._save_image(image, matches, paths) + if response: + if "error" in response: + _LOGGER.error(response["error"]) + else: + for detection in response["detections"]: + score = detection["confidence"] + boxes = [ + detection["top"], + detection["left"], + detection["bottom"], + detection["right"], + ] + label = detection["label"] + + # Exclude unlisted labels + if "*" not in self._dconfig and label not in self._dconfig: + continue + + # Exclude matches outside global area definition + if ( + boxes[0] < self._area[0] * img_height + or boxes[1] < self._area[1] * img_width + or boxes[2] > self._area[2] * img_height + or boxes[3] > self._area[3] * img_width + ): + continue + + # Exclude matches outside label specific area definition + if self._label_areas and ( + boxes[0] < self._label_areas[label][0] * img_height + or boxes[1] < self._label_areas[label][1] * img_width + or boxes[2] > self._label_areas[label][2] * img_height + or boxes[3] > self._label_areas[label][3] * img_width + ): + continue + + if label not in matches: + matches[label] = [] + matches[label].append({"score": float(score), "box": boxes}) + total_matches += 1 + + # Save Images + if total_matches and self._file_out: + paths = [] + for path_template in self._file_out: + if isinstance(path_template, template.Template): + paths.append( + path_template.render( + camera_entity=self._camera_entity + ) + ) + else: + paths.append(path_template) + self._save_image(image, matches, paths) self._matches = matches self._total_matches = total_matches From 3d5abfe42fcb72cc74958c076593a3080dd69a7f Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 31 Aug 2019 10:33:23 -0400 Subject: [PATCH 11/15] Fixed formatting and bug that should make linter happy --- .../components/doods/image_processing.py | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 93fb0a5a5f47c1..9c347b5085d4da 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -248,7 +248,7 @@ def _save_image(self, image, matches, paths): # Draw custom label regions/areas if label in self._label_areas and self._label_areas[label] != [0, 0, 1, 1]: - box_label = "{} Detection Area".format(label.capitalize()) + box_label = f"{label.capitalize()} Detection Area" draw_box( draw, self._label_areas[label], @@ -260,7 +260,7 @@ def _save_image(self, image, matches, paths): # Draw detected objects for instance in values: - box_label = "{0} {1:.1f}%".format(label, instance["score"]) + box_label = f'{label} {instance["score"]:.1f}%' # Already scaled, use 1 for width and height draw_box( draw, @@ -345,19 +345,17 @@ def process_image(self, image): matches[label].append({"score": float(score), "box": boxes}) total_matches += 1 - # Save Images - if total_matches and self._file_out: - paths = [] - for path_template in self._file_out: - if isinstance(path_template, template.Template): - paths.append( - path_template.render( - camera_entity=self._camera_entity - ) - ) - else: - paths.append(path_template) - self._save_image(image, matches, paths) + # Save Images + if total_matches and self._file_out: + paths = [] + for path_template in self._file_out: + if isinstance(path_template, template.Template): + paths.append( + path_template.render(camera_entity=self._camera_entity) + ) + else: + paths.append(path_template) + self._save_image(image, matches, paths) self._matches = matches self._total_matches = total_matches From aee7a8265528ab03823cf2d2997df6bf9d900bea Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 31 Aug 2019 10:44:52 -0400 Subject: [PATCH 12/15] Fixed one more issue with box drawing for areas --- .../components/doods/image_processing.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 9c347b5085d4da..700f34f62351c2 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -324,19 +324,19 @@ def process_image(self, image): # Exclude matches outside global area definition if ( - boxes[0] < self._area[0] * img_height - or boxes[1] < self._area[1] * img_width - or boxes[2] > self._area[2] * img_height - or boxes[3] > self._area[3] * img_width + boxes[0] < self._area[0] + or boxes[1] < self._area[1] + or boxes[2] > self._area[2] + or boxes[3] > self._area[3] ): continue # Exclude matches outside label specific area definition if self._label_areas and ( - boxes[0] < self._label_areas[label][0] * img_height - or boxes[1] < self._label_areas[label][1] * img_width - or boxes[2] > self._label_areas[label][2] * img_height - or boxes[3] > self._label_areas[label][3] * img_width + boxes[0] < self._label_areas[label][0] + or boxes[1] < self._label_areas[label][1] + or boxes[2] > self._label_areas[label][2] + or boxes[3] > self._label_areas[label][3] ): continue From 55393f52bebabdb06ce8a5052d65d3de21cd55f3 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 31 Aug 2019 12:11:50 -0400 Subject: [PATCH 13/15] removed extra imports --- homeassistant/components/doods/image_processing.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 700f34f62351c2..fd2734abb5e987 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -277,10 +277,6 @@ def _save_image(self, image, matches, paths): def process_image(self, image): """Process the image.""" - - from PIL import Image - import io - img = Image.open(io.BytesIO(bytearray(image))) img_width, img_height = img.size From f5a8d97c24254e3e86c9ff93ce1f93fe931914e6 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Sat, 31 Aug 2019 12:45:46 -0400 Subject: [PATCH 14/15] Reworked per suggestion --- .../components/doods/image_processing.py | 103 +++++++++--------- 1 file changed, 53 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index fd2734abb5e987..45912a9d282623 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -300,58 +300,61 @@ def process_image(self, image): matches = {} total_matches = 0 - if response: + if not response or "error" in response: if "error" in response: _LOGGER.error(response["error"]) - else: - for detection in response["detections"]: - score = detection["confidence"] - boxes = [ - detection["top"], - detection["left"], - detection["bottom"], - detection["right"], - ] - label = detection["label"] - - # Exclude unlisted labels - if "*" not in self._dconfig and label not in self._dconfig: - continue - - # Exclude matches outside global area definition - if ( - boxes[0] < self._area[0] - or boxes[1] < self._area[1] - or boxes[2] > self._area[2] - or boxes[3] > self._area[3] - ): - continue - - # Exclude matches outside label specific area definition - if self._label_areas and ( - boxes[0] < self._label_areas[label][0] - or boxes[1] < self._label_areas[label][1] - or boxes[2] > self._label_areas[label][2] - or boxes[3] > self._label_areas[label][3] - ): - continue - - if label not in matches: - matches[label] = [] - matches[label].append({"score": float(score), "box": boxes}) - total_matches += 1 - - # Save Images - if total_matches and self._file_out: - paths = [] - for path_template in self._file_out: - if isinstance(path_template, template.Template): - paths.append( - path_template.render(camera_entity=self._camera_entity) - ) - else: - paths.append(path_template) - self._save_image(image, matches, paths) + self._matches = matches + self._total_matches = total_matches + return + + for detection in response["detections"]: + score = detection["confidence"] + boxes = [ + detection["top"], + detection["left"], + detection["bottom"], + detection["right"], + ] + label = detection["label"] + + # Exclude unlisted labels + if "*" not in self._dconfig and label not in self._dconfig: + continue + + # Exclude matches outside global area definition + if ( + boxes[0] < self._area[0] + or boxes[1] < self._area[1] + or boxes[2] > self._area[2] + or boxes[3] > self._area[3] + ): + continue + + # Exclude matches outside label specific area definition + if self._label_areas and ( + boxes[0] < self._label_areas[label][0] + or boxes[1] < self._label_areas[label][1] + or boxes[2] > self._label_areas[label][2] + or boxes[3] > self._label_areas[label][3] + ): + continue + + if label not in matches: + matches[label] = [] + matches[label].append({"score": float(score), "box": boxes}) + total_matches += 1 + + # Save Images + if total_matches and self._file_out: + paths = [] + for path_template in self._file_out: + if isinstance(path_template, template.Template): + paths.append( + path_template.render(camera_entity=self._camera_entity) + ) + else: + paths.append(path_template) + self._save_image(image, matches, paths) self._matches = matches self._total_matches = total_matches From 801943d338a8a190208e7371425af7ae6de37424 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Tue, 3 Sep 2019 11:57:32 -0400 Subject: [PATCH 15/15] Changed output to debug for informational detection message --- homeassistant/components/doods/image_processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 45912a9d282623..ba44d86c2e4083 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -290,7 +290,7 @@ def process_image(self, image): # Run detection start = time.time() response = self._doods.detect(image, self._dconfig) - _LOGGER.info( + _LOGGER.debug( "doods detect: %s response: %s duration: %s", self._dconfig, response,