def obj_amplitude_second_order(params, slope, phase, pos, compare='hilbert', debug_obj=0):
            ampPhi, ampTheta, second_order = params
            analytic_pulse_theta = pulse.get_analytic_pulse_freq(ampTheta, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass, quadratic_term=second_order, quadratic_term_offset=bandpass[0])
            analytic_pulse_phi = pulse.get_analytic_pulse_freq(ampPhi, slope, phase, n_samples_time, sampling_rate, bandpass=bandpass, quadratic_term=second_order, quadratic_term_offset=bandpass[0])
            chi2 = 0
            if(debug_obj and self.i_slope_fit_iterations % 50 == 0):
                fig, ax = plt.subplots(5, 2, sharex=False, figsize=(20, 10))

            n_channels = len(V_timedomain)
            analytic_traces = np.zeros((n_channels, n_samples_time))
            # first determine the position with the larges xcorr
            channel_max = 0
            for trace in V_timedomain:
                if np.max(np.abs(trace)) > channel_max:
                    channel_max = np.max(np.abs(trace))
                    argmax = np.argmax(np.abs(trace))
                    imin = np.int(max(argmax - 50 * sampling_rate, 0))
                    imax = np.int(argmax + 50 * sampling_rate)
            for iCh, trace in enumerate(V_timedomain):
                analytic_trace_fft = np.sum(efield_antenna_factor[iCh] * np.array([analytic_pulse_theta, analytic_pulse_phi]), axis=0)
                analytic_traces[iCh] = fft.freq2time(analytic_trace_fft, sampling_rate)
                if compare == 'trace':
                    tmp = np.sum(np.abs(trace[imin:imax] - np.roll(analytic_traces[iCh], pos)[imin:imax]) ** 2) / noise_RMS ** 2
                elif compare == 'abs':
                    tmp = np.sum(np.abs(np.abs(trace[imin:imax]) - np.abs(np.roll(analytic_traces[iCh], pos)[imin:imax])) ** 2) / noise_RMS ** 2
                elif compare == 'hilbert':
                    tmp = np.sum(np.abs(np.abs(signal.hilbert(trace[imin:imax])) - np.abs(signal.hilbert(np.roll(analytic_traces[iCh], pos)[imin:imax]))) ** 2) / noise_RMS ** 2
                else:
                    raise NameError('Unsupported value for parameter "compare": {}. Value must be "trace", "abs" or "hilbert".'.format(compare))
                chi2 += tmp
                if(debug_obj and self.i_slope_fit_iterations % 50 == 0):
                    ax[iCh][1].plot(np.array(trace) / units.mV, label='measurement', color='blue')
                    ax[iCh][1].plot(np.roll(analytic_traces[iCh], pos) / units.mV, '--', label='fit', color='orange')
                    ax[iCh][1].plot(np.abs(signal.hilbert(trace)) / units.mV, linestyle=':', color='blue')
                    ax[iCh][1].plot(np.abs(signal.hilbert(np.roll(analytic_traces[iCh], pos))) / units.mV, ':', label='fit', color='orange')
                    ax[iCh][0].plot(np.abs(fft.time2freq(trace, sampling_rate)) / units.mV, label='measurement')
                    ax[iCh][0].plot(np.abs(fft.time2freq(np.roll(analytic_traces[iCh], pos), sampling_rate)) / units.mV, '--', label='fit')
                    ax[iCh][0].set_xlim([0, 600])
                    ax[iCh][1].set_xlim([imin - 500, imax + 500])
                    ax[iCh][1].axvline(imin, linestyle='--', alpha=.8)
                    ax[iCh][1].axvline(imax, linestyle='--', alpha=.8)
            if(debug_obj and self.i_slope_fit_iterations % 50 == 0):
                sim_channel = station.get_sim_station().get_channel(0)[0]
                ax[4][0].plot(sim_channel.get_frequencies() / units.MHz, np.abs(pulse.get_analytic_pulse_freq(ampTheta, slope, phase, len(sim_channel.get_times()), sim_channel.get_sampling_rate(), bandpass=bandpass, quadratic_term=second_order)), '--', color='orange')
                ax[4][0].plot(sim_channel.get_frequencies() / units.MHz, np.abs(station.get_sim_station().get_channel(0)[0].get_frequency_spectrum()[1]), color='blue')
                ax[4][1].plot(sim_channel.get_frequencies() / units.MHz, np.abs(pulse.get_analytic_pulse_freq(ampPhi, slope, phase, len(sim_channel.get_times()), sim_channel.get_sampling_rate(), bandpass=bandpass, quadratic_term=second_order)), '--', color='orange')
                ax[4][1].plot(sim_channel.get_frequencies() / units.MHz, np.abs(sim_channel.get_frequency_spectrum()[2]), color='blue')
                ax[4][0].set_xlim([20, 500])
                ax[4][1].set_xlim([20, 500])
            logger.debug("amp phi = {:.4g}, amp theta = {:.4g}, slope = {:.4g} chi2 = {:.8g}".format(ampPhi, ampTheta, slope, chi2))
            if(debug_obj and self.i_slope_fit_iterations % 50 == 0):
                fig.tight_layout()
                plt.show()
                self.i_slope_fit_iterations = 0
            self.i_slope_fit_iterations += 1
            return chi2
Esempio n. 2
0
def fold_efields(efield, zenith, azimuth, antenna_orientation,
                 antenna_pattern):
    """
	A function to do fold efields with the antenna response

	Apply the complex response of the antenna (the vector effective length)
	to an efield, and return the efield after the antenna

	Parameters
	----------
	signal: icetradio.I3EField
		the efield at the antenna

	zenith: float
		the zenith angle (in radians!) of the signal incident on the antenna

	azimuth: float
		the azimuth angle (in radians!) of the signal incident on the antenna

	antenna_orientation: array
		array of floats, specifically the orientation_theta, orientation_phi,
		rotation_theta, and rotation_phi, as they are defined in the NuRadioReco framework
		see https://nu-radio.github.io/NuRadioReco/pages/detector_database_fields.html#antenna-table
		or also the definitions in I3IceAntennaGeo
		https://code.icecube.wisc.edu/projects/icecube/browser/IceCube/sandbox/brianclark/ehe/radio/trunk/dataclasses/public/dataclasses/geometry/I3IceAntennaGeo.h 

	antenna_pattern: NuRadioReco.detector.antennapattern
		the antenna pattern for this antenna

	Returns
	-------
	trace: 
		the voltage trace that will be observed after being folded with the antenna
	"""

    # get the frequencies where the efield needs to be evaluated
    ff = util_dataclasses.get_frequencies_I3EField(efield)

    # get the fourier transforms of the field
    eTheta_freq = fft.time2freq(efield.eTheta.trace,
                                efield.eTheta.samplingRate)
    ePhi_freq = fft.time2freq(efield.ePhi.trace, efield.ePhi.samplingRate)

    # get the vector effective length (VEL)
    antenna_response = antenna_pattern.get_antenna_response_vectorized(
        ff, zenith, azimuth, *antenna_orientation)
    VEL = np.array([antenna_response['theta'], antenna_response['phi']])
    voltage_fft = np.sum(VEL * np.array([eTheta_freq, ePhi_freq]), axis=0)

    # we need to make sure to cancel out the DC offset
    voltage_fft[np.where(ff < 5 * units.MHz)] = 0.
    voltage_trace = fft.freq2time(voltage_fft, efield.eR.samplingRate)
    return voltage_trace
Esempio n. 3
0
def get_frequency_spectrum(energy,
                           theta,
                           N,
                           dt,
                           is_em_shower,
                           n,
                           R,
                           LPM=True,
                           a=None):
    """
    returns the complex amplitudes of the frequency spectrum of the neutrino radio signal

    Parameters
    ----------
    energy : float
        energy of the shower
    theta: float
        viewangle: angle between shower axis (neutrino direction) and the line
        of sight between interaction and detector
    N : int
        number of samples in the time domain
    dt: float
        time bin width, i.e. the inverse of the sampling rate
    is_em_shower: bool
        true if EM shower, false otherwise
    n: float
        index of refraction at interaction vertex
    R: float
        distance from vertex to observer
    LPM: bool (default True)
        enable/disable LPD effect
    a: float or None (default Nont)
        if variable set, the shower width is manually set to this value
    """
    eR, eTheta, ePhi = get_time_trace(energy,
                                      theta,
                                      N,
                                      dt,
                                      is_em_shower,
                                      n,
                                      R,
                                      LPM,
                                      a=a)
    return np.array([
        fft.time2freq(eR, 1. / dt),
        fft.time2freq(eTheta, 1. / dt),
        fft.time2freq(ePhi, 1. / dt)
    ])
Esempio n. 4
0
def butterworth_filter_trace(trace, sampling_frequency, passband, order=8):
    """
    Filters a trace using a Butterworth filter.

    Parameters
    ----------
    trace: array of floats
        Trace to be filtered
    sampling_frequency: float
        Sampling frequency
    passband: (float, float) tuple
        Tuple indicating the cutoff frequencies
    order: integer
        Filter order

    Returns
    ------
    filtered_trace: array of floats
        The filtered trace
    """

    n_samples = len(trace)

    spectrum = fft.time2freq(trace, sampling_frequency)
    frequencies = np.fft.rfftfreq(n_samples, 1 / sampling_frequency)

    filtered_spectrum = apply_butterworth(spectrum, frequencies, passband, order)
    filtered_trace = fft.freq2time(filtered_spectrum, sampling_frequency)

    return filtered_trace
Esempio n. 5
0
    def get_frequency_spectrum(self):
        if(self.__time_domain_up_to_date):
            self._frequency_spectrum = fft.time2freq(self._time_trace, self._sampling_rate)
            self._time_trace = None
#             logger.debug("frequency spectrum has shape {}".format(self._frequency_spectrum.shape))
            self.__time_domain_up_to_date = False
        return np.copy(self._frequency_spectrum)
Esempio n. 6
0
def get_frequency_spectrum(energy, theta, N, dt, shower_type, n_index, R, model, full_output=False, **kwargs):
    """
    returns the complex amplitudes of the frequency spectrum of the neutrino radio signal

    Parameters
    ----------
    energy : float
        energy of the shower
    theta: float
        viewangle: angle between shower axis (neutrino direction) and the line
        of sight between interaction and detector
    N : int
        number of samples in the time domain
    dt: float
        time bin width, i.e. the inverse of the sampling rate
    shower_type: string (default "HAD")
        type of shower, either "HAD" (hadronic), "EM" (electromagnetic) or "TAU" (tau lepton induced)
        note that TAU showers are currently only implemented in the ARZ2019 model
    n_index: float
        index of refraction at interaction vertex
    R: float
        distance from vertex to observer
    model: string
        specifies the signal model
        * ZHS1992: the original ZHS parametrization from E. Zas, F. Halzen, and T. Stanev, Phys. Rev. D 45, 362 (1992), doi:10.1103/PhysRevD.45.362, this parametrization does not contain any phase information
        * Alvarez2000: parameterization based on ZHS mainly based on J. Alvarez-Muniz, R. A. V ́azquez, and E. Zas, Calculation methods for radio pulses from high energyshowers, Physical Review D62 (2000) https://doi.org/10.1103/PhysRevD.84.103003
        * Alvarez2009: parameterization based on ZHS from J. Alvarez-Muniz, W. R. Carvalho, M. Tueros, and E. Zas, Coherent cherenkov radio pulses fromhadronic showers up to EeV energies, Astroparticle Physics 35 (2012), no. 6 287 – 299 and J. Alvarez-Muniz, C. James, R. Protheroe, and E. Zas, Thinned simulations of extremely energeticshowers in dense media for radio applications, Astroparticle Physics 32 (2009), no. 2 100 – 111
        * HCRB2017: analytic model from J. Hanson, A. Connolly Astroparticle Physics 91 (2017) 75-89
        * ARZ2019 semi MC time domain model from Alvarez-Muñiz, J., Romero-Wolf, A., & Zas, E. (2011). Practical and accurate calculations of Askaryan radiation. Physical Review D - Particles, Fields, Gravitation and Cosmology, 84(10). https://doi.org/10.1103/PhysRevD.84.103003
    full_output: bool (default False)    
        if True, askaryan modules can return additional output
    Returns
    -------
    spectrum: array
        the complex amplitudes for the given frequencies
    additional information: dict
        only available if `full_output` enabled

    """
    tmp = get_time_trace(energy, theta, N, dt, shower_type, n_index, R, model, full_output=full_output, **kwargs)
    if(full_output):
        return fft.time2freq(tmp[0], 1 / dt), tmp[1]
    else:
        return fft.time2freq(tmp, 1 / dt)
Esempio n. 7
0
    def tunnel_diode(self, channel):
        """
        Calculate a signal as processed by the tunnel diode.
        The given signal is convolved with the tunnel diode response as in
        AraSim.

        The diode model used in this module returns a dimensionless power trace,
        where the antenna resistance is only a normalisation for the final
        voltage. That's why the antenna resistance has been fixed to a value
        of 8.5 ohms.

        Parameters
        ----------
        channel: Channel
            Signal to be processed by the tunnel diode.

        Returns
        -------
        trace_after_tunnel_diode: array
            Signal output of the tunnel diode for the input `channel`.
            Careful! This trace is dimensionless and comes from a convolution
            of the power with the diode response.
        """
        t_max = 1e-7 * units.s
        antenna_resistance = 8.5 * units.ohm
        n_pts = int(t_max * channel.get_sampling_rate())
        times = np.linspace(0, t_max, n_pts + 1)
        diode_resp = self._td_fdown1(times) + self._td_fdown2(times)
        t_slice = times > self._td_args['up'][1]
        diode_resp[t_slice] += self._td_fup(times[t_slice])
        conv = scipy.signal.convolve(channel.get_trace() ** 2 / antenna_resistance,
                                     diode_resp, mode='full')
        # conv multiplied by dt so that the amplitude stays constant for
        # varying dts (determined emperically, see ARVZAskaryanSignal comments)
        # Setting output
        trace_after_tunnel_diode = conv / channel.get_sampling_rate()
        trace_after_tunnel_diode = trace_after_tunnel_diode[:channel.get_trace().shape[0]]

        # We filter the output if the band is specified
        if self._output_passband != (None, None):

            sampling_rate = channel.get_sampling_rate()
            trace_spectrum = time2freq(trace_after_tunnel_diode, sampling_rate)
            frequencies = np.linspace(0, sampling_rate / 2, len(trace_spectrum))
            if self._output_passband[0] is None:
                b, a = butter(6, self._output_passband[1], 'lowpass', analog=True)
            else:
                b, a = butter(6, self._output_passband, 'bandpass', analog=True)
            w, h = freqs(b, a, frequencies)
            trace_after_tunnel_diode = freq2time(h * trace_spectrum, sampling_rate)

        return trace_after_tunnel_diode
Esempio n. 8
0
def apply_amplifier_filter(voltage_trace, dT, amplifier_filter_response):
    """
	A function to apply amplifier+filter responses to a voltage trace

	Apply the complex response of the amplifier and filter (magnitude and phase)
	to a voltage trace, and return the trace after amplification

	Parameters
	----------
	voltage_trace: array
		the trace to which we want to apply the amplifier and filter

	dT: float
		the time between samples of the voltage trace

	azimuth: float
		the azimuth angle (in radians!) of the signal incident on the antenna

	amplifier_filter_response: array
		The dict containing the amplifier + filter response
		As loaded in the load_filter_amplifier_response function
	

	Returns
	-------
	trace: 
		the voltage trace that will be observed after applying the amps + filters

	"""
    orig_frequencies = amplifier_filter_response['frequencies']
    orig_phase = amplifier_filter_response['phase']
    orig_gain = amplifier_filter_response['gain']

    # interpolate the phase and gain
    interp_phase = interp1d(orig_frequencies,
                            np.unwrap(orig_phase),
                            bounds_error=False,
                            fill_value=0)
    interp_gain = interp1d(orig_frequencies,
                           orig_gain,
                           bounds_error=False,
                           fill_value=0)

    num_samples = len(voltage_trace)  # the number of samples
    frequencies = np.fft.rfftfreq(num_samples, dT)
    gain = interp_gain(frequencies)
    phase = np.exp(1j * interp_phase(frequencies))

    the_fft = fft.time2freq(voltage_trace, 1. / dT)
    the_fft *= (gain * phase)
    the_result_trace = fft.freq2time(the_fft, 1. / dT)
    return the_result_trace
Esempio n. 9
0
def delay_trace(trace, sampling_frequency, time_delay, delayed_samples):
    """
    Delays a trace by transforming it to frequency and multiplying by phases.
    Since this method is cyclic, the trace has to be cropped. It only accepts
    positive delays, so some samples from the beginning are thrown away and then
    some samples from the end so that the total number of samples is equal to
    the argument delayed samples.

    Parameters
    ----------
    trace: array of floats
        Array containing the trace
    sampling_frequency: float
        Sampling rate for the trace
    time_delay: float
        Time delay used for transforming the trace. Must be positive or 0
    delayed_samples: integer
        Number of samples that the delayed trace must contain

    Returns
    -------
    delayed_trace: array of floats
        The delayed, cropped trace
    """

    if time_delay < 0:
        msg = 'Time delay must be positive'
        raise ValueError(msg)

    n_samples = len(trace)

    spectrum = fft.time2freq(trace, sampling_frequency)
    frequencies = np.fft.rfftfreq(n_samples, 1 / sampling_frequency)

    spectrum *= np.exp(-1j * 2 * np.pi * frequencies * time_delay)

    delayed_trace = fft.freq2time(spectrum, sampling_frequency)

    init_sample = int(time_delay * sampling_frequency) + 1

    delayed_trace = delayed_trace[init_sample:None]
    delayed_trace = delayed_trace[:delayed_samples]

    return delayed_trace
 def __calculate_time_delays_amp(self, amp_type):
     """
     helper function to calculate the time delay of the amp for a delta pulse
     """
     amp_response_f = analog_components.load_amp_response(amp_type)
     sampling_rate = 10 * units.GHz  # assume a huge sampling rate to have a good time resolution
     n = 2 ** 12
     trace = np.zeros(n)
     trace[n // 2] = 1
     max_time = trace.argmax() / sampling_rate
     spec = fft.time2freq(trace, sampling_rate)
     ff = np.fft.rfftfreq(n, 1. / sampling_rate)
     amp_response_gain = amp_response_f['gain'](ff)
     amp_response_phase = amp_response_f['phase'](ff)
     mask = (ff < 70 * units.MHz) & (ff > 40 * units.MHz)
     spec[~mask] = 0
     trace2 = fft.freq2time(spec * amp_response_gain * amp_response_phase, sampling_rate)
     max_time2 = np.abs(trace2).argmax() / sampling_rate
     return max_time2 - max_time
    def __calculate_time_delays_cable(self):
        """
        helper function to calculate the time delay of the amp for a delta pulse
        """
        sampling_rate = 10 * units.GHz  # assume a huge sampling rate to have a good time resolution
        n = 2 ** 12
        trace = np.zeros(n)
        trace[n // 2] = 1
        max_time = trace.argmax() / sampling_rate
        spec = fft.time2freq(trace, sampling_rate)
        ff = np.fft.rfftfreq(n, 1. / sampling_rate)
        response = analog_components.get_cable_response(ff)
        response_gain = response['gain']
        response_phase = response['phase']
        trace2 = fft.freq2time(spec * response_gain * response_phase, sampling_rate)
#         import matplotlib.pyplot as plt
#         fig, ax = plt.subplots(1, 1)
#         ax.plot(trace)
#         ax.plot(trace2)
#         plt.show()
        max_time2 = np.abs(trace2).argmax() / sampling_rate
        return max_time2 - max_time
    def run(self,
            event,
            station,
            detector,
            amplitude=1 * units.mV,
            min_freq=50 * units.MHz,
            max_freq=2000 * units.MHz,
            type='perfect_white',
            excluded_channels=None,
            bandwidth=None):
        """
        Add noise to given event.

        Parameters
        ---------

        event

        station

        detector

        amplitude: float or dict of floats
            desired voltage of noise as V_rms for the specified bandwidth
            a dict can be used to specify a different amplitude per channel, the key is the channel_id
        min_freq: float
            Minimum frequency of passband for noise generation
        max_freq: float
            Maximum frequency of passband for noise generation
            If the maximum frequency is above the Nquist frequencey (0.5 * sampling rate), the Nquist frequency is used
        type: string
            perfect_white: flat frequency spectrum
            rayleigh: Amplitude of each frequency bin is drawn from a Rayleigh distribution
        excluded_channels: list of ints
            the channels ids of channels where no noise will be added, default is that no channel is excluded
        bandwidth: float or None (default)
            if this parameter is specified, the amplitude is interpreted as the amplitude for the bandwidth specified here
            Otherwise the amplitude is interpreted for the bandwidth of min(max_freq, 0.5 * sampling rate) - min_freq
            If `bandwidth` is larger then (min(max_freq, 0.5 * sampling rate) - min_freq) it has the same effect as `None`

        """
        if excluded_channels is None:
            excluded_channels = []
        channels = station.iter_channels()
        for channel in channels:
            if (channel.get_id() in excluded_channels):
                continue

            trace = channel.get_trace()
            sampling_rate = channel.get_sampling_rate()

            if (isinstance(amplitude, dict)):
                tmp_ampl = amplitude[channel.get_id()]
            else:
                tmp_ampl = amplitude

            noise = self.bandlimited_noise(min_freq=min_freq,
                                           max_freq=max_freq,
                                           n_samples=trace.shape[0],
                                           sampling_rate=sampling_rate,
                                           amplitude=tmp_ampl,
                                           type=type,
                                           bandwidth=bandwidth)

            if self.__debug:
                new_trace = trace + noise

                self.logger.debug("imput amplitude {}".format(amplitude))
                self.logger.debug("voltage RMS {}".format(
                    np.sqrt(np.mean(noise**2))))

                import matplotlib.pyplot as plt
                plt.plot(trace)
                plt.plot(noise)
                plt.plot(new_trace)

                plt.figure()
                plt.plot(
                    np.abs(fft.time2freq(trace, channel.get_sampling_rate())))
                plt.plot(
                    np.abs(fft.time2freq(noise, channel.get_sampling_rate())))
                plt.plot(
                    np.abs(
                        fft.time2freq(new_trace, channel.get_sampling_rate())))

                plt.show()

            new_trace = trace + noise
            channel.set_trace(new_trace, sampling_rate)
    def run(self, event, station, det, channel_ids, passband):
        """
        Run the module on a station

        Parameters:
        _________________
        event: NuRadioReco.framework.event.Event object
            The event the module should be run on
        station: NuRadioReco.framework.station.Station object
            The station the module should be run on
        det: NuRadioReco.detector.detector.Detector object of child object
            The detector description
        channel_ids: array of int
            IDs of the channels the module should be run on
        passband: array of float
            Lower and upper bound of the bandpass filter that is applied to the
            channel waveforms when determining correlation to the template. Can
            be used to filter out frequencies that are dominated by noise.
        """

        # Create data structured to store pulse properties
        propagation_times = np.zeros((len(channel_ids), 3))
        receive_angles = np.zeros((len(channel_ids), 3))
        found_solutions = np.zeros((len(channel_ids), 3))
        # Get vertex position
        vertex_position = None
        if self.__use_sim:
            for sim_shower in event.get_sim_showers():
                if sim_shower.has_parameter(shp.vertex):
                    vertex_position = sim_shower.get_parameter(shp.vertex)
                    break
        else:
            if station.has_parameter(stnp.nu_vertex):
                vertex_position = station.get_parameter(stnp.nu_vertex)
            elif station.has_parameter(stnp.vertex_2D_fit):
                vertex_2d = station.get_parameter(stnp.vertex_2D_fit)
                vertex_position = np.array([vertex_2d[0], 0, vertex_2d[1]])
        if vertex_position is None:
            raise RuntimeError('Could not find vertex position')
        correlation_size = 0
        for i_channel, channel_id in enumerate(channel_ids):
            channel = station.get_channel(channel_id)
            if channel.get_sampling_rate(
            ) != self.__electric_field_template.get_sampling_rate():
                raise RuntimeError(
                    'The channels and the electric field remplate need to have the same sampling rate.'
                )
            # Calculate size of largest autocorrelation
            if channel.get_number_of_samples(
            ) + self.__electric_field_template.get_number_of_samples(
            ) - 1 > correlation_size:
                correlation_size = channel.get_number_of_samples(
                ) + self.__electric_field_template.get_number_of_samples() - 1
            channel_position = det.get_relative_position(
                station.get_id(), channel_id)
            raytracer = NuRadioMC.SignalProp.analyticraytracing.ray_tracing(
                x1=vertex_position, x2=channel_position, medium=self.__medium)
            raytracer.find_solutions()
            # Loop through all 3 ray path types and store the properties into the data structure
            for i_solution, solution in enumerate(raytracer.get_results()):
                solution_type = raytracer.get_solution_type(i_solution)
                found_solutions[i_channel, solution_type - 1] += 1
                propagation_times[i_channel, solution_type -
                                  1] = raytracer.get_travel_time(i_solution)
                receive_vector = raytracer.get_receive_vector(i_solution)
                receive_angles[i_channel, solution_type -
                               1] = radiotools.helper.cartesian_to_spherical(
                                   receive_vector[0], receive_vector[1],
                                   receive_vector[2])[0]
        # We only want the relative time differences between channels, so we subtract the mean propagation time
        for i_solution in range(3):
            if len(propagation_times[:, i_solution][
                    propagation_times[:, i_solution] > 0]) > 0:
                propagation_times[:, i_solution][
                    propagation_times[:, i_solution] > 0] -= np.mean(
                        propagation_times[:, i_solution][
                            propagation_times[:, i_solution] > 0])
        correlation_sum = np.zeros((3, correlation_size))
        # Now we check which ray path results in the best correlation between channels
        for i_channel, channel_id in enumerate(channel_ids):
            channel = station.get_channel(channel_id)
            antenna_pattern = self.__antenna_provider.load_antenna_pattern(
                det.get_antenna_model(station.get_id(), channel_id))
            antenna_orientation = det.get_antenna_orientation(
                station.get_id(), channel_id)
            for i_solution in range(3):
                if found_solutions[i_channel, i_solution] > 0:
                    # We calculate the voltage template from the electric field template using the receiving angles
                    # for that raytracing solution
                    antenna_response = antenna_pattern.get_antenna_response_vectorized(
                        self.__electric_field_template.get_frequencies(),
                        receive_angles[i_channel, i_solution], 0.,
                        antenna_orientation[0], antenna_orientation[1],
                        antenna_orientation[2], antenna_orientation[3])
                    # For simplicity, we assume equal contribution on the E_theta and E_phi component
                    channel_template_spec = fft.time2freq(self.__electric_field_template.get_filtered_trace(passband), self.__electric_field_template.get_sampling_rate()) * \
                        det.get_amplifier_response(station.get_id(), channel_id, self.__electric_field_template.get_frequencies()) * (antenna_response['theta'] + antenna_response['phi'])
                    channel_template_trace = fft.freq2time(
                        channel_template_spec,
                        self.__electric_field_template.get_sampling_rate())
                    # We apply the expected time shift for the raytracing solution and calculate the correlation with the template
                    channel.apply_time_shift(
                        -propagation_times[i_channel, i_solution], True)
                    correlation = radiotools.helper.get_normalized_xcorr(
                        channel_template_trace,
                        channel.get_filtered_trace(passband))
                    correlation = np.abs(correlation)
                    correlation_sum[i_solution][:len(correlation
                                                     )] += correlation
                    channel.apply_time_shift(
                        propagation_times[i_channel, i_solution], True)

        correlation_sum_max = np.max(correlation_sum, axis=1)
        # We look for the raytracing solution that yielded the best correlation and store the corresponding properties
        # in the channel parameters
        for i_channel, channel_id in enumerate(channel_ids):
            channel = station.get_channel(channel_id)
            channel.set_parameter(
                chp.signal_time_offset,
                propagation_times[i_channel,
                                  np.argmax(correlation_sum_max)])
            channel.set_parameter(
                chp.signal_receiving_zenith,
                receive_angles[i_channel,
                               np.argmax(correlation_sum_max)])
            channel.set_parameter(
                chp.signal_ray_type,
                self.__raytracing_types[np.argmax(correlation_sum_max)])
Esempio n. 14
0
def generate_signal(deposited_energy,
                    shower_axis,
                    em_or_had,
                    launch_vector,
                    distance,
                    arrival_time,
                    n_index,
                    attenuation_values,
                    dt,
                    n_samples,
                    model,
                    seed,
                    keep_unattenuated_fields=False):
    """
	A function to generate askaryan fields at the antennas

	Get the askaryan signals/fields at the antenna. This means that the fields
	returned by this function will already include the polarization factors,
	the 1/R, and the attenuation due to the ice.

	Parameters
	----------
	deposited_energy: double or float
		energy deposited in the shower in eV
	
	shower_axis: I3Position
		the shower axis
	
	launch_vector: I3Position
		the launch vector of the ray that makes the signal

	distance: float
		the path length traveled by the signal in m (including ray bending!)

	arrival_time: float
		the time the field arrives at the antenna, in seconds (including ray bending!)

	n_index: float
		the index of refraction at the vertex

	atttenuation_values: complex np array
		the complex frequency-dependent attenuation factors

	dt: float
		the time between samples for the askaryan emission, in seconds

	n_samples: int
		the number of samples to have in the Askaryan emission

	model: string
		what Askaryan model should be used to generate the emission
		options are described in NuRadioMC.SignalGen.askaryan
		https://github.com/nu-radio/NuRadioMC/blob/master/NuRadioMC/SignalGen/askaryan.py

	seed: int
		what random number seed should be used in generating the askaryan emission

	keep_unattenuated_fields: bool
		whether or not to keep a copy of the E-fields that does not have attenuation factors applied
		default is False, to reduce file output sizes

	Returns
	-------
	signal: I3RadioSignal
		the radio signal container for this event
	"""

    local_launch_vector = util_dataclasses.i3pos_to_np(launch_vector)
    local_shower_axis = util_dataclasses.i3pos_to_np(shower_axis)

    viewing_angle = hp.get_angle(local_shower_axis, local_launch_vector)

    signal = askaryan.get_time_trace(energy=deposited_energy,
                                     theta=viewing_angle,
                                     N=n_samples,
                                     dt=dt,
                                     shower_type=em_or_had,
                                     n_index=n_index,
                                     R=distance,
                                     model=model,
                                     seed=seed)

    signal_spectrum = fft.time2freq(signal, 1. / dt)
    attenuated_signal_spectrum = signal_spectrum * attenuation_values
    attenuated_signal = fft.freq2time(attenuated_signal_spectrum, 1. / dt)

    # calculate the polarization
    polarization_direction_onsky = util_geo.calculate_polarization_vector(
        local_launch_vector, local_shower_axis)
    icetray.logging.log_debug("Polarization direction on sky {}".format(
        polarization_direction_onsky))

    # create the e-fields at the antenna
    this_eR_attenuated, this_eTheta_attenuated, this_ePhi_attenuated = np.outer(
        polarization_direction_onsky, attenuated_signal)

    # store the eR, eTheta, ePhi components in trace for attenuated field
    sampling_rate = 1. / dt
    eR_attenuated = util_dataclasses.fill_I3Trace(this_eR_attenuated,
                                                  arrival_time, sampling_rate)
    eTheta_attenuated = util_dataclasses.fill_I3Trace(this_eTheta_attenuated,
                                                      arrival_time,
                                                      sampling_rate)
    ePhi_attenuated = util_dataclasses.fill_I3Trace(this_ePhi_attenuated,
                                                    arrival_time,
                                                    sampling_rate)

    # put those traces into fields
    field_watt = util_dataclasses.fill_I3EField(eR_attenuated,
                                                eTheta_attenuated,
                                                ePhi_attenuated)

    # and finally, create and return a signal object
    signal = icetradio.I3RadioSignal()
    signal.view_angle = viewing_angle * icetray.I3Units.rad
    signal.polarization_vector = util_dataclasses.np_to_i3pos(
        polarization_direction_onsky, 'sph')
    signal.field_watt = field_watt

    if keep_unattenuated_fields:
        # make a copy of the fields that doesn't include the attenuation factor
        # we can generally *not* save this information as a space saving measure
        this_eR, this_eTheta, this_ePhi = np.outer(
            polarization_direction_onsky, signal)
        eR = util_dataclasses.fill_I3Trace(this_eR, arrival_time,
                                           sampling_rate)
        eTheta = util_dataclasses.fill_I3Trace(this_eTheta, arrival_time,
                                               sampling_rate)
        ePhi = util_dataclasses.fill_I3Trace(this_ePhi, arrival_time,
                                             sampling_rate)
        field_noatt = util_dataclasses.fill_I3EField(eR, eTheta, ePhi)
        signal.field_noatt = field_noatt

    return signal
Esempio n. 15
0
def plot_field_and_trace(the_field,
                         trace_after_antenna,
                         trace_after_amps,
                         info=None):
    """
	A function to plot the efields, trace after antenna, and trace after amps all together

	Parameters
	----------
	the_field: the I3EField
		the I3EField before the antenna

	trace_after_antenna: array
		voltage trace after the antenna but before amplifiers

	trace_after_amps: array
		voltage trace after the amplifiers and filters

	info : 
		information the user would like put into the output filename


	Returns
	-------
	void

	"""
    print("Plotting the event...")
    eTheta = the_field.eTheta.trace
    ePhi = the_field.ePhi.trace
    dT = 1. / the_field.eTheta.samplingRate
    eTheta_fft = fft.time2freq(eTheta, 1. / dT)
    ePhi_fft = fft.time2freq(ePhi, 1. / dT)

    trace_after_antenna_fft = fft.time2freq(trace_after_antenna, 1. / dT)
    trace_after_amps_fft = fft.time2freq(trace_after_amps, 1. / dT)

    times = util_dataclasses.get_I3Trace_times(the_field.eTheta)
    frequencies = np.fft.rfftfreq(len(eTheta), dT)

    fig, axs = plt.subplots(3, 2, figsize=(10, 10))
    axs[0][0].plot(times, eTheta, label='Theta Component')
    axs[0][0].plot(times, ePhi, '--', label='Phi Component')
    axs[0][0].legend()
    axs[0][0].set_xlabel('Time (ns)')
    axs[0][0].set_ylabel('V/m')
    axs[0][0].set_title("E-Fields -- Time Domain")

    axs[0][1].plot(frequencies, np.abs(eTheta_fft))
    axs[0][1].plot(frequencies, np.abs(ePhi_fft), '--')
    axs[0][1].set_xlim([0, 1])
    axs[0][1].set_ylabel('Spectrum')
    axs[0][1].set_xlabel('Frequency (GHz)')
    axs[0][1].set_title("E-Fields -- Frequency Domain")

    axs[1][0].plot(times, trace_after_antenna)
    axs[1][1].plot(frequencies, np.abs(trace_after_antenna_fft))
    axs[1][0].set_xlabel('Time (ns)')
    axs[1][0].set_ylabel('V')
    axs[1][0].set_title("Trace, After Antenna -- Time Domain")
    axs[1][1].set_xlabel('Freq (GHz)')
    axs[1][1].set_ylabel('V')
    axs[1][1].set_title("Trace, After Antenna -- Frequency Domain")
    axs[1][1].set_xlim([0, 1])

    axs[2][0].plot(times, trace_after_amps)
    axs[2][1].plot(frequencies, np.abs(trace_after_amps_fft))
    axs[2][0].set_xlabel('Time (ns)')
    axs[2][0].set_ylabel('V')
    axs[2][0].set_title("Trace, After Amps+Filter -- Time Domain")
    axs[2][1].set_xlabel('Freq (GHz)')
    axs[2][1].set_ylabel('V')
    axs[2][1].set_title("Trace, After Amps+Filter -- Frequency Domain")
    axs[2][1].set_xlim([0, 1])

    plt.tight_layout()
    save_tile = 'event.png'
    if info is not None:
        save_tile = 'event_' + info + 'png'
    fig.savefig(save_tile)
    plt.close(fig)
    del fig, axs
Esempio n. 16
0
def update_voltage_plot(
    electric_field,
    antenna_type,
    signal_zenith,
    signal_azimuth,
    amplifier_type,
    filter_toggle,
    filter_band,
    sampling_rate
):
    samples = int(512 * sampling_rate)
    electric_field = json.loads(electric_field)
    if electric_field is None:
        return {}, {}, ""
    antenna_pattern = antennapattern_provider.load_antenna_pattern(antenna_type)
    freqs = np.fft.rfftfreq(samples, 1. / sampling_rate)
    times = np.arange(samples) / sampling_rate
    signal_zenith = signal_zenith * units.deg
    signal_azimuth = signal_azimuth * units.deg

    antenna_response = antenna_pattern.get_antenna_response_vectorized(
        freqs,
        signal_zenith,
        signal_azimuth,
        0.,
        0.,
        90.*units.deg,
        180.*units.deg
    )
    detector_response_theta = antenna_response['theta']
    detector_response_phi = antenna_response['phi']
    channel_spectrum = antenna_response['theta'] * fft.time2freq(electric_field['theta'], sampling_rate) + antenna_response['phi'] * fft.time2freq(electric_field['phi'], sampling_rate)
    if amplifier_type is not None:
        if amplifier_type == 'iglu' or amplifier_type == 'rno_surface':
            amp_response = NuRadioReco.detector.RNO_G.analog_components.load_amp_response(amplifier_type)
            amplifier_response = amp_response['gain'](freqs) * amp_response['phase'](freqs)
        else:
            amp_response = NuRadioReco.detector.ARIANNA.analog_components.load_amplifier_response(amplifier_type)
            amplifier_response = amp_response['gain'](freqs) * amp_response['phase'](freqs)
        channel_spectrum = channel_spectrum * amplifier_response
        detector_response_theta *= amplifier_response
        detector_response_phi *= amplifier_response
    if 'filter' in filter_toggle:
        mask = freqs > 0
        b, a = scipy.signal.butter(10, filter_band, 'bandpass', analog=True)
        w, h = scipy.signal.freqs(b, a, freqs[mask])
        channel_spectrum[mask] = channel_spectrum[mask] * np.abs(h)
        detector_response_theta[mask] *= np.abs(h)
        detector_response_phi[mask] *= np.abs(h)
    channel_trace = fft.freq2time(channel_spectrum, sampling_rate)
    max_time = times[np.argmax(np.abs(channel_trace))]
    fig = plotly.subplots.make_subplots(rows=1, cols=2,
        shared_xaxes=False, shared_yaxes=False,
        vertical_spacing=0.01, subplot_titles=['Time Trace', 'Spectrum'])
    fig.append_trace(go.Scatter(
        x=times / units.ns,
        y=channel_trace / units.mV,
        name='U (t)'
    ), 1, 1)
    fig.append_trace(go.Scatter(
        x=freqs / units.MHz,
        y=np.abs(channel_spectrum) / (units.mV / units.GHz),
        name='U (f)'
    ), 1, 2)
    fig.update_xaxes(title_text='t [ns]', range=[max_time - 50.*units.ns, max_time + 50.*units.ns], row=1, col=1)
    fig.update_xaxes(title_text='f [MHz]', row=1, col=2)
    fig.update_yaxes(title_text='U [mV]', row=1, col=1)
    fig.update_yaxes(title_text='U [mV/GHz]', row=1, col=2)

    fig2 = plotly.subplots.make_subplots(rows=1, cols=2,
        shared_xaxes=False, shared_yaxes=True,
        vertical_spacing=0.01, subplot_titles=['Theta', 'Phi'])
    fig2.append_trace(go.Scatter(
        x=freqs / units.MHz,
        y=np.abs(detector_response_theta),
        name='Theta'
    ), 1, 1)
    fig2.append_trace(go.Scatter(
        x=freqs / units.MHz,
        y=np.abs(detector_response_phi),
        name='Phi'
    ), 1, 2)
    fig2.update_xaxes(title_text='f [MHz]', row=1, col=1)
    fig2.update_xaxes(title_text='f [MHz]', row=1, col=2)
    fig2.update_yaxes(title_text='VEL [m]', row=1, col=1)
    fig2.update_yaxes(title_text='VEL [m]', row=1, col=2)

    s = io.StringIO()
    np.savetxt(s, np.array([times, channel_trace]).T, delimiter=',')
    csv_string = "data:text/csv;charset=utf-8,%EF%BB%BF" + s.getvalue()

    return [fig, fig2, csv_string]
Esempio n. 17
0
def update_multi_channel_plot(evt_counter, filename, dropdown_traces,
                              dropdown_info, station_id,
                              open_template_timestamp, juser_id,
                              template_directory):
    if filename is None or station_id is None:
        return {}
    user_id = json.loads(juser_id)
    colors = plotly.colors.DEFAULT_PLOTLY_COLORS
    ariio = provider.get_arianna_io(user_id, filename)
    evt = ariio.get_event_i(evt_counter)
    station = evt.get_station(station_id)
    ymax = 0
    n_channels = 0
    plot_titles = []
    trace_start_times = []
    fig = plotly.subplots.make_subplots(rows=station.get_number_of_channels(),
                                        cols=2,
                                        shared_xaxes=True,
                                        shared_yaxes=False,
                                        vertical_spacing=0.01,
                                        subplot_titles=plot_titles)
    for i, channel in enumerate(station.iter_channels()):
        n_channels += 1
        trace = channel.get_trace() / units.mV
        ymax = max(ymax, np.max(np.abs(trace)))
        plot_titles.append('Channel {}'.format(channel.get_id()))
        plot_titles.append('Channel {}'.format(channel.get_id()))
        trace_start_times.append(channel.get_trace_start_time())
        if channel.get_trace() is not None:
            trace = channel.get_trace() / units.mV
            ymax = max(ymax, np.max(np.abs(trace)))
    if np.min(trace_start_times) > 1000. * units.ns:
        trace_start_time_offset = np.floor(
            np.min(trace_start_times) / 1000.) * 1000.
    else:
        trace_start_time_offset = 0
    channel_ids = []
    for i, channel in enumerate(station.iter_channels()):
        channel_ids.append(channel.get_id())
    channel_ids = sorted(channel_ids)
    if 'trace' in dropdown_traces:
        for i, channel_id in enumerate(channel_ids):
            channel = station.get_channel(channel_id)
            tt = channel.get_times() - trace_start_time_offset / units.ns
            if channel.get_trace() is None:
                continue
            trace = channel.get_trace() / units.mV
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=tt,
                    y=trace,
                    # text=df_by_continent['country'],
                    # mode='markers',
                    opacity=0.7,
                    marker={
                        'color': colors[i % len(colors)],
                        'line': {
                            'color': colors[i % len(colors)]
                        }
                    },
                    name=channel_id),
                i + 1,
                1)
            if 'RMS' in dropdown_info:
                fig.append_trace(
                    plotly.graph_objs.Scatter(
                        x=[0.99 * tt.max()],
                        y=[0.98 * trace.max()],
                        mode='text',
                        text=[
                            r'mu = {:.2g}, STD={:.2g}'.format(
                                np.mean(trace), np.std(trace))
                        ],
                        textposition='bottom left'), i + 1, 1)
    if 'envelope' in dropdown_traces:
        for i, channel_id in enumerate(channel_ids):
            channel = station.get_channel(channel_id)
            if channel.get_trace() is None:
                continue
            trace = channel.get_trace() / units.mV
            from scipy import signal
            yy = np.abs(signal.hilbert(trace))
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=channel.get_times() - trace_start_time_offset / units.ns,
                    y=yy,
                    # text=df_by_continent['country'],
                    # mode='markers',
                    opacity=0.7,
                    line=dict(
                        width=4, dash='dot'
                    ),  # dash options include 'dash', 'dot', and 'dashdot'
                    marker={
                        'color': colors[i % len(colors)],
                        'line': {
                            'color': colors[i % len(colors)]
                        }
                    },
                    name=i),
                i + 1,
                1)
    if 'crtemplate' in dropdown_traces:
        if 'NURADIORECOTEMPLATES' not in os.environ:  # if the environment variable is not set, we have to ask the user to specify the template location
            template_provider.set_template_directory(template_directory)
        ref_template = template_provider.get_cr_ref_template(station_id)
        if (station.has_parameter(stnp.cr_xcorrelations)):
            ref_templates = template_provider.get_set_of_cr_templates(
                station_id,
                n=station.get_parameter(
                    stnp.cr_xcorrelations)['number_of_templates'])
        for i, channel_id in enumerate(channel_ids):
            channel = station.get_channel(channel_id)
            if (channel.has_parameter(chp.cr_xcorrelations)):
                key = channel.get_parameter(
                    chp.cr_xcorrelations)['cr_ref_xcorr_template']
                ref_template = ref_templates[key][channel.get_id()]
            times = channel.get_times() - trace_start_time_offset
            trace = channel.get_trace()
            if trace is None:
                continue
            xcorr = channel.get_parameter(chp.cr_xcorrelations)['cr_ref_xcorr']
            xcorrpos = channel.get_parameter(
                chp.cr_xcorrelations)['cr_ref_xcorr_time']
            dt = times[1] - times[0]
            xcorr_max = xcorr
            if (channel.has_parameter(chp.cr_xcorrelations)):
                xcorr_max = channel.get_parameter(
                    chp.cr_xcorrelations)['cr_ref_xcorr_max']
            flip = np.sign(xcorr_max)
            tttemp = np.arange(0, len(ref_template) * dt, dt)
            yy = flip * ref_template * np.abs(trace).max()
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=tttemp[:len(trace)] / units.ns,
                    y=yy[:len(trace)] / units.mV,
                    opacity=0.7,
                    line=dict(
                        width=2, dash='dot'
                    ),  # dash options include 'dash', 'dot', and 'dashdot'
                    marker={
                        'color': colors[i % len(colors)],
                        'line': {
                            'color': colors[i % len(colors)]
                        }
                    },
                    name=i),
                i + 1,
                1)
            template_spectrum = fft.time2freq(yy, channel.get_sampling_rate())
            template_freqs = np.fft.rfftfreq(len(yy), dt)
            template_freq_mask = (template_freqs > channel.get_frequencies()[0]
                                  ) & (template_freqs <
                                       (channel.get_frequencies()[-1]))
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=template_freqs[template_freq_mask] / units.MHz,
                    y=np.abs(template_spectrum)[template_freq_mask] / units.mV,
                    line=dict(width=2, dash='dot'),
                    name=i), i + 1, 2)
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=[0.9 * times.max() / units.ns],
                    y=[0.8 * trace.max() / units.mV],
                    mode='text',
                    text=[
                        'cr xcorr= {:.2f}, {:.02f}'.format(xcorr, xcorr_max)
                    ],
                    textposition='top center'), i + 1, 1)
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=[0.9 * times.max() / units.ns],
                    y=[0.5 * trace.max() / units.mV],
                    mode='text',
                    text=['t = {:.0f}ns'.format(xcorrpos)],
                    textposition='top center'), i + 1, 1)
    if 'nutemplate' in dropdown_traces:
        if 'NURADIORECOTEMPLATES' not in os.environ:  # if the environment variable is not set, we have to ask the user to specify the template location
            template_provider.set_template_directory(template_directory)
        ref_template = template_provider.get_nu_ref_template(station_id)
        for i, channel_id in enumerate(channel_ids):
            channel = station.get_channel(channel_id)
            times = channel.get_times()
            trace = channel.get_trace()
            if trace is None:
                continue
            xcorr = channel.get_parameter(chp.nu_xcorrelations)['nu_ref_xcorr']
            xcorrpos = channel.get_parameter(
                chp.nu_xcorrelations)['nu_ref_xcorr_time']
            dt = times[1] - times[0]
            flip = np.sign(xcorr)
            tttemp = np.arange(0, len(ref_template) * dt, dt)
            yy = flip * ref_template * np.abs(trace).max()
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=tttemp[:len(trace)] / units.ns,
                    y=yy[:len(trace)] / units.mV,
                    # text=df_by_continent['country'],
                    # mode='markers',
                    opacity=0.7,
                    line=dict(
                        width=4, dash='dot'
                    ),  # dash options include 'dash', 'dot', and 'dashdot'
                    marker={
                        'color': colors[i % len(colors)],
                        'line': {
                            'color': colors[i % len(colors)]
                        }
                    },
                    name=i),
                i + 1,
                1)
            template_spectrum = fft.time2freq(yy, channel.get_sampling_rate())
            template_freqs = np.fft.rfftfreq(len(yy), dt)
            template_freq_mask = (template_freqs > channel.get_frequencies()[0]
                                  ) & (template_freqs <
                                       (channel.get_frequencies()[-1]))
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=template_freqs[template_freq_mask] / units.MHz,
                    y=np.abs(template_spectrum)[template_freq_mask] / units.mV,
                    line=dict(width=2, dash='dot'),
                    name=i), i + 1, 2)

            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=[0.9 * times.max() / units.ns],
                    y=[0.8 * trace.max() / units.mV],
                    mode='text',
                    text=['nu xcorr= {:.2f}'.format(xcorr)],
                    textposition='top center'), i + 1, 1)
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=[0.9 * times.max() / units.ns],
                    y=[0.5 * trace.max() / units.mV],
                    mode='text',
                    text=['t = {:.0f}ns'.format(xcorrpos)],
                    textposition='top center'), i + 1, 1)
    """
    if 'recefield' in dropdown_traces or 'simefield' in dropdown_traces:
        det.update(station.get_station_time())
    if 'recefield' in dropdown_traces:
        channel_ids = []
        for channel in station.iter_channels():
            channel_ids.append(channel.get_id())
        for electric_field in station.get_electric_fields():
            for i_trace, trace in enumerate(
                    trace_utilities.get_channel_voltage_from_efield(station, electric_field, channel_ids, det,
                                                                    station.get_parameter(stnp.zenith),
                                                                    station.get_parameter(stnp.azimuth),
                                                                    antenna_pattern_provider)):
                direction_time_delay = geometryUtilities.get_time_delay_from_direction(
                    station.get_parameter(stnp.zenith), station.get_parameter(stnp.azimuth),
                    det.get_relative_position(station.get_id(), channel_ids[i_trace]) - electric_field.get_position())
                time_shift = direction_time_delay - trace_start_time_offset
                plotly.graph_objs.append_trace(plotly.graph_objs.Scatter(
                    x=(electric_field.get_times() + time_shift) / units.ns,
                    y=fft.freq2time(trace, electric_field.get_sampling_rate()) / units.mV,
                    line=dict(
                        dash='solid',
                        color=colors[i_trace % len(colors)]
                    ),
                    opacity=.5
                ), i_trace + 1, 1)
                plotly.graph_objs.append_trace(plotly.graph_objs.Scatter(
                    x=electric_field.get_frequencies() / units.MHz,
                    y=np.abs(trace) / units.mV,
                    line=dict(
                        dash='solid',
                        color=colors[i_trace % len(colors)]
                    ),
                    opacity=.5
                ), i_trace + 1, 2)
    if 'simefield' in dropdown_traces:
        sim_station = station.get_sim_station()
        for i_channel, channel in enumerate(station.iter_channels()):
            for electric_field in sim_station.get_electric_fields_for_channels([channel.get_id()]):
                trace = \
                    trace_utilities.get_channel_voltage_from_efield(sim_station, electric_field, [channel.get_id()],
                                                                    det,
                                                                    electric_field.get_parameter(efp.zenith),
                                                                    electric_field.get_parameter(efp.azimuth),
                                                                    antenna_pattern_provider)[0]
                channel = station.get_channel(channel.get_id())
                if station.is_cosmic_ray():
                    direction_time_delay = geometryUtilities.get_time_delay_from_direction(
                        sim_station.get_parameter(stnp.zenith), sim_station.get_parameter(stnp.azimuth),
                        det.get_relative_position(sim_station.get_id(),
                                                  channel.get_id()) - electric_field.get_position())
                    time_shift = direction_time_delay - trace_start_time_offset
                else:
                    time_shift = - trace_start_time_offset
                fig.append_trace(plotly.graph_objs.Scatter(
                    x=(electric_field.get_times() + time_shift) / units.ns,
                    y=fft.freq2time(trace, electric_field.get_sampling_rate()) / units.mV,
                    line=dict(
                        dash='solid',
                        color=colors[i_channel % len(colors)]
                    ),
                    opacity=.5
                ), i_channel + 1, 1)
                fig.append_trace(plotly.graph_objs.Scatter(
                    x=electric_field.get_frequencies() / units.MHz,
                    y=np.abs(trace) / units.mV,
                    line=dict(
                        dash='solid',
                        color=colors[i_channel % len(colors)]
                    ),
                    opacity=.5
                ), i_channel + 1, 2)
    """
    for i, channel_id in enumerate(channel_ids):
        channel = station.get_channel(channel_id)
        fig['layout']['yaxis{:d}'.format(i * 2 +
                                         1)].update(range=[-ymax, ymax])
        fig['layout']['yaxis{:d}'.format(i * 2 +
                                         1)].update(title='voltage [mV]')

        if channel.get_trace() is None:
            continue
        spec = channel.get_frequency_spectrum()
        ff = channel.get_frequencies()
        fig.append_trace(
            plotly.graph_objs.Scatter(x=ff / units.MHz,
                                      y=np.abs(spec) / units.mV,
                                      opacity=0.7,
                                      marker={
                                          'color': colors[i % len(colors)],
                                          'line': {
                                              'color': colors[i % len(colors)]
                                          }
                                      },
                                      name=i), i + 1, 2)
        if 'L1' in dropdown_info:
            fig.append_trace(
                plotly.graph_objs.Scatter(
                    x=[0.9 * ff.max() / units.MHz],
                    y=[0.8 * np.abs(spec).max() / units.mV],
                    mode='text',
                    text=['max L1 = {:.2f}'.format(get_L1(spec))],
                    textposition='top center'), i + 1, 2)
    if trace_start_time_offset > 0:
        fig['layout']['xaxis1'].update(
            title='time [ns] - {:.0f}ns'.format(trace_start_time_offset))
    else:
        fig['layout']['xaxis1'].update(title='time [ns]')
    fig['layout']['xaxis2'].update(title='frequency [MHz]')
    fig['layout'].update(height=n_channels * 150)
    fig['layout'].update(showlegend=False)
    return fig