Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion _typos.toml
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@

[default.extend-identifiers]
nd_grid_positions = "nd_grid_positions"
12 changes: 7 additions & 5 deletions docs/getting-started/next-steps/access-scan-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ For an intro to running scans, see the example linked above. We will use the sca

## 1. Inspect the output from a scan you have just run

Having run the scan above, we can see inspect its status:
Having run the scan above, we can inspect its status:

--[]->[]--test_snippet--test_next_steps.py:test_scan_report:Inspect the scan report

Expand All @@ -40,9 +40,11 @@ Fetch the most recent scan history entry:

This gives you a handle to the scan data after execution.

!!! tip "Scan history is a list of all past scans"
`bec.history` behaves like a normal Python list. Index `0` accesses the first stored scan, while negative indices such as `-1`
access entries from the end, so `bec.history[-1]` gives you the most recent scan.
!!! tip "Scan history behaves like a Python list"
`bec.history` behaves like a normal Python list, but it only keeps a bounded local history of
recent readable scans. In the current client implementation, that history keeps up to 50
readable scans. Index `0` accesses the oldest stored entry, while negative indices such as
`-1` access entries from the end, so `bec.history[-1]` gives you the most recent scan.

## 3. Read one signal from the stored result

Expand All @@ -62,4 +64,4 @@ was run. It can be found in the `File: ` entry in the scan report, both for an a
history. You can open this file with your preferred HDF5 viewer, or with `h5py` in Python. For an introduction to working with the BEC HDF5 file structure, see [Open BEC HDF5 files with h5py](../../how-to/scans/open-bec-hdf5-files-with-h5py.md){ data-preview } and [Open BEC HDF5 files with silx](../../how-to/scans/open-bec-hdf5-files-with-silx.md){ data-preview } for a GUI-based approach.

!!! success What you have learned
You have successfully accessed previous scans through `bec.history`, and know how to access the recorded data from your scans after they have finished. You can now use this data for further analysis, plotting, or custom workflows.
You have successfully accessed previous scans through `bec.history`, and know how to access the recorded data from your scans after they have finished. You can now use this data for further analysis, plotting, or custom workflows.
27 changes: 18 additions & 9 deletions docs/getting-started/quick-start/04-run-your-first-scan.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,44 @@ If you are not sure which parameters a scan expects, ask the IPython client dire
scans.line_scan?
```

This will display the scan documentation, including its signature and an **example** of use:
This will display the scan documentation, including its live signature and an example. The exact
output depends on the scans currently published by your scan server. A typical `line_scan` help
entry looks like this:

```python
• default@bec [4/19] ❯❯ scans.line_scan?
Signature:
scans.line_scan(
*args,
steps: int,
relative: bool,
exp_time: float = 0,
steps: int = None,
relative: bool = False,
frames_per_trigger: int = 1,
settling_time: float = 0,
settling_time_after_trigger: float = 0,
readout_time: float = 0,
burst_at_each_point: int = 1,
**kwargs,
)
Docstring:
A line scan for one or more motors.

Args:
*args (Device, float, float): pairs of device / start position / end position
exp_time (float): exposure time in s. Default: 0
steps (int): number of steps. Default: 10
relative (bool): if True, the start and end positions are relative to the current position. Default: False
burst_at_each_point (int): number of acquisition per point. Default: 1
*args (Device, float, float): pairs of device / start / stop arguments
steps (int): number of points along the line
relative (bool): if True, interpret start and stop relative to the current position
exp_time (float): exposure time in seconds. Default: 0
frames_per_trigger (int): number of frames acquired per trigger. Default: 1
settling_time (float): settling time in seconds. Default: 0
settling_time_after_trigger (float): settling time after trigger in seconds. Default: 0
readout_time (float): readout time in seconds. Default: 0
burst_at_each_point (int): number of exposures at each point. Default: 1

Returns:
ScanReport

Examples:
>>> scans.line_scan(dev.motor1, -5, 5, dev.motor2, -5, 5, steps=10, exp_time=0.1, relative=True)
File: ~/PSI/bec/bec_lib/bec_lib/scans.py
Type: function
```

Expand Down
4 changes: 3 additions & 1 deletion docs/how-to/scans/access-bec-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ related:
- You are connected to a running BEC client session.
- The scans you want to inspect are still available in the BEC history and their files are readable from your current machine.

`bec.history` keeps a local history of recent scans. In current BEC, this history is limited to the last 10 000 readable scans. Each lookup returns a `ScanDataContainer`, the same data container type you also get from scan reports.
`bec.history` keeps a local history of recent scans. In the current client implementation, this
history keeps up to 50 readable scans. Each lookup returns a `ScanDataContainer`, the same data
container type you also get from scan reports.

The structure of this container is similar to the layout BEC writes under `entry/collection` in the HDF5 file. For example, `metadata` corresponds to `entry/collection/metadata`, and the device and readout access paths are built from `entry/collection/readout_groups`.

Expand Down
88 changes: 88 additions & 0 deletions docs/learn/scans/argument-bundles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
related:
- title: ScanArgument
url: learn/scans/scanargument.md
- title: GUI Config
url: learn/scans/gui-config.md
- title: Learn by Example
url: learn/scans/learn-by-example.md
---

# Argument Bundles

This page explains how scans can describe repeated positional input bundles when a normal fixed
Python signature is not enough.

## The Scan Signature

In the current scan implementation, a scan definition is described largely by its `__init__`
signature plus a few class attributes.

The scan server serializes that signature and publishes it to clients. The client then uses it to:

- expose the scan under `scans.<name>`
- attach a live Python signature in IPython
- validate kwargs and bundled positional inputs
- resolve device-name strings to device objects when the annotations require that

This means the signature is no longer only local Python documentation. It is part of the runtime API
contract between the scan server, the client, and GUIs.

## `arg_input` and `arg_bundle_size`

!!! tip
`arg_input` and `arg_bundle_size` are special cases for scans with an undefined number of
input arguments. Most scans developed in plugins do not need them.

If the number of input arguments is not fixed, the usual Python-style fixed signature is not enough.

For example, a line scan can work with any number of motors in parallel, so the scan cannot rely on
one fixed positional argument layout in the way an ordinary Python function usually would.

In those cases, scans with repeated positional bundles declare those bundles explicitly.

For a line scan, that can look like this:

```py
arg_input = {
"device": DeviceBase,
"start": float,
"stop": float,
}
arg_bundle_size = {"bundle": 3, "min": 1, "max": None}
```

`arg_bundle_size` then tells BEC how many positional values belong to one bundle and how many
bundles are allowed.

This is what lets BEC validate a call such as:

```py
scans.line_scan(dev.samx, -1, 1, dev.samy, -2, 2, steps=5, relative=False)
```

without treating those positional arguments as an unstructured `*args` blob.

Rich input metadata for individual parameters is covered separately on
[ScanArgument](scanargument.md).

## Reloading The Scan Server

If you add a new scan or change an existing scan class, the scan server must reload that Python code
before the changes become available.

In practice, that means you should restart or reload the scan server after editing scan
implementations. Otherwise the running server will continue using the old version of the scan.

## Next Step

After argument bundles, continue with [GUI Config](gui-config.md) to see how scans group inputs for
graphical clients.

## What To Remember

!!! info "What to remember"
- The serialized scan signature is part of the runtime API between the scan server, clients, and GUIs.
- `arg_input` and `arg_bundle_size` define repeated positional bundles such as move targets or line-scan ranges.
- `ScanArgument` covers rich metadata for individual inputs, while argument bundles describe repeated positional structure.
- After changing scan code, the scan server must be reloaded or restarted.
134 changes: 134 additions & 0 deletions docs/learn/scans/fast-axis-slow-axis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
---
related:
- title: Position Generators
url: learn/scans/position-generators.md
- title: ScanArgument
url: learn/scans/scanargument.md
- title: Learn by Example
url: learn/scans/learn-by-example.md
---

# Fast Axis and Slow Axis

When a scan moves more than one axis, the order of those axes matters.

BEC follows one consistent convention:

- the first user-provided axis is the fast axis
- the last user-provided axis is the slow axis

That means the fast axis changes most often, while the slow axis changes only after the fast-axis
sweep has finished.

## What That Means In Practice

A useful way to read the convention is:

For every point in `samx` from `-5` to `5`, move `samy` from `-10` to `10`.

In that example:

- `samx` is the fast axis
- `samy` is the slow axis

So the scan sweeps through all `samx` values while `samy` stays fixed, then advances `samy` to its
next value and repeats.

!!! example
A 2D grid scan like this:

```py
scans.grid_scan(dev.samx, -5, 5, 3, dev.samy, -10, 10, 5, snaked=False, relative=False)
```

would have `samx` as the fast axis and `samy` as the slow axis, so the scan would:

1. keep `samy` fixed at `-10` while it sweeps `samx` from `-5` to `5`
2. advance `samy` to the next value (in this case `-5`) while it again sweeps `samx` from `-5` to `5`
3. continue until the last `samy` value has been reached and its `samx` sweep has completed
4. finish the scan after the last row of fast-axis points has been acquired

If the requirement is to have `samy` as the fast axis and `samx` as the slow axis, swap the
order of the motor arguments:

```py
scans.grid_scan(dev.samy, -10, 10, 5, dev.samx, -5, 5, 3, snaked=False, relative=False)
```

## Why This Matters

This convention affects how you read and define multi-axis scans:

- the order of axes in a grid or nested scan is meaningful
- the generated point order follows that nesting
- snaking typically changes the traversal direction of the fast axis while keeping the same
slow-axis structure

Keeping that convention stable makes scan definitions easier to reason about and makes generated
point lists more predictable.

## A Simple Example

At the user level, a grid scan might look like this:

```py
scans.grid_scan(dev.samx, -5, 5, 3, dev.samy, -10, 10, 5, snaked=False)
```

The same ordering appears in the generated positions:

```py
positions = position_generators.nd_grid_positions(
[(-5.0, 5.0, 3), (-10.0, 10.0, 5)],
snaked=False,
)

for point in positions:
samx_position = point[0]
samy_position = point[1]
```

Here the first axis is the outer loop and the second axis is the inner loop:

- axis 1: fast axis
- axis 2: slow axis

So the point order follows this pattern:

1. keep the second axis fixed
2. sweep the first axis through all of its values
3. advance the second axis
4. repeat

## How To Read Existing Scan Code

When you see code such as:

```py
positions = position_generators.nd_grid_positions(
[(start_motor1, stop_motor1, steps_motor1), (start_motor2, stop_motor2, steps_motor2)],
snaked=True,
)
```

read it as:

- the first tuple defines the fast axis
- the second tuple defines the slow axis

That same idea also applies more generally to nested point generation in BEC's grid helpers: early
user-provided axis definitions correspond to faster-changing axes, and later ones correspond to
slower-changing axes.

## Next Step

After axis-order conventions, continue with [ScanArgument](scanargument.md).

That page covers the rich input metadata used in scan signatures.

## What To Remember

!!! info "What to remember"
- In BEC grid-style scans, the first user-provided axis is the fast axis and later axes change more slowly.
- The fast axis changes most often within the generated point list.
- This convention makes multi-axis scan definitions and point ordering easier to read.
Loading
Loading