def process_mc_petit(input_file, output_file): """ This function selects MC events in coincidences and with maximum charge on one of the four central SiPMs in the Hamamatsu plane. It also computes the charge in pe and the charge ratio of the SiPMs in the external ring over the total charge. """ df = mcio.load_mcsns_response(input_file) evt_groupby = ['event_id'] df['tofpet_id'] = df['sensor_id'].apply(prf.tofpetid) df_coinc = prf.compute_coincidences(df, evt_groupby=evt_groupby) df_center = prf.select_evts_with_max_charge_at_center( df_coinc, evt_groupby=evt_groupby, variable='charge') ratio_ch_corona = prf.compute_charge_ratio_in_corona( df_center, evt_groupby=evt_groupby, variable='charge') df_center['ratio_cor'] = ratio_ch_corona[df_center.index].values df = df_center.reset_index() store = pd.HDFStore(output_file, "w", complib=str("zlib"), complevel=4) store.put('mc', df, format='table', data_columns=True) store.close()
def test_contained_evts_and_compute_ratio_in_corona(ANTEADATADIR, filename, det_plane, data, variable): """ Checks whether the event is fully contained in the detection plane and checks that the ratio of charge in the external corona is correct. """ PATH_IN = os.path.join(ANTEADATADIR, filename) if data: df = pd.read_hdf(PATH_IN, '/data_0') df['intg_w_ToT'] = df['t2'] - df['t1'] evt_groupby = ['evt_number', 'cluster'] else: df = mcio.load_mcsns_response(PATH_IN) df['tofpet_id'] = df['sensor_id'].apply(prf.tofpetid) evt_groupby = ['event_id'] _, _, _, corona = prf.sensor_params(det_plane=det_plane) df_cov = prf.select_contained_evts(df, evt_groupby=evt_groupby, det_plane=det_plane) assert len(np.intersect1d(df_cov.sensor_id.unique(), corona)) == 0 ratio_cor = prf.compute_charge_ratio_in_corona(df_cov, evt_groupby=evt_groupby, variable=variable, det_plane=det_plane) assert np.count_nonzero(ratio_cor.values) == 0 ratios = prf.compute_charge_ratio_in_corona(df, evt_groupby=evt_groupby, variable=variable, det_plane=det_plane).values assert np.logical_and(ratios >= 0, ratios <= 1).all()
def test_select_evts_with_max_charge_at_center(ANTEADATADIR, filename, data, det_plane, coinc_plane_4tiles, variable, tot_mode): """ Checks that the max charge (in terms of the desired variable) is at center of the chosen plane. """ PATH_IN = os.path.join(ANTEADATADIR, filename) if data: df = pd.read_hdf(PATH_IN, '/data_0') df['intg_w_ToT'] = df['t2'] - df['t1'] evt_groupby = ['evt_number', 'cluster'] else: df = mcio.load_mcsns_response(PATH_IN) df['tofpet_id'] = df['sensor_id'].apply(prf.tofpetid) evt_groupby = ['event_id'] tofpet_id, central_sns, _, _ = prf.sensor_params(det_plane, coinc_plane_4tiles) df_center = prf.select_evts_with_max_charge_at_center( df, evt_groupby=evt_groupby, det_plane=det_plane, coinc_plane_4tiles=coinc_plane_4tiles, variable=variable, tot_mode=tot_mode) df_center = df_center[df_center.tofpet_id == tofpet_id] assert len(df_center) > 0 idx_max = df_center.groupby(evt_groupby)[variable].idxmax() for idx in idx_max: sns_max = df_center.loc[idx].sensor_id assert sns_max in central_sns
def test_compute_coincidences(ANTEADATADIR, filename, data): """ Checks that both planes of sensors have detected charge. """ PATH_IN = os.path.join(ANTEADATADIR, filename) if data: df = pd.read_hdf(PATH_IN, '/data_0') evt_groupby = ['evt_number', 'cluster'] else: df = mcio.load_mcsns_response(PATH_IN) df['tofpet_id'] = df['sensor_id'].apply(prf.tofpetid) evt_groupby = ['event_id'] df_coinc = prf.compute_coincidences(df, evt_groupby) sns = df_coinc.groupby(evt_groupby).sensor_id.unique() s_d = np.array([len(s[s < 100]) for s in sns]) s_c = np.array([len(s[s > 100]) for s in sns]) assert np.all(s_d) and np.all(s_c)
def characterize_coincidences(input_file: str, output_file: str, rmap: str): """ This function extracts the relevant information on position, time, sensor response, kind (point-like or not)) of the two gamma interactions of a coincidences. """ DataSiPM = db.DataSiPMsim_only('petalo', 0) # full body PET DataSiPM_idx = DataSiPM.set_index('SensorID') ### parameters for single photoelectron convolution in SiPM response tau_sipm = [100, 15000] time_window = 5000 time = np.arange(0, time_window) spe_resp, norm = shf.normalize_sipm_shaping(time, tau_sipm) thr_r = 4 # threshold use to create R map (pe) thr_phi = 4 # threshold on charge to reconstruct phi (pe) thr_z = 4 # threshold on charge to reconstruct z (pe) thr_e = 2 # threshold on charge (pe) n_pe = 1 # number of first photoelectrons to be considered for timestamp sigma_sipm = 40 #ps SiPM jitter sigma_elec = 30 #ps electronic jitter Rpos = load_map(rmap, group="Radius", node="f4pes150bins", x_name="PhiRms", y_name="Rpos", u_name="RposUncertainty") c0 = c1 = 0 true_r1, true_phi1, true_z1 = [], [], [] reco_r1, reco_phi1, reco_z1 = [], [], [] true_r2, true_phi2, true_z2 = [], [], [] reco_r2, reco_phi2, reco_z2 = [], [], [] sns_response1, sns_response2 = [], [] ### PETsys thresholds to extract the timestamp timestamp_thr = 0.25 first_sipm1 = [] first_sipm2 = [] first_time1 = [] first_time2 = [] true_time1, true_time2 = [], [] touched_sipms1, touched_sipms2 = [], [] photo1, photo2 = [], [] max_hit_distance1, max_hit_distance2 = [], [] event_ids = [] try: sns_response = load_mcsns_response(input_file) except ValueError: print(f'File {input_file} not found') exit() except OSError: print(f'File {input_file} not found') exit() except KeyError: print(f'No object named MC/sns_response in file {input_file}') exit() print(f'Analyzing file {input_file}') fluct_sns_response = snsf.apply_charge_fluctuation(sns_response, DataSiPM_idx) tof_bin_size = read_sensor_bin_width_from_conf(input_file, tof=True) particles = load_mcparticles(input_file) hits = load_mchits(input_file) tof_response = load_mcTOFsns_response(input_file) events = particles.event_id.unique() charge_range = ( 2000, 2250 ) # range to select photopeak - to be adjusted to the specific case print(f'Number of events in file = {len(events)}') for evt in events: evt_sns = fluct_sns_response[fluct_sns_response.event_id == evt] evt_sns = rf.find_SiPMs_over_threshold(evt_sns, threshold=thr_e) if len(evt_sns) == 0: continue ids_over_thr = evt_sns.sensor_id.astype('int64').values evt_parts = particles[particles.event_id == evt] evt_hits = hits[hits.event_id == evt] evt_tof = tof_response[tof_response.event_id == evt] evt_tof = evt_tof[evt_tof.sensor_id.isin(-ids_over_thr)] if len(evt_tof) == 0: continue ### If one wants to select only pure photoelectric events ### add the following lines #select, true_pos = mcf.select_photoelectric(evt_parts, evt_hits) #if not select: continue #if (len(true_pos) == 1) & (evt_hits.energy.sum() > 0.511): # continue pos1, pos2, q1, q2, true_pos1, true_pos2, true_t1, true_t2, sns1, sns2 = rf.reconstruct_coincidences( evt_sns, charge_range, DataSiPM_idx, evt_parts, evt_hits) if len(pos1) == 0 or len(pos2) == 0: c0 += 1 continue q1 = np.array(q1) q2 = np.array(q2) pos1 = np.array(pos1) pos2 = np.array(pos2) r1, phi1, z1 = rf.reconstruct_position(q1, pos1, Rpos, thr_r, thr_phi, thr_z) r2, phi2, z2 = rf.reconstruct_position(q2, pos2, Rpos, thr_r, thr_phi, thr_z) if (r1 > 1.e8) or (r2 > 1.e8): c1 += 1 continue ## Use absolute times in units of ps times = evt_tof.time_bin.values * tof_bin_size / units.ps ## add SiPM jitter, if different from zero if sigma_sipm > 0: times = np.round(np.random.normal(times, sigma_sipm)) evt_tof.insert(len(evt_tof.columns), 'time', times.astype(int)) # here we have bins of 1 ps ## produce a TOF dataframe with convolved time response tof_sns = evt_tof.sensor_id.unique() evt_tof_exp_dist = [] for s_id in tof_sns: tdc_conv = shf.sipm_shaping_convolution(evt_tof, spe_resp, s_id, time_window) tdc_conv_df = shf.build_convoluted_df(evt, s_id, tdc_conv) if sigma_elec > 0: tdc_conv_df = tdc_conv_df.assign( time=np.random.normal(tdc_conv_df.time.values, sigma_elec)) tdc_conv_df = tdc_conv_df[tdc_conv_df.charge > timestamp_thr / norm] tdc_conv_df = tdc_conv_df[tdc_conv_df.time == tdc_conv_df.time.min()] evt_tof_exp_dist.append(tdc_conv_df) evt_tof_exp_dist = pd.concat(evt_tof_exp_dist) try: min_id1, min_id2, min_t1, min_t2 = rf.find_coincidence_timestamps( evt_tof_exp_dist, sns1, sns2, n_pe) ave_pos1 = rf.calculate_average_SiPM_pos(min_id1, DataSiPM_idx) ave_pos2 = rf.calculate_average_SiPM_pos(min_id2, DataSiPM_idx) first_sipm1.append(ave_pos1) first_sipm2.append(ave_pos2) except WaveformEmptyTable: print(f'TOF dataframe has no minimum time for event {evt}') _, _, min_t1, min_t2 = [-1], [-1], -1, -1 first_sipm1.append(np.array([0, 0, 0])) first_sipm2.append(np.array([0, 0, 0])) first_time1.append(min_t1) first_time2.append(min_t2) ## extract information about the interaction being photoelectric phot, phot_pos = mcf.select_photoelectric(evt_parts, evt_hits) if not phot: phot1 = False phot2 = False else: scalar_prod = true_pos1.dot(phot_pos[0]) if scalar_prod > 0: phot1 = True phot2 = False else: phot1 = False phot2 = True if len(phot_pos) == 2: if scalar_prod > 0: phot2 = True else: phot1 = True ## extract information about the interaction being photoelectric-like distances1 = rf.find_hit_distances_from_true_pos(evt_hits, true_pos1) max_dist1 = distances1.max() distances2 = rf.find_hit_distances_from_true_pos(evt_hits, true_pos2) max_dist2 = distances2.max() event_ids.append(evt) reco_r1.append(r1) reco_phi1.append(phi1) reco_z1.append(z1) true_r1.append(np.sqrt(true_pos1[0]**2 + true_pos1[1]**2)) true_phi1.append(np.arctan2(true_pos1[1], true_pos1[0])) true_z1.append(true_pos1[2]) sns_response1.append(sum(q1)) touched_sipms1.append(len(q1)) true_time1.append(true_t1 / units.ps) photo1.append(phot1) max_hit_distance1.append(max_dist1) reco_r2.append(r2) reco_phi2.append(phi2) reco_z2.append(z2) true_r2.append(np.sqrt(true_pos2[0]**2 + true_pos2[1]**2)) true_phi2.append(np.arctan2(true_pos2[1], true_pos2[0])) true_z2.append(true_pos2[2]) sns_response2.append(sum(q2)) touched_sipms2.append(len(q2)) true_time2.append(true_t2 / units.ps) photo2.append(phot2) max_hit_distance2.append(max_dist2) a_true_r1 = np.array(true_r1) a_true_phi1 = np.array(true_phi1) a_true_z1 = np.array(true_z1) a_reco_r1 = np.array(reco_r1) a_reco_phi1 = np.array(reco_phi1) a_reco_z1 = np.array(reco_z1) a_sns_response1 = np.array(sns_response1) a_touched_sipms1 = np.array(touched_sipms1) a_first_sipm1 = np.array(first_sipm1) a_first_time1 = np.array(first_time1) a_true_time1 = np.array(true_time1) a_photo1 = np.array(photo1) a_max_hit_distance1 = np.array(max_hit_distance1) a_true_r2 = np.array(true_r2) a_true_phi2 = np.array(true_phi2) a_true_z2 = np.array(true_z2) a_reco_r2 = np.array(reco_r2) a_reco_phi2 = np.array(reco_phi2) a_reco_z2 = np.array(reco_z2) a_sns_response2 = np.array(sns_response2) a_touched_sipms2 = np.array(touched_sipms2) a_first_sipm2 = np.array(first_sipm2) a_first_time2 = np.array(first_time2) a_true_time2 = np.array(true_time2) a_photo2 = np.array(photo2) a_max_hit_distance2 = np.array(max_hit_distance2) a_event_ids = np.array(event_ids) np.savez(output_file, a_true_r1=a_true_r1, a_true_phi1=a_true_phi1, a_true_z1=a_true_z1, a_true_r2=a_true_r2, a_true_phi2=a_true_phi2, a_true_z2=a_true_z2, a_reco_r1=a_reco_r1, a_reco_phi1=a_reco_phi1, a_reco_z1=a_reco_z1, a_reco_r2=a_reco_r2, a_reco_phi2=a_reco_phi2, a_reco_z2=a_reco_z2, a_touched_sipms1=a_touched_sipms1, a_touched_sipms2=a_touched_sipms2, a_sns_response1=a_sns_response1, a_sns_response2=a_sns_response2, a_first_sipm1=a_first_sipm1, a_first_time1=a_first_time1, a_first_sipm2=a_first_sipm2, a_first_time2=a_first_time2, a_true_time1=a_true_time1, a_true_time2=a_true_time2, a_photo1=a_photo1, a_photo2=a_photo2, a_max_hit_distance1=a_max_hit_distance1, a_max_hit_distance2=a_max_hit_distance2, a_event_ids=a_event_ids) print(f'Not a coincidence: {c0}') print(f'Not passing threshold to reconstruct position = {c1}')
true_r1, true_phi1, true_z1 = [], [], [] reco_r1, reco_phi1, reco_z1 = [], [], [] true_r2, true_phi2, true_z2 = [], [], [] reco_r2, reco_phi2, reco_z2 = [], [], [] photo_response1, photo_response2 = [], [] first_sipm1, first_sipm2 = [], [] first_time1, first_time2 = [], [] touched_sipms1, touched_sipms2 = [], [] event_ids = [] for ifile in range(start, start + numb): file_name = file_full.format(ifile) try: sns_response = load_mcsns_response(file_name) except ValueError: print('File {} not found'.format(file_name)) continue except OSError: print('File {} not found'.format(file_name)) continue except KeyError: print('No object named MC/sns_response in file {0}'.format(file_name)) continue print('Analyzing file {0}'.format(file_name)) tof_bin_size = read_sensor_bin_width_from_conf(file_name) particles = load_mcparticles(file_name) hits = load_mchits(file_name)