def main():
    """Plot the timestack."""
    # load the dataset
    ds = xr.open_dataset(args.input[0])

    # extract variables
    secs = ellapsedseconds(pd.to_datetime(
        ds["time"].values).to_pydatetime())
    x = ds["distance"].values
    z = ds["eta"].values

    # plot
    fig, ax = plt.subplots(figsize=(12, 5))
    ax.set_axisbelow(False)
    m = ax.pcolormesh(secs, x, z.T, vmin=0, vmax=0.3)
    cb = plt.colorbar(m, aspect=15)
    cb.set_label("Surface elevation [m]")
    ax.grid(ls="--", lw=1, color="w")
    ax.set_ylabel("Distance [m]")
    ax.set_xlabel("Time [m]")
    sns.despine(ax=ax)
    fig.tight_layout()
    plt.savefig(args.input[0].replace(".nc", ".png"))
    plt.close()
Example #2
0
def detect_breaking_events(time,
                           crx_dist,
                           rgb,
                           crx_start=None,
                           crx_end=None,
                           px_mtrc="lightness",
                           colours=None,
                           resample_rule="100L",
                           algorithm="peaks",
                           peak_detection="local_maxima",
                           posterize=False,
                           ncolours=0,
                           threshold=0.1,
                           tswindow=11,
                           denoise=True,
                           pxwindow=3,
                           mask_drysand=False,
                           fix_constrast=False):
    """
    Detect wave breaking events.

    Two main methods are implemented:

    1 - Peak detection: detect wave breaking as lightness peaks in the
                        timestack

        Two peak detection methods are implemented:

        1-a Local maxima. Uses peaklocalextremas() function from pywavelearn
                          to detect local maximas corresponding to wave
                          breaking.

        1-b Differential. Uses the first temporal derivative of the pixel
                          intensity to detect sharp transitions in the
                          timestack that should correspond to wave breaking.

        In both cases, the user can tell to the script to classifiy the
        identified pixel peaks based on known colours. For exemple, water is
        usually blue, sand is brownish and breaking waves are whiteish.
        Only peaks corresponding to wave breakin are append to the output
        structure. This is step done using classifiy_colour()
        from pywavelearn.

    2 - Edge detection: detect wave breaking as sharp edges in the timestack

        Two-options are available:

        2-a Edges only. Wave breaking events are obtained applying a sobel
                        filter to the timestack. Edge locations (time,space)
                        are obrained as:

                        - argument of the maxima (argmax) of a cross-shore
                          pixel intenstiy series obtained at every timestamp.

                        - local maximas of a cross-shore pixel intenstiy series
                          obtained at every timestamp.

        2-b Edges and colours. Wave breaking events are obtained applying a
                               Sobel filter to the timestack and the detected
                               Edges are classified using the colour
                               information as in 1-a. Edge locations
                               (time,space) are obrained as:

                               - argument of the maxima (argmax) of a
                                 cross-shore pixel intenstiy series obtained
                                 at every timestamp.


                               - local maximas of a cross-shore pixel intenstiy
                                 series obtained at every timestamp.
    ----------
    Args:
        time (Mandatory [np.array]): Array of datetimes.

        crx_dist (Mandatory [np.array]): Array of cross-shore locations.

        rgb (Mandatory [np.array]): timestack array.
                                    Shape is [time,crx_dist,3].

        crx_start (Optional [float]): where in the cross-shore orientation to
                                       start the analysis.
                                       Default is crx_dist.min().

        crx_start (Optional [float]): where in the cross-shore orientation to
                                       finish the analysis.
                                       Default is crx_dist.max().

        px_mtrc (Optional [float]): Which pixel intenstiy metric to use.
                                    Default is "lightness".

        resample_rule (Optional [str]): To which frequency interpolate
                                        timeseries Default is  "100L".

        algorithm (Optional [str]): Wave breaking detection algorithm.
                                    Default is "peaks".

        peak_detection (Optional [str]): Peak detection algorithm.
                                         Default is  "local_maxima".

        threshold (Optional [float]): Threshold for peak detection algorithm.
                                      Default is 0.1

        tswindow (Optional [int]): Window for peak detection algorithm.
                                   Default is 11.

        denoise (Optional [bool]): = Denoise timestack using denoise_bilateral
                                     Default is True.

        pxwindow (Optional [int]): Window for denoise_bilateral. Default is 3.

        posterize (Optional [bool]): If true will reduce the number of colours
                                     in the timestack. Default is False.

        ncolours (Optional [str]): Number of colours to posterize.
                                   Default is 16.

        colours (Optional [dict]): A dictionary for the colour learning step.
                                    Something like:
                                    train_colours = {'labels':[0,1,2],
                                                     'aliases':
                                                     ["sand","water","foam"],
                                                     'rgb':[[195,185,155],
                                                            [30,75,75],
                                                            [255,255,255]]
                                                     'target':2}
                                    Default is None.

        mask_drysand (Experimental [bool]) = Mask dry sand using a
                                             colour-temperature (CCT)
                                             relationship. Default is False.
    ----------
    Return:
         time (Mandatory [np.array]): time of occurance of wave breaking
                                      events.

         breakers (Mandatory [np.array]): cross-shore location of wave breaking
                                          events.
    """
    if not crx_start:
        crx_start = crx_dist.min()
        crx_end = crx_dist.max()

    if posterize:
        print("  + >> posterizing")
        rgb = colour_quantization(rgb, ncolours=ncolours)

    # get colour data
    if algorithm == "colour" or algorithm == "edges_and_colour":
        target = colours["target"]
        labels = colours["labels"]
        dom_colours = colours["rgb"]

    # denoise a little bedore computing edges
    if denoise:
        rgb = denoise_bilateral(rgb, pxwindow, multichannel=True)
        # scale back to 0-255
        rgb = (rgb - rgb.min()) / (rgb.max() - rgb.min()) * 255

    # mask sand - Not fully tested
    if mask_drysand:
        print("  + >> masking dry sand [Experimental]")
        # calculate colour temperature
        cct = colour.xy_to_CCT_Hernandez1999(
            colour.XYZ_to_xy(colour.sRGB_to_XYZ(rgb / 255)))
        # scale back to 0-1
        cct = (cct - cct.min()) / (cct.max() - cct.min()) * 255
        # mask
        i, j = np.where(cct == 0)
        rgb[i, j, :] = 0

    if fix_constrast:
        print("  + >> fixing contrast")
        rgb = exposure.equalize_hist(rgb)
        # rgb = (rgb-rgb.min())/(rgb.max()-rgb.min())*255

    # detect edges
    if algorithm == "edges" or algorithm == "edges_and_colour":
        print("  + >> calculating edges")
        edges = sobel_h(rgb2grey(rgb))

    # get pixel lines and RGB values at selected locations only
    if algorithm == "peaks" or algorithm == "colour":
        print("  + >> extracting cross-shore pixels")
        # rescale
        rgb = (rgb - rgb.min()) / (rgb.max() - rgb.min()) * 255
        Y, crx_idx = get_analysis_locations(crx_dist, crx_start, crx_end)
        Time, PxInts, RGB = get_pixel_lines(time,
                                            rgb,
                                            crx_idx,
                                            resample_rule=resample_rule,
                                            pxmtc=px_mtrc)

    # get analysis frequency and a 1 sececond time window
    if not tswindow:
        fs = (time[1] - time[0]).total_seconds()
        win = np.int((1 / fs))
    else:
        win = tswindow

    print("  + >> detecting breaking events")
    PeakTimes = []
    print_check = False
    if algorithm == "peaks" or algorithm == "colour":
        if peak_detection == "argmax":
            peak_detection = "local_maxima"
            print("  - >> setting peak detection to local maxima")
        # loop over data rows
        for pxint, rgb in zip(PxInts, RGB):
            # calculate baseline
            bline = baseline(pxint, 2)
            # calculate pixel peaks
            if peak_detection == "local_maxima":
                _, max_idx = peaklocalextremas(pxint - bline,
                                               lookahead=win,
                                               delta=threshold *
                                               (pxint - bline).max())
            elif peak_detection == "differential":
                # calculate first derivative
                pxintdt = np.diff(pxint - bline)
                # remove values below zero
                pxintdt[pxintdt <= 0] = 0
                # scale from 0 to 1
                pxintdt = pxintdt / pxintdt.max()
                # get indexes
                max_idx = indexes(pxintdt, thres=threshold, min_dist=win)
            else:
                raise ValueError
            # colour learning step
            if algorithm == "colour":
                if not print_check:
                    print("  + >> colour learning")
                    print_check = True
                # classifiy pixels
                breaker_idxs = []
                for idx in max_idx:
                    y_pred = classify_colour(rgb[idx], dom_colours, labels)
                    if y_pred[0] == target:
                        breaker_idxs.append(idx)
            # peaks only
            else:
                breaker_idxs = max_idx
            PeakTimes.append(Time[breaker_idxs])
        # organize peaks and times
        Xpeaks = []
        Ypeaks = []
        for i, pxtimes in enumerate(PeakTimes):
            for v in pxtimes:
                Xpeaks.append(v)
            for v in np.ones(len(pxtimes)) * Y[i]:
                Ypeaks.append(v)
    # edges case
    if algorithm == "edges":
        Xpeaks = []
        Ypeaks = []
        # loop in time
        for i, t in enumerate(time):
            # cross-shore line
            crx_line = edges[i, :]
            # peaks with robust peak detection
            if peak_detection == "differential" or \
               peak_detection == "local_maxima":
                crx_line = (crx_line - crx_line.min()) / (crx_line.max() -
                                                          crx_line.min())
                if not np.all(crx_line == 0):
                    idx_peak = indexes(crx_line,
                                       thres=1 - threshold,
                                       min_dist=win)
                # apped peaks
                for peak in idx_peak:
                    if crx_dist[peak] > crx_start and crx_dist[peak] < crx_end:
                        Xpeaks.append(t)
                        Ypeaks.append(crx_dist[peak])
            # peaks with simple argmax - works better without colour learning
            else:
                peak = np.argmax(crx_line)
                if crx_dist[peak] > crx_start and crx_dist[peak] < crx_end:
                    Xpeaks.append(t)
                    Ypeaks.append(crx_dist[peak])
    # edges + colour learning case
    if algorithm == "edges_and_colour":
        Ipeaks = []
        Jpeaks = []
        # loop in time
        for i, t in enumerate(time):
            # cross-shore line
            crx_line = edges[i, :]
            if peak_detection == "differential" or \
               peak_detection == "local_maxima":
                crx_line = (crx_line - crx_line.min()) / (crx_line.max() -
                                                          crx_line.min())
                # peaks
                if not np.all(crx_line == 0):
                    idx_peak = indexes(crx_line,
                                       thres=1 - threshold,
                                       min_dist=win)
                    if not np.all(crx_line == 0):
                        idx_peak = indexes(crx_line,
                                           thres=1 - threshold,
                                           min_dist=win)
                # apped peaks
                for peak in idx_peak:
                    if crx_dist[peak] > crx_start and crx_dist[peak] < crx_end:
                        Ipeaks.append(i)
                        Jpeaks.append(peak)
            else:
                peak = np.argmax(crx_line)
                if crx_dist[peak] > crx_start and crx_dist[peak] < crx_end:
                    Ipeaks.append(i)
                    Jpeaks.append(peak)
        # colour learning step
        Xpeaks = []
        Ypeaks = []
        for i, j in zip(Ipeaks, Jpeaks):
            if not print_check:
                print("  + >> colour learning")
                print_check = True
            # classify colour
            y_pred = classify_colour(rgb[i, j, :], dom_colours, labels)
            if y_pred[0] == target:
                Xpeaks.append(time[i])
                Ypeaks.append(crx_dist[j])

    # sort values in time and outout
    y = np.array(Ypeaks)[np.argsort(date2num(Xpeaks))]
    x = np.array(Xpeaks)[np.argsort(Xpeaks)]

    return ellapsedseconds(x), y
Example #3
0
def main():
    """Call the main program."""
    # read parameters from JSON
    with open(args.input[0], 'r') as f:
        H = json.load(f)

    # input file names
    wavepaths = H["data"]["breaking"]
    timestack = H["data"]["timestack"]

    # shoreline
    has_shoreline = False
    try:
        shoreline = H["data"]["shoreline"]
        has_shoreline = True
    except Exception:
        has_shoreline = False

    # load shoreline
    if has_shoreline:

        # read variables
        shore = pd.read_csv(shoreline)
        tshore = shore["time"].values
        xshore = shore["shoreline"].values
    else:
        raise IOError("Sorry, you must provide a shoreline.")

    # regression parameters
    order = H["parameters"]["OLS_order"]
    weights_time_threshold = H["parameters"]["max_time_threshold"]

    # read timestack
    ds = xr.open_dataset(timestack)
    stk_time, stk_dist, rgb = process_timestack(ds)

    # time in seconds
    stk_secs = ellapsedseconds(stk_time)
    if not monotonic(stk_secs.tolist()):
        stk_secs = np.linspace(0, stk_secs.max(), rgb.shape[0])

    # fix distance offset
    stk_dist -= stk_dist.min()

    # projection time
    proj = H["parameters"]["projection"]

    # read waverays
    df = Dbf5(wavepaths, codec='utf-8').to_dataframe()
    if "newtime" in df.columns.values:
        df = df[["newtime", "newspace", "wave"]]
        df.columns = ["t", "x", "wave"]
    dfpts = df
    # else:

    # calculate optimal wave paths
    N = H["parameters"]["wavepath_npoints"]
    T, X, L = optimal_wavepaths(df,
                                order=order,
                                min_wave_period=1,
                                N=N,
                                project=False,
                                t_weights=weights_time_threshold)

    # loop over each wave path and get possible intersections
    TF = []  # for plotting
    XF = []  # for plotting
    TP = []  # projections
    XP = []  # projections
    TI = []  # intersections
    XI = []  # intersections
    WID = []
    k = 0
    for t1, x1, in zip(T, X):

        # extend the first wave path "t" seconds,
        tf, xf = projet_fowards(t1, x1, order=order, time=proj, N=N)

        # add projection to the current wavepath
        ta = np.hstack([t1, tf])
        xa = np.hstack([x1, xf])

        # append to output
        TF.append(t1)
        XF.append(x1)
        TP.append(tf)
        XP.append(xf)

        # look for intersections
        for t2, x2 in zip(T, X):
            # if they are the same, do nothing
            if np.array_equal(x1, x2):
                pass
            else:
                ti, xi = intersection(ta, xa, t2, x2)
                if ti.any():
                    TI.append(ti[0])
                    XI.append(xi[0])
                    WID.append(k)
        k += 1

    # process surf zone edge
    tsurf, xsurf = surfzone_edge(stk_secs, stk_dist, T, X)

    # interp shoreline and swash to the same time interval
    tmax = min(tsurf.max(), tshore.max())
    tmin = max(tsurf.min(), tshore.min())
    tfinal = np.arange(tmin, tmax, 0.1)
    f1 = interpolate.interp1d(tsurf, xsurf, kind="linear")
    f2 = interpolate.interp1d(tshore, xshore, kind="linear")
    xsurf = f1(tfinal)
    xshore = f2(tfinal)

    # intersect shoreline and surf zone edge
    #  with each overruning eventā 
    TShI = []
    XShI = []
    TSfI = []
    XSfI = []
    for ti, xi in zip(TI, XI):

        # find nearest shoreline and surf zone position in time
        idx1 = np.argmin(np.abs(tfinal - ti))

        # append
        TShI.append(tfinal[idx1])
        XShI.append(xshore[idx1])
        TSfI.append(tfinal[idx1])
        XSfI.append(xsurf[idx1])

    # to dataframe
    df = pd.DataFrame(np.vstack([TI, XI, XSfI, XShI, [len(X)] * len(XI),
                                 WID]).T,
                      columns=[
                          "time", "intersection", "surfzone_position",
                          "shoreline_position", "n_waves", "wave_id"
                      ])
    df = df.drop_duplicates()

    # dump to csv
    print(" --- Detected {} overruning events".format(len(df)))
    print(df)
    df.to_csv(H["data"]["output"])

    # plot projections results
    fig, ax = plot(stk_secs, stk_dist, rgb, TF, XF, TP, XP)

    # scatter mergings
    ax.scatter(df["time"],
               df["intersection"],
               120,
               marker="s",
               edgecolor="lawngreen",
               facecolor="none",
               lw=3,
               zorder=50,
               label="Detected bore merging")

    for t, xsf, xsh in zip(TI, XSfI, XShI):
        ax.axvline(t, color="r", lw=2, ls="--", zorder=20)
        ax.scatter(t,
                   xsf,
                   s=100,
                   marker="o",
                   zorder=50,
                   edgecolor="r",
                   facecolor="none",
                   lw=3)
        ax.scatter(t,
                   xsh,
                   s=100,
                   marker="o",
                   zorder=50,
                   edgecolor="r",
                   facecolor="none",
                   lw=3)

    # ax.scatter(tpeaks, xpeaks, 120, marker="o", zorder=100, color="r")

    # plot shoreline and surf zone
    ax.plot(tfinal, xshore, lw=3, ls="-", color="dodgerblue", zorder=25)

    ax.plot(tfinal, xsurf, lw=3, ls="-", color="dodgerblue", zorder=25)

    ax.fill_between(tfinal,
                    xshore,
                    xsurf,
                    facecolor="none",
                    edgecolor="w",
                    lw=2,
                    hatch="//",
                    alpha=0.5,
                    label=r"$\mathcal{X}$",
                    zorder=20)

    # finalize
    if H["parameters"]["savefig"]:
        plt.savefig(H["parameters"]["savefig"], dpi=120)
    if H["parameters"]["show_plot"]:
        plt.show()
    if args.force_show:
        plt.show()

    plt.close("all")

if __name__ == '__main__':
    # main()

    # files
    timestack = "Raw_Data/Timestacks/SevenMileBeach/20180614-001.nc"
    breaking = "Raw_Data/Breaking/SevenMileBeach/20180614-001.dbf"
    shoreline = "Raw_Data/Swash/SevenMileBeach/20180614-001.csv"
    merging = "Raw_Data/WaveOverruning/SevenMileBeach/20180614-001.csv"

    # process timestack
    t, sx, rgb = process_timestack(xr.open_dataset(timestack))
    sx -= sx.min()
    # sx = np.abs(sx)[::-1]
    t = ellapsedseconds(t)

    # process wavepaths
    wp = Dbf5(breaking, codec='utf-8').to_dataframe()
    if "newtime" in wp.columns.values:
        wp = wp[["newtime", "newspace", "wave"]]
        wp.columns = ["t", "x", "wave"]
    T, X, L = optimal_wavepaths(wp,
                                order=2,
                                min_wave_period=1,
                                N=250,
                                project=False,
                                t_weights=1)

    # process shoreline
    df = pd.read_csv(shoreline)
Example #5
0
    # load the dataset
    inp = args.input[0]
    ds = xr.open_dataset(inp)
    var = args.var[0]
    # tol = float(args.tol[0])

    # output locations
    path = args.path[0]
    subprocess.call("mkdir -p {}".format(path), shell=True)

    # get raster
    eta = ds[var].values

    # get coordinates
    time = ellapsedseconds(pd.to_datetime(ds["time"].values).to_pydatetime())
    dist = ds["distance"].values

    istr = inp.split("/")[-1]
    fname = os.path.join(path,
                         istr.strip("nc").strip("/").strip(".") + ".jpeg")

    # dump stack to a jpeg
    fig = plt.figure(frameon=False)
    fig.set_size_inches(len(time) / 100, len(dist) / 100)
    ax = plt.Axes(fig, [0., 0., 1., 1.])
    ax.set_axis_off()
    fig.add_axes(ax)
    ax.pcolormesh(time, dist, ds[var].values.T, cmap="inferno")
    # plt.show()
    fig.savefig(fname, dpi=100)
Example #6
0
def main():
    """Call the main script."""

    # mAHD lookup table - You will need to build yours!
    mAHD = pd.read_csv("/mnt/doc/DataHub/SMB-2018/mAHD.csv")

    # read wave paths
    df = read_wavepaths(args.svg[0])

    # read timestack
    ds = xr.open_dataset(args.netcdf[0])
    eta = ds["eta"].values
    time = ellapsedseconds(pd.to_datetime(ds["time"].values).to_pydatetime())
    dist = ds["distance"].values

    # Tree searsch for coordinates
    I, J = np.meshgrid(np.arange(0, eta.shape[0], 1),
                       np.arange(0, eta.shape[1], 1))
    # X, Y = np.meshgrid(time, dist)
    Tree = Tree = KDTree(np.vstack([I.flatten(), J.flatten()]).T)

    coords = df[["x", "y"]].values

    _, idx = Tree.query(coords, k=1)

    # unravel
    i = np.unravel_index(idx, I.shape)[0]
    j = np.unravel_index(idx, J.shape)[1]

    # get real-world coordinates
    df["time"] = time[j]
    df["dist"] = dist[::-1][i]

    # interpolate
    dt = 0.25
    Itime = []
    Idist = []
    Ilblb = []
    for l, ldf in df.groupby("label"):

        # new time
        tpred = np.arange(ldf["time"].min(), ldf["time"].max() + dt, dt)

        # OLS model
        model = Pipeline([('poly', PolynomialFeatures(degree=2)),
                          ('ols', LinearRegression(fit_intercept=False))])
        model.fit(ldf["time"].values.reshape(-1, 1), ldf["dist"].values)

        # predict
        ypred = model.predict(tpred.reshape(-1, 1))

        # cut at maximun runup
        idx = np.argmin(ypred)

        for t, y in zip(tpred[:idx], ypred[:idx]):
            Itime.append(t)
            Idist.append(y)
            Ilblb.append(l)

    # get a runup height in mAHD
    ImAH = []
    xmAHD = mAHD["distance"].values
    zmAHD = mAHD["height"].values
    for x in Idist:
        idx = np.argmin(np.abs(xmAHD - x))
        ImAH.append(zmAHD[idx])

    # final dataframe
    df = pd.DataFrame()
    df["time"] = Itime
    df["dist"] = Idist
    df["height"] = ImAH
    df["label"] = Ilblb
    df.to_csv(args.output[0])

    # plot
    fig, ax = plt.subplots(figsize=(12, 6))
    ax.pcolormesh(time, dist, eta.T)
    for _, gdf in df.groupby("label"):
        ax.plot(gdf["time"], gdf["dist"], ls="-", lw=2)
    sns.despine()
    ax.set_xlabel("Time [s")
    ax.set_ylabel("Distance [m]")
    ax.grid(color="w", ls="-", lw=2)
    fig.tight_layout()
    plt.savefig(args.output[0].replace(".csv", ".png"))
    plt.close()
Example #7
0
mpl.rcParams['axes.linewidth'] = 2

# quite skimage warningss
warnings.filterwarnings("ignore")

if __name__ == '__main__':
    # main()

    lim = 0.02

    # files
    f = "data/stacks/20180614_A_20180614_1900_1910.nc"

    # process timestack
    ds = xr.open_dataset(f)
    time = ellapsedseconds(ds["time"].values)
    distance = ds["distance"].values
    dx = distance.min()
    eta = ds["eta"].values

    # process wavepaths
    f = "data/tracking/SMB/20180614_A_20180614_1900_1910.dbf"
    wp = Dbf5(f, codec='utf-8').to_dataframe()
    if "newtime" in wp.columns.values:
        wp = wp[["newtime", "newspace", "wave"]]
        wp.columns = ["t", "x", "wave"]

    # unique colours
    colors = plt.cm.get_cmap("tab20", len(np.unique(wp["wave"].values)))

    # track
            print("   - run {} of {}".format(i + 1, NRUNS - 1), end="\r")
            i += 1  # skip the first run

            # open a figure
            fig, (ax1, ax2, ax3, ax4, ax5) = plt.subplots(5,
                                                          1,
                                                          figsize=(9, 12),
                                                          sharex=True)

            # open and process timestack
            f = main_data + timestacks + loc + date + "-{}.nc".format(
                str(i).zfill(3))
            t, x, rgb = process_timestack(xr.open_dataset(f))
            gray = rgb2gray(rgb)
            dtime = t
            t = ellapsedseconds(t)
            x -= x.min()
            pxint = gray[:, int(len(x) / 2)]

            # plot timestack
            ax1.pcolormesh(t, x, gray.T, cmap="Greys_r")

            # open and process wavepaths
            f = main_data + breaking + loc + date + "-{}.dbf".format(
                str(i).zfill(3))
            wp = Dbf5(f, codec='utf-8').to_dataframe()
            if "newtime" in wp.columns.values:
                wp = wp[["newtime", "newspace", "wave"]]
                wp.columns = ["t", "x", "wave"]
            T, X, _ = optimal_wavepaths(wp,
                                        order=2,
Example #9
0
def main():
    """ Run the main script """

    # read parameters from JSON ##
    with open(args.input[0], 'r') as f:
        H = json.load(f)

    # input timestack
    ncin = H["data"]["ncin"]

    # output
    outputto = os.path.abspath(H["data"]["dataout"])
    if not os.path.isdir(outputto):
        os.makedirs(outputto)
    basename = H["data"]["basename"]

    # get analysis parameters

    # which algorithm to use to detect breaking events
    brk_alg = H["parameters"]["breaking_algorithm"]
    if brk_alg not in ["colour", "peaks", "edges", "edges_and_colour"]:
            raise ValueError("Breaking detection algorithm should be \
                             either \'colour\', \'peaks\', \'edges\' or \
                             \'edges_and_colour\'.")
    # metric to use for pixel intensity
    px_mtrc = H["parameters"]["pixel_metric"]
    if px_mtrc not in ["lightness", "intensity"]:
        raise ValueError("Pixel metric should be either \'lightness\' \
                         or \'intensity\'.")
    # colour quantization
    qnt_cl = H["parameters"]["colour_quantization"]
    n_clrs = H["parameters"]["quantization_colours"]
    # peak detection method
    pxp_mtd = H["parameters"]["peak_detection_algorithm"]
    # threshold for peak detection
    px_trsh = H["parameters"]["pixel_threshold"]
    # pixel window for denoise_bilateral
    px_wndw = H["parameters"]["pixel_window"]
    # time window for peak detection
    ts_wndw = H["parameters"]["time_window"]
    # minimum number of samples to be used for the DBSCAN clustering step
    dbs_msp = H["parameters"]["min_samples"]
    # minimum distance be used for the DBSCAN clustering step
    dbs_eps = H["parameters"]["eps"]
    # distance metric for the DBSCAN clustering step
    dbs_mtc = H["parameters"]["dbscan_metric"]
    scipy_dists.append(['cityblock', 'cosine', 'euclidean',
                        'l1', 'l2', 'manhattan'])
    if dbs_mtc not in scipy_dists:
        raise ValueError("Distance \'{}\' is not valid. Please check \
                         scipy.spatial.distance for valid \
                         options.".format(dbs_mtc))
    # resample rule for timeseries
    rs_rule = H["parameters"]["resample_rule"]
    # surf zone limits from input parameters file, if False, will call GUI.
    sz_lims = H["parameters"]["surf_zone_lims"]
    if not sz_lims[0]:
        sz_gui = True
    else:
        sz_gui = False
    # sand colour from input parameter file, if False, will call GUI
    s_color = H["parameters"]["sand_colour"]
    if not s_color[0]:
        sand_colour_gui = True
    else:
        sand_colour_gui = False
        sand = np.array(s_color)
    # water colour from input parameter file, if False, will call GUI
    w_color = H["parameters"]["water_colour"]
    if not w_color[0]:
        water_colour_gui = True
    else:
        water_colour_gui = False
        water = np.array(w_color)
    # foam colour from input parameter file, if False, will call GUI
    f_color = H["parameters"]["foam_colour"]
    if not f_color[0]:
        foam_colour_gui = True
    else:
        foam_colour_gui = False
        foam = np.array(f_color)
    # number of clicks for GUI
    n_clks = H["parameters"]["nclicks"]
    # pixel window for GUI
    px_wind = H["parameters"]["gui_window"]
    # try to fix bad pixel values
    gap_val = H["parameters"]["gap_value"]
    # plot?
    plot = H["parameters"]["plot"]

    # process timestack

    # read timestack
    ds = xr.open_dataset(ncin)
    # load timestack variables #
    x = ds["x"].values
    y = ds["y"].values
    # compute distance from shore
    stk_len = np.sqrt(((x.max() - x.min()) * (x.max() - x.min())) +
                      ((y.max() - y.min()) * (y.max() - y.min())))
    crx_dist = np.linspace(0, stk_len, len(x))
    # get timestack times
    stk_time = pd.to_datetime(ds["time"].values).to_pydatetime()
    stk_secs = ellapsedseconds(stk_time)
    # get RGB values
    rgb = ds["rgb"].values
    # try to fix vertical grey strips, if any
    ifix, jfix = np.where(np.all(rgb == gap_val, axis=-1))
    rgb[ifix, jfix, :] = rgb[ifix-2, jfix-2, :]

    # get analysis limits
    if sz_gui:
        crx_start, crx_end = get_analysis_domain(stk_secs, crx_dist, rgb)
    else:
        crx_start = sz_lims[0]
        crx_end = sz_lims[1]

    # run the analysis
    if brk_alg in ["colour", "edges_and_colour"]:
        # sand
        if sand_colour_gui:
            df_sand = get_training_data(rgb, regions=1, region_names=["sand"],
                                        iwin=px_wind, jwin=px_wind,
                                        nclicks=n_clks)
            _, _, sand = get_dominant_colour(df_sand, n_colours=8)
            sand = sand[0]
        # water
        if water_colour_gui:
            df_water = get_training_data(rgb, regions=1,
                                         region_names=["water"],
                                         iwin=px_wind, jwin=px_wind,
                                         nclicks=n_clks)
            _, _, water = get_dominant_colour(df_water, n_colours=8)
            water = water[0]
        # foam
        if foam_colour_gui:
            df_foam = get_training_data(rgb, regions=1,
                                        region_names=["foam"],
                                        iwin=px_wind, jwin=px_wind,
                                        nclicks=n_clks)
            _, _, foam = get_dominant_colour(df_foam, n_colours=8)
            foam = foam[0]

        # build colour structures
        train_colours = {'labels': [0, 1, 2],
                         'aliases': ["sand", "water", "foam"],
                         'rgb': [sand, water, foam],
                         'target': 2}
    else:
        train_colours = None

    # detect breaking events
    times, breakers = detect_breaking_events(stk_time, crx_dist, rgb,
                                             crx_start=crx_start,
                                             crx_end=crx_end,
                                             tswindow=ts_wndw,
                                             pxwindow=px_wndw,
                                             px_mtrc=px_mtrc,
                                             resample_rule=rs_rule,
                                             algorithm=brk_alg,
                                             peak_detection=pxp_mtd,
                                             posterize=qnt_cl,
                                             ncolours=n_clrs,
                                             threshold=px_trsh,
                                             colours=train_colours,
                                             fix_constrast=True)
    # DBSCAN
    print("  + >> clustering wave paths")
    df_dbscan = dbscan(times, breakers, dbs_eps, dbs_msp, dbs_mtc)

    # Outputs
    print("  + >> writting output")
    if os.path.isfile(os.path.join(outputto, basename+".csv")):
        overwrite = input("  |- >> overwrite output?")
        if overwrite[0] == "y":
            write_outputs(outputto, basename, stk_secs,
                          crx_dist, rgb, df_dbscan)
        else:
            print("  - >> warning: not writing outputs")
    else:
        write_outputs(outputto, basename, stk_secs, crx_dist, rgb, df_dbscan)

    if plot:
        print("  + >> plotting")
        fig = plt.figure(figsize=(16, 6))
        ax1 = fig.add_axes([0.1, 0.1, 0.7, 0.8])
        ax2 = fig.add_axes([0.85, 0.1, 0.1, 0.8])

        # get unique colours for each wave
        colors = plt.cm.get_cmap("Set1",
                                 len(np.unique(df_dbscan["wave"].values)))

        # get traning colours
        if train_colours:
            for label, region, color in zip(train_colours["labels"],
                                            train_colours["aliases"],
                                            train_colours["rgb"]):
                ax2.scatter(1, label, 1000, marker="s", color=color/255,
                            label=region, edgecolor="k", lw=2)
            # set axis
            ax2.set_yticks(train_colours["labels"])
            ax2.set_yticklabels(train_colours["aliases"])
            ax2.set_ylim(-0.25, len(train_colours["aliases"])-1+0.25)
            for tick in ax2.get_yticklabels():
                tick.set_rotation(90)
            ax2.xaxis.set_major_locator(plt.NullLocator())
        else:
            for label, region, color in zip([0, 1, 2],
                                            ['N/A', 'N/A', 'N/A'],
                                            [[1, 1, 1], [1, 1, 1], [1, 1, 1]]):
                ax2.scatter(1, label, 1000, marker="s",
                            color=[1, 1, 1], label=region, edgecolor="k", lw=2)
            # set axis
            ax2.set_yticks([0, 1, 2])
            ax2.set_yticklabels(['N/A', 'N/A', 'N/A'])
            ax2.set_ylim(-0.25, len([0, 1, 2])-1+0.25)
            for tick in ax2.get_yticklabels():
                tick.set_rotation(90)
            ax2.xaxis.set_major_locator(plt.NullLocator())
        ax2.set_title("Training colours")

        # plot timestack
        im = ax1.pcolormesh(stk_secs, crx_dist, rgb.mean(axis=2).T)
        # set timestack to  to true color
        rgba = construct_rgba_vector(np.rollaxis(rgb, 1, 0), n_alpha=0)
        rgba[rgba > 1] = 1
        im.set_array(None)
        im.set_edgecolor('none')
        im.set_facecolor(rgba)

        # plot analysis limits
        ax1.axhline(y=crx_start, lw=3, color="darkred", ls="--")
        ax1.axhline(y=crx_end, lw=3, color="darkred", ls="--")

        # plot all identified breaking events
        ax1.scatter(times, breakers, 20, color="k", alpha=0.5, marker="+",
                    lw=1, zorder=10)

        # DBSCAN plot
        k = 0
        for label, group in df_dbscan.groupby("wave"):
            if label > -1:
                # get x and y for regression
                xwave = group["time"].values
                ywave = group["breaker"].values
                # scatter points
                ax1.scatter(xwave, ywave, 40, label="wave " + str(label),
                            c=colors(k), alpha=0.5, marker="o", edgecolor="k")
                bbox = dict(boxstyle="square",
                            ec="k", fc="w", alpha=0.5)
                ax1.text(xwave.min(), ywave.max(), "wave " + str(k),
                         rotation=45, fontsize=8, zorder=10, bbox=bbox)
                k += 1

        # set axes limits
        ax1.set_xlim(xmin=stk_secs.min(), xmax=stk_secs.max())
        ax1.set_ylim(ymin=crx_dist.min(), ymax=crx_dist.max())

        # set axes labels
        ax1.set_ylabel(r"Cross-shore distance $[m]$", fontsize=16)
        ax1.set_xlabel(r"Time $[s]$", fontsize=16)

        # seaborn despine
        sns.despine(ax=ax2, bottom=True)
        sns.despine(ax=ax1)

        # fig.tight_layout()

        plt.show()