fpfind 3.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fpfind/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ from importlib.metadata import version
2
+
3
+ import fpfind.lib
4
+ from fpfind.lib.constants import NP_PRECISEFLOAT, TSRES
5
+ from fpfind.lib.utils import get_overlap
6
+
7
+ __all__ = [
8
+ "fpfind",
9
+ "NP_PRECISEFLOAT",
10
+ "TSRES",
11
+ "get_overlap", # used in tests
12
+ ]
13
+
14
+ VERSION = version("fpfind")
fpfind/_postinstall ADDED
@@ -0,0 +1,5 @@
1
+ #!/bin/sh
2
+ # Performs post-installation compilation of freqcd. Do not modify.
3
+ TARGET=freqcd
4
+ if [ ! -z "$1" ]; then TARGET="$1"; fi
5
+ gcc -o ${TARGET} freqcd.c lib/getopt.c -lm
fpfind/apps/fpplot.py ADDED
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env python3
2
+ """NOT FOR PRODUCTION USE - but in main branch for ease of maintenance ;)
3
+
4
+ Changelog:
5
+ 2024-02-02, Justin: Init
6
+ """
7
+
8
+ import argparse
9
+ import logging
10
+ import sys
11
+
12
+ import kochen # v0.2024.4
13
+ import kochen.logging
14
+ import kochen.scriptutil
15
+ import matplotlib.pyplot as plt
16
+ import numpy as np
17
+ from S15lib.g2lib.g2lib import histogram
18
+
19
+ from fpfind.lib.parse_timestamps import read_a1_overlapping
20
+ from fpfind.lib.utils import (
21
+ get_first_overlapping_epoch,
22
+ get_timestamp_pattern,
23
+ normalize_timestamps,
24
+ )
25
+
26
+ _ENABLE_BREAKPOINT = False
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ def plotter(
31
+ alice,
32
+ bob,
33
+ freq,
34
+ time,
35
+ width,
36
+ resolution,
37
+ window=800,
38
+ save=False,
39
+ normalize=False,
40
+ decimation=1,
41
+ ):
42
+ bob = (bob - time) / (1 + freq)
43
+ ys, xs, stats = histogram(
44
+ alice,
45
+ bob,
46
+ duration=width,
47
+ resolution=resolution,
48
+ statistics=True,
49
+ window=window,
50
+ )
51
+ # Custom breakpoint for experimentation
52
+ if _ENABLE_BREAKPOINT:
53
+ globals().update(locals()) # write all local variables to global scope
54
+ raise # noqa: PLE0704
55
+
56
+ if decimation > 1:
57
+ ys = ys[::decimation]
58
+ xs = xs[::decimation]
59
+
60
+ elapsed = (alice[-1] - alice[0]) * 1e-9
61
+ _center = ys[500:1500]
62
+ _sides = np.array(list(ys[:500]) + list(ys[1500:]))
63
+ coincidences = np.sum(_center) - np.sum(_sides) * len(_center) / len(_sides)
64
+
65
+ s1 = len(alice) / elapsed
66
+ s2 = len(bob) / elapsed
67
+ c = coincidences / elapsed
68
+ print("Totals:")
69
+ print(f" S1: {len(alice):d}")
70
+ print(f" S2: {len(bob):d}")
71
+ print(f" C: {coincidences:.0f}")
72
+ print(f"Time elapsed: {elapsed:.3f}s")
73
+ print(f" s1: {s1:.0f}")
74
+ print(f" s2: {s2:.0f}")
75
+ print(f" sg: {(len(alice) * len(bob)) ** 0.5 / elapsed:.0f}")
76
+ print(f" c: {c:.0f}")
77
+ print(f"Efficiency: {100 * c / np.sqrt(s1 * s2):.1f}%")
78
+
79
+ # Normalize?
80
+ plt.subplots(figsize=(5, 3.5))
81
+ if normalize:
82
+ accs = s1 * s2 * elapsed * resolution * 1e-9
83
+ yerrs = np.sqrt(ys) / accs
84
+ ys = ys / accs
85
+ print(f"Normalizing by background = {accs}")
86
+ plt.errorbar(xs, ys, yerrs, fmt=".", markersize=3, elinewidth=1)
87
+
88
+ else:
89
+ plt.plot(xs, ys, linewidth=1)
90
+
91
+ plt.xlabel("Delay, $\\tau$ (ns)")
92
+ plt.ylabel("$g^{(2)}(\\tau)$")
93
+ plt.tight_layout()
94
+ if save:
95
+ plt.xlim(np.min(xs), np.max(xs))
96
+ plt.tight_layout()
97
+ plt.savefig(save, dpi=250)
98
+ plt.show()
99
+ globals().update(locals())
100
+ raise # noqa: PLE0704
101
+
102
+
103
+ def attenuate(ts, transmission=1):
104
+ if transmission >= 1:
105
+ return ts
106
+ mask = np.random.random(size=ts.size) < transmission
107
+ return ts[mask]
108
+
109
+
110
+ def main(): # noqa: PLR0915
111
+ global _ENABLE_BREAKPOINT # noqa: PLW0603
112
+ parser = kochen.scriptutil.generate_default_parser(__doc__, "fpfind")
113
+
114
+ # Boilerplate
115
+ # fmt: off
116
+ pgroup_config = parser.add_argument_group("display/configuration")
117
+ pgroup_config.add_argument(
118
+ "-h", "--help", action="store_true",
119
+ help="Show this help message and exit")
120
+ pgroup_config.add_argument(
121
+ "-v", "--verbosity", action="count", default=0,
122
+ help="Specify debug verbosity, e.g. -vv for more verbosity")
123
+ pgroup_config.add_argument(
124
+ "-L", "--logging", metavar="",
125
+ help="Log to file, if specified. Log level follows verbosity.")
126
+ pgroup_config.add_argument(
127
+ "--quiet", action="store_true",
128
+ help="Suppress errors, but will not block logging")
129
+ pgroup_config.add_argument(
130
+ "--config", metavar="", is_config_file_arg=True,
131
+ help="Path to configuration file")
132
+ pgroup_config.add_argument(
133
+ "--save", metavar="", is_write_out_config_file_arg=True,
134
+ help="Path to configuration file for saving, then immediately exit")
135
+ pgroup_config.add_argument(
136
+ "--experiment", action="store_true",
137
+ help=argparse.SUPPRESS)
138
+
139
+ # Timestamp importing arguments
140
+ pgroup_ts = parser.add_argument_group("importing timestamps")
141
+ pgroup_ts.add_argument(
142
+ "-t", "--reference", metavar="",
143
+ help="Timestamp file in 'a1' format, from low-count side (reference)")
144
+ pgroup_ts.add_argument(
145
+ "-T", "--target", metavar="",
146
+ help="Timestamp file in 'a1' format, from high-count side")
147
+ pgroup_ts.add_argument(
148
+ "-X", "--legacy", action="store_true",
149
+ help="Parse raw timestamps in legacy mode (default: %(default)s)")
150
+ pgroup_ts.add_argument(
151
+ "-Z", "--skip-duration", metavar="", type=float, default=0,
152
+ help="Specify initial duration to skip, in seconds (default: %(default)s)")
153
+
154
+ # Epoch importing arguments
155
+ pgroup_ep = parser.add_argument_group("importing epochs")
156
+ pgroup_ep.add_argument(
157
+ "-d", "--sendfiles", metavar="",
158
+ help="SENDFILES, from low-count side (reference)")
159
+ pgroup_ep.add_argument(
160
+ "-D", "--t1files", metavar="",
161
+ help="T1FILES, from high-count side")
162
+ pgroup_ep.add_argument(
163
+ "-e", "--first-epoch", metavar="",
164
+ help="Specify filename of first overlapping epoch, optional")
165
+ pgroup_ep.add_argument(
166
+ "-n", "--num-epochs", metavar="", type=int, default=1,
167
+ help="Specify number of epochs to import (default: %(default)d)")
168
+ pgroup_ep.add_argument(
169
+ "-z", "--skip-epochs", metavar="", type=int, default=0,
170
+ help="Specify number of initial epochs to skip (default: %(default)d)")
171
+
172
+ # Epoch importing arguments
173
+ pgroup_chselect = parser.add_argument_group("channel selection")
174
+ pgroup_chselect.add_argument(
175
+ "-m", "--reference-pattern", metavar="", type=int,
176
+ help="Pattern mask for selecting detector events from low-count side")
177
+ pgroup_chselect.add_argument(
178
+ "-M", "--target-pattern", metavar="", type=int,
179
+ help="Pattern mask for selecting detector events from high-count side")
180
+
181
+ # Plotting parameters
182
+ pgroup = parser.add_argument_group("plotting")
183
+ pgroup.add_argument(
184
+ "--df", type=float, default=0.0,
185
+ help="Specify clock skew, in absolute units (default: %(default)f)")
186
+ pgroup.add_argument(
187
+ "--dt", type=float, default=0.0,
188
+ help="Specify time delay, in units of ns (default: %(default)f)")
189
+ pgroup.add_argument(
190
+ "--width", type=float, default=1000,
191
+ help="Specify one-sided width of histogram, in units of ns (default: %(default)f)") # noqa: E501
192
+ pgroup.add_argument(
193
+ "-r", "--resolution", "--final-res", type=float, default=1,
194
+ help="Specify resolution of histogram, in units of ns (default: %(default)f)")
195
+ pgroup.add_argument(
196
+ "--duration", type=float, default=1,
197
+ help="Specify duration of timestamps to use, units of s (default: %(default)f)")
198
+ pgroup.add_argument(
199
+ "--save-plot",
200
+ help="Specify filename to save the plot to")
201
+ pgroup.add_argument(
202
+ "--normalize", action="store_true",
203
+ help="Normalize g(2) plot")
204
+ pgroup.add_argument(
205
+ "--decimation", type=int, default=1,
206
+ help="Take only every N-th point, for clarity (default: %(default)d)")
207
+ # fmt: on
208
+
209
+ # Parse arguments and configure logging
210
+ args = kochen.scriptutil.parse_args_or_help(parser, ignore_unknown=True)
211
+ kochen.logging.set_default_handlers(logger, file=args.logging)
212
+ kochen.logging.set_logging_level(logger, args.verbosity)
213
+ logger.debug("%s", args)
214
+
215
+ # Set experimental mode
216
+ if args.experiment:
217
+ _ENABLE_BREAKPOINT = True
218
+
219
+ # Obtain timestamps needed for fpplot
220
+ # alice: low count side - chopper - HeadT2 - sendfiles (reference)
221
+ # bob: high count side - chopper2 - HeadT1 - t1files
222
+ if args.sendfiles is not None and args.t1files is not None:
223
+ logger.info(" Reading from epoch directories...")
224
+ _is_reading_ts = False
225
+
226
+ # Automatically choose first overlapping epoch if not supplied manually
227
+ first_epoch, available_epochs = get_first_overlapping_epoch(
228
+ args.sendfiles,
229
+ args.t1files,
230
+ first_epoch=args.first_epoch,
231
+ return_length=True,
232
+ )
233
+ if available_epochs < args.num_epochs + args.skip_epochs:
234
+ logger.warning(" Insufficient epochs")
235
+
236
+ # Read epochs
237
+ alice, aps = get_timestamp_pattern(
238
+ args.sendfiles, "T2", first_epoch, args.skip_epochs, args.num_epochs
239
+ )
240
+ bob, bps = get_timestamp_pattern(
241
+ args.t1files, "T1", first_epoch, args.skip_epochs, args.num_epochs
242
+ )
243
+
244
+ elif args.target is not None and args.reference is not None:
245
+ logger.info(" Reading from timestamp files...")
246
+ _is_reading_ts = True
247
+ (alice, bob), (aps, bps) = read_a1_overlapping(
248
+ args.reference,
249
+ args.target,
250
+ legacy=args.legacy,
251
+ duration=args.duration + args.skip_duration,
252
+ )
253
+
254
+ else:
255
+ logger.error("Timestamp files/epochs must be supplied with -tT/-dD")
256
+ sys.exit(1)
257
+
258
+ # Select events only from specified channels
259
+ if args.reference_pattern is not None:
260
+ alice = alice[(aps & args.reference_pattern).astype(bool)]
261
+ if args.target_pattern is not None:
262
+ bob = bob[(bps & args.target_pattern).astype(bool)]
263
+
264
+ # Normalize timestamps to common time reference near start, so that
265
+ # frequency compensation will not shift the timing difference too far
266
+ skip = args.skip_duration if _is_reading_ts else 0
267
+ alice, bob = normalize_timestamps(alice, bob, skip=skip)
268
+
269
+ alice = attenuate(alice, 1)
270
+ bob = attenuate(bob, 1)
271
+
272
+ plotter(
273
+ alice,
274
+ bob,
275
+ args.df,
276
+ args.dt,
277
+ 2 * args.width,
278
+ resolution=args.resolution,
279
+ save=args.save_plot,
280
+ normalize=args.normalize,
281
+ decimation=args.decimation,
282
+ )
283
+
284
+
285
+ if __name__ == "__main__":
286
+ main()
@@ -0,0 +1,38 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ import parse_timestamps as parser
4
+
5
+ Ta = 2**27
6
+ N = 2**18
7
+ # binwidth = 32
8
+
9
+ def g2(arr1, arr2, start_time, delta_t):
10
+ maxdelay = 10000000
11
+ arr1 = arr1[np.where((arr1 > start_time) & (arr1 <= start_time + Ta))]
12
+ arr2 = arr2[np.where((arr2 > start_time) & (arr2 <= start_time + Ta))]
13
+ arr3 = np.zeros(N, dtype = np.int32)
14
+ delay = 0
15
+
16
+ for event in arr2:
17
+ while arr1.size > 1:
18
+ if (event - arr1[0] >= maxdelay):
19
+ # np.delete(arr1, 0)
20
+ arr1 = arr1[1:]
21
+ # del arr1[:1]
22
+ else: break
23
+ for stamp in arr1:
24
+ delay = event - stamp
25
+ arr3[int(delay // delta_t) % N] += 1
26
+
27
+ return arr3
28
+
29
+ # alice1 = parser.read_a1('./data/1_rawevents/raw_alice_20221109170747.dat', legacy = True)
30
+ # bob1 = parser.read_a1('./data/1_rawevents/raw_bob_20221109170747.dat', legacy = True)
31
+ # start = max(alice1[0], bob1[0])
32
+ # alice_pro = alice1[np.where((alice1 > start) & (alice1 <= start + Ta))]
33
+ # bob_pro = bob1[np.where((bob1 > start) & (bob1 <= start + Ta))]
34
+ # print(alice_pro.size)
35
+ # print(bob_pro.size)
36
+ # g2alicebob = g2(alice_pro, bob_pro)
37
+ # plt.plot(g2alicebob)
38
+ # plt.show()
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ # Companion script to create sample binary timestamps for freqcd.c
3
+ # Justin, 2023-02-03
4
+ #
5
+ # Examples:
6
+ #
7
+ # 1. Generate legacy timestamps and save to file:
8
+ #
9
+ # ./generate_freqcd_testcase.py \
10
+ # -o .input \
11
+ # -t 0 1000000000 2000000000 \
12
+ # -x
13
+ # ./freqcd -f 1000000 < .input
14
+ #
15
+ # 2. Pipe timestamps directly into freqcd
16
+ #
17
+ # ./generate_freqcd_testcase.py -t 0 1000000000 2000000000 |\
18
+ # ./freqcd -f 1000000 |\
19
+ # ./freqcd -f -999933 -o .output
20
+ #
21
+ # 3. Dynamically generate timestamps via command line and save to file
22
+ #
23
+ # ./generate_freqcd_testcase.py -o .input
24
+ #
25
+ # 4. Read timestamps from event supplied via command line or file
26
+ #
27
+ # cat .input | ./generate_freqcd_testcase.py
28
+ # ./generate_freqcd_testcase.py .input
29
+ #
30
+
31
+ import argparse
32
+ import pathlib
33
+ import secrets
34
+ import struct
35
+ import sys
36
+ import warnings
37
+
38
+ import fpfind.lib.parse_timestamps as ts_parser
39
+
40
+ warnings.simplefilter(action="once", category=UserWarning)
41
+
42
+ LEGACY = False
43
+
44
+ def get_event(timestamp, detectors: int = 0b0001):
45
+ """
46
+
47
+ Timestamps are not restricted to the (1 << 54) cap, to reflect the
48
+ filespec of truncating the excess significant bits. Use case in
49
+ having user see that the timestamps have indeed overflowed, e.g. for
50
+ verifying overflow response. A warning will be issued for precaution.
51
+ """
52
+ assert isinstance(timestamp, int) and (0 <= timestamp)
53
+ if timestamp >= (1 << 54):
54
+ warnings.warn(
55
+ f"Timestamp overflow detected, "
56
+ "will truncate accordingly."
57
+ )
58
+ timestamp = timestamp & 0x3FFFFFFFFFFFFF
59
+ assert isinstance(detectors, int) and (0 <= detectors < 16)
60
+ event = (timestamp << 10) + detectors
61
+ if LEGACY:
62
+ event = ((event & 0xFFFFFFFF) << 32) + (event >> 32)
63
+ return struct.pack("=Q", event)
64
+
65
+ def fwrite(event, filename: str = ".freqcd.input"):
66
+ with open(filename, "ab") as f:
67
+ f.write(event)
68
+
69
+ def owrite(event):
70
+ sys.stdout.buffer.write(event)
71
+ sys.stdout.flush()
72
+
73
+ def main():
74
+ global LEGACY
75
+ parser = argparse.ArgumentParser(description="Generates binary timestamp events for testing freqcd.")
76
+ parser.add_argument("-o", help="output file, defaults to stdout stream")
77
+ parser.add_argument("-t", type=int, nargs="+", help="list of timestamps in decimal")
78
+ parser.add_argument("-x", action="store_true", help="legacy format")
79
+ parser.add_argument("infile", nargs="?", help="optional filename of events ('-' if reading from stdin)")
80
+
81
+ # Print help if no arguments supplied
82
+ if len(sys.argv) == 1:
83
+ parser.print_help(sys.stderr)
84
+ sys.exit(1)
85
+
86
+ args = parser.parse_args()
87
+ if args.x:
88
+ LEGACY = True
89
+
90
+ # Modify method of writing
91
+ write = owrite
92
+ if args.o:
93
+ write = lambda e: fwrite(e, args.o)
94
+ open(args.o, "wb").close() # truncate file
95
+
96
+ # Different methods of reading timestamps
97
+ # 1. Read decimal timestamps from command line
98
+ # e.g. "-t 0 1000 2000"
99
+ if args.t:
100
+ for ts in args.t:
101
+ write(get_event(ts))
102
+
103
+ # 2. Read raw timestamps from file and print as decimal
104
+ # e.g. "filename.dat"
105
+ # 3. Read raw timestamps from stdin
106
+ # e.g. "-"
107
+ elif args.infile:
108
+ inputfile = args.infile
109
+
110
+ # Direct reading from stdin currently not supported
111
+ # instead creating a temporary file to store binary data
112
+ HAS_TEMP_FILE = args.infile == "-"
113
+ if HAS_TEMP_FILE:
114
+ inputfile = pathlib.Path(secrets.token_hex(5) + "_tempinput")
115
+
116
+ # Read binary timestamps from file
117
+ try:
118
+ if HAS_TEMP_FILE:
119
+ with open(inputfile, "wb") as f:
120
+ f.write(sys.stdin.buffer.read())
121
+
122
+ # Parsing binary timestamps
123
+ ts, _ = ts_parser.read_a1(inputfile, args.x)
124
+ for t in ts:
125
+ print(int(t * ts_parser.TSRES.PS4.value))
126
+
127
+ # Clean up
128
+ finally:
129
+ if HAS_TEMP_FILE:
130
+ inputfile.unlink()
131
+
132
+ # 4. Read decimal timestamps from interactive prompt,
133
+ # note output file in this case should be supplied.
134
+ # This is only for testing purposes.
135
+ else:
136
+ # Read from stdin
137
+ print("Enter timestamps in decimal separated by newlines.")
138
+ print("Press Ctrl-C to stop input.")
139
+ try:
140
+ while True:
141
+ try:
142
+ event = get_event(int(input()))
143
+ write(event)
144
+ except ValueError:
145
+ pass
146
+ except KeyboardInterrupt:
147
+ pass
148
+
149
+ if __name__ == "__main__":
150
+ main()
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ # Justin, 2022-10-07
3
+ # From single timestamp g(2) file, generate increasingly frequency detuned timestamping.
4
+ #
5
+ # Worked out the theory with Darren:
6
+ # Given a clock rate of f Hz, and the detuning offset denoted D,
7
+ # the detuned frequency is f + D, where D/f = detuning ratio (e.g. 1e-9).
8
+ #
9
+ # Here's an example to illustrate the relationship:
10
+ # The device keeps time via the clock rate. Suppose the clock fed to it
11
+ # is 10Hz, then the device which registers 10 clock ticks as 1 second will
12
+ # read out 1 second. The same device with 15 Hz will then read 1.5 seconds,
13
+ # i.e. faster clock => timestamp is faster relative to actual time.
14
+ #
15
+ # Given a detuning ratio R, the frequency fed to the device is now
16
+ # f + R*f, so the effective timestamp has an additional (1+R) product.
17
+
18
+ import parse_timestamps as parser
19
+ import numpy as np
20
+
21
+ DATA_DIR = "./Clock-sync/20221007_freqdetuneg2"
22
+ FILE = DATA_DIR + "/singletimestampnodelay.a1.dat"
23
+
24
+ # Read file
25
+ t, p = parser.read_a1(FILE, legacy=True)
26
+
27
+ # Perform frequency detuning of timestamp clock
28
+ def detune(t: np.ndarray, p: np.ndarray, channel: int, ratio: float = 0, offset: float = 0):
29
+ """Performs frequency detuning of timestamp clock on one channel.
30
+
31
+ Args:
32
+ t: Timestamp data
33
+ p: Detector channel pattern
34
+ channel: Choice of channel, follows detector pattern, i.e. 0b0100 for channel 3
35
+ ratio: Frequency detuning ratio, i.e. 0 is no detuning
36
+ offset: Performs fixed timing offset, optional
37
+ """
38
+ tc, pc = t[p==channel], p[p==channel]
39
+ tc = (tc-t[0])*(1+ratio) + t[0] + offset # assume both devices are synchronized t
40
+ return tc, pc
41
+
42
+ parser.write_a2(DATA_DIR + "/c1_a2.dat", t[p==1], p[p==1], legacy=True)
43
+ parser.write_a1(DATA_DIR + "/c4e10.dat", *detune(t, p, 8, 1e-10), legacy=True)
44
+ parser.write_a1(DATA_DIR + "/c4e9.dat", *detune(t, p, 8, 1e-9), legacy=True)
45
+ parser.write_a1(DATA_DIR + "/c4e8.dat", *detune(t, p, 8, 1e-8), legacy=True)
46
+ parser.write_a1(DATA_DIR + "/c4e7.dat", *detune(t, p, 8, 1e-7), legacy=True)
47
+ parser.write_a1(DATA_DIR + "/c4e6.dat", *detune(t, p, 8, 1e-6), legacy=True)
48
+ parser.write_a1(DATA_DIR + "/c4e5.dat", *detune(t, p, 8, 1e-5), legacy=True)
49
+ parser.write_a1(DATA_DIR + "/c4e4.dat", *detune(t, p, 8, 1e-4), legacy=True)
50
+ parser.write_a1(DATA_DIR + "/c4e3.dat", *detune(t, p, 8, 1e-3), legacy=True)
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+ # Prints 'a1' timestamp files as binary text
3
+ # Justin, 2025-08-27
4
+ #
5
+ # Useful for downstream textual processing, e.g. regex-filtering with grep.
6
+ # Pass '-' as argument to read from standard input instead of a file.
7
+ #
8
+ # Example:
9
+ # 00000000: 01011000110100001111011100001000 00000000000000000000000000000111
10
+ # 00000008: 00000010110000111111001100100001 00000000000000000000000000001010
11
+ # 00000010: 01011111000110000100101100001000 00000000000000000000000000010000
12
+ # 00000018: 01101111101000111111101100101000 00000000000000000000000000010000
13
+ # ...
14
+
15
+ test -z "$1" && { echo "usage: show-timestamps {<FILE>|-}"; exit 1; }
16
+ xxd -e -g 4 -c 8 "$1" | cut -b11-27 | xxd -r -p - | xxd -b -g 4 -c 8 | cut -b1-75
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+ # Prints 'a1' timestamp files as hexadecimal text
3
+ # Justin, 2025-08-27
4
+ #
5
+ # Useful for downstream textual processing, e.g. regex-filtering with grep.
6
+ # Pass '-' as argument to read from standard input instead of a file.
7
+ #
8
+ # Example:
9
+ # 00000000: 58d0f708 00000007
10
+ # 00000008: 02c3f321 0000000a
11
+ # 00000010: 5f184b08 00000010
12
+ # 00000018: 6fa3fb28 00000010
13
+ # ...
14
+
15
+ test -z "$1" && { echo "usage: show-timestamps-hex {<FILE>|-}"; exit 1; }
16
+ xxd -e -g 4 -c 8 "$1" | cut -b1-27
fpfind/apps/tsviz.py ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python3
2
+ # Visualize timestamp file
3
+ # Justin, 2025-04-07
4
+ #
5
+ # Currently hardcoded with read_a1(legacy=True).
6
+
7
+ import sys
8
+
9
+ import matplotlib.pyplot as plt
10
+ import numpy as np
11
+
12
+ import fpfind.lib.parse_timestamps as parser
13
+
14
+ if len(sys.argv) < 2:
15
+ print("usage: tsviz.py <FILE> [legacy]")
16
+ sys.exit(1)
17
+
18
+ legacy = len(sys.argv) >= 3
19
+ filename = sys.argv[1]
20
+ ts, ps = parser.read_a1(filename, legacy=legacy, ignore_rollover=True)
21
+
22
+
23
+ def generate_xy(ts, ps, ch):
24
+ """
25
+ Args:
26
+ ch: Channel number, i.e. between 1 and 4 inclusive.
27
+ """
28
+ p = int(2 ** (ch - 1))
29
+ xs = ts[(ps & p).astype(bool)]
30
+ ys = np.ones(xs.size) * ch
31
+ return xs, ys
32
+
33
+
34
+ args = ["x"]
35
+ kwargs = {"markersize": 3}
36
+
37
+ fig, ax = plt.subplots(figsize=(12, 3))
38
+ plt.plot(*generate_xy(ts, ps, 1), *args, **kwargs)
39
+ plt.plot(*generate_xy(ts, ps, 2), *args, **kwargs)
40
+ plt.plot(*generate_xy(ts, ps, 3), *args, **kwargs)
41
+ plt.plot(*generate_xy(ts, ps, 4), *args, **kwargs)
42
+
43
+ plt.ylim([-1, 6])
44
+ plt.yticks([1, 2, 3, 4])
45
+ plt.gca().invert_yaxis()
46
+ plt.ylabel("Channel")
47
+ plt.xlabel("Time (ns)")
48
+ plt.tight_layout()
49
+
50
+ plt.show()