API

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

scanspec.core

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]

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]

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]

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]

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]

Implemented by subclasses to produce 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]

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]

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]

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]

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]

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]

Squash the Dimensions together of the scan (but not positions) into one long line.

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]

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]

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]

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.fly(spec: scanspec.specs.Spec, duration: float)[source]

Flyscan, zipping TIME=duration for every position

# 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)[source]

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

# 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.regions

Inheritance diagram of scanspec.regions

class scanspec.regions.Region[source]

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]

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]

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]

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]

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]

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]

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]

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]

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