evedata.scan.controllers.version_mapping module
Mapping SCML contents to the entities of the scan subpackage.
There are different versions of the schema underlying the SCML files. Hence, mapping the contents of an SCML file to the data model of the evedata package requires to get the correct mapper for the specific version. This is the typical use case for the factory pattern.
Users of the module hence will typically only obtain a
VersionMapperFactory
object to get the correct mappers for individual
files. Furthermore, “users” basically boils down to the Scan
class. Therefore, users of
the evedata package usually do not interact directly with any of the
classes provided by this module.
Overview
Being version agnostic with respect to eveH5 and SCML schema versions is a
central aspect of the evedata package. This requires facilities mapping
the actual SCML files to the data model provided by the entities
technical layer of the scan
subpackage. The File
facade obtains the correct VersionMapper
object via the
VersionMapperFactory
, providing a SCML
resource object to the factory. It is
the duty of the factory to obtain the “version” attribute from the
SCML
object.
Fig. 45 Class hierarchy of the evedata.scan.controllers.version_mapping
module, providing the functionality to map different SCML file
schemas to the data structure provided by the Scan
and Station
classes. The factory
will be used to get the correct mapper for a given SCML file.
For each SCML schema version, there exists an individual
VersionMapperVxmy
class dealing with the version-specific mapping.
The idea behind the Mapping
class is to provide simple mappings for
attributes and alike that need not be hard-coded and can be stored
externally, e.g. in YAML files. This would make it easier to account
for (simple) changes.
For each SCML schema version, there exists an individual
VersionMapperVxmy
class dealing with the version-specific mapping.
Currently, we assume major and minor version numbers to be relevant. Hence,
the xmy
suffix for the individual mapper classes. That
part of the mapping common to all versions of the SCML schema takes place
in the VersionMapper
parent class. The idea behind the Mapping
class is to provide simple mappings for attributes and alike that can be
stored externally, e.g. in YAML files. This would make it easier to
account for (simple) changes.
General aspects
Both, representing the SCML contents as well as mapping the information from
a given SCML file to their representing entities do not strive to preserve
the full information available. The evedata
interface is in the
luxurious situation to not require all information nor to perform scans.
Hence, for the time being, only those parts necessary for interpreting an
eveH5 file and providing the valuable abstractions for the users are
obtained from an SCML file and mapped to the respective representations.
One particularly important aspect: Only those scan modules that are actually connected (either to the root node or to another scan module) are actually mapped and contained in the list of scan modules.
Mapping tasks for SCML schema up to v9.2
Currently, the structure of the entities in the evedata.scan.entities
subpackage is not stable, and furthermore, there is no overview how much the
SCML schema has changed for the different versions. Hence, the tasks
described here will definitely change and evolve over time.
Map SCML metadata (version, location) ✓
Map scan – if it exists
Map scan metadata (repeat_count, comment, description, …) (✓)
Map scan modules (✓)
Map basic metadata ✓
Map parent, appended, nested, is_nested ✓
Distinguish types of scan modules: “classic” vs. snapshot ✓
Extract list of detector channels and motor axes ✓
Map pre- and post-scans ✓
Map positionings ✓
Calculate number of positions per pass/total and actual positions ✓
Map plots? ✗
Map remaining information? ✗
Map all devices ✗
Map detectors
Map motors
Map devices
Todo
Implement mapping of positions for axes using plugins as step function. The reason for not yet having implemented this case: these axes need to have access to another reference axis, and the current implementation cannot guarantee this axis being present.
Currently, there is a need to map at least basic information about the scan
modules, to proceed with several controllers from the
evedata.evefile.controllers
subpackage: mpskip, separating datasets
of redefined channels, obtain set values for axes. For details, see the
respective section of the Architecture document.
Important
Due to the reasons mentioned above (urgent need to map only basic
information about the scan modules), the mapping of the SCML file
contents to the entities from the evedata.scan.entities
subpackage is only rudimentary. Therefore, both mapping and internal
handling of the SCML/XML tree will probably change in the future.
Distinguishing types of scan modules
Currently, only two types of scan modules are distinguished: “classical”
modules and snapshot modules. The distinction is based on the existence of
the tag <classic>
: if present, we have a “classical” scan module,
if not, we have a snapshot module.
For “classical” modules, both axes and channels are mapped, for static axis or channel snapshot modules, axes or channels, respectively.
Creating positions for individual scan modules
Currently, (up to eveH5 schema version 7), the storage layer does not resemble
nor preserve the structure of a scan, particularly its composition of distinct
scan modules. To allow for sensible access to the data, the evedata
interface tries to reconstruct this structure, at least as long as the SCML
is available from the eveH5 file. This means that for each individual HDF5
dataset, the positions belonging to the individual scan modules need to be
known, as one and the same channel or axis can be used in different scan
modules. Hence, the mapper tries to calculate the number of positions per
pass for each scan module, the number of total positions for each scan
module, and finally the actual position (count)s for each scan module.
With the current way the engine stores the data, determining the actual position counts belonging to each individual scan module is not possible in full generality. Situations that may corrupt the algorithm to determine the positions from the scan description or even make it impossible to proceed contain:
Axes positions defined as lists in external files.
Axes positions can be loaded from external files. However, only the file name (and path) are stored in the SCML. There is no guarantee at all that the file contents are the same as they were during scan execution. Besides that, the
evedata
interface usually cannot access the file containing the positions. In case the axis whose positions are defined using an external file has more positions set than any other axis in the scan module (the same applies if it is the only axis in the scan module), the algorithm calculating the (number of) positions for the scan module will fail.External events leading to skipping execution of parts of a scan module.
Generally, users can skip parts of a scan module. Similarly, events can be defined in scans that skip parts of a scan module given a specific condition. In both cases, these events will not be saved in any way in the resulting eveH5 file. Hence, there is no way in determining that an event occurred. This most probably leads to a full corruption of the positions determined by the algorithm.
Scans containing the MPSKIP detector
This is a special situation dealt with by the
mpskip
module. Intrinsically, the positions cannot be determined algorithmically. However, given certain assumptions discussed in the documentation of thempskip
module, mapping should be possible.
Besides these general problems with accurately determining the (number of) positions per scan module, the algorithm generally proceeds as follows, assuming a list of those scan modules to be available that are part of the actual scan, together with the information on nesting and appending:
For each scan module, obtain all axes, and for each axis, determine the number of positions. The maximum of the number of positions of all axes within a scan module defines the number of positions per pass of a scan module, stored in the
ScanModule.number_of_positions_per_pass
attribute.For each scan module, check the “number of measurements” parameter (known also as “measurements per point”, MPP, or “valuecount” in the SCML schema), and multiply the number of positions per pass with its value, to obtain the correct number of positions per pass.
For each scan module, determine whether a positioning takes place. Positionings get their own position (count) after all positions in the scan module have been accessed. In case of nested scan modules, the nested scan modules are executed for each individual position of the next-higher scan module. Hence, the parent scan module of a (series of) nested scan module(s) acts as a “for loop”.
For each scan module, determine whether it is nested, and if so, multiply the number of positions per pass by the number of positions of the next-higher scan module, stored in the
ScanModule.number_of_positions
attribute.For each scan module, obtain the actual position counts, starting with the first scan module with parent
0
(defined as root node and start of the scan) and with1
as first position (count), stored in theScanModule.positions
attribute.
A general plausibility check is to compare the number of positions
calculated by this algorithm with the number of positions created during the
actual scan. The latter can be easily obtained using the special HDF5
dataset containing the mapping of position (count)s to timestamps.
Furthermore, obtaining the shape of the data in an HDF5 dataset is a “cheap”
operation not requiring reading the data themselves, hence, not breaking
with the general philosophy of the evedata
interface to read data
usually only on demand.
Fundamental change of SCML schema with v10
Most probably, the SCML schema will be changed quite substantially in the
future, based on all the experience with developing the evedata
package
and the data models implemented therein.
Depending on how dramatic this changes will be, the mappers for versions up to the current one (v9.x) and those of the next major version may differ substantially.
Module documentation
- class evedata.scan.controllers.version_mapping.VersionMapperFactory
Bases:
object
Factory for obtaining the correct version mapper object.
There are different versions of the schema underlying the SCML files. Hence, mapping the contents of an SCML file to the entities of the
scan
subpackage requires to get the correct mapper for the specific version. This is the typical use case for the factory pattern.- scml
Python object representation of an SCML file
- Raises:
ValueError – Raised if no SCML object is present
Examples
Using the factory is pretty simple. There are actually two ways how to set the scml attribute – either explicitly or when calling the
get_mapper()
method of the factory:factory = VersionMapperFactory() factory.scml = scml_object mapper = factory.get_mapper()
factory = VersionMapperFactory() mapper = factory.get_mapper(scml=scml_object)
In both cases,
mapper
will contain the correct mapper object, andscml_object
contains the Python object representation of an SCML file.- get_mapper(scml=None)
Return the correct mapper for a given SCML file.
For convenience, the returned mapper has its
VersionMapper.source
attribute already set to thescml
object used to get the mapper for.- Parameters:
scml (
evedata.scan.boundaries.scml.SCML
) – Python object representation of an SCML file- Returns:
mapper – Mapper used to map the SCML file contents to evedata structures.
- Return type:
- Raises:
ValueError – Raised if no scml object is present
- class evedata.scan.controllers.version_mapping.VersionMapper
Bases:
object
Mapper for mapping the SCML file contents to data structures.
This is the base class for all version-dependent mappers. Given that there are different versions of the SCML schema, each version gets handled by a distinct mapper subclass.
To get an object of the appropriate class, use the
VersionMapperFactory
factory.- source
Python object representation of an SCML file
- destination
High(er)-level data structure representing an SCML file
Can alternatively be an
evedata.scan.boundaries.scan.Station
object.
Examples
Although the
VersionMapper
class is not meant to be used directly, its use is prototypical for all the concrete mappers:mapper = VersionMapper() mapper.map(source=scml, destination=scan)
Usually, you will obtain the correct mapper from the
VersionMapperFactory
. In this case, the returned mapper has itssource
attribute already set for convenience:factory = VersionMapperFactory() mapper = factory.get_mapper(scml=scml) mapper.map(destination=scan)
- map(source=None, destination=None)
Map the SCML file contents to data structures.
- Parameters:
source (
evedata.scan.boundaries.scml.SCML
) – Python object representation of an SCML filedestination (
evedata.scan.boundaries.scan.Scan
) –High(er)-level evedata structure representing an SCML file
Can alternatively be an
evedata.scan.boundaries.scan.Station
object.
- Raises:
ValueError – Raised if either source or destination are not provided
- class evedata.scan.controllers.version_mapping.VersionMapperV9m0
Bases:
VersionMapper
Mapper for mapping SCML v9.0 file contents to data structures.
Note
Currently, most mapping is implemented in this class, and needs to be moved upwards in the inheritance hierarchy once this hierarchy exists.
Furthermore, only a minimal set of mappings is currently performed, far from being a complete mapping of the contents of an SCML file.
Note
Only those scan modules that are actually connected (either to the root node or to another scan module) are actually mapped and contained in the list of scan modules.
- source
Python object representation of an SCML file
- destination
High(er)-level data structure representing an SCML file
Examples
Mapping a given SCML file to the evedata structures is the same for each of the mappers:
mapper = VersionMapperV9m0() mapper.map(source=scml, destination=scan)
Usually, you will obtain the correct mapper from the
VersionMapperFactory
. In this case, the returned mapper has itssource
attribute already set for convenience:factory = VersionMapperFactory() mapper = factory.get_mapper(scml=scml) mapper.map(destination=scan)
- map(source=None, destination=None)
Map the SCML file contents to data structures.
- Parameters:
source (
evedata.scan.boundaries.scml.SCML
) – Python object representation of an SCML filedestination (
evedata.scan.boundaries.scan.Scan
) –High(er)-level evedata structure representing an SCML file
Can alternatively be an
evedata.scan.boundaries.scan.Station
object.
- Raises:
ValueError – Raised if either source or destination are not provided
- class evedata.scan.controllers.version_mapping.VersionMapperV9m1
Bases:
VersionMapperV9m0
Mapper for mapping SCML v9.1 file contents to data structures.
Note
SCML schema versions 9.0 and 9.1 differ only in their version number. Hence, this is an empty class delegating everything to its parent.
- source
Python object representation of an SCML file
- destination
High(er)-level data structure representing an SCML file
Examples
Mapping a given SCML file to the evedata structures is the same for each of the mappers:
mapper = VersionMapperV9m1() mapper.map(source=scml, destination=scan)
Usually, you will obtain the correct mapper from the
VersionMapperFactory
. In this case, the returned mapper has itssource
attribute already set for convenience:factory = VersionMapperFactory() mapper = factory.get_mapper(scml=scml) mapper.map(destination=scan)
- map(source=None, destination=None)
Map the SCML file contents to data structures.
- Parameters:
source (
evedata.scan.boundaries.scml.SCML
) – Python object representation of an SCML filedestination (
evedata.scan.boundaries.scan.Scan
) –High(er)-level evedata structure representing an SCML file
Can alternatively be an
evedata.scan.boundaries.scan.Station
object.
- Raises:
ValueError – Raised if either source or destination are not provided
- class evedata.scan.controllers.version_mapping.VersionMapperV9m2
Bases:
VersionMapperV9m0
Mapper for mapping SCML v9.2 file contents to data structures.
Note
SCML schema versions 9.0 and 9.2 differ only in their version number. Hence, this is an empty class delegating everything to its parent.
- source
Python object representation of an SCML file
- destination
High(er)-level data structure representing an SCML file
Examples
Mapping a given SCML file to the evedata structures is the same for each of the mappers:
mapper = VersionMapperV9m2() mapper.map(source=scml, destination=scan)
Usually, you will obtain the correct mapper from the
VersionMapperFactory
. In this case, the returned mapper has itssource
attribute already set for convenience:factory = VersionMapperFactory() mapper = factory.get_mapper(scml=scml) mapper.map(destination=scan)
- map(source=None, destination=None)
Map the SCML file contents to data structures.
- Parameters:
source (
evedata.scan.boundaries.scml.SCML
) – Python object representation of an SCML filedestination (
evedata.scan.boundaries.scan.Scan
) –High(er)-level evedata structure representing an SCML file
Can alternatively be an
evedata.scan.boundaries.scan.Station
object.
- Raises:
ValueError – Raised if either source or destination are not provided