0
# Flyers and Continuous Scanning
1
2
Interfaces and implementations for continuous scanning and fly scanning where data is collected while motors are in motion or during continuous acquisitions. The flyer interface enables coordinated data collection from multiple devices during uninterrupted operations.
3
4
## Capabilities
5
6
### Flyer Interface
7
8
The base protocol for all flyer devices, defining the standard workflow for continuous data collection operations.
9
10
```python { .api }
11
class FlyerInterface:
12
"""
13
Interface for flyer devices that collect data continuously during motion.
14
15
The flyer protocol involves four key operations:
16
1. kickoff() - Start the continuous operation
17
2. complete() - Wait for or signal completion
18
3. collect() - Retrieve collected data as events
19
4. describe_collect() - Provide metadata about collected data
20
"""
21
def kickoff(self):
22
"""
23
Start the flyer operation.
24
25
Returns:
26
StatusBase: Status indicating when flying has started
27
"""
28
29
def complete(self):
30
"""
31
Wait for flying to complete or signal completion.
32
33
Can be either a query (returns immediately if done) or
34
a command (initiates completion sequence).
35
36
Returns:
37
StatusBase: Status tracking completion
38
"""
39
40
def collect(self):
41
"""
42
Retrieve collected data as generator of proto-events.
43
44
Yields:
45
dict: Events with keys {'time', 'timestamps', 'data'}
46
- time (float): UNIX timestamp for event
47
- timestamps (dict): Per-signal timestamps
48
- data (dict): Per-signal data values
49
"""
50
51
def describe_collect(self):
52
"""
53
Describe the format of data returned by collect().
54
55
Returns:
56
dict: Nested dictionary with stream names as keys,
57
signal descriptions as values
58
"""
59
60
def collect_tables(self):
61
"""
62
Alternative data collection format (proposed).
63
64
Returns:
65
Iterable: Data organized as tables rather than events
66
"""
67
```
68
69
### Area Detector Time Series Collection
70
71
Flyer implementation for area detector time series data collection, enabling continuous acquisition of waveform data with timestamps.
72
73
```python { .api }
74
class AreaDetectorTimeseriesCollector(Device):
75
"""
76
Collects time series data from area detectors during fly scans.
77
78
This flyer collects waveform data and timestamps from area detector
79
time series plugins, supporting pause/resume functionality.
80
81
Parameters:
82
- prefix (str): EPICS PV prefix for time series records
83
- stream_name (str): Name for the data stream (default: 'primary')
84
"""
85
def __init__(self, prefix, *, stream_name='primary', **kwargs): ...
86
87
# Component signals
88
control: EpicsSignal # TSControl - acquisition control
89
num_points: EpicsSignal # TSNumPoints - number of points to collect
90
cur_point: EpicsSignalRO # TSCurrentPoint - current point counter
91
waveform: EpicsSignalRO # TSTotal - collected waveform data
92
waveform_ts: EpicsSignalRO # TSTimestamp - waveform timestamps
93
94
def kickoff(self):
95
"""Start time series collection."""
96
97
def complete(self):
98
"""Wait for collection to finish."""
99
100
def collect(self):
101
"""Yield time series data as events."""
102
103
def describe_collect(self):
104
"""Describe time series data format."""
105
106
def pause(self):
107
"""Pause data collection."""
108
109
def resume(self):
110
"""Resume data collection."""
111
```
112
113
### Waveform Collection
114
115
General-purpose waveform collector for devices that provide time-stamped waveform data.
116
117
```python { .api }
118
class WaveformCollector(Device):
119
"""
120
Collects waveform data with timestamps during fly scans.
121
122
Used with timestamp devices and other waveform-generating hardware.
123
Supports configurable data interpretation (time vs indices).
124
125
Parameters:
126
- prefix (str): EPICS PV prefix for waveform records
127
- stream_name (str): Name for the data stream
128
"""
129
def __init__(self, prefix, *, stream_name='primary', **kwargs): ...
130
131
# Component signals
132
select: EpicsSignal # Sw-Sel - waveform selection
133
reset: EpicsSignal # Rst-Sel - reset control
134
waveform_count: EpicsSignalRO # Val:TimeN-I - point count
135
waveform: EpicsSignalRO # Val:Time-Wfrm - waveform data
136
waveform_nord: EpicsSignalRO # Val:Time-Wfrm.NORD - number of points
137
data_is_time: Signal # Configure data interpretation
138
139
def kickoff(self):
140
"""Start waveform collection."""
141
142
def complete(self):
143
"""Wait for waveform collection to finish."""
144
145
def collect(self):
146
"""Yield waveform data as events."""
147
148
def describe_collect(self):
149
"""Describe waveform data format."""
150
```
151
152
### Monitor-Based Flying
153
154
Mixin class that implements flyer functionality by monitoring device attributes during continuous operations.
155
156
```python { .api }
157
class MonitorFlyerMixin:
158
"""
159
Flyer mixin that monitors specified attributes during flight.
160
161
This mixin enables any device to act as a flyer by subscribing to
162
attribute changes and collecting the monitored data.
163
164
Parameters:
165
- monitor_attrs (list): Signal attribute names to monitor during flight
166
- stream_names (dict): Mapping of attributes to stream names
167
- pivot (bool): Data organization mode:
168
- False: Single event with arrays of all values
169
- True: Separate events for each value/timestamp pair
170
"""
171
def __init__(self, *, monitor_attrs=None, stream_names=None, pivot=False, **kwargs): ...
172
173
def kickoff(self):
174
"""
175
Start monitoring specified attributes.
176
177
Subscribes to attribute changes and begins collecting data.
178
179
Returns:
180
StatusBase: Completed status (monitoring starts immediately)
181
"""
182
183
def complete(self):
184
"""
185
Signal that monitoring should stop.
186
187
Returns:
188
StatusBase: Status tracking monitor completion
189
"""
190
191
def collect(self):
192
"""
193
Yield collected monitor data as events.
194
195
Data organization depends on pivot setting:
196
- pivot=False: Single event with all collected data arrays
197
- pivot=True: One event per monitored value/timestamp pair
198
199
Yields:
200
dict: Events containing monitored attribute data
201
"""
202
203
def describe_collect(self):
204
"""
205
Describe monitored data format.
206
207
Returns:
208
dict: Description of monitored signals by stream name
209
"""
210
211
def pause(self):
212
"""Pause monitoring (unsubscribe from attributes)."""
213
214
def resume(self):
215
"""Resume monitoring (resubscribe to attributes)."""
216
```
217
218
### Simulation and Testing Flyers
219
220
Mock flyer implementations for testing and development without requiring hardware.
221
222
```python { .api }
223
class TrivialFlyer(Device):
224
"""
225
Simple flyer that returns empty data for testing.
226
227
Complies with flyer API but generates no actual data.
228
Used for testing flyscan protocols and timing.
229
"""
230
def __init__(self, **kwargs): ...
231
232
def kickoff(self):
233
"""Return completed status immediately."""
234
235
def complete(self):
236
"""Return completed status immediately."""
237
238
def collect(self):
239
"""Yield 100 empty events for testing."""
240
241
class MockFlyer(Device):
242
"""
243
Comprehensive mock flyer for testing flyscan APIs.
244
245
Simulates realistic flyer behavior with threaded execution,
246
motor movement, and detector data collection.
247
248
Parameters:
249
- detector (Device): Detector device to collect from
250
- motor (Device): Motor device to move during scan
251
- start (float): Starting motor position
252
- stop (float): Ending motor position
253
- num_points (int): Number of data points to collect
254
"""
255
def __init__(self, detector, motor, *, start=0, stop=1, num_points=10, **kwargs): ...
256
257
def kickoff(self):
258
"""Start threaded motor movement and data collection."""
259
260
def complete(self):
261
"""Wait for scan completion."""
262
263
def collect(self):
264
"""Yield collected motor and detector data."""
265
```
266
267
## Usage Examples
268
269
### Basic Flyer Operation
270
271
```python
272
from ophyd import Device
273
from ophyd.flyers import MonitorFlyerMixin
274
275
# Create a flyer device that monitors motor position
276
class MotorFlyer(MonitorFlyerMixin, Device):
277
def __init__(self, motor, **kwargs):
278
self.motor = motor
279
super().__init__(
280
monitor_attrs=['motor.position'],
281
stream_names={'motor.position': 'primary'},
282
**kwargs
283
)
284
285
# Use the flyer
286
motor_flyer = MotorFlyer(my_motor, name='motor_flyer')
287
288
# Standard flyer workflow
289
kickoff_status = motor_flyer.kickoff() # Start monitoring
290
# ... motor moves or other operations happen ...
291
complete_status = motor_flyer.complete() # Signal completion
292
wait(complete_status) # Wait for completion
293
294
# Collect the data
295
data_events = list(motor_flyer.collect())
296
data_description = motor_flyer.describe_collect()
297
```
298
299
### Area Detector Time Series
300
301
```python
302
from ophyd.flyers import AreaDetectorTimeseriesCollector
303
304
# Create time series collector
305
ts_collector = AreaDetectorTimeseriesCollector(
306
'XF:DET:TS:',
307
name='ts_collector',
308
stream_name='detector_timeseries'
309
)
310
311
# Configure collection
312
ts_collector.num_points.set(1000) # Collect 1000 points
313
314
# Execute flyer protocol
315
kickoff_status = ts_collector.kickoff()
316
wait(kickoff_status) # Wait for acquisition to start
317
318
complete_status = ts_collector.complete()
319
wait(complete_status) # Wait for acquisition to finish
320
321
# Retrieve time series data
322
events = list(ts_collector.collect())
323
for event in events:
324
print(f"Time: {event['time']}")
325
print(f"Data: {event['data']}")
326
```
327
328
### Monitor-Based Multi-Device Flying
329
330
```python
331
# Monitor multiple devices during a scan
332
class MultiDeviceFlyer(MonitorFlyerMixin, Device):
333
def __init__(self, motor, detector, **kwargs):
334
self.motor = motor
335
self.detector = detector
336
super().__init__(
337
monitor_attrs=['motor.position', 'detector.value'],
338
stream_names={
339
'motor.position': 'primary',
340
'detector.value': 'detector_stream'
341
},
342
pivot=True, # Separate events for each reading
343
**kwargs
344
)
345
346
flyer = MultiDeviceFlyer(my_motor, my_detector, name='multi_flyer')
347
348
# Use with bluesky plans
349
from bluesky.plans import fly
350
from bluesky import RunEngine
351
352
RE = RunEngine()
353
RE(fly([flyer])) # Execute fly plan with the flyer
354
```
355
356
## Integration with Bluesky
357
358
Flyers integrate seamlessly with Bluesky's experiment orchestration:
359
360
- **Run Engine Compatibility**: All flyers work with Bluesky's RunEngine
361
- **Event Model**: Data follows Bluesky's event model for analysis pipeline integration
362
- **Stream Organization**: Multiple data streams allow flexible data organization
363
- **Status Protocol**: Uses ophyd's StatusBase for proper asynchronous operation tracking
364
- **Pause/Resume**: Supports experiment pause/resume functionality where implemented
365
366
Flyers enable sophisticated data collection scenarios including fly scans, continuous monitoring, and coordinated multi-device acquisition during uninterrupted motion or operations.