id3c.utils.flyscan_3idc_analysis ================================ .. py:module:: id3c.utils.flyscan_3idc_analysis .. autoapi-nested-parse:: Post-run analysis helpers for ``flyscan_3idc`` runs. This module pairs each detector frame with the motor's interpolated position at the frame's IOC timestamp, using the monitor streams that ``flyscan_3idc.flyscan`` sets up via ``@bpp.monitor_during_decorator``. No bluesky, no ophyd, no RunEngine — purely operates on ``BlueskyRun``-shaped objects from tiled / databroker. Usage ----- :: from tiled.client import from_profile cat = from_profile("your_profile")["your_tree"] run = cat[-1] from id3c.utils.flyscan_3idc_analysis import pair_frames_to_positions df = pair_frames_to_positions(run) # df columns: image_number, timestamp, position # df.index: absolute timestamp (float seconds since epoch) df.to_csv("scan.csv") Design notes ------------ - IOC timestamps are the system of record for pairing (per the ``flyscan_3idc`` strategy doc, Phase 0.2 / Phase 0e). The primary-stream snapshots from the plan are a progress indicator; this module's output is the high-fidelity pairing. - Empirically (verified during the 2026-06-08 commissioning session against ``adsimdet`` + ``gp:m1``): - the m1 monitor stream's record-order is interleaved across multiple CA dispatcher segments; sorting by timestamp yields a strictly increasing position trace at constant velocity in the in-scan window. - the HDF array_counter monitor stream's record-order is also interleaved, but sorting by timestamp yields strictly monotonic counter values (0, 1, 2, ..., contiguous). - The function uses linear interpolation of motor position vs motor IOC timestamp. Linear is exact for a motor at constant velocity in the in-scan window (which is the entire reason the plan sets velocity = (p_end-p_start)/(num_frames*t_period) and taxis the motor up to scan velocity before crossing p_start). - Frames whose timestamps fall outside the motor stream's time range are dropped (extrapolation is rejected, never silent). - Frames whose interpolated positions fall outside ``[p_start, p_end]`` are dropped (this is "frames captured during taxi-in / coast-out" — they're in the HDF5 file but not part of the scan). Attributes ---------- .. autoapisummary:: id3c.utils.flyscan_3idc_analysis.logger Functions --------- .. autoapisummary:: id3c.utils.flyscan_3idc_analysis.pair_frames_to_positions Module Contents --------------- .. py:data:: logger .. py:function:: pair_frames_to_positions(run) -> pandas.DataFrame Pair each in-scan HDF frame with the interpolated motor position. Reads everything from the run's start-document metadata and the standard monitor streams set up by ``flyscan_3idc.flyscan``: - ``_monitor`` — motor position vs IOC timestamp. - ``_hdf1_array_counter_monitor`` — HDF ``array_counter`` vs IOC timestamp. - ``p_start`` / ``p_end`` from ``run.metadata["start"]``. Frames captured outside ``[p_start, p_end]`` are dropped. Frames whose IOC timestamps fall outside the motor stream's time range are dropped (extrapolation never happens silently). :param run: Tiled / databroker run object. Must have ``.metadata["start"]`` with the keys ``p_start``, ``p_end``, ``flymotor_name``, ``det_name``, and must expose monitor streams named per the convention above. :type run: BlueskyRun :returns: Columns: ``image_number`` (int64, the HDF array_counter value at frame capture), ``timestamp`` (float, IOC time of capture), ``position`` (float, motor position in engineering units, linearly interpolated). Indexed by ``timestamp`` ascending. :rtype: pandas.DataFrame :raises KeyError: Required metadata key or monitor stream is missing from the run. :raises ValueError: Motor stream has fewer than 2 samples (cannot interpolate).