class LabIlluminationGenerator:
    def __init__(self, camera, extractor, pedestal, nsb_rate):
        self.camera = camera
        self.extractor = extractor
        self.pedestal = pedestal
        self.nsb_rate = nsb_rate

        pulse_area = self.camera.photoelectron_pulse.area
        spectrum_average = self.camera.photoelectron_spectrum.average
        pe_conversion = pulse_area * spectrum_average
        self.pe_conversion = pe_conversion
        self.n_pixels = self.camera.mapping.n_pixels
        self.n_superpixels = self.camera.mapping.n_superpixels

        self.signal_time = 40  # ns
        self.signal_index = self.camera.get_waveform_sample_from_time(
            self.signal_time)
        self.source = PhotoelectronSource(camera=self.camera)
        self.acquisition = EventAcquisition(camera=self.camera)

        self.illumination = 50

    def set_illumination(self, illumination):
        self.illumination = illumination

    @property
    def event_table_layout(self):
        class EventTable(tables.IsDescription):
            n_triggers = tables.Int64Col(shape=self.n_superpixels)
            true_charge = tables.Int64Col(shape=self.n_pixels)
            measured_charge = tables.Float64Col(shape=self.n_pixels)

            # Event metadata
            illumination = tables.Float64Col()

        return EventTable

    def generate_event(self):
        nsb_pe = self.source.get_nsb(self.nsb_rate)
        signal_pe = self.source.get_uniform_illumination(self.signal_time,
                                                         self.illumination,
                                                         laser_pulse_width=3)
        true_charge = signal_pe.get_photoelectrons_per_pixel(self.n_pixels)
        readout = self.acquisition.get_continuous_readout(nsb_pe + signal_pe)

        trigger = self.acquisition.trigger
        digital_trigger = trigger.get_superpixel_digital_trigger_line(readout)
        n_triggers = trigger.get_n_superpixel_triggers(digital_trigger)

        waveform = self.acquisition.get_sampled_waveform(readout)
        measured_charge = self.extractor.extract(waveform, self.signal_index)
        calibrated_charge = (measured_charge -
                             self.pedestal) / self.pe_conversion

        return dict(
            n_triggers=n_triggers,
            true_charge=true_charge,
            measured_charge=calibrated_charge,
            illumination=self.illumination,
        )
Esempio n. 2
0
def _perform_sp_bias_scan(camera, nsb):
    """
    While observing NSB only, scan through trigger threshold and measure
    superpixel trigger rate

    Parameters
    ----------
    camera : Camera
        Description of the camera
    nsb : float
        NSB rate (Unit: MHz)

    Returns
    -------
    thresholds : ndarray
        Thresholds sampled
    mean_rate : ndarray
        Average trigger rate at each threshold
    std_rate : ndarray
        Standard deviation of trigger rate at each threshold

    """
    original_n_pixels = camera.mapping.n_pixels
    camera.mapping.reinitialise(4)  # Only use 1 superpixel for this process
    source = PhotoelectronSource(camera=camera)
    acquisition = EventAcquisition(camera=camera)
    trigger = acquisition.trigger
    n_repeats = 10000  # Repeats for statistics
    n_thresholds = 50
    thresholds = None  # Delayed initialisation based on readout limits
    n_triggers = np.zeros((n_repeats, n_thresholds))
    for iev in trange(n_repeats, desc="Measuring bias curve"):
        photoelectrons = source.get_nsb(nsb)
        readout = acquisition.get_continuous_readout(photoelectrons)

        # Define thresholds based on waveform readout
        if thresholds is None:
            min_ = readout.min()
            min_ = min_ if min_ >= 0 else 0
            max_ = readout.max() * 5
            max_ = max_ if max_ > 0 else 1
            thresholds = np.linspace(min_, max_, n_thresholds)
            thresholds /= camera.photoelectron_pulse.height  # Convert to p.e.

        # Scan through thresholds to count triggers
        for i, thresh in enumerate(thresholds):
            camera.update_trigger_threshold(thresh)
            digital_trigger = trigger.get_superpixel_digital_trigger_line(readout)
            n_triggers[iev, i] = trigger.get_n_superpixel_triggers(digital_trigger)

    n_triggers[:, n_triggers.sum(0) <= 1] = np.nan  # Remove entries with low statistics
    n_triggers_avg = n_triggers.mean(0)
    n_triggers_err = np.sqrt(n_triggers_avg / n_repeats)
    rate_avg = n_triggers_avg / (camera.continuous_readout_duration * 1e-9)
    rate_err = n_triggers_err / (camera.continuous_readout_duration * 1e-9)

    camera.mapping.reinitialise(original_n_pixels)
    return thresholds, rate_avg, rate_err
Esempio n. 3
0
def measure_opct(camera, plot_path=None):
    print("Measuring OPCT")
    original_n_pixels = camera.mapping.n_pixels
    camera.mapping.reinitialise(1)  # Only use 1 pixel for this process
    camera.coupling.update_nsb_rate(0)  # No NSB
    source = PhotoelectronSource(camera=camera)
    acquisition = EventAcquisition(camera=camera)

    illuminations = [1.0, 1.5, 2.0]
    n_illuminations = len(illuminations)
    n_events = 20000

    # Simulate waveforms and extract charge
    charge_arrays = []
    time = 20
    extractor = ChargeExtractor.from_camera(camera)  # Default extractor
    for illumination in illuminations:
        charge_array = np.zeros(n_events)
        for iev in range(n_events):
            pe = source.get_uniform_illumination(time, illumination)
            readout = acquisition.get_continuous_readout(pe)
            waveform = acquisition.get_sampled_waveform(readout)
            charge_array[iev] = extractor.extract(waveform, time)[0]
        charge_arrays.append(charge_array / camera.photoelectron_pulse.area)

    charge_containers = [
        ChargeContainer(c, n_bins=100, range_=(-1, 8))
        for c in charge_arrays
    ]

    # Fit spectra
    pdf = SiPMGentile(n_illuminations=n_illuminations)
    cost = BinnedNLL(pdf, charge_containers)
    values, errors = minimize_with_iminuit(cost)

    if plot_path is not None:
        values_array = np.array(list(values.values()))
        fig = plt.figure(figsize=(20, 10))
        x = np.linspace(-1, 8, 100000)
        for i in range(n_illuminations):
            ax = fig.add_subplot(n_illuminations, 1, i+1)
            charge = charge_containers[i]
            ax.hist(
                charge.between,
                weights=charge.hist,
                bins=charge.edges,
                histtype='step',
                density=True
            )
            fy = pdf(x, values_array, i)
            ax.plot(x, fy)
        print(f"Saving plot: {plot_path}")
        fig.savefig(plot_path)

    camera.mapping.reinitialise(original_n_pixels)
    return values['opct'], errors['opct']
Esempio n. 4
0
def get_camera_and_readout(pulse_width, mv_per_pe, spectrum, nsb_rate):
    camera = Camera(continuous_readout_duration=int(2e5),
                    n_waveform_samples=int(2e5),
                    mapping=SSTCameraMapping(n_pixels=1),
                    photoelectron_pulse=GaussianPulse(sigma=pulse_width,
                                                      mv_per_pe=mv_per_pe),
                    photoelectron_spectrum=spectrum)
    source = PhotoelectronSource(camera=camera, seed=1)
    acquisition = EventAcquisition(camera=camera, seed=1)
    pe = source.get_nsb(nsb_rate)
    return camera, acquisition.get_continuous_readout(pe)
Esempio n. 5
0
def main():
    normalise_charge = True
    spectra = {
        "No OCT":
        SiPMPrompt(opct=0, normalise_charge=normalise_charge),
        "Prompt OCT (20%)":
        SiPMPrompt(opct=0.2, normalise_charge=normalise_charge),
        "Delayed OCT (20%, τ=24ns)":
        SiPMDelayed(opct=0.2,
                    time_constant=24,
                    normalise_charge=normalise_charge),
        "Delayed OCT (55%, τ=24ns)":
        SiPMDelayed(opct=0.55,
                    time_constant=24,
                    normalise_charge=normalise_charge),
    }

    path = "/Users/Jason/Software/TargetCalib/source/dev/reference_pulse_checs_V1-1-0.cfg"
    ref_x, ref_y = np.loadtxt(path, unpack=True)
    pulse = GenericPulse(ref_x * 1e9, ref_y, mv_per_pe=4)

    p_waveform = WaveformPlot(talk=True)

    for name, spectrum in spectra.items():
        camera = Camera(
            continuous_readout_duration=128,
            n_waveform_samples=128,
            mapping=SSTCameraMapping(n_pixels=1),
            photoelectron_spectrum=spectrum,
            photoelectron_pulse=pulse,
        )
        source = PhotoelectronSource(camera=camera)
        acquisition = EventAcquisition(camera=camera)
        n_events = 100
        waveform = np.zeros((n_events, 128))
        for iev in trange(n_events):
            pe = source.get_uniform_illumination(30, 50)
            readout = acquisition.get_continuous_readout(pe)
            waveform[iev] = acquisition.get_sampled_waveform(readout)[0]
        waveform_avg = np.mean(waveform, 0)

        p_waveform.ax.plot(waveform_avg, label=name)

    p_waveform.add_legend('best')
    p_waveform.ax.set_xlabel("Time (ns)")
    p_waveform.ax.set_ylabel("Amplitude (mV)")
    p_waveform.ax.set_title("Average Pulse (50 p.e.)")
    p_waveform.save("pulse.pdf")
Esempio n. 6
0
def obtain_pedestal(camera, extractor, nsb_rate=40):
    print("Obtaining pedestal")

    # Update the AC coupling for this nsb rate
    camera.coupling.update_nsb_rate(nsb_rate)

    source = PhotoelectronSource(camera=camera)
    acquisition = EventAcquisition(camera=camera)

    n_empty = 100
    pedestal_array = np.zeros((n_empty, camera.mapping.n_pixels))
    for i in trange(n_empty, desc="Measuring pedestal"):
        nsb_pe = source.get_nsb(nsb_rate)
        readout = acquisition.get_continuous_readout(nsb_pe)
        waveform = acquisition.get_sampled_waveform(readout)
        charge = extractor.extract(waveform, camera.n_waveform_samples//2)
        pedestal_array[i] = charge
    return np.median(pedestal_array)
Esempio n. 7
0
def measure_50pe_pulse(camera):
    print("Measuring 50pe pulse")
    original_n_pixels = camera.mapping.n_pixels
    camera.mapping.reinitialise(1)  # Only use 1 pixel for this process
    camera.coupling.update_nsb_rate(0)  # No NSB
    source = PhotoelectronSource(camera=camera)
    acquisition = EventAcquisition(camera=camera)

    time = 20
    illumination = 50
    n_events = 100
    readout = np.zeros((n_events, camera.continuous_readout_time_axis.size))
    for iev in range(n_events):
        pe = source.get_uniform_illumination(time, illumination)
        readout[iev] = acquisition.get_continuous_readout(pe)[0]
    avg = readout.mean(0)
    camera.mapping.reinitialise(original_n_pixels)
    return camera.continuous_readout_time_axis, avg
    def __init__(self, camera, extractor, pedestal, nsb_rate):
        self.camera = camera
        self.extractor = extractor
        self.pedestal = pedestal
        self.nsb_rate = nsb_rate

        pulse_area = self.camera.photoelectron_pulse.area
        spectrum_average = self.camera.photoelectron_spectrum.average
        pe_conversion = pulse_area * spectrum_average
        self.pe_conversion = pe_conversion
        self.n_pixels = self.camera.mapping.n_pixels
        self.n_superpixels = self.camera.mapping.n_superpixels

        self.signal_time = 40  # ns
        self.signal_index = self.camera.get_waveform_sample_from_time(
            self.signal_time)
        self.source = PhotoelectronSource(camera=self.camera)
        self.acquisition = EventAcquisition(camera=self.camera)

        self.illumination = 50
    def __init__(self, path, camera, extractor, pedestal, nsb_rate):
        if not exists(path):
            raise ValueError(f"No path found: {self.path}")

        self.path = path
        self.camera = camera
        self.extractor = extractor
        self.pedestal = pedestal
        self.nsb_rate = nsb_rate

        self.n_pixels = self.camera.mapping.n_pixels

        self.reader = PhotoelectronReader(self.path)

        if self.n_pixels != 2048:
            print(
                "Warning: Full camera pixels not simulated for cherenkov events"
            )

        self.source = PhotoelectronSource(camera=self.camera)
        self.acquisition = EventAcquisition(camera=self.camera)
Esempio n. 10
0
def main():
    spectrum = SiPMGentileSPE(opct=0.2, normalise_x=False)
    camera = Camera(photoelectron_spectrum=spectrum,
                    mapping=SSTCameraMapping(n_pixels=1))
    n_events = 200000

    source = PhotoelectronSource(camera=camera)
    pe1 = []
    pe50 = []
    for iev in trange(n_events):
        pe1.append(source.get_uniform_illumination(20, 1).charge.sum())
        pe50.append(source.get_uniform_illumination(20, 20).charge.sum())
    pe1 = np.array(pe1)
    pe50 = np.array(pe50)

    # embed()

    plot = SpectrumPlot(sidebyside=True)
    plot.ax.hist(
        pe1,
        bins=70,
        histtype='step',
        label=f"λ = 1, Average Charge Per Event = {pe1.mean():.2f} f.c.")
    plot.ax.hist(
        pe50,
        bins=70,
        histtype='step',
        label=f"λ = 20, Average Charge Per Event = {pe50.mean():.2f} f.c.")
    # plot.ax.set_yscale('log')
    plot.ax.set_xlabel("Charge Per Event (f.c.)")
    plot.ax.set_ylabel("Number of Events")
    plot.add_legend('best')
    plot.save(get_plot("d201029_sipm_calib_doc/charge_mc.pdf"))

    spectrum = SiPMGentileSPE(opct=0.2, normalise_x=True)
    camera = Camera(photoelectron_spectrum=spectrum,
                    mapping=SSTCameraMapping(n_pixels=1))
    n_events = 200000

    source = PhotoelectronSource(camera=camera)
    pe1 = []
    pe50 = []
    for iev in trange(n_events):
        pe1.append(source.get_uniform_illumination(20, 1).charge.sum())
        pe50.append(source.get_uniform_illumination(20, 20).charge.sum())
    pe1 = np.array(pe1)
    pe50 = np.array(pe50)

    # embed()

    plot = SpectrumPlot(sidebyside=True)
    plot.ax.hist(
        pe1,
        bins=70,
        histtype='step',
        label=f"λ = 1, Average Charge Per Event = {pe1.mean():.2f} p.e.")
    plot.ax.hist(
        pe50,
        bins=70,
        histtype='step',
        label=f"λ = 20, Average Charge Per Event = {pe50.mean():.2f} p.e.")
    # plot.ax.set_yscale('log')
    plot.ax.set_xlabel("Charge Per Event (p.e.)")
    plot.ax.set_ylabel("Number of Events")
    plot.add_legend('best')
    plot.save(get_plot("d201029_sipm_calib_doc/charge_pe.pdf"))
Esempio n. 11
0
class CherenkovShowerGenerator:
    def __init__(self, path, camera, extractor, pedestal, nsb_rate):
        if not exists(path):
            raise ValueError(f"No path found: {self.path}")

        self.path = path
        self.camera = camera
        self.extractor = extractor
        self.pedestal = pedestal
        self.nsb_rate = nsb_rate

        self.n_pixels = self.camera.mapping.n_pixels

        self.reader = PhotoelectronReader(self.path)

        if self.n_pixels != 2048:
            print(
                "Warning: Full camera pixels not simulated for cherenkov events"
            )

        self.source = PhotoelectronSource(camera=self.camera)
        self.acquisition = EventAcquisition(camera=self.camera)

    @property
    def event_table_layout(self):
        class EventTable(tables.IsDescription):
            n_triggers = tables.Int64Col(shape=1)
            signal_pe = tables.Int64Col(shape=self.n_pixels)
            signal_charge = tables.Float64Col(shape=self.n_pixels)
            peak_index = tables.Int64Col(shape=self.n_pixels)
            measured_charge = tables.Float64Col(shape=self.n_pixels)

            # Event metadata
            event_index = tables.UInt64Col()
            event_id = tables.UInt64Col()
            telescope_id = tables.UInt8Col()
            n_photoelectrons = tables.UInt64Col()
            energy = tables.Float64Col()
            alt = tables.Float64Col()
            az = tables.Float64Col()
            core_x = tables.Float64Col()
            core_y = tables.Float64Col()
            h_first_int = tables.Float64Col()
            x_max = tables.Float64Col()
            shower_primary_id = tables.UInt8Col()

        return EventTable

    def __iter__(self):
        for cherenkov_pe in self.reader:
            nsb_pe = self.source.get_nsb(self.nsb_rate)
            signal_pe = self.source.resample_photoelectron_charge(cherenkov_pe)
            signal_pe_per_pixel = signal_pe.get_photoelectrons_per_pixel(
                self.n_pixels)
            signal_charge_per_pixel = signal_pe.get_charge_per_pixel(
                self.n_pixels)
            readout = self.acquisition.get_continuous_readout(nsb_pe +
                                                              signal_pe)

            n_triggers = self.acquisition.get_trigger(readout).size

            waveform = self.acquisition.get_sampled_waveform(readout)
            peak_index = self.extractor.obtain_peak_index_from_neighbours(
                waveform)
            measured_charge = self.extractor.extract(waveform, peak_index)

            yield dict(
                n_triggers=n_triggers,
                signal_pe=signal_pe_per_pixel,
                signal_charge=signal_charge_per_pixel,
                peak_index=peak_index,
                measured_charge=measured_charge,
                **signal_pe.metadata,
            )
def main():
    camera = Camera(
        continuous_readout_duration=10000,
        mapping=SSTCameraMapping(n_pixels=1),
        photoelectron_spectrum=SiPMDelayed(opct=0.2, time_constant=24),
        # photoelectron_spectrum=PerfectPhotosensor(),
    )
    source = PhotoelectronSource(camera=camera)
    nsb_rate = 2.3
    n_events = 100000
    dt = []
    rng = np.random.RandomState(None)
    for iev in trange(n_events):
        pe = source.get_nsb(rate=nsb_rate)
        # pe = Photoelectrons(
        #     pixel=np.zeros(1),
        #     time=np.full(1, 20.),
        #     charge=np.ones(1),
        # )
        # pe = camera.photoelectron_spectrum.apply(pe, rng)
        time = np.sort(pe.time)
        # time = pe.time
        if time.size > 1:
            # embed()
            dt.append(time[1] - time[0])
            # dt.append(time[1:] - time[0])
            # if (np.diff(time) < 100).any():
            #     embed()
            # dt.append(np.diff(time))
    # dt = dt[dt < 1000]
    # dt = (time - time[:, None]).ravel()
    # dt = dt[dt > 8]
    # embed()
    dt = np.array(dt)
    # dt = np.concatenate(dt)
    dt = dt[(dt > 0) & (dt < 1000)]
    hist, edges = np.histogram(dt, bins=200)
    between = (edges[1:] + edges[:-1]) / 2

    def func(t, a0, tc0, a1, tc1):
        return a0 * 1 / tc0 * np.exp(t / -tc0) + a1 * 1 / tc1 * np.exp(
            t / -tc1)

    def cost_binned_nll(a0, tc0, a1, tc1):
        f_y = func(between, a0, tc0, a1, tc1)
        scale = np.sum(hist) / np.sum(f_y)
        return np.sum(_bin_nll(f_y * scale, hist))

    def cost_ls(a0, tc0, a1, tc1):
        f_y = func(between, a0, tc0, a1, tc1)
        gt5 = hist > 5
        return np.sum((hist[gt5] - f_y[gt5])**2 / hist[gt5])

    def cost_unbinned_nll(a0, tc0, a1, tc1):
        if np.isnan(np.array([a0, tc0, a1, tc1])).any():
            return np.inf
        f_y = func(dt, a0, tc0, a1, tc1)
        return -_sum_log_x(f_y)

    initial = dict(a0=1, tc0=30, a1=1, tc1=1e3 / nsb_rate)
    limits = dict(limit_a0=(0, None),
                  limit_tc0=(1, None),
                  limit_a1=(0, None),
                  limit_tc1=(1, None))
    fixed = dict(
        # fix_a0=True,
        # fix_tc0=True,
        # fix_a1=True,
        # fix_tc1=True
    )

    m0 = iminuit.Minuit(
        cost_binned_nll,
        **initial,
        **limits,
        **fixed,
        errordef=0.5,
        print_level=0,
        pedantic=False,
        throw_nan=True,
    )
    m0.migrad()
    m0.hesse()

    # # Attempt to run HESSE to compute parabolic errors.
    # with warnings.catch_warnings():
    #     warnings.simplefilter("ignore", iminuit.util.HesseFailedWarning)
    #     m0.hesse()

    # embed()
    print(m0.values)
    print(m0.errors)

    # popt, pcov = curve_fit(func, between, hist)
    # embed()
    p_timesep = TimeSeperationPlot(talk=True)
    # p_timesep.ax.hist(dt, bins=100)#, density=True)
    # p_timesep.ax.plot(between, hist, '.')
    (_, caps, _) = p_timesep.ax.errorbar(between,
                                         hist,
                                         yerr=np.sqrt(hist),
                                         mew=1,
                                         capsize=1,
                                         elinewidth=0.5,
                                         markersize=2,
                                         linewidth=0.5,
                                         fmt='.',
                                         zorder=1)
    # p_timesep.ax.errorbar(between, hist, yerr=np.sqrt(hist))
    f_y = func(between, *m0.values.values())
    scale = np.sum(hist) / np.sum(f_y)
    p_timesep.ax.plot(between, f_y * scale)
    p_timesep.ax.text(0.1,
                      0.8,
                      fr"$τ_S$={m0.values['tc0']:.2f}",
                      transform=p_timesep.ax.transAxes)
    p_timesep.ax.text(0.7,
                      0.4,
                      fr"$τ_L$={m0.values['tc1']:.2f}",
                      transform=p_timesep.ax.transAxes)
    p_timesep.ax.set_yscale("log")
    p_timesep.ax.set_xlabel("Δt (ns)")
    p_timesep.save("timesep.pdf")