resonance.api.core.scan¶
resonance.api.core.scan
¶
Scan orchestration and execution using core primitives.
ScanPlan(points, motor_names, ai_channels, shutter='Light Output', actuate_every=False)
¶
Validated scan plan constructed from a list of points or a DataFrame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
list[ScanPoint]
|
Ordered sequence of scan points. |
required |
motor_names
|
list[str]
|
Names of all motors referenced in the scan. |
required |
ai_channels
|
list[str]
|
Analog input channel names to acquire at each point. |
required |
shutter
|
str
|
DIO channel name of the light shutter. |
'Light Output'
|
actuate_every
|
bool
|
If True, the shutter is opened and closed per point. If False, the shutter is opened once for the entire scan. |
False
|
Examples:
>>> df = pd.DataFrame({
... "Sample X": [0, 10, 20],
... "Sample Y": [0, 0, 0],
... "exposure": [0.1, 0.1, 0.1],
... })
>>> scan_plan = ScanPlan.from_dataframe(df, ai_channels=["Photodiode"])
Source code in src/resonance/api/core/scan.py
from_dataframe(df, ai_channels=None, default_exposure=1.0, default_delay=0.2, shutter='Light Output', actuate_every=False)
classmethod
¶
Build a validated scan plan from a DataFrame.
Each row becomes one ScanPoint. Motor columns are detected via
validate_scan_dataframe; an optional exposure column is also detected.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
df
|
DataFrame
|
DataFrame whose columns are motor names and optionally an exposure time column. |
required |
ai_channels
|
list[str] or None
|
Channels to acquire at each point. If None, a beamline default set is used. |
None
|
default_exposure
|
float
|
Exposure time in seconds applied when the DataFrame has no exposure column. |
1.0
|
default_delay
|
float
|
Settle delay in seconds after each motor move. |
0.2
|
shutter
|
str or None
|
DIO channel name of the light shutter. If None, the shutter is not used |
'Light Output'
|
actuate_every
|
bool
|
Per-point shutter mode. |
False
|
Returns:
| Type | Description |
|---|---|
ScanPlan
|
Fully validated scan plan. |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If the DataFrame fails structural or value validation. |
Source code in src/resonance/api/core/scan.py
estimated_duration_seconds(motor_time=0.1, api_time=0.5)
¶
Compute estimated total scan duration.
Per-point cost: motor_time + api_time + exposure_time + delay_after_move.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motor_time
|
float
|
Expected motor-move overhead per point in seconds. |
0.1
|
api_time
|
float
|
Expected API round-trip overhead per point in seconds. |
0.5
|
Returns:
| Type | Description |
|---|---|
float
|
Total estimated duration in seconds. |
Notes
Does not account for shutter actuation overhead when actuate_every=True.
Source code in src/resonance/api/core/scan.py
describe(motor_time=0.1, api_time=0.5)
¶
Print a human-readable summary of the scan plan.
Displays point count, unique motor values, and estimated duration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motor_time
|
float
|
Motor-move overhead per point in seconds used for the estimate. |
0.1
|
api_time
|
float
|
API overhead per point in seconds used for the estimate. |
0.5
|
Source code in src/resonance/api/core/scan.py
ScanExecutor(conn)
¶
Executes ScanPlan instances against a live BCS server.
Supports two interrupt modes:
-
Programmatic abort: call
await executor.abort()from any async context to set the abort flag. The next check insideexecute_pointorwait_for_settleraisesScanAbortedError. -
Jupyter / IPython interrupt: create the scan as an
asyncio.Taskand callawait bl.abort_scan()from another cell to set the abort flag and stop after the current point.
asyncio.CancelledError is raised when the Task is cancelled via
task.cancel(). execute_scan catches this, sets the abort flag,
and returns any partial results already collected. If no results have
been collected the error is re-raised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conn
|
BCSServer
|
Active BCS server connection. |
required |
Source code in src/resonance/api/core/scan.py
current_scan
property
¶
Currently running scan plan.
Returns:
| Type | Description |
|---|---|
ScanPlan or None
|
The active |
abort()
async
¶
Request abort of the running scan.
Sets the internal AbortFlag. The scan loop will raise
ScanAbortedError at the next abort-check site (start of
execute_point, inside wait_for_settle, or inside
wait_for_motors).
Notes
Safe to call from a separate task or thread-pool executor while the scan is running.
Source code in src/resonance/api/core/scan.py
execute_point(point, motor_timeout=30.0, restore_motors=False, use_shutter=True, detector=None)
async
¶
Execute a single scan point.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point
|
ScanPoint
|
The point to execute. |
required |
motor_timeout
|
float
|
Maximum time in seconds to wait for motors to reach position. |
30.0
|
restore_motors
|
bool
|
If True, motor positions are restored to their pre-move values after the point completes. |
False
|
use_shutter
|
bool
|
If True, the shutter is opened and closed around acquisition. Pass False when the caller already holds the shutter open. |
True
|
detector
|
AreaDetector or None
|
If provided, a 2D detector image is acquired after AI acquisition using the point's exposure_time. Shutter actuation is hardware-driven; no plan-level shutter wraps this call. |
None
|
Returns:
| Type | Description |
|---|---|
ScanResult
|
Measured values with per-channel |
Raises:
| Type | Description |
|---|---|
ScanAbortedError
|
If the abort flag is set before or during execution. |
MotorError
|
If a motor move or restore fails. |
Source code in src/resonance/api/core/scan.py
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 | |
execute_scan(scan_plan, progress=True, writer=None, detector=None)
async
¶
Execute a complete scan plan and return results as a DataFrame.
The shutter behaviour depends on scan_plan.actuate_every:
False(default): the shutter is opened once before the first point and closed after the last point (or on abort).True: the shutter is actuated individually for every point viaexecute_point.
Interrupt modes
Programmatic: call await executor.abort() from any async context.
The AbortFlag is checked at the start of each point;
ScanAbortedError is raised and partial results are returned.
Jupyter / IPython: create this coroutine as an asyncio.Task and
call await bl.abort_scan() from another cell to set the abort
flag and stop after the current point.
asyncio.CancelledError: raised when the Task is cancelled via
task.cancel(). Partial results are returned if any points
completed; otherwise the error is re-raised.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scan_plan
|
ScanPlan
|
Validated scan plan to execute. |
required |
progress
|
bool
|
If True and tqdm is installed, show an async progress bar. Falls back to simple per-point print statements. |
True
|
writer
|
RunWriter or None
|
If provided, scalar scan data (motor positions, AI means, exposure, timestamps) are written to the open beamtime database. The writer must not have an open run before this method is called; it will call open_run, open_stream, write_event, and close_run internally. |
None
|
detector
|
AreaDetector or None
|
If provided, a 2D image is acquired at each scan point and written to the "detector_image" field in the primary stream. Requires writer to be set for persistence. Shutter is hardware-driven. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per completed point with motor positions, per-channel mean/std columns, exposure time, and timestamp. |
Notes
TODO: add Bluesky-compatible start/stop document emission once RunWriter is stable¶
Source code in src/resonance/api/core/scan.py
394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 | |