We have some docs on deploying the model here: running your impulse locally, but yes, this is only C++ for now. There is only one function that you need to invoke there, which is the run_classifier
function, e.g. via:
ei_impulse_result_t result;
// the features are stored into flash, and we don't want to load everything into RAM
signal_t features_signal;
features_signal.total_length = sizeof(features) / sizeof(features[0]);
features_signal.get_data = &raw_feature_get_data;
// invoke the impulse
EI_IMPULSE_ERROR res = run_classifier(&features_signal, &result, true);
Not entirely sure how hard it would be to use e.g. micropython-wrap to make this function callable from the MicroPython environment. If someone that reads this in the future has an idea and wants to contribute that, I’d be very happy to take it!
Regarding a REST endpoint, you can (sort of) do this already, but it requires two REST calls. One to upload the data to the testing
endpoint in the ingestion API, and then one to classify the new sample. Here’s an example in Python:
import json, os
import time, hmac, hashlib
import requests, numpy as np
# project ID is in the studio URL, API and HMAC keys can be obtained from Dashboard > Keys
PROJECT_ID = 1
API_KEY = "ei_0de116b6fa6099a831153825b79a08dd6bea65ef05064830966676e50a4cc269"
HMAC_KEY = "5e188e72873a76734fac5fe13e1987c3bad3c473a67354022bdaf3f995e9271f"
# array of your features
features = [ 0.3200, 3.6900, -5.4600, -3.6100, 4.7300, -2.7200, -7.4400, 4.9700, -0.7700, -8.9600, 4.4500, 0.0900, -10.3100, 4.4900, 1.1300, -10.3100, 4.4900, 1.1300, -15.5000, 5.6100, 3.5900, -19.8100, 6.0100, 6.4400, -19.9800, 5.6000, 8.2700, -19.9800, 3.9800, 9.4800, -19.9800, 2.7100, 9.9100, -19.9800, 0.9900, 9.4900, -19.9800, 0.9900, 9.4900, -19.9800, -0.5600, 8.8500, -19.9800, -1.1000, 8.6200, -19.8800, -0.6900, 8.2300, -17.6600, -1.1300, 5.9700, -16.8000, -0.9000, 5.9500, -16.7600, -0.0200, 6.2600, -16.7600, -0.0200, 6.2600, -14.1400, 0.6100, 4.8000, -10.2000, 0.2800, 2.4300, -8.4800, 1.2800, 2.2200, -7.8500, 2.1200, 2.0800, -6.6100, 2.6200, 0.4400, -3.9100, 1.6900, -3.0100, -3.9100, 1.6900, -3.0100, 0.1500, 0.6600, -5.3600, 3.3200, 1.0000, -5.5000, 6.2300, 0.4300, -6.1900, 8.9000, -1.1700, -7.1000, 10.5600, -1.7000, -5.9900, 12.9600, -2.4300, -5.5400, 12.9600, -2.4300, -5.5400, 18.0600, -4.6300, -7.2100, 19.9700, -7.2800, -8.3200, 19.9700, -6.5100, -5.9200, 19.9700, -5.5700, -3.6700, 19.9700, -6.7000, -3.7700, 19.9700, -8.5300, -5.3900, 19.9700, -8.5300, -5.3900, 19.9700, -8.4400, -5.8600, 18.6100, -6.8500, -4.8100, 14.9500, -4.9400, -3.4200, 12.0800, -3.5500, -2.2500, 10.4900, -3.7700, -3.2300, 8.4200, -3.9000, -4.3800, 8.4200, -3.9000, -4.3800, 5.2700, -2.1900, -3.6600, 2.2200, -0.5100, -1.6400, 1.3100, -0.1100, -0.9600, 2.2600, -1.2100, -1.5900, 0.7500, 0.6100, -1.2400, 0.7500, 0.6100, -1.2400, -1.5500, 2.5200, 1.2200, -2.0300, 1.9200, 0.6500, -4.5400, 0.0800, 0.2200, -7.1100, 0.5200, 0.9400, -10.6300, 1.5800, 2.0400, -18.0500, 4.0600, 5.5600, -18.0500, 4.0600, 5.5600, -19.9800, 6.1200, 10.9200, -19.9800, 4.6100, 13.2300, -19.9800, 0.9700, 12.6300, -19.9800, -1.3200, 11.2500, -19.9800, -2.8900, 9.3400, -19.9800, -3.3400, 8.7000, -19.9800, -3.3400, 8.7000, -19.9800, -2.3700, 8.2100, -18.9800, -2.7700, 6.7800, -15.0600, -3.7300, 4.7400, -15.3900, -2.1400, 5.6200, -15.2300, -0.7300, 5.3500, -12.3400, -0.7300, 3.3300, -12.3400, -0.7300, 3.3300, -8.7400, -0.7000, 0.3100, -6.8100, -0.4200, -0.1900, -6.0600, 1.1100, -0.4400, -4.3300, 2.0900, -0.7300, -1.5200, 1.5900, -2.5000, 0.5800, 1.1600, -4.4000, 0.5800, 1.1600, -4.4000, 1.9000, 2.2900, -4.2200, 8.1200, 2.3600, -7.4300, 11.2200, 0.1500, -6.7800, 12.5300, -0.6300, -6.8300, 16.6900, -1.3300, -5.7300, 19.9700, -3.4500, -7.0500, 19.9700, -3.4500, -7.0500, 19.9700, -6.9800, -9.3700, 19.9700, -7.8600, -8.3100, 19.9700, -6.3200, -6.0000, 19.9700, -5.9100, -5.5600, 19.9700, -7.3900, -6.4000, 19.1600, -6.3500, -5.7900, 19.1600, -6.3500, -5.7900, 15.0400, -5.2300, -4.7600, 12.0300, -5.0000, -4.6000, 10.3300, -5.2500, -4.3900, 9.0400, -4.4700, -4.9800, 6.1000, -3.2100, -4.5100, 2.2400, -2.1300, -3.1400, 2.2400, -2.1300, -3.1400, 0.9400, -1.4800, -2.3300, 1.2400, -1.3300, -2.4600, 0.0300, -0.1500, -1.3100, -2.7500, 0.9200, 0.8700, -4.7000, 0.3600, 1.2200, -5.4100, -1.4000, 1.0400, -5.4100, -1.4000, 1.0400, -6.6300, -0.9800, 1.6700, -10.6700, 0.8800, 4.1900, -14.3800, 0.6900, 5.8900, -15.6700, -1.5200, 6.5500, -17.2600, -2.6000, 7.6700, -17.2600, -2.6000, 7.6700, -18.8400, -2.4200, 8.3100, -19.9800, -2.5600, 9.1200, -19.9800, -3.3300, 9.8500, -19.9800, -4.6500, 8.4400, -19.9800, -5.1700, 7.7300, -19.9300, -4.5400, 7.8600, -19.9300, -4.5400, 7.8600, -18.8700, -4.2900, 7.0800, -17.7800, -3.7400, 5.9400 ]
# reshape from 1D to 3D array (not required for audio)
features = np.reshape(features, (int(len(features) / 3), 3))
def upload_file():
# empty signature (all zeros). HS256 gives 32 byte signature, and we encode in hex, so we need 64 characters here
emptySignature = ''.join(['0'] * 64)
data = {
"protected": {
"ver": "v1",
"alg": "HS256",
"iat": time.time() # epoch time, seconds since 1970
},
"signature": emptySignature,
"payload": {
"device_type": "DISCO-L475VG-IOT01A",
"interval_ms": 16,
"sensors": [
{ "name": "accX", "units": "m/s2" },
{ "name": "accY", "units": "m/s2" },
{ "name": "accZ", "units": "m/s2" }
],
"values": features.tolist()
}
}
# encode in JSON
encoded = json.dumps(data)
# sign message
signature = hmac.new(bytes(HMAC_KEY, 'utf-8'), msg = encoded.encode('utf-8'), digestmod = hashlib.sha256).hexdigest()
# set the signature again in the message, and encode again
data['signature'] = signature
encoded = json.dumps(data)
# and upload the file
res = requests.post(url='https://ingestion.edgeimpulse.com/api/testing/data',
data=encoded,
headers={
'Content-Type': 'application/json',
'x-file-name': 'testing.01',
'x-api-key': API_KEY
})
if (res.status_code == 200):
print('Uploaded file to Edge Impulse', res.status_code, res.content)
return res.text
else:
raise Exception('Failed to upload file to Edge Impulse', res.status_code, res.content)
def get_sample_id_from_name(file_name):
filter = '?category=testing&filename=' + os.path.splitext(file_name)[0]
# query all samples in the testing category
res = requests.get(url='https://studio.edgeimpulse.com/v1/api/' + str(PROJECT_ID) + '/raw-data' + filter,
headers={
'Content-Type': 'application/json',
'x-api-key': API_KEY
})
# see if the API request succeeded
ret = res.json()
if (ret['success'] == False):
raise Exception(ret['error'])
# and return the id of the sample
return ret['samples'][0]['id']
def classify_sample(id):
res = requests.get(url='https://studio.edgeimpulse.com/v1/api/' + str(PROJECT_ID) + '/classify/' + str(id),
headers={
'Content-Type': 'application/json',
'x-api-key': API_KEY
})
ret = res.json()
if (ret['success'] == False):
raise Exception(ret['error'])
return ret['classifications']
file_name = upload_file()
print('file name is', file_name)
sample_id = get_sample_id_from_name(file_name)
print('sample_id', sample_id)
classifications = classify_sample(sample_id)
print('classifications', classifications)
Which returns the classifications per block:
[{
'learnBlock': {
'id': 50,
'type': 'keras',
'name': 'NN Classifier',
'dsp': [41],
'title': 'Neural Network (Keras)'
},
'result': [{
'idle': 0.000745,
'snake': 3.5e-05,
'updown': 0.001455,
'wave': 0.997766
}],
'minimumConfidenceRating': 0.8
}, {
'learnBlock': {
'id': 53,
'type': 'anomaly',
'name': 'Anomaly detection',
'dsp': [41],
'title': 'K-means Anomaly Detection'
},
'result': [{
'anomaly': 0.2667196043261167
}],
'minimumConfidenceRating': 0.3
}]
I’ve added an item to the backlog to add a single API call for this, but for now the above should work (maybe add a call to delete the sample afterwards).