GUI
The gui
package provides support for establishing graphical user interfaces (GUIs) by means of a
scriptable GUI and
a DOOCS property server. The contents of this package is shown below:
import doocspie
help(doocspie.gui)
Help on package doocspie.gui in doocspie:
NAME
doocspie.gui - Support modules for graphical user interfaces (GUIs).
PACKAGE CONTENTS
datalyzer (package)
property_server
FILE
/home/cbehrens/Home/Repositories/gitlab/doocspie/doocspie/gui/__init__.py
In the following, we show how to configure the scriptable GUI, which is called datalyzer
and
is very similar to the
TrainAbo
discussed in an earlier section of this documentation. Additionally, we explain how the
PropertyServer
class can be utilized in conjunction with a dedicated DOOCS property server for data
visualization with the Java DOOCS
Data Display (JDDD).
Datalyzer
The scriptable GUI for data visualization is realized by the Datalyzer
class of the datalyzer
sub-package, and
its documentation, which is very similar to the TrainAbo
, is presented here:
help(doocspie.gui.datalyzer.Datalyzer)
Help on class Datalyzer in module doocspie.gui.datalyzer:
class Datalyzer(builtins.object)
| Datalyzer(properties=None, timeout_seconds=10, buffer_size=32, allow_zero_events=False)
|
| Datalyzer class for providing a scriptable graphical user interface (GUI).
|
| This class provides the methods for providing a scriptable GUI for data visualization.
|
| Methods defined here:
|
| __init__(self, properties=None, timeout_seconds=10, buffer_size=32, allow_zero_events=False)
| Constructor of the datalyzer class.
|
| This constructor initializes the instance with optional parameters (see 'Args' below).
|
| Args:
| properties (tuple or dict, optional): The optional properties to get train events with readouts for.
| timeout_seconds (int): The optional timeout seconds for unsuccessful readout.
| buffer_size (int): The optional number of buffers to be used for storing previous readouts.
| allow_zero_events (bool, optional): The optional state for allow zero train events.
|
| __str__(self)
| Special method to return a properly formatted string representation of the datalyzer.
|
| add(self, address, label=None, offset=None, timestamp_event=None, meta_event=None, start=None, elements=None)
| Add address with optional parameters to the properties for synchronous readout.
|
| Args:
| address (str): The DOOCS address to add to the properties for synchronous readout.
| label (str, optional): Optional label for DOOCS address as a means for alternative property access.
| offset (int, optional): Optional offset to be applied on event numbers for synchronization.
| timestamp_event (bool, optional): Optional state to determine using the timestamp as an alternative event.
| meta_event (str, optional): Optional Meta property to replace the default event with.
| start (int, optional): Optional start position for reading out array-like types.
| elements (int, optional): Optional number of elements to read out for array-like types.
|
| Returns:
| None
|
| Raises:
| DoocspieException: Doocspie related exception for address duplication.
|
| add_axis_labels(self, source, x_label=None, y_label=None)
| Add axis labels to the given data source (either a property added or a registered function).
|
| Args:
| source (str): The data source (property or registered function) to add the axis labels to.
| x_label (str, optional): Optional x-label for the given source.
| y_label (str, optional): Optional y-label for the given source.
|
| Returns:
| None
|
| register(self, arg)
| Decorator with optional label supplied via argument for registration of a function.
|
| Args:
| arg (str or function): Either a label (str) attached to the function or the function itself.
|
| Returns:
| None
|
| Raises:
| DoocspieException: Doocspie related exception for an already registered function label.
|
| startup(self)
| Start up the datalyzer GUI application.
|
| Returns:
| None
|
| update(self)
| Update the newly added properties to for synchronous readout.
|
| Returns:
| None
|
| ----------------------------------------------------------------------
| Readonly properties defined here:
|
| actual_properties
| tuple: The properties that can actually be readout in the datalyzer.
|
| registered_functions
| tuple: The registered functions that can be used for synchronized readout in the datalyzer.
|
| unusable_properties
| tuple: The properties that cannot be used for synchronized readout in the datalyzer.
|
| usable_properties
| tuple: The properties that can be used for synchronized readout in the datalyzer.
|
| zero_event_properties
| tuple: The properties that have an event number of 'zero'.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
The Datalyzer
class can simply be imported directly from
the doocspie.gui
subpackage as is presented below. In
the first code block, we show again how the TrainAbo
can be configured for synchronized
readout of DOOCS properties
and looping callback functions. Subsequent analysis and plotting must be provide by
third-party tools. The second code
block then demonstrates the same for the Datalyzer
, which however starts up a full-blown
GUI for data visualization.
from doocspie.abo import TrainAbo
train_abo = TrainAbo(train_events=42)
train_abo.add("FLASH.DIAG/TOROID/1FL0UBC2/CHARGE.TD", label="charge 1")
train_abo.add("FLASH.DIAG/TOROID/2FL0DBC2/CHARGE.TD", label="charge 2")
ratios = []
def ratio(train_event):
charge_1 = train_event.get("charge 1").data[0]
charge_2 = train_event.get("charge 2").data[0]
ratios.append(charge_1 / charge_2)
train_abo.start_loop(ratio)
# now 'ratios' can be used for plotting purposes or further analysis
from doocspie.gui import Datalyzer
datalyzer = Datalyzer()
datalyzer.add("FLASH.DIAG/TOROID/1FL0UBC2/CHARGE.TD", label="charge 1")
datalyzer.add("FLASH.DIAG/TOROID/2FL0DBC2/CHARGE.TD", label="charge 2")
@datalyzer.register
def ratio(train_event):
charge_1 = train_event.get("charge 1").data[0]
charge_2 = train_event.get("charge 2").data[0]
return charge_1 / charge_2
datalyzer.startup()
When comparing the both code blocks, there is a noticeable similarity between the two
interfaces. In the latter case, we
instantiate a Datalyzer
instance and add two DOOCS properties
with its address and a label, respectively.
Furthermore, we register a custom callback function, which will be available in the
GUI for data visualization as well.
Finally, a PyQt based GUI gets started by invoking the startup
method, and its appearance (including the e-log
printer dialog) is presented here:
The Datalyzer
, just like the TrainAbo
,
can also be instantiated directly with DOOCS properties as is
demonstrated below. The custom callback functions still must be registered though,
utilizing the Python decorator called
register
,
which is also part of the Datalyzer
interface. Once registered, the function
will be available as
another data source for visualization. Such a custom data source, i.e. a registered
function, will either be named after
its function definition name or after an optional label, which can be passed to the
register
decorator. Both is
presented in the example below, where the registered functions also show up in the
printout of the Datalyzer
instance:
addresses = {"FLASH.DIAG/TOROID/1FL0UBC2/CHARGE.TD": {"label": "charge 1"}}
datalyzer = Datalyzer(addresses)
@datalyzer.register
def charge_first_bunch(train_event):
return train_event.get("charge 1").data[0]
@datalyzer.register("first bunch")
def charge_first_bunch(train_event):
return train_event.get("FLASH.DIAG/TOROID/1FL0UBC2/CHARGE.TD").data[0]
print(datalyzer)
Datalyzer(actual_properties=('FLASH.DIAG/TOROID/1FL0UBC2/CHARGE.TD', 'charge 1'), registered_functions=('charge_first_bunch', 'first bunch'))
Here, we show again the actual properties and registered functions by means of Python properties:
print("actual properties of datalyzer:", datalyzer.actual_properties)
print("registered functions of datalyzer:", datalyzer.registered_functions)
actual properties of datalyzer: ('FLASH.DIAG/TOROID/1FL0UBC2/CHARGE.TD', 'charge 1')
registered functions of datalyzer: ('charge_first_bunch', 'first bunch')
Duplications in registered function labels are indicated with a corresponding custom exception:
try:
@datalyzer.register("first bunch")
def charge_second_bunch(train_event):
return train_event.get("charge").data[1]
except doocspie.DoocspieException as exc:
print("exception message:", exc.message)
exception message: registered function label 'first bunch' already exists
Just like the TrainAbo
, the Datalyzer
does internal feasibility checks on the added properties, and thus the
update
method must be called in order to see effects on the added properties. However, this
update
method is
also called implicitly when the Datalyzer
is being started up, hence it can
usually be omitted. The following code
example shows the effect of the update
method:
addresses = ("FLASH.DIAG/TOROID/1FL0UBC2/CHARGE.TD",)
datalyzer = Datalyzer(addresses)
datalyzer.add("FLASH.DIAG/TOROID/2FL0DBC2/CHARGE.TD", label="charge 2")
datalyzer.add("TEST.DOOCS/UNIT_TEST_SUPPORT/PY_DOOCS/FLOAT")
print("actual properties before update:", datalyzer.actual_properties)
datalyzer.update()
print("actual properties after update:", datalyzer.actual_properties)
print("unusable properties:", datalyzer.unusable_properties)
actual properties before update: ('FLASH.DIAG/TOROID/1FL0UBC2/CHARGE.TD',)
actual properties after update: ('FLASH.DIAG/TOROID/1FL0UBC2/CHARGE.TD', 'FLASH.DIAG/TOROID/2FL0DBC2/CHARGE.TD', 'charge 2')
unusable properties: (Unusable(address='TEST.DOOCS/UNIT_TEST_SUPPORT/PY_DOOCS/FLOAT', label=None, reason='zero event number'),)
Finally, we show a comprehensive example of the Datalyzer
application with all its features:
properties = {"FLASH.DIAG/TOROID.ML/1FL0UBC2/CHARGE.TD": {"label": "charge 1"},
"FLASH.DIAG/TOROID.ML/2FL0DBC2/CHARGE.TD": {"label": "charge 2"},
"FLASH.SYNC/LASER.LOCK.EXP/F1.PG.OSC/FMC0.MD22.1.ENCODER_POSITION.RD":
{"label": "encoder"},
"FLASH.FEL/FL21.MOTOR/MOTOR7/FODOMETER": {},
"FLASH.DIAG/CAMERA/OTR9FL2XTDS/IMAGE_EXT": None}
datalyzer = Datalyzer(properties, allow_zero_events=True)
datalyzer.add_axis_labels("charge 1", "Bins", "Charge (nC)")
datalyzer.add_axis_labels("encoder", y_label="Encoder (mm)")
datalyzer.add_axis_labels("FLASH.DIAG/CAMERA/OTR9FL2XTDS/IMAGE_EXT",
x_label="horizontal", y_label="vertical")
@datalyzer.register
def charge_1_first_bunch(train_event):
return train_event.get("FLASH.DIAG/TOROID.ML/1FL0UBC2/CHARGE.TD").data[0]
@datalyzer.register("first bunch of 2. charge monitor")
def charge_2_first_bunch(train_event):
return train_event.get("charge 2").data[0]
datalyzer.add_axis_labels("charge_1_first_bunch", y_label="Charge (nC)")
datalyzer.add_axis_labels("first bunch of 2. charge monitor", y_label="Charge (nC)")
import time
start = time.time()
@datalyzer.register
def update_rate(_):
global start
stop = time.time()
rate = stop - start
start = stop
return 1 / rate
datalyzer.startup()
The application with its three sub-tools, Viewer, Logger and Correlator is presented below:
The Viewer tool is able to visualize images and array-like data types, and also provides averaging and background correction. For scalar-like data types, the Logger tool allows plotting of time series with optional averaging and a movable line marker. The Correlator tool can correlate scalar-like data types, and additionally provides plot symbol customization. Axis labels can optionally be added.
PropertyServer
The default method for visualization of DOOCS property data is realized by the Java
DOOCS Data Display (JDDD).
Typically, recurrent and daily tasks are handled via DOOCS servers, and those come
along with corresponding JDDD panels,
i.e. server and visualization are bundled together. In situations where dedicated
servers are not available or where one
might want to visualize custom data on the fly via JDDD, the PropertyServer
class comes handy. This class provides
simplified access to property servers at FLASH, and its documentation is presented
in the following. Refer to
https://confluence.desy.de/display/FLASHUSER/Generic+User+Properties
for more details on the DOOCS beamline servers for
generic properties at FLASH.
help(doocspie.gui.property_server.PropertyServer)
Help on class PropertyServer in module doocspie.gui.property_server:
class PropertyServer(builtins.object)
| PropertyServer class for simplified access to the property servers.
|
| This class provides the property server class for simplified access to property servers at FLASH.
|
| Methods defined here:
|
| get(self, location, property)
| Get the readout from the property server for the given location and property.
|
| Args:
| location (str): The property server location to request the readout for the property from.
| property (str): The actual property to request the readout from.
|
| Returns:
| Readout: An instance of the readout object from the property server for the given location and property.
|
| Raises:
| ValueError: Exception for non-existing location and/or property or not supported full address input.
| DoocspieException: Doocspie related exception for unexpected events.
|
| get_locations(self, full_address=False)
| Get all available property server locations with the option to return the full addresses.
|
| Args:
| full_address (bool, optional): The optional state for returning the full addresses.
|
| Returns:
| tuple: All available property server locations.
|
| get_properties_of_location(self, location, full_address=False)
| Get all available property server properties with the option to return the full addresses.
|
| Args:
| location (str): The property server location to request the properties from.
| full_address (bool, optional): The optional state for returning the full addresses.
|
| Returns:
| tuple: All available property server properties.
|
| Raises:
| ValueError: Exception for non-existing location.
|
| set(self, location, property, value, allow_resizing=False)
| Set the property server content for the given location and property with the given value.
|
| Args:
| location (str): The property server location of the property to set the value to.
| property (str): The actual property to set the value to.
| value (property dependent): The value to set the property to.
| allow_resizing(bool, optional): The optional state for allowing resizing of array-like DOOCS types.
|
| Returns:
| None
|
| Raises:
| ValueError: Exception non-existing location and/or property or for not supported full address input.
| DoocspieException: Doocspie related exception for unexpected events.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
The PropertyServer
class can simply be imported directly
from the doocspie.abo subpackage as is shown in the code
example below. Here, we instantiate an instance and return the server locations:
from doocspie.gui import PropertyServer
property_server = PropertyServer()
property_server.get_locations()
('FL21', 'FL23', 'FL24', 'FL26', 'BL1', 'BL3', 'PG1', 'PG2')
The default get_locations
method returns just the location without its facility and device part, which is also
the
most relevant information in practice. However, the method also excepts the boolean
full_address
parameter in order
to optionally return the full location as shown here:
property_server.get_locations(full_address=True)
('FLASH.EXP/USER.STORE.FL21/FL21',
'FLASH.EXP/USER.STORE.FL23/FL23',
'FLASH.EXP/USER.STORE.FL24/FL24',
'FLASH.EXP/USER.STORE.FL26/FL26',
'FLASH.EXP/USER.STORE.BL1/BL1',
'FLASH.EXP/USER.STORE.BL3/BL3',
'FLASH.EXP/USER.STORE.PG1/PG1',
'FLASH.EXP/USER.STORE.PG2/PG2')
Once decided for a location, all properties of the particular location can be
returned by means of the
get_properties_of_location
method. The following code example presents all the properties available, which
consist
of scalar type (DOUBLE
) and 1D/2D array data (A_DOUBLE
/A_XY
):
properties = property_server.get_properties_of_location("FL24")
filtered_properties = tuple(p for p in properties if p[-1].isdigit()) # to reduce output
filtered_properties
('VAL.01',
'VAL.02',
'VAL.03',
'VAL.04',
'VAL.05',
'VAL.06',
'VAL.07',
'VAL.08',
'VAL.09',
'VAL.10',
'VAL.11',
'VAL.12',
'VAL.13',
'VAL.14',
'VAL.15',
'VAL.16',
'ARRAY1D.01',
'ARRAY1D.02',
'ARRAY1D.03',
'ARRAY1D.04',
'ARRAY2D.01',
'ARRAY2D.02',
'ARRAY2D.03',
'ARRAY2D.04')
The following two images show example JDDD panel plots for 1D and 2D array data, respectively:
Similar to get_locations
mentioned earlier, the get_properties_of_location
method also allows to return full
addresses via its boolean full_address
parameter as is demonstrated in the
following:
property_server.get_properties_of_location("FL24", full_address=True)[0]
'FLASH.EXP/USER.STORE.FL24/FL24/VAL.01'
Proper ValueError
exceptions get raised for properties of not existing locations being requested:
try:
property_server.get_properties_of_location("not existing")
except ValueError as err:
print(err)
location 'not existing' does not exist
Once decided for a location and property, the get
method
can simply be used to retrieve data as is demonstrated in
the following example. The get
method requires a location and property
string, and it always return an instance of
Readout
,
which has been covered in great detail before.
value_readout = property_server.get("FL24", "VAL.01")
array_1d_readout = property_server.get("FL24", "ARRAY1D.01")
array_2d_readout = property_server.get("FL24", "ARRAY2D.01")
print("PropertyServer's 'get' method always returns a 'Readout':",
all([isinstance(value_readout, doocspie.doocspie.io.Readout),
isinstance(array_1d_readout, doocspie.doocspie.io.Readout),
isinstance(array_2d_readout, doocspie.doocspie.io.Readout)]))
print()
print("readout.type of 'VAL' property:", value_readout.type)
print("readout.type of 'ARRAY1D' property:", array_1d_readout.type)
print("readout.type of 'ARRAY2D' property:", array_2d_readout.type)
PropertyServer's 'get' method always returns a 'Readout': True
readout.type of 'VAL' property: DOUBLE
readout.type of 'ARRAY1D' property: A_DOUBLE
readout.type of 'ARRAY2D' property: A_XY
Trying to get data for non existing locations or properties results in ValueError
exceptions:
try:
property_server.get("not existing", "ARRAY2D.01")
except ValueError as err:
print(err)
try:
property_server.get("FL24", "not existing")
except ValueError as err:
print(err)
location 'not existing' does not exist
property 'not existing' does not exist
The get
method does not except full addresses for the location and/or property part,
respectively:
try:
property_server.get("FLASH.EXP/USER.STORE.FL24/FL24", "ARRAY2D.01")
except ValueError as err:
print(err)
full address of location and/or property is not supported
Besides retrieving data, the PropertyServer
class also permits to send data to the
property servers via its set
method, similar the get
method but with an additional value parameter
to pass. The following example demonstrates
how to get/set data of/to a particular property server:
readout_before = property_server.get("FL24", "VAL.01")
property_server.set("FL24", "VAL.01", 42) # the actual 'set' command
readout_after = property_server.get("FL24", "VAL.01")
print("readout data of 'VAL.01' at 'FL24' before 'set' command:", readout_before.data)
print("readout data of 'VAL.01' at 'FL24' after 'set' command:", readout_after.data)
readout data of 'VAL.01' at 'FL24' before 'set' command: 23.0
readout data of 'VAL.01' at 'FL24' after 'set' command: 42.0
The set
provides an optional allow_resizing
parameter of boolean type, which renders resizing of array-like
DOOCS data types possible, in case resizing is implemented in the particular DOOCS
server of interest. The following
code example presents how such a resizing can be accomplished, and it also shows
what happens otherwise, i.e. without
resizing being allowed:
readout_before = property_server.get("FL24", "ARRAY2D.02")
print("readout data before resizing:", readout_before.data.tolist())
new_data = list(readout_before.data) + [[23, 42]]
try:
property_server.set("FL24", "ARRAY2D.02", new_data)
except doocspie.DoocspieException as exc:
print("without resizing -> exception message:", exc.message)
property_server.set("FL24", "ARRAY2D.02", new_data, allow_resizing=True)
readout_after = property_server.get("FL24", "ARRAY2D.02")
print("readout data after resizing:", readout_after.data.tolist())
readout data before resizing: [[0.0, 0.0]]
without resizing -> exception message: wrong input data size
readout data after resizing: [[0.0, 0.0], [23.0, 42.0]]