From f439791f509bf29a62e74625e2dbf7243de22389 Mon Sep 17 00:00:00 2001 From: blaise-muhirwa Date: Wed, 19 Jul 2023 16:57:09 -0700 Subject: [PATCH 1/7] fix post_image_query endpoint --- app/api/endpoints/image_queries.py | 24 ++++---------- app/core/utils.py | 19 +++++++++++ app/schemas/schemas.py | 46 +------------------------- test/api/test_image_queries.py | 52 ++++++++++++------------------ 4 files changed, 46 insertions(+), 95 deletions(-) diff --git a/app/api/endpoints/image_queries.py b/app/api/endpoints/image_queries.py index f3877618..2b58f77e 100644 --- a/app/api/endpoints/image_queries.py +++ b/app/api/endpoints/image_queries.py @@ -3,12 +3,11 @@ from typing import Optional import numpy as np -from fastapi import APIRouter, Depends, Query +from fastapi import APIRouter, Depends, Query, Request from model import ImageQuery from PIL import Image from app.core.utils import get_groundlight_sdk_instance, get_motion_detector_instance, prefixed_ksuid -from app.schemas.schemas import ImageQueryCreate logger = logging.getLogger(__name__) @@ -18,19 +17,13 @@ @router.post("", response_model=ImageQuery) async def post_image_query( - props: ImageQueryCreate = Depends(ImageQueryCreate), + detector_id: str, + wait: float = None, + request: Request = None, gl: Depends = Depends(get_groundlight_sdk_instance), motion_detector: Depends = Depends(get_motion_detector_instance), ): - """ - Submit an image query to the detector. - NOTE: For now motion detection assumes that images are submitted to the - same detector. If the client sends the same image to multiple detectors - we would incorrectly flag no motion detected for the second detector. - """ - image = props.image - detector_id = props.detector_id - wait_time = props.wait + image = await request.body() img = Image.open(BytesIO(image)) img_numpy = np.array(img) @@ -43,19 +36,14 @@ async def post_image_query( ) if motion_detected or iq_response_is_improvable: - image_query = gl.submit_image_query(detector=detector_id, image=image, wait=wait_time) - - # Store the cloud's response so that if the next image has no motion, we will return - # the same response. + image_query = gl.submit_image_query(detector=detector_id, image=image, wait=wait) motion_detector.image_query_response = image_query logger.debug("Motion detected") return image_query logger.debug("No motion detected") - new_image_query = ImageQuery(**motion_detector.image_query_response.dict()) new_image_query.id = prefixed_ksuid(prefix="iqe_") - return new_image_query diff --git a/app/core/utils.py b/app/core/utils.py index 8cca31bb..07ecec94 100644 --- a/app/core/utils.py +++ b/app/core/utils.py @@ -1,5 +1,8 @@ +from io import BytesIO + import ksuid from fastapi import Request +from PIL import Image def get_groundlight_sdk_instance(request: Request): @@ -33,3 +36,19 @@ def prefixed_ksuid(prefix: str = None) -> str: k = ksuid.KsuidMs() out = f"{prefix}{k}" return out + + +def pil_image_to_bytes(img: Image.Image, format: str = "JPEG") -> bytes: + """ + Convert a PIL Image object to raw bytes. + + Args: + img (Image.Image): The PIL Image object. + format (str, optional): The image format. Defaults to "JPEG". + + Returns: + bytes: The raw bytes of the image. + """ + with BytesIO() as buffer: + img.save(buffer, format=format) + return buffer.getvalue() diff --git a/app/schemas/schemas.py b/app/schemas/schemas.py index 790de4bd..b6e13852 100644 --- a/app/schemas/schemas.py +++ b/app/schemas/schemas.py @@ -1,8 +1,6 @@ -import base64 -import binascii from typing import Optional -from pydantic import BaseModel, Field, confloat, validator +from pydantic import BaseModel, Field, confloat class DetectorCreate(BaseModel): @@ -15,45 +13,3 @@ class DetectorCreate(BaseModel): ), ) pipeline_config: Optional[str] = Field(None, description="Pipeline config") - - -class ImageQueryCreate(BaseModel): - """ - NOTE: For the `image` field, types bytes, BytesIO, BufferedReader, Image.Image - and numpy.ndarray are not JSON compatible. For now we are only supporting - str type although the Groundlight SDK accepts all the above. - Reference: https://fastapi.tiangolo.com/tutorial/encoder/ - """ - - detector_id: str = Field(description="Detector ID") - image: str = Field( - description="Image to submit to the detector. The image is expected to be a base64 encoded string." - ) - wait: Optional[float] = Field(None, description="How long to wait for a confident response (seconds)") - - @validator("image") - @classmethod - def validate_image(cls, value): - return cls._sanitize_image_input(image=value) - - @classmethod - def _sanitize_image_input(cls, image: str) -> bytes: - """Sanitizes the image input to be a bytes object. - - Args: - image str: Image input assumed to be a base64 encoded string. - - Raises: - ValueError: In case the image type is not supported. - - Returns: - bytes: Image bytes. - """ - if isinstance(image, str): - # The image is a base64 encoded string, so decode it - try: - return base64.b64decode(image) - except binascii.Error as e: - raise ValueError(f"Invalid base64 string: {e}") - - raise ValueError(f"Unsupported input image type: {type(image)}") diff --git a/test/api/test_image_queries.py b/test/api/test_image_queries.py index 3322d680..7deebff0 100644 --- a/test/api/test_image_queries.py +++ b/test/api/test_image_queries.py @@ -1,50 +1,38 @@ -import base64 -import urllib.parse -from io import BytesIO - import pytest +from groundlight import Groundlight +from model import Detector from PIL import Image -from app.api.api import DETECTORS, IMAGE_QUERIES -from app.api.naming import full_path +from app.core.utils import pil_image_to_bytes from app.main import app from ..conftest import TestClient client = TestClient(app) +# Detector ID associated with the detector with parameters +# name="edge_testing_det", +# query="Is there a dog in the image?", +# confidence_threshold=0.9 DETECTOR_ID = "det_2SagpFUrs83cbMZsap5hZzRjZw4" -@pytest.fixture -def detector_id(): - url = full_path(DETECTORS) + f"/{DETECTOR_ID}" - response = client.get(url).json() +@pytest.fixture(name="gl") +def fixture_gl() -> Groundlight: + """Creates a Groundlight client object""" + return Groundlight(endpoint="http://localhost:6717") - return response["id"] + +@pytest.fixture +def detector(gl: Groundlight) -> Detector: + return gl.get_detector(id=DETECTOR_ID) -def test_post_image_queries(detector_id): +def test_post_image_queries(gl: Groundlight, detector: Detector): """ - NOTE: We need to encode the image as a base64 string since bytes are not - JSON-serializable. + Tests that submitting an image query using the edge server proceeds + without failure. """ image = Image.open("test/assets/dog.jpeg") - byte_array = BytesIO() - image.save(byte_array, format="JPEG") - - image_encoding = base64.b64encode(byte_array.getvalue()).decode() - image_encoding = urllib.parse.quote_plus(image_encoding) - - url = full_path(IMAGE_QUERIES) + f"?detector_id={detector_id}&image={image_encoding}&wait=10" - - response = client.post(url).json() - - assert "id" in response - assert "detector_id" in response - assert "query" in response - assert "created_at" in response - assert "type" in response - assert "result" in response - assert "result_type" in response - assert response["detector_id"] == detector_id + image_bytes = pil_image_to_bytes(img=image) + gl.submit_image_query(detector=detector.id, image=image_bytes, wait=10.0) From 96d6bf05e453e5923c3c177be214c1e1657984f8 Mon Sep 17 00:00:00 2001 From: blaise-muhirwa Date: Thu, 20 Jul 2023 13:58:02 -0700 Subject: [PATCH 2/7] fix tests --- app/api/endpoints/image_queries.py | 50 ++++++------ app/core/motion_detection.py | 9 +++ app/core/utils.py | 2 +- app/schemas/schemas.py | 5 ++ configs/edge.yaml | 3 +- configs/get_config.py | 9 ++- configs/nginx.conf | 2 +- poetry.lock | 122 ++++++++++++++--------------- pyproject.toml | 2 +- test/api/test_motdet.py | 1 + 10 files changed, 112 insertions(+), 93 deletions(-) diff --git a/app/api/endpoints/image_queries.py b/app/api/endpoints/image_queries.py index 2b58f77e..030c6fda 100644 --- a/app/api/endpoints/image_queries.py +++ b/app/api/endpoints/image_queries.py @@ -1,11 +1,11 @@ import logging from io import BytesIO -from typing import Optional import numpy as np -from fastapi import APIRouter, Depends, Query, Request +from fastapi import APIRouter, Depends, HTTPException, Request +from groundlight import Groundlight from model import ImageQuery -from PIL import Image +from PIL import Image, ImageFile from app.core.utils import get_groundlight_sdk_instance, get_motion_detector_instance, prefixed_ksuid @@ -14,6 +14,22 @@ router = APIRouter() +ImageFile.LOAD_TRUNCATED_IMAGES = True + + +def safe_submit_image_query(gl: Groundlight, detector_id: str, image: bytes, wait: float = None) -> ImageQuery: + """ + This ensures that we correctly handle HTTP error status codes. In some cases, for example, + 400 error codes are forwarded as 500 by FastAPI, which is not what we want. + """ + try: + return gl.submit_image_query(detector=detector_id, image=image, wait=wait) + + except Exception as e: + if hasattr(e, "status"): + raise HTTPException(status_code=e.status, detail=str(e)) + raise e + @router.post("", response_model=ImageQuery) async def post_image_query( @@ -27,16 +43,14 @@ async def post_image_query( img = Image.open(BytesIO(image)) img_numpy = np.array(img) + if not motion_detector.is_enabled(): + return safe_submit_image_query(gl=gl, detector_id=detector_id, image=image, wait=wait) + async with motion_detector.lock: motion_detected = await motion_detector.motion_detected(new_img=img_numpy) - iq_response_is_improvable = ( - motion_detector.image_query_response is not None - and motion_detector.image_query_response.result.label == "UNSURE" - ) - - if motion_detected or iq_response_is_improvable: - image_query = gl.submit_image_query(detector=detector_id, image=image, wait=wait) + if motion_detected: + image_query = safe_submit_image_query(gl=gl, detector_id=detector_id, image=image, wait=wait) motion_detector.image_query_response = image_query logger.debug("Motion detected") return image_query @@ -45,19 +59,3 @@ async def post_image_query( new_image_query = ImageQuery(**motion_detector.image_query_response.dict()) new_image_query.id = prefixed_ksuid(prefix="iqe_") return new_image_query - - -@router.get("", response_model=ImageQuery) -async def handle_get_requests( - page: Optional[int] = Query(...), - page_size: Optional[int] = Query(...), - query_id: Optional[str] = Query(...), - gl: Depends = Depends(get_groundlight_sdk_instance), -): - """ - Handles GET requests for image queries endpoint. - """ - if query_id is not None: - return gl.get_image_query(image_query_id=query_id) - - return gl.list_image_queries(page=page, page_size=page_size) diff --git a/app/core/motion_detection.py b/app/core/motion_detection.py index ed6f909a..1405e86d 100644 --- a/app/core/motion_detection.py +++ b/app/core/motion_detection.py @@ -18,6 +18,7 @@ class MotdetParameterSettings(BaseSettings): motdet_val_threshold: int = Field( 50, description="The minimum brightness change for a pixel for it to be considered changed." ) + enabled: bool = Field(False, description="Determines if motion detection is enabled by default.") class Config: env_file = ".env" @@ -38,6 +39,14 @@ def __init__(self, parameters: MotdetParameterSettings): self._previous_image = None self.lock = Lock() self._image_query_response = None + self._motion_detection_enabled = parameters.enabled + + def is_enabled(self) -> bool: + return self._motion_detection_enabled + + def enable(self) -> None: + if not self._motion_detection_enabled: + self._motion_detection_enabled = True @property def image_query_response(self): diff --git a/app/core/utils.py b/app/core/utils.py index 07ecec94..da88dc07 100644 --- a/app/core/utils.py +++ b/app/core/utils.py @@ -40,7 +40,7 @@ def prefixed_ksuid(prefix: str = None) -> str: def pil_image_to_bytes(img: Image.Image, format: str = "JPEG") -> bytes: """ - Convert a PIL Image object to raw bytes. + Convert a PIL Image object to JPEG bytes. Args: img (Image.Image): The PIL Image object. diff --git a/app/schemas/schemas.py b/app/schemas/schemas.py index b6e13852..013f9779 100644 --- a/app/schemas/schemas.py +++ b/app/schemas/schemas.py @@ -13,3 +13,8 @@ class DetectorCreate(BaseModel): ), ) pipeline_config: Optional[str] = Field(None, description="Pipeline config") + + +class ImageQueryCreate(BaseModel): + detector_id: str = Field(description="Detector ID") + wait: float = Field(None, description="How long to wait for a confident response") diff --git a/configs/edge.yaml b/configs/edge.yaml index 4da977be..db85e4d6 100644 --- a/configs/edge.yaml +++ b/configs/edge.yaml @@ -1,5 +1,6 @@ # Configure motion detection parameters motdet: + enabled: false percentage-threshold: 5.0 - val-threshold: 50 + val-threshold: 50 diff --git a/configs/get_config.py b/configs/get_config.py index 26b04d1a..f31c3665 100644 --- a/configs/get_config.py +++ b/configs/get_config.py @@ -6,17 +6,22 @@ def get_edge_config(filename: str) -> dict: config = yaml.safe_load(ymlfile) return config + def set_motion_detection_env_vars(config: dict) -> None: with open(".env", "a") as envfile: percentage_threshold = str(config["motdet"]["percentage-threshold"]) val_threshold = str(config["motdet"]["val-threshold"]) + enabled = str(config["motdet"]["enabled"]) + + envfile.write( + f"MOTDET_PERCENTAGE_THRESHOLD={percentage_threshold}\nMOTDET_VAL_THRESHOLD={val_threshold}\nENABLED={enabled}" + ) - envfile.write(f"MOTDET_PERCENTAGE_THRESHOLD={percentage_threshold}\nMOTDET_VAL_THRESHOLD={val_threshold}") # This gets used in the Dockerfile to generate a .env file # used for motion detection. In the future we might not need # to use a .env file for motion detection parameters, but until -# then, this file is needed. +# then, this file is needed. if __name__ == "__main__": config = get_edge_config(filename="configs/edge.yaml") set_motion_detection_env_vars(config=config) diff --git a/configs/nginx.conf b/configs/nginx.conf index 880c8242..81d9ef81 100644 --- a/configs/nginx.conf +++ b/configs/nginx.conf @@ -14,7 +14,7 @@ http { # 422 - Unprocessable Entity # 404 - Not Found proxy_intercept_errors on; - error_page 422 404 405 = @fallback; + error_page 404 422 405 = @fallback; } location @fallback { diff --git a/poetry.lock b/poetry.lock index 64900aeb..56482b6c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -201,13 +201,13 @@ files = [ [[package]] name = "click" -version = "8.1.5" +version = "8.1.6" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.5-py3-none-any.whl", hash = "sha256:e576aa487d679441d7d30abb87e1b43d24fc53bffb8758443b1a9e1cee504548"}, - {file = "click-8.1.5.tar.gz", hash = "sha256:4be4b1af8d665c6d942909916d31a213a106800c47d0eeba73d34da3cbc11367"}, + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, ] [package.dependencies] @@ -312,13 +312,13 @@ graph = ["objgraph (>=1.7.2)"] [[package]] name = "distlib" -version = "0.3.6" +version = "0.3.7" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] @@ -375,13 +375,13 @@ pyflakes = ">=3.0.0,<3.1.0" [[package]] name = "framegrab" -version = "0.2" +version = "0.2.1" description = "Easily grab frames from cameras or streams" optional = false python-versions = ">=3.7,<4.0" files = [ - {file = "framegrab-0.2-py3-none-any.whl", hash = "sha256:ccc0932e8be54b86bc198b92c61f0de755af38ec6bd31a050ade22ef2a454a70"}, - {file = "framegrab-0.2.tar.gz", hash = "sha256:2fa42064c09abb3683e3331849b40b39ae213c24f9f7f726549b10788bb77efd"}, + {file = "framegrab-0.2.1-py3-none-any.whl", hash = "sha256:89340ec7014e8e117f091810b5e2da7637c5c584b99dd7413930868c9e9c7609"}, + {file = "framegrab-0.2.1.tar.gz", hash = "sha256:df79d5a4607aeaba43a54e11a4cc7f612df8444f28ddbb1ba9bb61c9d0808771"}, ] [package.dependencies] @@ -560,13 +560,13 @@ socks = ["socksio (==1.*)"] [[package]] name = "identify" -version = "2.5.24" +version = "2.5.25" description = "File identification library for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, - {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, + {file = "identify-2.5.25-py2.py3-none-any.whl", hash = "sha256:9df2489842707d431b38ce3410ef8df40da5b10a3e28a3fcac1a42523e956409"}, + {file = "identify-2.5.25.tar.gz", hash = "sha256:db4de0e758c0db8f81996816cd2f3f2f8c5c8d49a7fd02f3b4109aac6fd80e29"}, ] [package.extras] @@ -914,13 +914,13 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.8.1" +version = "3.9.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.8.1-py3-none-any.whl", hash = "sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c"}, - {file = "platformdirs-3.8.1.tar.gz", hash = "sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528"}, + {file = "platformdirs-3.9.1-py3-none-any.whl", hash = "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f"}, + {file = "platformdirs-3.9.1.tar.gz", hash = "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421"}, ] [package.extras] @@ -1153,51 +1153,51 @@ cli = ["click (>=5.0)"] [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] @@ -1455,13 +1455,13 @@ test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "my [[package]] name = "virtualenv" -version = "20.23.1" +version = "20.24.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.23.1-py3-none-any.whl", hash = "sha256:34da10f14fea9be20e0fd7f04aba9732f84e593dac291b757ce42e3368a39419"}, - {file = "virtualenv-20.23.1.tar.gz", hash = "sha256:8ff19a38c1021c742148edc4f81cb43d7f8c6816d2ede2ab72af5b84c749ade1"}, + {file = "virtualenv-20.24.1-py3-none-any.whl", hash = "sha256:01aacf8decd346cf9a865ae85c0cdc7f64c8caa07ff0d8b1dfc1733d10677442"}, + {file = "virtualenv-20.24.1.tar.gz", hash = "sha256:2ef6a237c31629da6442b0bcaa3999748108c7166318d1f55cc9f8d7294e97bd"}, ] [package.dependencies] @@ -1684,4 +1684,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "ef29c59018347aa0b293b4e827de2675f9308f0be4cf9b7aa555bafc63e7b8ae" +content-hash = "adc5b1b2562d02acf3b09cd5af75a00e52fe748f5ea6c71e95de408f46300577" diff --git a/pyproject.toml b/pyproject.toml index aab06698..5fecb087 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ pydantic = "^1.10.2" groundlight = "0.9.0" opencv-python = "^4.7.0.72" pillow = "^9.5.0" -framegrab = "^0.2" +framegrab = "^0.2.1" pyyaml = "^6.0" svix-ksuid = "^0.6.2" diff --git a/test/api/test_motdet.py b/test/api/test_motdet.py index a44c7053..54d7ce02 100644 --- a/test/api/test_motdet.py +++ b/test/api/test_motdet.py @@ -6,6 +6,7 @@ from app.main import app +app.state.motion_detector.enable() client = TestClient(app) # Detector ID associated with the detector with parameters From 72af771a5b068ae876080942e106f0e5acffdaf1 Mon Sep 17 00:00:00 2001 From: blaise-muhirwa Date: Thu, 20 Jul 2023 13:59:30 -0700 Subject: [PATCH 3/7] nits --- app/api/endpoints/image_queries.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/api/endpoints/image_queries.py b/app/api/endpoints/image_queries.py index 030c6fda..574bfe13 100644 --- a/app/api/endpoints/image_queries.py +++ b/app/api/endpoints/image_queries.py @@ -51,6 +51,8 @@ async def post_image_query( if motion_detected: image_query = safe_submit_image_query(gl=gl, detector_id=detector_id, image=image, wait=wait) + # Store the cloud's response so that if the next image has no motion, we will return + # the same response motion_detector.image_query_response = image_query logger.debug("Motion detected") return image_query From 422b4455f85417087de47299d51fc4cae254ab51 Mon Sep 17 00:00:00 2001 From: blaise-muhirwa Date: Thu, 20 Jul 2023 14:46:44 -0700 Subject: [PATCH 4/7] update workflow file --- .github/workflows/pipeline.yaml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 3209c056..6fb770e8 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -23,14 +23,15 @@ jobs: with: python-version: ${{ env.PYTHON_VERSION }} - - name: Install Docker - run: | - sudo apt-get update - sudo apt-get install apt-transport-https ca-certificates curl software-properties-common + - name: Install Docker + run: | + sudo apt update + sudo apt install -y apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - sudo apt-get update - sudo apt-get install docker-ce + sudo apt update + sudo apt install -y docker-ce + - name: Build Docker Image run: docker build --target production-image --tag groundlight-edge . From 4022fc71ff0350cb5ceac7323286a5fcc9bdb915 Mon Sep 17 00:00:00 2001 From: blaise-muhirwa Date: Thu, 20 Jul 2023 14:53:18 -0700 Subject: [PATCH 5/7] update workflow file --- .github/workflows/pipeline.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 6fb770e8..44e1e0a3 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -23,15 +23,15 @@ jobs: with: python-version: ${{ env.PYTHON_VERSION }} - - name: Install Docker - run: | - sudo apt update - sudo apt install -y apt-transport-https ca-certificates curl software-properties-common + - name: Install Docker + run: | + sudo apt-get update + sudo apt-get install apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - sudo apt update - sudo apt install -y docker-ce - + sudo apt-get update + sudo apt-get remove containerd.io + sudo apt-get install docker-ce - name: Build Docker Image run: docker build --target production-image --tag groundlight-edge . From b0c1ccc4e2a28cdc07e9d5e7602d462cf02dbc5b Mon Sep 17 00:00:00 2001 From: blaise-muhirwa Date: Thu, 20 Jul 2023 14:57:26 -0700 Subject: [PATCH 6/7] fix? --- .github/workflows/pipeline.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 44e1e0a3..61eab058 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -26,11 +26,11 @@ jobs: - name: Install Docker run: | sudo apt-get update + sudo apt-get remove docker docker-engine docker.io containerd runc sudo apt-get install apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo apt-get update - sudo apt-get remove containerd.io sudo apt-get install docker-ce - name: Build Docker Image run: docker build --target production-image --tag groundlight-edge . From 1a4ce679807a1611dfb992bb4e34b8b04fccf591 Mon Sep 17 00:00:00 2001 From: blaise Date: Fri, 21 Jul 2023 17:04:03 +0000 Subject: [PATCH 7/7] remove moby-runc package, which conflicts with docker-ce installation --- .github/workflows/pipeline.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 61eab058..22bfee0c 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -26,7 +26,7 @@ jobs: - name: Install Docker run: | sudo apt-get update - sudo apt-get remove docker docker-engine docker.io containerd runc + sudo apt-get remove moby-runc sudo apt-get install apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"