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.
8 pip3 install altair[save]
17from pathlib
import Path
18from initialise
import load_pueoEvent_Dataset
19from scipy.signal
import lfilter
26def load_waveforms_old(dataset: ROOT.pueo.Dataset, antennas: str =
None) -> pl.DataFrame():
27 r"""! Loads time domain signals
30 @param[in] dataset The output of #initialise.load_pueoEvent_Dataset
31 @param[in] antennas (optional) **whitespace separated list** specifying
32 [AntNum](#antenna_attributes.ALL_PUEO_ANTENNA_NAMES)s.
33 @retval waveforms The desired [channels](#antenna_attributes.read_global_channel_mapping)
34 and their waveforms [Volts]
36@todo Currently main instrument only, since oftentimes LF is not simulated
39 * Users can optionally provide a string containing a whitespace separated list of antenna
40 indices (ie [AntNum](#antenna_attributes.read_global_channel_mapping))
41 * If no antennas is specified, all MI channels will be loaded into the DataFrame.
42 * An exception is raised if the list contains invalid `AntNum`s.
55 $ waveforms (volts) <array[f64, 1024]>
56 $ step size (nanoseconds) <f64>
60 from antenna_attributes
import read_global_channel_mapping, ALL_PUEO_ANTENNA_NAMES
63 channel_mapping: pl.DataFrame = read_global_channel_mapping()
66 antennas = antennas.split()
67 pl.Series(antennas, dtype=ALL_PUEO_ANTENNA_NAMES)
68 except pl.exceptions.InvalidOperationError
as exc:
69 print(
"InvalidOperationError:", exc)
70 print(
"\n\nPerhaps you included a non-existent antenna index?")
71 print(
"aborting operation at function: load_waveforms()")
75 read_global_channel_mapping().filter(pl.col(
"AntNum").is_in(antennas))
79 channel_mapping = channel_mapping.filter(pl.col(
"AntType") !=
"LF")
81 useful = dataset.useful()
84 waveforms = channel_mapping.select(
85 pl.all().exclude("Comment"),
87 #return list, not np.array
88 pl.col("GlobalChan").map_batches(
89 lambda s: [np.asarray(useful.volts[int(i)], dtype=float) for i in s]
90 ).alias("waveforms (volts)"),
92 pl.col("GlobalChan").map_batches(
93 lambda s: [float(useful.dt[int(i)]) for i in s]
94 ).alias("step size (nanoseconds)")
98 waveforms = channel_mapping.select(
99 pl.all().exclude(
"Comment"),
101 pl.col(
"GlobalChan").map_batches(
102 lambda s: np.array([useful.volts[i]
for i
in s])
103 ).alias(
"waveforms (volts)"),
105 pl.col(
"GlobalChan").map_batches(
106 lambda s: np.array([useful.dt[i]
for i
in s])
107 ).alias(
"step size (nanoseconds)")
112def load_waveforms(dataset: ROOT.pueo.Dataset, antennas: str =
None) -> pl.DataFrame:
113 from antenna_attributes
import read_global_channel_mapping, ALL_PUEO_ANTENNA_NAMES
118 channel_mapping: pl.DataFrame = read_global_channel_mapping()
121 antennas = antennas.split()
122 pl.Series(antennas, dtype=ALL_PUEO_ANTENNA_NAMES)
123 except pl.exceptions.InvalidOperationError
as exc:
124 print(
"InvalidOperationError:", exc)
125 print(
"\n\nPerhaps you included a non-existent antenna index?")
126 print(
"aborting operation at function: load_waveforms()")
129 channel_mapping = read_global_channel_mapping().filter(pl.col(
"AntNum").is_in(antennas))
131 channel_mapping = channel_mapping.filter(pl.col(
"AntType") !=
"LF")
133 useful = dataset.useful()
135 waveforms = channel_mapping.select(
136 pl.all().exclude(
"Comment"),
138 pl.col(
"GlobalChan").map_batches(
140 [np.asarray(list(useful.volts[int(i)]), dtype=np.float64)
for i
in s]
142 ).alias(
"waveforms (volts)"),
144 pl.col(
"GlobalChan").map_batches(
145 lambda s: pl.Series([float(useful.dt[int(i)])
for i
in s])
146 ).alias(
"step size (nanoseconds)")
149 waveforms = waveforms.with_columns(
150 pl.col("waveforms (volts)")
151 .map_batches(window_batches)
152 .alias("waveforms (volts)")
157def upsample_waveforms(waveforms: pl.DataFrame, upsample_factor: int) -> pl.DataFrame:
159 [resample()](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.resample.html)
160 to upsample the waveforms.
162 @param[in] waveforms The output of #load_waveforms
163 @param[in] upsample_factor
164 @retval upsampled_waveforms
166 * The following columns are required in the input:
167 -# `waveforms (volts)`
168 -# `step size (nanoseconds)`
169 * No additional columns will be attached to the output,
170 `waveforms (volts)` and `step size (nanoseconds)` will be upsampled and replaced.
173 from scipy.signal
import resample
174 signal_length = len(waveforms[
"waveforms (volts)"][0])
176 upsampled_waveforms = waveforms.with_columns(
177 pl.col(
"waveforms (volts)").map_batches(
178 lambda wf: resample(wf, signal_length * upsample_factor, axis=1)
180 pl.col(
"step size (nanoseconds)") / upsample_factor
183 return upsampled_waveforms
187def window_batches(wfs: pl.Series) -> np.ndarray:
193 imax = np.argmax(abs(wf))
195 tmp = np.zeros_like(wf)
196 lo = max(imax - WINDOW_front, 0)
197 hi = min(imax + WINDOW_back+1, wf.size)
198 tmp[lo:hi] = wf[lo:hi]
205def load_waveforms_from_root(filepath, ev=0):
206 from antenna_attributes
import read_global_channel_mapping
210 channel_mapping: pl.DataFrame = read_global_channel_mapping()
211 channel_mapping = channel_mapping.filter(pl.col(
"AntType") ==
"LF")
213 channel_mapping_H = channel_mapping.filter(pl.col(
"Pol") ==
"H")
214 channel_mapping_V = channel_mapping.filter(pl.col(
"Pol") ==
"V")
216 lib_dir = str(os.getenv(
'PUEO_UTIL_INSTALL_DIR'))+
'/lib64'
218 ROOT.gSystem.Load(lib_dir+
"/libPueoSim.so")
219 ROOT.gSystem.Load(lib_dir+
"/libNiceMC.so")
222 rootFile = ROOT.TFile.Open(filepath)
223 passTree = rootFile.Get(
"passTree")
225 passTree.GetEntry(ev)
228 det_evt = passTree.detectorEvents[1]
230 def load_y_H(s: pl.Series) -> np.ndarray:
232 np.array(det_evt.waveformsH[int(i)-192].GetY(), copy=
True)
236 def load_y_V(s: pl.Series) -> np.ndarray:
238 np.array(det_evt.waveformsV[int(i)-200].GetY(), copy=
True)
241 waveformsH = channel_mapping_H.select(
242 pl.all().exclude(
"Comment"),
245 .map_batches(load_y_H)
246 .alias(
"waveforms (volts)"),
247 pl.col(
"GlobalChan").map_batches(
248 lambda s: np.array([1/3
for i
in s])
249 ).alias(
"step size (nanoseconds)")
251 waveformsV = channel_mapping_V.select(
252 pl.all().exclude(
"Comment"),
255 .map_batches(load_y_V)
256 .alias(
"waveforms (volts)"),
257 pl.col(
"GlobalChan").map_batches(
258 lambda s: np.array([1/3
for i
in s])
259 ).alias(
"step size (nanoseconds)")
262 waveforms = pl.concat([waveformsH, waveformsV], how=
"vertical")
265 waveforms = waveforms.with_columns(
266 pl.col(
"waveforms (volts)")
267 .map_batches(filter_batch)
268 .alias(
"waveforms (volts)")
272 waveforms = waveforms.with_columns(
273 pl.col(
"waveforms (volts)")
274 .map_batches(window_batches)
275 .alias(
"waveforms (volts)")
281def filter_batch(s: pl.Series) -> np.ndarray:
283 taps = np.array([1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
284 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
285 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
286 1, 1, 1, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2,
287 -2, -1, -1, -1, -1, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
288 2, 2, 1, 1, 1, 0, 0, -1, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2,
289 -1, -1, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 0, -1, -2,
290 -2, -2, -4, -4, -2, -2, -1, 0, 1, 2, 2, 2, 4, 2, 2, 1, 0, -1,
291 -2, -2, -2, -2, -2, -1, 1, 2, 4, 4, 2, -1, -2, -2, -2, 0, 1, 1, 1])
292 return np.array([lfilter(taps, 1.0, np.asarray(w, dtype=np.float64))
for w
in s])
296def plot_waveforms(waveforms: pl.DataFrame, plot_name: str) ->
None:
297 r"""!Uses Vega-Altair to make waveform plots.
299 @param[in] waveforms The output of any other functions in @ref load_waveforms
300 @param[in] plot_name Be sure to specify the file format (eg. `my_plot.png` or `my_plot.html`)
302* The following columns are required in the input:
303 * `waveforms (volts)`
304 * `step size (nanoseconds)`
309* Example: Plot the vpol channels of three antennas using the last event in `run0/`.\n
310 Place the figure in the `/tmp` directory.
312run_zero_data = load_pueoEvent_Dataset(pueo_mc_data=Path("/home/all_my_runs"), run_number=0)
314wf = load_waveforms(dataset=run_zero_data, antennas="403 111 201")
315plot_waveforms(wf.filter(pl.col("Pol") == "V"), plot_name="/tmp/waveforms.html")
317 @note The actual file name will be changed to `/tmp/Vpol_waveforms.html`.
318 Had we chosen not to filter out the horizontally polarized channels,
319 there will be another plot: `/tmp/Hpol_waveforms.html`.
322 <iframe src="example_three_antenna_waveforms_from_pueoEvent.html" width="950" height="1000"></iframe>
328 waveforms.select(
"PhiSector",
"Ring")
330 time = np.zeros_like(waveforms[
"waveforms (volts)"])
331 signal_length = time.shape[1]
333 for i, dt
in enumerate(waveforms[
"step size (nanoseconds)"]):
334 time[i] = dt * np.arange(signal_length)
337 waveforms.with_columns(pl.Series(time).alias(
"time (ns)"))
338 .explode(
"waveforms (volts)",
"time (ns)")
342 plot_path = Path(plot_name)
343 destination = plot_path.parent
346 for frame
in waveforms_list:
347 pol = frame[
"Pol"][0]
350 alt.Chart(frame).mark_line()
353 y=alt.Y(
'waveforms (volts)').title(
"[Volts]"),
356 column=alt.Column(
"PhiSector:O", header=alt.Header(labelFontSize=16, titleFontSize=16)),
357 row=alt.Row(
"Ring:O", header=alt.Header(labelFontSize=16, titleFontSize=16))
359 .properties(title=f
"{pol}Pol Waveforms")
360 .configure_title(fontSize=16)
363 pn = destination / f
"{pol}pol_{plot_path.name}"
364 chart.save(f
"{str(pn)}")
367def load_waveforms_from_IceFinal(filepath: str, event_number: [int] = 0,
368 antennas: str =
None, upsample_factor: int =
None,
369 want_plots: bool =
False,
370 plot_name: str =
None) -> tuple[pl.DataFrame(), np.array(1024)]:
371 r"""! Loads time domain signals from an IceFinal file;
372 users can optionally provide a list of antenna indices.
375 @param[in] filepath path to `IceFinal_*_allTree.root`
376 @param[in] event_number (optional) an integer or a **list** of **integers**, defaults to event 0
377 @param[in] antennas (optional) **whitespace separated string** specifying the antenna names
378 @param[in] upsample_factor (optional)
379 @param[in] want_plots (optional) defaults to False
380 @param[in] plot_name (optional)
381 @retval waveforms_frame waveforms recorded by the antennas [Volts]
382 @retval time_mesh time mesh corresponding to the waveforms [seconds].
384* The function will error out if the `passTree` of the IceFinal file (see `filepath`) is empty.
385 (ie. no triggered events)
386* If no antennas are specified, all waveforms will be loaded into the DataFrame,
387 which presents them in [ascending](#antenna_attributes.ALL_PUEO_ANTENNA_NAMES) `AntNum`.
390 load_waveforms_from_IceFinal("IceFinal_420_allTree.root", antennas="403 111 201", event_number=[0, 1])
393 ┌──────────────┬────────┬─────────────┬─────────────────────┬───────────────────────┐
394 │ event number ┆ AntNum ┆ pueoSim idx ┆ VPol waveforms (V) ┆ truth (signal origin) │
395 │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
396 │ i32 ┆ enum ┆ u8 ┆ array[f64, 1024] ┆ array[f64, 3] │
397 ╞══════════════╪════════╪═════════════╪═════════════════════╪═══════════════════════╡
398 │ 0 ┆ 201 ┆ 1 ┆ […] ┆ […] │
399 │ 0 ┆ 403 ┆ 11 ┆ […] ┆ […] │
400 │ 0 ┆ 111 ┆ 40 ┆ […] ┆ […] │
401 │ 1 ┆ 201 ┆ 1 ┆ […] ┆ […] │
402 │ 1 ┆ 403 ┆ 11 ┆ […] ┆ […] │
403 │ 1 ┆ 111 ┆ 40 ┆ […] ┆ […] │
404 └──────────────┴────────┴─────────────┴─────────────────────┴───────────────────────┘
406* The waveforms can be upsampled by @p upsample_factor.
407* `truth (signal origin)` is a unit vector that represents the true direction of the incoming
409 * Note that in the IceFinal files, the variable `RFdir_payload` is the direction the signal is
411 * Therefore, to obtain the direction that the signal is coming **from**,
412 we need the opposite vector: \n
413 `signal_origin` = - `RFdir_payload`
414 * This is useful for verifying the correctness of the [correlation maps](@ref example_corr_map).
416* The function also optionally creates waveform plots and saves to the working directory
417 (ie. wherever the user calls this script) via the internal function #plot_waveforms().
418 Below is an example of a three-antenna plot (actually two plots would be made, one for each event):
420 load_waveforms_from_IceFinal(
421 "IceFinal_420_allTree.root", antennas="403 111 201",
422 want_plots=True, event_number=[0, 1])
424 \anchor exampleThreeAntennaPlot
426 <iframe src="example_three_antenna_waveforms.html" width="950" height="1000"></iframe>
428 @todo **currently main instrument only**
430 @warning Will abort if non-existent antenna indices are provided.
432 load_waveforms_from_IceFinal("IceFinal_420_allTree.root", antennas="201 511")
434 In this case, Antenna `511` does not exist, since there are only 4 antennas in each `PhiSector`.\n
436 > InvalidOperationError: conversion from `str` to `enum` failed in column '' for 1 out of 2 values: ["511"]
438 > Ensure that all values in the input column are present in the categories of the enum datatype.
440 > Perhaps you included a non-existent antenna index?
441 > aborting operation at function: load_waveforms_from_IceFinal()
444 from antenna_attributes
import read_global_channel_mapping, ALL_PUEO_ANTENNA_NAMES
447 channel_mapping: pl.DataFrame = read_global_channel_mapping()
450 if isinstance(event_number, int):
451 event_number = [event_number]
454 if antennas
is not None:
457 antennas = antennas.split()
458 pl.Series(antennas, dtype=ALL_PUEO_ANTENNA_NAMES)
459 except pl.exceptions.InvalidOperationError
as exc:
460 print(
"InvalidOperationError:", exc)
461 print(
"\n\nPerhaps you included a non-existent antenna index?")
462 print(
"aborting operation at function: load_waveforms_from_IceFinal()")
465 _index_conversion = (
466 generate_dataframe_from_pueodata_qrh().filter(pl.col(
"AntNum").is_in(antennas))
469 _index_conversion: pl.DataFrame = generate_dataframe_from_pueodata_qrh()
471 ROOT.gSystem.Load(
"libNiceMC.so")
472 ROOT.gSystem.Load(
"libPueoSim.so")
474 _rootFile: ROOT.TFile = ROOT.TFile.Open(filepath)
475 _passTree: ROOT.TTree = _rootFile.Get(
'passTree')
477 waveforms: [pl.DataFrame] = []
478 for ev
in event_number:
480 if ev < 0
or ev >= _passTree.GetEntries():
481 print(f
"Invalid event index \"{ev}\" specified for {filepath}")
482 print(f
"There are only {_passTree.GetEntries()} events.")
485 _passTree.GetEvent(ev)
486 _df: pl.DataFrame = (
489 pl.lit(ev).alias(
"event number"),
490 pl.col(
"AntNum",
"pueoSim idx"),
491 pl.col(
"pueoSim idx").map_batches(
493 _passTree.detectorEvents[0].waveformsV[i].GetY()
496 ).alias(
"VPol waveforms (V)"),
497 pl.col(
"pueoSim idx").map_batches(
499 -(_passTree.detectorEvents[0].RFdir_payload)
502 ).alias(
"truth (signal origin)"),
505 waveforms.append(_df)
508 time_mesh = np.array(_passTree.detectorEvents[0].waveformsV[0].GetX())
511 waveforms = pl.concat(waveforms)
518 if upsample_factor
is not None:
519 from scipy.signal
import resample
520 upsampled_waveforms = waveforms.with_columns(
521 pl.col(
"VPol waveforms (V)").map_batches(
522 lambda s: resample(s, len(time_mesh) * upsample_factor, axis=1)
525 _, upsampled_time_mesh = resample(waveforms[
"VPol waveforms (V)"][0],
526 num=len(time_mesh) * upsample_factor, t=time_mesh)
528 return upsampled_waveforms, upsampled_time_mesh
530 return waveforms, time_mesh
535if __name__ ==
"__main__":
544 myDataset: ROOT.pueo.Dataset = load_pueoEvent_Dataset(pueo_mc_data=Path(
"/users/PAS0654/yuchieh/geometry_bug"), run_number=1)
545 print(f
"The current run is {myDataset.getCurrRun()}")
552 print(f
"The current run is {myDataset.getCurrRun()}")
555 if not myDataset.getEntry(1):
556 print(
"no this entry")
559 print(f
"Loaded event {myDataset.current()}")
573 wf: pl.DataFrame = load_waveforms(dataset=myDataset, antennas=
"119 219 319 419 120 220 320 420")
576 plot_waveforms(wf, plot_name=f
"/users/PAS0654/yuchieh/geometry_bug/GeoTest_MI_Evt{ev}_update.png")