DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
tp_dump.py
Go to the documentation of this file.
1#!/usr/bin/env python
2"""
3Display diagnostic information for TPs in a given
4tpstream file.
5"""
6import trgtools
7from trgtools.plot import PDFPlotter
8
9import trgdataformats
10
11import numpy as np
12import matplotlib.pyplot as plt
13from matplotlib.backends.backend_pdf import PdfPages
14from scipy import stats
15
16import os
17import argparse
18
19
20TICK_TO_SEC_SCALE = 16e-9 # s per tick
21
22
23def find_save_name(run_id: int, file_index: int, overwrite: bool) -> str:
24 """
25 Find a new save name or overwrite an existing one.
26
27 Parameters:
28 run_id (int): The run number for the read file.
29 file_index (int): The file index for the run number of the read file.
30 overwrite (bool): Overwrite the 0th plot directory of the same naming.
31
32 Returns:
33 (str): Save name to write as.
34
35 This is missing the file extension. It's the job of the save/write command
36 to append the extension.
37 """
38 # Try to find a new name.
39 name_iter = 0
40 save_name = f"tp_{run_id}-{file_index:04}_figures_{name_iter:04}"
41
42 # Outputs will always create a PDF, so use that as the comparison.
43 while not overwrite and os.path.exists(save_name + ".pdf"):
44 name_iter += 1
45 save_name = f"tp_{run_id}-{file_index:04}_figures_{name_iter:04}"
46 print(f"Saving outputs to ./{save_name}.*")
47
48 return save_name
49
50
51def plot_pdf_sot_vs_channel(tp_data: np.ndarray, pdf: PdfPages) -> None:
52 """
53 Plot the TP channel vs time over threshold scatter plot.
54
55 Parameter:
56 tp_data (np.ndarray): Array of TPs.
57 pdf (PdfPages): The PdfPages object that this plot will be appended to.
58
59 Returns:
60 Nothing. Mutates :pdf: with the new plot.
61
62 Does not have a seconds option. This plot is more informative in the ticks version.
63 """
64 plt.figure(figsize=(6, 4), dpi=200)
65
66 plt.plot(tp_data['channel'], tp_data['samples_over_threshold'], 'hk', mew=0, alpha=0.4, ms=2, label='TP', rasterized=True)
67
68 plt.title("TP Samples Over Threshold vs Channel")
69 plt.xlabel("Channel")
70 plt.ylabel("Samples Over Threshold (Readout Ticks)")
71 plt.legend()
72
73 plt.tight_layout()
74 pdf.savefig() # Many scatter points makes this a PNG ._.
75 plt.close()
76 return None
77
78
79def plot_pdf_adc_integral_vs_peak(tp_data: np.ndarray, pdf: PdfPages, verbosity: int = 0) -> None:
80 """
81 Plot the ADC Integral vs ADC Peak.
82
83 Parameters:
84 tp_data (np.ndarray): Array of TPs.
85 pdf (PdfPages): The PdfPages object that this plot will be appended to.
86 verbosity (int): Verbose level to print information.
87
88 Returns:
89 Nothing. Mutates :pdf: with the new plot.
90 """
91 if verbosity >= 2:
92 print(
93 "Number of ADC Integrals at Signed 16 Limit:",
94 np.sum(tp_data['adc_integral'] == np.power(2, 15)-1)
95 )
96 print("Total number of TPs:", len(tp_data['adc_peak']))
97 high_integral_locs = np.where(tp_data['adc_integral'] == np.power(2, 15)-1)
98 unity = (np.min(tp_data['adc_peak']), np.max(tp_data['adc_peak']))
99
100 plt.figure(figsize=(6, 4), dpi=200)
101
102 plt.plot(
103 tp_data['adc_peak'],
104 tp_data['adc_integral'],
105 c="#00000055",
106 ms=2,
107 ls='none',
108 marker='o',
109 mew=0,
110 label='TP',
111 zorder=2,
112 rasterized=True
113 )
114 plt.scatter(
115 tp_data['adc_peak'][high_integral_locs],
116 tp_data['adc_integral'][high_integral_locs],
117 c='#63ACBE',
118 s=2, marker='+',
119 label=r'$2^{15}-1$',
120 zorder=3,
121 rasterized=True
122 )
123 plt.plot(
124 unity,
125 unity,
126 color='#EE442F',
127 label="Unity",
128 lw=2,
129 zorder=1
130 )
131
132 plt.title("ADC Integral vs ADC Peak")
133 plt.xlabel("ADC Peak")
134 plt.ylabel("ADC Integral")
135 plt.legend()
136
137 plt.tight_layout()
138 pdf.savefig()
139 plt.close()
140 return None
141
142
143def write_summary_stats(data: np.ndarray, filename: str, title: str) -> None:
144 """
145 Writes the given summary statistics to :filename:.
146
147 Parameters:
148 data (np.ndarray): Array of a TP data member.
149 filename (str): File to append outputs to.
150 title (str): Title of the TP data member.
151
152 Appends statistics to the given file.
153 """
154 # Algorithm, Det ID, etc. are not expected to vary.
155 # Check first that they don't vary, and move on if so.
156 if np.all(data == data[0]):
157 print(f"{title} data member is the same for all TPs. Skipping summary statistics.")
158 return None
159
160 summary = stats.describe(data)
161 std = np.sqrt(summary.variance)
162 with open(filename, 'a') as out:
163 out.write(f"{title}\n")
164 out.write(f"Reference Statistics:\n"
165 f"\tTotal # TPs = {summary.nobs},\n"
166 f"\tMean = {summary.mean:.2f},\n"
167 f"\tStd = {std:.2f},\n"
168 f"\tMin = {summary.minmax[0]},\n"
169 f"\tMax = {summary.minmax[1]}.\n")
170 std3_count = np.sum(data > summary.mean + 3*std) + np.sum(data < summary.mean - 3*std)
171 std2_count = np.sum(data > summary.mean + 2*std) + np.sum(data < summary.mean - 2*std)
172 out.write(f"Anomalies:\n"
173 f"\t# of >3 Sigma TPs = {std3_count},\n"
174 f"\t# of >2 Sigma TPs = {std2_count}.\n")
175 out.write("\n\n")
176
177 return None
178
179
180def parse():
181 parser = argparse.ArgumentParser(
182 description="Display diagnostic information for TPs for a given HDF5 file."
183 )
184 parser.add_argument(
185 "filename",
186 help="Absolute path to tpstream file to display."
187 )
188 parser.add_argument(
189 "--verbose", "-v",
190 action="count",
191 help="Increment the verbose level (errors, warnings, all)."
192 "Save names and skipped writes are always printed. Default: 0.",
193 default=0
194 )
195 parser.add_argument(
196 "--start-frag",
197 type=int,
198 help="Fragment to start loading from (inclusive); can take negative integers. Default: -10",
199 default=-10
200 )
201 parser.add_argument(
202 "--end-frag",
203 type=int,
204 help="Fragment to stop loading at (exclusive); can take negative integers. Default: N",
205 default=0
206 )
207 parser.add_argument(
208 "--no-anomaly",
209 action="store_true",
210 help="Pass to not write 'tp_anomaly_summary.txt'. Default: False."
211 )
212 parser.add_argument(
213 "--seconds",
214 action="store_true",
215 help="Pass to use seconds instead of time ticks. Default: False."
216 )
217 parser.add_argument(
218 "--linear",
219 action="store_true",
220 help="Pass to use linear histogram scaling. Default: plots both linear and log."
221 )
222 parser.add_argument(
223 "--log",
224 action="store_true",
225 help="Pass to use logarithmic histogram scaling. Default: plots both linear and log."
226 )
227 parser.add_argument(
228 "--overwrite",
229 action="store_true",
230 help="Overwrite old outputs. Default: False."
231 )
232 parser.add_argument(
233 "--batch-mode", "-b",
234 action="store_true",
235 help="Do you want to run in batch mode (without loading bars/tqdm)?"
236 )
237
238 return parser.parse_args()
239
240
241def main():
242 """
243 Drives the processing and plotting.
244 """
245 # Process Arguments & Data
246 args = parse()
247 filename = args.filename
248 verbosity = args.verbose
249 start_frag = args.start_frag
250 end_frag = args.end_frag
251 no_anomaly = args.no_anomaly
252 seconds = args.seconds
253 overwrite = args.overwrite
254 batch_mode = args.batch_mode
255
256 linear = args.linear
257 log = args.log
258
259 # User didn't pass either flag, so default to both being true.
260 if (not linear) and (not log):
261 linear = True
262 log = True
263
264 data = trgtools.TPReader(filename, verbosity, batch_mode)
265
266 # Check that there are TP fragments.
267 if len(data.get_fragment_paths()) == 0:
268 print("File doesn't contain any TriggerPrimitive fragments.")
269 return 1
270
271 # Load all case
272 if start_frag == 0 and end_frag == -1:
273 data.read_all_fragments() # Has extra debug/warning info
274 else:
275 if end_frag == 0: # Ex: [-10:0] is bad.
276 frag_paths = data.get_fragment_paths()[start_frag:]
277 else:
278 frag_paths = data.get_fragment_paths()[start_frag:end_frag]
279
280 for path in frag_paths:
281 data.read_fragment(path)
282
283 # Find a new save name or overwrite an old one.
284 save_name = find_save_name(data.run_id, data.file_index, overwrite)
285
286 print(f"Number of TPs: {data.tp_data.shape[0]}") # Enforcing output for a useful metric.
287
288 # Plotting
289
290 if not no_anomaly:
291 anomaly_filename = f"{save_name}.txt"
292 if verbosity >= 2:
293 print(f"Writing descriptive statistics to {anomaly_filename}.")
294 if os.path.isfile(anomaly_filename):
295 # Prepare a new tp_anomaly_summary.txt
296 os.remove(anomaly_filename)
297
298 time_label = "Time (s)" if seconds else "Time (Ticks)"
299
300 # Dictionary containing unique title, xlabel, and xticks (only some)
301 plot_hist_dict = {
302 'adc_integral': {
303 'title': "ADC Integral Histogram",
304 'xlabel': "ADC Integral",
305 'ylabel': "Count",
306 'linear': linear,
307 'linear_style': dict(color='#63ACBE', alpha=0.6, label='Linear'),
308 'log': log,
309 'log_style': dict(color='#EE442F', alpha=0.6, label='Log')
310 },
311 'adc_peak': {
312 'title': "ADC Peak Histogram",
313 'xlabel': "ADC Count",
314 'ylabel': "Count",
315 'linear': linear,
316 'linear_style': dict(color='#63ACBE', alpha=0.6, label='Linear'),
317 'log': log,
318 'log_style': dict(color='#EE442F', alpha=0.6, label='Log')
319 },
320 # TODO: Channel should bin on the available
321 # channels; however, this is inconsistent
322 # between detectors (APA/CRP).
323 # Requires loading channel maps.
324 'channel': {
325 'title': "Channel Histogram",
326 'xlabel': "Channel Number",
327 'ylabel': "Count",
328 'linear': linear,
329 'linear_style': dict(color='#63ACBE', alpha=0.6, label='Linear'),
330 'log': log,
331 'log_style': dict(color='#EE442F', alpha=0.6, label='Log')
332 },
333 'detid': {
334 'title': "Detector ID Histogram",
335 'xlabel': "Detector IDs",
336 'ylabel': "Count",
337 'linear': linear,
338 'linear_style': dict(color='#63ACBE', alpha=0.6, label='Linear'),
339 'log': log,
340 'log_style': dict(color='#EE442F', alpha=0.6, label='Log'),
341 'use_integer_xticks': True
342 },
343 'flag': {
344 'title': "Flag Histogram",
345 'xlabel': "Flags",
346 'ylabel': "Count",
347 'linear': linear,
348 'linear_style': dict(color='#63ACBE', alpha=0.6, label='Linear'),
349 'log': log,
350 'log_style': dict(color='#EE442F', alpha=0.6, label='Log'),
351 'use_integer_xticks': True
352 },
353 'samples_over_threshold': {
354 'title': "Samples Over Threshold Histogram",
355 'xlabel': time_label,
356 'ylabel': "Count",
357 'linear': linear,
358 'linear_style': dict(color='#63ACBE', alpha=0.6, label='Linear'),
359 'log': log,
360 'log_style': dict(color='#EE442F', alpha=0.6, label='Log')
361 },
362 'samples_to_peak': {
363 'title': "Samples To Peak Histogram",
364 'xlabel': time_label,
365 'ylabel': "Count",
366 'linear': linear,
367 'linear_style': dict(color='#63ACBE', alpha=0.6, label='Linear'),
368 'log': log,
369 'log_style': dict(color='#EE442F', alpha=0.6, label='Log')
370 },
371 'time_start': {
372 'title': "Relative Time Start Histogram",
373 'xlabel': time_label,
374 'ylabel': "Count",
375 'linear': linear,
376 'linear_style': dict(color='#63ACBE', alpha=0.6, label='Linear'),
377 'log': log,
378 'log_style': dict(color='#EE442F', alpha=0.6, label='Log')
379 },
380 'version': {
381 'title': "Version Histogram",
382 'xlabel': "Versions",
383 'ylabel': "Count",
384 'linear': linear,
385 'linear_style': dict(color='#63ACBE', alpha=0.6, label='Linear'),
386 'log': log,
387 'log_style': dict(color='#EE442F', alpha=0.6, label='Log'),
388 'use_integer_xticks': True
389 }
390 }
391
392 pdf_plotter = PDFPlotter(save_name)
393
394 # Generic plots
395 for tp_key in data.tp_data.dtype.names:
396 if 'time' in tp_key: # Special case.
397 time = data.tp_data[tp_key]
398 if seconds:
399 time = time * TICK_TO_SEC_SCALE
400 min_time = np.min(time) # Prefer making the relative time change.
401 pdf_plotter.plot_histogram(time - min_time, plot_hist_dict[tp_key])
402 if not no_anomaly:
403 write_summary_stats(time - min_time, anomaly_filename, tp_key)
404 continue
405
406 pdf_plotter.plot_histogram(data.tp_data[tp_key], plot_hist_dict[tp_key])
407 if not no_anomaly:
408 write_summary_stats(data.tp_data[tp_key], anomaly_filename, tp_key)
409
410 pdf = pdf_plotter.get_pdf()
411 # Analysis plots
412 # ==== Time Over Threshold vs Channel ====
413 plot_pdf_sot_vs_channel(data.tp_data, pdf)
414 # ========================================
415
416 # ==== ADC Integral vs ADC Peak ====
417 plot_pdf_adc_integral_vs_peak(data.tp_data, pdf, verbosity)
418 # ===================================
419 pdf_plotter.close()
420
421 return None
422
423
424if __name__ == "__main__":
425 main()
None write_summary_stats(np.ndarray data, str filename, str title)
Definition tp_dump.py:143
parse()
Definition tp_dump.py:180
None plot_pdf_adc_integral_vs_peak(np.ndarray tp_data, PdfPages pdf, int verbosity=0)
Definition tp_dump.py:79
str find_save_name(int run_id, int file_index, bool overwrite)
Definition tp_dump.py:23
None plot_pdf_sot_vs_channel(np.ndarray tp_data, PdfPages pdf)
Definition tp_dump.py:51