Getting Started¶
At the moment pybhspc only provides low-level bindings to SPCM-DLL functions. Using these requires familiarizing yourself with the SPCM-DLL documentation.
Here we demonstrate a basic FIFO (time tag stream) mode acquisition.
Setting Up¶
First, let's import the package, and the module spcm
(as well as a few
others that we will use below):
import bh_spc
from bh_spc import spcm
import time
import matplotlib.pyplot as plt
import numpy as np
The first step in using the SPCM functions is to initialize the library. This
initializes both the library itself and the available devices. Initialization
requires an .ini
file containing mode selection (hardware or simulator) and
acquisition parameters.
In a real acquisition, you might want to load an .ini
file with the desired
parameters (possibly one saved in the SPCM application). Here we will use
pybhspc's utility functions to provide a temporary .ini
file that does
nothing but select the DLL operation mode (in this case simulation of
SPC-180NX):
with bh_spc.ini_file(
bh_spc.minimal_spcm_ini(spcm.DLLOperationMode.SIMULATE_SPC_180NX)
) as ini:
spcm.init(ini)
To run this code with a real device, you would replace SIMULATE_SPC_180NX
with HARDWARE
.
SPCM-DLL distinguishes multiple installed SPC boards ("modules") using a module number ranging from 0 to 31. In simulation mode, all of these numbers are valid. In hardware mode, if you have a single SPC board, it should be module number 0. Since we will be specifying this many times, let's define a variable:
mod_no = 0
It is a good idea to confirm that initialization of the module was successful. The following will also give the reason for failure if initialization was unsuccessful:
spcm.get_init_status(mod_no)
<InitStatus.OK: 0>
Getting and Setting Parameters¶
Now let's set the SPC module's mode to FIFO mode. For most non-ancient SPC boards, FIFO mode is mode 1 (different numbers are required for SPC-600, 630, and 130 (but not 130EM/IN)):
spcm.set_parameter(mod_no, spcm.ParID.MODE, 1)
Let's take a look at all the parameters:
params = spcm.get_parameters(mod_no)
for par, val in params.items():
print(f"{par} = {val}")
cfd_limit_low = -0.0 cfd_limit_high = 80.0 cfd_zc_level = 0.0 cfd_holdoff = 5.0 sync_zc_level = 0.0 sync_freq_div = 4 sync_holdoff = 4.0 sync_threshold = -19.60784339904785 tac_range = 50.033573150634766 tac_gain = 1 tac_offset = 0.0 tac_limit_low = 10.196078300476074 tac_limit_high = 80.0 adc_resolution = 12 ext_latch_delay = 0 collect_time = 0.009999999776482582 display_time = 1.0 repeat_time = 10.0 stop_on_time = 1 stop_on_ovfl = 1 dither_range = 0 count_incr = 1 mem_bank = 0 dead_time_comp = 0 scan_control = 0 routing_mode = 0 tac_enable_hold = 0.0 mode = 1 scan_size_x = 1 scan_size_y = 1 scan_rout_x = 1 scan_rout_y = 1 scan_polarity = 0 scan_flyback = 65537 scan_borders = 0 pixel_time = 2.0000000233721948e-07 pixel_clock = 0 line_compression = 1 trigger = 0 ext_pixclk_div = 1 rate_count_time = 1.0 macro_time_clk = 0 add_select = 0 adc_zoom = 0 xy_gain = 1 img_size_x = 1 img_size_y = 1 img_rout_x = 1 img_rout_y = 1 master_clock = 2 adc_sample_delay = 0 detector_type = 1 tdc_control = 0 chan_enable = 4191231 chan_slope = 0 chan_spec_no = 34835 tdc_offset1 = 0.0 tdc_offset2 = 0.0 tdc_offset3 = 0.0 tdc_offset4 = 0.0
Note that not all of these parameters have meaning in every SPC model, and many don't apply at all to FIFO mode.
In an actual FIFO acquisition with hardware, you will almost certainly need
to adjust some or all of these: cfd_*
, sync_*
, tac_*
,
ext_latch_delay
, collect_time
, stop_on_time
, dither_range
,
routing_mode
, trigger
, and macro_time_clk
. See the BH documentation and
TCSPC Handbook for details.
# For this demonstration, we will turn off `stop_on_time`, mostly because it
# does not appear to work in simulation mode. By turning it off, running this
# example on real hardware should behave the same way.
params.stop_on_time = 0
# (Set other parameters here!)
spcm.set_parameters(mod_no, params)
It is also a good idea to read out all the parameters after making changes, because some parameters will be snapped to allowed values. We'll skip that here for brevity.
Acquiring Data¶
Now let's acquire some data. In FIFO mode, it is necessary to frequently read out the available data during an acquisition ("measurement") so that the FIFO (first-in first-out) buffer of the device does not overflow (FIFO overflow can be detected, but that is not covered here).
In this simple example, let's just run the acquisition for a short, fixed time (the duration is software-controlled here, so it won't be precise):
duration = 0.1 # s
buf_size = 32768 # Max number of 16-bit words in a single read.
spcm.start_measurement(mod_no)
start_time = time.monotonic()
data = [] # Collect arrays of data into a list.
while True:
elapsed = time.monotonic() - start_time
if elapsed >= duration:
spcm.stop_measurement(mod_no)
break
buf = spcm.read_fifo_to_array(mod_no, buf_size)
if len(buf):
data.append(buf)
if len(buf) < buf_size: # We've read all there is to read.
time.sleep(0.001)
# Make sure to read the data that arrived after stopping (if you need it).
while True:
buf = spcm.read_fifo_to_array(mod_no, buf_size)
if not len(buf):
break
data.append(buf)
read_fifo_to_array()
returns an array.array
of unsigned 16-bit numbers,
but the event records are 32-bit. Numpy can be used to concatenate all the
acquired data into a single array of 32-bit records:
records = np.concatenate(data).view(np.uint32)
len(records)
1245278
It is beyond pybhspc's job to interpret the event records. However, let's do a few simple things.
Bit 29 (with bit 0 being the least significant bit) is the GAP bit, which indicates there was a FIFO overflow. Let's make sure no overflow occurred:
had_gap = np.any(np.bitwise_and(records, 1 << 29))
print("There was {} gap".format("a" if had_gap else "no"))
There was no gap
Photon records have bits 31 and 28 cleared. Let's count the photons:
photons = np.extract(np.bitwise_and(records, 0b1001 << 28) == 0, records)
len(photons)
1245184
And just for fun, we can plot a histogram of the photon microtimes, which are the lower 12 bits of the higher 16 bits of the records (admittedly this is not all that interesting with the simulated data):
max_12bit = (1 << 12) - 1 # 4095
microtimes = np.bitwise_and(np.right_shift(photons, 16), max_12bit)
# Reverse the microtimes by subtracting from the max value, because the raw
# microtime is measured from photon to SYNC, not SYNC to photon.
microtimes = max_12bit - microtimes
h = plt.hist(microtimes, bins=64)