Python
In SAFE you can integrate your own Python functions directly into SAFE. This can be used both in measurement steps, if you for example need to communicate with your own products API, and in processing if you want to use your own analysis.
Python 3.6
or above must be installed. The numpy
package must also be installed.
Using a Python function in a measurement step
To use a Python function in a measurement step you must drag out the measurement track Python Script onto a step. The track shows you a console which shows you every print statement from your function when the step is started.
If more than one Python script is in a step they will run sequentially with a priority based on where the Python Script is placed in the step, but they will run simultaneously to any other tracks in the step.
Your Python function must have only three input variables and must return the same three variables.
When used in a measurement step, data will be sent to your function as a dictonary like the example below.
user_data = {'success': True} # script success variable (True, False)
You can add key/value pairs to this dict and it will be passed between all the functions you run during a sequence.
Use the 'success' key to tell SAFE if the script ran successfully or not. Returning False
will NOT stop the
sequence, but it will be clear in the log if the script ran successfully or not.
Your Python function should be defined as the example function foo
below.
The file in which your function is, must have a if __name__ == __main__:
clause with the content shown in the example below.
It is advised not to make additional function calls or change the code in the if __name__ == '__main__':
clause. Doing so can cause unexpected behavior.
def foo(data, variables, metadata):
"""
Example function to be used in a SAFE measurement sequence.
:param data: A dictionary to be used throughout a measurement sequence.
:param variables: A dictionary of all the variables your have defined in the current project
:param metadata: A dictionary af all the metadata you have defined in the current project
:return: data, variables, metadata: The data dictionary, the variables dictionary and the metadata dictionary
"""
print('Hello world!')
return data, variables, metadata
if __name__ == '__main__':
import sys
import json
import numpy as np
import time
def serializeDict(d):
return {k.replace('\'', '"'): v.tolist() if isinstance(v, np.ndarray) else serializeDict(v) if isinstance(v, dict) else v for k, v in d.items()}
def deserializeDict(d):
return {k.replace('\'', '"'): np.array(v) if isinstance(v, list) else deserializeDict(v) if isinstance(v, dict) else v for k, v in d.items()}
info, variables, metadata = {}, {}, {}
for i, arg in enumerate(sys.argv):
if i == 2:
variables = deserializeDict(json.loads(arg)) # decode data to numpy arrays
elif i == 3:
metadata = deserializeDict(json.loads(arg)) # decode data to numpy arrays
data = deserializeDict(json.loads(sys.stdin.read())) # read and decode data to numpy arrays
# If you need to setup something before running the function do it here
returnList = list(globals()[sys.argv[1]](data, variables, metadata)) # function call
for i, ret in enumerate(returnList[:3]):
if not isinstance(ret, dict):
raise ValueError('Function must return two dictionaries.')
if i == 0:
print(f"***DATA***")
elif i == 1:
print(f"***VARIABLES***")
elif i == 2:
print(f"***METADATA***")
print(json.dumps(serializeDict(ret))) # encode and print output
# NO PRINTS AFTER THIS LINE #
Using a Python function in processing
To use a Python function in processing you must drag out the processing block Python Script from the toolbox Scripts. Here you can choose filepath and function name in the attribute dock. When the block is connected to other blocks data will be sent to your function as a dictonary.
Below is an example of how the dictionary looks when one single-column dataset is routed into a script block with one input.
The '0'
key relates to the input the data is coming from. If more inputs are use they will be referred to respectively.
If a dataset has more columns it is represented in all the arrays.
import numpy as np
data = {'0': {'msg': '',
'fs': np.array([[48000]]),
'xlabel': np.array([['Time']]),
'ylabel': np.array([['Amplitude']]),
'xunit': np.array([['s']]),
'yunit': np.array([['SPL']]),
'X': np.array([[1],
[2],
[3]]),
'Y': np.array([[5],
[8],
[9]])
}
}
Your Python function should be defined as the example function bar
below. If you have defined variables and/or
metadata in your project they are also available to monitor or edit in scripts. You cannot add new variables or metadata
in a script.
The file in which your function is, must have a if __name__ == __main__:
clause with the content shown in the example below.
It is advised not to make additional function calls or change the code in the if __name__ == '__main__':
clause. Doing so can cause unexpected behavior.
def bar(data, variables, metadata):
"""
Example function to be used in a SAFE measurement sequence.
:param data: A dictionary to be used throughout a measurement sequence.
:param variables: A dictionary of all the variables your have defined in the current project
:param metadata: A dictionary af all the metadata you have defined in the current project
:return: data, variables, metadata: The data dictionary, the variables dictionary and the metadata dictionary
"""
data['0']['Y'] = data['0']['Y'] * 2
variables['my_var'] = 4
return data, variables, metadata
if __name__ == '__main__':
import sys
import json
import numpy as np
import time
def serializeDict(d):
return {k.replace('\'', '"'): v.tolist() if isinstance(v, np.ndarray) else serializeDict(v) if isinstance(v, dict) else v for k, v in d.items()}
def deserializeDict(d):
return {k.replace('\'', '"'): np.array(v) if isinstance(v, list) else deserializeDict(v) if isinstance(v, dict) else v for k, v in d.items()}
info, variables, metadata = {}, {}, {}
for i, arg in enumerate(sys.argv):
if i == 2:
variables = deserializeDict(json.loads(arg)) # decode data to numpy arrays
elif i == 3:
metadata = deserializeDict(json.loads(arg)) # decode data to numpy arrays
data = deserializeDict(json.loads(sys.stdin.read())) # read and decode data to numpy arrays
# If you need to setup something before running the function do it here
returnList = list(globals()[sys.argv[1]](data, variables, metadata)) # function call
for i, ret in enumerate(returnList[:3]):
if not isinstance(ret, dict):
raise ValueError('Function must return two dictionaries.')
if i == 0:
print(f"***DATA***")
elif i == 1:
print(f"***VARIABLES***")
elif i == 2:
print(f"***METADATA***")
print(json.dumps(serializeDict(ret))) # encode and print output
# NO PRINTS AFTER THIS LINE #
Takeaway
If you had not noticed the if __name__ == '__main__':
clause is the same for both examples.
With this you only need to create one script to handle all your measurement sequence and processing needs.