-
Notifications
You must be signed in to change notification settings - Fork 1
Writing Script Definitions
These must be python 2/3 compatible see https://github.com/ISISComputingGroup/ibex_user_manual/wiki/Using-Futurize
For an example script definition see here. A script definition is defined by creating a class implementing a ScriptDefinition (a python interface) class DoRun(ScriptDefinition):
. ScriptDefinition must be imported before doing this so add at the top of your file from genie_python.genie_script_generator import ScriptDefinition
.
To implement an ScriptDefinition this DoRun class must have methods run
, parameters_valid
and get_help
. With run
and parameters_valid
signatures may look like this: def run(self, temp="1", field="1"):
, def parameters_valid(self, temp="1", field="1"):
.
Both run
and parameters_valid
methods must take the same arguments (in this case temp="1", field="1"
) and must have the self
argument first. All the arguments except self must have a default value in our example "1" is the defaults. All the arguments taken except for self
are of type string, if you want to change this see "Casting Parameters" below.
When defining a get_help
method we are returning a string that we want to be displayed to the users in the script generator UI, for example:
def get_help(self):
return "My help string"
If we do not want to display a string to the user we return None:
def get_help(self):
return None
Optionally a script definition can specify an estimate_time
method, which will take the same arguments as run
and parameters_valid
and return a float giving an estimate of how long a script will take (in seconds). When defining this method we are returning how long each row in the script generator table will take to run.
def estimate_time(self, field1="1", field2="2"):
return float(field1) * float(field2)
In order for this method to perform arithmetic operations on scientific notations, it is mandatory to cast the argument as float for e.g. float(field1)
.
The initial value of an action in the table can be defined within your script definition. Simply set the default values of the run method and these will be converted into a string for the initial value in the table.
Please note that the value you put as a default will be passed through any casters when running. So for example with our magnet_device_type
caster below the valid inputs into the action table are LF, TF, ZF or N/A which are cast to Danfysik, T20, Active ZF and N/A. When defining the default you should use the valid inputs for the actions table e.g. LF, TF, ZF or N/A.
This decorator allows you to cast the parameters you receive for your run
and parameters_valid
methods, for example:
@cast_parameters_to(temp=float, field=float)
def run(self, temp=1.0, float=1.0):
Here we pass temp a cast called float. This will call float(temp)
on whatever is passed to run
before run
is called so you can treat temp as a floating-point number. If casting the input fails this will be propagated back up to the user. Other casters include int, long and hex. If there does not exist a caster that fits your specification you can write one yourself see "Custom Casters".
When float, int etc. are not enough you can define your own function to cast an input. These take one argument (the argument to cast) and return the casted value. See examples below for details.
Say we want our users to be able to input a floating-point value for the temperature, but we also want them to be able to input the word "KEEP" or even "KeEp" to not change the value we can define the caster below:
def float_or_keep(temp):
if temp.lower() == "keep":
return None
else:
return float(temp)
Say we have a set of magnets that we are wanting the user to select in an argument (or no magnet "N/A") we can define the below caster:
magnet_devices = {"ZF": "Active ZF", "LF": "Danfysik", "TF": "T20 Coils"}
# Convert to magnet device type if possible, if not they have input an incorrect magnet_device
# Raise a ValueError and this will be caught and displayed to the user that the conversion is incorrect
def magnet_device_type(magnet_device):
magnet_device = magnet_device.upper()
if magnet_device in magnet_devices.keys():
return magnet_devices[magnet_device]
elif magnet_device == "N/A":
return magnet_device
raise ValueError("Magnet device must be one of {} or N/A".format(magnet_devices))
To say we cannot cast the argument we raise a ValueError which tells cast_parameters_to that we have failed to cast the argument. The string you type into the ValueError (e.g. "Magnet device must be one of {} or N/A".format(magnet_devices)) will be propagated back up to the user.
CopyPreviousRow
instances can be used to specify parameters in functions which should copy any previous row's value. This could be used to avoid re-specifying the values manually when creating a script.
Usage:
from genie_python.genie_script_generator import ScriptDefinition, cast_parameters_to, CopyPreviousRow
class DoRun(ScriptDefinition):
@cast_parameters_to(temperature=float_or_keep, field=float_or_keep, mevents=int, magnet_device=magnet_device_type)
def run(self, temperature=CopyPreviousRow(1.0), field=1.0, mevents=10, magnet_device="N/A"):
...
The temperature
field here would still have a default value of 1.0 if no actions exist, however if a user changes this value and adds another row it will be copied over to the next row.
The script generator uses the python that we bundle with it which comes pre-installed with everything you would expect on an instrument (except inst) so you can easily write genie commands into your script definition etc.
However, it does not come with installed instrument scripts that you obtain via inst. Due to this, if you try to import inst outside of your run
method your script definition will produce errors for your users and fail to load when not on the instrument itself. Our suggestion is to import inst inside the run function and use it as normal in there:
def run(...):
import inst
inst.instrument_script_method()