diff --git a/README.md b/README.md
index 0be201ee70b9c7e06f187461093b1dd72d92ed48..7d6778965c5e54d703e157525fd9133134184e00 100644
--- a/README.md
+++ b/README.md
@@ -10,13 +10,14 @@
 - [at_time_file](wetb/hawc2/at_time_file.py): read at output_at_time files
 - [ascii2bin](wetb/hawc2/ascii2bin): Compress HAWC2 ascii result files to binary
 
-
-
 ### [gtsdf](wetb/gtsdf)
 General Time Series Data Format, a binary hdf5 data format for storing time series data.
 - [gtsdf](wetb/gtsdf/gtsdf.py): read/write/append gtsdf files
 - [unix_time](wetb/gtsdf/unix_time.py): convert between datetime and unix time (seconds since 1/1/1970)
 
+### [fatigue_tools](wetb/fatigue_tools)
+- [fatigue](wetb/fatigue_tools/fatigue.py): Rainflow counting, cycle matrix and equvivalent loads
+
 ### [FAST](wetb/fast)
 Tools for working with NREL's FAST code (An aeroelastic computer-aided engineering (CAE) tool for horizontal axis wind turbines)
 - [fast_io](wetb/fast/fast_io.py): Read binary and ascii result files
diff --git a/wetb/fatigue_tools/__init__.py b/wetb/fatigue_tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/wetb/fatigue_tools/fatigue.py b/wetb/fatigue_tools/fatigue.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0b57e70deb6d692018ebd74d4b5af7a346758c6
--- /dev/null
+++ b/wetb/fatigue_tools/fatigue.py
@@ -0,0 +1,350 @@
+'''
+Created on 04/03/2013
+@author: mmpe
+'''
+
+#try:
+#    """
+#    The cython_import function compiles modules using cython.
+#    It is found at: https://github.com/madsmpedersen/MMPE/blob/master/cython_compile/cython_compile.py
+#    """
+#    from mmpe.cython_compile.cython_compile import cython_import
+#except ImportError:
+#    cython_import = __import__
+import numpy as np
+
+
+def rfc_hist(sig_rf, nrbins=46):
+    """
+    Histogram of rainflow counted cycles
+    ====================================
+
+    hist, bin_edges, bin_avg = rfc_hist(sig, nrbins=46)
+
+    Divide the rainflow counted cycles of a signal into equally spaced bins.
+
+    Created on Wed Feb 16 16:53:18 2011
+    @author: David Verelst
+    Modified 10.10.2011 by Mads M Pedersen to elimintate __copy__ and __eq__
+
+    Parameters
+    ----------
+    sig_rf : array-like
+        As output by rfc_astm or rainflow
+
+    nrbins : int, optional
+        Divide the rainflow counted amplitudes in a number of equally spaced
+        bins.
+
+    Returns
+    -------
+    hist : array-like
+        Counted rainflow cycles per bin, has nrbins elements
+
+    bin_edges : array-like
+        Edges of the bins, has nrbins+1 elements.
+
+    bin_avg : array-like
+        Average rainflow cycle amplitude per bin, has nrbins elements.
+    """
+
+    rf_half = sig_rf
+
+    # the Matlab approach is to divide into 46 bins
+    bin_edges = np.linspace(0, 1, num=nrbins + 1) * rf_half.max()
+    hist = np.histogram(rf_half, bins=bin_edges)[0]
+    # calculate the average per bin
+    hist_sum = np.histogram(rf_half, weights=rf_half, bins=bin_edges)[0]
+    # replace zeros with one, to avoid 0/0
+    hist_ = hist.copy()
+    hist_[(hist == 0).nonzero()] = 1.0
+    # since the sum is also 0, the avg remains zero for those whos hist is zero
+    bin_avg = hist_sum / hist_
+
+    return hist, bin_edges, bin_avg
+
+
+def check_signal(signal):
+    # check input data validity
+    if not type(signal).__name__ == 'ndarray':
+        raise TypeError('signal must be ndarray, not: ' + type(signal).__name__)
+
+    elif len(signal.shape) not in (1, 2):
+        raise TypeError('signal must be 1D or 2D, not: ' + str(len(signal.shape)))
+
+    if len(signal.shape) == 2:
+        if signal.shape[1] > 1:
+            raise TypeError('signal must have one column only, not: ' + str(signal.shape[1]))
+    if np.min(signal) == np.max(signal):
+        raise TypeError("Signal contains no variation")
+
+
+def rainflow_windap(signal, levels=255., thresshold=(255 / 50)):
+    """Windap equivalent rainflow counting
+
+
+    Calculate the amplitude and mean values of half cycles in signal
+
+    This algorithms used by this routine is implemented directly as described in
+    "Recommended Practices for Wind Turbine Testing - 3. Fatigue Loads", 2. edition 1990, Appendix A
+
+    Parameters
+    ----------
+    Signal : array-like
+        The raw signal
+
+    levels : int, optional
+        The signal is discretize into this number of levels.
+        255 is equivalent to the implementation in Windap
+
+    thresshold : int, optional
+        Cycles smaller than this thresshold are ignored
+        255/50 is equivalent to the implementation in Windap
+
+    Returns
+    -------
+    ampl : array-like
+        Peak to peak amplitudes of the half cycles
+
+    mean : array-like
+        Mean values of the half cycles
+
+
+    Example
+    -------
+    >>> signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
+    >>> ampl, mean = rainflow_windap(signal)
+    """
+    check_signal(signal)
+    #type <double> is required by <find_extreme> and <rainflow>
+    signal = signal.astype(np.double)
+
+    offset = np.nanmin(signal)
+    signal -= offset
+    if np.nanmax(signal) > 0:
+        gain = np.nanmax(signal) / levels
+        signal = signal / gain
+        signal = np.round(signal).astype(np.int)
+
+        from wetb.fatigue_tools.peak_trough import peak_trough
+
+
+        #Convert to list of local minima/maxima where difference > thresshold
+        sig_ext = peak_trough(signal, thresshold)
+
+        from wetb.fatigue_tools.pair_range import pair_range_amplitude_mean
+
+        #rainflow count
+        ampl_mean = pair_range_amplitude_mean(sig_ext)
+
+        ampl_mean = np.array(ampl_mean)
+        ampl_mean = np.round(ampl_mean / thresshold) * gain * thresshold
+        ampl_mean[:, 1] += offset
+        return ampl_mean.T
+
+
+
+def rainflow_astm(signal):
+    """Matlab equivalent rainflow counting
+
+    Calculate the amplitude and mean values of half cycles in signal
+
+    This implemementation is based on the c-implementation by Adam Nieslony found at
+    the MATLAB Central File Exchange http://www.mathworks.com/matlabcentral/fileexchange/3026
+
+    Parameters
+    ----------
+    Signal : array-like
+        The raw signal
+
+    Returns
+    -------
+    ampl : array-like
+        peak to peak amplitudes of the half cycles (note that the matlab implementation
+        uses peak amplitude instead of peak to peak)
+
+    mean : array-like
+        Mean values of the half cycles
+
+
+    Examples
+    --------
+    >>> signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
+    >>> ampl, mean = rainflow_astm(signal)
+    """
+    check_signal(signal)
+
+    # type <double> is reuqired by <find_extreme> and <rainflow>
+    signal = signal.astype(np.double)
+
+    # Import find extremes and rainflow.
+    # If possible the module is compiled using cython otherwise the python implementation is used
+    #cython_import('fatigue_tools.rainflowcount_astm')
+    from wetb.fatigue_tools.rainflowcount_astm import find_extremes, rainflowcount
+
+    # Remove points which is not local minimum/maximum
+    sig_ext = find_extremes(signal)
+
+    # rainflow count
+    ampl_mean = np.array(rainflowcount(sig_ext))
+
+    return np.array(ampl_mean).T
+
+
+def eq_load(signals, no_bins=46, m=[3, 4, 6, 8, 10, 12], neq=1, rainflow_func=rainflow_windap):
+    """Equivalent load calculation
+
+    Calculate the equivalent loads for a list of Wohler exponent and number of equivalent loads
+
+    Parameters
+    ----------
+    signals : list of tuples or array_like
+        - if list of tuples: list must have format [(sig1_weight, sig1),(sig2_weight, sig1),...] where\n
+            - sigx_weight is the weight of signal x\n
+            - sigx is signal x\n
+        - if array_like: The signal
+    no_bins : int, optional
+        Number of bins in rainflow count histogram
+    m : int, float or array-like, optional
+        Wohler exponent (default is [3, 4, 6, 8, 10, 12])
+    neq : int, float or array-like, optional
+        The equivalent number of load cycles (default is 1, but normally the time duration in seconds is used)
+    rainflow_func : {rainflow_windap, rainflow_astm}, optional
+        The rainflow counting function to use (default is rainflow_windap)
+
+    Returns
+    -------
+    eq_loads : array-like
+        List of lists of equivalent loads for the corresponding equivalent number(s) and Wohler exponents
+
+    Examples
+    --------
+    >>> signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
+    >>> eq_load(signal, no_bins=50, neq=[1, 17], m=[3, 4, 6], rainflow_func=rainflow_windap)
+    [[10.311095426959747, 9.5942535021382174, 9.0789213365013932], # neq = 1, m=[3,4,6]
+    [4.010099657859783, 4.7249689509841746, 5.6618639965313005]], # neq = 17, m=[3,4,6]
+
+    eq_load([(.4, signal), (.6, signal)], no_bins=50, neq=[1, 17], m=[3, 4, 6], rainflow_func=rainflow_windap)
+    [[10.311095426959747, 9.5942535021382174, 9.0789213365013932], # neq = 1, m=[3,4,6]
+    [4.010099657859783, 4.7249689509841746, 5.6618639965313005]], # neq = 17, m=[3,4,6]
+    """
+    try:
+        return eq_load_and_cycles(signals, no_bins, m, neq, rainflow_func)[0]
+    except TypeError:
+        return [[np.nan] * len(np.atleast_1d(m))] * len(np.atleast_1d(neq))
+
+#    ampl, _ = rainflow_func(signals)
+#    if ampl is None:
+#        return []
+#    hist_data, x, bin_avg = rfc_hist(ampl, no_bins)
+#
+#    m = np.atleast_1d(m)
+#
+#    return np.array([np.power(np.sum(0.5 * hist_data * np.power(bin_avg, m[i])) / neq, 1. / m[i]) for i in range(len(m))])
+
+
+def eq_load_and_cycles(signals, no_bins=46, m=[3, 4, 6, 8, 10, 12], neq=[10 ** 6, 10 ** 7, 10 ** 8], rainflow_func=rainflow_windap):
+    """Calculate combined fatigue equivalent load
+
+    Parameters
+    ----------
+    signals : list of tuples or array_like
+        - if list of tuples: list must have format [(sig1_weight, sig1),(sig2_weight, sig1),...] where\n
+            - sigx_weight is the weight of signal x\n
+            - sigx is signal x\n
+        - if array_like: The signal
+    no_bins : int, optional
+        Number of bins for rainflow counting
+    m : int, float or array-like, optional
+        Wohler exponent (default is [3, 4, 6, 8, 10, 12])
+    neq : int or array-like, optional
+        Equivalent number, default is [10^6, 10^7, 10^8]
+    rainflow_func : {rainflow_windap, rainflow_astm}, optional
+        The rainflow counting function to use (default is rainflow_windap)
+
+    Returns
+    -------
+    eq_loads : array-like
+        List of lists of equivalent loads for the corresponding equivalent number(s) and Wohler exponents
+    cycles : array_like
+        2d array with shape = (no_ampl_bins, no_mean_bins)
+    ampl_bin_mean : array_like
+        mean amplitude of the bins
+    ampl_bin_edges
+        Edges of the amplitude bins
+    """
+    cycles, ampl_bin_mean, ampl_bin_edges, _, _ = cycle_matrix(signals, no_bins, 1, rainflow_func)
+    if 0:  #to be similar to windap
+        ampl_bin_mean = (ampl_bin_edges[:-1] + ampl_bin_edges[1:]) / 2
+        cycles, ampl_bin_mean = cycles.flatten(), ampl_bin_mean.flatten()
+    eq_loads = [[((np.sum(cycles * ampl_bin_mean ** _m) / _neq) ** (1. / _m)) for _m in np.atleast_1d(m)]  for _neq in np.atleast_1d(neq)]
+    return eq_loads, cycles, ampl_bin_mean, ampl_bin_edges
+
+
+
+def cycle_matrix(signals, ampl_bins=10, mean_bins=10, rainflow_func=rainflow_windap):
+    """Markow load cycle matrix
+
+    Calculate the Markow load cycle matrix
+
+    Parameters
+    ----------
+    Signals : array-like or list of tuples
+        if array-like, the raw signal
+        if list of tuples, list of (weight, signal), e.g. [(0.1,sig1), (0.8,sig2), (.1,sig3)]
+    ampl_bins : int or array-like, optional
+        if int, Number of amplitude value bins (default is 10)
+        if array-like, the bin edges for amplitude
+    mean_bins : int or array-like, optional
+        if int, Number of mean value bins (default is 10)
+        if array-like, the bin edges for mea
+    rainflow_func : {rainflow_windap, rainflow_astm}, optional
+        The rainflow counting function to use (default is rainflow_windap)
+
+    Returns
+    -------
+    cycles : ndarray, shape(ampl_bins, mean_bins)
+        A bi-dimensional histogram of load cycles(full cycles). Amplitudes are histogrammed along the first dimension and mean values are histogrammed along the second dimension.
+    ampl_bin_mean : ndarray, shape(ampl_bins,)
+        The average cycle amplitude of the bins
+    ampl_edges : ndarray, shape(ampl_bins+1,)
+        The amplitude bin edges
+    mean_bin_mean : ndarray, shape(ampl_bins,)
+        The average cycle mean of the bins
+    mean_edges : ndarray, shape(mean_bins+1,)
+        The mean bin edges
+
+    Examples
+    --------
+    >>> signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
+    >>> cycles, ampl_bin_mean, ampl_edges, mean_bin_mean, mean_edges = cycle_matrix(signal)
+    >>> cycles, ampl_bin_mean, ampl_edges, mean_bin_mean, mean_edges = cycle_matrix([(.4, signal), (.6,signal)])
+    """
+
+    if isinstance(signals[0], tuple):
+        ampls = np.empty((0,), dtype=np.float64)
+        means = np.empty((0,), dtype=np.float64)
+        weights = np.empty((0,), dtype=np.float64)
+        for w, signal in signals:
+            a, m = rainflow_func(signal[:])
+            ampls = np.r_[ampls, a]
+            means = np.r_[means, m]
+            weights = np.r_[weights, (np.zeros_like(a) + w)]
+    else:
+        ampls, means = rainflow_func(signals[:])
+        weights = np.ones_like(ampls)
+    if isinstance(ampl_bins, int):
+        ampl_bins = np.linspace(0, 1, num=ampl_bins + 1) * ampls.max()
+    cycles, ampl_edges, mean_edges = np.histogram2d(ampls, means, [ampl_bins, mean_bins], weights=weights)
+
+    ampl_bin_sum = np.histogram2d(ampls, means, [ampl_bins, mean_bins], weights=weights * ampls)[0]
+    ampl_bin_mean = np.zeros_like(cycles)
+    mask = (cycles > 0)
+    ampl_bin_mean[mask] = ampl_bin_sum[mask] / cycles[mask]
+    mean_bin_sum = np.histogram2d(ampls, means, [ampl_bins, mean_bins], weights=weights * means)[0]
+    mean_bin_mean = np.zeros_like(cycles)
+    mean_bin_mean[cycles > 0] = mean_bin_sum[cycles > 0] / cycles[cycles > 0]
+    cycles = cycles / 2  # to get full cycles
+    return cycles, ampl_bin_mean, ampl_edges, mean_bin_mean, mean_edges
+
+
diff --git a/wetb/fatigue_tools/pair_range.py b/wetb/fatigue_tools/pair_range.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a493d701e5760682061369f6f2c1414f8d63ce1
--- /dev/null
+++ b/wetb/fatigue_tools/pair_range.py
@@ -0,0 +1,198 @@
+
+import cython
+import numpy as np
+# cimport numpy as np
+
+
+@cython.locals(p=cython.int, q=cython.int, f=cython.int, flow=list, k=cython.int, n=cython.int, ptr=cython.int)
+def pair_range_amplitude(x):  # cpdef pair_range(np.ndarray[long,ndim=1]  x):
+    """
+    Returns a list of half-cycle-amplitudes
+    x: Peak-Trough sequence (integer list of local minima and maxima)
+
+    This routine is implemented according to
+    "Recommended Practices for Wind Turbine Testing - 3. Fatigue Loads", 2. edition 1990, Appendix A
+    except that a list of half-cycle-amplitudes are returned instead of a from_level-to_level-matrix
+    """
+
+    x = x - np.min(x)
+    k = np.max(x)
+    n = x.shape[0]
+    S = np.zeros(n + 1)
+
+    #A = np.zeros(k+1)
+    flow = []
+    S[1] = x[0]
+    ptr = 1
+    p = 1
+    q = 1
+    f = 0
+    # phase 1
+    while True:
+        p += 1
+        q += 1
+
+        # read
+        S[p] = x[ptr]
+        ptr += 1
+
+        if q == n:
+            f = 1
+        while p >= 4:
+            if (S[p - 2] > S[p - 3] and S[p - 1] >= S[p - 3] and S[p] >= S[p - 2]) \
+                or\
+                    (S[p - 2] < S[p - 3] and S[p - 1] <= S[p - 3] and S[p] <= S[p - 2]):
+                ampl = abs(S[p - 2] - S[p - 1])
+                # A[ampl]+=2 #Two half cycles
+                flow.append(ampl)
+                flow.append(ampl)
+                S[p - 2] = S[p]
+
+                p -= 2
+            else:
+                break
+
+        if f == 0:
+            pass
+        else:
+            break
+    # phase 2
+    q = 0
+    while True:
+        q += 1
+        if p == q:
+            break
+        else:
+            ampl = abs(S[q + 1] - S[q])
+            # A[ampl]+=1
+            flow.append(ampl)
+    return flow
+
+
+
+
+
+@cython.locals(p=cython.int, q=cython.int, f=cython.int, flow=list, k=cython.int, n=cython.int, ptr=cython.int)
+def pair_range_from_to(x):  # cpdef pair_range(np.ndarray[long,ndim=1]  x):
+    """
+    Returns a list of half-cycle-amplitudes
+    x: Peak-Trough sequence (integer list of local minima and maxima)
+
+    This routine is implemented according to
+    "Recommended Practices for Wind Turbine Testing - 3. Fatigue Loads", 2. edition 1990, Appendix A
+    except that a list of half-cycle-amplitudes are returned instead of a from_level-to_level-matrix
+    """
+
+    x = x - np.min(x)
+    k = np.max(x)
+    n = x.shape[0]
+    S = np.zeros(n + 1)
+
+    A = np.zeros((k + 1, k + 1))
+    S[1] = x[0]
+    ptr = 1
+    p = 1
+    q = 1
+    f = 0
+    # phase 1
+    while True:
+        p += 1
+        q += 1
+
+        # read
+        S[p] = x[ptr]
+        ptr += 1
+
+        if q == n:
+            f = 1
+        while p >= 4:
+            #print S[p - 3:p + 1]
+            #print S[p - 2], ">", S[p - 3], ", ", S[p - 1], ">=", S[p - 3], ", ", S[p], ">=", S[p - 2], (S[p - 2] > S[p - 3] and S[p - 1] >= S[p - 3] and S[p] >= S[p - 2])
+            #print S[p - 2], "<", S[p - 3], ", ", S[p - 1], "<=", S[p - 3], ", ", S[p], "<=", S[p - 2], (S[p - 2] < S[p - 3] and S[p - 1] <= S[p - 3] and S[p] <= S[p - 2])
+            #print (S[p - 2] > S[p - 3] and S[p - 1] >= S[p - 3] and S[p] >= S[p - 2]) or (S[p - 2] < S[p - 3] and S[p - 1] <= S[p - 3] and S[p] <= S[p - 2])
+            if (S[p - 2] > S[p - 3] and S[p - 1] >= S[p - 3] and S[p] >= S[p - 2]) or \
+               (S[p - 2] < S[p - 3] and S[p - 1] <= S[p - 3] and S[p] <= S[p - 2]):
+                A[S[p - 2], S[p - 1]] += 1
+                A[S[p - 1], S[p - 2]] += 1
+                S[p - 2] = S[p]
+                p -= 2
+            else:
+                break
+
+        if f == 1:
+            break  # q==n
+    # phase 2
+    q = 0
+    while True:
+        q += 1
+        if p == q:
+            break
+        else:
+            #print S[q], "to", S[q + 1]
+            A[S[q], S[q + 1]] += 1
+    return A
+
+@cython.locals(p=cython.int, q=cython.int, f=cython.int, flow=list, k=cython.int, n=cython.int, ptr=cython.int)
+def pair_range_amplitude_mean(x):  # cpdef pair_range(np.ndarray[long,ndim=1]  x):
+    """
+    Returns a list of half-cycle-amplitudes
+    x: Peak-Trough sequence (integer list of local minima and maxima)
+
+    This routine is implemented according to
+    "Recommended Practices for Wind Turbine Testing - 3. Fatigue Loads", 2. edition 1990, Appendix A
+    except that a list of half-cycle-amplitudes are returned instead of a from_level-to_level-matrix
+    """
+
+    x = x - np.min(x)
+    k = np.max(x)
+    n = x.shape[0]
+    S = np.zeros(n + 1)
+    ampl_mean = []
+    A = np.zeros((k + 1, k + 1))
+    S[1] = x[0]
+    ptr = 1
+    p = 1
+    q = 1
+    f = 0
+    # phase 1
+    while True:
+        p += 1
+        q += 1
+
+                # read
+        S[p] = x[ptr]
+        ptr += 1
+
+        if q == n:
+            f = 1
+        while p >= 4:
+            if (S[p - 2] > S[p - 3] and S[p - 1] >= S[p - 3] and S[p] >= S[p - 2]) \
+                or\
+                    (S[p - 2] < S[p - 3] and S[p - 1] <= S[p - 3] and S[p] <= S[p - 2]):
+                # Extract two intermediate half cycles
+                ampl = abs(S[p - 2] - S[p - 1])
+                mean = (S[p - 2] + S[p - 1]) / 2
+                ampl_mean.append((ampl, mean))
+                ampl_mean.append((ampl, mean))
+
+                S[p - 2] = S[p]
+
+                p -= 2
+            else:
+                break
+
+        if f == 0:
+            pass
+        else:
+            break
+    # phase 2
+    q = 0
+    while True:
+        q += 1
+        if p == q:
+            break
+        else:
+            ampl = abs(S[q + 1] - S[q])
+            mean = (S[q + 1] + S[q]) / 2
+            ampl_mean.append((ampl, mean))
+    return ampl_mean
diff --git a/wetb/fatigue_tools/pair_range.pyd b/wetb/fatigue_tools/pair_range.pyd
new file mode 100644
index 0000000000000000000000000000000000000000..e3e28cb4e3bb7ab0fc866c3ab7d1ec242458abdb
Binary files /dev/null and b/wetb/fatigue_tools/pair_range.pyd differ
diff --git a/wetb/fatigue_tools/peak_trough.py b/wetb/fatigue_tools/peak_trough.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc6bde699eee1cbd1406f58bca043a5f637bfa42
--- /dev/null
+++ b/wetb/fatigue_tools/peak_trough.py
@@ -0,0 +1,100 @@
+import cython
+import numpy as np
+#cimport numpy as np
+
+@cython.locals(BEGIN=cython.int, MINZO=cython.int, MAXZO=cython.int, ENDZO=cython.int, \
+               R=cython.int, L=cython.int, i=cython.int, p=cython.int, f=cython.int)
+def peak_trough(x, R):  #cpdef np.ndarray[long,ndim=1] peak_trough(np.ndarray[long,ndim=1] x, int R):
+    """
+    Returns list of local maxima/minima.
+
+    x: 1-dimensional numpy array containing signal
+    R: Thresshold (minimum difference between succeeding min and max
+
+    This routine is implemented directly as described in
+    "Recommended Practices for Wind Turbine Testing - 3. Fatigue Loads", 2. edition 1990, Appendix A
+    """
+
+    BEGIN = 0
+    MINZO = 1
+    MAXZO = 2
+    ENDZO = 3
+    S = np.zeros(x.shape[0] + 1, dtype=np.int)
+
+    L = x.shape[0]
+    goto = BEGIN
+
+    while 1:
+        if goto == BEGIN:
+            trough = x[0]
+            peak = x[0]
+
+            i = 0
+            p = 1
+            f = 0
+            while goto == BEGIN:
+                i += 1
+                if i == L:
+                    goto = ENDZO
+                    continue
+                else:
+                    if x[i] > peak:
+                        peak = x[i]
+                        if peak - trough >= R:
+                            S[p] = trough
+                            goto = MAXZO
+                            continue
+                    elif x[i] < trough:
+                        trough = x[i]
+                        if peak - trough >= R:
+                            S[p] = peak
+                            goto = MINZO
+                            continue
+
+        elif goto == MINZO:
+            f = -1
+
+            while goto == MINZO:
+                i += 1
+                if i == L:
+                    goto = ENDZO
+                    continue
+                else:
+                    if x[i] < trough:
+                        trough = x[i]
+                    else:
+                        if x[i] - trough >= R:
+                            p += 1
+                            S[p] = trough
+                            peak = x[i]
+                            goto = MAXZO
+                            continue
+        elif goto == MAXZO:
+            f = 1
+            while goto == MAXZO:
+                i += 1
+                if i == L:
+                    goto = ENDZO
+                    continue
+                else:
+                    if x[i] > peak:
+                        peak = x[i]
+                    else:
+                        if peak - x[i] >= R:
+                            p += 1
+                            S[p] = peak
+                            trough = x[i]
+                            goto = MINZO
+                            continue
+        elif goto == ENDZO:
+
+            n = p + 1
+            if abs(f) == 1:
+                if f == 1:
+                    S[n] = peak
+                else:
+                    S[n] = trough
+            else:
+                S[n] = (trough + peak) / 2
+            S = S[1:n + 1]
+            return S
diff --git a/wetb/fatigue_tools/peak_trough.pyd b/wetb/fatigue_tools/peak_trough.pyd
new file mode 100644
index 0000000000000000000000000000000000000000..db4087cd89878143c3de4f75a60b5026f5dceb1f
Binary files /dev/null and b/wetb/fatigue_tools/peak_trough.pyd differ
diff --git a/wetb/fatigue_tools/rainflowcount_astm.py b/wetb/fatigue_tools/rainflowcount_astm.py
new file mode 100644
index 0000000000000000000000000000000000000000..06924c526cb5007e3e50ed9a1e678c41b60a8f3c
--- /dev/null
+++ b/wetb/fatigue_tools/rainflowcount_astm.py
@@ -0,0 +1,102 @@
+'''
+Created on 27/02/2013
+
+@author: mmpe
+
+
+
+How to use:
+
+import_cython("cy_rainflowcount",'cy_rainflowcount.py','')
+from cy_rainflowcount import find_extremes,rainflow
+
+ext = find_extremes(np.array([-2,0,1,0,-3,0,5,0,-1,0,3,0,-4,0,4,0,-2]).astype(np.double))
+print rainflow(ext)
+'''
+
+import numpy as np
+
+
+def find_extremes(signal):  #cpdef find_extremes(np.ndarray[double,ndim=1] signal):
+    """return indexes of local minima and maxima plus first and last element of signal"""
+
+    #cdef int pi, i
+    # sign of gradient
+    sign_grad = np.int8(np.sign(np.diff(signal)))
+
+    # remove plateaus(sign_grad==0) by sign_grad[plateau_index]=sign_grad[plateau_index-1]
+    plateau_indexes, = np.where(sign_grad == 0)
+    if len(plateau_indexes) > 0 and plateau_indexes[0] == 0:
+        # first element is a plateau
+        if len(plateau_indexes) == len(sign_grad):
+                # All values are equal to crossing level!
+                return np.array([0])
+
+        # set first element = first element which is not a plateau and delete plateau index
+        i = 0
+        while sign_grad[i] == 0:
+            i += 1
+        sign_grad[0] = sign_grad[i]
+
+        plateau_indexes = np.delete(plateau_indexes, 0)
+
+    for pi in plateau_indexes.tolist():
+        sign_grad[pi] = sign_grad[pi - 1]
+
+    extremes, = np.where(np.r_[1, (sign_grad[1:] * sign_grad[:-1] < 0), 1])
+
+    return signal[extremes]
+
+
+def rainflowcount(sig):  #cpdef rainflowcount(np.ndarray[double,ndim=1] sig):
+    """Cython compilable rain ampl_mean count without time analysis
+
+
+    This implemementation is based on the c-implementation by Adam Nieslony found at
+    the MATLAB Central File Exchange http://www.mathworks.com/matlabcentral/fileexchange/3026
+
+    References
+    ----------
+    Adam Nieslony, "Determination of fragments of multiaxial service loading
+    strongly influencing the fatigue of machine components,"
+    Mechanical Systems and Signal Processing 23, no. 8 (2009): 2712-2721.
+
+    and is based on the following standard:
+    ASTM E 1049-85 (Reapproved 1997), Standard practices for cycle counting in
+    fatigue analysis, in: Annual Book of ASTM Standards, vol. 03.01, ASTM,
+    Philadelphia, 1999, pp. 710-718.
+
+    Copyright (c) 1999-2002 by Adam Nieslony
+
+    Ported to Cython compilable Python by Mads M Pedersen
+    In addition peak amplitude is changed to peak to peak amplitude
+
+
+    """
+
+    #cdef int sig_ptr, index
+    #cdef double ampl
+    a = []
+    sig_ptr = 0
+    ampl_mean = []
+    for _ in range(len(sig)):
+        a.append(sig[sig_ptr])
+        sig_ptr += 1
+        while len(a) > 2 and abs(a[-3] - a[-2]) <= abs(a[-2] - a[-1]):
+            ampl = abs(a[-3] - a[-2])
+            mean = (a[-3] + a[-2]) / 2;
+            if len(a) == 3:
+                del a[0]
+                if ampl > 0:
+                    ampl_mean.append((ampl, mean))
+            elif len(a) > 3:
+                del a[-3:-1]
+                if ampl > 0:
+                    ampl_mean.append((ampl, mean))
+                    ampl_mean.append((ampl, mean))
+    for index in range(len(a) - 1):
+        ampl = abs(a[index] - a[index + 1])
+        mean = (a[index] + a[index + 1]) / 2;
+        if ampl > 0:
+            ampl_mean.append((ampl, mean))
+    return ampl_mean
diff --git a/wetb/fatigue_tools/rainflowcount_astm.pyd b/wetb/fatigue_tools/rainflowcount_astm.pyd
new file mode 100644
index 0000000000000000000000000000000000000000..6ef6cd427dfb019332137805ebe4db80d90dcbc8
Binary files /dev/null and b/wetb/fatigue_tools/rainflowcount_astm.pyd differ
diff --git a/wetb/fatigue_tools/tests/__init__.py b/wetb/fatigue_tools/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/wetb/fatigue_tools/tests/test_fatigue.py b/wetb/fatigue_tools/tests/test_fatigue.py
new file mode 100644
index 0000000000000000000000000000000000000000..7949add8d12e7334b7a08730dfb0f3729d50dd47
--- /dev/null
+++ b/wetb/fatigue_tools/tests/test_fatigue.py
@@ -0,0 +1,67 @@
+'''
+Created on 16/07/2013
+
+@author: mmpe
+'''
+
+import unittest
+
+import numpy as np
+from wetb.fatigue_tools.fatigue import eq_load, rainflow_astm, rainflow_windap, \
+    cycle_matrix
+from wetb.hawc2 import Hawc2io
+
+
+class Test(unittest.TestCase):
+
+
+    def test_astm1(self):
+
+        signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
+
+        ampl, mean = rainflow_astm(signal)
+        np.testing.assert_array_equal(np.histogram2d(ampl, mean, [6, 4])[0], np.array([[ 0., 1., 0., 0.],
+                                                                                                           [ 1., 0., 0., 2.],
+                                                                                                           [ 0., 0., 0., 0.],
+                                                                                                           [ 0., 0., 0., 1.],
+                                                                                                           [ 0., 0., 0., 0.],
+                                                                                                           [ 0., 0., 1., 2.]]))
+
+    def test_windap1(self):
+        signal = np.array([-2.0, 0.0, 1.0, 0.0, -3.0, 0.0, 5.0, 0.0, -1.0, 0.0, 3.0, 0.0, -4.0, 0.0, 4.0, 0.0, -2.0])
+        ampl, mean = rainflow_windap(signal, 18, 2)
+        np.testing.assert_array_equal(np.histogram2d(ampl, mean, [6, 4])[0], np.array([[ 0., 0., 1., 0.],
+                                                                                       [ 1., 0., 0., 2.],
+                                                                                       [ 0., 0., 0., 0.],
+                                                                                       [ 0., 0., 0., 1.],
+                                                                                       [ 0., 0., 0., 0.],
+                                                                                       [ 0., 0., 2., 1.]]))
+
+    def test_windap2(self):
+        data = Hawc2io.ReadHawc2("test_files/test").ReadBinary([2]).flatten()
+        np.testing.assert_allclose(eq_load(data, neq=61), np.array([[1.356, 1.758, 2.370, 2.784, 3.077, 3.296]]), 0.01)
+
+
+    def test_astm2(self):
+        data = Hawc2io.ReadHawc2("test_files/test").ReadBinary([2]).flatten()
+        np.testing.assert_allclose(eq_load(data, neq=61, rainflow_func=rainflow_astm), np.array([[1.356, 1.758, 2.370, 2.784, 3.077, 3.296]]), 0.01)
+
+
+    def test_windap3(self):
+        data = Hawc2io.ReadHawc2("test_files/test").ReadBinary([2]).flatten()
+        np.testing.assert_array_equal(cycle_matrix(data, 4, 4, rainflow_func=rainflow_windap)[0], np.array([[  14., 65., 39., 24.],
+                                                                   [  0., 1., 4., 0.],
+                                                                   [  0., 0., 0., 0.],
+                                                                   [  0., 1., 2., 0.]]) / 2)
+
+
+    def test_astm3(self):
+        data = Hawc2io.ReadHawc2("test_files/test").ReadBinary([2]).flatten()
+        np.testing.assert_allclose(cycle_matrix(data, 4, 4, rainflow_func=rainflow_astm)[0], np.array([[ 24., 83., 53., 26.],
+                                                                                                           [  0., 1., 4., 0.],
+                                                                                                           [  0., 0., 0., 0.],
+                                                                                                           [  0., 1., 2., 0.]]) / 2, 0.001)
+
+if __name__ == "__main__":
+    #import sys;sys.argv = ['', 'Test.testName']
+    unittest.main()
diff --git a/wetb/fatigue_tools/tests/test_files/test.dat b/wetb/fatigue_tools/tests/test_files/test.dat
new file mode 100644
index 0000000000000000000000000000000000000000..60c7efd914a0cf82ef78a7f21777d14c1bf40422
Binary files /dev/null and b/wetb/fatigue_tools/tests/test_files/test.dat differ
diff --git a/wetb/fatigue_tools/tests/test_files/test.sel b/wetb/fatigue_tools/tests/test_files/test.sel
new file mode 100644
index 0000000000000000000000000000000000000000..185e5253c0e5f1947b6a9d22079811714bc79206
--- /dev/null
+++ b/wetb/fatigue_tools/tests/test_files/test.sel
@@ -0,0 +1,114 @@
+________________________________________________________________________________________________________________________
+  Version ID : Pydap 0.1
+                                                            Time : 15:04:16
+                                                            Date : 01:08.2012
+________________________________________________________________________________________________________________________
+  Result file : unit_test/test_files/test.dat
+________________________________________________________________________________________________________________________
+   Scans    Channels    Time [sec]      Format
+       2440     50         61.000       BINARY
+
+  Channel   Variable Description               
+
+     1      Time                           s          Time
+     2      WSP gl. coo.,Vy                m/s        Free wind speed Vy, gl. coo, of gl. pos    0.75,   0.00, -40.75
+     3      WSP gl. coo.,Vy                m/s        Free wind speed Vy, gl. coo, of gl. pos    1.00,   0.00, -39.00
+     4      WSP gl. coo.,Vy                m/s        Free wind speed Vy, gl. coo, of gl. pos    7.00,   0.00, -33.00
+     5       Omega                         rad/s       Rotor speed
+     6      Ae pos x , glco, R= 20.5       m          Aero position x  of blade  1 at radius  20.50, global coo.
+     7      Ae pos y , glco, R= 20.5       m          Aero position y  of blade  1 at radius  20.50, global coo.
+     8      Ae pos z , glco, R= 20.5       m          Aero position z  of blade  1 at radius  20.50, global coo.
+     9      Ae pos x , glco, R= 20.5       m          Aero position x  of blade  2 at radius  20.50, global coo.
+    10      Ae pos y , glco, R= 20.5       m          Aero position y  of blade  2 at radius  20.50, global coo.
+    11      Ae pos z , glco, R= 20.5       m          Aero position z  of blade  2 at radius  20.50, global coo.
+    12      Ae pos x , glco, R= 20.5       m          Aero position x  of blade  3 at radius  20.50, global coo.
+    13      Ae pos y , glco, R= 20.5       m          Aero position y  of blade  3 at radius  20.50, global coo.
+    14      Ae pos z , glco, R= 20.5       m          Aero position z  of blade  3 at radius  20.50, global coo.
+    15      Ae pos x , glco, R= 16.5       m          Aero position x  of blade  1 at radius  16.49, global coo.
+    16      Ae pos y , glco, R= 16.5       m          Aero position y  of blade  1 at radius  16.49, global coo.
+    17      Ae pos z , glco, R= 16.5       m          Aero position z  of blade  1 at radius  16.49, global coo.
+    18      Ae pos x , glco, R= 16.5       m          Aero position x  of blade  2 at radius  16.49, global coo.
+    19      Ae pos y , glco, R= 16.5       m          Aero position y  of blade  2 at radius  16.49, global coo.
+    20      Ae pos z , glco, R= 16.5       m          Aero position z  of blade  2 at radius  16.49, global coo.
+    21      Ae pos x , glco, R= 16.5       m          Aero position x  of blade  3 at radius  16.49, global coo.
+    22      Ae pos y , glco, R= 16.5       m          Aero position y  of blade  3 at radius  16.49, global coo.
+    23      Ae pos z , glco, R= 16.5       m          Aero position z  of blade  3 at radius  16.49, global coo.
+    24      Ae pos x , glco, R= 11.6       m          Aero position x  of blade  1 at radius  11.59, global coo.
+    25      Ae pos y , glco, R= 11.6       m          Aero position y  of blade  1 at radius  11.59, global coo.
+    26      Ae pos z , glco, R= 11.6       m          Aero position z  of blade  1 at radius  11.59, global coo.
+    27      Ae pos x , glco, R= 11.6       m          Aero position x  of blade  2 at radius  11.59, global coo.
+    28      Ae pos y , glco, R= 11.6       m          Aero position y  of blade  2 at radius  11.59, global coo.
+    29      Ae pos z , glco, R= 11.6       m          Aero position z  of blade  2 at radius  11.59, global coo.
+    30      Ae pos x , glco, R= 11.6       m          Aero position x  of blade  3 at radius  11.59, global coo.
+    31      Ae pos y , glco, R= 11.6       m          Aero position y  of blade  3 at radius  11.59, global coo.
+    32      Ae pos z , glco, R= 11.6       m          Aero position z  of blade  3 at radius  11.59, global coo.
+    33      Ae pos x , glco, R=  7.6       m          Aero position x  of blade  1 at radius   7.60, global coo.
+    34      Ae pos y , glco, R=  7.6       m          Aero position y  of blade  1 at radius   7.60, global coo.
+    35      Ae pos z , glco, R=  7.6       m          Aero position z  of blade  1 at radius   7.60, global coo.
+    36      Ae pos x , glco, R=  7.6       m          Aero position x  of blade  2 at radius   7.60, global coo.
+    37      Ae pos y , glco, R=  7.6       m          Aero position y  of blade  2 at radius   7.60, global coo.
+    38      Ae pos z , glco, R=  7.6       m          Aero position z  of blade  2 at radius   7.60, global coo.
+    39      Ae pos x , glco, R=  7.6       m          Aero position x  of blade  3 at radius   7.60, global coo.
+    40      Ae pos y , glco, R=  7.6       m          Aero position y  of blade  3 at radius   7.60, global coo.
+    41      Ae pos z , glco, R=  7.6       m          Aero position z  of blade  3 at radius   7.60, global coo.
+    42      Ae pos x , glco, R=  4.0       m          Aero position x  of blade  1 at radius   4.01, global coo.
+    43      Ae pos y , glco, R=  4.0       m          Aero position y  of blade  1 at radius   4.01, global coo.
+    44      Ae pos z , glco, R=  4.0       m          Aero position z  of blade  1 at radius   4.01, global coo.
+    45      Ae pos x , glco, R=  4.0       m          Aero position x  of blade  2 at radius   4.01, global coo.
+    46      Ae pos y , glco, R=  4.0       m          Aero position y  of blade  2 at radius   4.01, global coo.
+    47      Ae pos z , glco, R=  4.0       m          Aero position z  of blade  2 at radius   4.01, global coo.
+    48      Ae pos x , glco, R=  4.0       m          Aero position x  of blade  3 at radius   4.01, global coo.
+    49      Ae pos y , glco, R=  4.0       m          Aero position y  of blade  3 at radius   4.01, global coo.
+    50      Ae pos z , glco, R=  4.0       m          Aero position z  of blade  3 at radius   4.01, global coo.
+________________________________________________________________________________________________________________________
+Scale factors:
+  1.90625E-03
+  3.86850E-04
+  3.93534E-04
+  3.63391E-04
+  1.56250E-05
+  6.40656E-04
+  0.00000E+00
+  1.89066E-03
+  6.40656E-04
+  0.00000E+00
+  1.89066E-03
+  6.40656E-04
+  0.00000E+00
+  1.89066E-03
+  5.15428E-04
+  6.87809E-07
+  1.76543E-03
+  5.15428E-04
+  6.87809E-07
+  1.76543E-03
+  5.15428E-04
+  6.87809E-07
+  1.76543E-03
+  3.62459E-04
+  1.85211E-06
+  1.61246E-03
+  3.62459E-04
+  1.85211E-06
+  1.61246E-03
+  3.62459E-04
+  1.85211E-06
+  1.61246E-03
+  2.38552E-04
+  5.23494E-06
+  1.48855E-03
+  2.38552E-04
+  5.23494E-06
+  1.48855E-03
+  2.38551E-04
+  5.23494E-06
+  1.48855E-03
+  1.29018E-04
+  1.14916E-05
+  1.37902E-03
+  1.29018E-04
+  1.14916E-05
+  1.37902E-03
+  1.29018E-04
+  1.14916E-05
+  1.37902E-03