API

The top level scanspec module contains a number of packages that can be used from code:

scanspec

scanspec.__version__: str

Version number as calculated by https://github.com/dls-controls/versiongit

scanspec.core

class scanspec.core.Serializable[source]

pydantic BaseModel that adds support for positional arguments and a type parameter from class name.

See also

https://pydantic-docs.helpmanual.io/usage/exporting_models/ for information on the usage of the dict() and json() methods

scanspec.core.Positions

Positions map {key: positions_ndarray} E.g. {xmotor: array([0, 1, 2]), ymotor: array([2, 2, 2])}

alias of Dict[Any, numpy.ndarray]

scanspec.core.T

The type of class the function will return

alias of TypeVar(‘T’)

scanspec.core.if_instance_do(x, cls: Type[T], func: Callable[[T], Any])[source]

If x is of type cls then return func(x), otherwise return NotImplemented. Used as a helper when implementing operator overloading

class scanspec.core.Dimension(positions: Dict[Any, numpy.ndarray], lower: Dict[Any, numpy.ndarray] = None, upper: Dict[Any, numpy.ndarray] = None, snake: bool = False)[source]

Bases: object

Represents a linear stack of positions and bounds. A list of Dimensions is interpreted as nested from slowest moving to fastest moving, so each faster Dimension will iterate once per position of the slower Dimension. When fly-scanning they key will traverse lower-position-upper on the fastest Dimension for each point in the scan.

Parameters
  • positions – The centre positions of the scan for each key

  • lower – Lower bounds if different from positions

  • upper – Upper bounds if different from positions

  • snake – If True then every other iteration of this Dimension within a slower moving Dimension will be reversed

positions

The centre positions of the scan for each key

lower

The lower bounds of each scan point for each key for fly-scanning

upper

The upper bounds of each scan point for each key for fly-scanning

snake

Whether every other iteration of this Dimension within a slower moving Dimension will be reversed

keys() → List[source]

The keys that are present in positions, lower and upper which will move during the scan

tile(reps: int)scanspec.core.Dimension[source]

Return a new Dimension that iterates self reps times

>>> dim = Dimension({"x": np.array([1, 2, 3])})
>>> dim.tile(reps=2).positions
{'x': array([1, 2, 3, 1, 2, 3])}
repeat(reps: int)scanspec.core.Dimension[source]

Return a new Dimension that repeats each point in self reps times

>>> dim = Dimension({"x": np.array([1, 2, 3])})
>>> dim.repeat(reps=2).positions
{'x': array([1, 1, 2, 2, 3, 3])}
mask(mask: numpy.ndarray)scanspec.core.Dimension[source]
>>> dim = Dimension({"x": np.array([1, 2, 3])})
>>> dim.mask(np.array([1, 0, 1])).positions
{'x': array([1, 3])}
copy()scanspec.core.Dimension[source]

Return a shallow copy of the current Dimension (dicts copied, arrays within them are not)

concat(other: scanspec.core.Dimension)scanspec.core.Dimension[source]

Return a new Dimension with arrays from self and other concatenated together. Require both Dimensions to have the same keys and snake settings

>>> dim = Dimension({"x": np.array([1, 2, 3])})
>>> dim2 = Dimension({"x": np.array([5, 6, 7])})
>>> dim.concat(dim2).positions
{'x': array([1, 2, 3, 5, 6, 7])}
zip(other: scanspec.core.Dimension)scanspec.core.Dimension[source]

Return a new Dimension with arrays from keys of self and other merged together. Require both Dimensions to not share keys, and to have the snake settings

>>> dimx = Dimension({"x": np.array([1, 2, 3])})
>>> dimy = Dimension({"y": np.array([5, 6, 7])})
>>> dimx.zip(dimy).positions
{'x': array([1, 2, 3]), 'y': array([5, 6, 7])}
scanspec.core.squash_dimensions(dimensions: List[scanspec.core.Dimension], check_path_changes=True)scanspec.core.Dimension[source]

Squash a list of nested Dimensions into a single one.

Parameters
  • dimensions – The Dimensions to squash, from slowest to fastest moving

  • check_path_changes – If True then check that nesting the output Dimension within other Dimensions will provide the same path as nesting the input Dimension within other Dimensions

>>> dimx = Dimension({"x": np.array([1, 2])}, snake=True)
>>> dimy = Dimension({"y": np.array([3, 4])})
>>> squash_dimensions([dimy, dimx]).positions
{'y': array([3, 3, 4, 4]), 'x': array([1, 2, 2, 1])}
class scanspec.core.Path(dimensions: List[scanspec.core.Dimension], start: int = 0, num: int = None)[source]

Bases: object

Create a consumable Path through a list of Dimensions representing a scan path.

Parameters
  • dimensions – The Dimensions describing the scan, from slowest to fastest moving

  • start – The index of where in the Path to start

  • num – The number of scan points to produce after start. None means up to the end

dimensions

The Dimensions describing the scan, from slowest to fastest moving

index

Index that is next to be consumed

end_index

Index of the end point, one more than the last index that will be produced

consume(num: int = None)scanspec.core.Dimension[source]

Consume at most num points from the Path and return as a Dimension

>>> dimx = Dimension({"x": np.array([1, 2])}, snake=True)
>>> dimy = Dimension({"y": np.array([3, 4])})
>>> path = Path([dimy, dimx])
>>> path.consume(3).positions
{'y': array([3, 3, 4]), 'x': array([1, 2, 2])}
>>> path.consume(3).positions
{'y': array([4]), 'x': array([1])}
>>> path.consume(3).positions
{'y': array([], dtype=int64), 'x': array([], dtype=int64)}
class scanspec.core.SpecPositions(dimensions: List[scanspec.core.Dimension])[source]

Bases: object

Convenience iterable that produces the scan positions for each axis. For better performance, consume from a Path instead.

Parameters

dimensions – The Dimensions describing the scan, from slowest to fastest moving

>>> dimx = Dimension({"x": np.array([1, 2])}, snake=True)
>>> dimy = Dimension({"y": np.array([3, 4])})
>>> sp = SpecPositions([dimy, dimx])
>>> for p in sp: print(p)
{'y': 3, 'x': 1}
{'y': 3, 'x': 2}
{'y': 4, 'x': 2}
{'y': 4, 'x': 1}
dimensions

The Dimensions describing the scan, from slowest to fastest moving

property keys

The keys that will be present in each position dictionary

scanspec.specs

Inheritance diagram of scanspec.specs

class scanspec.specs.Spec[source]

Bases: scanspec.core.Serializable

Abstract baseclass for the specification of a scan. Supports operators:

  • *: Outer Product of two Specs, nesting the second within the first

  • +: Zip two Specs together, iterating in tandem

  • &: Mask the Spec with a Region, excluding positions outside it

  • ~: Snake the Spec, reversing every other iteration of it

keys() → List[source]

Return the list of keys that are present in the positions, from slowest moving to fastest moving

create_dimensions(bounds=True, nested=False) → List[scanspec.core.Dimension][source]

Implemented by subclasses to produce the Dimension list that contribute to positions, from slowest moving to fastest moving

path()scanspec.core.Path[source]

Return a Path through the scan that can be consumed in chunks to give positions and bounds

positions()scanspec.core.SpecPositions[source]

Return a SpecPositions that can be iterated position by position

class scanspec.specs.Product(outer, inner)[source]

Bases: scanspec.specs.Spec

Outer product of two Specs, nesting inner within outer. This means that inner will run in its entirety at each point in outer.

Parameters
  • outer (Spec) – Will be executed once

  • inner (Spec) – Will be executed len(outer) times

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line

spec = Line("y", 1, 2, 3) * Line("x", 3, 4, 12)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-1.png
class scanspec.specs.Zip(left, right)[source]

Bases: scanspec.specs.Spec

Run two Specs in parallel, merging their positions together. Typically formed using the + operator.

Parameters
  • left (Spec) – The left-hand Spec to Zip, will appear earlier in keys

  • right (Spec) – The right-hand Spec to Zip, will appear later in keys

Dimensions are merged by:

  • If right creates a single Dimension of size 1, expand it to the size of the fastest Dimension created by left

  • Merge individual dimensions together from fastest to slowest

This means that Zipping a Spec producing Dimensions [l2, l1] with a Spec producing Dimension [r1] will assert len(l1)==len(r1), and produce Dimensions [l2, l1+r1].

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line

spec = Line("z", 1, 2, 3) * Line("y", 3, 4, 5) + Line("x", 4, 5, 5)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-2.png
class scanspec.specs.Mask(spec, region, check_path_changes=True)[source]

Bases: scanspec.specs.Spec

Restrict the given Spec to only the positions that fall inside the given Region.

Parameters
  • spec (Spec) – The Spec containing the source positions

  • region (Region) – The Region that positions will be inside

  • check_path_changes (bool) – If True path through scan will not be modified by squash

Typically created with the & operator. It also pushes down the & | ^ - operators to its Region to avoid the need for brackets on combinations of Regions.

If a Region spans multiple Dimensions, these Dimensions will be squashed together.

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line
from scanspec.regions import Circle

spec = Line("y", 1, 3, 3) * Line("x", 3, 5, 5) & Circle("x", "y", 4, 2, 1.2)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-3.png
class scanspec.specs.Snake(spec)[source]

Bases: scanspec.specs.Spec

Run the Spec in reverse on every other iteration when nested inside another Spec. Typically created with the ~ operator.

Parameters

spec (Spec) – The Spec to run in reverse every other iteration

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line

spec = Line("y", 1, 3, 3) * ~Line("x", 3, 5, 5)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-4.png
class scanspec.specs.Concat(left, right)[source]

Bases: scanspec.specs.Spec

Concatenate two Specs together, running one after the other. Each Dimension of left and right must contain the same keys.

Parameters
  • left (Spec) – The left-hand Spec to Zip, positions will appear earlier

  • right (Spec) – The right-hand Spec to Zip, positions will appear later

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line, Concat

spec = Concat(Line("x", 1, 3, 3), Line("x", 4, 5, 5))
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-5.png
class scanspec.specs.Squash(spec, check_path_changes=True)[source]

Bases: scanspec.specs.Spec

Squash the Dimensions together of the scan (but not positions) into one linear stack.

Parameters
  • spec (Spec) – The Spec to squash the dimensions of

  • check_path_changes (bool) – If True path through scan will not be modified by squash

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line, Squash

spec = Squash(Line("y", 1, 2, 3) * Line("x", 0, 1, 4))
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-6.png
class scanspec.specs.Line(key, start, stop, num)[source]

Bases: scanspec.specs.Spec

Linearly spaced points in the given key, with first and last points centred on start and stop.

Parameters
  • key (Any) – An identifier for what to move

  • start (float) – Centre point of the first point of the line

  • stop (float) – Centre point of the last point of the line

  • num (int) – Number of points to produce. Must be >= 1

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line

spec = Line("x", 1, 2, 5)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-7.png
classmethod bounded(key, lower, upper, num)[source]

Specify a Line by extreme bounds instead of centre points.

Parameters
  • key (Any) – An identifier for what to move

  • lower (float) – Lower bound of the first point of the line

  • upper (float) – Upper bound of the last point of the line

  • num (int) – Number of points to produce. Must be >= 1

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line

spec = Line.bounded("x", 1, 2, 5)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-8.png
class scanspec.specs.Static(key, value, num=1)[source]

Bases: scanspec.specs.Spec

A static point, repeated “num” times, with “key” at “value”. Can be used to set key=value at every point in a scan.

Parameters
  • key (Any) – An identifier for what to move

  • value (float) – The value at each point

  • num (int) – How many times to repeat this point. Must be >= 1

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line, Static

spec = Line("y", 1, 2, 3) + Static("x", 3)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-9.png
class scanspec.specs.Spiral(x_key, y_key, x_start, y_start, x_range, y_range, num, rotate=0.0)[source]

Bases: scanspec.specs.Spec

Archimedean spiral of “x_key” and “y_key”, starting at centre point (“x_start”, “y_start”) with angle “rotate”. Produces “num” points in a spiral spanning width of “x_range” and height of “y_range”

Parameters
  • x_key (Any) – An identifier for what to move for x

  • y_key (Any) – An identifier for what to move for y

  • x_start (float) – x centre of the spiral

  • y_start (float) – y centre of the spiral

  • x_range (float) – x width of the spiral

  • y_range (float) – y width of the spiral

  • num (int) – Number of points in the spiral

  • rotate (float) – How much to rotate the angle of the spiral

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Spiral

spec = Spiral("x", "y", 1, 5, 10, 50, 30)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-10.png
classmethod spaced(x_key, y_key, x_start, y_start, radius, dr, rotate=0.0)[source]

Specify a Spiral equally spaced in “x_key” and “y_key” by specifying the “radius” and difference between each ring of the spiral “dr”

Parameters
  • x_key (Any) – An identifier for what to move for x

  • y_key (Any) – An identifier for what to move for y

  • x_start (float) – x centre of the spiral

  • y_start (float) – y centre of the spiral

  • radius (float) – radius of the spiral

  • dr (float) – difference between each ring

  • rotate (float) – How much to rotate the angle of the spiral

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Spiral

spec = Spiral.spaced("x", "y", 0, 0, 10, 3)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-111.png
scanspec.specs.TIME = 'TIME'

Can be used as a special key to indicate how long each point should be

scanspec.specs.REPEAT = 'REPEAT'

Can be used as a special key to indicate repeats of a whole spec

scanspec.specs.fly(spec: scanspec.specs.Spec, duration: float)scanspec.specs.Spec[source]

Flyscan, zipping TIME=duration for every position

Parameters
  • spec – The source Spec to continuously move

  • duration – How long to spend at each point in the spec

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line, fly

spec = fly(Line("x", 1, 2, 3), 0.1)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-12.png
scanspec.specs.step(spec: scanspec.specs.Spec, duration: float, num: int = 1)[source]

Step scan, adding num x TIME=duration as an inner dimension for every position

Parameters
  • spec – The source Spec with positions to move to and stop

  • duration – The duration of each scan point

  • num – Number of points to produce with given duration at each of point in the spec

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line, step

spec = step(Line("x", 1, 2, 3), 0.1)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-13.png
scanspec.specs.repeat(spec: scanspec.specs.Spec, num: int, blend=False)[source]

Repeat spec num times

Parameters
  • spec – The source Spec that will be iterated

  • num – The number of times to repeat it

  • blend – If True and the slowest dimension of spec is snaked then the end and start of consecutive iterations of Spec will be blended together, leaving no gap

scanspec.specs.spec_from_dict(d: Dict)scanspec.specs.Spec[source]

Create a Spec from a dictionary representation of it

>>> spec_from_dict(
... {'type': 'Line', 'key': 'x', 'start': 1.0, 'stop': 2.0, 'num': 3})
Line(key='x', start=1.0, stop=2.0, num=3)
scanspec.specs.spec_from_json(text: str)scanspec.specs.Spec[source]

Create a Spec from a JSON representation of it

>>> spec_from_json(
... '{"type": "Line", "key": "x", "start": 1.0, "stop": 2.0, "num": 3}')
Line(key='x', start=1.0, stop=2.0, num=3)

scanspec.regions

Inheritance diagram of scanspec.regions

class scanspec.regions.Region[source]

Bases: scanspec.core.Serializable

Abstract baseclass for a Region that can Mask a Spec. Supports operators:

key_sets() → List[Set[str]][source]

Implemented by subclasses to produce the non-overlapping sets of keys this region spans

mask(positions: Dict[Any, numpy.ndarray])numpy.ndarray[source]

Implemented by subclasses to produce a mask of which positions are in the region

scanspec.regions.get_mask(region: scanspec.regions.Region, positions: Dict[Any, numpy.ndarray])numpy.ndarray[source]

If there is an overlap of keys of region and positions return a mask of the positions in the region, otherwise return all ones

class scanspec.regions.CombinationOf(left, right)[source]

Bases: scanspec.regions.Region

Abstract baseclass for a combination of two regions, left and right

Parameters
  • left (Region) – The left-hand Region to combine

  • right (Region) – The right-hand Region to combine

class scanspec.regions.UnionOf(left, right)[source]

Bases: scanspec.regions.CombinationOf

A position is in UnionOf(a, b) if it is in either a or b. Typically created with the | operator

Parameters
  • left (Region) – The left-hand Region to combine

  • right (Region) – The right-hand Region to combine

>>> r = Range("x", 0.5, 2.5) | Range("x", 1.5, 3.5)
>>> r.mask({"x": np.array([0, 1, 2, 3, 4])})
array([False,  True,  True,  True, False])
class scanspec.regions.IntersectionOf(left, right)[source]

Bases: scanspec.regions.CombinationOf

A position is in IntersectionOf(a, b) if it is in both a and b. Typically created with the & operator

Parameters
  • left (Region) – The left-hand Region to combine

  • right (Region) – The right-hand Region to combine

>>> r = Range("x", 0.5, 2.5) & Range("x", 1.5, 3.5)
>>> r.mask({"x": np.array([0, 1, 2, 3, 4])})
array([False, False,  True, False, False])
class scanspec.regions.DifferenceOf(left, right)[source]

Bases: scanspec.regions.CombinationOf

A position is in DifferenceOf(a, b) if it is in a and not in b. Typically created with the - operator

Parameters
  • left (Region) – The left-hand Region to combine

  • right (Region) – The right-hand Region to combine

>>> r = Range("x", 0.5, 2.5) - Range("x", 1.5, 3.5)
>>> r.mask({"x": np.array([0, 1, 2, 3, 4])})
array([False,  True, False, False, False])
class scanspec.regions.SymmetricDifferenceOf(left, right)[source]

Bases: scanspec.regions.CombinationOf

A position is in SymmetricDifferenceOf(a, b) if it is in either a or b, but not both. Typically created with the ^ operator

Parameters
  • left (Region) – The left-hand Region to combine

  • right (Region) – The right-hand Region to combine

>>> r = Range("x", 0.5, 2.5) ^ Range("x", 1.5, 3.5)
>>> r.mask({"x": np.array([0, 1, 2, 3, 4])})
array([False,  True, False,  True, False])
class scanspec.regions.Range(key, min, max)[source]

Bases: scanspec.regions.Region

Mask contains positions of key >= min and <= max

Parameters
  • key (Any) – The key matching the axis to mask in the spec

  • min (float) – The minimum inclusive value in the region

  • max (float) – The minimum inclusive value in the region

>>> r = Range("x", 1, 2)
>>> r.mask({"x": np.array([0, 1, 2, 3, 4])})
array([False,  True,  True, False, False])
class scanspec.regions.Rectangle(x_key, y_key, x_min, y_min, x_max, y_max, angle=0)[source]

Bases: scanspec.regions.Region

Mask contains positions of key within a rotated xy rectangle

Parameters
  • x_key (Any) – The key matching the x axis of the spec

  • y_key (Any) – The key matching the y axis of the spec

  • x_min (float) – Minimum inclusive x value in the region

  • y_min (float) – Minimum inclusive y value in the region

  • x_max (float) – Maximum inclusive x value in the region

  • y_max (float) – Maximum inclusive y value in the region

  • angle (float) – Clockwise rotation angle of the rectangle

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line
from scanspec.regions import Rectangle

grid = Line("y", 1, 3, 10) * ~Line("x", 0, 2, 10)
spec = grid & Rectangle("x", "y", 0, 1.1, 1.5, 2.1, 30)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-14.png
class scanspec.regions.Circle(x_key, y_key, x_centre, y_centre, radius)[source]

Bases: scanspec.regions.Region

Mask contains positions of key within an xy circle of given radius

Parameters
  • x_key (Any) – The key matching the x axis of the spec

  • y_key (Any) – The key matching the x axis of the spec

  • x_centre (float) – Minimum inclusive x value in the region

  • y_centre (float) – Minimum inclusive y value in the region

  • radius (float) – Radius of the circle

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line
from scanspec.regions import Circle

grid = Line("y", 1, 3, 10) * ~Line("x", 0, 2, 10)
spec = grid & Circle("x", "y", 1, 2, 0.9)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-15.png
scanspec.regions.find_regions(obj) → Iterator[scanspec.regions.Region][source]

Recursively iterate over obj and its children, yielding any Region instances found

scanspec.plot

scanspec.plot.plot_spec(spec: scanspec.specs.Spec)[source]

Plot a spec, drawing the path taken through the scan, using a different colour for each point, grey for the turnarounds, and marking the centrepoints with a filled circle if there are less than 200 of them. If the scan is 2D then 2D regions are shown in black.

# Example Spec

from scanspec.plot import plot_spec
from scanspec.specs import Line
from scanspec.regions import Circle

cube = Line("z", 1, 3, 3) * Line("y", 1, 3, 10) * ~Line("x", 0, 2, 10)
spec = cube & Circle("x", "y", 1, 2, 0.9)
plot_spec(spec)

(Source code, png, hires.png, pdf)

../_images/api-16.png