diff --git a/src/pandablocks/hdf.py b/src/pandablocks/hdf.py index 9633c943..541f67a0 100644 --- a/src/pandablocks/hdf.py +++ b/src/pandablocks/hdf.py @@ -198,11 +198,7 @@ def mean_callable(data): return (data[column_name] * field.scale / gate_duration) + field.offset return mean_callable - elif ( - raw - and not field.is_pcap_bits_or_samples - and (field.scale != 1 or field.offset != 0) - ): + elif raw and field.has_scale_or_offset: return lambda data: data[column_name] * field.scale + field.offset else: return lambda data: data[column_name] diff --git a/src/pandablocks/responses.py b/src/pandablocks/responses.py index f68039de..ed18e1d6 100644 --- a/src/pandablocks/responses.py +++ b/src/pandablocks/responses.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Optional, Tuple @@ -238,29 +238,31 @@ class FieldCapture: name: str type: np.dtype capture: str - scale: Optional[float] - offset: Optional[float] - units: Optional[str] + scale: Optional[float] = field(default=None) + offset: Optional[float] = field(default=None) + units: Optional[str] = field(default=None) - @property - def raw_mode_dataset_dtype(self) -> np.dtype: - """We use double for all dtypes, - unless the field is a PCAP.BITS or PCAP.SAMPLES.""" - - if self.is_pcap_bits_or_samples: - return self.type - - if None in (self.scale, self.offset, self.units): + def __post_init__(self): + sou = (self.scale, self.offset, self.units) + if sou != (None, None, None) and None in sou: raise ValueError( - "If any of `scale`, `offset`, or `units` is set, all must be set" + f"If any of `scale={self.scale}`, `offset={self.offset}`" + f", or `units={self.units}` is set, all must be set." ) - return np.dtype("float64") + @property + def raw_mode_dataset_dtype(self) -> np.dtype: + """We use double for all dtypes that have scale and offset.""" + if self.scale is not None and self.offset is not None: + return np.dtype("float64") + return self.type @property - def is_pcap_bits_or_samples(self) -> bool: + def has_scale_or_offset(self) -> bool: """Return True if this field is a PCAP.BITS or PCAP.SAMPLES field""" - return self.scale is None and self.offset is None and self.units is None + return (self.scale is not None and self.offset is not None) and ( + self.scale != 1 or self.offset != 0 + ) class Data: diff --git a/tests/test_hdf.py b/tests/test_hdf.py index 3f73ab4b..98c16aaf 100644 --- a/tests/test_hdf.py +++ b/tests/test_hdf.py @@ -71,11 +71,11 @@ def test_field_capture_pcap_bits(): units=None, ) - assert pcap_bits_frame_data.is_pcap_bits_or_samples + assert not pcap_bits_frame_data.has_scale_or_offset assert pcap_bits_frame_data.raw_mode_dataset_dtype is np.dtype("uint32") - some_other_frame_data = FieldCapture( - name="some_other_frame_data", + frame_data_without_scale_offset = FieldCapture( + name="frame_data_without_scale_offset", type=np.dtype("uint32"), capture="Value", scale=1.0, @@ -83,24 +83,60 @@ def test_field_capture_pcap_bits(): units="", ) - assert not some_other_frame_data.is_pcap_bits_or_samples - assert some_other_frame_data.raw_mode_dataset_dtype is np.dtype("float64") + assert not frame_data_without_scale_offset.has_scale_or_offset + assert frame_data_without_scale_offset.raw_mode_dataset_dtype is np.dtype("float64") - malformed_frame_data = FieldCapture( - name="malformed_frame_data", + with pytest.raises( + ValueError, + match=( + "If any of `scale=None`, `offset=0.0`, or " + "`units=` is set, all must be set" + ), + ): + _ = FieldCapture( + name="malformed_frame_data", + type=np.dtype("uint32"), + capture="Value", + scale=None, + offset=0.0, + units="", + ) + + frame_data_with_offset = FieldCapture( + name="frame_data_with_offset", type=np.dtype("uint32"), capture="Value", - scale=None, + scale=1.0, + offset=1.0, + units="", + ) + frame_data_with_scale = FieldCapture( + name="frame_data_with_scale", + type=np.dtype("uint32"), + capture="Value", + scale=1.1, offset=0.0, units="", ) - assert not some_other_frame_data.is_pcap_bits_or_samples - with pytest.raises( - ValueError, - match="If any of `scale`, `offset`, or `units` is set, all must be set", - ): - assert malformed_frame_data.raw_mode_dataset_dtype is np.dtype("float64") + assert frame_data_with_offset.has_scale_or_offset + assert frame_data_with_offset.raw_mode_dataset_dtype is np.dtype("float64") + assert frame_data_with_scale.has_scale_or_offset + assert frame_data_with_scale.raw_mode_dataset_dtype is np.dtype("float64") + + frame_data_with_scale_and_offset = FieldCapture( + name="frame_data_with_scale_and_offset", + type=np.dtype("uint32"), + capture="Value", + scale=1.1, + offset=0.0, + units="", + ) + + assert frame_data_with_scale_and_offset.has_scale_or_offset + assert frame_data_with_scale_and_offset.raw_mode_dataset_dtype is np.dtype( + "float64" + ) @pytest.mark.parametrize(