API¶
The top level scanspec module contains a number of packages that can be used from code:
scanspec.core
: Core classes likeDimension
andPath
scanspec.specs
:Spec
and its subclassesscanspec.regions
:Region
and its subclassesscanspec.plot
:plot_spec
to visualize a scan
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()
andjson()
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
See also
-
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
andupper
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
See also
-
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
See also
>>> 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
¶
-
class
scanspec.specs.
Spec
[source]¶ Bases:
scanspec.core.Serializable
Abstract baseclass for the specification of a scan. Supports operators:
*
: OuterProduct
of two Specs, nesting the second within the first+
:Zip
two Specs together, iterating in tandem&
:Mask
the Spec with aRegion
, 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.
# 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)
-
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
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)
-
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
Typically created with the
&
operator. It also pushes down the& | ^ -
operators to itsRegion
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)
-
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)
-
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
# 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)
-
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
# 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)
-
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
# 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)
-
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
# 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)
-
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)
-
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)
-
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 moveduration – 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)
-
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 stopduration – 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)
-
scanspec.specs.
repeat
(spec: scanspec.specs.Spec, num: int, blend=False)[source]¶ Repeat spec num times
- Parameters
spec – The source
Spec
that will be iteratednum – 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
¶
-
class
scanspec.regions.
Region
[source]¶ Bases:
scanspec.core.Serializable
Abstract baseclass for a Region that can
Mask
aSpec
. Supports operators:|
:UnionOf
two Regions, positions present in either&
:IntersectionOf
two Regions, positions present in both-
:DifferenceOf
two Regions, positions present in first not second^
:SymmetricDifferenceOf
two Regions, positions present in one not both
-
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
-
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
>>> 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
>>> 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
>>> 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
>>> 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
>>> 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)
-
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
# 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)
-
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)