1818
1919def compute_tof_in_each_cluster (
2020 da : StreakClusteredData [RunType ],
21+ chopper_delay : WavelengthDefinitionChopperDelay ,
2122 mod_period : ModulationPeriod ,
2223) -> TofDetector [RunType ]:
2324 """Fits a line through each cluster, the intercept of the line is t0.
@@ -32,6 +33,7 @@ def compute_tof_in_each_cluster(
3233 of the points in the cluster, and probably should belong to another cluster or
3334 are part of the background.
3435 3. Go back to 1) and iterate until convergence. A few iterations should be enough.
36+ 4. Finally, round the estimated t0 to the closest known chopper opening time.
3537 """
3638 if isinstance (da , sc .DataGroup ):
3739 return sc .DataGroup (
@@ -42,7 +44,7 @@ def compute_tof_in_each_cluster(
4244 sin_theta_L = sc .sin (da .bins .coords ['two_theta' ] / 2 ) * da .bins .coords ['Ltotal' ]
4345 t = time_of_arrival (
4446 da .bins .coords ['event_time_offset' ],
45- da .coords [ 'tc' ]. to ( unit = da . bins .coords ['event_time_offset' ]. unit ) ,
47+ da .bins .coords ['frame_cutoff_time' ] ,
4648 )
4749 for _ in range (15 ):
4850 s , t0 = _linear_regression_by_bin (sin_theta_L , t , da .data )
@@ -54,11 +56,33 @@ def compute_tof_in_each_cluster(
5456 too_far_from_center = (distance_to_self > max_distance_from_streak_line ),
5557 )
5658
57- da = da .assign_coords (t0 = sc .values (t0 ))
58- da = da .bins .assign_coords (tof = (t - sc .values (t0 )))
59+ # The t0 estimate from fitting is influenced by peak overlap, background,
60+ # and other factors that can make the estimate offset from the true
61+ # chopper opening time that it should match.
62+ # We know the true chopper opening times, so instead of using the t0 estimte
63+ # directly we can round the estimate to the closest chopper opening time.
64+ # That way the t0 estimate becomes more robust and is guaranteed to correspond to
65+ # a true chopper opening time.
66+ t0 = _round_t0_to_nearest_chopper_opening (sc .values (t0 ), mod_period , chopper_delay )
67+ da = da .assign_coords (t0 = t0 )
68+ da = da .bins .assign_coords (tof = (t - t0 ))
5969 return da
6070
6171
72+ def _round_t0_to_nearest_chopper_opening (
73+ t0 : sc .Variable ,
74+ mod_period : sc .Variable ,
75+ chopper_delay : sc .Variable ,
76+ ) -> sc .Variable :
77+ out = t0 - chopper_delay
78+ out /= mod_period
79+ out += 0.5
80+ sc .floor (out , out = out )
81+ out *= mod_period
82+ out += chopper_delay
83+ return out
84+
85+
6286def _linear_regression_by_bin (
6387 x : sc .Variable , y : sc .Variable , w : sc .Variable
6488) -> tuple [sc .Variable , sc .Variable ]:
@@ -118,14 +142,14 @@ def _compute_d_given_list_of_peaks(
118142
119143def time_of_arrival (
120144 event_time_offset : sc .Variable ,
121- tc : sc .Variable ,
145+ frame_cutoff_time : sc .Variable ,
122146):
123147 """Does frame unwrapping for pulse shaping chopper modes.
124148
125- Events before the "cutoff time" `tc` are assumed to come from the previous pulse."""
149+ Events before the "cutoff time" are assumed to come from the previous pulse."""
126150 _eto = event_time_offset
127151 T = sc .scalar (1 / 14 , unit = 's' ).to (unit = _eto .unit )
128- tc = tc .to (unit = _eto .unit )
152+ tc = frame_cutoff_time .to (unit = _eto .unit )
129153 return sc .where (_eto >= tc , _eto , _eto + T )
130154
131155
@@ -135,13 +159,13 @@ def _tof_from_dhkl(
135159 coarse_dhkl : sc .Variable ,
136160 Ltotal : sc .Variable ,
137161 mod_period : sc .Variable ,
138- time0 : sc .Variable ,
162+ chopper_delay : sc .Variable ,
139163) -> sc .Variable :
140164 """Computes tof for BEER given the dhkl peak that the event belongs to"""
141165 # Source: https://www.mcstas.org/download/components/current/contrib/NPI_tof_dhkl_detector.comp
142166 # tref = 2 * d_hkl * sin(theta) / hm * Ltotal
143- # tc = time_of_arrival - time0 - tref
144- # dt = floor(tc / mod_period + 0.5) * mod_period + time0
167+ # tc = time_of_arrival - chopper_delay - tref
168+ # dt = floor(tc / mod_period + 0.5) * mod_period + chopper_delay
145169 # tof = time_of_arrival - dt
146170 c = (- 2 * 1.0 / (scipp .constants .h / scipp .constants .m_n )).to (
147171 unit = f'{ time_of_arrival .unit } /m/angstrom'
@@ -150,12 +174,12 @@ def _tof_from_dhkl(
150174 out *= sc .sin (theta )
151175 out *= Ltotal
152176 out += time_of_arrival
153- out -= time0
177+ out -= chopper_delay
154178 out /= mod_period
155179 out += 0.5
156180 sc .floor (out , out = out )
157181 out *= mod_period
158- out += time0
182+ out += chopper_delay
159183 out *= - 1
160184 out += time_of_arrival
161185 return out
@@ -168,7 +192,7 @@ def geometry_graph() -> GeometryCoordTransformGraph:
168192def tof_from_known_dhkl_graph (
169193 mod_period : ModulationPeriod ,
170194 pulse_length : PulseLength ,
171- time0 : WavelengthDefinitionChopperDelay ,
195+ chopper_delay : WavelengthDefinitionChopperDelay ,
172196 dhkl_list : DHKLList ,
173197 gg : GeometryCoordTransformGraph ,
174198) -> TofCoordTransformGraph :
@@ -200,7 +224,7 @@ def _compute_coarse_dspacing(
200224 ** graph .tof .elastic ("tof" ),
201225 'pulse_length' : lambda : pulse_length ,
202226 'mod_period' : lambda : mod_period ,
203- 'time0 ' : lambda : time0 ,
227+ 'chopper_delay ' : lambda : chopper_delay ,
204228 'tof' : _tof_from_dhkl ,
205229 'time_of_arrival' : time_of_arrival ,
206230 'coarse_dhkl' : _compute_coarse_dspacing ,
0 commit comments