pueoAnalysisTools
Loading...
Searching...
No Matches
waveform_plots.py
Go to the documentation of this file.
1r"""!
2@file waveform_plots.py
3@brief `main()` shows how to use
4[load_waveforms()](#waveform_plots.load_waveforms) and
5[load_waveforms_from_IceFinal()](#waveform_plots.load_waveforms_from_IceFinal).
6@note Altair is needed to make the plot.
7 ```
8 pip3 install altair[save]
9 ```
10"""
11
12# Examples are in the main function at the bottom of the script
13
14import ROOT
15import numpy as np
16import polars as pl
17from pathlib import Path
18from initialise import load_pueoEvent_Dataset
19from scipy.signal import lfilter
20
21# import waveform tools from pueoTALON for conditioning (filters) waveforms
22import sys
23sys.path.append("../pueoTALON")
24import pueoPythonAnalyzer.waveform as pueoTalonwaveform
25
26
28
29
30
31def load_waveforms_old(dataset: ROOT.pueo.Dataset, antennas: str = None) -> pl.DataFrame():
32 r"""! Loads time domain signals
33 @ingroup lwf
34
35 @param[in] dataset The output of #initialise.load_pueoEvent_Dataset
36 @param[in] antennas (optional) **whitespace separated list** specifying
37 [AntNum](#antenna_attributes.ALL_PUEO_ANTENNA_NAMES)s.
38 @retval waveforms The desired [channels](#antenna_attributes.read_global_channel_mapping)
39 and their waveforms [Volts]
40
41@todo Currently main instrument only, since oftentimes LF is not simulated
42
43* `antenna`:
44 * Users can optionally provide a string containing a whitespace separated list of antenna
45 indices (ie [AntNum](#antenna_attributes.read_global_channel_mapping))
46 * If no antennas is specified, all MI channels will be loaded into the DataFrame.
47 * An exception is raised if the list contains invalid `AntNum`s.
48
49* Output schema:
50 ```
51 $ GlobalChan <u32>
52 $ AntNum <enum>
53 $ AntIdx <u32>
54 $ Pol <enum>
55 $ AntType <enum>
56 $ SurfNum <u8>
57 $ SurfChanNum <u8>
58 $ PhiSector <u8>
59 $ Ring <u8>
60 $ waveforms (volts) <array[f64, 1024]>
61 $ step size (nanoseconds) <f64>
62 ```
63 """
64
65 from antenna_attributes import read_global_channel_mapping, ALL_PUEO_ANTENNA_NAMES
66
67 if antennas is None:
68 channel_mapping: pl.DataFrame = read_global_channel_mapping()
69 else: # if user provides a list of antennas, filter out unused antennas
70 try: # see if the user had provided a bad antenna index
71 antennas = antennas.split()
72 pl.Series(antennas, dtype=ALL_PUEO_ANTENNA_NAMES)
73 except pl.exceptions.InvalidOperationError as exc:
74 print("InvalidOperationError:", exc)
75 print("\n\nPerhaps you included a non-existent antenna index?")
76 print("aborting operation at function: load_waveforms()")
77 exit(1)
78
79 channel_mapping = (
80 read_global_channel_mapping().filter(pl.col("AntNum").is_in(antennas))
81 )
82
83 # Filter out LF channels
84 channel_mapping = channel_mapping.filter(pl.col("AntType") != "LF")
85
86 useful = dataset.useful()
87
88 '''
89 waveforms = channel_mapping.select(
90 pl.all().exclude("Comment"),
91
92 #return list, not np.array
93 pl.col("GlobalChan").map_batches(
94 lambda s: [np.asarray(useful.volts[int(i)], dtype=float) for i in s]
95 ).alias("waveforms (volts)"),
96
97 pl.col("GlobalChan").map_batches(
98 lambda s: [float(useful.dt[int(i)]) for i in s]
99 ).alias("step size (nanoseconds)")
100 )
101 '''
102
103 waveforms = channel_mapping.select(
104 pl.all().exclude("Comment"),
105
106 pl.col("GlobalChan").map_batches(
107 lambda s: np.array([useful.volts[i] for i in s])
108 ).alias("waveforms (volts)"),
109
110 pl.col("GlobalChan").map_batches(
111 lambda s: np.array([useful.dt[i] for i in s])
112 ).alias("step size (nanoseconds)")
113 )
114
115 return waveforms
116
117def load_waveforms(run:int, entry:int=None, eventNumber:int=None, antennas: str = None, anttype='MI', upsample_factor=1) -> pl.DataFrame:
118
119 r"""! Loads time domain signals
120 @ingroup lwf
121
122 @param[in] int run number
123 @param[in] event event, this is entry number in the data file
124 @param[in] eventID This is evnet number, you can specify either entry or eventnumber
125 @param[in] antennas [AntNum](#antenna_attributes.ALL_PUEO_ANTENNA_NAMES)s.
126 @param[in] anttype Either MI or LF
127 @paran[in] upsample_factor
128 @retval waveforms The desired [channels](#antenna_attributes.read_global_channel_mapping)
129 and their waveforms [Volts]
130
131
132 * `antenna`:
133 * Users can optionally provide a string containing a whitespace separated list of antenna
134 indices (ie [AntNum](#antenna_attributes.read_global_channel_mapping))
135 * If no antennas is specified, all MI channels will be loaded into the DataFrame.
136 * An exception is raised if the list contains invalid `AntNum`s.
137
138 * Output schema:
139 ```
140 $ GlobalChan <u32>
141 $ AntNum <enum>
142 $ AntIdx <u32>
143 $ Pol <enum>
144 $ AntType <enum>
145 $ SurfNum <u8>
146 $ SurfChanNum <u8>
147 $ PhiSector <u8>
148 $ Ring <u8>
149 $ waveforms (volts) <array[f64, 1024]>
150 $ step size (nanoseconds) <f64>
151 ```
152 """
153
154 from antenna_attributes import read_global_channel_mapping, ALL_PUEO_ANTENNA_NAMES
155 import numpy as np
156 import polars as pl
157
158 if antennas is None:
159 channel_mapping: pl.DataFrame = read_global_channel_mapping()
160 else:
161 try:
162 antennas = antennas.split()
163 pl.Series(antennas, dtype=ALL_PUEO_ANTENNA_NAMES)
164 except pl.exceptions.InvalidOperationError as exc:
165 print("InvalidOperationError:", exc)
166 print("\n\nPerhaps you included a non-existent antenna index?")
167 print("aborting operation at function: load_waveforms()")
168 exit(1)
169
170 channel_mapping = read_global_channel_mapping().filter(pl.col("AntNum").is_in(antennas))
171
172 if anttype=="MI":
173 channel_mapping = channel_mapping.filter(pl.col("AntType") != "LF")
174 elif anttype=="LF":
175 channel_mapping = channel_mapping.filter(pl.col("AntType") == "LF")
176
177 # attemps to use pueoTALON to do conditioning and upsample: This should handle all the filtering
178
179 if entry is not None:
180 processor = pueoTalonwaveform.Channels(run=run, entry=entry, geometry='jan26') # adapt if needed
181 elif eventNumber is not None:
182 processor = pueoTalonwaveform.Channels(run=run, eventNumber=eventNumber, geometry='jan26') # adapt if needed
183 waveform_list = []
184 dt_list = []
185 chan_list = []
186
187 # This should handle all the filters (upsampling, MUOS, dedispersion, etc.), as well as cable delays.
188 for ch in channel_mapping["GlobalChan"]:
189 if anttype=="LF":
190 sig, new_dt = processor.condition_lfsignal(int(ch), zero_pad_factor = 1, upsample_factor = upsample_factor, dt = 1/3)
191 elif anttype=="MI":
192 sig, new_dt = processor.condition_signal(int(ch), zero_pad_factor = 1, upsample_factor = upsample_factor, dt = 1/3)
193 waveform_list.append(sig[0]) # shape (1, N)
194 dt_list.append(new_dt)
195 chan_list.append(int(ch))
196
197 # Fill conditioned waveforms into dataframe
198 wf_df = pl.DataFrame({
199 "GlobalChan": chan_list,
200 "waveforms (volts)": waveform_list,
201 "step size (nanoseconds)": dt_list
202 })
203
204 waveforms = channel_mapping.join(wf_df, on="GlobalChan")
205
206 '''
207 useful = dataset.useful()
208
209 waveforms = channel_mapping.select(
210 pl.all().exclude("Comment"),
211
212 pl.col("GlobalChan").map_elements(
213 lambda i: np.asarray(list(useful.volts[int(i)]), dtype=np.float64),
214 return_dtype=pl.Array(pl.Float64, 1024)
215 ).alias("waveforms (volts)"),
216
217 pl.lit(1/3).alias("step size (nanoseconds)")
218 )
219
220 # ToDo: make use of the conditionings in pueoTALON
221 if anttype=="LF":
222 waveforms = waveforms.with_columns(
223 pl.col("waveforms (volts)")
224 .map_batches(filter_batch, return_dtype=pl.Array(pl.Float64, 1024))
225 .alias("waveforms (volts)")
226 )
227
228 waveforms = waveforms.with_columns(
229 pl.col("waveforms (volts)")
230 .map_batches(window_batches, return_dtype=pl.Array(pl.Float64, 1024))
231 .alias("waveforms (volts)")
232 )
233 '''
234 return waveforms
235
236def upsample_waveforms(waveforms: pl.DataFrame, upsample_factor: int) -> pl.DataFrame:
237 r"""!Calls SciPy's
238 [resample()](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.resample.html)
239 to upsample the waveforms.
240 @ingroup lwf
241 @param[in] waveforms The output of #load_waveforms
242 @param[in] upsample_factor
243 @retval upsampled_waveforms
244
245 * The following columns are required in the input:
246 -# `waveforms (volts)`
247 -# `step size (nanoseconds)`
248 * No additional columns will be attached to the output,
249 `waveforms (volts)` and `step size (nanoseconds)` will be upsampled and replaced.
250
251 """
252 from scipy.signal import resample
253 signal_length = len(waveforms["waveforms (volts)"][0])
254
255 upsampled_waveforms = waveforms.with_columns(
256 pl.col("waveforms (volts)").map_batches(
257 lambda wf: resample(wf, signal_length * upsample_factor, axis=1)
258 ,return_dtype=pl.Array(pl.Float64, 1024*upsample_factor)
259 ),
260 pl.col("step size (nanoseconds)") / upsample_factor
261 )
262
263 return upsampled_waveforms
264
265
266
267def window_batches(wfs: pl.Series) -> pl.Series:
268 WINDOW_front = 80
269 WINDOW_back = 80
270 N = 1024
271
272 out = []
273
274 for wf in wfs:
275 wf = np.asarray(wf)
276
277 imax = np.argmax(np.abs(wf))
278
279 tmp = np.zeros(N, dtype=np.float64)
280
281 lo = max(imax - WINDOW_front, 0)
282 hi = min(imax + WINDOW_back + 1, N)
283
284 tmp[lo:hi] = wf[lo:hi]
285
286 out.append(tmp)
287
288 return pl.Series(out)
289
290def load_waveforms_from_root(filepath, ev=0):
291 from antenna_attributes import read_global_channel_mapping
292 import ROOT
293 import os
294
295 channel_mapping: pl.DataFrame = read_global_channel_mapping()
296 channel_mapping = channel_mapping.filter(pl.col("AntType") == "LF")
297
298 channel_mapping_H = channel_mapping.filter(pl.col("Pol") == "H")
299 channel_mapping_V = channel_mapping.filter(pl.col("Pol") == "V")
300
301 lib_dir = str(os.getenv('PUEO_UTIL_INSTALL_DIR'))+'/lib'
302
303 ROOT.gSystem.Load(lib_dir+"/libPueoSim.so")
304 ROOT.gSystem.Load(lib_dir+"/libNiceMC.so")
305
306
307 rootFile = ROOT.TFile.Open(filepath)
308 passTree = rootFile.Get("passTree")
309
310 passTree.GetEntry(ev)
311
312
313 det_evt = passTree.detectorEvents[1]
314
315 def load_y_H(s: pl.Series) -> np.ndarray:
316 return np.stack([
317 np.array(det_evt.waveformsH[int(i)-192].GetY(), copy=True)
318 for i in s
319 ])
320
321 def load_y_V(s: pl.Series) -> np.ndarray:
322 return np.stack([
323 np.array(det_evt.waveformsV[int(i)-200].GetY(), copy=True)
324 for i in s
325 ])
326 waveformsH = channel_mapping_H.select(
327 pl.all().exclude("Comment"),
328
329 pl.col("GlobalChan")
330 .map_batches(load_y_H, return_dtype=pl.Array(pl.Float64, 1024))
331 .alias("waveforms (volts)"),
332 pl.lit(1/3)
333 .cast(pl.Float64)
334 .alias("step size (nanoseconds)")
335 )
336 waveformsV = channel_mapping_V.select(
337 pl.all().exclude("Comment"),
338
339 pl.col("GlobalChan")
340 .map_batches(load_y_V, return_dtype=pl.Array(pl.Float64, 1024))
341 .alias("waveforms (volts)"),
342 pl.lit(1/3)
343 .cast(pl.Float64)
344 .alias("step size (nanoseconds)")
345 )
346
347 waveforms = pl.concat([waveformsH, waveformsV], how="vertical")
348
349
350 waveforms = waveforms.with_columns(
351 pl.col("waveforms (volts)")
352 .map_batches(filter_batch)
353 .alias("waveforms (volts)")
354 )
355
356
357 waveforms = waveforms.with_columns(
358 pl.col("waveforms (volts)")
359 .map_batches(window_batches)
360 .alias("waveforms (volts)")
361 )
362
363 return waveforms
364
365
366def filter_batch(s: pl.Series) -> np.ndarray:
367 # s is shape (n_rows, 1024)
368 taps = np.array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
369 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
370 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
371 1, 1, 1, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2,
372 -2, -1, -1, -1, -1, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
373 2, 2, 1, 1, 1, 0, 0, -1, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2,
374 -1, -1, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 0, -1, -2,
375 -2, -2, -4, -4, -2, -2, -1, 0, 1, 2, 2, 2, 4, 2, 2, 1, 0, -1,
376 -2, -2, -2, -2, -2, -1, 1, 2, 4, 4, 2, -1, -2, -2, -2, 0, 1, 1, 1])
377 return np.array([lfilter(taps, 1.0, np.asarray(w, dtype=np.float64)) for w in s])
378
379
380
381def plot_waveforms(waveforms: pl.DataFrame, plot_name: str) -> None:
382 r"""!Uses Vega-Altair to make waveform plots.
383 @ingroup lwf
384 @param[in] waveforms The output of any other functions in @ref load_waveforms
385 @param[in] plot_name Be sure to specify the file format (eg. `my_plot.png` or `my_plot.html`)
386
387* The following columns are required in the input:
388 * `waveforms (volts)`
389 * `step size (nanoseconds)`
390 * `PhiSector`
391 * `Ring`
392 * `Pol`
393
394* Example: Plot the vpol channels of three antennas using the last event in `run0/`.\n
395 Place the figure in the `/tmp` directory.
396```python
397run_zero_data = load_pueoEvent_Dataset(pueo_mc_data=Path("/home/all_my_runs"), run_number=0)
398run_zero_data.last()
399wf = load_waveforms(dataset=run_zero_data, antennas="403 111 201")
400plot_waveforms(wf.filter(pl.col("Pol") == "V"), plot_name="/tmp/waveforms.html")
401```
402 @note The actual file name will be changed to `/tmp/Vpol_waveforms.html`.
403 Had we chosen not to filter out the horizontally polarized channels,
404 there will be another plot: `/tmp/Hpol_waveforms.html`.
405
406 \htmlonly
407 <iframe src="example_three_antenna_waveforms_from_pueoEvent.html" width="950" height="1000"></iframe>
408 \endhtmlonly
409 """
410 import altair as alt
411
412 # this forces function to error out instead of failing silently
413 waveforms.select("PhiSector", "Ring")
414 wf_array = np.stack(waveforms["waveforms (volts)"].to_list())
415 time = np.zeros_like(wf_array)
416 signal_length = time.shape[1]
417
418 for i, dt in enumerate(waveforms["step size (nanoseconds)"]):
419 time[i] = dt * np.arange(signal_length)
420
421 waveforms_list = (
422 waveforms.with_columns(pl.Series(time).alias("time (ns)"))
423 .explode("waveforms (volts)", "time (ns)")
424 .partition_by("Pol")
425 )
426
427 plot_path = Path(plot_name)
428 destination = plot_path.parent
429
430 # make the plots, let Altair take care of subplot positions automatically
431 for frame in waveforms_list:
432 pol = frame["Pol"][0]
433
434 chart = (
435 alt.Chart(frame).mark_line()
436 .encode(
437 x='time (ns):Q',
438 y=alt.Y('waveforms (volts)').title("[Volts]"),
439 )
440 .facet(
441 column=alt.Column("PhiSector:O", header=alt.Header(labelFontSize=16, titleFontSize=16)),
442 row=alt.Row("Ring:O", header=alt.Header(labelFontSize=16, titleFontSize=16))
443 )
444 .properties(title=f"{pol}Pol Waveforms")
445 .configure_title(fontSize=16)
446 )
447
448 pn = destination / f"{pol}pol_{plot_path.name}"
449 chart.save(f"{str(pn)}")
450
451
452def load_waveforms_from_IceFinal(filepath: str, event_number: [int] = 0,
453 antennas: str = None, upsample_factor: int = None,
454 want_plots: bool = False,
455 plot_name: str = None) -> tuple[pl.DataFrame(), np.array(1024)]:
456 r"""! Loads time domain signals from an IceFinal file;
457 users can optionally provide a list of antenna indices.
458 @ingroup lwf
459
460 @param[in] filepath path to `IceFinal_*_allTree.root`
461 @param[in] event_number (optional) an integer or a **list** of **integers**, defaults to event 0
462 @param[in] antennas (optional) **whitespace separated string** specifying the antenna names
463 @param[in] upsample_factor (optional)
464 @param[in] want_plots (optional) defaults to False
465 @param[in] plot_name (optional)
466 @retval waveforms_frame waveforms recorded by the antennas [Volts]
467 @retval time_mesh time mesh corresponding to the waveforms [seconds].
468
469* The function will error out if the `passTree` of the IceFinal file (see `filepath`) is empty.
470 (ie. no triggered events)
471* If no antennas are specified, all waveforms will be loaded into the DataFrame,
472 which presents them in [ascending](#antenna_attributes.ALL_PUEO_ANTENNA_NAMES) `AntNum`.
473* Example:
474 ```
475 load_waveforms_from_IceFinal("IceFinal_420_allTree.root", antennas="403 111 201", event_number=[0, 1])
476 ```
477 ```
478 ┌──────────────┬────────┬─────────────┬─────────────────────┬───────────────────────┐
479 │ event number ┆ AntNum ┆ pueoSim idx ┆ VPol waveforms (V) ┆ truth (signal origin) │
480 │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
481 │ i32 ┆ enum ┆ u8 ┆ array[f64, 1024] ┆ array[f64, 3] │
482 ╞══════════════╪════════╪═════════════╪═════════════════════╪═══════════════════════╡
483 │ 0 ┆ 201 ┆ 1 ┆ […] ┆ […] │
484 │ 0 ┆ 403 ┆ 11 ┆ […] ┆ […] │
485 │ 0 ┆ 111 ┆ 40 ┆ […] ┆ […] │
486 │ 1 ┆ 201 ┆ 1 ┆ […] ┆ […] │
487 │ 1 ┆ 403 ┆ 11 ┆ […] ┆ […] │
488 │ 1 ┆ 111 ┆ 40 ┆ […] ┆ […] │
489 └──────────────┴────────┴─────────────┴─────────────────────┴───────────────────────┘
490 ```
491* The waveforms can be upsampled by @p upsample_factor.
492* `truth (signal origin)` is a unit vector that represents the true direction of the incoming
493 signal.
494 * Note that in the IceFinal files, the variable `RFdir_payload` is the direction the signal is
495 travelling **to**.
496 * Therefore, to obtain the direction that the signal is coming **from**,
497 we need the opposite vector: \n
498 `signal_origin` = - `RFdir_payload`
499 * This is useful for verifying the correctness of the [correlation maps](@ref example_corr_map).
500
501* The function also optionally creates waveform plots and saves to the working directory
502 (ie. wherever the user calls this script) via the internal function #plot_waveforms().
503 Below is an example of a three-antenna plot (actually two plots would be made, one for each event):
504 ```python
505 load_waveforms_from_IceFinal(
506 "IceFinal_420_allTree.root", antennas="403 111 201",
507 want_plots=True, event_number=[0, 1])
508 ```
509 \anchor exampleThreeAntennaPlot
510 \htmlonly
511 <iframe src="example_three_antenna_waveforms.html" width="950" height="1000"></iframe>
512 \endhtmlonly
513 @todo **currently main instrument only**
514
515 @warning Will abort if non-existent antenna indices are provided.
516 ```
517 load_waveforms_from_IceFinal("IceFinal_420_allTree.root", antennas="201 511")
518 ```
519 In this case, Antenna `511` does not exist, since there are only 4 antennas in each `PhiSector`.\n
520 Thus, you would see
521 > InvalidOperationError: conversion from `str` to `enum` failed in column '' for 1 out of 2 values: ["511"]
522 >
523 > Ensure that all values in the input column are present in the categories of the enum datatype.
524 >
525 > Perhaps you included a non-existent antenna index?
526 > aborting operation at function: load_waveforms_from_IceFinal()
527 """
528 #from antenna_attributes import generate_dataframe_from_pueodata_qrh, ALL_PUEO_ANTENNA_NAMES
529 from antenna_attributes import read_global_channel_mapping, ALL_PUEO_ANTENNA_NAMES
530
531 if antennas is None:
532 channel_mapping: pl.DataFrame = read_global_channel_mapping()
533
534 # turn int into a list[int] so that it's iterable
535 if isinstance(event_number, int):
536 event_number = [event_number]
537
538 # if user provides a list of antennas, filter out unused antennas
539 if antennas is not None:
540 # see if the user had provided a bad antenna index
541 try:
542 antennas = antennas.split()
543 pl.Series(antennas, dtype=ALL_PUEO_ANTENNA_NAMES)
544 except pl.exceptions.InvalidOperationError as exc:
545 print("InvalidOperationError:", exc)
546 print("\n\nPerhaps you included a non-existent antenna index?")
547 print("aborting operation at function: load_waveforms_from_IceFinal()")
548 exit(1)
549
550 _index_conversion = (
551 generate_dataframe_from_pueodata_qrh().filter(pl.col("AntNum").is_in(antennas))
552 )
553 else:
554 _index_conversion: pl.DataFrame = generate_dataframe_from_pueodata_qrh()
555
556 ROOT.gSystem.Load("libNiceMC.so")
557 ROOT.gSystem.Load("libPueoSim.so")
558
559 _rootFile: ROOT.TFile = ROOT.TFile.Open(filepath)
560 _passTree: ROOT.TTree = _rootFile.Get('passTree')
561
562 waveforms: [pl.DataFrame] = []
563 for ev in event_number:
564
565 if ev < 0 or ev >= _passTree.GetEntries():
566 print(f"Invalid event index \"{ev}\" specified for {filepath}")
567 print(f"There are only {_passTree.GetEntries()} events.")
568 exit(1)
569
570 _passTree.GetEvent(ev)
571 _df: pl.DataFrame = (
572 _index_conversion
573 .select(
574 pl.lit(ev).alias("event number"),
575 pl.col("AntNum", "pueoSim idx"),
576 pl.col("pueoSim idx").map_batches(
577 lambda s: np.array([
578 _passTree.detectorEvents[0].waveformsV[i].GetY()
579 for i in s
580 ])
581 ).alias("VPol waveforms (V)"),
582 pl.col("pueoSim idx").map_batches(
583 lambda s: np.array([
584 -(_passTree.detectorEvents[0].RFdir_payload)
585 for _ in s
586 ])
587 ).alias("truth (signal origin)"),
588 )
589 )
590 waveforms.append(_df)
591 print(waveforms)
592 # time mesh in seconds
593 time_mesh = np.array(_passTree.detectorEvents[0].waveformsV[0].GetX())
594
595 # turn the list of waveform dataframes into one dataframe and apply SciPy upsampling if needed
596 waveforms = pl.concat(waveforms)
597
598 # if want_plots:
599 # _plot_waveforms_frame( # plot in nanoseconds
600 # _explode_waveforms_frame(waveforms, time_mesh * 1e9), plot_name=plot_name
601 # )
602
603 if upsample_factor is not None:
604 from scipy.signal import resample
605 upsampled_waveforms = waveforms.with_columns(
606 pl.col("VPol waveforms (V)").map_batches(
607 lambda s: resample(s, len(time_mesh) * upsample_factor, axis=1)
608 )
609 )
610 _, upsampled_time_mesh = resample(waveforms["VPol waveforms (V)"][0],
611 num=len(time_mesh) * upsample_factor, t=time_mesh)
612
613 return upsampled_waveforms, upsampled_time_mesh
614
615 return waveforms, time_mesh
616
617
618
619
620if __name__ == "__main__":
621 # Below we illustrate the basic features of the Dataset class,
622 # and how to pass it to the analysis functions.
623 # For more pueo::Dataset class methods, see its header file:
624 # https://github.com/PUEOCollaboration/pueoEvent/blob/main/src/pueo/Dataset.h
625
626 # Use run zero (for example) to initialize the Dataset
627 # Warning: make sure that the run is not empty (has events),
628 # otherwise pueo::Dataset's constructor will crash
629 #myDataset: ROOT.pueo.Dataset = load_pueoEvent_Dataset(pueo_data=Path("/scratch/midway3/yuchiehku/flight"), run_number=797)
630 #print(f"The current run is {myDataset.getCurrRun()}")
631
632 # suppose we now want to use another run, maybe run one. Warning: `/tmp/run1/` has to exist!
633 #if not myDataset.loadRun(1, ROOT.pueo.Dataset.PUEO_MC_DATA):
634 # exit(1)
635 #else:
636 # print(f"The current run is {myDataset.getCurrRun()}")
637 #print(f"The current run is {myDataset.getCurrRun()}")
638
639 # Suppose we want to analyze the 15th entry
640 #if not myDataset.getEvent(2518):
641 # print("no this entry")
642 # exit(1)
643 #else:
644 # print(f"Loaded event {myDataset.current()}")
645
646 # We could also analyze the last event
647 # myDataset.last()
648
649 # ---------------------------- Examples ---------------------------- #
650
651 # Example 1: plot the VPol waveforms of just three antennas:
652 #wf: pl.DataFrame = load_waveforms(dataset=myDataset, antennas="403 111 201")
653 #plot_waveforms(wf.filter(pl.col("Pol") == "V"), plot_name="./three_waveforms.png")
654
655 # Example 2: plot upsampled VPol and HPol waveforms of all antennas
656 #wf: pl.DataFrame = load_waveforms(dataset=myDataset)
657 run = 797
658 eventNumber = 2518
659 anttype = "MI"
660 wf: pl.DataFrame = load_waveforms(run, eventNumber=eventNumber, anttype=anttype, upsample_factor=3)
661 print(wf)
662 #wf: pl.DataFrame = load_waveforms_from_root("/scratch/midway3/yuchiehku/simplesim_lat88_noiseless/run80/IceFinal_80_allTree.root", ev=ev)
663 #up: pl.DataFrame = upsample_waveforms(waveforms=wf, upsample_factor=3)
664 plot_waveforms(wf, plot_name=f"test{eventNumber}.png")