Skip to main content
Version: current

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.

Preqequisite

Python 3.6 or above must 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 can only have two input variables and must return the same two variables.

When used in a measurement step, data will be sent to your function as a dictonary like the example below.

user_data = {'step': 1, # the current step number
'repeaterList': [1, 0], # the current repeater count
'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 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.

caution

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):
"""
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
:return: data, variables: The data dictionary and the variables dictionary
"""
print('Hello world!')
return data, variables
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 = {}, {}
for i, arg in enumerate(sys.argv):
if i == 2:
variables = 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)) # function call
for ret in returnList[:2]:
if not isinstance(ret, dict):
raise ValueError('Function must return two dictionaries.')
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 in your project they are also available to monitor or edit in scripts. The file in which your function is, must have a if __name__ == __main__: clause with the content shown in the example below.

caution

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):
"""
Example function to be used in a SAFE processing block
:param data: A dictionary of all the incoming datasets
:param variables: A dictionary of all the variables your have defined in the current project
:return: data, variables: The data dictionary and the variables dictionary
"""
data['0']['Y'] = data['0']['Y'] * 2
variables['my_var'] = 4
return data, variables
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 = {}, {}
for i, arg in enumerate(sys.argv):
if i == 2:
variables = 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)) # function call
for ret in returnList[:2]:
if not isinstance(ret, dict):
raise ValueError('Function must return two dictionaries.')
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.