Skip to content

TDC Renderer

TdcRenderer converts raw time-to-digital-converter tap codes into calibrated time-of-arrival values. It is the same algorithm used by LINTag and LINCam Capture; exposed in Python for off-line reprocessing of raw recordings.

from photonscore import TdcRenderer

r = TdcRenderer(
    rnd_seed=42,
    seed_data=initial_calibration,
    update_period=4096,
    number_of_taps=216,
    clock_period=2000,    # 2 ns in ps
    mode="Sum",
)

# Per-chunk rendering — state is preserved across calls
dt = r.render_picosecond_index(raw_chunk)

TdcRenderer

tdc_renderer.TdcRenderer

TDC tap-code renderer.

Construct once with the device's seed calibration and clock period, then call :meth:render (or :meth:render_picosecond_index) for each chunk of raw events. The renderer keeps internal state so successive chunks share the same drifting calibration.

Source code in tdc_renderer.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
class TdcRenderer:
  """TDC tap-code renderer.

  Construct once with the device's seed calibration and clock period,
  then call :meth:`render` (or :meth:`render_picosecond_index`) for
  each chunk of raw events. The renderer keeps internal state so
  successive chunks share the same drifting calibration.
  """

  _impl: Union[TdcStdRndRenderer, TdcSumRenderer]

  def __init__(
    self,
    rnd_seed: int,
    seed_data: np.ndarray,
    update_period: int,
    number_of_taps: int,
    clock_period: float,
    mode: str = "Sum"
  ):
    """Set up the renderer.

    Args:
        rnd_seed: Seed for the internal pseudo-random generator
            (only relevant in ``"Rnd"`` mode).
        seed_data: Initial calibration array used to bootstrap the
            renderer. Must be at least `update_period` long.
        update_period: How many events between successive
            recalibrations.
        number_of_taps: Number of physical TDC taps on the device.
        clock_period: Period of the TDC reference clock in
            picoseconds.
        mode: ``"Sum"`` (default) returns the deterministic
            sum-of-tap-widths; ``"Rnd"`` returns a randomly dithered
            picosecond index.

    Raises:
        ValueError: If `mode` is not ``"Sum"`` or ``"Rnd"`` or
            `seed_data` is too short.
    """
    if (mode not in ["Sum", "Rnd"]):
      raise ValueError("Mode must be 'Sum' or 'Rnd'")

    if (len(seed_data) <= update_period):
      raise ValueError(
        "Length of 'seed_data' must be greater or equal than 'update_period'"
      )

    if (mode == "Sum"):
      self._impl = TdcSumRenderer()
      self._impl.create(
        rnd_seed, seed_data, len(seed_data), update_period, number_of_taps,
        clock_period
      )

    if (mode == "Rnd"):
      self._impl = TdcStdRndRenderer()
      self._impl.create(
        rnd_seed, seed_data, len(seed_data), update_period, number_of_taps,
        clock_period
      )

  def render(self, chunk: np.ndarray) -> np.ndarray:
    """Render `chunk` of raw tap codes into the renderer's native units.

    For ``"Sum"`` mode the result is the cumulative tap width in
    raw units; for ``"Rnd"`` mode it is a dithered picosecond index.
    """
    return self._impl.render_chunk(chunk)

  def render_picosecond_index(self, chunk: np.ndarray) -> np.ndarray:
    """Render `chunk` directly as picosecond indices.

    Available in both modes. In ``"Sum"`` mode the picosecond index
    is derived from the tap widths and `clock_period`; in ``"Rnd"``
    mode it is the dithered output described above.
    """
    return self._impl.render_chunk_picosecond_int(chunk)

__init__(rnd_seed, seed_data, update_period, number_of_taps, clock_period, mode='Sum')

Set up the renderer.

Parameters:

Name Type Description Default
rnd_seed int

Seed for the internal pseudo-random generator (only relevant in "Rnd" mode).

required
seed_data ndarray

Initial calibration array used to bootstrap the renderer. Must be at least update_period long.

required
update_period int

How many events between successive recalibrations.

required
number_of_taps int

Number of physical TDC taps on the device.

required
clock_period float

Period of the TDC reference clock in picoseconds.

required
mode str

"Sum" (default) returns the deterministic sum-of-tap-widths; "Rnd" returns a randomly dithered picosecond index.

'Sum'

Raises:

Type Description
ValueError

If mode is not "Sum" or "Rnd" or seed_data is too short.

Source code in tdc_renderer.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def __init__(
  self,
  rnd_seed: int,
  seed_data: np.ndarray,
  update_period: int,
  number_of_taps: int,
  clock_period: float,
  mode: str = "Sum"
):
  """Set up the renderer.

  Args:
      rnd_seed: Seed for the internal pseudo-random generator
          (only relevant in ``"Rnd"`` mode).
      seed_data: Initial calibration array used to bootstrap the
          renderer. Must be at least `update_period` long.
      update_period: How many events between successive
          recalibrations.
      number_of_taps: Number of physical TDC taps on the device.
      clock_period: Period of the TDC reference clock in
          picoseconds.
      mode: ``"Sum"`` (default) returns the deterministic
          sum-of-tap-widths; ``"Rnd"`` returns a randomly dithered
          picosecond index.

  Raises:
      ValueError: If `mode` is not ``"Sum"`` or ``"Rnd"`` or
          `seed_data` is too short.
  """
  if (mode not in ["Sum", "Rnd"]):
    raise ValueError("Mode must be 'Sum' or 'Rnd'")

  if (len(seed_data) <= update_period):
    raise ValueError(
      "Length of 'seed_data' must be greater or equal than 'update_period'"
    )

  if (mode == "Sum"):
    self._impl = TdcSumRenderer()
    self._impl.create(
      rnd_seed, seed_data, len(seed_data), update_period, number_of_taps,
      clock_period
    )

  if (mode == "Rnd"):
    self._impl = TdcStdRndRenderer()
    self._impl.create(
      rnd_seed, seed_data, len(seed_data), update_period, number_of_taps,
      clock_period
    )

render(chunk)

Render chunk of raw tap codes into the renderer's native units.

For "Sum" mode the result is the cumulative tap width in raw units; for "Rnd" mode it is a dithered picosecond index.

Source code in tdc_renderer.py
78
79
80
81
82
83
84
def render(self, chunk: np.ndarray) -> np.ndarray:
  """Render `chunk` of raw tap codes into the renderer's native units.

  For ``"Sum"`` mode the result is the cumulative tap width in
  raw units; for ``"Rnd"`` mode it is a dithered picosecond index.
  """
  return self._impl.render_chunk(chunk)

render_picosecond_index(chunk)

Render chunk directly as picosecond indices.

Available in both modes. In "Sum" mode the picosecond index is derived from the tap widths and clock_period; in "Rnd" mode it is the dithered output described above.

Source code in tdc_renderer.py
86
87
88
89
90
91
92
93
def render_picosecond_index(self, chunk: np.ndarray) -> np.ndarray:
  """Render `chunk` directly as picosecond indices.

  Available in both modes. In ``"Sum"`` mode the picosecond index
  is derived from the tap widths and `clock_period`; in ``"Rnd"``
  mode it is the dithered output described above.
  """
  return self._impl.render_chunk_picosecond_int(chunk)