slic is a python library providing a toolbox for creating control environments as well as writing automation scripts or control GUIs for experiments.
slic is a re-write/re-factor of eco with the aim to increase maintainability and allow easier scaling to many beamlines. The main goal was a clear separation between, on the one hand, the DAQ/scanning library and the library of general-purpose devices and, on the other hand, the beamline codes. This allows the different parts to move at different paces as well as giving a clear border between working and in-development code: New ideas and features can be build for an individual beamline, only when they are ready to be used they are generalized and moved into the library shared among the beamlines.
slic consists of a core library for static recording and scans as well as a devices library containing classes that represent physical devices. As work-in-progress, the core library has seen most changes so far while the devices have not been worked on as much. Furthermore, there's a GUI frontend built on top of slic as backend included.
The beamline codes can be found here:
Please click here for some FAQs.
The core library contains
- scanner —
Scanner
class collecting several types of scans, e.g., along different numbers of axes. Scans are performed by alternatingly changing one or moreAdjustables
and performing a static recording via one or moreAcquisition
objects. Optionally, each step can be tested by aCondition
. Besides, a generator for run names/numbers and a class to hold meta information about a scan is included. - acquisition — Classes for static recording via several means available at SwissFEL. Specifically, the following packages are wrapped for a common interface:
- sf_daq (via a standalone BrokerClient)
- bsread
- DataAPI
DIA (Detector integration API)- epics PVs
- adjustable — ABC for physical/virtual devices that can be moved or otherwise adjusted. The
PVAdjustable
class handles interaction with the typical set of epics PV defining a device (set value, readback, moving status). A generic class is also provided, which turns a getter/setter pair into an adjustable. - condition — Classes that collect statistics over a given time window and test whether a value was in a specified range often enough. This allows to define what conditions are considered good enough for a recording.
- device — Representation of larger hardware components that consist of several adjustables. Devices can also be nested allowing to represent, e.g., a whole beamline.
SimpleDevice
is a straight-forward interface for creating devices. The included collection of device implementations can be found inslic.devices
. - sensor — ABC for physical/virtual devices that are read out and analysed. Recording is started and stopped, and an aggregation function is applied to the data collected in between. The default aggregation is averaging via
np.mean
. TheBSSensor
andPVSensor
classes handle reading from a single bsread channel or epics PV, respectively. Several classes exist that allow combining multiple channels (e.g.,Norm
,Combined
, and theirBS
counterparts). Sensors may be used for live-plotting scans via grum, i.e., by providing adefault_sensor
toScanner
or asensor
to a specific scan method likescan1d
. - task — Simplifying wrappers for python's threading.Thread, which allow return values and forward exceptions raised within a thread to the calling scope. A nicer
__repr__
makes tasks easier to use in ipython. More specific tasks are also available: the DAQTask can hold information about the files it is writing, and the Loop comes in two variants (infinite and with time out) that both call a function repeatedly.
Collection of (mostly) well-defined and generalized devices used at SwissFEL.
A Device
(cf. also in the overview scheme) is a collection of several Adjustables
and/or other Devices
optionally with added methods and functionality. Again, a nicely readable __repr__
is provided. The main goal is to provide folder-like namespaces to organize adjustables in meaningful hardware abstractions and allow for better discoverabilty, e.g., via ipython's autocomplete. A simple interface is the SimpleDevice
, which combines the Device
class with types.SimpleNamespace.
One of the goals of the "library-first" approach of slic is to provide means for creating "disposable GUIs", meaning that a new GUI for a specific experiment can be created quickly relying on a solid library in the backend and giving free choice over the GUI toolkit used. Therefore, the experiment control and DAQ library is cleanly separated from the GUI code, and the interfaces are clearly defined.
slic comes with an example GUI (written in wxPython) built on top:
In order to further the "disposable GUIs" concept, this GUI is very modular: Tabs can be enabled or disabled upon instantiation. Each tab interfaces a single feature of slic:
- Static →
Acquisition.acquire()
- Scan →
Scanner.scan1D()
- Tweak →
Adjustable.set_target_value()
- etc.
Additionally, the wx built-ins are extended by several widgets specific to experiment control (numeral input boxes are calculators, which perform simple math on enter press; filename boxes increment a counter when pressing up; etc.), which can be re-used easily.
Collection of utility functions and classes that are used throughout both of the above parts of the code. This ranges from interactive Yes/No prompts over channel lists and directory/file path handlers to printing and json helpers.
Note: Currently it is preferable to use the current code from git instead of the latest release from conda.
The latest slic release is available via conda from the PSI channel. You may either create a new dedicated environment
conda create --name slic -c paulscherrerinstitute slic
or install it (and its dependencies) into your current conda environment
conda install -c paulscherrerinstitute slic
The beamline codes are hosted in git repositories and should be cloned (here, with Alvra as example, for other beamlines replace alvra
with the respective name):
- either via https:
git clone https://gitlab.psi.ch/slic/alvra.git
- or via ssh:
git clone [email protected]:slic/alvra.git
Activate the conda environment used during the installation, e.g.,
conda activate slic
Until we have a small launcher script, the ipython environment for, e.g., Alvra can be started like this:
ipython -i alvra/alvra.py
This assumes that the command is executed from the folder where the beamline script was cloned into — adapt as necessary. This starts an interactive ipython shell from the beamline script. Again, Alvra is just an example, for other beamlines replace alvra
with the respective name.
All dependencies should be available via conda.
A new conda environment containing all dependencies can be created from conda-env.yml
:
conda env create --name slic --file conda-env.yml
The dependencies are pulled from the PSI channel and conda-forge as needed.
Similarly, the dependencies may also be added to an existing environment:
conda activate the_target_env
conda env update --file conda-env.yml
The library and the beamline codes are stored in separate git repositories within a gitlab group. This allows to easily check out only the desired parts.
For the most current code, both parts should be cloned (here, with Alvra as example, for other beamlines replace alvra
with the respective name):
- either via https:
git clone https://gitlab.psi.ch/slic/slic.git
git clone https://gitlab.psi.ch/slic/alvra.git
- or via ssh:
git clone [email protected]:slic/slic.git
git clone [email protected]:slic/alvra.git
Activate the conda environment into which the dependencies were installed, e.g.,
conda activate slic
Until we have a small launcher script, the ipython environment for, e.g., Alvra can be started like this:
PYTHONPATH=slic:$PYTHONPATH ipython -i alvra/alvra.py
This assumes that both repos have been cloned into the same folder, and that the command is executed from that folder — adapt as necessary. The first part makes sure that the library can be found; the second starts an interactive ipython shell from the beamline script. Again, Alvra is just an example, for other beamlines replace alvra
with the respective name.
While many concepts and ideas are inherited from eco, slic differs in several key aspects:
eco is meant to run within ipython, in fact it starts its own ipython shell upon start-up, providing a control environment for experiments. It does so by parsing a dictionary of "eco components" and creating objects from those.
slic, as the "l" suggests, is meant to be used as a library from a script that may be running in, e.g., ipython, the regular python interpreter or jupyter. A specific beamline script running in an interactive ipython shell provides an environment identical to eco by importing and instantiating classes from slic.
Conventions are formalized by ABCs (Abstract Base Classes) in order to provide immediate error messages for incomplete new (sub-)classes. True to python's duck typing, the actual library does not enforce the use of the ABCs, they are merely meant as help for writing new sub-classes.
slic is based on code from the two actively used eco forks: