def fastmc(input_file: str, output_file: str, table_folder: str): """ This function simulates reconstructed coincidences starting from true information and using previously built error matrices. """ err_r_phot_file = table_folder + '/errmat_test_r_phot_like.npz' err_r_compt_file = table_folder + '/errmat_test_r_compt_like.npz' err_phi_phot_file = table_folder + '/errmat_test_phi_phot_like.npz' err_phi_compt_file = table_folder + '/errmat_test_phi_compt_like.npz' err_z_phot_file = table_folder + '/errmat_test_z_phot_like.npz' err_z_compt_file = table_folder + '/errmat_test_z_compt_like.npz' err_t_phot_file = table_folder + '/errmat_test_t_phot_like.npz' err_t_compt_file = table_folder + '/errmat_test_t_compt_like.npz' errmat_r_phot = errmat.errmat(err_r_phot_file) errmat_r_compt = errmat.errmat(err_r_compt_file) errmat_phi_phot = errmat3d.errmat3d(err_phi_phot_file) errmat_phi_compt = errmat3d.errmat3d(err_phi_compt_file) errmat_z_phot = errmat3d.errmat3d(err_z_phot_file) errmat_z_compt = errmat3d.errmat3d(err_z_compt_file) errmat_t_phot = errmat.errmat(err_t_phot_file) errmat_t_compt = errmat.errmat(err_t_compt_file) energy_threshold = 0.98 try: particles = mcio.load_mcparticles(input_file) except: print(f'File {input_file} not found!') exit() hits = mcio.load_mchits(input_file) events = particles.event_id.unique() reco = pd.DataFrame(columns=[ 'event_id', 'true_energy', 'true_r1', 'true_phi1', 'true_z1', 'true_t1', 'true_r2', 'true_phi2', 'true_z2', 'true_t2', 'phot_like1', 'phot_like2', 'reco_r1', 'reco_phi1', 'reco_z1', 'reco_t1', 'reco_r2', 'reco_phi2', 'reco_z2', 'reco_t2' ]) for evt in events: evt_df = fmc.simulate_reco_event(evt, hits, particles, errmat_r_phot, errmat_phi_phot, errmat_z_phot, errmat_t_phot, errmat_r_compt, errmat_phi_compt, errmat_z_compt, errmat_t_compt, energy_threshold, photo_range=1., only_phot=False) reco = pd.concat([reco, evt_df]) store = pd.HDFStore(output_file, "w", complib=str("zlib"), complevel=4) store.put('reco', reco, format='table', data_columns=True) store.close()
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}')
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) tof_response = load_mcTOFsns_response(file_name) events = particles.event_id.unique() for evt in events: ### Select photoelectric events only evt_parts = particles[particles.event_id == evt] evt_hits = hits[hits.event_id == evt] 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
errmat_r_compt = errmat.errmat(err_r_compt_file) errmat_phi_phot = errmat3d.errmat3d(err_phi_phot_file) errmat_phi_compt = errmat3d.errmat3d(err_phi_compt_file) errmat_z_phot = errmat3d.errmat3d(err_z_phot_file) errmat_z_compt = errmat3d.errmat3d(err_z_compt_file) errmat_t_phot = errmat.errmat(err_t_phot_file) errmat_t_compt = errmat.errmat(err_t_compt_file) energy_threshold = 0.98 for file_number in range(start, start + numb_of_files): sim_file = folder_in + '/' + filename + f'.{file_number}.h5' out_file = folder_out + '/' + filename + f'_reco_thr0pes.{file_number}.h5' try: particles = mcio.load_mcparticles(sim_file) except: print(f'File {sim_file} not found!') continue hits = mcio.load_mchits(sim_file) events = particles.event_id.unique() reco = pd.DataFrame(columns=[ 'event_id', 'true_energy', 'true_r1', 'true_phi1', 'true_z1', 'true_t1', 'true_r2', 'true_phi2', 'true_z2', 'true_t2', 'phot_like1', 'phot_like2', 'reco_r1', 'reco_phi1', 'reco_z1', 'reco_t1', 'reco_r2', 'reco_phi2', 'reco_z2', 'reco_t2' ]) for evt in events: evt_df = fmc.simulate_reco_event(evt, hits,