-
Notifications
You must be signed in to change notification settings - Fork 28
Add support for EIM files with shared memory, add support for setting thresholds #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
janjongboom
wants to merge
9
commits into
master
Choose a base branch
from
shm
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
0ed07db
Add support for shared memory inference
janjongboom ce449c2
Cleanup
janjongboom 38862c3
Remove set-thresholds.py
janjongboom 9709116
Add set_threshold example
janjongboom 162eb4b
Remove debug statement
janjongboom 1fcafd8
image: missing importing sys package
mmajchrzycki 562c566
runner: small simplification
mmajchrzycki 54a146f
set-thresholds: make the example interactive
mmajchrzycki 5c18fbc
image: more optimized image conversion
mmajchrzycki File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,3 +8,4 @@ build | |
.vscode | ||
*.eim | ||
.act-secrets | ||
*.png |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,12 +6,12 @@ | |
import signal | ||
import socket | ||
import json | ||
|
||
from multiprocessing import shared_memory, resource_tracker | ||
import numpy as np | ||
|
||
def now(): | ||
return round(time.time() * 1000) | ||
|
||
|
||
class ImpulseRunner: | ||
def __init__(self, model_path: str): | ||
self._model_path = model_path | ||
|
@@ -20,6 +20,8 @@ def __init__(self, model_path: str): | |
self._client = None | ||
self._ix = 0 | ||
self._debug = False | ||
self._hello_resp = None | ||
self._shm = None | ||
|
||
def init(self, debug=False): | ||
if not os.path.exists(self._model_path): | ||
|
@@ -50,27 +52,71 @@ def init(self, debug=False): | |
self._client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | ||
self._client.connect(socket_path) | ||
|
||
return self.hello() | ||
hello_resp = self._hello_resp = self.hello() | ||
|
||
if ('features_shm' in hello_resp.keys()): | ||
shm_name = hello_resp['features_shm']['name'] | ||
# python does not want the leading slash | ||
shm_name = shm_name.lstrip('/') | ||
shm = shared_memory.SharedMemory(name=shm_name) | ||
self._shm = { | ||
'shm': shm, | ||
'type': hello_resp['features_shm']['type'], | ||
'elements': hello_resp['features_shm']['elements'], | ||
'array': np.ndarray((hello_resp['features_shm']['elements'],), dtype=np.float32, buffer=shm.buf) | ||
} | ||
|
||
return self._hello_resp | ||
|
||
def __del__(self): | ||
self.stop() | ||
|
||
def stop(self): | ||
if self._tempdir: | ||
if self._tempdir is not None: | ||
shutil.rmtree(self._tempdir) | ||
self._tempdir = None | ||
|
||
if self._client: | ||
if self._client is not None: | ||
self._client.close() | ||
self._client = None | ||
|
||
if self._runner: | ||
if self._runner is not None: | ||
os.kill(self._runner.pid, signal.SIGINT) | ||
# todo: in Node we send a SIGHUP after 0.5sec if process has not died, can we do this somehow here too? | ||
self._runner = None | ||
|
||
if self._shm is not None: | ||
self._shm['shm'].close() | ||
resource_tracker.unregister(self._shm['shm']._name, "shared_memory") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to do this because we don't own the shared memory - otherwise will get a warning from the resource tracker that we leave shared memory behind. |
||
self._shm = None | ||
|
||
def hello(self): | ||
msg = {"hello": 1} | ||
return self.send_msg(msg) | ||
|
||
def classify(self, data): | ||
msg = {"classify": data} | ||
if self._shm: | ||
self._shm['array'][:] = data | ||
|
||
msg = { | ||
"classify_shm": { | ||
"elements": len(data), | ||
} | ||
} | ||
else: | ||
msg = {"classify": data} | ||
|
||
if self._debug: | ||
msg["debug"] = True | ||
|
||
send_resp = self.send_msg(msg) | ||
return send_resp | ||
|
||
def set_threshold(self, obj): | ||
if not 'id' in obj: | ||
raise Exception('set_threshold requires an object with an "id" field') | ||
|
||
msg = { 'set_threshold': obj } | ||
return self.send_msg(msg) | ||
|
||
def send_msg(self, msg): | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
#!/usr/bin/env python | ||
|
||
import device_patches # Device specific patches for Jetson Nano (needs to be before importing cv2) # noqa: F401 | ||
|
||
try: | ||
import cv2 | ||
except ImportError: | ||
print('Missing OpenCV, install via `pip3 install "opencv-python>=4.5.1.48,<5"`') | ||
exit(1) | ||
import os | ||
import sys | ||
import getopt | ||
import json | ||
from edge_impulse_linux.image import ImageImpulseRunner | ||
|
||
runner = None | ||
|
||
def help(): | ||
print('python set-thresholds.py <path_to_model.eim> <path_to_image.jpg>') | ||
|
||
def main(argv): | ||
try: | ||
opts, args = getopt.getopt(argv, "h", ["--help"]) | ||
except getopt.GetoptError: | ||
help() | ||
sys.exit(2) | ||
|
||
for opt, arg in opts: | ||
if opt in ('-h', '--help'): | ||
help() | ||
sys.exit() | ||
|
||
if len(args) != 2: | ||
help() | ||
sys.exit(2) | ||
|
||
model = args[0] | ||
|
||
dir_path = os.path.dirname(os.path.realpath(__file__)) | ||
modelfile = os.path.join(dir_path, model) | ||
|
||
print('MODEL: ' + modelfile) | ||
|
||
with ImageImpulseRunner(modelfile) as runner: | ||
try: | ||
model_info = runner.init() | ||
# model_info = runner.init(debug=True) # to get debug print out | ||
|
||
print('Loaded runner for "' + model_info['project']['owner'] + ' / ' + model_info['project']['name'] + '"') | ||
if not 'thresholds' in model_info['model_parameters']: | ||
print('This model does not expose any thresholds, build a new Linux deployment (.eim file) to get configurable thresholds') | ||
exit(1) | ||
|
||
print('Thresholds:') | ||
for threshold in model_info['model_parameters']['thresholds']: | ||
print(' -', json.dumps(threshold)) | ||
|
||
# Example output for an object detection model: | ||
# Thresholds: | ||
# - {"id": 3, "min_score": 0.20000000298023224, "type": "object_detection"} | ||
|
||
img = cv2.imread(args[1]) | ||
if img is None: | ||
print('Failed to load image', args[1]) | ||
exit(1) | ||
|
||
# imread returns images in BGR format, so we need to convert to RGB | ||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) | ||
# this mode uses the same settings used in studio to crop and resize the input | ||
features, cropped = runner.get_features_from_image_auto_studio_settings(img) | ||
|
||
print("Which threshold would you like to change? (id)") | ||
while True: | ||
try: | ||
threshold_id = int(input('Enter threshold ID: ')) | ||
if threshold_id not in [t['id'] for t in model_info['model_parameters']['thresholds']]: | ||
print('Invalid threshold ID, try again') | ||
continue | ||
break | ||
except ValueError: | ||
print('Invalid input, please enter a number') | ||
|
||
print("Enter a new threshold value (between 0.0 and 1.0):") | ||
while True: | ||
try: | ||
new_threshold = float(input('New threshold value: ')) | ||
if new_threshold < 0.0 or new_threshold > 1.0: | ||
print('Invalid threshold value, must be between 0.0 and 1.0') | ||
continue | ||
break | ||
except ValueError: | ||
print('Invalid input, please enter a number') | ||
|
||
# dynamically override the thresold from 0.2 -> 0.8 | ||
runner.set_threshold({ | ||
'id': threshold_id, | ||
'min_score': new_threshold, | ||
}) | ||
|
||
res = runner.classify(features) | ||
print('classify response', json.dumps(res, indent=4)) | ||
|
||
finally: | ||
if (runner): | ||
runner.stop() | ||
|
||
if __name__ == "__main__": | ||
main(sys.argv[1:]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,3 @@ | ||
numpy>=1.19 | ||
PyAudio==0.2.11 | ||
psutil>=5.8.0 | ||
edge_impulse_linux | ||
six==1.16.0 | ||
numpy>=1.19,<3 | ||
PyAudio>=0.2.11,<0.3 | ||
six>=1.16.0,<2 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have found the for loops at the end of
get_features_from_image_with_studio_mode()
add quite a lot of latency to inference- switching out for numpy flatten instead speeds things up significantly on slower devicesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, the new implementation is ~10 times faster.