Code Examples¶
Time Histogram Pipe (stand-alone TDC)¶
The following example demonstrates usage of a 1D time histogram for the TDC channel input 0 of a stand-alone TDC:
// requires C99 standard or later
#include <scTDC.h>
#include <stdio.h>
int main()
{
int dd = sc_tdc_init_inifile("tdc_gpx3.ini");
if (dd < 0) { // unable to initialize hardware. dd contains error code
char error_description[ERRSTRLEN];
sc_get_err_msg(dd, error_description);
puts(error_description);
return dd;
} else { // dd is device descriptor which is used in all other functions
double tdc_binsize;
int ret = sc_tdc_get_binsize2(dd, &tdc_binsize);
if (ret < 0) { //if there is error happened.
puts("could not get binsize");
return ret;
}
printf("tdc binsize is %lf\n", tdc_binsize);
// now open a tdc_histo pipe
struct sc_pipe_tdc_histo_params_t params;
params.depth = BS32; // 32 bit per time channel (point) in the histogram
params.channel = 0; // pipe for channel #0 is requested
params.modulo = 0; // modulo is off
params.binning = 4; // histogram binning is set to 4
params.offset = 1000; // histogram starts from the 1000 time bins (see sc_tdc_get_binsize2()).
params.size = 2000; // histogram size is 2000 time bins (but note binning)!
params.accumulation_ms = 0; // accumulation is off
params.allocator_owner = NULL; // parameter for allocator cbf
params.allocator_cb = NULL; // internal allocator is used
int pipe_id = sc_pipe_open2(dd, TDC_HISTO, (void *)¶ms);
if (pipe_id < 0) { //pipe_id contains error code
char error_description[ERRSTRLEN];
sc_get_err_msg(pipe_id, error_description);
puts(error_description);
return pipe_id;
}
sc_tdc_start_measure2(dd, 1000); // start a measurement for 1000 milliseconds
unsigned *tdc_histo;
ret = sc_pipe_read2(dd, pipe_id, (void *)&tdc_histo, -1); //after the call
// tdc_histo pointer will point to the histogram data. The buffer for the
// histogram data will be allocated by an internal allocator and will be
// destroyed during the next call to sc_pipe_read2.
// -1 in the last argument means to wait infinitely (2^32 milliseconds).
if (ret < 0) { // note that this could be timeout as well
char error_description[ERRSTRLEN];
sc_get_err_msg(pipe_id, error_description);
puts(error_description);
return ret;
}
// Now, we have the data and may process it or visualize it.
// For example, tdc_histo[0] is the first histogram element,
// that contains the summed number of detected pulses for the
// basic time bins '4000', '4001', '4002', '4003' --- since
// the pipe was configured with binning 4 and offset 1000.
sc_pipe_close2(dd, pipe_id);
// Here, the data pipe is closed. NOTE: tdc_histo now points to an
// invalid memory region, that must not be accessed anymore. Any access
// to the tdc_data has to be performed before calling sc_pipe_close2().
// If necessary, make a copy before calling sc_pipe_close2().
sc_tdc_deinit2(dd); // Release hardware and resources.
return 0;
}
}
Image Pipe¶
The following example demonstrates usage of a data pipe for images ( images are targeted towards delay-line detector and camera applications and do not receive data, if a stand-alone TDC is used):
int image2d_ex() // 2d image example.
{
const uint32_t size_x = 512;
const uint32_t size_y = 512;
// initialize the hardware and get a device descriptor
int dd = sc_tdc_init_inifile("tdc_gpx3.ini");
if (dd < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(dd, error_description);
printf("error! code: %d, message: %s\n", dd, error_description);
return dd;
}
struct sc_pipe_dld_image_xy_params_t prms;
memset(&prms, 0, sizeof(prms));
prms.depth = BS32; //4 bytes per pixel in the image
prms.channel = -1; //all channels together
prms.binning = {1, 1, 1};
prms.roi = {{0,0,0}, {size_x, size_y, -1}};
// configure an "image pipe" and store the pipe id in "pd"
int pd = sc_pipe_open2(dd, DLD_IMAGE_XY, (void *)&prms);
if (pd < 0) { // check for an error
char error_description[ERRSTRLEN];
sc_get_err_msg(pd, error_description);
printf("error! code: %d, message: %s\n", pd, error_description);
sc_tdc_deinit2(dd);
return pd;
}
int ret = sc_tdc_start_measure2(dd, 200); //start 200 ms measurement
if (ret < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(ret, error_description);
printf("error! code: %d, message: %s\n", ret, error_description);
sc_pipe_close2(dd, pd);
sc_tdc_deinit2(dd);
return ret;
}
uint32_t *image;
// The following sc_pipe_read2(...) call blocks (waits) until the measurement
// is finished. The image variable is set to the image data buffer, allocated
// by the library ("internal memory allocation"). The size of the image buffer
// in bytes is roi_x * roi_y * 4, in this example 512 * 512 * 4 (see prms.roi
// and prms.depth settings).
// Deallocation happens when the next call to sc_pipe_read2(), sc_pipe_close2()
// or sc_tdc_deinit2() is made.
ret = sc_pipe_read2(dd, pd, (void **) &image, UINT32_MAX);
if (ret < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(ret, error_description);
printf("error! code: %d, message: %s\n", ret, error_description);
sc_pipe_close2(dd, pd);
sc_tdc_deinit2(dd);
return ret;
}
// Here, the application can process the image data.
for (size_t i=0; i<size_x * size_y; ++i) {
fprintf(stderr, "%08x\n", image[i]);
}
// close the pipe (this invalidates the image pointer) and the device
sc_pipe_close2(dd, pd);
sc_tdc_deinit2(dd);
fprintf(stderr, "\n");
return 0;
}
External Memory Allocation¶
Example using a statistics pipe with external allocator:
class Allocator
{
// This is the allocator class which stores all statistics in the mem_chunks_.
// Deallocation happens when the object is destroyed.
std::list <std::unique_ptr <unsigned char []> > mem_chunks_;
const size_t chunk_size_;
public:
Allocator (size_t s) :chunk_size_(s) {}
static int pre_alloc(void *p, void **u) {
return (static_cast <Allocator *> (p))->alloc(u);
}
int alloc(void **u) {
std::unique_ptr <unsigned char []> chunk(new unsigned char [chunk_size_]);
memset(&(chunk[0]), 0, chunk_size_); // initialize memory to all zeros
*u = &(chunk[0]);
mem_chunks_.push_back(std::move(chunk));
return 0;
}
};
int statistics_pipe_ex()
{
// initialize the hardware and get device descriptor
int dd = sc_tdc_init_inifile("tdc_gpx3.ini");
if (dd < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(dd, error_description);
printf("error! code: %d, message: %s\n", dd, error_description);
return dd;
}
sc_pipe_statistics_params_t prms;
memset(&prms, 0, sizeof(prms));
Allocator mem(sizeof(statistics_t));
prms.allocator_owner = static_cast <void *> (&mem);
prms.allocator_cb = &(mem.pre_alloc);
int pd = sc_pipe_open2(dd, STATISTICS, (void *)&prms);
if (pd < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(pd, error_description);
printf("error! code: %d, message: %s\n", pd, error_description);
sc_tdc_deinit(dd);
return pd;
}
int ret = sc_tdc_start_measure2(dd, 200); // start 200 ms measure
if (ret < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(ret, error_description);
printf("error! code: %d, message: %s\n", ret, error_description);
sc_pipe_close2(dd, pd);
sc_tdc_deinit(dd);
return ret;
}
statistics_t *stat;
// During measurement, the scTDC library calls the Allocator::pre_alloc
// function, which allocates memory to hold one instance of a statistics_t
// record, saves the memory block in the list and returns it to the library.
// The next function blocks until the measurement is finished. The application
// gets the pointer to the statistics data from the sc_pipe_read2() function.
// The memory is deallocated when the 'mem' variable is destroyed (which
// happens automatically at the end of main()). If the application calls
// sc_tdc_start_measure2() and sc_pipe_read2() several times, here the 'mem'
// object accumulates memory chunks in the list.
// An alternative implementation of the Allocator class could also choose
// to just allocate a single memory buffer and return this buffer from the
// allocator callback multiple times. The library modifies the buffer by
// adding values to its elements, rather than overwriting elements with new
// values. This results in an accumulating effect across multiple measurements
// (the library does not reset the buffer to zero, so the zeroing remains
// under the control of the application developer).
// This works for images and histograms as well.
int ret = sc_pipe_read2(dd, pd, (void *)&stat, UINT32_MAX);
if (ret < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(ret, error_description);
printf("error! code: %d, message: %s\n", ret, error_description);
sc_pipe_close2(dd, pd);
sc_tdc_deinit(dd);
return ret;
}
// Here we can do something with the statistics data
printf("counts_read[0][0] = %d\n", stat->counts_read[0][0]);
sc_pipe_close2(dd, pd);
sc_tdc_deinit2(dd);
// Here, the statistics data is still accessible because the 'mem' object is
// still on the stack.
// In case of 'internal' memory allocation, the pointer to the statistics data
// would be invalid at this point.
return 0;
}
User Callbacks Pipe¶
The following example shows how to set up the user callbacks pipe to process TDC or DLD data in a sequence-of-events form:
#include <stdio.h>
#include <stdlib.h>
#include <scTDC.h>
struct sc_DeviceProperties3 sizes;
/* Include an actual Semaphore implementation, such as in
* https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
*/
class Semaphore;
Semaphore sem;
void cb_start(void *p) {
/* this function gets called every time a measurement starts */
}
void cb_end(void *p) {
/* this function gets called every time a measurement finishes */
sem.signal();
}
void cb_millis(void *p) {
/* this function gets called every time a millisecond has ellapsed as
* tracked by the hardware */
}
void cb_stat(void *p, const struct statistics_t *stat) {
/* this function gets called every time statistics data is received,
* usually at the end of every measurement, but before the end-of-measurement
* callback */
}
void cb_tdc_event
(void *priv,
const struct sc_TdcEvent *const event_array,
size_t event_array_len)
{
const char *buffer = (const char *) event_array;
size_t j;
for (j=0; j<event_array_len; ++j) {
const struct sc_TdcEvent *obj =
(const struct sc_TdcEvent *)(buffer + j * sizes.tdc_event_size);
/* insert code here, that uses the TDC event data.
* obj->channel, obj->time_data ... contain information about
* the j-th TDC event provided during this call.
*/
}
}
void cb_dld_event
(void *priv,
const struct sc_DldEvent *const event_array,
size_t event_array_len)
{
const char *buffer = (const char *) event_array;
size_t j;
for (j=0; j<event_array_len; ++j) {
const struct sc_DldEvent *obj =
(const struct sc_DldEvent *)(buffer + j * sizes.tdc_event_size);
/* insert code here, that uses the DLD event data.
* obj->dif1, obj->dif2, obj->sum ... contain information about
* the j-th DLD event provided during this call. */
}
}
int main()
{
int dd;
int ret;
struct PrivData priv_data;
char *buffer;
struct sc_pipe_callbacks *cbs;
struct sc_pipe_callback_params_t params;
int pd;
dd = sc_tdc_init_inifile("tdc_gpx3.ini");
if (dd < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(dd, error_description);
printf("error! code: %d, message: %s\n", dd, error_description);
return dd;
}
ret = sc_tdc_get_device_properties(dd, 3, &sizes);
if (ret < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(ret, error_description);
printf("error! code: %d, message: %s\n", ret, error_description);
return ret;
}
buffer = calloc(1, sizes.user_callback_size);
cbs = (struct sc_pipe_callbacks *)buffer;
cbs->priv = &priv_data;
cbs->start_of_measure = cb_start;
cbs->end_of_measure = cb_end;
cbs->millisecond_countup = cb_millis;
cbs->statistics = cb_stat;
cbs->tdc_event = cb_tdc_event;
cbs->dld_event = cb_dld_event;
params.callbacks = cbs;
pd = sc_pipe_open2(dd, USER_CALLBACKS, ¶ms);
if (pd < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(pd, error_description);
printf("error! code: %d, message: %s\n", pd, error_description);
return pd;
}
free(buffer);
ret = sc_tdc_start_measure2(dd, 1000);
if (ret < 0) {
char error_description[ERRSTRLEN];
sc_get_err_msg(ret, error_description);
printf("error! code: %d, message: %s\n", ret, error_description);
return dd;
}
/* Wait until the semaphore is signalled, which happens in our callback for
* the end-of-measurement event */
sem.wait();
sc_pipe_close2(dd, pd);
sc_tdc_deinit2(dd);
return 0;
}
Camera pipes for frame meta info, raw images and blobs¶
The following example shows the usage of the pipe types PIPE_CAM_FRAMES and PIPE_CAM_BLOBS for camera applications which enable reading of per-frame meta information, raw image data, and blob data:
#include <scTDC.h>
#include <scTDC_cam.h>
#include <scTDC_cam_types.h>
#include <scTDC_types.h>
#include <scTDC_error_codes.h>
#include <iostream>
#include <chrono>
#include "sema.h"
// Semaphore implementation available at
// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
class TestCamFramesBlobs {
Semaphore sema;
bool end_of_meas = false;
public:
enum { EXPOSURE_MICROSECS = 1000, NUMBER_OF_FRAMES = 500 };
static void static_complete_cb(void* p, int reason) {
static_cast<TestCamFramesBlobs*>(p)->complete_cb(reason);
}
void complete_cb(int reason) {
if (reason != 4) {
end_of_meas = true;
sema.signal();
}
}
void run() {
auto dd = sc_tdc_init_inifile("tdc_gpx3.ini");
if (dd < 0) {
char buf[255];
sc_get_err_msg(dd, buf);
std::cout << "error during initialization : " << buf << "\n";
return;
}
sc_tdc_cam_set_exposure(dd, EXPOSURE_MICROSECS, NUMBER_OF_FRAMES);
auto pipedesc = sc_pipe_open2(dd, PIPE_CAM_FRAMES, nullptr);
if (pipedesc < 0) {
char buf[255];
sc_get_err_msg(pipedesc, buf);
std::cout << "error during pipe_open for frames: " << buf << "\n";
sc_tdc_deinit2(dd);
return;
}
auto pipedesc2 = sc_pipe_open2(dd, PIPE_CAM_BLOBS, nullptr);
if (pipedesc2 < 0) {
char buf[255];
sc_get_err_msg(pipedesc2, buf);
std::cout << "error during pipe_open for blobs: " << buf << "\n";
sc_pipe_close2(dd, pipedesc);
sc_tdc_deinit2(dd);
return;
}
auto ret2 = sc_tdc_set_complete_callback2(dd, this, static_complete_cb);
if (ret2 < 0) {
char buf[255];
sc_get_err_msg(ret2, buf);
std::cout << "error while setting complete cb : " << buf << "\n";
}
auto ret3 = sc_tdc_start_measure2(dd, 100);
if (ret3 < 0) {
char buf[255];
sc_get_err_msg(ret3, buf);
std::cout << "error during start of measurement : " << buf << "\n";
}
auto t1 = std::chrono::steady_clock::now();
while(true) {
void* buf_frames = nullptr;
void* buf_blobs = nullptr;
int ret = sc_pipe_read2(dd, pipedesc, &buf_frames, 50);
if (ret == SC_TDC_ERR_TIMEOUT) {
if (end_of_meas) {
break;
}
else {
continue;
}
}
else if (ret < 0) {
break;
}
int ret2 = sc_pipe_read2(dd, pipedesc2, &buf_blobs, 1);
if (buf_frames != nullptr) {
const auto& meta = *static_cast<sc_cam_frame_meta_t*>(buf_frames);
std::cout << "frame #" << meta.frame_idx << " width=" << meta.width
<< " height=" << meta.height << " adc=" << meta.adc
<< " time=" << meta.frame_time << " bpp="
<< ((meta.pixelformat == SC_CAM_PIXELFORMAT_UINT8) ? 8 : 16)
<< " last=" << ((meta.flags & SC_CAM_FRAME_IS_LAST_FRAME) > 0)
<< "\n";
}
else {
std::cout << "FRAME MISSING!" << std::endl; // this should not happen
}
if (buf_blobs != nullptr) {
const auto& metablobs = *static_cast<sc_cam_blob_meta_t*>(buf_blobs);
std::cout << "number of blobs: " << metablobs.nr_blobs << "\n";
if (metablobs.nr_blobs > 0) {
const auto* blobs =
reinterpret_cast<sc_cam_blob_position_t*>(
static_cast<char*>(buf_blobs) + metablobs.data_offset);
std::cout << " blob 0 : x = " << blobs[0].x << " y = " << blobs[0].y
<< "\n";
}
}
else {
std::cout << "no blobs\n";
}
if (ret >= 0 && ret2 < 0) {
std::cout << "FRAME BUT NO BLOB!\n";
}
}
sema.wait();
std::cout << "duration: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - t1).count() << " ms\n";
sc_pipe_close2(dd, pipedesc);
sc_tdc_deinit2(dd);
}
};
int main() {
TestCamFramesBlobs t;
t.run();
}