diff --git a/ana/LED/SPS_histogram.py b/ana/LED/SPS_histogram.py new file mode 100644 index 00000000..5b548dcc --- /dev/null +++ b/ana/LED/SPS_histogram.py @@ -0,0 +1,155 @@ +<<<<<<< Updated upstream +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd +import numpy as np +import mplhep as hep +from pathlib import Path +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('dataset', type=Path, help='LED bias scan file path') +args = parser.parse_args() + +data_dir = args.dataset.parent +output_dir = data_dir / f"{args.dataset.stem}_SPS_graphs" +output_dir.mkdir(exist_ok = True) + +df = pd.read_csv(args.dataset, comment='#', skipinitialspace=True) + +channel_groups = df.groupby('ch') + +for channel, ch_df in channel_groups: + + # group by trim_inv + trim_inv_groups = ch_df.groupby('trim_inv') + + for trim_inv, trim_df in trim_inv_groups: + + phase_ck_groups = trim_df.groupby('phase_ck') + + fig, ax = plt.subplots(figsize=(8, 5)) + + data_hist = [] + labels = [] + + for phase_ck, phase_df in phase_ck_groups: + + data_hist.append(phase_df["adc"]) + SiPM_DAC = phase_df["SiPM_DAC"].iloc[10] + LED_DAC = phase_df["LED_DAC"].iloc[10] + labels.append(f"phase_ck = {phase_ck}") + + max_adc = int(np.max(data_hist)) + min_adc = int(np.min(data_hist)) + bins = np.arange(min_adc - 0.5, max_adc + 1.5, 1) + + counts, edges = np.histogram(data_hist, bins) + errors = np.sqrt(counts) + centers = (edges[:-1] + edges[1:]) / 2 + hep.histplot((counts, edges), ax=ax, histtype='step', density=False, label=labels) + ax.errorbar(centers, counts, yerr=errors, fmt='.', capsize=2, markersize=3) + ax.set_xlabel('ADC value') + ax.set_ylabel('Count Per Bin') + ax.set_title(f'Single Photon Spectrum\n HGCROC Channel = {channel}, TRIM_INV = {trim_inv}, SiPM_DAC = {SiPM_DAC}, LED_DAC = {LED_DAC}') + ax.grid(False) + ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=7) + plt.tight_layout() + #ax.xaxis.set_major_locator(plt.MultipleLocator(1)) + + output_name = (f'channel_{channel}_trim_inv_{trim_inv}_SiPM_DAC_{SiPM_DAC}_LED_DAC_{LED_DAC}.png') + plt.savefig(output_dir / output_name, dpi=300) + plt.close() +======= +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np + +def analyze_and_plot_sps(csv_file_path="SPS-scan.csv"): + print(f"Loading data from: {csv_file_path}") + + df = pd.read_csv(csv_file_path, comment='#') + df.columns = df.columns.str.strip() + + adc_col = [col for col in df.columns if col.lower() == 'adc'][0] + + # Reconstruct event IDs if not explicitly present + event_cols = [c for c in df.columns if c.lower() in ['event', 'evt', 'event_id']] + if event_cols: + evt_col = event_cols[0] + else: + evt_col = 'event_id' + df[evt_col] = (df['sample'] == 0).groupby([df['ch'], df['trim_inv'], df['phase_ck']]).cumsum() + + # --- STEP 1: DNL CORRECTION PER TRIM_INV SETTING --- + # Determine base TRIM_INV and calculate shift delta: 0, 1, or 2 + base_trim = df['trim_inv'].min() + df['delta_trim'] = df['trim_inv'] - base_trim + + # Subtract 1 ADC tic per trim_inv step from each raw BX sample + df['adc_dnl_corrected'] = df[adc_col] - df['delta_trim'] + + print(f"Step 1: Applied DNL baseline shift corrections (base TRIM_INV = {base_trim})...") + + # --- STEP 2: SUM ACROSS 3 BXs PER EVENT --- + summed_3bx = ( + df.groupby([evt_col, 'ch', 'trim_inv', 'phase_ck'], as_index=False)['adc_dnl_corrected'] + .sum() + .rename(columns={'adc_dnl_corrected': 'adc_3bx_dnl_corrected'}) + ) + + # --- STEP 3: AVERAGE ACROSS THE THREE TRIM_INV SETTINGS --- + print("Step 2: Combining and averaging across TRIM_INV settings...") + avg_trim = ( + summed_3bx.groupby([evt_col, 'ch', 'phase_ck'], as_index=False)['adc_3bx_dnl_corrected'] + .mean() + .rename(columns={'adc_3bx_dnl_corrected': 'averaged_adc_sum'}) + ) + + # --- PLOTTING --- + fig, axes = plt.subplots(1, 2, figsize=(15, 6)) + + # Plot 1: Pulse Timing Scan (Phase curve) + for ch, ch_data in avg_trim.groupby('ch'): + phase_stats = ch_data.groupby('phase_ck')['averaged_adc_sum'].agg(['mean', 'std', 'count']) + phase_stats['sem'] = phase_stats['std'] / np.sqrt(phase_stats['count']) + + axes[0].errorbar( + phase_stats.index, + phase_stats['mean'], + yerr=phase_stats['sem'], + fmt='-o', + capsize=4, + label=f'Channel {ch}' + ) + + axes[0].set_title("DNL-Corrected Phase Scan\n(3-BX Sum vs. PHASE_CK)", fontsize=12) + axes[0].set_xlabel("PHASE_CK", fontsize=11) + axes[0].set_ylabel("DNL-Corrected 3-BX Summed ADC", fontsize=11) + axes[0].grid(True, linestyle="--", alpha=0.6) + axes[0].legend() + + # Plot 2: Histogram of DNL-Smoothed Signal Amplitudes + for ch, ch_data in avg_trim.groupby('ch'): + axes[1].hist( + ch_data['averaged_adc_sum'], + bins=100, + alpha=0.6, + label=f'Channel {ch}', + edgecolor='black', + linewidth=0.5 + ) + + axes[1].set_title("SPS Histogram (DNL Corrected)", fontsize=12) + axes[1].set_xlabel("Amplitude (DNL-Corrected 3-BX Summed ADC)", fontsize=11) + axes[1].set_ylabel("Counts", fontsize=11) + axes[1].grid(True, linestyle="--", alpha=0.6) + axes[1].legend() + + plt.tight_layout() + plt.savefig("sps_readout_dnl_corrected.png", dpi=300) + plt.show() + +if __name__ == "__main__": + analyze_and_plot_sps("SPS-scan.csv") +>>>>>>> Stashed changes diff --git a/app/tool/tasks/SPS_readout.cxx b/app/tool/tasks/SPS_readout.cxx new file mode 100644 index 00000000..e5073726 --- /dev/null +++ b/app/tool/tasks/SPS_readout.cxx @@ -0,0 +1,305 @@ +#include "SPS_readout.h" + +#include +#include + +#include "../daq_run.h" +#include "../pftool.h" +#include "pflib/Bias.h" +#include "pflib/HcalTarget.h" +<<<<<<< Updated upstream +#include "pflib/TRIG.h" +#include "pflib/packing/Hex.h" + ======= +>>>>>>> Stashed changes +#include "pflib/utility/string_format.h" + + ENABLE_LOGGING(); + +void sps_readout(Target* tgt) { +<<<<<<< Updated upstream + int cmb_to_ch[16][4] = { +======= + int cmb_to_ch[16][4] = { +>>>>>>> Stashed changes + {0, 1, 2, 3}, + {4, 5, 6, 7}, + {9, 10, 11, 12}, + {13, 14, 15, 16}, + {18, 19, 20, 21}, + {22, 23, 24, 25}, + {27, 28, 29, 30}, + {31, 32, 33, 34}, + {36, 37, 38, 39}, + {40, 41, 42, 43}, + {45, 46, 47, 48}, + {49, 50, 51, 52}, + {54, 55, 56, 57}, + {58, 59, 60, 61}, + {63, 64, 65, 66}, + {67, 68, 69, 70} + }; + +<<<<<<< Updated upstream + tgt->setup_run(1, Target::DaqFormat::ECOND_SW_HEADERS, 1); + pflib::DAQ& daq = tgt->daq(); + + auto hcalbp = dynamic_cast(tgt); +======= + // 3 time samples per event + tgt->setup_run(3, Target::DaqFormat::ECOND_SW_HEADERS, 1); + + auto hcalbp = dynamic_cast(tgt); +>>>>>>> Stashed changes + if (!hcalbp) { + PFEXCEPTION_RAISE("BadTarget", + "led_bias_scan only available for Hcal targets"); + } + + int iboard = 1; + iboard = pftool::readline_int("Which board? ", iboard); + // static void bias(const std::string& cmd, pflib::HcalTarget* pft){ + auto& bias = hcalbp->bias(iboard); + auto& mapping{tgt->getRocErxMapping()}; + +<<<<<<< Updated upstream + int cmb_port = pftool::readline_int("Channel to scan on? ", 0); + int nevents = pftool::readline_int("How many events per time point? ", 1000); + int start_led = tgt->fc().fc_get_setup_led(); + int tgt_bx = + pftool::readline_int("Target BX? (~22 should be BX = 4) ", start_led); + int len_bx = pftool::readline_int("Number of BX to scan over? ", 2); + tgt->fc().setL1AperROR(len_bx); + // int start_led_new = pftool::readline_int("Calibration L1A offset for LED + // start point", start_led); + int n_links = 2 * tgt->nrocs(); + + int start_SiPM = bias.readSiPM(cmb_port).value_or(-1); + int start_LED = bias.readLED(cmb_port).value_or(-1); + int new_SiPM = pftool::readline_int("SiPM DAC value? ", start_SiPM); + int new_LED = pftool::readline_int("LED DAC value? ", start_LED); + + int start_phase_ck = pftool::readline_int("Starting PHASE_CK value? ", 2); + int end_phase_ck = pftool::readline_int("Ending PHASE_CK value? ", 2); + int trim_inv = pftool::readline_int("TRIM_INV value? ", 2); + int trim_range = pftool::readline_int( + "Range of TRIM_INV values to scan over for convolution? ", 1); + + bias.setSiPM(cmb_port, new_SiPM); + bias.setLED(cmb_port, new_LED); +======= + int cmb_port = pftool::readline_int("Channel to scan on? ", 0); + int nevents = pftool::readline_int("How many events per time point? ", 1000); + int tgt_bx = pftool::readline_int("Target BX? ", 22); + int start_led = tgt->fc().fc_get_setup_led(); + // int start_led_new = pftool::readline_int("Calibration L1A offset for LED + // start point", start_led); + int n_links = 2 * tgt->nrocs(); + + int start_SiPM = bias.readSiPM(cmb_port).value_or(-1); + int start_LED = bias.readLED(cmb_port).value_or(-1); + int new_SiPM = pftool::readline_int("SiPM DAC value? ", start_SiPM); + int new_LED = pftool::readline_int("LED DAC value? ", start_LED); + + int start_phase_ck = pftool::readline_int("Starting PHASE_CK value? ", 0); + int end_phase_ck = pftool::readline_int("Ending PHASE_CK value? ", 0); + int trim_inv = pftool::readline_int("TRIM_INV value? ", 2); + int trim_range = pftool::readline_int( + "Range of TRIM_INV values to scan over for convolution? ", 1); + + bias.setSiPM(cmb_port, new_SiPM); + bias.setLED(cmb_port, new_LED); +>>>>>>> Stashed changes + + pflib::ROC roc{tgt->roc(iboard)}; + + std::string fname; + auto test_param_builder = roc.testParameters(); + fname = pftool::readline_path("SPS-scan", ".csv"); + +<<<<<<< Updated upstream + int g = 0; + int phase_ck = 0; + + DecodeAndWriteToCSV writer { + fname, + [&](std::ofstream& f) { + nlohmann::ordered_json header; + f << std::boolalpha << "# " << header << '\n' + << "i_cmb_port,ch,trim_inv,phase_ck,SiPM_DAC,LED_DAC," +======= + int g = 0; + int phase_ck = 0; + + DecodeAndWriteToCSV writer{ + fname, + [&](std::ofstream& f) { + nlohmann::ordered_json header; + //header["Min CMB port"] = min_cmb_port; + //header["Max CMB port"] = max_cmb_port; + //header["LED DAC start"] = LEDstart; + //header["LED DAC end"] = LEDend; + //header["SiPM DAC start"] = SiPMstart; + //header["SiPM DAC end"] = SiPMend; + f << std::boolalpha << "# " << header << '\n' + << "i_cmb_port,ch,trim_inv,phase_ck," +>>>>>>> Stashed changes + << pflib::packing::Sample::to_csv_header << '\n'; + }, + [&](std::ofstream& f, + const pflib::packing::MultiSampleECONDEventPacket& ep) { + for (int j = 0; j < 4; j++) { + auto ch = cmb_to_ch[cmb_port][j]; + auto [i_erx, i_ch] = mapping.toErxChannel(iboard, ch); +<<<<<<< Updated upstream + f << cmb_port << ',' << i_ch << ',' << g << ',' << phase_ck << ',' + << new_SiPM << ',' << new_LED << ','; + ep.samples[ep.i_soi].channel(i_erx, i_ch).to_csv(f); + f << '\n'; +======= + for (std::size_t sample = 0; sample < ep.samples.size(); ++sample) { + f << cmb_port << ',' << i_ch << ',' << g << ',' << phase_ck << ',' ; + ep.samples[sample].channel(i_erx, i_ch).to_csv(f); + f << '\n'; + } +>>>>>>> Stashed changes + } + }, + n_links + }; + +<<<<<<< Updated upstream + // Makes sure charge injections are turned on for this individual channel + for (int j = 0; j < 4; j++) { + auto ch = cmb_to_ch[cmb_port][j]; + int link = (ch / 36); + auto channel_page = pflib::utility::string_format("CH_%d", ch); + auto refvol_page = + pflib::utility::string_format("REFERENCEVOLTAGE_%d", link); + auto calib_page = pflib::utility::string_format("CALIB_%d", link); + auto global_page = pflib::utility::string_format("GLOBALANALOG_%d", link); + test_param_builder.add(refvol_page, "CALIB", 0) + .add(refvol_page, "CALIB_2V5", 0) + .add(refvol_page, "INTCTEST", 1) + .add(refvol_page, "CHOICE_CINJ", 0) + .add(global_page, "CD", 2) + .add(global_page, "CF", 8) + .add(global_page, "RF", 10) + .add(channel_page, "HIGHRANGE", 0) + .add(channel_page, "LOWRANGE", 0) + .add(calib_page, "INPUTDAC", + 32) // No idea what this should be (MAXES out at 63) + .add(channel_page, "INPUTDAC", 32) // No idea what this should be + .add(global_page, "GAIN_CONV", 1) // 0 or 1 + .add(calib_page, "GAIN_CONV", 7); // 0 or 1 + for (int k = 0; k < 4; k++) { + auto cm_page = pflib::utility::string_format("CM_%d", link); + test_param_builder.add(cm_page, "GAIN_CONV", 7); // 0 to 7 + } + test_param_builder.add(channel_page, "GAIN_CONV", 7); // 0 to 7 +======= + // Makes sure charge injections are turned on for this individual channel + for (int j = 0; j < 4; j++) { + auto ch = cmb_to_ch[cmb_port][j]; + int link = (ch / 36); + auto channel_page = pflib::utility::string_format("CH_%d", ch); + auto refvol_page = + pflib::utility::string_format("REFERENCEVOLTAGE_%d", link); + auto calib_page = pflib::utility::string_format("CALIB_%d", link); + auto global_page = pflib::utility::string_format("GLOBALANALOG_%d", link); + test_param_builder.add(refvol_page, "CALIB", 0) + .add(refvol_page, "CALIB_2V5", 0) + .add(refvol_page, "INTCTEST", 1) + .add(refvol_page, "CHOICE_CINJ", 0) + .add(global_page, "CD", 2) + .add(global_page, "CF", 8) + .add(global_page, "RF", 10) + .add(channel_page, "HIGHRANGE", 0) + .add(channel_page, "LOWRANGE", 0) + .add(calib_page, "INPUTDAC", 0) // No idea what this should be + .add(channel_page, "INPUTDAC", 0) // No idea what this should be + .add(global_page, "GAIN_CONV", 1) // 0 or 1 + .add(calib_page, "GAIN_CONV", 4); // 0 or 1 + for (int k = 0; k < 4; k++) { + auto cm_page = pflib::utility::string_format("CM_%d", link); + test_param_builder.add(cm_page, "GAIN_CONV", 4); // 0 to 7 + } + test_param_builder.add(channel_page, "GAIN_CONV", 4); // 0 to 7 +>>>>>>> Stashed changes + } + + auto test_param_handle = test_param_builder.apply(); + +<<<<<<< Updated upstream + for (g = (trim_inv - trim_range); g < (trim_inv + trim_range + 1); g++) { + pflib_log(info) << "TRIM_INV set to = " << g; + + std::map> page_stat; + + for (int j = 0; j < 4; j++) { + auto ch = cmb_to_ch[cmb_port][j]; + auto ch_str = pflib::utility::string_format("CH_%d", ch); + page_stat[ch_str]["TRIM_INV"] = g; + } + auto trim_inv_apply = tgt->tempApplyAllROCs(page_stat); + + for (phase_ck = start_phase_ck; phase_ck <= end_phase_ck; phase_ck++) { + pflib_log(info) << "PHASE_CK = " << phase_ck; + + auto phase_test_handle = + roc.testParameters().add("TOP", "PHASE_CK", phase_ck).apply(); + + bool enable_l1a_follow; + // int central_charge_to_l1a = tgt->fc().fc_get_setup_led(); + + // tgt->fc().fc_setup_led(start_led_new); + tgt->fc().fc_setup_led(tgt_bx); + pflib_log(info) << " Target BX = " << tgt_bx << "\n"; + + daq_run(tgt, "LED", writer, nevents, pftool::state.daq_rate); + usleep(10); + // auto data = buffer.get_buffer(); + // auto mapping = tgt->getRocErxMapping(); + // auto [i_erx, i_ch] = mapping.toErxChannel(i_roc, 17); + // for (std::size_t i{0}; i < data.size(); i++) { + // for (int j = 0; j < nr_bx; j++) { + // adcs[j].push_back(data[i].samples.at(j).channel(i_erx, i_ch).adc()); + //} + //} + } + } + +} // sps_readout +======= + int base_trim_inv = pftool::readline_int("Base TRIM_INV value? ", 2); + + for (g = base_trim_inv; g <= base_trim_inv + 2; g++) { + pflib_log(info) << "TRIM_INV set to = " << g << " (offset +" + << (g - base_trim_inv) << ")"; + + std::map> page_stat; + + for (int j = 0; j < 4; j++) { + auto ch = cmb_to_ch[cmb_port][j]; + auto ch_str = pflib::utility::string_format("CH_%d", ch); + page_stat[ch_str]["TRIM_INV"] = g; + } + auto trim_inv_apply = tgt->tempApplyAllROCs(page_stat); + + for (phase_ck = start_phase_ck; phase_ck <= end_phase_ck; phase_ck++) { + pflib_log(info) << "PHASE_CK = " << phase_ck; + + auto phase_test_handle = + roc.testParameters().add("TOP", "PHASE_CK", phase_ck).apply(); + + // tgt->fc().fc_setup_led(start_led_new); + tgt->fc().fc_setup_led(tgt_bx); + pflib_log(info) << " Target BX = " << tgt_bx << "\n"; + + daq_run(tgt, "LED", writer, nevents, pftool::state.daq_rate); + usleep(10); + } + } + +} // sps_readout +>>>>>>> Stashed changes diff --git a/app/tool/tasks/SPS_readout.h b/app/tool/tasks/SPS_readout.h new file mode 100644 index 00000000..995655cc --- /dev/null +++ b/app/tool/tasks/SPS_readout.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../pftool.h" + +/** + * TASKS.SPS_SCAN + * + * Used to scan different SiPM or LED DAC values/biases. + * Both can either be varied over a range, or kept constant if the same value is + * entered for the start and stop value. One can choose how many ports have CMBs + * connected An LED flash on one CMB flashes into four HGCROC channels + * simultaneously, thus the pulses of all four respective channels are recorded + * in the csv file. + * + */ +void sps_readout(Target* tgt); diff --git a/app/tool/tasks/tasks.cxx b/app/tool/tasks/tasks.cxx index 3588f028..148a77d2 100644 --- a/app/tool/tasks/tasks.cxx +++ b/app/tool/tasks/tasks.cxx @@ -5,6 +5,7 @@ */ #include "../pftool.h" +#include "SPS_readout.h" #include "channel_wise_calib_scan.h" #include "charge_timescan.h" #include "examine_phase.h" @@ -79,7 +80,10 @@ auto menu_tasks = "calibrate TRIM_TOA parameters for each channel", trim_toa_scan) ->line("TOA_SCAN", "calibrate TRIM_TOA parameters for each channel", toa_scan) - ->line("LED_BIAS_SCAN", "Sweeps SiPM and LED DACs", led_bias_scan); + ->line("LED_BIAS_SCAN", "Sweeps SiPM and LED DACs", led_bias_scan) + ->line("SPS_READOUT", + "Take data of a photospectrum on one bunch crossing", + sps_readout); auto menu_expert_tasks = menu_tasks->submenu("EXPERT", "low-level but complicated tasks")