Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion doc/pvs.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,25 @@ This is `num_periods * num_spectra * num_time_channels * 8 bytes`.

These PVs provide the X points (the time-of-flight bin centres) for a time of flight spectrum. This array has length `NUMTIMECHANNELS`.

### `SPEC:<period>:<spectrum>:XE`

These PVs provide the X bin *edges* for a time of flight spectrum. This array has length `NUMTIMECHANNELS + 1`.

### `SPEC:<period>:<spectrum>:Y`

These PVs provide the Y points (the number of counts in the relevant bin) for a time of flight spectrum. This array has length `NUMTIMECHANNELS`.
These PVs provide the normalised Y data (the number of counts in the relevant bin, divided by bin width) for a time of
flight spectrum. This array has length `NUMTIMECHANNELS`.

### `SPEC:<period>:<spectrum>:YC`

These PVs provide the raw Y data (the number of counts in the relevant bin) for a time of flight spectrum.
This array has length `NUMTIMECHANNELS`.

::::{important}
`<period>` is {external+ibex_developers_manual:doc}`one-indexed in all user-facing PVs <specific_iocs/datastreaming/ADRs/007_periods>`
for backwards compatibility, so the PV `SPEC:1:57:X` will return data that was streamed with `period=0` in Kafka.
Asking for period zero, as in a PV like `SPEC:0:57:X`, will return data for the *current* period.
::::
:::

## Meta-diagnostics
Expand Down
5 changes: 5 additions & 0 deletions src/kafka_dae_diagnostics/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ class Data:
Veto names, as a numpy array of strings.
"""

current_period: int = 0
"""
The zero-indexed period into which the most recent data was received.
"""

@property
def enabled_vetos_array(self) -> npt.NDArray[np.int32]:
"""Array describing whether each of the 32 veto bits is currently enabled."""
Expand Down
2 changes: 2 additions & 0 deletions src/kafka_dae_diagnostics/kafka/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ def handle_pu00(data: Data, msg: NonEmptyMessage) -> None:

data.raw_frames += 1
data.raw_uah += proton_charge
data.current_period = period

try:
data.raw_frames_pd[period] += 1
data.raw_uah_pd[period] += proton_charge
Expand Down
28 changes: 24 additions & 4 deletions src/kafka_dae_diagnostics/pvs/spectrum_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(self, prefix: str, data: Data) -> None:
"""
self._data = data
self._prefix = prefix
self._channel_regex = re.compile(rf"^{re.escape(prefix)}SPEC:(\d+):(\d+):([XY])$")
self._channel_regex = re.compile(rf"^{re.escape(prefix)}SPEC:(\d+):(\d+):(X|Y|XE|YC)$")

def testChannel(self, name: str) -> bool | str:
"""Test whether a channel with the given name can be served by this handler.
Expand All @@ -41,7 +41,7 @@ def testChannel(self, name: str) -> bool | str:
match = self._channel_regex.fullmatch(name)
return (
match is not None
and int(match.group(1)) < self._data.num_periods
and int(match.group(1)) <= self._data.num_periods
and int(match.group(2)) < self._data.num_spectra
)

Expand All @@ -57,22 +57,42 @@ def makeChannel(self, name: str, peer: str) -> SharedPV:

match = self._channel_regex.fullmatch(name)
assert match is not None, "No match in makeChannel after there was a match in testChannel?"
period = int(match.group(1))
user_period = int(match.group(1))
det = int(match.group(2))
typ = match.group(3)

callback_id = f"{name}#{uuid.uuid4()}"

def extract_data(
typ: str = typ, period: int = period, det: int = det
typ: str = typ, user_period: int = user_period, det: int = det
) -> npt.NDArray[np.float64]:

# user_period starts from 1, with 0 corresponding to "current period".
# Internally, the histogram is zero-indexed.
if user_period == 0:
period = self._data.current_period
else:
period = user_period - 1

match typ:
case "Y":
# Counts in each histogram bin, divided by histogram
# bin width. Units: counts/us
return (
self._data.spectra[period][det]
/ (self._data.bin_boundaries[1:] - self._data.bin_boundaries[:-1])
).astype(np.float64)
case "YC":
# Raw counts in each histogram bin. Units: counts
return self._data.spectra[period][det]
case "X":
# Bin-centres along the x (time-of-flight) dimension. Units: ns
return (
(self._data.bin_boundaries[1:] + self._data.bin_boundaries[:-1]) / 2
).astype(np.float64)
case "XE":
# Bin-edges along the x (time-of-flight) dimension. Units: ns
return self._data.bin_boundaries.astype(np.float64)
case _: # pragma: no cover (unreachable)
raise ValueError(f"Unknown channel type: {typ}")

Expand Down
7 changes: 6 additions & 1 deletion tests/pvs/test_spectrum_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@ def handler(data: Data):
def test_spectrum_handler_test_channel(handler: SpectrumHandler):
assert handler.testChannel("UNITTEST:SPEC:0:0:X")
assert handler.testChannel("UNITTEST:SPEC:0:0:Y")
assert handler.testChannel("UNITTEST:SPEC:0:0:XE")
assert handler.testChannel("UNITTEST:SPEC:0:0:YC")
assert not handler.testChannel("UNITTEST:SPEC:0:0:")
assert not handler.testChannel("UNITTEST:SPEC:0:0:Y2")
assert not handler.testChannel("BLAHBLAH:SPEC:0:0:Y")
assert not handler.testChannel("BLAHBLAH:SPEC:0:0:Y")
assert not handler.testChannel("some_random_pv")


@pytest.mark.parametrize("pvname", ["UNITTEST:SPEC:0:0:X", "UNITTEST:SPEC:0:0:Y"])
@pytest.mark.parametrize(
"pvname",
["UNITTEST:SPEC:0:0:X", "UNITTEST:SPEC:0:0:Y", "UNITTEST:SPEC:1:0:XE", "UNITTEST:SPEC:1:0:YC"],
)
def test_spectrum_handler_make_channel(handler: SpectrumHandler, pvname: str, data: Data):
assert len(data.callbacks) == 0
pv = handler.makeChannel(pvname, "localhost")
Expand Down
Loading