--- /dev/null
+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.
--- /dev/null
+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}"
--- /dev/null
+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",
+ ],
+)