Find a number of consecutive beats and use them to initialize:
        - recent qrs amplitude
        - recent noise amplitude
        - recent rr interval
        - qrs detection threshold

        The learning works as follows:
        - Find all local maxima (largest sample within `qrs_radius`
          samples) of the filtered signal.
        - Inspect the local maxima until `n_calib_beats` beats are
          found:
          - Calculate the cross-correlation between a ricker wavelet of
            length `qrs_width`, and the filtered signal segment centered
            around the local maximum.
          - If the cross-correlation exceeds 0.6, classify it as a beat.
        - Use the beats to initialize the previously described
          parameters.
        - If the system fails to find enough beats, the default
          parameters will be used instead. See the docstring of
          `XQRS._set_default_init_params` for detauls.

        Parameters
        ----------
        n_calib_beats : int, optional
            Number of calibration beats to detect for learning

def _learn_init_params(self, n_calib_beats=8):
        """
        Find a number of consecutive beats and use them to initialize:
        - recent qrs amplitude
        - recent noise amplitude
        - recent rr interval
        - qrs detection threshold

        The learning works as follows:
        - Find all local maxima (largest sample within `qrs_radius`
          samples) of the filtered signal.
        - Inspect the local maxima until `n_calib_beats` beats are
          found:
          - Calculate the cross-correlation between a ricker wavelet of
            length `qrs_width`, and the filtered signal segment centered
            around the local maximum.
          - If the cross-correlation exceeds 0.6, classify it as a beat.
        - Use the beats to initialize the previously described
          parameters.
        - If the system fails to find enough beats, the default
          parameters will be used instead. See the docstring of
          `XQRS._set_default_init_params` for detauls.

        Parameters
        ----------
        n_calib_beats : int, optional
            Number of calibration beats to detect for learning


        """
        if self.verbose:
            print('Learning initial signal parameters...')

        last_qrs_ind = -self.rr_max
        qrs_inds = []
        qrs_amps = []
        noise_amps = []

        ricker_wavelet = signal.ricker(self.qrs_radius * 2, 4).reshape(-1,1)

        # Find the local peaks of the signal.
        peak_inds_f = find_local_peaks(self.sig_f, self.qrs_radius)

        # Peak numbers at least qrs_width away from signal boundaries
        peak_nums_r = np.where(peak_inds_f > self.qrs_width)[0]
        peak_nums_l = np.where(peak_inds_f <= self.sig_len - self.qrs_width)[0]

        # Skip if no peaks in range
        if (not peak_inds_f.size or not peak_nums_r.size
                                 or not peak_nums_l.size):
            if self.verbose:
                print('Failed to find %d beats during learning.'
                      % n_calib_beats)
            self._set_default_init_params()
            return

        # Go through the peaks and find qrs peaks and noise peaks.
        # only inspect peaks with at least qrs_radius around either side
        for peak_num in range(peak_nums_r[0], peak_nums_l[-1]):
            i = peak_inds_f[peak_num]
            # Calculate cross-correlation between the filtered signal
            # segment and a ricker wavelet

            # Question: should the signal be squared? Case for inverse qrs
            # complexes
            sig_segment = normalize((self.sig_f[i - self.qrs_radius:
                                                i + self.qrs_radius]).reshape(-1, 1), axis=0)

            xcorr = np.correlate(sig_segment[:, 0], ricker_wavelet[:,0])

            # Classify as qrs if xcorr is large enough
            if xcorr > 0.6 and i-last_qrs_ind > self.rr_min:
                last_qrs_ind = i
                qrs_inds.append(i)
                qrs_amps.append(self.sig_i[i])
            else:
                noise_amps.append(self.sig_i[i])

            if len(qrs_inds) == n_calib_beats:
                break

        # Found enough calibration beats to initialize parameters
        if len(qrs_inds) == n_calib_beats:

            if self.verbose:
                print('Found %d beats during learning.' % n_calib_beats
                      + ' Initializing using learned parameters')

            # QRS amplitude is most important.
            qrs_amp = np.mean(qrs_amps)

            # Set noise amplitude if found
            if noise_amps:
                noise_amp = np.mean(noise_amps)
            else:
                # Set default of 1/10 of qrs amplitude
                noise_amp = qrs_amp / 10

            # Get rr intervals of consecutive beats, if any.
            rr_intervals = np.diff(qrs_inds)
            rr_intervals = rr_intervals[rr_intervals < self.rr_max]
            if rr_intervals.any():
                rr_recent = np.mean(rr_intervals)
            else:
                rr_recent = self.rr_init

            # If an early qrs was detected, set last_qrs_ind so that it can be
            # picked up.
            last_qrs_ind = min(0, qrs_inds[0] - self.rr_min - 1)

            self._set_init_params(qrs_amp_recent=qrs_amp,
                                  noise_amp_recent=noise_amp,
                                  rr_recent=rr_recent,
                                  last_qrs_ind=last_qrs_ind)
            self.learned_init_params = True

        # Failed to find enough calibration beats. Use default values.
        else:
            if self.verbose:
                print('Failed to find %d beats during learning.'
                      % n_calib_beats)

            self._set_default_init_params()