Skip to content

Commit d9407b9

Browse files
committed
Add support for non-numeric keys in KeyData.as_single_value via uniqueness check
1 parent d02f80b commit d9407b9

2 files changed

Lines changed: 36 additions & 2 deletions

File tree

extra_data/keydata.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ def data_counts(self, labelled=True):
327327

328328
return res
329329

330-
def as_single_value(self, rtol=1e-5, atol=0.0, reduce_by='median'):
330+
def as_single_value(self, rtol=1e-5, atol=0.0, reduce_by=None):
331331
"""Retrieve a single reduced value if within tolerances.
332332
333333
The relative and absolute tolerances *rtol* and *atol* work the
@@ -340,13 +340,33 @@ def as_single_value(self, rtol=1e-5, atol=0.0, reduce_by='median'):
340340
the first value encountered. By default, 'median' is used.
341341
342342
If within tolerances, the reduced value is returned.
343+
344+
For non-numerical keys like strings, the method instead always
345+
checks for uniqueness and returns such a value, if present.
343346
"""
344347

345348
data = self.ndarray()
346349

347350
if len(data) == 0:
348351
raise NoDataError(self.source, self.key)
349352

353+
if not np.issubdtype(self.dtype, np.number):
354+
# Handle non-numeric types first.
355+
356+
if reduce_by is not None:
357+
raise TypeError('custom reduce method not supported for '
358+
'non-numeric type')
359+
360+
unique_values = np.unique(data, axis=None)
361+
362+
if len(unique_values) > 1:
363+
raise ValueError(f'str values are not unique: {unique_values}')
364+
365+
return unique_values[0]
366+
367+
elif reduce_by is None:
368+
reduce_by = 'median'
369+
350370
if callable(reduce_by):
351371
value = reduce_by(data)
352372
elif isinstance(reduce_by, str) and hasattr(np, reduce_by):

extra_data/tests/test_keydata.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .mockdata import write_file
1212
from .mockdata.xgm import XGM
1313

14+
1415
def test_get_keydata(mock_spb_raw_run):
1516
run = RunDirectory(mock_spb_raw_run)
1617
print(run.instrument_sources)
@@ -43,6 +44,7 @@ def test_get_keydata(mock_spb_raw_run):
4344
with pytest.raises(TypeError):
4445
iter(xgm_beam_x)
4546

47+
4648
def test_select_trains(mock_spb_raw_run):
4749
run = RunDirectory(mock_spb_raw_run)
4850
xgm_beam_x = run['SPB_XTD9_XGM/DOOCS/MAIN', 'beamPosition.ixPos.value']
@@ -277,6 +279,7 @@ def test_single_value(mock_sa3_control_data, monkeypatch):
277279

278280
imager = f['SA3_XTD10_IMGFEL/CAM/BEAMVIEW:daqOutput', 'data.image.pixels']
279281
flux = f['SA3_XTD10_XGM/XGM/DOOCS', 'pulseEnergy.photonFlux']
282+
state = f['SA3_XTD10_XGM/XGM/DOOCS', 'state']
280283

281284
# Try without data for a source and key.
282285
with pytest.raises(NoDataError):
@@ -309,6 +312,17 @@ def test_single_value(mock_sa3_control_data, monkeypatch):
309312
assert flux.as_single_value(rtol=1, reduce_by=np.mean) == np.mean(data)
310313
assert flux.as_single_value(atol=len(data)-1, reduce_by='first') == 0
311314

315+
# Try strings.
316+
assert state[5:].as_single_value() == 'ON'
317+
318+
with pytest.raises(ValueError):
319+
# Contains two unique values.
320+
state.as_single_value()
321+
322+
with pytest.raises(TypeError):
323+
# Does not accept reduce_by
324+
state.as_single_value(reduce_by='mean')
325+
312326
# Try vector data.
313327
intensity = f['SA3_XTD10_XGM/XGM/DOOCS:output', 'data.intensityTD']
314328
data = np.repeat(data, intensity.shape[1]).reshape(-1, intensity.shape[-1])
@@ -405,6 +419,6 @@ def test_units(mock_sa3_control_data):
405419

406420
# Check that it still works after selecting 0 trains
407421
assert xgm_intensity.select_trains(np.s_[:0]).units == 'μJ'
408-
422+
409423
# units are added to xarray's attributes
410424
assert xgm_intensity.xarray().attrs['units'] == 'μJ'

0 commit comments

Comments
 (0)