def fit_peaks(self, his=None, rx=None, clear=True): """ Fit gaussian peaks to 1D plot. If his is not given the current plot is used. If rx is not given, the current range is used Returns list of lists: [E, x0, dx, A, dA, s, Area] where E is name of the peak, x0, A and s are fitted parameters and d'something' is its uncertainity. Area is total calculated area. """ if rx is None: rx = Experiment.xlim if len(rx) != 2: print('Please use x range in format rx=(min, max), where', 'min and max are integers.') return None # Deactivate all the plots for p in Experiment.plots: if p.active: p.active = False peaks = [] for p in self.peaks: if rx[0] <= p.get('E') <= rx[1]: peaks.append(p) PF = PeakFitter(peaks, 'linear', '') if his is not None: if isinstance(his, int): if his > 0: data = self.hisfile.load_histogram(his) if data[0] != 1: print('{} is not a 1D histogram'.format(his)) return None x_axis = data[1] weights = data[3] title = self.hisfile.histograms[his]['title'].strip() title = '{}:{}'.format(his, self._replace_latex_chars(title)) else: try: x_axis = Experiment.plots[his].histogram.x_axis weights = Experiment.plots[his].histogram.weights title = Experiment.plots[his].histogram.title except IndexError: print('There is no plot in the registry under the', 'number', his, 'use show_registry() to see', 'available plots') return None else: x_axis = Experiment.plots[-1].histogram.x_axis weights = Experiment.plots[-1].histogram.weights title = Experiment.plots[-1].histogram.title dweights = self._standard_errors_array(weights) if clear: self.clear() histo_data = histogram.Histogram() histo_data.x_axis = x_axis histo_data.weights = weights histo_data.errors = dweights histo_data.title = title plot_data = Plot(histo_data, 'histogram', True) # The histogram data is plotted here so the fit function # may be overlaid on in. However, the plot_data is appended # to the registry after the fit functions so it is on top of the # registry. self.plotter.plot1d(plot_data, xlim=rx) fit_result = PF.fit(x_axis[rx[0]:rx[1]], weights[rx[0]:rx[1]], dweights[rx[0]:rx[1]]) histo_baseline = histogram.Histogram() histo_baseline.x_axis = x_axis[rx[0]:rx[1]] histo_baseline.weights = fit_result['baseline'] histo_baseline.title = 'Baseline' plot_baseline = Plot(histo_baseline, 'function', True) self.plotter.plot1d(plot_baseline, xlim=rx) histo_peaks = histogram.Histogram() histo_peaks.x_axis = fit_result['x_axis'] histo_peaks.weights = fit_result['fit'] histo_peaks.title = 'Fit' plot_peaks = Plot(histo_peaks, 'function', True) # Append all the plots to the registry, but # keep original data at the end, so the next fit_peaks() # call will use then again as default Experiment.plots.append(plot_baseline) Experiment.plots.append(plot_peaks) Experiment.plots.append(plot_data) # Plot the last one with the auto_scale if needed if Experiment.ylim is None: ylim = self._auto_scale_y() else: ylim = Experiment.ylim self.plotter.plot1d(plot_peaks, xlim=rx, ylim=ylim) print('#{:^8} {:^8} {:^8} {:^8} {:^8} {:^8} {:^8}' .format('Peak', 'x0', 'dx', 'A', 'dA', 's', 'Area')) peak_data = [] for i, peak in enumerate(peaks): if peak.get('ignore') == 'True': continue x0 = PF.params['x{}'.format(i)].value dx = PF.params['x{}'.format(i)].stderr A = PF.params['A{}'.format(i)].value dA = PF.params['A{}'.format(i)].stderr s = PF.params['s{}'.format(i)].value Area = PF.find_area(x_axis, i) print('{:>8} {:>8.2f} {:>8.2f} {:>8.1f} {:>8.1f} {:>8.3f} {:>8.1f}' .format(peaks[i].get('E'), x0, dx, A, dA, s, Area)) peak_data.append([peaks[i].get('E'), x0, dx, A, dA, s, Area]) return peak_data
def dd(self, his, xc=None, yc=None, logz=None): """Plot 2D histogram, his may be a positive integer (loads histogram from the data file) negative integer (2D plots registry) or Plot instance (must be a 2D plot) xc is x range, yc is y range, that may be applied immediately, see also xc() and yc() functions """ self.mode = 2 for p in Experiment.maps: p.active = False plot = None self.plotter.clear() if isinstance(his, int): if his > 0: data = self.hisfile.load_histogram(his) if data[0] != 2: print('{} is not a 2D histogram'.format(his)) return None title = self.hisfile.histograms[his]['title'].strip() title = '{}:{}'.format(his, self._replace_latex_chars(title)) histo = histogram.Histogram(dim=2) histo.title = title histo.x_axis = data[1] histo.y_axis = data[2] histo.weights = data[3] plot = Plot(histo, 'map', True) Experiment.maps.append(plot) else: # plot histogram from the registry # Numbered by negative numbers (-1 being the latest) # Call show_registers for a list of available plots try: plot = Experiment.maps[his] Experiment.maps[his].active = True except IndexError: print('There is no 2D plot in the registry under the', 'number', his, 'use show_registry() to see', 'available plots') return None elif isinstance(his, Plot): # If instance of Plot class is given, mark it active and add # to the deque (if not already there) # and to the array to be returned at the end if his.histogram.dim != 2: print('This {} is not a 2D histogram'.format(his)) return None his.active = True plot = his if his not in Experiment.maps: Experiment.maps.append(his) if xc is not None: Experiment.xlim2d = xc if yc is not None: Experiment.ylim2d = yc if logz is None: use_log = Experiment.logz else: use_log = logz if plot is not None: self.plotter.plot2d(plot, Experiment.xlim2d, Experiment.ylim2d, use_log) return [plot]
def gy(self, his, gate_y, gate_x=None, bg_gate=None, norm=1, bin_size=1, clear=True, plot=True): """Make projection on X axis of 2D histogram with gate set on Y (gate_y) and possibly on X (gate_x) see gx for more details """ if gate_y is None or len(gate_y) != 2: print('Please select gate on Y in a (min, max) format') return None if gate_x is not None and len(gate_x) != 2: print('Please select gate on X in a (min, max) format') return None # If clear flag used, clear the plotting area if clear and plot: self.plotter.clear() # Switch mode to 1D self.mode = 1 # Deactivate all plots if clear flag is used if clear and plot: for p in Experiment.plots: p.active = False data = self.hisfile.load_histogram(his) if data[0] != 2: print('{} is not a 2D histogram'.format(his)) return None # x for x_axis data # y for y_axis data # w for weights # g for gate (result) # bg for background gate x = data[1] y = data[2] w = data[3] if gate_x is None: gate_x = [0, len(x)-2] x = x[gate_x[0]:gate_x[1]+1] g = w[gate_x[0]:gate_x[1]+1, gate_y[0]:gate_y[1]+1].sum(axis=1) dg = self._standard_errors_array(g) if bg_gate is not None: if (bg_gate[1] - bg_gate[0]) != (gate_y[1] - gate_y[0]): print('#Warning: background and gate of different widths') bg = w[gate_x[0]:gate_x[1]+1, bg_gate[0]:bg_gate[1]+1].sum(axis=1) g = g - bg # Note that since the gate is adding bins, the formula # used for standard error is no longer valid # This approximation should be good enough though dbg = self._standard_errors_array(bg) dg = self._add_errors(dg, dbg) title = '{}:{} gy({},{})'.format(his, self.hisfile.\ histograms[his]['title'].strip(), gate_y[0], gate_y[1]) if bg_gate is not None: title += ' bg ({}, {})'.format(bg_gate[0], bg_gate[1]) title = self._replace_latex_chars(title) histo = histogram.Histogram() histo.title = title histo.x_axis = x histo.weights = g histo.errors = dg gate_plot = Plot(histo, 'histogram', True) gate_plot.bin_size = bin_size gate_plot.norm = norm Experiment.plots.append(gate_plot) if plot: ylim = None if self.ylim is None: ylim = self._auto_scale_y() else: ylim = self.ylim self.plotter.plot1d(gate_plot, Experiment.xlim, ylim) return gate_plot
def gx(self, his, gate_x, gate_y=None, bg_gate=None, norm=1, bin_size=1, clear=True, plot=True): """Make projection on Y axis of 2D histogram with gate set on X (gate_x) and possibly on Y (gate_y) his: is a histogram id in a file gate_x: is range of bins in (x0, x1) format, this selects the range of X columns to be projected on Y axis gate_y: is a range of bins in (y0, y1) format (optional), this truncates the range of the projection along the Y axis bg_gate: is a range of bins in (x0, x1) format (optional), this selects the background gate that is subtracted from the selected gate_x norm: normalization factor (see d()) bin_size: binning factor (see d()) clear: True by default, clears previous plots plot: True by default, if False no plotting is taking place, only the plot object is being returned """ if gate_x is None or len(gate_x) != 2: print('Please select gate on X in a (min, max) format') return None if gate_y is not None and len(gate_y) != 2: print('Please select gate on Y in a (min, max) format') return None # If clear flag used, clear the plotting area if clear and plot: self.plotter.clear() # Switch mode to 1D self.mode = 1 # Deactivate all plots if clear flag is used if clear and plot: for p in Experiment.plots: p.active = False data = self.hisfile.load_histogram(his) if data[0] != 2: print('{} is not a 2D histogram'.format(his)) return None # x for x_axis data # y for y_axis data # w for weights # g for gate (result) # bg for background gate x = data[1] y = data[2] w = data[3] if gate_y is None: gate_y = [0, len(y)-2] y = y[gate_y[0]:gate_y[1]+1] g = w[gate_x[0]:gate_x[1]+1, gate_y[0]:gate_y[1]+1].sum(axis=0) dg = self._standard_errors_array(g) if bg_gate is not None: if (bg_gate[1] - bg_gate[0]) != (gate_x[1] - gate_x[0]): print('#Warning: background and gate of different widths') bg = w[bg_gate[0]:bg_gate[1]+1, gate_y[0]:gate_y[1]+1].sum(axis=0) g = g - bg # Note that since the gate is adding bins, the formula # used for standard error is no longer valid # This approximation should be good enough though dbg = self._standard_errors_array(bg) dg = self._add_errors(dg, dbg) title = '{}:{} gx({},{})'.format(his, self.hisfile.\ histograms[his]['title'].strip(), gate_x[0], gate_x[1]) if bg_gate is not None: title += ' bg ({}, {})'.format(bg_gate[0], bg_gate[1]) title = self._replace_latex_chars(title) histo = histogram.Histogram() histo.title = title histo.x_axis = y histo.weights = g histo.errors = dg gate_plot = Plot(histo, 'histogram', True) gate_plot.bin_size = bin_size gate_plot.norm = norm if plot: Experiment.plots.append(gate_plot) ylim = None if self.ylim is None: ylim = self._auto_scale_y() else: ylim = self.ylim self.plotter.plot1d(gate_plot, Experiment.xlim, ylim) return gate_plot
def d(self, *args, norm=1, bin_size=1, clear=True): """ Plot 1D histogram. * args: is a list of histograms that may be given as: - positive integer: is interpreted as the histogram id from a currently open file - negative integer: is interpreted as the registry number (see (show_registers()) - Plot object: see Plot class - string: in 'x-y' format where x and y are integers (note also mandatory quatation marks) is interpreted as a range of histograms ids * norm: may be given as a single float or int value or an 'area' string, also a list of lenght matching the *args list may be used with any combination of the above accepted values * bin_size: must be an integer, a list of ints is also accepted (see norm) * clear: is True by default, which means that previous plot is cleared if False is given, the previous plots are not cleared. Example: e.d(100, plot1, '105-106', -3, bin_size=[1, 2, 1, 1, 10], clear=False) """ plots = [] his_list = self._expand_d_args(args) normalization = self._expand_norm(norm, len(his_list)) if normalization is None: return None bin_sizes = self._expand_bin_sizes(bin_size, len(his_list)) if bin_sizes is None: return None # Clear the plotting area (of clear is False, the currently # active plots are not deactivated, so they got replotted at # the end of this function) self.plotter.clear() # Switch mode to 1D self.mode = 1 # Deactivate current plots if clear flag is used if clear: for p in Experiment.plots: p.active = False # Prepare data for plotting for i_plot, his in enumerate(his_list): if isinstance(his, int): # load histograms from the file if his > 0: data = self.hisfile.load_histogram(his) if data[0] != 1: print('{} is not a 1D histogram'.format(his)) return None title = self.hisfile.histograms[his]['title'].strip() title = '{}:{}'.format(his, self._replace_latex_chars(title)) histo = histogram.Histogram() histo.title = title histo.x_axis = data[1] histo.weights = data[3] histo.errors = self._standard_errors_array(data[3]) plot = Plot(histo, 'histogram', True) plot.bin_size = bin_sizes[i_plot] plot.norm = normalization[i_plot] plots.append(plot) Experiment.plots.append(plot) else: # plot histograms from registry # Numbered by negative numbers (-1 being the latest) # Call show_registers for a list of available plots try: plot = Experiment.plots[his] Experiment.plots[his].active = True Experiment.plots[his].bin_size = bin_sizes[i_plot] Experiment.plots[his].norm = normalization[i_plot] except IndexError: print('There is no plot in the registry under the', 'number', his, 'use show_registry() to see', 'available plots') return None plots.append(plot) elif isinstance(his, Plot): # If instance of Plot class is given, mark it active and add # to the deque (if not already there) # and to the array to be returned at the end his.active = True his.bin_size = bin_sizes[i_plot] his.norm = normalization[i_plot] plots.append(his) if his not in Experiment.plots: Experiment.plots.append(his) # Count the number of active plots active_plots = 0 for plot in Experiment.plots: if plot.active: active_plots += 1 # Here the actual plotting happens i_plot = 0 for plot in Experiment.plots: if plot.active: i_plot += 1 # If ylim is not given explicitely, go through the # active plots to find the plot limits # This is run only for the last plot. # Note that this is neccesary as matplotlib is not # autoscaling Y axis when # changing the X axis is being changed # If, in a future, the behaviour of matplotlib # changes, this part may dropped ylim = None if self.ylim is None and i_plot == active_plots: ylim = self._auto_scale_y() else: ylim = self.ylim # Note that ylim is autoscaled above if self.ylim is None # But we still keep self.ylim None, # to indicate autoscaling self.plotter.plot1d(plot, Experiment.xlim, ylim) # Return plots that were added or activated return plots
def fit_decay(self, his, gate, cycle, t_bin=1, time_range=None, model='grow_decay', pars=None, clear=True): """Fits decay time profile (grow-in/decay cycle): * his: is E-time histogram id * gate: should be given in format: ((x0, x1, (bg0, bg1)) * cycle: is list of beam start, beam stop, cycle end, e.g. (0, 100, 300) * t_bin: is a binning parameter (optional) * time_range: is a gate in time in (t0, t1) format (optional) * model: is a model used for fit (see decay_fitter) (default is 'grow_decay') * pars is a list of dictionaries (one dict per each parameter) (optional, use if model is different than the default one, see decay_fitter for details) """ if pars is None: T0 = {'name' : 'T0', 'value' : cycle[0], 'vary' : False} T1 = {'name' : 'T1', 'value' : cycle[1], 'vary' : False} T2 = {'name' : 'T2', 'value' : cycle[2], 'vary' : False} P1 = {'name' : 'P1', 'value' : 100.0} t1 = {'name' : 't1', 'value' : 100.0} parameters = [T0, T1, T2, P1, t1] if model == 'grow_decay2': P2 = {'name' : 'P2', 'value' : 1000.0} t2 = {'name' : 't2', 'value' : 1000.0} parameters.append(P2) parameters.append(t2) else: parameters = pars df = DecayFitter() xgate = self.gx(his, gate_x=gate[0], gate_y=time_range, bin_size=t_bin, plot=False) bckg = self.gx(his, gate_x=gate[1], gate_y=time_range, bin_size=t_bin, plot=False) dyg = self._standard_errors_array(xgate.histogram.weights) dyb = self._standard_errors_array(bckg.histogram.weights) gate_histo = histogram.Histogram() gate_histo.x_axis = xgate.histogram.x_axis gate_histo.weights = xgate.histogram.weights - bckg.histogram.weights gate_histo.errors = numpy.sqrt(dyg**2 + dyb**2) gate_histo.title = '{}: gx {} bg {} bin {}'.\ format(his, gate[0], gate[1], t_bin) plot_data = Plot(gate_histo, 'errorbar', True) t, n, parameters = df.fit(gate_histo.x_axis, gate_histo.weights, gate_histo.errors, model, parameters) fit_histo = histogram.Histogram() fit_histo.x_axis = t fit_histo.weights = n fit_histo.title = self._replace_latex_chars('Fit: {}'.format(model)) plot_fit = Plot(fit_histo, 'function', True) if clear: self.clear() self.plotter.plot1d(plot_fit, [cycle[0], cycle[2]], None) self.plotter.plot1d(plot_data, [cycle[0], cycle[2]], None) Experiment.plots.append(plot_fit) Experiment.plots.append(plot_data) return parameters