In this lab, we build on Timestamp and Time‐over‐Threshold with List Data Buffer (Lab 10), where data from each event contains both ToT (Time‐over‐Threshold) and ToF (Time‐of‐Flight) values. Instead of saving to a file and inspecting with a hex editor, we will read the data directly in Python, decode it, and plot two histograms (one for ToT, one for ToF).
We will acquire 10,000 events from the list block in chunks, showing a simple progress indicator. Then we plot the resulting distributions side by side with Matplotlib.
Firmware Assumptions
- Your firmware has a List block (e.g.,
List_0
) that outputs:- ToT as a 32‐bit word.
- ToF as a 32‐bit word.
- Hence each “event” is 8 bytes total, or 2 × 32‐bit words in the list data stream.
- The list block is configured to capture exactly these two quantities per event, in the order:
ToT
(first 32 bits)ToF
(next 32 bits)
(If your block arrangement differs, adapt the decode logic accordingly.)
Python Script Explanation
- AddNewDevice
- We connect to the DT1260 via USB using a JSON file describing the registers/memory map.
- Parameter Setup
- We tell the list block how many events we want or set it to “free run” mode.
- In this example, we’ll manually read until we reach 10,000 total events.
- Start Acquisition
- We run
ExecuteCommand("board0:/MMCComponents/List_0.start", "")
or the equivalent.
- We run
- Read in Chunks
- We repeatedly call
sdk.ReadData("board0:/MMCComponents/List_0", buffer_list)
to retrieve events. - Each read might return a certain number of new 32‐bit words. We decode them into (ToT, ToF) pairs and store them in Python lists.
- We keep track of how many events we’ve stored and display a progress message.
- We repeatedly call
- Stop Acquisition
- Optionally we send
List_0.stop
or rely on max events.
- Optionally we send
- Plot
- After collecting 10,000 events, we create two subplots in Matplotlib:
- Left: histogram of ToT
- Right: histogram of ToF
- After collecting 10,000 events, we create two subplots in Matplotlib:
lab11_list_readout.py
Code
Key Points
- Chunked Acquisition:
We callReadData
in a loop, decoding newly arrived words into(ToT, ToF)
pairs. - Progress:
After each chunk, we print how many events have been collected so far. - Decoding:
Each event is 2 × 32‐bit words: the first for ToT, the second for ToF. - Plotting:
Two histograms side by side for the final 10,000 events.
Decoding the TOT and TOF from Raw Bytes
After reading a chunk of data from the List block, we must decode the raw bytes into the two 32-bit values representing Time-over-Threshold (ToT) and Time-of-Flight (ToF). Each event consists of two 32-bit words (8 bytes total). Here’s a snippet illustrating the process using the Python struct
module:
import struct
# valid_samples is the total number of bytes in this chunk (e.g., buffer_list.info.valid_samples).
# Each "word" is 4 bytes, so we divide by 4 to find the number of 32-bit words.
valid_words = buffer_list.info.valid_samples // 4
# Each event: 2 consecutive 32-bit words (ToT + ToF).
chunk_events = valid_words // 2
# Decode the chunk by slicing 8-byte segments (4 bytes for ToT, 4 bytes for ToF).
idx = 0
for _ in range(chunk_events):
# Read the first 4 bytes (ToT) as a 32-bit little-endian integer
word_tot = struct.unpack("<I", buffer_list.data[idx:idx+4])[0]
# Read the next 4 bytes (ToF) as a 32-bit little-endian integer
word_tof = struct.unpack("<I", buffer_list.data[idx+4:idx+8])[0]
# Advance the index by 8 bytes for the next event
idx += 8
# Append the decoded values to Python lists (arrays)
tot_data.append(word_tot)
tof_data.append(word_tof)
Explanation:
valid_samples
gives the total number of bytes in the read buffer.- We assume each 32-bit integer is 4 bytes, so
valid_words
is(valid_samples / 4)
. - Since each event is 2 such 32-bit words, we compute
chunk_events = (number of words) / 2
. - For each event, we slice 8 bytes (
idx : idx + 8
) from the buffer and unpack them withstruct.unpack("<I", ...)
. The format string"<I"
decodes a 32-bit little-endian unsigned integer. - We store the decoded
ToT
andToF
in separate lists for analysis or plotting.
We can also plot the temporal differences between consecutive ToF measurements to see how the signal changes from one event to the next. Below, we create a list differences
by subtracting the (i−1)-th ToF from the i-th ToF, then plot a histogram of those differences. For example:
# Compute the time differences between consecutive ToF measurements
differences = [tof_data[i] - tof_data[i-1] for i in range(1, len(tof_data))]
# Plot the differences in a histogram, with bins from 0 to 2000 in steps of 10
ax2.hist(differences, bins=range(0, 2000, 10), alpha=0.7)
ax2.set_title("ToF Differences Histogram")
ax2.grid(True)
This histogram reveals how much the ToF changes from one event to the next. If most differences are zero, it indicates consecutive events have nearly identical ToF values. A broader distribution suggests more variation in the arrival times. You can adjust the bin range (range(0, 2000, 10)
) based on your expected scale of ToF changes.
Running the Script
- Install needed Python packages:
pip install scisdk matplotlib
- Compile & Program your firmware that outputs ToT/ToF in a list block.
- Copy the matching
RegisterFile.json
from Sci-Compiler’s output into this script’s folder. - Adjust the
AddNewDevice("usb:10500", ...)
call for your board’s serial number and the correct JSON filename. - Run:
python lab11_list_readout.py
- Watch the console messages showing progress. After 10,000 events, a Matplotlib window appears with TOT and TOF histograms.
Important: If you recompile the firmware, the register addresses (and the generated JSON) might change. Always use the latest JSON file that corresponds to the bitstream you programmed on your board.
Conclusion
This method shows how to read raw events (ToT + ToF) from the DT1260 into Python, decode each event’s data, and visualize it in real time or offline. By using chunked reads, we can monitor progress and avoid overly large memory allocations, making it suitable for bigger acquisitions as well.