From: Benjamin Braatz Date: Wed, 11 Aug 2021 09:17:56 +0000 (+0200) Subject: Initial commit of skeleton. X-Git-Url: http://git.graph-it.com/?a=commitdiff_plain;h=a8ef5f12f4da30e4a036691616cea6a5c89c151f;p=graphit%2Fcontrolpi-imageproc.git Initial commit of skeleton. --- a8ef5f12f4da30e4a036691616cea6a5c89c151f diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7e2588 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +dist/ +controlpi_imageproc.egg-info/ +venv/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ebb8ac1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2021 Graph-IT GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ba64656 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# ControlPi Plugin for AI Image Processing +This distribution package contains a plugin for the +[ControlPi system](https://docs.graph-it.com/graphit/controlpi), that +processes images from the controlpi-camera plugin with an AI model. diff --git a/controlpi_plugins/imageproc.py b/controlpi_plugins/imageproc.py new file mode 100644 index 0000000..273f4b5 --- /dev/null +++ b/controlpi_plugins/imageproc.py @@ -0,0 +1,78 @@ +import asyncio +import concurrent.futures +import urllib.request +import PIL + +from controlpi import BasePlugin, Message, MessageTemplate + +from typing import Deque, Tuple + + +class ImageProc(BasePlugin): + # The configuration schema contains the properties that can and + # should be set in the configuration JSON file. + CONF_SCHEMA = {'properties': + {'camera': {'type': 'string'}, + 'url': {'type': 'string'}}, + 'required': ['camera', 'url']} + + def process_conf(self) -> None: + """Register plugin as bus client.""" + # The latest available image: + self._current_image = None + # We want to send events with the analysis results: + sends = [MessageTemplate({'event': {'const': 'image analysed'}, + 'image': {'type': 'string'}, + 'category': {'type': 'string'}})] + # We want to receive new image events from the configured camera: + receives = [MessageTemplate({'original sender': + {'const': self.conf['camera']}, + 'event': + {'const': 'new image'}, + 'image': {'type': 'string'}})] + # We register ourselves at the message bus: + self.bus.register(self.name, 'ImageProc', + sends, receives, self._receive) + + async def _receive(self, message: Message) -> None: + # Since the only messages we receive are new image events, + # we can immediately process them by just setting + # self._current_image + self._current_image = message['image'] + + async def run(self) -> None: + """Run image processing.""" + loop = asyncio.get_running_loop() + executor = concurrent.futures.ProcessPoolExecutor() + # Initialise the model: + model = await loop.run_in_executor(executor, Model()) + print("Model initialised.") + while True: + # Wait until an image is there: + while not self._current_image: + await asyncio.sleep(1) + image = self._current_image + url = self.conf['url'] + image + # Give it to the model: + category = await loop.run_in_executor(executor, + model.analyse(url)) + # Send the result on the message bus: + await self.bus.send(Message(self.name, + {'event': 'image analysed', + 'image': image, + 'category': category})) + + +class Model: + def __init__(self): + # Dummy model just sleeps for 10 seconds to simulate heavy work: + time.sleep(10) + + def analyse(self, url): + # Load image from given URL into PIL: + image = PIL.Image.open(urllib.request.urlopen(url)) + # Dummy model just sleeps for 5 seconds to simulate heavy work: + time.sleep(5) + # Return image size as "category": + width, height = image.size + return f"{width}x{height}" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..886691b --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +import setuptools + +with open("README.md", "r") as readme_file: + long_description = readme_file.read() + +setuptools.setup( + name="controlpi-imageproc", + version="0.1.0", + author="Graph-IT GmbH", + author_email="info@graph-it.com", + description="ControlPi Plugin for AI Image Processing", + long_description=long_description, + long_description_content_type="text/markdown", + url="http://docs.graph-it.com/graphit/controlpi-imageproc", + packages=["controlpi_plugins"], + install_requires=[ + "Pillow", + "controlpi @ git+git://git.graph-it.com/graphit/controlpi.git@master", + ], + classifiers=[ + "Programming Language :: Python", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], +)