Belle II Software  release-08-01-10
resolution.py
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 
4 
11 
12 from tracking.validation.plot import ValidationPlot, compose_axis_label, get_unit
13 
14 # get error function as a np.ufunc vectorised for numpy array
15 from tracking.root_utils import root_save_name
16 
17 import collections
18 
19 import numpy as np
20 
21 from tracking.validation.tolerate_missing_key_formatter import TolerateMissingKeyFormatter
22 
23 formatter = TolerateMissingKeyFormatter()
24 
25 
26 class ResolutionAnalysis(object):
27  """Perform resolution analysis"""
28 
29 
30  default_outlier_z_score = 5.0
31 
32  default_min_required_entries = 50
33 
34  default_plot_name = "{plot_name_prefix}_{subplot_name}{plot_name_postfix}"
35 
36  default_plot_title = "{subplot_title} of {quantity_name}{plot_title_postfix}"
37 
38  default_which_plots = [
39  "resolution",
40  ]
41 
42 
43  default_is_expert = True
44 
45  def __init__(
46  self,
47  quantity_name,
48  bin_spacing, # can be [0,0.5,1.0] will have 2 bins
49  bin_name,
50  bin_unit=None,
51  unit=None,
52  outlier_z_score=None,
53  contact='',
54  plot_name=None,
55  plot_title=None,
56  min_required_entries=None, # minimum number of entries in a bin for the resolution fit
57  plot_name_prefix='', # depricated use plot_name instead
58  plot_name_postfix='', # depricated use plot_name instead
59  plot_title_postfix='', # depricated use plot_title instead
60  referenceFileName=None, # if set binnings of histograms will be read from corresponding histogram in this file
61  ):
62  """Performs a comparison of an estimated quantity to their truths by generating standardized validation plots."""
63 
64 
65  self.quantity_namequantity_name = quantity_name
66 
67  self.unitunit = unit or get_unit(quantity_name)
68 
69  self.bin_spacingbin_spacing = bin_spacing
70 
71  self.bin_namebin_name = bin_name
72 
73  self.bin_unitbin_unit = bin_unit
74 
75  if outlier_z_score is None:
76 
77  self.outlier_z_scoreoutlier_z_score = self.default_outlier_z_scoredefault_outlier_z_score
78  else:
79  self.outlier_z_scoreoutlier_z_score = outlier_z_score
80 
81 
82  self.min_required_entriesmin_required_entries = min_required_entries
83  if self.min_required_entriesmin_required_entries is None:
84  self.min_required_entriesmin_required_entries = self.default_min_required_entriesdefault_min_required_entries
85 
86 
87  self.plot_nameplot_name = plot_name
88 
89  self.plot_titleplot_title = plot_title
90 
91 
92  self.plot_name_prefixplot_name_prefix = plot_name_prefix or root_save_name(quantity_name)
93 
94  self.plot_name_postfixplot_name_postfix = plot_name_postfix
95 
96  self.plot_title_postfixplot_title_postfix = plot_title_postfix
97 
98 
99  self._contact_contact = contact
100 
101  self.plotsplots = collections.OrderedDict()
102 
103 
104  self.referenceFileNamereferenceFileName = referenceFileName
105 
106  def analyse(
107  self,
108  bin_values,
109  truths,
110  estimates,
111  which_plots=None,
112  is_expert=None
113  ):
114  """Compares the concrete estimate to the truth and generates plots of the resolution
115 
116  Parameters
117  ----------
118  bin_values : array_like(float
119  The parametr used for binning
120  truths : array_like(float)
121  Sample of the true values
122  estimates : array_like(float)
123  Corresponding estimations
124  """
125 
126  if is_expert is None:
127  is_expert = self.default_is_expertdefault_is_expert
128 
129  if which_plots is None:
130  which_plots = self.default_which_plotsdefault_which_plots
131 
132  quantity_name = self.quantity_namequantity_name
133 
134  # axis_label = compose_axis_label(quantity_name, self.unit)
135 
136  plot_name_prefix = self.plot_name_prefixplot_name_prefix
137  outlier_z_score = self.outlier_z_scoreoutlier_z_score
138 
139  plot_name = self.plot_nameplot_name
140  if plot_name is None:
141  plot_name = self.default_plot_namedefault_plot_name
142 
143  plot_name = formatter.format(plot_name,
144  quantity_name=quantity_name,
145  plot_name_prefix=plot_name_prefix,
146  plot_name_postfix=self.plot_name_postfixplot_name_postfix)
147 
148  plot_title = self.plot_titleplot_title
149  if plot_title is None:
150  plot_title = self.default_plot_titledefault_plot_title
151 
152  plot_title = formatter.format(plot_title,
153  quantity_name=quantity_name,
154  plot_title_postfix=self.plot_title_postfixplot_title_postfix)
155 
156  # compute residuals
157  residuals = estimates - truths
158 
159  # Resolution #
160 
161  if "resolution" in which_plots:
162 
163  # creating plots for all configured bins
164  res_histogram = []
165  resolution_values = []
166 
167  for i in range(len(self.bin_spacingbin_spacing) - 1):
168  lower_bin = self.bin_spacingbin_spacing[i]
169  upper_bin = self.bin_spacingbin_spacing[i + 1]
170  assert (lower_bin < upper_bin)
171  bin_center = lower_bin + (upper_bin - lower_bin) / 2.0
172  assert (len(bin_values) == len(residuals))
173 
174  # compile a list of values which are in this bin
175  sel_residuals = collections.deque()
176 
177  for i in range(len(bin_values)):
178  if bin_values[i] >= lower_bin and bin_values[i] < upper_bin:
179  sel_residuals.append(residuals[i])
180 
181  residuals_hist_name = formatter.format(plot_name, subplot_name="residuals") + \
182  "{}_to_{}".format(lower_bin, upper_bin)
183  vplot = ValidationPlot(residuals_hist_name, self.referenceFileNamereferenceFileName)
184  vplot.hist(sel_residuals,
185  outlier_z_score=outlier_z_score,
186  is_expert=is_expert)
187  vplot.xlabel = compose_axis_label("#Delta " + quantity_name + " (estimate - truth)", self.unitunit)
188  vplot.title = formatter.format(plot_title, subplot_title='Residual distribution')
189 
190  # this values will stay None if no fit could be performed
191  gaus_sigma = None
192  gaus_sigma_err = None
193 
194  # check if the minimum number of entries are in the histogram
195  if vplot.histograms[0].GetEntries() >= self.min_required_entriesmin_required_entries:
196  fit_res = vplot.fit_gaus(z_score=1)
197 
198  # extract fit result from ROOT's TFitResut
199  params = fit_res.GetParams()
200  errs = fit_res.Errors()
201 
202  # gaus_mean = params[1]
203  gaus_sigma = params[2]
204  gaus_sigma_err = errs[2]
205 
206  res_histogram += [(lower_bin, upper_bin, bin_center, vplot)]
207  self.plotsplots['residuals' + residuals_hist_name] = vplot
208 
209  # store the fit results
210  resolution_values += [(lower_bin, upper_bin, bin_center, gaus_sigma, gaus_sigma_err)]
211 
212  resolution_graph_name = formatter.format(plot_name, subplot_name="resolution")
213  resolution_graph = ValidationPlot(resolution_graph_name, self.referenceFileNamereferenceFileName)
214 
215  # compile all requried data going into the final TGraphErrors
216  xs = []
217  xs_err = []
218  ys = []
219  ys_err = []
220 
221  for v in resolution_values:
222  # could be None if no fit was possible for this bin
223  if v[3]:
224  xs += [v[2]]
225  xs_err = [0.0]
226  ys += [v[3]]
227  ys_err = [v[4]]
228 
229  # convert to numpy array before giving to the plotting code
230  resolution_graph.grapherrors((np.array(xs), np.array(xs_err)), (np.array(ys), np.array(ys_err)),
231  is_expert=is_expert)
232  resolution_graph.xlabel = compose_axis_label(self.bin_namebin_name, self.bin_unitbin_unit)
233  resolution_graph.ylabel = compose_axis_label(self.quantity_namequantity_name, self.unitunit)
234  resolution_graph.title = formatter.format(plot_title, subplot_title='Resolution')
235 
236  self.plotsplots[resolution_graph_name] = resolution_graph
237 
238 
240 
241  @property
242  def contact(self):
243  """Get the contact person's name"""
244  return self._contact_contact
245 
246  @contact.setter
247  def contact(self, contact):
248  """Set the contact person's name"""
249  self._contact_contact = contact
250  for validation_plot in list(self.plotsplots.values()):
251  validation_plot.contact = contact
252 
253  def write(self, tDirectory=None):
254  """Write all validation plot to the given Root directory"""
255  for validation_plot in list(self.plotsplots.values()):
256  validation_plot.write(tDirectory)
int default_min_required_entries
default minimum number of entries
Definition: resolution.py:32
quantity_name
cached name of the quantity in the truth-classification analysis
Definition: resolution.py:65
contact
Forward the contact to all plots by reassigning the contact.
Definition: resolution.py:239
plot_title
cached value of the plot title
Definition: resolution.py:89
outlier_z_score
cached value of the Z-score (for outlier detection)
Definition: resolution.py:77
plots
cached value of the dictionary of plots to be created
Definition: resolution.py:101
float default_outlier_z_score
default Z-score (for outlier detection)
Definition: resolution.py:30
unit
cached measurement unit for this truth-classification analysis
Definition: resolution.py:67
_contact
cached value of the contact person
Definition: resolution.py:99
plot_name_postfix
cached value of the suffix appended to the plot name
Definition: resolution.py:94
plot_name
cached value of the base name of the plot
Definition: resolution.py:87
bin_unit
cached value of the bin measurement unit
Definition: resolution.py:73
plot_title_postfix
cached value of the suffix appended to the plot title
Definition: resolution.py:96
def analyse(self, bin_values, truths, estimates, which_plots=None, is_expert=None)
Definition: resolution.py:113
min_required_entries
cached value of the minimum number of entries
Definition: resolution.py:82
bin_spacing
cached value of the histogram bin spacing
Definition: resolution.py:69
plot_name_prefix
cached value of the prefix prepended to the plot name
Definition: resolution.py:92
bool default_is_expert
by default, create expert plots
Definition: resolution.py:43
list default_which_plots
default list of plots to create
Definition: resolution.py:38
referenceFileName
cached value of the reference filename
Definition: resolution.py:104
def __init__(self, quantity_name, bin_spacing, bin_name, bin_unit=None, unit=None, outlier_z_score=None, contact='', plot_name=None, plot_title=None, min_required_entries=None, plot_name_prefix='', plot_name_postfix='', plot_title_postfix='', referenceFileName=None)
Definition: resolution.py:61