def distort_waveform(self, waveform, length_samples: int=None):
        if length_samples is not None:
            extra_samples = length_samples - len(waveform)
            if extra_samples >= 0:
                y_sig = np.concatenate([waveform, np.zeros(extra_samples)])
            else:
                y_sig = waveform[:extra_samples]
        else:
            y_sig = waveform
        for filt_id in range(self._num_models):
            filt = self.get('filter_model_{:02}'.format(filt_id))

            if not filt:
                pass  # dict is empty
            else:
                model = filt['model']
                if not self.cfg_hardware_friendly():
                    if model == 'high-pass':
                        y_sig = kf.bias_tee_correction(
                            y_sig, sampling_rate=self.cfg_sampling_rate(),
                            **filt['params'])
                    elif model == 'exponential':
                        y_sig = kf.exponential_decay_correction(
                            y_sig, sampling_rate=self.cfg_sampling_rate(),
                            **filt['params'])
                    elif model == 'bounce':
                        y_sig = kf.bounce_correction(
                            y_sig, sampling_rate=self.cfg_sampling_rate(),
                            **filt['params'])
                    else:
                        raise KeyError('Model {} not recognized'.format(model))
                else:
                    raise NotImplementedError()
        y_sig *= self.cfg_gain_correction()
        return y_sig
    def fit_high_pass(self, start_time_fit, end_time_fit):
        '''
        Fits a model for a simple RC high-pass
            exp(-t/tau), tau = RC
        to the last trace that was measured (self.waveform).
        The fit model and result are saved in self.fit_model and self.fit_res,
        respectively. The new predistortion kernel and information about the
        fit is stored in self.new_kernel_dict.

        Args:
            start_time_fit (float): start of the fitted interval
            end_time_fit (float):   end of the fitted interval
        '''
        self._start_idx = np.argmin(np.abs(self.time_pts - start_time_fit))
        self._stop_idx = np.argmin(np.abs(self.time_pts - end_time_fit))

        # Prepare the fit model: exponential, where only tau is varied
        self.fit_model = lmfit.Model(fm.ExpDecayFunc)
        self.fit_model.set_param_hint('tau',
                                      value=end_time_fit - start_time_fit,
                                      vary=True)
        self.fit_model.set_param_hint('offset', value=0, vary=False)
        self.fit_model.set_param_hint('amplitude', value=1, vary=True)
        self.fit_model.set_param_hint('n', value=1, vary=False)
        params = self.fit_model.make_params()

        # Do the fit
        fit_res = self.fit_model.fit(
            data=self.waveform[self._start_idx:self._stop_idx],
            t=self.time_pts[self._start_idx:self._stop_idx],
            params=params)
        self.fitted_waveform = fit_res.eval(
            t=self.time_pts[self._start_idx:self._stop_idx])

        tau = fit_res.best_values['tau']

        # Check if parameters are physical and print warnings if not
        if tau < 0:
            print('Warning: unphysical tau = {} (expect tau > 0).'.format(tau))
        # Save the fit results and predicted correction
        self.fit_res = fit_res

        self.predicted_waveform = kf.bias_tee_correction(
            self.waveform, tau=tau, sampling_rate=self.scope_sampling_rate)
    def _update_latest_params(self, json_string):
        """
        Uses a JSON formatted string to update the parameters of the
        latest fit.

        For each model does the following
            1. update the 'fit' dict
            4. calculate the new "fit"
            5. Plot the new "fit"

        Currently only supported for the high-pass and exponential model.
        """
        try:
            par_dict = json.loads(json_string)
        except Exception as e:
            print(e)
            return

        # 1. update the 'fit' dict
        self.fit_res.best_values.update(par_dict)
        self.fitted_waveform = self.fit_res.eval(
            t=self.time_pts[self._start_idx:self._stop_idx],
            tau=self.fit_res.best_values['tau'])

        if self._fit_model_loop == 'high-pass':
            self.predicted_waveform = kf.bias_tee_correction(
                self.waveform,
                tau=self.fit_res.best_values['tau'],
                sampling_rate=self.scope_sampling_rate)

        elif self._fit_model_loop == 'exponential':
            self.predicted_waveform = kf.exponential_decay_correction(
                self.waveform,
                tau=self.fit_res.best_values['tau'],
                amp=self.fit_res.best_values['amp'],
                sampling_rate=self.scope_sampling_rate)

        # The fit results still have to be updated
        self.plot_fit(self._t_start_loop,
                      self._t_stop_loop,
                      nr_plot_pts=self.cfg_nr_plot_points())
    def distort_waveform(self,
                         waveform,
                         length_samples: int = None,
                         inverse: bool = False):
        """
        Distorts a waveform using the models specified in the Kernel Object.
        Args:
            waveform (array)    : waveform to be distorted
            lenght_samples (int): number of samples after which to cut of wf
            inverse (bool)      : if True apply the inverse of the waveform.

        Returns:
            y_sig (array)       : waveform with distortion filters applied

        N.B. the bounce correction does not have an inverse implemented
            (June 2018) MAR
        """
        if length_samples is not None:
            extra_samples = length_samples - len(waveform)
            if extra_samples >= 0:
                y_sig = np.concatenate([waveform, np.zeros(extra_samples)])
            else:
                y_sig = waveform[:extra_samples]
        else:
            y_sig = waveform

        # Specific real-time filters are turned on below
        self.set_realtime_distortions_zero()
        nr_real_time_exp_models = 0
        nr_real_time_hp_models = 0
        nr_real_time_bounce_models = 0
        for filt_id in range(self._num_models):
            filt = self.get('filter_model_{:02}'.format(filt_id))

            if not filt:
                pass  # dict is empty
            else:
                model = filt['model']
                if model == 'high-pass':
                    if ('real-time' in filt.keys() and filt['real-time']):
                        # Implementation tested and found not working -MAR
                        raise NotImplementedError()
                        nr_real_time_hp_models += 1
                        if nr_real_time_hp_models > 1:
                            raise ValueError()
                    else:
                        y_sig = kf.bias_tee_correction(
                            y_sig,
                            sampling_rate=self.cfg_sampling_rate(),
                            inverse=inverse,
                            **filt['params'])
                elif model == 'exponential':
                    if ('real-time' in filt.keys() and filt['real-time']):
                        AWG = self.instr_AWG.get_instr()

                        AWG.set(
                            'sigouts_{}_precompensation_exponentials'
                            '_{}_timeconstant'.format(
                                self.cfg_awg_channel() - 1,
                                nr_real_time_exp_models),
                            filt['params']['tau'])
                        AWG.set(
                            'sigouts_{}_precompensation_exponentials'
                            '_{}_amplitude'.format(self.cfg_awg_channel() - 1,
                                                   nr_real_time_exp_models),
                            filt['params']['amp'])
                        AWG.set(
                            'sigouts_{}_precompensation_exponentials'
                            '_{}_enable'.format(self.cfg_awg_channel() - 1,
                                                nr_real_time_exp_models), 1)

                        nr_real_time_exp_models += 1
                        if nr_real_time_exp_models > 5:
                            raise ValueError()
                    else:
                        y_sig = kf.exponential_decay_correction(
                            y_sig,
                            sampling_rate=self.cfg_sampling_rate(),
                            inverse=inverse,
                            **filt['params'])
                elif model == 'bounce':
                    if ('real-time' in filt.keys() and filt['real-time']):
                        AWG = self.instr_AWG.get_instr()

                        AWG.set(
                            'sigouts_{}_precompensation_bounces'
                            '_{}_delay'.format(self.cfg_awg_channel() - 1,
                                               nr_real_time_bounce_models),
                            filt['params']['tau'])
                        AWG.set(
                            'sigouts_{}_precompensation_bounces'
                            '_{}_amplitude'.format(self.cfg_awg_channel() - 1,
                                                   nr_real_time_bounce_models),
                            filt['params']['amp'])
                        AWG.set(
                            'sigouts_{}_precompensation_bounces'
                            '_{}_enable'.format(self.cfg_awg_channel() - 1,
                                                nr_real_time_bounce_models), 1)

                        nr_real_time_bounce_models += 1
                        if nr_real_time_bounce_models > 1:
                            raise ValueError()
                    else:
                        y_sig = kf.first_order_bounce_corr(
                            sig=y_sig,
                            delay=filt['params']['tau'],
                            amp=filt['params']['amp'],
                            awg_sample_rate=2.4e9)

                else:
                    raise KeyError('Model {} not recognized'.format(model))

        if inverse:
            y_sig /= self.cfg_gain_correction()
        else:
            y_sig *= self.cfg_gain_correction()
        return y_sig
    def distort_waveform(self,
                         waveform,
                         length_samples: int = None,
                         inverse: bool = False):
        """
        Distorts a waveform using the models specified in the Kernel Object.

        Args:
            waveform (array)    : waveform to be distorted
            lenght_samples (int): number of samples after which to cut of wf
            inverse (bool)      : if True apply the inverse of the waveform.

        Return:
            y_sig (array)       : waveform with distortion filters applied

        N.B. The bounce correction does not have an inverse implemented
            (June 2018) MAR
        N.B.2 The real-time FIR also does not have an inverse implemented.
            (May 2019) MAR
        N.B.3 the real-time distortions are reset and set on the HDAWG every
            time a waveform is distorted. This is a suboptimal workflow.

        """
        if length_samples is not None:
            extra_samples = length_samples - len(waveform)
            if extra_samples >= 0:
                y_sig = np.concatenate([waveform, np.zeros(extra_samples)])
            else:
                y_sig = waveform[:extra_samples]
        else:
            y_sig = waveform

        # Specific real-time filters are turned on below
        self.set_unused_realtime_distortions_zero()
        nr_real_time_exp_models = 0
        nr_real_time_hp_models = 0
        nr_real_time_bounce_models = 0
        for filt_id in range(self._num_models):
            filt = self.get("filter_model_{:02}".format(filt_id))

            if not filt:
                pass  # dict is empty
            else:
                model = filt["model"]
                AWG = self.instr_AWG.get_instr()
                if model == "high-pass":
                    if "real-time" in filt.keys() and filt["real-time"]:
                        # Implementation tested and found not working -MAR
                        raise NotImplementedError()
                        nr_real_time_hp_models += 1
                        if nr_real_time_hp_models > 1:
                            raise ValueError()
                    else:
                        y_sig = kf.bias_tee_correction(
                            y_sig,
                            sampling_rate=self.cfg_sampling_rate(),
                            inverse=inverse,
                            **filt["params"])
                elif model == "exponential":
                    if "real-time" in filt.keys() and filt["real-time"]:

                        AWG.set(
                            "sigouts_{}_precompensation_exponentials"
                            "_{}_timeconstant".format(
                                self.cfg_awg_channel() - 1,
                                nr_real_time_exp_models),
                            filt["params"]["tau"],
                        )
                        AWG.set(
                            "sigouts_{}_precompensation_exponentials"
                            "_{}_amplitude".format(self.cfg_awg_channel() - 1,
                                                   nr_real_time_exp_models),
                            filt["params"]["amp"],
                        )
                        AWG.set(
                            "sigouts_{}_precompensation_exponentials"
                            "_{}_enable".format(self.cfg_awg_channel() - 1,
                                                nr_real_time_exp_models),
                            1,
                        )

                        nr_real_time_exp_models += 1
                        if nr_real_time_exp_models > 5:
                            raise ValueError()
                    else:
                        y_sig = kf.exponential_decay_correction(
                            y_sig,
                            sampling_rate=self.cfg_sampling_rate(),
                            inverse=inverse,
                            **filt["params"])
                elif model == "bounce":
                    if "real-time" in filt.keys() and filt["real-time"]:

                        AWG.set(
                            "sigouts_{}_precompensation_bounces"
                            "_{}_delay".format(self.cfg_awg_channel() - 1,
                                               nr_real_time_bounce_models),
                            filt["params"]["tau"],
                        )
                        AWG.set(
                            "sigouts_{}_precompensation_bounces"
                            "_{}_amplitude".format(self.cfg_awg_channel() - 1,
                                                   nr_real_time_bounce_models),
                            filt["params"]["amp"],
                        )
                        AWG.set(
                            "sigouts_{}_precompensation_bounces"
                            "_{}_enable".format(self.cfg_awg_channel() - 1,
                                                nr_real_time_bounce_models),
                            1,
                        )

                        nr_real_time_bounce_models += 1
                        if nr_real_time_bounce_models > 1:
                            raise ValueError()
                    else:
                        y_sig = kf.first_order_bounce_corr(
                            sig=y_sig,
                            delay=filt["params"]["tau"],
                            amp=filt["params"]["amp"],
                            awg_sample_rate=2.4e9,
                        )

                elif model == "FIR":
                    fir_filter_coeffs = filt["params"]["weights"]
                    if "real-time" in filt.keys() and filt["real-time"]:
                        if len(fir_filter_coeffs) != 40:
                            raise ValueError(
                                "Realtime FIR filter must contain 40 weights")
                        else:
                            AWG.set(
                                "sigouts_{}_precompensation_fir_coefficients".
                                format(self.cfg_awg_channel() - 1),
                                fir_filter_coeffs,
                            )
                            AWG.set(
                                "sigouts_{}_precompensation_fir_enable".format(
                                    self.cfg_awg_channel() - 1),
                                1,
                            )
                    else:
                        if not inverse:
                            y_sig = signal.lfilter(fir_filter_coeffs, 1, y_sig)
                        elif inverse:
                            y_sig = signal.lfilter(np.ones(1),
                                                   fir_filter_coeffs, y_sig)

                else:
                    raise KeyError("Model {} not recognized".format(model))

        if inverse:
            y_sig /= self.cfg_gain_correction()
        else:
            y_sig *= self.cfg_gain_correction()
        return y_sig