Skip to content

Commit cc1c0ee

Browse files
committed
WIP: Add noise to list-mode data
1 parent 700ee8c commit cc1c0ee

1 file changed

Lines changed: 92 additions & 0 deletions

File tree

applications/pctpairprotons/pctpairprotons.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python
22
import argparse
33
import json
4+
import sys
45
import itk
56
from itk import PCT as pct
67
import numpy as np
@@ -72,8 +73,95 @@ def build_parser():
7273
"--psout", help="Name of tree in output phase space", default="PhaseSpace"
7374
)
7475

76+
data_noise_group = parser.add_argument_group("Data noise")
77+
data_noise_group.add_argument('--noise-measurements', help="Standard deviation of the Gaussian noise on the measurements (energy or time)", type=float)
78+
data_noise_group.add_argument('--noise-position', help="Standard deviation of the Gaussian noise on the position", type=float)
79+
data_noise_group.add_argument('--tracker-distance', help="Distance between the two trackers of the upstream and downstream detectors, used to calculate noise on directions", type=float)
80+
data_noise_group.add_argument('--seed', help="Random number generator seed", type=int)
81+
7582
return parser
7683

84+
def add_noise(pairs, measurement_column, noise_measurements, noise_position, tracker_distance, seed):
85+
86+
rng = np.random.default_rng(seed)
87+
88+
if noise_measurements is not None:
89+
measurement_in = pairs[measurement_column + "_in"]
90+
measurement_out = pairs[measurement_column + "_out"]
91+
measurement_in += rng.normal(scale=noise_measurements, size=len(measurement_in))
92+
measurement_out += rng.normal(scale=noise_measurements, size=len(measurement_out))
93+
94+
if noise_position is not None:
95+
96+
u_in = pairs["u_in"]
97+
u_out = pairs["u_out"]
98+
v_in = pairs["v_in"]
99+
v_out = pairs["v_out"]
100+
101+
noise_u_in = u_in + rng.normal(scale=noise_position, size=len(u_in))
102+
noise_u_out = u_out + rng.normal(scale=noise_position, size=len(u_out))
103+
noise_v_in = v_in + rng.normal(scale=noise_position, size=len(v_in))
104+
noise_v_out = v_out + rng.normal(scale=noise_position, size=len(v_out))
105+
106+
if tracker_distance is None:
107+
print("Warning: noise on position was provided, but tracker distance is unspecified. No noise on directions will be applied.", file=sys.stderr)
108+
else:
109+
# Recover the point on the second tracker in each detector
110+
w_in = pairs["w_in"]
111+
w_out = pairs["w_out"]
112+
du_in = pairs["du_in"]
113+
du_out = pairs["du_out"]
114+
dv_in = pairs["dv_in"]
115+
dv_out = pairs["dv_out"]
116+
dw_in = pairs["dw_in"]
117+
dw_out = pairs["dw_out"]
118+
119+
w_in_2 = w_in - tracker_distance
120+
slope_in = (w_in_2 - w_in) / dw_in
121+
u_in_2 = u_in + slope_in * du_in
122+
v_in_2 = v_in + slope_in * dv_in
123+
124+
w_out_2 = w_out + tracker_distance
125+
slope_out = (w_out_2 - w_out) / dw_out
126+
u_out_2 = u_out + slope_out * du_out
127+
v_out_2 = v_out + slope_out * dv_out
128+
129+
# Add noise to the point on the second tracker
130+
noise_u_in_2 = u_in_2 + rng.normal(scale=noise_position, size=len(u_in_2))
131+
noise_v_in_2 = v_in_2 + rng.normal(scale=noise_position, size=len(v_in_2))
132+
noise_u_out_2 = u_out_2 + rng.normal(scale=noise_position, size=len(u_out_2))
133+
noise_v_out_2 = v_out_2 + rng.normal(scale=noise_position, size=len(v_out_2))
134+
135+
# Build a direction from these noisy points
136+
noise_du_in = noise_u_in - noise_u_in_2
137+
noise_dv_in = noise_v_in - noise_v_in_2
138+
noise_dw_in = tracker_distance
139+
noise_du_out = noise_u_out_2 - noise_u_out
140+
noise_dv_out = noise_v_out_2 - noise_v_out
141+
noise_dw_out = tracker_distance
142+
143+
norm_in = np.sqrt(noise_du_in**2 + noise_dv_in**2 + noise_dw_in**2)
144+
norm_out = np.sqrt(noise_du_out**2 + noise_dv_out**2 + noise_dw_out**2)
145+
146+
noise_du_in /= norm_in
147+
noise_dv_in /= norm_in
148+
noise_dw_in /= norm_in
149+
noise_du_out /= norm_out
150+
noise_dv_out /= norm_out
151+
noise_dw_out /= norm_out
152+
153+
pairs["du_in"] = noise_du_in
154+
pairs["du_out"] = noise_du_out
155+
pairs["dv_in"] = noise_dv_in
156+
pairs["dv_out"] = noise_dv_out
157+
pairs["dw_in"] = noise_dw_in
158+
pairs["dw_out"] = noise_dw_out
159+
160+
pairs["u_in"] = noise_u_in
161+
pairs["v_in"] = noise_v_in
162+
pairs["u_out"] = noise_u_out
163+
pairs["v_out"] = noise_v_out
164+
77165

78166
def process(args_info: argparse.Namespace):
79167
import uproot
@@ -186,6 +274,10 @@ def load_tree_as_df(root_file, tree_name):
186274
np.recarray.sort(pairs, order=["RunID", "EventID", "TrackID_in", "TrackID_out"])
187275
verbose("Merged input and output phase spaces.")
188276

277+
if args_info.noise_measurements is not None or args_info.noise_position is not None:
278+
verbose("Adding noise…")
279+
add_noise(pairs, measurement_column, args_info.noise_measurements, args_info.noise_position, args_info.tracker_distance, args_info.seed)
280+
189281
if args_info.fit is not None:
190282
verbose("Converting energy loss or TOF to WEPL…")
191283
with open(args_info.fit, encoding="utf-8") as f:

0 commit comments

Comments
 (0)