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 *)&params);
    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, &params);
  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();
}