@@ -63,13 +63,7 @@ def get_required_timespan(self) -> float:
6363
6464 def extract (self , data : sc .DataArray ) -> Any :
6565 """Extract the latest value from the data, unwrapped."""
66- # Check if data has the concat dimension
67- if not hasattr (data , 'dims' ) or self ._concat_dim not in data .dims :
68- # Data doesn't have concat dim - already a single frame
69- return data
70-
71- # Extract last frame along concat dimension
72- return data [self ._concat_dim , - 1 ]
66+ return data [self ._concat_dim , - 1 ] if self ._concat_dim in data .dims else data
7367
7468
7569class FullHistoryExtractor (UpdateExtractor ):
@@ -110,36 +104,40 @@ def __init__(
110104 self ._aggregation = aggregation
111105 self ._concat_dim = concat_dim
112106 self ._aggregator : Callable [[sc .DataArray , str ], sc .DataArray ] | None = None
107+ self ._duration : sc .Variable | None = None
113108
114109 def get_required_timespan (self ) -> float :
115110 """Return the required window duration."""
116111 return self ._window_duration_seconds
117112
118113 def extract (self , data : sc .DataArray ) -> Any :
119114 """Extract a window of data and aggregate over the time dimension."""
120- # Calculate cutoff time using scipp's unit handling
115+ # Calculate cutoff time
121116 time_coord = data .coords [self ._concat_dim ]
122- latest_time = time_coord [ - 1 ]
123- duration = sc .scalar (self ._window_duration_seconds , unit = 's' ).to (
124- unit = time_coord .unit
125- )
117+ if self . _duration is None :
118+ self . _duration = sc .scalar (self ._window_duration_seconds , unit = 's' ).to (
119+ unit = time_coord .unit
120+ )
126121
127122 # Estimate frame period from median interval to handle timing noise.
128123 # Shift cutoff by half period to place boundary between frame slots,
129124 # avoiding inclusion of extra frames due to timing jitter.
125+ latest_time = time_coord [- 1 ]
130126 if len (time_coord ) > 1 :
131127 intervals = time_coord [1 :] - time_coord [:- 1 ]
132128 median_interval = sc .median (intervals )
133- cutoff_time = latest_time - duration + 0.5 * median_interval
129+ cutoff_time = latest_time - self . _duration + 0.5 * median_interval
134130 # Clamp to ensure at least latest frame included
135131 # (handles narrow windows where duration < median_interval)
136132 if cutoff_time > latest_time :
137133 cutoff_time = latest_time
138134 else :
139135 # Single frame: use duration-based cutoff
140- cutoff_time = latest_time - duration
136+ cutoff_time = latest_time - self . _duration
141137
142- # Use label-based slicing with inclusive lower bound
138+ # Use label-based slicing with inclusive lower bound. If timestamps were precise
139+ # we would actually want exclusive lower bound, but since there is jitter anyway
140+ # the cutoff shift above should handle that well enough.
143141 windowed_data = data [self ._concat_dim , cutoff_time :]
144142
145143 # Resolve and cache aggregator function on first call
0 commit comments