Writing new scan point generators

Let’s walk through the simplest generator, LineGenerator, and see how it is written.

###
# Copyright (c) 2016, 2017 Diamond Light Source Ltd.

We import the baseclass Generator and the compatibility wrappers around the Python range() function and the numpy module

#    Charles Mita - initial API and implementation and/or initial documentation
#
###

Our new subclass includes a docstring giving a short explanation of what it does and registers itself as a subclass of Generator for deserialization purposes.

    def __init__(self, axes, units, start, stop, size, alternate=False):
        """
        Args:
            axes (str/list(str)): The scannable axes E.g. "x" or ["x", "y"]
            units (str/list(str)): The scannable units. E.g. "mm" or ["mm", "mm"]
            start (float/list(float)): The first position to be generated.
                e.g. 1.0 or [1.0, 2.0]
            stop (float or list(float)): The final position to be generated.
                e.g. 5.0 or [5.0, 10.0]
            size (int): The number of points to generate. E.g. 5
            alternate(bool): Specifier to reverse direction if
                generator is nested
        """

        self.axes = to_list(axes)
        self.start = to_list(start)
        self.stop = to_list(stop)
        self.alternate = alternate
        self.units = {d:u for (d, u) in zip(self.axes, to_list(units))}

        if len(self.axes) != len(set(self.axes)):
            raise ValueError("Axis names cannot be duplicated; given %s" %
                             axes)

        if len(self.axes) != len(self.start) or \
           len(self.axes) != len(self.stop):
            raise ValueError(
                "Dimensions of axes, start and stop do not match")

        self.size = size

        self.step = []
        if self.size < 2:
            self.step = [0]*len(self.start)
        else:
            for axis in range_(len(self.start)):
                self.step.append(
                    (self.stop[axis] - self.start[axis])/(self.size - 1))

The initializer performs some basic validation on the parameters and stores them. The units get stored as a dictionary attribute of axis->unit:

    def prepare_arrays(self, index_array):
        arrays = {}
        for axis, start, stop in zip(self.axes, self.start, self.stop):
            d = stop - start
            step = float(d)
            # if self.size == 1 then single point case
            if self.size > 1:
                step /= (self.size - 1)
            f = lambda t: (t * step) + start
            arrays[axis] = f(index_array)
        return arrays

This is used by CompoundGenerator to create the points for this generator. This method should create, for each axis the generator defines, an array of positions by transforming the input index array. The index array will be the numpy array [0, 1, 2, …, n-1, n] for normal positions, and [-0.5, 0.5, 1.5, …, n-0.5, n+0.5] when used to calculate boundary positions.

The arrays are returned as a dictionary of {axis_name : numpy float array}