Skip to content

Commit a3dc372

Browse files
authored
Image detection example - read from folder (#6)
1 parent b4bc5f1 commit a3dc372

File tree

22 files changed

+281
-61
lines changed

22 files changed

+281
-61
lines changed

examples/datalogger/app.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
display-name: Data logger
22
description: A simple data logger from MQTT data
33

4-
container-dependencies:
4+
module-dependencies:
5+
- arduino/mqtt

examples/imgdetection/app.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
display-name: Image detection
2+
description: Image detection
3+
4+
module-dependencies:
5+
- arduino/object_detection
6+
7+
models:
8+
- vision/yolo11
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from appslab.modules.log import Log
2+
from appslab.modules.objectdetection import ObjectDetection
3+
from appslab.modules.folderwatcher import FolderWatcher
4+
from appslab.pipeline import Pipeline
5+
import logging
6+
import asyncio
7+
8+
logging.basicConfig(level=logging.INFO)
9+
10+
async def main():
11+
watcher = FolderWatcher(".")
12+
object_detection = ObjectDetection()
13+
logger = Log()
14+
15+
pipe = Pipeline()
16+
pipe.add_source(watcher)
17+
pipe.add_processor(lambda x: {'image': x})
18+
pipe.add_processor(object_detection)
19+
pipe.add_sink(logger)
20+
pipe.start()
21+
22+
await asyncio.sleep(60)
23+
await pipe.stop()
24+
25+
if __name__ == "__main__":
26+
try:
27+
# TODO: can we find a way to not expose the asyncio dependency to the user?
28+
asyncio.run(main())
29+
except KeyboardInterrupt:
30+
pass

internal/declarative/main.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@ async def main():
99
pipe.add_sink(print)
1010
pipe.start()
1111

12-
try:
13-
await asyncio.sleep(5)
14-
finally:
15-
await pipe.stop()
12+
await asyncio.sleep(5)
13+
await pipe.stop()
1614

1715
if __name__ == "__main__":
1816
try:
17+
# TODO: can we find a way to not expose the asyncio dependency to the user?
1918
asyncio.run(main())
2019
except KeyboardInterrupt:
2120
pass

internal/folderwatcher/main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from appslab.modules.folderwatcher import FolderWatcher
2+
3+
def main():
4+
watcher = FolderWatcher(".")
5+
watcher.start()
6+
content = watcher.wait_for_event()
7+
print(content)
8+
watcher.stop()
9+
10+
if __name__ == "__main__":
11+
main()

pyproject.toml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,27 @@ dev = [
3030
"pytest",
3131
"appslab_modules[all]",
3232
]
33-
db_storage = [
33+
dbstorage = [
3434
"influxdb_client>=1.48.0",
3535
]
3636
mqtt = [
3737
"paho_mqtt>=2.1.0",
3838
]
39+
objdetection = [
40+
"Pillow",
41+
]
42+
edge-impulse-classification = [
43+
"Pillow",
44+
]
45+
folderwatcher = [
46+
"watchdog>=6.0.0",
47+
]
3948
all = [
40-
"appslab_modules[db_storage]",
49+
"appslab_modules[dbstorage]",
4150
"appslab_modules[mqtt]",
51+
"appslab_modules[objdetection]",
52+
"appslab_modules[edge-impulse-classification]",
53+
"appslab_modules[folderwatcher]",
4254
]
4355

4456
[project.urls]
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: db_storage
1+
name: arduino/dbstorage
22
module_description: "Simplified database storage layer for Arduino sensor data"
33
require_container: true
44

src/appslab/modules/edge_impulse_classification/__init__.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from appslab.core import load_module_compose_file, parse_docker_compose_variable
44
import logging
55
import io
6+
from PIL import Image
7+
from io import BytesIO
68

79
logger = logging.getLogger(__name__)
810

@@ -28,6 +30,14 @@ def detect_from_file(self, image_path :str) -> dict:
2830
logger.error(f"Error: {e}")
2931
return None
3032

33+
def _get_image_type(self, image_bytes):
34+
try:
35+
image = Image.open(BytesIO(image_bytes))
36+
return image.format.lower() # Returns 'jpeg', 'png', etc.
37+
except Exception as e:
38+
print(f"Error detecting image type: {e}")
39+
return None
40+
3141
def detect(self, image_bytes, image_type:str = "jpg") -> dict:
3242
if not image_bytes or not image_type:
3343
return None
@@ -55,12 +65,33 @@ def detect(self, image_bytes, image_type:str = "jpg") -> dict:
5565
return None
5666

5767
def process(self, item):
58-
if isinstance(item, str):
59-
return self.parse(item)
60-
elif isinstance(item, dict) and 'image' in item and item['image'] != "":
61-
image = item['image']
62-
image_type = image.split('.')[-1]
63-
return self.detect(image, image_type.lower())
68+
"""Process an item to detect objects in an image.
6469
65-
return item # No processing needed
70+
Args:
71+
item: A file path (str) or a dictionary with the 'image' and 'image_type' keys (dict). 'image_type' is optional while 'image' contains image as bytes.
72+
"""
73+
try:
74+
if isinstance(item, str):
75+
# Use this like a file path
76+
with open(item, 'rb') as f:
77+
return self.detect(f.read(), item.split('.')[-1])
78+
elif isinstance(item, dict) and 'image' in item and item['image'] != "":
79+
image = item['image']
80+
if 'image_type' in item and item['image_type'] != "":
81+
image_type = item['image_type']
82+
else:
83+
image_type = self._get_image_type(image)
84+
85+
if image_type is None:
86+
logger.debug(f"[{self.__class__}] Discarding not supported file type")
87+
return None
88+
89+
return self.detect(image, image_type.lower())
90+
91+
return item # No processing needed
92+
except FileNotFoundError:
93+
logger.error(f"[{self.__class__}] File not found: {item}")
94+
except Exception as e:
95+
logger.error(f"[{self.__class__}] Error processing file {item}: {e}")
96+
return None
6697

src/appslab/modules/edge_impulse_classification/module_config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: edge_impulse_classification
1+
name: arduino/edge_impulse_classification
22
module_description: "Image classification using Edge Impulse"
33
require_container: true
44
configuration_variables:
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
name: execute_function
1+
name: arduino/execute_function
22
module_description: "Module that allow the execute of a user defined function."

0 commit comments

Comments
 (0)