def draw_occupancy(self, plane, name=None, cluster=True, tel_coods=False, cut='', **dkw): cut_string = self.Cut(cut) + TCut( '' if cluster else 'plane == {}'.format(plane)) self.Tree.SetEstimate( sum(self.get_tree_vec('n_hits_tot', cut, dtype='u1'))) x, y = self.get_tree_vec(var=self.get_hit_vars(plane, cluster, tel_coods), cut=cut_string) bins = Bins.get_native_global() if tel_coods else Bins.get_pixel() tit = f'{"Cluster" if cluster else "Hit"} Occupancy {f"ROC {plane}" if name is None else name}' return self.Draw.histo_2d( x, y, bins, **prep_kw( dkw, title=tit, x_tit='x [mm]' if tel_coods else 'Column', y_tit='y [mm]' if tel_coods else 'Row', y_off=1.2, file_name=f'{"Cluster" if cluster else "Hit"}Map{plane}'))
def draw_occupancies(self): c = Draw.canvas(w=2, h=.75, divide=(2, )) self.Draw.histo_2d(self.X1, self.Y1, Bins.get_pixel(), x_tit='Column Tel', y_tit='Row Tel', canvas=c.cd(1)) self.Draw.histo_2d(self.X2, self.Y2, Bins.get_pixel(), x_tit='Column DUT', y_tit='Row DUT', canvas=c.cd(2))
def draw_raw_occupancies(): c = plot.canvas('c', w=1.2, h=1.2, divide=(2, 2)) p = get_tree_vec(t, 'plane', dtype='i2') for i in range(4): t.SetEstimate(count_nonzero(p == i)) x, y = get_tree_vec(t, ['col', 'row'], cut=f'plane == {i}', dtype='i2') plot.histo_2d(x, y, Bins.get_pixel(), canvas=c.cd(i + 1))
def get_signal_map(self, res=None, cut=None, fid=False, square=False, m=None, n=None, local=True, _redo=False): self.Tree.SetEstimate(self.Run.NEvents) var, bins = self.get_track_vars( local=local) + [self.get_ph_var()], Bins.get_global( res, square) if m is None else self.get_fid_bins(m, n) x, y, zz = self.get_tree_vec( var, self.Cut.generate_custom(exclude='fiducial', prnt=False) if not fid and cut is None else self.Cut(cut)) tit, ztit = 'Pulse Height Map', 'Pulse Height [mV]' return self.Draw.prof2d(x, y, zz, bins, tit, **self.Tracks.ax_tits(), z_tit=ztit, z_range=self.find_sm_range(res, square, m, n, _redo=_redo), show=False, pal=53)
def draw_cluster_occupancies(self, planes=None, cut='', show=True, **dkw): self.Run.set_estimate(self.NRocs * self.Run.NEvents) n = self.get_tree_vec('n_clusters', self.Cut(cut), dtype='i') self.Run.set_estimate(sum(n)) loc = array( self.get_tree_vec([self.XVar, self.YVar], self.Cut(cut), dtype='i2')).T c = self.Draw.canvas('Cluster Maps', w=.66 * ceil(self.NRocs / 2), h=1.1, divide=(int(ceil(self.NRocs / 2)), 2), show=show) for i, pl in enumerate( (range(self.NRocs) if planes is None else planes), 1): x, y = loc[tile(insert(zeros(self.NRocs - 1, '?'), pl, True), n.size // self.NRocs).repeat(n)].T self.Draw.histo_2d(x, y, Bins.get_pixel(), canvas=c.cd(i), **prep_kw(dkw, title='Cluster Maps', x_tit='col', y_tit='row'))
def find_mask_(self, plane=1, _redo=False): self.Tree.SetEstimate( sum(self.get_tree_vec('n_hits_tot', dtype='u1', nentries=50000))) x, y = self.get_tree_vec(self.get_hit_vars( arange(self.Run.NTelPlanes)[::-1][plane]), nentries=50000, dtype='u2') histos = [ self.Draw.distribution(x, Bins.get_pixel_x(), show=False), self.Draw.distribution(y, Bins.get_pixel_y(), show=False) ] return array([ h.GetBinCenter(i) for h in histos for i in [ h.FindFirstBinAbove(h.GetMaximum() * .01), h.FindLastBinAbove(h.GetMaximum() * .01) ] ], 'i')[[0, 2, 1, 3]].tolist()
def get_hitmap(self, res=None, cut='', _redo=False): self.Run.set_estimate() x, y = self.get_tree_vec(self.get_track_vars(), self.Cut(cut)) return self.Draw.histo_2d(x, y, Bins.get_global(res), 'Hit Map', x_tit='Track Position X [mm]', y_tit='Track Position Y [mm]', show=False)
def draw_y(self, off=0, start=0, end=None, **dkw): x, y = self.get_data(off, start, end)[1].T h = self.Draw.histo_2d( x, y, Bins.get_pixel_y() * 2, **prep_kw(dkw, x_tit=f'Row Plane {self.DUTPlane - self.NTelPlanes}', y_tit=f'Row DUT {self.TelPlane}')) Draw.info(f'Correlation Coefficent: {h.GetCorrelationFactor():.2f}', size=.03)
def __init__(self, run_number, diamond_nr=1, testcampaign=None, load_tree=None, verbose=False, prnt=True): self.Run = self.init_run(run_number, testcampaign, load_tree, verbose) self.DUT = self.Run.DUTs[diamond_nr - 1] super(DUTAnalysis, self).__init__(testcampaign, sub_dir=join(self.DUT.Name, str(self.Run.Number)), verbose=verbose) self.Tree = self.Run.Tree self.StartTime = self.Run.StartTime if self.Tree.Hash( ) else time_stamp(self.Run.LogStart) self.print_start(run_number, prnt, dut=self.DUT.Name) self.update_config() # Sub-Analyses self.Currents = Currents(self) if self.Tree.Hash(): self.NRocs = self.Run.NPlanes self.Cut = self.init_cut() self.StartEvent = self.Cut.get_min_event() self.EndEvent = self.Cut.get_max_event() self.Bins = Bins(self) self.Tracks = Tracks(self) self.Tel = Telescope(self) # alignment self.Alignment = None self.IsAligned = self.check_alignment()
def find_sm_range(self, res=None, square=False, m=None, n=None, _redo=False): var, bins = self.get_track_vars() + [ self.get_ph_var() ], Bins.get_global(res, square) if m is None else self.get_fid_bins( m, n) return ax_range(get_2d_hist_vec( self.Draw.prof2d(*self.get_tree_vec(var, self.Cut()), bins, show=False)), thresh=4)
def get_map(self, res=None, fid=False, cut=None, local=False, _redo=False): x, y, zz = self.get_tree_vec( self.get_track_vars(local=local) + [self.get_var()], self.Cut(cut) if cut or fid else self.Cut.exclude('fiducial')) tit, (xtit, ytit), ztit = 'Efficiency Map', [ f'Track Position {i} [mm]' for i in ['X', 'Y'] ], 'Efficiency [%]' return self.Draw.prof2d(x, y, zz * 100, Bins.get_global(res), tit, x_tit=xtit, y_tit=ytit, z_tit=ztit, show=False)
def draw_normal_distribution(self, m=20, n=30, show=True): ph, x, y = self.get_tree_vec(var=[self.get_ph_var()] + self.get_track_vars(), cut=self.Cut()) ix, bx, iy, by = self.get_fid_bins(m, n) n = cumsum(histogram2d(x, y, [bx, by])[0].flatten().astype( 'i'))[:-1] # get the number of events for each bin values = split(ph[lexsort((digitize(x, bx), digitize(y, by)))], n) # bin x and y and sort then ph according to bins values = concatenate([ lst / mean(lst) * mean(ph) for lst in values if lst.size > 2 ]) # normalise the values of each bin return self.Draw.distribution( values, self.Bins.get_pad_ph(Bins.find_width(values)), 'Signal Distribution Normalised by area mean', x_tit='Pulse Height [mV]', show=show)
def find_beam_interruptions(self, bin_width=10, threshold=.4): """ Looking for beam interruptions by evaluating the event rate. """ bin_values, time_bins = histogram( self.Run.Time / 1000, bins=Bins(self.Ana).get_raw_time(bin_width)[1]) m = mean( bin_values[bin_values.argsort()][-20:-10] ) # take the mean of the 20th to the 10th highest bin to get an estimate of the plateau deviating_bins = where(abs(1 - bin_values / m) > threshold)[0] times = time_bins[deviating_bins] + bin_width / 2 - self.Run.Time[ 0] / 1000 # shift to the center of the bin not_connected = where( concatenate([[False], deviating_bins[:-1] != deviating_bins[1:] - 1 ]))[0] # find the bins that are not consecutive times = split(times, not_connected) interruptions = [[ self.get_event_at_time(v, rel=True) for v in [t[0], t[-1]] ] for t in times] if len(times[0]) else [] return interruptions
def draw_trigger_phase(self, dut=False, cut=None, **kwargs): x = self.get_tree_vec( f'trigger_phase[{1 if dut else 0}]', self.Cut.generate_custom( exclude=['trigger_phase']) if cut is None else TCut(cut)) h = self.Draw.distribution(x, Bins.make(-.5, 10), 'Trigger Phase', x_tit='Trigger Phase', show=False) return self.Draw.distribution( h, **prep_kw(kwargs, y_off=1.7, lm=.16, file_name=f'TriggerPhase{dut:d}', gridx=True, y_range=ax_range(0, h.GetMaximum(), 0, .15), stats=set_entries()))
def draw_split_map_disto(self, m, n=None, x_range=None, fit=True, normalise=False, show=True): x = get_2d_hist_vec(self.split_signal_map(m, n, show=False)[0]) / ( self.get_pulse_height() if normalise else 1) h = self.Draw.distribution( x, Bins.make(*choose(x_range, ax_range(x, 0, .1, .4, thresh=3)), n=sqrt(x.size)), 'Signal Map Distribution', x_tit='{}Pulse Height{}'.format( *['Normalised ', ''] if normalise else ['', ' [mV]']), show=show) h.Fit('gaus', 'qs{}'.format('' if fit else '0')) format_statbox(h, all_stat=True, fit=fit) return mean_sigma(x)
def draw_efficiency_map(self, res=None, n_sigma=4, cut=None, show=True): cut_string = choose(self.Cut(cut) + self.Cut.get('tracks'), self.Cut.generate_custom(exclude=['fiducial']), decider=cut) e, x, y = self.get_tree_vec(var=[self.get_eff_var(n_sigma)] + self.get_track_vars(), cut=cut_string) p = self.Draw.prof2d(x, y, e * 100, Bins.get_global(res), 'Efficiency Map', x_tit='X [cm]', y_tit='Y [cm]', z_tit='Efficiency [%]', ncont=100, z_range=[0, 100], show=show) set_2d_ranges(p, dx=3, dy=3) self.draw_fid_cut(scale=10) self.draw_detector_size() self.Draw.save_plots('EffMap')
def draw_event(self, event, show=True, grid=True): x, y, p = self.get_tree_vec(self.get_hit_vars(0, cluster=False) + ['plane'], nentries=1, firstentry=event) c = self.Draw.canvas(w=1.5, h=1.5, divide=(int(ceil(sqrt(self.NRocs))), int(ceil(sqrt(self.NRocs)))), show=show) for i in range(self.NRocs): self.Draw.histo_2d(x[p == i], y[p == i], Bins.get_pixel(), 'Hits', draw_opt='col', x_tit='col', y_tit='row', rm=.03, stats=0, show=show, canvas=c.cd(i + 1)) self.draw_pixel_grid() if grid else do_nothing()
def draw_sig_map_disto(self, res=None, cut=None, fid=True, x_range=None, redo=False, normalise=False, ret_value=False, ph_bins=None, show=True, save=True): x = get_2d_hist_vec( self.draw_signal_map(res, cut, fid, redo=redo, show=False), err=False) / (self.get_pulse_height() if normalise else 1) x_range = choose(x_range, ax_range(x, fl=.1, fh=.1)) h = self.Draw.distribution(x, Bins.make(*x_range, n=sqrt(x.size)), 'Signal Map Distribution', x_tit='Pulse Height [mV]', y_off=2, lm=.15, show=show, save=save) return mean_sigma(x) if ret_value else h
def draw_occupancies(): c = plot.canvas('c', w=1.5, h=1.5, divide=(2, 2)) for i in range(z.NPlanes): x, y = z.get_tree_vec(['cluster_col[{}]'.format(i), 'cluster_row[{}]'.format(i)]) plot.histo_2d(x, y, Bins.get_pixel(), stats=0, show=0, canvas=c.cd(i + 1))
def draw_hitmap(dut=1, res=None): x, y = get_tree_vec(t, [f'dia_track_{i}_local[{dut - 1}]' for i in ['x', 'y']]) plot.histo_2d(x * 10, y * 10, Bins.get_global(res), 'HitMap', x_tit='Track Position X [mm]', y_tit='Track Position Y [mm]')
class DUTAnalysis(Analysis): PhTit = 'Pulse Height [mV]' def __init__(self, run_number, diamond_nr=1, testcampaign=None, load_tree=None, verbose=False, prnt=True): self.Run = self.init_run(run_number, testcampaign, load_tree, verbose) self.DUT = self.Run.DUTs[diamond_nr - 1] super(DUTAnalysis, self).__init__(testcampaign, sub_dir=join(self.DUT.Name, str(self.Run.Number)), verbose=verbose) self.Tree = self.Run.Tree self.StartTime = self.Run.StartTime if self.Tree.Hash( ) else time_stamp(self.Run.LogStart) self.print_start(run_number, prnt, dut=self.DUT.Name) self.update_config() # Sub-Analyses self.Currents = Currents(self) if self.Tree.Hash(): self.NRocs = self.Run.NPlanes self.Cut = self.init_cut() self.StartEvent = self.Cut.get_min_event() self.EndEvent = self.Cut.get_max_event() self.Bins = Bins(self) self.Tracks = Tracks(self) self.Tel = Telescope(self) # alignment self.Alignment = None self.IsAligned = self.check_alignment() def __str__(self): return f'{self.__class__.__name__} of {self.Run} and {self.DUT}' def __repr__(self): return f'{self.__class__.__name__} of {self.DUT}, {self.Run}' # ---------------------------------------- # region INIT @staticmethod def init_run(run_number, testcampaign, load_tree, verbose): return Run(run_number, testcampaign, load_tree, verbose) def init_cut(self): return Cut(self) def update_config(self): pass @property def info_header(self): return ['Run', 'Type', 'Diamond', 'Flux [kHz/cm2]', 'HV [V]'] def show_information(self, header=True, prnt=True, ret_row=False): rows = [[ self.Run.Number, self.Run.Info['runtype'], self.DUT.Name, '{:14.1f}'.format(self.Run.Flux.n), '{:+6.0f}'.format(self.DUT.Bias) ]] if ret_row: return rows[0] print_table(rows, self.info_header if header else None, prnt=prnt) # endregion INIT # ---------------------------------------- # ---------------------------------------- # region SAVE def save_all(self, print_link=True): self.save_data() self.save_plots(print_link) def save_data(self, data=None): if self.Draw.MountExists: data = choose(data, self.get_data()) with h5py.File(join(self.Draw.ServerMountDir, 'data', 'data.hdf5'), 'a') as f: if self.TCString not in f: f.create_group(self.TCString) if str(self.DUT.Number) not in f[self.TCString]: f[self.TCString].create_dataset( str(self.DUT.Number), data=zeros((self.Run.get_max_run() + 1, len(data), 2), 'd')) f[self.TCString][str( self.DUT.Number)][self.Run.Number] = array([[v.n, v.s] for v in data], 'd') @reload_tree @quiet def save_plots(self, print_link=True): self.draw_hitmap(show=False) self.draw_signal_distribution(show=False) self.draw_signal_map(show=False) self.Currents.draw(show=False, savename='Current') self.draw_flux(save=self.has_branch('rate'), show=False) self.draw_pulse_height(show=False) self.Draw.print_http('plots.html', force_print=print_link) def save_tree(self, cut=None): f = TFile('test.root', 'RECREATE') t = self.Tree.CloneTree(0) n = self.Tree.Draw('Entry$', self.Cut(cut), 'goff') good_events = self.Run.get_tree_vec(n, dtype='i4') self.PBar.start(n) for i, ev in enumerate(good_events): self.Tree.GetEntry(ev) t.Fill() self.PBar.update(i) f.cd() t.Write() macro = self.Run.RootFile.Get('region_information') if macro: macro.Write() f.Write() f.Close() self.info('successfully saved tree with only cut events.') # endregion SAVE # ---------------------------------------- # ---------------------------------------- # region GET def get_data(self): return [] def has_branch(self, branch): return self.Run.has_branch(branch) def get_time(self): return self.Run.get_time() def get_track_vars(self, mm=True, local=True, pixel=False): return self.Cut.get_track_vars(self.DUT.Number - 1, mm, local, pixel) def get_t_var(self): return 'time / 1000.' if self.Run.TimeOffset is None else '(time - {}) / 1000.'.format( self.Run.TimeOffset) def get_eff_var(self, *args, **kwargs): return '' def get_t_off(self, rel_time): return self.Run.StartTime if rel_time else 0 def get_t_args(self, rel_time=False): return {'x_tit': 'Time [hh:mm]', 't_ax_off': self.get_t_off(rel_time)} def get_tree_vec(self, var, cut='', dtype=None, nentries=None, firstentry=0): return self.Run.get_tree_vec(var, cut, dtype, nentries, firstentry) def get_events(self, cut=None, redo=False): if type(cut) == str: return self.get_tree_vec('Entry$', cut, dtype='i4') cut = self.Cut(cut) return do_hdf5(self.make_simple_hdf5_path('', cut.GetName(), 'Events'), self.get_tree_vec, redo, dtype='i4', var='Entry$', cut=cut) if cut.GetTitle() else arange(self.Run.NEvents) def get_event_cut(self, cut=None, redo=False): return self.make_event_cut(self.get_events(cut, redo)) def set_event_cut(self, events): """ selects the [events] in the TTree, undo with self.reset_entries()""" from ROOT import TEntryList elist = TEntryList() for ev in array(events, 'i'): elist.Enter(ev) self.Tree.SetEntryList(elist) def reset_entries(self): """ reset the TEntryList from the TTree """ self.Tree.SetEntryList(0) def make_event_cut(self, events): c = zeros(self.Run.NEvents, dtype=bool) c[events] = True return c def get_pulser_cut(self, inv=False): cut = self.make_event_cut(self.get_events(self.Cut['pulser'])) return invert(cut) if inv else cut def get_sub_events(self, cut): e = array(self.get_events()) s = array(self.get_events(cut)) cut = zeros(self.Run.NEvents + 1, bool) cut[s] = True return cut[e] def get_event_at_time(self, seconds, rel=False): return self.Run.get_event_at_time(seconds, rel) @save_pickle(sub_dir='Entries', suf_args='all') def get_n_entries(self, cut=None, _redo=False): return self.Tree.GetEntries(self.Cut(cut).GetTitle()) def get_current(self): return self.Currents.get() def get_irradiation(self): return self.DUT.get_irradiation(self.TCString) def get_attenuator(self): return False def get_ph_var(self, ped=False): return '' def get_pulse_height(self, *args, **kwargs): return ufloat(0, 0) def get_sm_data(self, cut=None, fid=False): """ :return: signal map data as numpy array [[x], [y], [ph]] with units [[mm], [mm], [mV]] :param cut: applies all cuts if None is provided. :param fid: return only values within the fiducial region set in the AnalysisConfig.ini""" cut = self.Cut.generate_custom(exclude=[ 'fiducial' ], prnt=False) if not fid and cut is None else self.Cut(cut) return self.get_tree_vec(self.get_track_vars() + [self.get_ph_var()], cut) @save_pickle('Uniformity', sub_dir='Signal', suf_args='all') def get_uniformity(self, use_fwc=True, _redo=False): return self.draw_uniformity(use_fwc=use_fwc, redo=_redo, show=False) def get_track_length_var(self): dx2, dy2 = [ 'TMath::Power(TMath::Tan(TMath::DegToRad() * {}_{}), 2)'.format( 'slope' if self.Run.has_branch('slope_x') else 'angle', direction) for direction in ['x', 'y'] ] return '{} * TMath::Sqrt({} + {} + 1)'.format(self.DUT.Thickness, dx2, dy2) def get_flux(self, plane=None, corr=True, full_size=False, redo=False): return self.Tel.get_flux(plane, corr, True, full_size, _redo=redo) def get_ph_values(self, *args, **kwargs): """ :returns: all pulse height values for a given cut. [numpy.ndarray] """ def get_signal_name(self, *args, **kwargs): """ :returns: the pulse height variable in the tree. [str] """ def get_signal_var(self, *args, **kwargs): """ :returns: the pulse height variable in the tree + corrections. [str] """ def get_split_ph(self, m=2): return get_2d_hist_vec(self.split_signal_map(m, show=0)[0]) def get_next_dut(self, dut_nr=None): return next( (dut for dut in self.Run.DUTs if dut.Number != self.DUT.Number), 1) if dut_nr is None else self.Run.DUTs[dut_nr - 1] # endregion GET # ---------------------------------------- # ---------------------------------------- # region ALIGNMENT def get_alignment(self): from src.event_alignment import EventAligment return EventAligment def init_alignment(self, **kwargs): if self.Alignment is None: self.Alignment = self.get_alignment()(self.Run.Converter) return self.Alignment def get_aligned(self, *args, **kwargs): return self.get_alignment()(self.Run.Converter).get_aligned( *args, **kwargs) @save_pickle('Events', sub_dir='Alignment') def check_alignment_(self, _redo=False): return calc_eff(values=self.init_alignment().get_aligned())[0] > 99.7 def check_alignment(self, redo=False): """ check if the events are aligned""" is_aligned = self.check_alignment_(_redo=redo) warning(f'Run {self.Run.Number} is misaligned :-(', prnt=not is_aligned) return is_aligned def draw_alignment(self, bin_size=1000, **kwargs): """ draw the aligment of telescope and DUT events """ self.init_alignment() bins, y = self.Alignment.get_time_bins( bin_size=bin_size), self.Alignment.get_aligned(bin_size=bin_size) x, y = (bins[1][:-1] + diff(bins[1]) / 2)[:y.size].repeat(y + 1), full( sum(y + 1), 1) h = self.Draw.histo_2d( x, y, bins + [3, 0, 3], 'Event Alignment', **prep_kw(kwargs, x_tit='Time hh:mm', y_tit='Alignment', stats=False, l_off_y=99, center_y=True, draw_opt='col', **Draw.mode(2, lm=.05, y_off=.3), pal=(3, array([1, 633, 418], 'i')), t_ax_off=0, rm=.03, z_range=[0, 2])) Draw.legend([ Draw.box(0, 0, 0, 0, line_color=c, fillcolor=c) for c in [418, 633] ], ['aligned', 'misaligned'], 'f') self.Draw.save_plots('EventAlignment') return h # endregion ALIGNMENT # ---------------------------------------- # ---------------------------------------- # region ALIASES def draw_chi2(self, *args, **kwargs): return self.Tracks.draw_chi2(*args, **kwargs) def draw_chi2s(self, *args, **kwargs): return self.Tracks.draw_chi2s(*args, **kwargs) def draw_angle(self, *args, **kwargs): return self.Tracks.draw_angle(*args, **kwargs) def draw_angles(self, *args, **kwargs): return self.Tracks.draw_angles(*args, **kwargs) def draw_occupancies(self, *args, **kwargs): return self.Tel.draw_occupancies(*args, **kwargs) def draw_cluster_occupancies(self, *args, **kwargs): return self.Tel.draw_cluster_occupancies(*args, **kwargs) def get_mean_angle(self, mode, redo): return self.Tracks.get_mean_angle(mode, _redo=redo) def draw_current(self, *args, **kwargs): return self.Currents.draw(*args, **kwargs) def draw_flux(self, *args, **kwargs): return self.Tel.draw_flux(*args, **kwargs) def get_pulse_height_trend(self, *args): return TProfile() def draw_pulse_height(self, *args, **kwargs): return TProfile() def get_signal_distribution(self, *args, **kwargs): pass def draw_signal_distribution(self, *args, **kwargs): pass def draw_raw_signal_distribution(self, *args, **kwargs): return self.draw_signal_distribution(*args, **kwargs) def draw_fid_cut(self, scale=10): return self.Cut.draw_fid(scale) def get_efficiency(self, *args, **kwargs): pass def draw_beam_profile(self, *args, **kwargs): return self.Tracks.draw_beam_profile(*args, **kwargs) def purity(self, redo=False): return self.Cut.purity(_redo=redo) # endregion ALIASES # ---------------------------------------- # ---------------------------------------- # region SIZES def draw_size(self, size=None, color=1, name=''): if size is None: return warning( 'The {} size of "{}" was not specified in the dia info'.format( ' {}'.format(name) if name else '', self.DUT.Name)) c = get_last_canvas() cx, cy = self.find_center() x, y = size c.cd() return Draw.box(cx - x / 2, cy - y / 2, cx + x / 2, cy + y / 2, line_color=color, width=3, name=name, fillstyle=4000) def draw_detector_size(self): return self.draw_size(self.DUT.Size, color=432, name='sensor') def draw_active_area(self): pass def draw_all_sizes(self): s0 = self.draw_fid_cut() s0.SetName('fiducial') s1 = self.draw_detector_size() s2 = self.draw_active_area() return [s0, s1, s2] @save_pickle('Center', sub_dir='Maps') def find_center(self, _redo=False, h=None, _no_save=False): h = choose(h, self.get_signal_map(cut='', _redo=_redo)) px, py = h.ProjectionX(), h.ProjectionY() return array([ mean([ p.GetBinCenter(b) for b in [ p.FindFirstBinAbove(p.GetMaximum() * .5), p.FindLastBinAbove(p.GetMaximum() * .5) ] ]) for p in [px, py] ]) # endregion SIZES # ---------------------------------------- # ---------------------------------------- # region EFFICIENCY def draw_efficiency(self, bin_size=None, show=True): self.Draw.efficiency(*self.get_tree_vec( [self.get_t_var(), self.get_eff_var()], self.Cut()), self.Bins.get_time(bin_size), show=show, **self.get_t_args()) def draw_efficiency_map(self, res=None, n_sigma=4, cut=None, show=True): cut_string = choose(self.Cut(cut) + self.Cut.get('tracks'), self.Cut.generate_custom(exclude=['fiducial']), decider=cut) e, x, y = self.get_tree_vec(var=[self.get_eff_var(n_sigma)] + self.get_track_vars(), cut=cut_string) p = self.Draw.prof2d(x, y, e * 100, Bins.get_global(res), 'Efficiency Map', x_tit='X [cm]', y_tit='Y [cm]', z_tit='Efficiency [%]', ncont=100, z_range=[0, 100], show=show) set_2d_ranges(p, dx=3, dy=3) self.draw_fid_cut(scale=10) self.draw_detector_size() self.Draw.save_plots('EffMap') # endregion SIZES # ---------------------------------------- def draw_ph_pull(self, bin_size=None, fit=True, binning=None, **kwargs): p = self.draw_pulse_height(bin_size, show=False, save=False)[0] h = self.Draw.pull( p, binning, ret_h=True, title=f'Signal Bin{Bins.get_size(bin_size)} Distribution', **prep_kw(kwargs)) format_statbox(h, all_stat=True, fit=fit) h.Fit('gaus', f'qs{"" if fit else 0}') self.Draw.save_plots(h.GetTitle().replace(" ", "")) return h def draw_track_length(self, show=True): h = TH1F('htd', 'Track Distance in Diamond', 200, self.DUT.Thickness, self.DUT.Thickness + 1) self.Tree.Draw('{}>>htd'.format(self.get_track_length_var()), 'n_tracks', 'goff') format_histo(h, x_tit='Distance [#mum]', y_tit='Entries', y_off=2, lw=2, stats=0, fill_color=Draw.FillColor, ndivx=405) self.Draw(h, show, lm=.16) return h def draw_signal_vs_angle(self, mode='x', bin_size=.1, show=True): p = TProfile('psa{}'.format(mode), 'Pulse Height vs. Angle in {}'.format(mode.title()), *self.Bins.get_angle(bin_size)) self.Tree.Draw( '{}:angle_{m}>>psa{m}'.format(self.get_signal_var(), m=mode), self.Cut(), 'goff') format_histo(p, x_tit='Track Angle [deg]', y_tit='Pulse Height [mV]', y_off=1.5) self.Draw(p, show=show, lm=.12, stats=set_entries()) def draw_signal_vs_trigger_phase(self, dut=False, cut=None, show=True): x, y = self.get_tree_vec( ['trigger_phase[{}]'.format([0, 1][dut]), self.get_signal_var()], self.Cut(cut)) self.Draw.profile(x, y, make_bins(11), 'Signal vs. Trigger Phase', x_tit='Trigger Phase', y_tit='Pulse Height [mV]', show=show) def draw_signal_vs_chi2(self, m=0, **dkw): x, y = self.get_tree_vec([self.Tracks.chi2_var(m), self.get_ph_var()], cut=self.Cut.no_chi2()) self.Draw.profile( x, y, **prep_kw(dkw, x_tit=f'#chi^{{2}} in {self.Tracks.chi2_tit(m)}', y_tit=self.PhTit)) def draw_signal_vs_chi2_cut(self, n=16, x0=20, **dkw): xx, xy, y = self.get_tree_vec( self.Tracks.chi2_vars() + [self.get_ph_var()], self.Cut.no_chi2()) x = linspace(x0, 100, n + 1) qx, qy = [quantile(c, x / 100) for c in [xx, xy]] y = [ mean_sigma(y[(xx < qx[i]) & (xy < qy[i])])[0] for i in range(qx.size) ] self.Draw.graph( x, y, 'PH V Chi2Cut', **prep_kw(dkw, x_tit='#chi^{2} Cut Quantile [%]', y_tit=self.PhTit, draw_opt='ae3', opacity=.2, fill_color=2)) return self.Draw.graph(x, uarr2n(y), draw_opt='samel', lw=2) # ---------------------------------------- # region SIGNAL MAP @save_pickle('SMRange', sub_dir='Maps', suf_args='all') def find_sm_range(self, res=None, square=False, m=None, n=None, _redo=False): var, bins = self.get_track_vars() + [ self.get_ph_var() ], Bins.get_global(res, square) if m is None else self.get_fid_bins( m, n) return ax_range(get_2d_hist_vec( self.Draw.prof2d(*self.get_tree_vec(var, self.Cut()), bins, show=False)), thresh=4) @save_pickle('SM', sub_dir='Maps', print_dur=True, suf_args='all') def get_signal_map(self, res=None, cut=None, fid=False, square=False, m=None, n=None, local=True, _redo=False): self.Tree.SetEstimate(self.Run.NEvents) var, bins = self.get_track_vars( local=local) + [self.get_ph_var()], Bins.get_global( res, square) if m is None else self.get_fid_bins(m, n) x, y, zz = self.get_tree_vec( var, self.Cut.generate_custom(exclude='fiducial', prnt=False) if not fid and cut is None else self.Cut(cut)) tit, ztit = 'Pulse Height Map', 'Pulse Height [mV]' return self.Draw.prof2d(x, y, zz, bins, tit, **self.Tracks.ax_tits(), z_tit=ztit, z_range=self.find_sm_range(res, square, m, n, _redo=_redo), show=False, pal=53) def draw_signal_map(self, res=None, cut=None, fid=False, square=False, m=None, n=None, local=True, scale=False, redo=False, **kwargs): h = self.get_signal_map(res, cut, fid, square, m, n, local, _redo=redo) h.Scale(1 / self.get_pulse_height().n) if scale else do_nothing() rz = array([h.GetMinimum(), h.GetMaximum() ]) * 1 / self.get_pulse_height().n if scale else None h = self.Draw.prof2d( h, **prep_kw(kwargs, centre=4, ncont=50, ndivy=510, ndivx=510, pal=53, z_tit='Relative Pulse Height' if scale else None, z_range=rz)) if m is None: self.draw_fid_cut( ) if 'mirror' not in kwargs and 'rot' not in kwargs else do_nothing( ) self.Draw.save_plots( 'SignalMap2D', **prep_kw(kwargs, save=res is None and m is None and not scale)) return h @save_pickle('HM', sub_dir='Maps', suf_args='all') def get_hitmap(self, res=None, cut='', _redo=False): self.Run.set_estimate() x, y = self.get_tree_vec(self.get_track_vars(), self.Cut(cut)) return self.Draw.histo_2d(x, y, Bins.get_global(res), 'Hit Map', x_tit='Track Position X [mm]', y_tit='Track Position Y [mm]', show=False) def draw_hitmap(self, res=None, cut='', redo=False, **kwargs): return self.Draw(self.get_hitmap(res, cut, _redo=redo), **prep_kw(kwargs, centre=4, title='DUT Hit Map'), leg=self.Cut.get_fid(), file_name='HitMap') def get_fid_bins(self, m, n): n = choose(n, m) x, y = self.Cut.load_fiducial() * 10 x_bins = linspace(x[0], x[2], m + 1) y_bins = linspace(y[0], y[2], n + 1) return [m, x_bins, n, y_bins] def get_fid_bin_events(self, m, n, mi, ni): m, x, n, y = self.get_fid_bins(m, n) cut = self.Cut.generate_sub_fid( 'f{}{}{}{}'.format(m, n, mi, ni), *array([x[mi], x[mi + 1], y[ni], y[ni + 1]]) / 10) return self.get_sub_events(cut) def split_signal_map(self, m=2, n=None, redo=False, draw_n=False, grid=True, **kwargs): h = self.draw_signal_map(fid=True, m=m, n=n, redo=redo, **prep_kw(kwargs, stats=False)) Draw.grid(*get_2d_bins(h, arr=True), width=2) if grid else do_nothing() self.Draw.save_plots('SplitSigMap') Draw.bin_numbers(h, draw_n) return h def draw_split_means(self, n=10): x = arange(1, n + 1).tolist() y = [ mean_sigma(get_2d_hist_vec(h))[0] for h in [self.split_signal_map(i, i, show=False)[0] for i in x] ] self.Draw.graph(x, y, title='Split Means', x_tit='Division', y_tit='Pulse Height [mV]', draw_opt='ap') def get_ph_bins(self, n=10, pmin=90, pmax=95, show=True): h = self.split_signal_map(n, n, show=show, grid=False) (x, y), v = get_2d_vecs(h) wx, wy = diff(x)[0] / 2, diff(y)[0] / 2 points = array(meshgrid(x, y)).T[where((v >= pmin) & (v < pmax))] for ix, iy in points: Draw.box(ix - wx, iy - wy, ix + wx, iy + wy, line_color=840, width=4, show=show) return points, wx, wy def draw_ph_bin_disto(self, n=10, pmin=90, pmax=95, **dkw): ph, x, y = self.get_tree_vec(var=[self.get_ph_var()] + self.get_track_vars(), cut=self.Cut()) points, wx, wy = self.get_ph_bins(n, pmin, pmax, show=False) cut = any([(x > ix - wx) & (x < ix + wx) & (y > iy - wy) & (x < iy + wy) for ix, iy in points], axis=0) return self.Draw.distribution( ph[cut], tit=f'Pulse Height of Areas in [{pmin}, {pmax}] mV', **prep_kw(dkw, x_tit='Pulse Height [mV]')) def draw_normal_distribution(self, m=20, n=30, show=True): ph, x, y = self.get_tree_vec(var=[self.get_ph_var()] + self.get_track_vars(), cut=self.Cut()) ix, bx, iy, by = self.get_fid_bins(m, n) n = cumsum(histogram2d(x, y, [bx, by])[0].flatten().astype( 'i'))[:-1] # get the number of events for each bin values = split(ph[lexsort((digitize(x, bx), digitize(y, by)))], n) # bin x and y and sort then ph according to bins values = concatenate([ lst / mean(lst) * mean(ph) for lst in values if lst.size > 2 ]) # normalise the values of each bin return self.Draw.distribution( values, self.Bins.get_pad_ph(Bins.find_width(values)), 'Signal Distribution Normalised by area mean', x_tit='Pulse Height [mV]', show=show) def draw_sig_map_disto(self, res=None, cut=None, fid=True, x_range=None, redo=False, normalise=False, ret_value=False, ph_bins=None, show=True, save=True): x = get_2d_hist_vec( self.draw_signal_map(res, cut, fid, redo=redo, show=False), err=False) / (self.get_pulse_height() if normalise else 1) x_range = choose(x_range, ax_range(x, fl=.1, fh=.1)) h = self.Draw.distribution(x, Bins.make(*x_range, n=sqrt(x.size)), 'Signal Map Distribution', x_tit='Pulse Height [mV]', y_off=2, lm=.15, show=show, save=save) return mean_sigma(x) if ret_value else h def draw_split_map_disto(self, m, n=None, x_range=None, fit=True, normalise=False, show=True): x = get_2d_hist_vec(self.split_signal_map(m, n, show=False)[0]) / ( self.get_pulse_height() if normalise else 1) h = self.Draw.distribution( x, Bins.make(*choose(x_range, ax_range(x, 0, .1, .4, thresh=3)), n=sqrt(x.size)), 'Signal Map Distribution', x_tit='{}Pulse Height{}'.format( *['Normalised ', ''] if normalise else ['', ' [mV]']), show=show) h.Fit('gaus', 'qs{}'.format('' if fit else '0')) format_statbox(h, all_stat=True, fit=fit) return mean_sigma(x) def get_sm_std(self, res=None, redo=False): return self.draw_sig_map_disto(show=False, res=res, redo=redo, normalise=True, ret_value=True, save=False)[1] def draw_sm_profile(self, mode='x', factor=1.5, cut=None, fid=False, hitmap=False, redo=False, show=True): s = self.draw_signal_map(factor, cut, fid, hitmap=hitmap, redo=redo, show=False) g = Draw.make_tgrapherrors('g_smp', 'Signal Map Profile') values = [ [] for _ in range(s.GetNbinsX() if mode == 'x' else s.GetNbinsY()) ] for xbin in range(s.GetNbinsX()): for ybin in range(s.GetNbinsY()): value = s.GetBinContent(xbin, ybin) if value: values[(xbin if mode == 'x' else ybin)].append(value) for i, lst in enumerate(values): m, sigma = mean_sigma(lst) if lst else (0., 0.) xval = s.GetXaxis().GetBinCenter( i) if mode == 'x' else s.GetYaxis().GetBinCenter(i) g.SetPoint(i, xval, m) g.SetPointError(i, 0, sigma) format_histo(g, x_tit='Track in {m} [cm]'.format(m=mode), y_tit='Pulse Height [au]', y_off=1.5, ndivx=515) self.Draw(g, 'SignalMapProfile', draw_opt='ap', lm=.14, show=show, gridx=True) def draw_error_signal_map(self, show=True): h = self.draw_signal_map(show=False, fid=True).ProjectionXY('', 'c=e') format_histo(h, name='hsme', title='Signal Map Errors', y_off=1.6) self.Draw(h, 'SignalMapErrors', lm=.12, rm=.11, show=show, draw_opt='colz') return h def get_signal_spread(self, min_percent=5, max_percent=99, prnt=True): """ Calculates the relative spread of mean signal response from the 2D signal response map. """ pickle_path = self.make_pickle_path('SignalMaps', 'Spread', self.Run.Number, self.DUT.Number) def f(): h = self.draw_sig_map_disto(show=False) q = array([min_percent, max_percent]) / 100. y = zeros(2, 'd') mean_error = mean([ v.n for v in get_2d_hist_vec(self.draw_error_signal_map( show=False)) ]) h.GetQuantiles(2, y, q) return ufloat(y[1], mean_error) / ufloat(y[0], mean_error) - 1 ratio = do_pickle(pickle_path, f) self.info('Relative Signal Spread is: {:2.2f} %'.format(ratio * 100), prnt=prnt) return ratio def draw_low_sig_map(self, high=10, low=None, fid=False, cut=None, hit_map=True): kwargs = { 'redo': True, 'cut': self.Cut.no_fid(fid, cut) + self.Cut.ph(high, low) } self.draw_hitmap(**kwargs) if hit_map else self.draw_signal_map( **kwargs) def correlate_sm(self, run, col=True, **kwargs): sm1, sm2 = [ ana.get_signal_map(fid=True, **kwargs) for ana in [ self, self.__class__(run, self.DUT.Number, self.TCString, verbose=False, prnt=False) if isint(run) else run ] ] c = correlate_all_maps(sm1, sm2) return c if col else c.T def find_best_sm_correlation(self, run): c = self.correlate_sm(run, res=1) self.info( f'best correlation: {max(c):.2f} at shift {unravel_index(argmax(c), c.shape)}' ) return c.max() def draw_sm_correlation_vs_shift(self, run, col=True, res=1, n=4, **kwargs): c = self.correlate_sm(run, col, res=res) iy, ix = unravel_index(argmax(c), c.shape) x = (arange(2 * n + 1) - n + ix) x = x % c.shape[1] if max(x) > c.shape[1] else x self.Draw.graph( x, c[iy][x], **prep_kw(kwargs, x_tit=f'Shift in {"X" if col else "Y"}', y_tit='Correlation Factor')) def draw_sm_correlation(self, run, m=10, show=True): x0, x1 = [ get_2d_hist_vec(f.split_signal_map(m, show=False)[0], err=False) for f in [ self, self.__class__(run, self.DUT.Number, self.TCString, prnt=False) ] ] g = self.Draw.histo_2d(x0, x1, self.Bins.get_pad_ph(2) * 2, 'Signal Map Correlation', show=show, x_tit='Pulse Height {} [mV]'.format( self.Run.Number), y_tit='Pulse Height {} [mV]'.format(run), x_range=ax_range(x0, 0, .1, .1), y_range=ax_range(x1, 0, .1, .1)) Draw.info('Correlation Factor: {:.2f}'.format( g.GetCorrelationFactor())) def draw_corr_coeff(self, run, show=True): x = [5, 10, 25, 50, 100, 200] ana = self.__class__(run, self.DUT.Number, self.TCString, prnt=False) y = [ correlate(*[ get_2d_hist_vec(f.split_signal_map(m, show=False)[0], err=False, zero_supp=0) for f in [self, ana] ]) for m in x ] self.Draw.graph(x, y, 'Signal Map Correlation', x_tit='Number of Bins', y_tit='Correlation Coefficient', show=show) # endregion SIGNAL MAP # ---------------------------------------- def draw_uniformity(self, h=None, use_fwc=True, redo=False, **kwargs): noise = self.Pedestal.get_fwhm( raw=True, redo=redo) if h is None and hasattr(self, 'Pedestal') else 0 h = choose(h, self.draw_raw_signal_distribution, redo=redo, **prep_kw(kwargs, normalise=True)) format_histo( h, **prep_kw(kwargs, x_range=ax_range(h=h, thresh=h.GetMaximum() * .02, fl=.2, fh=.6))) format_statbox(h, w=.35, all_stat=True) (low, high), m = get_fwhm( h, ret_edges=True), get_fw_center(h) if use_fwc else ufloat( h.GetMean(), h.GetMeanError()) fwhm, half_max = high - low, h.GetMaximum() / 2 li = Draw.vertical_line(m.n, style=7, w=2) a = Draw.arrow(low.n, high.n, half_max, half_max, col=2, width=3, opt='<>', size=.02) Draw.legend([a, li], ['FWHM', 'FWC' if use_fwc else 'Mean'], 'l', y2=.72, w=.2) fwhm = usqrt(fwhm**2 - noise**2) if fwhm > noise else usqrt( (noise + ufloat(1, 1))**2 - noise**2) # correct fwhm for noise fwhm = add_err(fwhm, 2 * h.GetBinWidth(1)) # add error for false estimate value = fwhm / m Draw.add_stats_entry(h, f'FWHM/{"FWC" if use_fwc else "Mean"}', value, line=3) self.info(f'Uniformity: {value:.2f}') self.Draw.save_plots('Uniformity', **kwargs) return m, fwhm, value def model_trap_number(self, f=1000, t=1, max_traps=10000, steps=20, show=True): filled_traps = zeros(steps, dtype=int) decay = vectorize(self.decay) n_traps = [] for i in range(steps): filled_traps[i] = f filled_traps = decay(filled_traps, t) n_traps.append(min(sum(filled_traps), max_traps)) g = Draw.make_tgrapherrors(x=arange(steps), y=n_traps) format_histo(g, title='Number of Filled Traps', x_tit='Time [s]', y_tit='Number of Traps', y_off=1.7) self.Draw(g, draw_opt='ap', lm=.13, show=show) return n_traps[-1] def draw_n_traps(self, t=1, max_traps=1e5, nbins=20): x, y = log_bins(nbins, 100, 1e6)[1], [] self.PBar.start(x.size) for f in x: y.append(self.model_trap_number(f, t, int(max_traps), show=False)) self.PBar.update() g = Draw.make_tgrapherrors(x=x / 1000, y=y) format_histo(g, title='Number of Filled Traps vs Flux', x_tit='Flux [kHz/cm^{2}]', y_tit='Number of Filled Traps', y_off=1.7) self.Draw(g, draw_opt='ap', lm=.13, logx=True) # ---------------------------------------- # region HELPERS def reload_tree_(self): self.Tree = self.Run.load_rootfile(prnt=False) for field in self.__dict__.values(): if hasattr(field, 'Tree'): field.Tree = self.Tree def fit_langau(self, h=None, nconv=30, show=True, chi_thresh=8, fit_range=None): h = self.draw_signal_distribution(show=False) if h is None and hasattr( self, 'draw_signal_distribution') else h self.Draw.histo(h, show=show) fit = Langau(h, nconv, fit_range) fit.get_parameters() fit(show=show) update_canvas() if fit.get_chi2() > chi_thresh and nconv < 80: self.info( 'Chi2 too large ({c:2.2f}) -> increasing number of convolutions by 5' .format(c=fit.get_chi2())) fit = self.fit_langau(h, nconv + Draw.get_count('langau') * 5, chi_thresh=chi_thresh, show=show) print('MPV: {:1.1f}'.format(fit.get_mpv())) format_statbox(h, fit=True, all_stat=True) Draw.reset_count('langau') self.Draw.add(fit) return fit @staticmethod def decay(n, t): return count_nonzero(rand(n) <= exp(-1. / t))
def draw_pixel_grid(): n, x, n, y = Bins.get_pixel() Draw.grid(x, y, color=921)
def get_time_bins(self, off=0, bin_size=50): e = self.get_data(off)[2] return Bins.make( array([self.Run.get_time_at_event(i) for i in e[::bin_size]]))