import copy

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
import seaborn as sns
import pandas as pd
from scipy.io import loadmat
from scipy.signal import savgol_filter
from scipy import fftpack
from scipy import signal
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from scipy.stats.kde import gaussian_kde
from matplotlib.ticker import MaxNLocator
from matplotlib import animation, rc
import hdf5storage
import pwlf
import time
from IPython.display import display, clear_output
from sklearn.preprocessing import StandardScaler
from obspy.signal import cross_correlation
import copy

def node_reduction(data: np.ndarray, n_clusters: int, max_n_clusters=20, n_components=12, sample_labels=None, plot=True):
    """ Reduces node dimension to n_clusters.

    :param data: Data of size (n_samples, n_nodes)
    :param n_clusters: Number of clusters
    :param max_n_clusters: Maximum number of clusters for evaluation
    :param n_components: Number of PCA components
    :return: df: DataFrame
    """
    n_samples = data.shape[0]
    n_nodes = data.shape[1]

    # Perform PCA for dimensionality reduction
    print('Perform PCA')
    pca = PCA(n_components=n_components)
    pca_result = pca.fit_transform(data.T)

    if plot:
        fig = plt.figure(figsize=(7,3.4))
        gs = fig.add_gridspec(nrows=2, ncols=21)
        ax = [[] for i in range(3)]
        cm = plt.cm.get_cmap('viridis')

        ax[0] = fig.add_subplot(gs[:1, :6])
        plt.scatter(pca_result[:, 0], pca_result[:, 1], c=pca_result[:, 2], cmap=cm)
        plt.clim(np.min(pca_result[:, 2]), np.max(pca_result[:, 2]))
        ax[0].set_xlabel('1st PC [a.u.]')
        ax[0].set_ylabel('2nd PC [a.u.]')
        ax[0].spines['right'].set_visible(False), ax[0].spines['top'].set_visible(False)
        cb = plt.colorbar(ax=ax[0])
        cb.set_label(label='3rd PC [a.u.]')

    # Check score of K-Means for max_n_clusters
    n_clusters_list = list(range(1, max_n_clusters + 1))
    score = []
    for i, val in enumerate(n_clusters_list):
        km = KMeans(n_clusters=val)
        clusters = km.fit(pca_result)
        score.append(clusters.inertia_ / n_nodes)

    if plot:
        ax[1] = fig.add_subplot(gs[:1, 9:14])
        plt.plot(n_clusters_list, score, c='black')
        ax[1].set_xlabel('Nr. of clusters')
        ax[1].set_ylabel('MSE [a.u.]')
        #plt.title('Distance to nearest cluster center')
        ax[1].xaxis.set_major_locator(MaxNLocator(integer=True))
        ax[1].spines['right'].set_visible(False), ax[1].spines['top'].set_visible(False)

    # Perform K-Means on data
    print('Perform KMeans')
    km = KMeans(n_clusters=n_clusters)
    clusters = km.fit(pca_result)

    df = pd.DataFrame(pca_result)
    df['Cluster'] = clusters.labels_

    if plot:
        ax[2] = fig.add_subplot(gs[:1, 16:])
        palette = sns.color_palette('muted', n_clusters)
        sns.scatterplot(x=0, y=1, data=df, s=50, hue='Cluster', style='Cluster', palette=palette)
        ax[2].set_xlabel('1st PC [a.u.]')
        ax[2].set_ylabel('2nd PC [a.u.]')
        ax[2].spines['right'].set_visible(False), ax[2].spines['top'].set_visible(False)
        ax[2].get_legend().remove()
        #plt.title('Clustering of the first two princ. components')
        fig.savefig('figures/Ch2_NodeReduction.png', dpi=300)

    # Make DataFrame
    df = pd.DataFrame()
    df['sample'] = np.tile(np.arange(0, n_samples), n_nodes)
    df['node'] = np.repeat(np.arange(0, n_nodes), n_samples)
    df['cluster'] = np.repeat(list(clusters.labels_), n_samples)
    df['value'] = data.T.flatten()
    if sample_labels is not None:
        df['sample_label'] = np.tile(sample_labels, n_nodes)

    return df

def acf(data: np.ndarray, n_lags, partial=False):
    """ Auto Correlation Function.

    :param data: Array of size (n_samples, n_nodes)
    :param n_lags: Lag size [samples]
    :param partial: Choose for partial or non-partial
    :return:
    """
    n_samples = data.shape[0]
    n_nodes = data.shape[1]

    lags = [i for i in range(n_lags)]
    acf_list = []

    if partial:
        for i in range(n_nodes):
            corr = [1. if lag == 0 else np.corrcoef(data[lag:, i], data[:-lag, i])[0][1] for lag in lags]
            acf_list.append(np.array(corr))
            acf_array = np.array(acf_list).T
    else:
        for i in range(n_nodes):
            mean = np.mean(data[:, i])
            var = np.var(data[:, i])
            data_p = data[:, i] - mean
            corr = [1. if lag == 0 else np.sum(data_p[lag:] * data_p[:-lag]) / n_samples / var for lag in lags]
            acf_list.append(np.array(corr))
            acf_array = np.array(acf_list).T

    # Confidence
    conf = (1.96/np.sqrt(n_samples), -1.96/np.sqrt(n_samples))

    return acf_array, conf

def fft(data: np.ndarray, fs: float, downscale_factor=None):
    """

    :param data:
    :param fs:
    :param downscale_factor:
    :return:
    """
    n_samples = data.shape[0]
    n_nodes = data.shape[1]

    spect = np.zeros((n_samples, n_nodes))
    for i in range(n_nodes):
        print('Compute FFT of channel: ' + str(i))
        spect[:, i] = np.abs(fftpack.fft(data[:, i]))
    freq = fftpack.fftfreq(n_samples) * fs

    # Cut negative frequencies
    spect_pos = spect[:len(freq[freq >= 0])]
    freq_pos = freq[freq >= 0]

    if downscale_factor is not None:
        spect_resampled = []
        print('Downscale FFTs')
        for i in range(n_nodes):
            spect_filtered = savgol_filter(np.abs(spect_pos[:, i]), int(n_samples/500)-2, 3)
            spect_resampled.append(signal.resample_poly(spect_filtered, up=3, down=3*downscale_factor))
        freq_resampled = signal.resample_poly(freq_pos, up=3, down=3*downscale_factor)

        return np.array(spect_resampled).T, freq_resampled

    return spect_pos, freq_pos

def distribution(data: np.ndarray, xlim: tuple=None):
    """

    :param data:
    :param xlim:
    :return:
    """
    n_nodes = data.shape[1]

    if xlim is None:
        xlim = (np.min(data), np.max(data))
    kde_x = np.linspace(xlim[0], xlim[1], 200)
    density = []
    for i in range(n_nodes):
        print('Compute KDE of channel: ' + str(i))
        kde = gaussian_kde(data[:, i])
        density.append(kde(kde_x))

    return kde_x, np.array(density).T

def plot_distribution(data: np.ndarray, xlim: tuple=None, n_clusters: int=6, qq_plot: bool=True, title: str=''):
    print('Status: Get distributions.')
    kde_x, kde = distribution(data, xlim=xlim)

    print('Status: Reduction of nodes.')
    df_kde = node_reduction(kde, n_clusters=n_clusters, sample_labels=kde_x, plot=False)

    sns.set_style('whitegrid')
    palette = sns.color_palette('muted', n_clusters)

    if qq_plot:
        normal = np.sort(np.random.normal(size=data.shape[0]))
        df_qq = node_reduction(np.sort(data, axis=0), n_clusters=n_clusters, sample_labels=normal)

        fig = plt.figure(figsize=(8, 16))
        gs = fig.add_gridspec(nrows=int(n_clusters / 2 + 1), ncols=2)
        ax = [[] for i in range(n_clusters)]
        ax[0] = fig.add_subplot(gs[:1, :])
        sns.lineplot(x='sample_label', y='value', data=df_kde, hue='cluster', palette=palette, ax=ax[0])
        ax[0].set_xlabel('Magnitude')
        ax[0].set_ylabel('Density')
        ax[0].set_title(title)

        row = np.repeat(np.arange(1, int(n_clusters / 2 + 2)), 2)
        col = np.tile(np.array([[0, 1], [1, 2]]), 3)
        for i in range(n_clusters):
            ax[i] = fig.add_subplot(gs[row[i]:row[i + 2], col[0, i]:col[1, i]])
            for k in range(df_qq.shape[0]):
                if df_qq['cluster'][k] == i:
                    node = df_qq['node'][k]
                    break
            sns.scatterplot(x='sample_label', y='value', data=df_qq.where(df_qq['node'] == node),
                            ax=ax[i], label='from cluster ' + str(i), edgecolor=None, color=palette[i])
            ax[i].set_xlabel('Theoretical quantiles')
            ax[i].set_ylabel('Sample quantiles')
            ax[i].legend()
        plt.tight_layout()

    else:
        plt.figure(figsize=(6, 4))
        sns.lineplot(x='sample_label', y='value', data=df_kde, hue='cluster', palette=palette)
        plt.xlabel('Magnitude')
        plt.ylabel('Density')
        plt.title('PCA on density distributions')

    print('Status: Save plot.')
    #plt.savefig('../code/figures/Ch2_DataDistribution' + title + '.png')

def acf_plot(data: np.ndarray, n_clusters=5, n_lags=1000):
    """

    :param data:
    :param n_clusters:
    :param n_lags:
    :return:
    """
    acf, conf = acf(data, n_lags=n_lags)
    df = node_reduction(acf, n_clusters)
    plt.figure(figsize=(12, 8))
    sns.set_style('whitegrid')
    plt.xlim(0, n_lags)
    sns.lineplot(x='sample', y='value', data=df, hue='cluster', palette='colorblind')
    plt.plot([0, n_lags], [conf[1], conf[1]], color='black', ls='--')
    plt.plot([0, n_lags], [conf[0], conf[0]], color='black', ls='--')
    plt.xlabel('Lag')
    plt.ylabel('Correlation')
    plt.title('Autocorrelation')
    plt.savefig('../doc/figures/preprocess_acf.png')

def fft_plot(data: np.ndarray, n_clusters=5):
    """

    :param data:
    :param n_clusters:
    :return:
    """
    spect, freq = pre.fft(train_set.numpy(), fs=512, downscale_factor=20)
    df = pre.node_reduction(spect, n_clusters, sample_labels=freq)
    plt.figure(figsize=(12, 8))
    sns.set_style('whitegrid')
    # for i in range(60):
    #    plt.plot(freq, spect[:, i], color='tab:blue')
    sns.lineplot(x='sample_label', y='value', data=df, hue='cluster', palette='colorblind')

    plt.plot([120, 120], [0, 800], color='black', ls='--')
    plt.xlabel('Frequency [Hz]')
    plt.ylabel('Amplitude [-]')
    title = ['High frequency spectrum', 'Low frequency spectrum']
    save_name = ['fft_high', 'fft_low']
    xlim = [150, 25]
    for i in range(2):
        plt.xlim(0, xlim[i])
        plt.ylim(0, 800)
        plt.title(title[i])
        if i == 1:
            plt.plot([.5, .5], [0, 800], color='black', ls='--')
        plt.savefig('../doc/figures/preprocess_' + save_name[i])

def anim_connectome(patient_ID: str, hour: str, sperseg: float, soverlap: float, save=False, anim=True, fps=10):
    """

    """
    # Import signal
    ID = patient_ID
    h = hour
    data = hdf5storage.loadmat('../data/ID' + ID + '_' + h + '.mat')
    info = loadmat('../data/ID' + ID + '_info.mat')
    data = data['EEG']
    fs = float(info['fs'])

    nperseg = int(sperseg * fs)
    noverlap = int(soverlap * fs)

    corrmats = np.zeros((data.shape[0], data.shape[0], int((data.shape[1] - nperseg) / (nperseg - noverlap))))
    for i in range(corrmats.shape[2]):
        corrmats[:, :, i] = np.corrcoef(data[:, int(i * (nperseg - noverlap)):int(i * (nperseg - noverlap) + nperseg)])
    corr_val = np.mean(np.mean(np.abs(corrmats), axis=0), axis=0)

    if save:
        np.save('../data/corrmat_ID' + ID + '_' + h + '.npy', corrmats)
        np.save('../data/corrmean_ID' + ID + '_' + h + '.npy', corr_val)

    if anim:
        # equivalent to rcParams['animation.html'] = 'html5'
        rc('animation', html='html5')

        def update(frame):
            level.set_array(corrmats[:, :, frame].ravel())
            dot.set_data(t[frame], corr_val[frame])
            line.set_data([t[frame], t[frame]], [0, 30])
            return level

        corr = np.ones((corrmats.shape[0], corrmats.shape[0]))
        sns.set_style('whitegrid')

        fig = plt.figure(figsize=(10, 10))
        gs = fig.add_gridspec(2, 2)

        ax0 = fig.add_subplot(gs[:1, :])
        f, t, Sxx = signal.spectrogram(data[0, :], fs, nperseg=nperseg, noverlap=noverlap)
        t = t[:-1] / 60
        ax0.pcolormesh(t, f, Sxx[:, :-1], vmin=0, vmax=20, cmap='viridis')
        ax0.set_title('ID' + ID + ' h' + h + ' Spectrogram of Channel 00')
        ax0.set_ylabel('Frequency [Hz]')
        ax0.set_ylim(0, 30)
        ax0.set_xlabel('Time [min]')
        line, = ax0.plot([], [], color='red')

        ax1 = fig.add_subplot(gs[1:, :1])
        level = ax1.pcolormesh(corr, cmap='bwr', vmin=-1, vmax=1)
        ax1.invert_yaxis()
        ax1.set_title('Correlation matrix')

        ax2 = fig.add_subplot(gs[1:, 1:])
        ax2.plot(t, corr_val)
        ax2.set_title('Mean abs. correlation')
        ax2.set_xlim(0, t[-1])
        ax2.set_ylim(0, np.max(corr_val) + 0.2)
        ax2.set_xlabel('Time [min]')
        dot, = ax2.plot([], [], 'o', color='red')

        ani = animation.FuncAnimation(fig, update, frames=corrmats.shape[2], interval=20)

        ani.save('../doc/figures/corr_ID' + ID + '_' + h + '.gif', writer='imagemagick', fps=fps)

def nonlin_corr_metric(x: np.ndarray, y: np.ndarray, t_shift: int):
    """
    :param x:
    :param y:
    :param t_shift: In samples
    :param save_name:
    :return:
    """
    break_locs = np.linspace(-5, 5, 50)
    h2_shift = []  # Array with correlation per time shift
    r2_shift = []  # Array with correlation per time shift

    for i in range(2 * t_shift):
        # Cut data to time shift
        y_data = y[2 * t_shift - i: len(y)-i]
        x_data = x[t_shift: -t_shift]
        # Normalize data
        y_norm = (y_data - np.mean(y_data)) / np.std(y_data)
        x_norm = (x_data - np.mean(x_data)) / np.std(x_data)
        # Non-linear fit
        g = pwlf.PiecewiseLinFit(x_norm, y_norm)
        g.fit_with_breaks(break_locs)
        expect = np.mean(np.abs(y_norm - g.predict(x_norm)))  # Uniform distribution
        h2_shift.append(1 - expect**2 / np.var(y_norm))
        # Linear fit
        r2_shift.append(np.corrcoef(x_norm, y_norm)[0, 1]**2)

    h2 = h2_shift[int(np.argmax(np.asarray(h2_shift)))]
    h2_dt = int(np.argmax(np.asarray(h2_shift)) - t_shift)  # dt for best correlation from x to y
    r2 = r2_shift[int(np.argmax(np.asarray(r2_shift)))]
    r2_dt = int(np.argmax(np.asarray(r2_shift)) - t_shift)  # dt for best correlation from x to y

    return r2, h2, r2_dt, h2_dt, r2_shift, h2_shift

def plot_nonlin_corr(r2, r2_dt=None, h2=None, h2_dt=None, save_name='default'):
    fs = 256

    if h2 is not None:
        sns.set_style('ticks')
        fig = plt.figure(figsize=(7.4, 3))
        gs = fig.add_gridspec(nrows=1, ncols=2)
        cmap = 'viridis'

        ax0 = fig.add_subplot(gs[:, :1])
        sns.heatmap(r2, cmap=cmap, cbar_kws={'label': '$r_2$ [-]'})
        #ax0.set_title('r2 linear correlation')
        #ax0.get_xaxis().set_visible(False)

        ax1 = fig.add_subplot(gs[:, 1:])
        sns.heatmap(h2, cmap=cmap, cbar_kws={'label': '$h_2$ [-]'})
        #ax1.set_title('h2 non-linear correlation')
        #ax1.get_xaxis().set_visible(False), ax1.get_yaxis().set_visible(False)

        #ax2 = fig.add_subplot(gs[1:, :1])
        #vlim = np.max(np.abs(r2_dt))
        #sns.heatmap(r2_dt / fs * 1000, cmap='bwr', vmin=-vlim, vmax=vlim)
        #ax2.set_title('r2 time shift [ms]')

        #ax3 = fig.add_subplot(gs[1:, 1:])
        #vlim = np.max(np.abs(h2_dt))
        #sns.heatmap(h2_dt / fs * 1000, cmap='bwr', vmin=-vlim, vmax=vlim)
        #ax3.set_title('h2 time shift [ms]')
        #ax3.get_yaxis().set_visible(False)

        ax0.set_xlabel('Channel index [-]')
        ax0.set_ylabel('Channel index [-]')
        ax1.set_xlabel('Channel index [-]')
        ax1.set_ylabel('Channel index [-]')

    else:
        fig = plt.figure(figsize=(8, 10))
        gs = fig.add_gridspec(2, 1)
        cmap = 'viridis'

        ax0 = fig.add_subplot(gs[:1, :1])
        sns.heatmap(r2, cmap=cmap)
        ax0.set_title('r2 linear correlation')
        ax0.get_xaxis().set_visible(False)

        #ax2 = fig.add_subplot(gs[1:, :1])
        #vlim = np.max(np.abs(r2_dt))
        #sns.heatmap(r2_dt / fs * 1000, cmap='bwr', vmin=-vlim, vmax=vlim)
        #ax2.set_title('r2 time shift [ms]')

    plt.tight_layout()
    plt.savefig('../doc/figures/fc_' + save_name + '.png', dpi=300)
    plt.close()

def nonlin_corr(patient_id: str, time_begin: list, duration: float,
                           t_shift=0.2, plot_name=None):
    """

    :param patient_id:
    :param time_begin: List with [hour, minute].
    :param duration: In seconds.
    :param t_shift: In seconds.
    :param plot_name:
    :return:
    """
    # Load and prepare data
    data_mat = loadmat('../data/' + patient_id + '_' + str(time_begin[0]) + 'h.mat')
    info_mat = loadmat('../data/' + patient_id + '_info.mat')
    fs = float(info_mat['fs'])
    sample_begin = int(time_begin[1] * 60 * fs)
    sample_end = sample_begin + int(duration * fs)
    data_raw = data_mat['EEG'][:20, sample_begin:sample_end].transpose()
    # Resample to 256 Hz
    data_raw = signal.resample(data_raw, num=int(data_raw.shape[0] / fs * 256), axis=0)
    fs = 256

    # Compute correlation
    h2 = np.zeros((data_raw.shape[1], data_raw.shape[1]))
    r2 = np.zeros((data_raw.shape[1], data_raw.shape[1]))
    h2_dt = np.zeros((data_raw.shape[1], data_raw.shape[1]))
    r2_dt = np.zeros((data_raw.shape[1], data_raw.shape[1]))
    #h2_shift_list = []
    #r2_shift_list = []

    start_time = time.time()

    for i in range(data_raw.shape[1]):
        for j in range(data_raw.shape[1]):
            r2[i, j], h2[i, j], r2_dt[i, j], h2_dt[i, j], _, _ = nonlin_corr_metric(
                data_raw[:, i], data_raw[:, j], t_shift=int(t_shift * fs))
            # h2_shift_list.append(h2_shift)
            # r2_shift_list.append(r2_shift)
        t_rem = (time.time() - start_time) / (i + 1) * (data_raw.shape[1] - i + 1)
        clear_output()
        print(f'Computed columns: {i}/{data_raw.shape[1]} | Time remaining [min]: {t_rem / 60:.3}')

    save_name = '../data/fc_' + patient_id + '_' + str(time_begin[0]) + 'h' + str(time_begin[1]) + 'min_final'
    np.save(save_name + '_r2.npy', r2)
    np.save(save_name + '_r2_dt.npy', r2_dt)
    np.save(save_name + '_h2.npy', h2)
    np.save(save_name + '_h2_dt.npy', h2_dt)

    if plot_name is not None:
        plot_nonlin_corr(r2=r2, h2=h2, r2_dt=r2_dt, h2_dt=h2_dt, save_name=plot_name)

def lin_corr(patient_id: str, time_begin: list, duration: float, t_lag=0.7, critical_corr=0.7):
    """

    :param patient_id:
    :param time_begin: List with [hour, minute].
    :param duration: In seconds.
    :param t_lag: In seconds.
    :param critical_corr:
    :return:
    """
    # Load and prepare data
    data_mat = loadmat('../data/' + patient_id + '_' + str(time_begin[0]) + 'h.mat')
    info_mat = loadmat('../data/' + patient_id + '_info.mat')
    fs = float(info_mat['fs'])
    sample_begin = int(time_begin[1] * 60 * fs)
    sample_end = sample_begin + int(duration * fs)
    data_raw = data_mat['EEG'][:, sample_begin:sample_end].transpose()

    # fs = 1024
    # t = np.linspace(0, 5*60, 5*60*fs)
    # f = [.5, 4, 20, 50, 120]
    # data_raw = np.zeros((len(t), len(f)))
    # for i, val in enumerate(f):
    #     data_raw[:, i] = 100 * np.sin(2 * np.pi * val * t)

    n_lag = int(t_lag * fs)
    factor = 0.99

    # Compute normalized cross correlation (NCC)
    cctl = np.zeros((data_raw.shape[1], data_raw.shape[1], (n_lag * 2) + 1))
    for from_ in range(data_raw.shape[1]):
        for to_ in range(data_raw.shape[1]):
            x = data_raw[:, to_]
            y = data_raw[n_lag:-n_lag, from_]
            cctl[from_, to_, :] = cross_correlation.correlate_template(x, y)

    # Calculate peak cross correlation (cc) and corresponding time lag (tl)
    sign = np.sign(np.max(cctl, axis=2) - np.abs(np.min(cctl, axis=2)))
    cc = np.multiply(np.max(np.abs(cctl), axis=2), sign)
    mask = np.where(np.abs(cc) > critical_corr, 1, np.nan)
    tl_n = np.argmax(np.abs(cctl), axis=2)
    tl = (tl_n - n_lag) * mask / fs * 1000  # in [ms]
    tl_no_mask = (tl_n - n_lag) / fs * 1000  # in [ms], used for plots

    # Calculate mean tau
    # Tile and stack values for future operations
    tl_n_stacked = np.dstack([tl_n] * cctl.shape[2])
    arg_tau_stacked = factor * np.dstack([cc] * cctl.shape[2])
    mask_stacked = np.dstack([np.where(np.abs(cc) > critical_corr, 1, 0)] * cctl.shape[2])
    t_indices_tiled = np.tile(np.arange(0, cctl.shape[2]), (cctl.shape[0], cctl.shape[0], 1))
    # Get indices of values close to factor of peak cross correlation
    close_indices = np.isclose(cctl, arg_tau_stacked, rtol=1e-1) * t_indices_tiled
    # Create mask to separate negative and positive tau
    higher_tau_mask = np.where(close_indices - tl_n_stacked > 0, 1, 0)
    lower_tau_mask = np.where((tl_n_stacked - close_indices > 0) & (close_indices != 0), 1, 0)
    # Eliminate possible third occurrence of np.isclose() to factor
    higher_edge_indices = np.where(np.diff(higher_tau_mask) == -1, 1, 0) * t_indices_tiled[:, :, :-1]
    higher_edge_indices = np.min(np.where(higher_edge_indices == 0, np.inf, higher_edge_indices), axis=2)
    higher_third_occ_mask = np.where(t_indices_tiled > np.dstack([higher_edge_indices] * cctl.shape[2]), 0, 1)
    lower_edge_indices = np.where(np.diff(lower_tau_mask) == 1, 1, 0) * t_indices_tiled[:, :, :-1]
    lower_edge_indices = np.max(lower_edge_indices, axis=2)
    lower_third_occ_mask = np.where(t_indices_tiled < np.dstack([lower_edge_indices] * cctl.shape[2]), 0, 1)
    # Apply masks (apply mask for critical correlation separately to get all taus for plots)
    higher_tau_masked_all = close_indices * higher_tau_mask * higher_third_occ_mask
    higher_tau_masked = higher_tau_masked_all * mask_stacked
    lower_tau_masked_all = close_indices * lower_tau_mask * lower_third_occ_mask
    lower_tau_masked = lower_tau_masked_all * mask_stacked
    # Compute median along time lag axis and ignore zero entries
    higher_tau = np.ma.median(np.ma.masked_where(higher_tau_masked == 0, higher_tau_masked), axis=2).filled(0)
    lower_tau = np.ma.median(np.ma.masked_where(lower_tau_masked == 0, lower_tau_masked), axis=2).filled(0)
    # Get taus without mask for critical correlation for plots
    higher_tau_all = np.ma.median(np.ma.masked_where(higher_tau_masked_all == 0, higher_tau_masked_all)
                                  , axis=2).filled(0)
    higher_tau_all = (higher_tau_all - n_lag) / fs * 1000  # in [ms]
    lower_tau_all = np.ma.median(np.ma.masked_where(lower_tau_masked_all == 0, lower_tau_masked_all)
                                 , axis=2).filled(0)
    lower_tau_all = (lower_tau_all - n_lag) / fs * 1000  # in [ms]
    # Calculate mean distance for tau to cc
    tau_n = (higher_tau - lower_tau) / 2
    tau = np.where(tau_n == 0, np.nan, tau_n) / fs * 1000  # in [ms]
    tau = np.abs(tau)  # this line has to be removed in the future

    # Additional masks for plots (diagonal, upper triangle, ...)
    tl_masked = tl.copy()
    cc_masked = cc.copy()
    np.fill_diagonal(tl_masked, np.nan)
    np.fill_diagonal(cc_masked, np.nan)
    cc_masked[np.triu_indices(cc_masked.shape[0], k=1)] = np.nan

    # Plot cc, tl and tau
    # General settings
    sns.set_style('ticks')
    fig = plt.figure(figsize=(7, 9))
    gs = fig.add_gridspec(3, 10)
    cmap_div = copy.copy(mpl.cm.get_cmap('seismic'))
    cmap_div.set_bad('dimgrey')
    cmap_uni = copy.copy(mpl.cm.get_cmap('viridis'))
    cmap_uni.set_bad('dimgrey')

    # Subplot: Peak cross correlation
    ax0 = fig.add_subplot(gs[:1, :5])
    sns.heatmap(cc_masked, cmap=cmap_div, vmin=-1, vmax=1, cbar_kws={'label': 'Peak cross correlation [-]'})
    ax0.set_xlabel('Channel index [-]'), ax0.set_ylabel('Channel index [-]')

    # Subplot: Histogram of peak cross correlation
    sns.set_style('white')
    ax1 = fig.add_subplot(gs[:1, 6:])
    ax1.add_patch(mpl.patches.Rectangle(xy=(-critical_corr, 0), width=2*critical_corr, height=330,
                                        facecolor='red', edgecolor='red', alpha=.1))
    sns.distplot(cc_masked, kde=False)
    ymin, ymax = ax1.get_ylim()
    xmin, xmax = ax1.get_xlim()
    #label = 'Critical corr. = +/- ' + str(critical_corr)
    #plt.plot([critical_corr, critical_corr], [ymin, ymax], linestyle='--', color='black', label=label)
    #plt.plot([-critical_corr, -critical_corr], [ymin, ymax], linestyle='--', color='black')
    ax1.set_xlim(xmin, xmax), ax1.set_ylim(ymin, 330)
    #plt.legend()
    ax1.set_xlabel('Peak cross correlation [-]'), ax1.set_ylabel('Nr. of occurrence [-]')
    ax1.spines['right'].set_visible(False), ax1.spines['top'].set_visible(False)

    # Subplot: Time lag
    sns.set_style('ticks')
    ax2 = fig.add_subplot(gs[1:2, :5])
    vlim = np.nanmax(np.abs(tl))
    sns.heatmap(tl_masked, cmap=cmap_div, vmin=-vlim, vmax=vlim, cbar_kws={'label': 'Corresponding time lag [ms]'})
    ax2.set_xlabel('Channel index [-]'), ax2.set_ylabel('Channel index [-]')

    # Subplot: Histogram of time lag
    sns.set_style('white')
    ax3 = fig.add_subplot(gs[1:2, 6:])
    sns.distplot(tl_masked, kde=False, bins=[(i - 11) * 2 - 1 for i in range(24)])
    ax3.set_xlim(-22, 22)
    ax3.set_xlabel('Time lag [ms]'), ax3.set_ylabel('Nr. of occurrence [-]')
    ax3.spines['right'].set_visible(False), ax3.spines['top'].set_visible(False)

    # Subplot: Tau
    sns.set_style('ticks')
    ax4 = fig.add_subplot(gs[2:, :5])
    sns.heatmap(tau, cmap=cmap_uni, cbar_kws={'label': 'Corresponding $\\tau$ [ms]'})
    ax4.set_xlabel('Channel index [-]'), ax4.set_ylabel('Channel index [-]')

    # Subplot: Histogram of tau
    sns.set_style('white')
    ax5 = fig.add_subplot(gs[2:, 6:])
    auto_corr = tau.copy()
    auto_corr[np.diag_indices(auto_corr.shape[0])] = np.nan
    sns.distplot(auto_corr, kde=False, label='Cross correlated')
    sns.distplot(np.diagonal(tau), kde=False, label='Auto correlated', bins=10)
    plt.legend(loc='upper left', frameon=False)
    ax5.set_xlabel('$\\tau$ [ms]'), ax5.set_ylabel('Nr. of occurrence [-]')
    ax5.spines['right'].set_visible(False), ax5.spines['top'].set_visible(False)

    plt.tight_layout()
    save_name = patient_id + '_' + str(time_begin[0]) + 'h' + str(time_begin[1]) + 'm'
    plt.savefig('../doc/figures/cc_' + save_name + '.png', dpi=300)
    plt.close()

    # t vector for plots
    sns.set_style('ticks')
    plt.figure(figsize=(5, 4))
    t = np.arange(0, cctl.shape[2])
    t = (t - n_lag) / fs * 1000
    n0 = 2  # Base node
    N = 5  # Number of line plots
    peaks_x, peaks_y, taus_x_0, taus_x_1, taus_y = [], [], [], [], []
    # Colors
    cmap = plt.get_cmap('viridis')
    colors = [cmap(i) for i in np.linspace(0, 1, N)]
    ax6 = plt.gca()
    ax6.add_patch(mpl.patches.Rectangle(xy=(-t_lag*1000, -critical_corr), width=t_lag*2000, height=2*critical_corr,
                                        facecolor='red', edgecolor='red', alpha=.1))

    for i in range(N):
        n1 = n0 + i - int(N/2)  # Reference node
        plt.plot(t, cctl[n0, n1, :], color='black', lw='1.2')
        if n1 == n0:
            plt.plot(t, cctl[n0, n1, :], color='red', lw='.9')
        else:
            plt.plot(t, cctl[n0, n1, :], color=colors[i], lw='.9')
        if cc[n0, n1] >= critical_corr or cc[n0, n1] <= -critical_corr:
            peaks_x.append(tl_no_mask[n0, n1])
            peaks_y.append(cc[n0, n1])
            taus_x_0.append(higher_tau_all[n0, n1])
            taus_x_1.append(lower_tau_all[n0, n1])
            taus_y.append(cc[n0, n1] * factor)

    #cb = plt.colorbar(ax=ax)
    #cb.set_label(label='Nodes')

    plt.scatter(peaks_x, peaks_y, color='black', marker='d', label='Peak', zorder=N + 1)
    plt.scatter(taus_x_0, taus_y, color='black', marker='<', label='Right tau', zorder=N + 1)
    plt.scatter(taus_x_1, taus_y, color='black', marker='>', label='Left tau', zorder=N + 1)
    ymin, ymax = plt.gca().get_ylim()
    plt.plot([0, 0], [ymin, ymax], color='black', lw='.3')
    #         color='black', linestyle='--', label='Critical corr.', lw=.5)
    #plt.plot([-t_lag*1000, t_lag*1000], [-critical_corr, -critical_corr], color='black', linestyle='--', lw=.5)
    plt.ylim(ymin, ymax)
    plt.xlabel('Time lag [ms]'), plt.ylabel('NCC [-]')
    #plt.title('Normalized cross correlation: examples')
    plt.legend(loc='upper right', frameon=False)
    plt.xlim(-t_lag*1000, t_lag*1000)
    ax6.spines['right'].set_visible(False), ax6.spines['top'].set_visible(False)
    save_name = patient_id + '_' + str(time_begin[0]) + 'h' + str(time_begin[1]) + 'm'
    plt.savefig('../doc/figures/cctl_' + save_name + '.png', dpi=300)
    plt.close()

    # Plot raw signal
    plt.figure(figsize=(2.5, 6))
    sns.set_style('white')
    node_idx = [i + n0 - int(N / 2) for i in range(N)]
    plot_duration = 4  # [s]
    offset_array = np.linspace(0, (N - 1) * 200, N)
    for i in range(N):
        plt.plot(data_raw[:int(plot_duration * fs), node_idx[i]] + offset_array[i], color='black', lw='1.2')
        if i == int(N/2):
            plt.plot(data_raw[:int(plot_duration * fs), node_idx[i]] + offset_array[i], color='red', lw='.8')
        else:
            plt.plot(data_raw[:int(plot_duration * fs), node_idx[i]] + offset_array[i], color=colors[i], lw='.8')
    plt.plot([(plot_duration - 1) * fs, plot_duration * fs], [-200, -200], color='black', lw='1.5')
    plt.plot([plot_duration * fs - 8, plot_duration * fs - 8], [-200, 100], color='black', lw='1.5')
    ax7 = plt.gca()
    ax7.set_yticks(offset_array)
    ax7.set_yticklabels(['Ch. ' + str(i) for i in node_idx])
    ax7.get_yticklabels()[int(N/2)].set_weight('bold')
    ax7.set_xticklabels([])
    ax7.set_xlim((-10, int(plot_duration * fs)))
    ax7.spines['right'].set_visible(False), ax7.spines['top'].set_visible(False)
    ax7.spines['left'].set_visible(False), ax7.spines['bottom'].set_visible(False)
    plt.tight_layout()
    plt.savefig('../doc/figures/ccex_' + save_name + '.png', dpi=300)
    plt.close()

def determine_sample_size(patient_id: list=None, time_begin: list=None, max_sample_size: float=None, dt: float=None,
                          save_name='default', load_name=None):
    """

    :param patient_id: List with string or multiple strings.
    :param time_begin: List with list with [hour, minute] or multiple lists.
    :param max_sample_size: In seconds.
    :param dt: In seconds.
    :param save_name: String.
    :param load_name: If not None, only plot is applied.
    :return:
    """
    if load_name is None:
        corr_dts = []
        t_size = np.arange(dt, max_sample_size, dt).tolist()
        for i, id_ in enumerate(patient_id):
            print('Computes job ' + str(i) + '/' + str(len(patient_id) - 1))
            # Load and prepare data
            data_mat = loadmat('../data/' + id_ + '_' + str(time_begin[i][0]) + 'h.mat')
            info_mat = loadmat('../data/' + id_ + '_info.mat')
            fs = float(info_mat['fs'])
            sample_begin = int(time_begin[i][1] * 60 * fs)
            sample_end = sample_begin + int(max_sample_size * fs)
            data_raw = data_mat['EEG'][:, sample_begin:sample_end].transpose()

            # Get correlation matrices
            corr = np.zeros((len(t_size) + 1, data_raw.shape[1], data_raw.shape[1]))
            corr_dt = np.zeros((len(t_size), data_raw.shape[1], data_raw.shape[1]))
            sc = StandardScaler()
            for j, t in enumerate(t_size):
                data_norm = sc.fit_transform(data_raw[:int(t * fs), :])
                corr[j + 1, :, :] = np.corrcoef(data_norm.T)
                corr_dt[j, :, :] = corr[j + 1, :, :] - corr[j, :, :]

            corr_dts.append(np.mean(np.mean(np.abs(corr_dt), axis=1), axis=1))

        # Make DataFrame
        df = pd.DataFrame()
        for i, id_ in enumerate(patient_id):
            sub_df = pd.DataFrame()
            sub_df['unique_id'] = (np.ones(len(t_size)) * i).astype(int)
            sub_df['Patient ID'] = [id_ for _ in range(len(t_size))]
            sub_df['dt'] = [dt for _ in range(len(t_size))]
            time_begin_str = id_ + ': ' + str(time_begin[i][0]) + 'h' + str(time_begin[i][1]) + 'm'
            sub_df['time_begin'] = [time_begin_str for _ in range(len(t_size))]
            sub_df['corr_dt'] = corr_dts[i]
            sub_df['t_size'] = t_size
            df = df.append(sub_df, ignore_index=True)
        df.to_pickle('../data/sample_size_det_' + save_name + '.pkl')

    else:
        df = pd.read_pickle('../data/sample_size_det_' + load_name + '.pkl')

    # Plot results
    palette = sns.color_palette('muted', 3)
    df['t_size'] = df['t_size']/60
    sns.set_style('ticks')
    plt.figure(figsize=(4, 3))
    sns.lineplot(x='t_size', y='corr_dt', data=df, hue='Patient ID', palette=palette)
    plt.legend(frameon=False)
    plt.xlabel('Duration [min]')
    plt.ylabel('$\Delta R(d)$ [-]')
    plt.xlim(df['t_size'].min(), df['t_size'].max())
    plt.ylim(0, 0.04)
    ax = plt.gca()
    ax.spines['right'].set_visible(False), ax.spines['top'].set_visible(False)
    plt.tight_layout()
    plt.savefig('../doc/figures/sample_size_det_' + save_name + '.png', dpi=300)
    plt.close()

def determine_fc_change(patient_id: list=None, time_begin: list=None, max_sample_size: float=None, t_length: float=None,
                        save_name='default', load_name=None):
    if load_name is None:
        corr_dts = []
        dt = 60
        t_steps = np.arange(dt, max_sample_size, dt).tolist()
        swa = np.load('swa.npy')
        smooth = np.load('swa_smooth.npy')
        for i, id_ in enumerate(patient_id):
            print('Computes job ' + str(i) + '/' + str(len(patient_id) - 1))
            # Load and prepare data
            data_mat = loadmat('../data/' + id_ + '_' + str(time_begin[i][0]) + 'h.mat')
            info_mat = loadmat('../data/' + id_ + '_info.mat')
            fs = float(info_mat['fs'])
            sample_begin = int(time_begin[i][1] * 60 * fs)
            sample_end = sample_begin + int((max_sample_size + t_length) * fs)
            data_raw = data_mat['EEG'][:, sample_begin:sample_end].transpose()

            # Get correlation matrices
            corr = np.zeros((len(t_steps) + 1, data_raw.shape[1], data_raw.shape[1]))
            corr_dt = np.zeros((len(t_steps), data_raw.shape[1], data_raw.shape[1]))
            sc = StandardScaler()
            data_norm = sc.fit_transform(data_raw[int(0 * fs):int(t_length * fs), :])
            corr[0, :, :] = np.corrcoef(data_norm.T)
            for j, t in enumerate(t_steps):
                data_norm = sc.fit_transform(data_raw[int(t * fs):int((t + t_length) * fs), :])
                corr[j + 1, :, :] = np.corrcoef(data_norm.T)
                corr_dt[j, :, :] = corr[j + 1, :, :] - corr[j, :, :]

            corr_dts.append(np.mean(np.mean(np.abs(corr_dt), axis=1), axis=1))

            t_steps = np.asarray(t_steps)/60 + 0 # + 10
            #plt.figure(figsize=(3, 3))  # 2, 3
            fig, ax0 = plt.subplots(figsize=(4, 3))
            #ax0.scatter(t_steps, corr_dts[0], label='1. NREM Segment of P7', color='black', s=4)
            ax0.plot(t_steps, corr_dts[0], label='1. NREM Segment of P7', color='black', lw=1)
            ax0.set_xlabel('Starting time [min]')
            ax0.set_ylabel('$\Delta R(t_0)$ [-]')

            ax1 = ax0.twinx()
            ax1.plot(np.linspace(0, 60, len(smooth)), smooth, lw=1.7, color='darkred', alpha=.4)
            ax1.set_ylabel('SW band power\ndensity [$\mu$V$^2$/Hz]', color='darkred')
            ax1.tick_params(axis='y', labelcolor='darkred')

            ax0.spines['top'].set_visible(False), ax1.spines['top'].set_visible(False)
            plt.xlim(0, 60)
            plt.tight_layout()
            plt.savefig('figures/fig_Ch2_changeFC.png', dpi=300)
            plt.close()
            break


#! /usr/bin/env python3

import unittest
import rostest
from ecs.cpd import DiffRatio, VarRatio

class CPDTestCase(unittest.TestCase):

    def testDiffRatio(self):
        method = DiffRatio()
        for i in range(40):
            method.addValue(1)
        for i in range(40):
            method.addValue(3)
        self.assertEqual(method.isChangePoint(), True)

    def testVarRatio(self):
        method = VarRatio(var_stable=1.0)
        for i in range(20):
            method.addValue(2)
        for i in range(20):
            method.addValue(-2)
        self.assertEqual(method.isChangePoint(), True)

if __name__ == "__main__":
    rostest.unitrun("ecs", "cpd_tests", CPDTestCase)


import sys
import os
from FYP_GUI import*
from FYP_DIP import*

class FYP_CED(QMainWindow):
    def __init__(self):
        self.tool_ori = -1
        self.tool_ehc = -1
        self.confirmation = [False, False]
        self.size = (300,300)
        super(FYP_CED, self).__init__()
        self.setWindowTitle("Enhanced Canny Edge Detection Using Integrated Filters and Edge-Chain Filtering Technique by LOW JUN HONG (BS18110173)")
        self.showMaximized()
        self.setStyleSheet("""QToolTip {
                                   background-color: black;
                                   color: white;
                                   border: 0
                                   }""")
        self.UI = WidgetUI(self)
        self.inputImage = PhotoViewer(self, (125,230,125), tooltip='Input image')
        self.inputImage.photoClicked.connect(lambda : self.zoomImage((True, -1)))
        self.outputImage = [PhotoViewer(self, (230,125,0), tooltip='Gaussian Filtered Image'),
                            PhotoViewer(self, (230,125,0), tooltip='Gradient Image'),
                            PhotoViewer(self, (230,125,0), tooltip='Thinned Image'),
                            PhotoViewer(self, (230,125,0), tooltip='Thresholded Image'),
                            PhotoViewer(self, (230,125,0), tooltip='Hysteresis Image'),
                            PhotoViewer(self, (230,125,0), tooltip='Classic Edge Map'),

                            PhotoViewer(self, (230, 0, 180), tooltip='GABF Image'),
                            PhotoViewer(self, (230, 0, 125), tooltip='Gradient Image'),
                            PhotoViewer(self, (230, 0, 125), tooltip='Thinned Image'),
                            PhotoViewer(self, (230, 0, 125), tooltip='Thresholded Image'),
                            PhotoViewer(self, (230, 0, 125), tooltip='Hysteresis Image'),
                            PhotoViewer(self, (230, 0, 180), tooltip='EC Formation Image'),
                            PhotoViewer(self, (230, 0, 180), tooltip='EC Filtering Image'),
                            PhotoViewer(self, (230, 0, 125), tooltip='Enhanced Edge Map')
                            ]
        for i,output in enumerate(self.outputImage):
            if i<=5:
                output.photoClicked.connect(lambda x=i : self.zoomImage((True, x)))
            else:
                output.photoClicked.connect(lambda x=i-6 : self.zoomImage((False, x)))
        self.initUI()
        self.CV = DIP()
        self.oriCED = [None, None, None, None, None, None, None]
        self.enhCED = [None, None, None, None, None, None, None, None, None, None, None]

    def initUI(self):
        mainBG = self.UI.CanvasLabel("FYP/mainBG.png", True, True, Qt.AlignCenter)
        mainLay = QVBoxLayout()
        mainLay.addLayout(self.titleBAR())

        mainBG.setLayout(mainLay)
        self.setCentralWidget(mainBG)
        cover = self.UI.cover()
        cover[1].addWidget(self.UI.PushBtnIcon("FYP/start.png", lambda : self.exitCover(cover[0]), size=(250,100)))
        cover[0].exec_()      ###################### cover please reopen !!

        bottomLay = QHBoxLayout()
        bottomLay.addWidget(self.Controls())
        bottomLay.addWidget(self.Intermediate())
        bottomLay.addLayout(self.FinalOutput())
        empty = QWidget()
        empty.setFixedWidth(5)
        bottomLay.addWidget(empty)
        bottomLay.addLayout(self.benchmark())
        bottomLay.addWidget(empty)
        mainLay.addLayout(bottomLay)

    def titleBAR(self):
        lay = QHBoxLayout()
        lay.addWidget(self.UI.CanvasLabel("FYP/UMSlogo.png", True, True, Qt.AlignLeft, fixedSize=(350,120)))
        lay.addWidget(self.UI.Label_TextOnly("ENHANCED CANNY EDGE DETECTION USING INTEGRATED FILTERS\nAND EDGE-CHAIN FILTERING TECHNIQUE", ("Times New Roman BOLD", 20), align=Qt.AlignCenter))
        lay.addWidget(self.UI.CanvasLabel("FYP/MCGlogo.png", True, True, Qt.AlignRight, fixedSize=(230,120)))
        lay.setAlignment(Qt.AlignTop)
        return lay

    def exitCover(self, msg):
        msg.close()

    def Controls(self):
        leftWDG = QWidget()
        leftWDG.setFixedWidth(490)
        lay = QHBoxLayout()
        namelay = QVBoxLayout()
        namelay.addWidget(self.UI.CanvasLabel("FYP/CLASSICAL.png", True, True, Qt.AlignCenter, fixedSize=(44,302)))
        namelay.addWidget(self.UI.CanvasLabel("FYP/ENHANCED.png", True, True, Qt.AlignCenter, fixedSize=(44,302)))

        self.slider_params = [self.UI.SliderWidget(Qt.Horizontal, 5, 3, 15, 150, lambda : self.update_params(1, 0), step=2),
                              self.UI.SliderWidget(Qt.Horizontal, 150, 0, 5000, 150, lambda : self.update_params(1, 1, deci=True), step=1),
                              self.UI.SliderWidget(Qt.Horizontal, 90, 0, 255, 150, lambda : self.update_params(1, 2), step=1),
                              self.UI.SliderWidget(Qt.Horizontal, 35, 0, 255, 150, lambda : self.update_params(1, 3), step=1),

                              self.UI.SliderWidget(Qt.Horizontal, 5, 3, 15, 150, lambda : self.update_params(1, 4), step=2),
                              self.UI.SliderWidget(Qt.Horizontal, 150, 0, 5000, 150, lambda : self.update_params(1, 5, deci=True), step=1),
                              self.UI.SliderWidget(Qt.Horizontal, 65, 0, 255, 150, lambda: self.update_params(1, 6), step=1),
                              self.UI.SliderWidget(Qt.Horizontal, 48, 0, 255, 150, lambda: self.update_params(1, 7), step=1),
                              self.UI.SliderWidget(Qt.Horizontal, 18, 0, 255, 150, lambda: self.update_params(1, 8), step=1),
                              # self.UI.SliderWidget(Qt.Horizontal, 120, 100, 300, 150, lambda: self.update_params(1, 7, deci=True), step=1),
                              self.UI.SliderWidget(Qt.Horizontal, 55, 0, 100, 150, lambda: self.update_params(1, 9, deci=True), step=1),
                              self.UI.SliderWidget(Qt.Horizontal, 0, 0, 100, 150, lambda: self.update_params(1, 10, deci=True), step=1)
                              ]
        self.spinner_params = [self.UI.SpinBox(True, 3,15,5,ODD=True, width=50, action=lambda : self.update_params(2,0)),
                               self.UI.SpinBox(False, 0,50,1.5, width=50, action=lambda : self.update_params(2, 1, deci=True), step=0.05),
                               self.UI.SpinBox(True, 0,255,90, width=50, action=lambda : self.update_params(2, 2)),
                               self.UI.SpinBox(True, 0,255,35, width=50, action=lambda : self.update_params(2, 3)),

                               self.UI.SpinBox(True, 3,15,5,ODD=True, width=50, action=lambda : self.update_params(2,4)),
                               self.UI.SpinBox(False, 0,50,1.5, width=50, action=lambda : self.update_params(2, 5, deci=True), step=0.05),
                               self.UI.SpinBox(True, 0, 255, 65, width=50, action=lambda: self.update_params(2, 6)),

                               self.UI.SpinBox(True, 0, 255, 48, width=50, action=lambda: self.update_params(2, 7)),
                               self.UI.SpinBox(True, 0, 255, 18, width=50, action=lambda: self.update_params(2, 8)),
                               # self.UI.SpinBox(False, 1.0, 3.0, 1.2, width=50, action=lambda: self.update_params(2, 7, deci=True), step=0.05),
                               self.UI.SpinBox(False, 0, 1.0, 0.55, width=50, action=lambda: self.update_params(2, 9, deci=True), step=0.01),
                               self.UI.SpinBox(False, 0, 1.0, 0, width=50, action=lambda: self.update_params(2, 10, deci=True), step=0.01)
                               ]
        controlLay = QVBoxLayout()
        controlLay.addStretch(2)
        controlLay.addLayout(self.paramController("Gaussian Kernel", self.slider_params[0], self.spinner_params[0]))
        controlLay.addLayout(self.paramController("Gauss. Spatial \u03C3\u209B", self.slider_params[1], self.spinner_params[1]))
        controlLay.addLayout(self.paramController("High Threshold", self.slider_params[2], self.spinner_params[2]))
        controlLay.addLayout(self.paramController("Low Threshold", self.slider_params[3], self.spinner_params[3]))
        controlLay.addWidget(QWidget())
        controlLay.addLayout(self.paramController("", QWidget(), QWidget()))

        LeftCentreLay = QHBoxLayout()
        ToolLay = QVBoxLayout()
        ToolLay.addWidget(self.UI.PushBtnIcon("FYP/Open.png", self.openDialog, size=(45,45), tooltip='Open Image'))
        ToolLay.addWidget(self.UI.PushBtnIcon("FYP/Detect.png", lambda: self.update_tools(2), size=(45, 45), tooltip='Detect Edge'))
        self.compareImgBtn = self.UI.PushBtnIcon("FYP/Compare.png", lambda: self.update_tools(3), size=(50, 45), checkable=True, tooltip='Compare ground')

        ToolLay.addWidget(self.compareImgBtn)
        ToolLay.addWidget(self.UI.PushBtnIcon("FYP/Save.png", lambda: self.update_tools(4), size=(45, 45), tooltip='Save Image'))
        LeftCentreLay.addLayout(ToolLay)
        LeftCentreLay.addWidget(self.UI.imageFrame((330,330), self.inputImage))

        controlLay.addLayout(LeftCentreLay)
        controlLay.addStretch(1)

        controlLay.addLayout(self.paramController("GABF Kernel", self.slider_params[4], self.spinner_params[4]))
        controlLay.addLayout(self.paramController("Gauss. Spatial \u03C3\u209B", self.slider_params[5], self.spinner_params[5]))
        controlLay.addLayout(self.paramController("Gauss. Range \u03C3r", self.slider_params[6], self.spinner_params[6]))
        controlLay.addLayout(self.paramController("High Threshold", self.slider_params[7], self.spinner_params[7]))
        controlLay.addLayout(self.paramController("Low Threshold", self.slider_params[8], self.spinner_params[8]))
        controlLay.addLayout(self.paramController("GradientMean k1", self.slider_params[9], self.spinner_params[9]))
        controlLay.addLayout(self.paramController("ChainMean k2", self.slider_params[10], self.spinner_params[10]))
        controlLay.addStretch(2)

        lay.addLayout(namelay)
        lay.addLayout(controlLay)
        leftWDG.setLayout(lay)
        return leftWDG

    def paramController(self, name, slider, spinner):
        lay = QHBoxLayout()
        lay.addWidget(self.UI.Label_TextOnly(name, ("Times New Roman", 15), align=Qt.AlignLeft, width=180))
        lay.addWidget(slider)
        lay.addWidget(spinner)
        return lay

    def ver_Plane(self, topName, topImage, botName, botImage):
        lay = QVBoxLayout()
        lay.addWidget(self.UI.Label_TextOnly(topName, ("Times New Roman BOLD", 15), align=Qt.AlignCenter))
        lay.addWidget(topImage)
        lay.addSpacing(5)
        lay.addWidget(botImage)
        lay.addWidget(self.UI.Label_TextOnly(botName, ("Times New Roman BOLD", 15), align=Qt.AlignCenter))
        return lay

    def benchmark(self):
        self.result = []
        self.compare = []

        lay = QVBoxLayout()
        lay.addSpacing(10)
        lay.addWidget(self.UI.Label_TextOnly("Benchmark Test\n", ("Times New Roman BOLD", 15), align=Qt.AlignCenter))

        testName = ["Precision",
                "Recall",
                "F-measure",

                "Precision",
                "Recall",
                "F-measure",
                #Super final
                "Precision",
                "Recall",
                "F-measure",
                ]
        for i,test in enumerate(testName):
            if i==0:
                lay.addWidget(self.UI.Label_TextOnly('CLASSICAL', ("Times New Roman BOLD", 14), align=Qt.AlignCenter, border=2, color=(230, 125, 0, 0.7), height=40))
            elif i==3:
                lay.addSpacing(75)
                lay.addWidget(self.UI.Label_TextOnly('ENHANCED', ("Times New Roman BOLD", 14), align=Qt.AlignCenter, border=2, color=(230, 0, 125, 0.7), height=40))
                lay.addWidget(self.UI.Label_TextOnly('Gaussian-Adaptive Bilateral Filtering', ("Times New Roman ", 11), align=Qt.AlignCenter, fontColor=(33,33,33,0.7)))
                lay.addWidget(HLine())
            elif i==6:
                lay.addWidget(self.UI.Label_TextOnly('Edge-Chain Filtering', ("Times New Roman ", 11), align=Qt.AlignCenter, fontColor=(33, 33, 33, 0.7)))
                lay.addWidget(HLine())
            testWidget = QWidget()
            testLay = QHBoxLayout(testWidget)

            if i>=3:
                testLay.addWidget(self.UI.Label_TextOnly(test, ("Times New Roman ", 13), align=Qt.AlignLeft, width=160))
                self.compare.append((self.UI.CanvasLabel("", True, True, Qt.AlignCenter, fixedSize=(20,20)),
                                     self.UI.Label_TextOnly("", ("Times New Roman", 12), align=Qt.AlignRight, width=60)))
                testLay.addWidget(self.compare[i-3][1])
                testLay.addWidget(self.compare[i-3][0])
            else:
                testLay.addWidget(self.UI.Label_TextOnly(test, ("Times New Roman ", 13), align=Qt.AlignLeft, width=180))
                testLay.addWidget(self.UI.Label_TextOnly("", ("Times New Roman", 12), align=Qt.AlignRight, width=40))
                testLay.addWidget(self.UI.CanvasLabel("", True, True, Qt.AlignCenter, fixedSize=(20,20)))

            self.result.append(self.UI.Label_TextOnly("-", ("Times New Roman", 12), align=Qt.AlignRight, width=80, border=1))
            testLay.addWidget(self.result[i])
            testWidget.setLayout(testLay)
            lay.addWidget(testWidget)

        lay.addStretch(3)
        return lay

    def Intermediate(self):
        scroll = QScrollArea()
        scroll.setStyleSheet('background-color: transparent; border: 0')
        scroll.setFixedWidth(660)
        scroll_content = QWidget()
        scroll_Lay = QHBoxLayout(scroll_content)

        lay1 = self.ver_Plane("Gaussian\nFiltering", self.UI.imageFrame((320,320), self.outputImage[0]),
                              "Gaussian-Adaptive\nBilateral Filtering", self.UI.imageFrame((320,320), self.outputImage[6]))     #############
        lay2 = self.ver_Plane("Gradient \nComputation", self.UI.imageFrame((320, 320), self.outputImage[1]),
                              "Gradient \nComputation", self.UI.imageFrame((320, 320), self.outputImage[7]))
        lay3 = self.ver_Plane("Non-Maxima\nSuppression", self.UI.imageFrame((320, 320), self.outputImage[2]),
                              "Non-Maxima\nSuppression", self.UI.imageFrame((320, 320), self.outputImage[8]))
        lay4 = self.ver_Plane("Double\nThresholding", self.UI.imageFrame((320, 320), self.outputImage[3]),
                              "Double\nThresholding", self.UI.imageFrame((320, 320), self.outputImage[9]))
        lay5 = self.ver_Plane("\nHysteresis", self.UI.imageFrame((320, 320), self.outputImage[4]),
                              "Hysteresis\n", self.UI.imageFrame((320, 320), self.outputImage[10]))
        empty = QWidget()
        empty.setFixedSize(320,320)
        lay6 = self.ver_Plane("\n", empty,
                              "Edge Chain\nFormation", self.UI.imageFrame((320, 320), self.outputImage[11]))  ##################
        lay7 = self.ver_Plane("\n", empty,
                              "Edge Chain\nFiltering", self.UI.imageFrame((320, 320), self.outputImage[12]))             ##################

        scroll_Lay.addLayout(lay1)
        scroll_Lay.addLayout(lay2)
        scroll_Lay.addLayout(lay3)
        scroll_Lay.addLayout(lay4)
        scroll_Lay.addLayout(lay5)
        scroll_Lay.addLayout(lay6)
        scroll_Lay.addLayout(lay7)
        scroll.setWidget(scroll_content)
        return scroll

    def FinalOutput(self):
        resultLay = QVBoxLayout()
        resultLay.addWidget(self.UI.Label_TextOnly("Classical Output", ("Times New Roman BOLD", 15), align=Qt.AlignCenter))
        resultLay.addWidget(self.UI.imageFrame((350, 350), self.outputImage[5]))
        resultLay.addWidget(self.UI.imageFrame((350, 350), self.outputImage[13]))
        resultLay.addWidget(self.UI.Label_TextOnly("Enhanced Output", ("Times New Roman BOLD", 15), align=Qt.AlignCenter))
        resultLay.addSpacing(40)
        return resultLay

    def update_params(self, flag, ind, deci=None):
        if flag==1:
            if deci:
                self.spinner_params[ind].setValue(self.slider_params[ind].value()/100)
            else:
                self.spinner_params[ind].setValue(self.slider_params[ind].value())

        elif flag==2:
            self.slider_params[ind].blockSignals(True)
            if deci:
                self.slider_params[ind].setValue(int(round(self.spinner_params[ind].value()*100)))
            else:
                self.slider_params[ind].setValue(self.spinner_params[ind].value())
            self.slider_params[ind].blockSignals(False)

        if 0<=ind<=1:
            self.tool_ori = 1
        elif 2<=ind<=3 and self.tool_ori==0:
            self.tool_ori = 2
        elif 4<=ind<=6:
            self.tool_ehc = 1
        elif 7<=ind<=8 and self.tool_ehc == 0:
            self.tool_ehc = 2
        elif 9<=ind<=10 and self.tool_ehc == 0:
            self.tool_ehc = 3

    def openDialog(self):
        allConfirmed = False
        if all(self.confirmation):
            allConfirmed = True
        self.confirmation = [False, False]
        msg = QDialog(self)
        msg.setWindowFlag(Qt.FramelessWindowHint)
        lay = QVBoxLayout()
        textLay = QHBoxLayout()
        textLay.addWidget(self.UI.Label_TextOnly("Test Image", ("Times New Roman", 12), align=Qt.AlignCenter, border=2, width=300))
        textLay.addWidget(self.UI.Label_TextOnly("Ground Truth", ("Times New Roman", 12), align=Qt.AlignCenter, border=2, width=300))
        lay.addLayout(textLay)
        btnLay = QHBoxLayout()
        self.openImage = [self.UI.PushBtnIcon('FYP/image.png', lambda : self.update_tools(0), size=(300,300), border=1), self.UI.PushBtnIcon('FYP/image.png', lambda: self.update_tools(1), size=(300,300), border=1)]
        btnLay.addWidget(self.openImage[0])
        btnLay.addWidget(self.openImage[1])
        lay.addLayout(btnLay)

        def confirm():
            if not all(self.confirmation) or self.groundImg.shape[0]!=self.ori.shape[0] or self.groundImg.shape[1]!=self.ori.shape[1]:
                return
            # if both image obtained, then can close else return
            self.Render(self.ori.copy(), self.inputImage)
            if len(self.ori.shape)==3:
                self.input = self.CV.cvtGreyscale(self.ori.copy())
            else:
                self.input = self.ori.copy()
            self.tool_ori = self.tool_ehc = -1
            self.compareImgBtn.setChecked(False)
            msg.close()
        def cancel():
            if allConfirmed:
                self.confirmation = [True, True]
            msg.close()

        btnLayBottom = QHBoxLayout()
        btnLayBottom.addStretch()
        btnLayBottom.addWidget(self.UI.PushBtnText('Confirm', confirm, width=80))
        btnLayBottom.addWidget(self.UI.PushBtnText('Cancel', cancel, width=80))
        lay.addLayout(btnLayBottom)
        msg.setLayout(lay)

        msg.exec_()

    def update_ParamValue(self, filename):
        print(filename, " updating params value ...")
        if filename=='21077':
            params = (5, 1.35, 90, 30, 5, 1.05, 44, 52, 11, 0.46, 0.15)
        elif filename=='24077':
            params = (5, 3, 97, 41, 5, 2.65, 71, 54, 18, 0.5, 0.15)
        elif filename=='67079':
            params = (5, 2.6, 79, 37, 5, 2.65, 54, 36, 10, 0.60, 0.05)
        elif filename=='69020':
            params = (5, 1.55, 66, 36, 5, 1.5, 37, 43, 17, 0.69, 0.11)
        elif filename=='100075':
            params = (5, 2.9, 79, 36, 5, 3.2, 73, 50, 30, 0.67, 0.09)
        elif filename=='113044':
            params = (5, 1.35, 94, 46, 5, 1.35, 64, 40, 22, 0.72, 0.18)
        elif filename=='299086':
            params = (5, 1.4, 64, 28, 5, 1.55, 70, 31, 15, 0.55, 0.12)
        elif filename=='323016':
            params = (7, 1.1, 70, 40, 7, 0.8, 88, 56, 25, 0.57, 0.07)
        elif filename=='187071':
            params = (7, 1.45, 105, 41, 7, 1.3, 75, 55, 24, 0.64, 0.08)
        elif filename=='368078':
            params = (5, 1.35, 96, 33, 5, 1.3, 88, 58, 12, 0.44, 0.1)
        else:
            print('not matched, resetting params value')
            params = (5, 1.5, 90, 35, 5, 1.5, 65, 48, 18, 0.55, 0)
        for i,spin in enumerate(self.spinner_params):
            spin.setValue(params[i])

    def update_tools(self, flag, groundPath=None):
        if flag==0 or flag==1:
            if groundPath:
                file = groundPath
            else:
                filter = "Images (*.png *.jpg *.tif *.tiff)"
                if flag==0:
                    pathSuggest = f'./FYP/Image'
                else:
                    pathSuggest = f'./FYP/Ground'
                file, _ = QFileDialog.getOpenFileName(self, "File Directory", pathSuggest, filter)
            if file == (""):
                return
            else:
                img = self.CV.read(file)
                if flag==0:
                    self.ori = img.copy()

                if flag ==1:
                    self.groundImg = img.copy()
                self.confirmation[flag] = True
                self.Render(img.copy(), self.openImage[flag], icon=True)
                if flag==0:
                    groundPath = f'./FYP/Ground/'+str(os.path.basename(file).split('.')[0])+'.png'
                    if os.path.isfile(groundPath):
                        self.update_tools(1, groundPath)
                        self.update_ParamValue(str(os.path.basename(file).split('.')[0]))

        elif flag==2 and all(self.confirmation):
            #################### Classic ##########################
            if self.tool_ori==1 or self.tool_ori==-1:
                print(np.average(np.abs(np.average(self.input.copy())-self.input.copy()))*255)
                gaussFltr = self.CV.gaussianFilter(self.input.copy(), self.spinner_params[0].value(), self.spinner_params[1].value())
                self.oriCED[0] = gaussFltr.copy()*255

                grad2x2, ang2x2 = self.CV.gradient2x2(gaussFltr.copy())
                self.oriCED[1] = grad2x2.copy()*255

                self.oriCED[2] = self.CV.nonMax_Supp(grad2x2.copy(), ang2x2.copy())
                self.tool_ori = 2

                for i in range(3):
                    self.Render(self.oriCED[i].copy(), self.outputImage[i])

            if self.tool_ori==2:
                self.oriCED[3] = self.CV.thresholding(self.oriCED[2].copy(), self.spinner_params[2].value(), self.spinner_params[3].value())

                self.oriCED[4],_ = self.CV.hysteresis(self.oriCED[3].copy())

                for i in range(3, 5):
                    self.Render(self.oriCED[i].copy(), self.outputImage[i])
                self.Render(self.oriCED[4].copy(), self.outputImage[5])

            #################### Enhanced ##########################
            if self.tool_ehc==1 or self.tool_ehc==-1:
                GABFfilter = self.CV.gabf(self.input.copy(), self.spinner_params[4].value(), self.spinner_params[5].value(), float(self.spinner_params[6].value()/255.0))
                self.enhCED[0] = GABFfilter.copy()*255

                gradENH, angENH = self.CV.gradient2x2(GABFfilter.copy())
                self.enhCED[1] = gradENH.copy()*255

                self.enhCED[2] = self.CV.nonMax_Supp(gradENH.copy(), angENH.copy())
                self.tool_ehc = 2

                for i in range(3):
                    self.Render(self.enhCED[i].copy(), self.outputImage[i+6])
            if self.tool_ehc==2:

                self.enhCED[3] = self.CV.thresholding(self.enhCED[2].copy(), self.spinner_params[7].value(), self.spinner_params[8].value())

                self.enhCED[4], nms_e = self.CV.hysteresis(self.enhCED[3].copy(), self.enhCED[2].copy())

                self.enhCED[5], self.enhCED[6], self.chainInfo = self.CV.chainFormation(self.enhCED[4].copy(), nms_e.copy())
                self.tool_ehc = 3
                for i in range(3, 6):
                    self.Render(self.enhCED[i].copy(), self.outputImage[i+6])
            if self.tool_ehc==3:

                self.enhCED[7], self.enhCED[10] = self.CV.chainFiltering(self.enhCED[6].copy(), self.chainInfo, self.spinner_params[9].value(), self.spinner_params[10].value())

                self.Render(self.enhCED[7].copy(), self.outputImage[12])
                self.Render(self.enhCED[7].copy(), self.outputImage[13])

            ########  benchmark  ###############
            result1 = [0,0,0]
            result2 = [0,0,0,0,0,0]
            print("Ori: w={}; \u03C3\u209B={}; TH={}; TL={}".format(self.spinner_params[0].value(), round(self.spinner_params[1].value(),2), self.spinner_params[2].value(), self.spinner_params[3].value()))

            result1[0], result1[1], result1[2], self.oriCED[5] = self.CV.getConfusionMat(self.groundImg.copy(), self.oriCED[4].copy())

            self.result[0].setText(str(round(result1[0], 4)))
            self.result[1].setText(str(round(result1[1], 4)))
            self.result[2].setText(str(round(result1[2], 4)))

            print("Enh: w={}; s={}; r={}; TH={}; TL={}".format(self.spinner_params[4].value(), round(self.spinner_params[5].value(),2), round(self.spinner_params[6].value(),2), round(self.spinner_params[7].value(),2), round(self.spinner_params[8].value(),2)))
            result2[0], result2[1], result2[2], self.enhCED[8] = self.CV.getConfusionMat(self.groundImg.copy(), self.enhCED[4].copy())

            self.result[3].setText(str(round(result2[0], 4)))
            self.result[4].setText(str(round(result2[1], 4)))
            self.result[5].setText(str(round(result2[2], 4)))
            result2[3], result2[4], result2[5], self.enhCED[9] = self.CV.getConfusionMat(self.groundImg.copy(), self.enhCED[7].copy())
            self.result[6].setText(str(round(result2[3], 4)))
            self.result[7].setText(str(round(result2[4], 4)))
            self.result[8].setText(str(round(result2[5], 4)))

            ### comparison ####
            for ind,myResult in enumerate(result2):

                if ind>=3:
                    percent = (round(myResult, 4) - round(result1[ind-3], 4)) * 100
                else:
                    percent = (round(myResult, 4) - round(result1[ind], 4))*100
                if percent==0:
                    self.compare[ind][0].setPixmap(QPixmap(''))
                    self.compare[ind][1].setText('')
                elif percent>0:
                    self.compare[ind][0].setPixmap(QPixmap('FYP/up.png'))
                    self.compare[ind][1].setText(str(round(percent, 2)) + '%')
                    self.compare[ind][1].setStyleSheet('color: rgb(0,150,0)')
                else:
                    self.compare[ind][0].setPixmap(QPixmap('FYP/down.png'))
                    self.compare[ind][1].setText(str(round(percent, 2)) + '%')
                    self.compare[ind][1].setStyleSheet('color: rgb(180,0,0)')

            print('_______________________________________')

            self.tool_ori = self.tool_ehc = 0       # reset
        elif flag == 3 and self.tool_ehc!=-1 and self.tool_ori!=-1:
            if self.compareImgBtn.isChecked():
                self.Render(self.oriCED[5], self.outputImage[4])
                self.Render(self.oriCED[5], self.outputImage[5])
                self.Render(self.enhCED[8], self.outputImage[10])
                self.Render(self.enhCED[10], self.outputImage[12])
                self.Render(self.enhCED[9], self.outputImage[13])
                self.Render(self.groundImg.copy(), self.inputImage)
            else:
                self.Render(self.oriCED[4], self.outputImage[4])
                self.Render(self.oriCED[4], self.outputImage[5])
                self.Render(self.enhCED[4], self.outputImage[10])
                self.Render(self.enhCED[7], self.outputImage[12])
                self.Render(self.enhCED[7], self.outputImage[13])
                self.Render(self.ori.copy(), self.inputImage)
        elif flag==4 and self.tool_ehc!=-1 and self.tool_ori!=-1:
            folder = QFileDialog.getExistingDirectory(self, ("Save Output"), QDir.currentPath())
            if folder == (""):
                return
            else:
                self.CV.save(folder+'/GaussianFiltering.tif', self.oriCED[0].astype(np.uint8))
                self.CV.save(folder + '/Gradient.tif', self.oriCED[1].astype(np.uint8))
                self.CV.save(folder + '/NMS.tif', self.oriCED[2])
                self.CV.save(folder + '/Thresholded.tif', self.oriCED[3])
                self.CV.save(folder+'/ClassicOutput.tif', self.oriCED[4])
                self.CV.save(folder + '/ClassicGround.tif', cv2.cvtColor(self.oriCED[5], cv2.COLOR_BGR2RGB))

                self.CV.save(folder+'/01GABF.tif', self.enhCED[0].astype(np.uint8))
                self.CV.save(folder + '/02Gradient_enh.tif', self.enhCED[1].astype(np.uint8))
                self.CV.save(folder + '/03NMS_enh.tif', self.enhCED[2])
                self.CV.save(folder + '/04Thresholded_enh.tif', self.enhCED[3])
                self.CV.save(folder + '/05Hysteresis_enh.tif', self.enhCED[4])
                self.CV.save(folder + '/06EC_formation.tif', self.enhCED[5])
                self.CV.save(folder + '/07EnhancedOutput.tif', self.enhCED[7])
                self.CV.save(folder + '/08HysteresisGround.tif', cv2.cvtColor(self.enhCED[8], cv2.COLOR_BGR2RGB))
                self.CV.save(folder + '/09EnhancedGround.tif', cv2.cvtColor(self.enhCED[9], cv2.COLOR_BGR2RGB))
                self.CV.save(folder + '/10ECF_comp.tif', cv2.cvtColor(self.enhCED[10], cv2.COLOR_BGR2RGB))

    def zoomImage(self, flag):
        if not all(self.confirmation):
            return
        if flag[0]:    #ori
            if flag[1]==0:
                text = "Ori_Gaussian Filtering: w ={}; \u03C3\u209B = {}".format(self.spinner_params[0].value(), round(self.spinner_params[1].value(),2))
            elif flag[1]==1:
                text = "Ori_Gradient Computation"
            elif flag[1]==2:
                text = "Ori_Non-Maxima Suppression"
            elif flag[1]==3:
                text = "Ori_Double Thresholding: TH = {}; TL = {}".format(self.spinner_params[2].value(), self.spinner_params[3].value())
            elif flag[1] == 4:
                text = "Ori_Hysteresis: TH = {}; TL = {}".format(self.spinner_params[2].value(), self.spinner_params[3].value())
            elif flag[1]==5:
                text = "Ori: w={}; \u03C3\u209B={}; TH={}; TL={}".format(self.spinner_params[0].value(), round(self.spinner_params[1].value(),2), self.spinner_params[2].value(), self.spinner_params[3].value())
                self.UI.imageZoomDialog(text, self.oriCED[4])
                self.UI.imageZoomDialog(text, self.oriCED[5])
                return
            image = self.oriCED[flag[1]]
            if flag[1]==-1:
                self.UI.imageZoomDialog("Ground Truth", self.groundImg.copy())
                grey = self.input.copy()*255
                self.UI.imageZoomDialog("Greyscale", grey.astype(np.uint8))
                self.UI.imageZoomDialog("Original Input", self.ori.copy())
                return
        else:       #enh
            if flag[1] == 0:
                text = "Enh_GABF: kernel = {}; \u03C3\u209B = {}; \u03C3r = {}".format(self.spinner_params[4].value(), round(self.spinner_params[5].value(),2), round(self.spinner_params[6].value(),2))
            elif flag[1] == 1:
                text = "Enh_Gradient Computation"
            elif flag[1] == 2:
                text = "Enh_Non-Maxima Suppression"
            elif flag[1] == 3:
                text = "Enh_Double Thresholding: TH={}; TL={}".format(round(self.spinner_params[7].value(),2), round(self.spinner_params[8].value(),2))
            elif flag[1] == 4:
                text = "Enh_Hysteresis: w={}; s={}; r={}; TH={}; TL={}".format(self.spinner_params[4].value(), round(self.spinner_params[5].value(),2), round(self.spinner_params[6].value(),2), round(self.spinner_params[7].value(),2), round(self.spinner_params[8].value(),2))
                self.UI.imageZoomDialog(text, self.enhCED[4])
                self.UI.imageZoomDialog(text, self.enhCED[8])
                return
            elif flag[1] == 5:
                text = "Enh_Edge Chain Formation"
            elif flag[1] == 6:
                text = "Enh_Edge Chain Filtering"
                self.UI.imageZoomDialog(text, self.enhCED[7])
                self.UI.imageZoomDialog(text, self.enhCED[10])
                return
            elif flag[1] == 7:      #########3
                text = "Enh: w={}; s={}; r={}; TH={}; TL={}".format(self.spinner_params[4].value(), round(self.spinner_params[5].value(),2), round(self.spinner_params[6].value(),2), round(self.spinner_params[7].value(),2), round(self.spinner_params[8].value(),2))
                self.UI.imageZoomDialog(text, self.enhCED[7])
                self.UI.imageZoomDialog(text, self.enhCED[9])
                return
                # return
            image = self.enhCED[flag[1]]
        self.UI.imageZoomDialog(text, image.astype(np.uint8))

    def Render(self, image, canvas, icon=False):
        image = image.astype(np.uint8)
        image = cv2.resize(image, dsize=(self.size[0], self.size[1]))
        if len(image.shape)==3:
            image_RGBA = cv2.cvtColor(image, cv2.COLOR_BGR2RGBA)
        else:
            image_RGBA = cv2.cvtColor(image, cv2.COLOR_GRAY2RGBA)
        QtImg = QImage(image_RGBA.data, image_RGBA.shape[1], image_RGBA.shape[0], QImage.Format_ARGB32)
        if not icon:
            canvas.setPhoto(QPixmap(QtImg))
        else:
            canvas.setIcon(QIcon(QPixmap.scaled(QPixmap.fromImage(QtImg), image.shape[1], image.shape[0], Qt.KeepAspectRatio, Qt.SmoothTransformation)))

def main():
    app = QApplication(sys.argv)
    win = FYP_CED()
    win.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()


import tensorflow as tf

from config import FLAGS

class Model(object):
    def __init__(self, batchloader, is_training=True):
        self.batchloader = batchloader
        self.is_training = is_training
        self.lr = tf.placeholder(tf.float32, shape=(), name="learning_rate")

        with tf.name_scope("Placeholders"):
            self.input_text = tf.placeholder(tf.int32,
                                             shape=(FLAGS.BATCH_SIZE, FLAGS.SEQ_LEN),
                                             name="input_text")

            self.label = tf.placeholder(tf.int32,
                                        shape=(FLAGS.BATCH_SIZE),
                                        name="label")

        with tf.variable_scope("Embedding"):
            self.embedding = tf.get_variable(name="embedding",
                                 shape=[FLAGS.VOCAB_SIZE, FLAGS.EMBED_SIZE],
                                 dtype=tf.float32,
                                 initializer=tf.truncated_normal_initializer(stddev=0.1))

        with tf.name_scope("Embedding_text"):
            # NHWC
            self.embedded_text = tf.expand_dims(tf.nn.embedding_lookup(self.embedding, self.input_text),
                                                axis=-1)
            assert self.embedded_text.shape == (FLAGS.BATCH_SIZE, FLAGS.SEQ_LEN, FLAGS.EMBED_SIZE, 1)

        with tf.variable_scope("CNN"):
            self.maxpooled_output = []
            self.cnn_W = []
            for i, filter_size in enumerate(FLAGS.FILTER_SIZE):
                layer_name = "cnn_layer_%d_%d" % (i, filter_size)
                with tf.variable_scope(layer_name):
                    self.cnn_W.append(tf.get_variable(name=layer_name+"_W",
                                                      shape=(filter_size,
                                                             FLAGS.EMBED_SIZE,
                                                             1,
                                                             FLAGS.FILTER_NUM),
                                                      dtype=tf.float32,
                                                      initializer=tf.truncated_normal_initializer(stddev=0.1)))

                    conv = tf.nn.conv2d(self.embedded_text,
                                        self.cnn_W[i],
                                        strides=[1, 1, 1, 1],
                                        padding="VALID",
                                        name=layer_name+"_conv")

                    maxpooled = tf.nn.max_pool(conv,
                                               ksize=[1, FLAGS.SEQ_LEN - filter_size + 1, 1, 1],
                                               strides=[1, 1, 1, 1],
                                               padding="VALID")

                    self.maxpooled_output.append(maxpooled)

            with tf.variable_scope("Feaure"):
                self.feature = tf.reshape(tf.concat(self.maxpooled_output, axis=3),
                                          shape=[FLAGS.BATCH_SIZE, FLAGS.FILTER_NUM * len(FLAGS.FILTER_SIZE)])

                if self.is_training:
                    self.feature = tf.nn.dropout(self.feature, FLAGS.DROPOUT_KEEP)

        with tf.variable_scope("Linear"):
            self.W = tf.get_variable(name="W",
                                     shape=(FLAGS.FILTER_NUM * len(FLAGS.FILTER_SIZE),
                                            FLAGS.CLASS_NUM),
                                     dtype=tf.float32,
                                     initializer=tf.truncated_normal_initializer(stddev=0.1))
            self.b = tf.get_variable(name="b",
                                     shape=(FLAGS.CLASS_NUM),
                                     dtype=tf.float32,
                                     initializer=tf.zeros_initializer())

        with tf.name_scope("Logits"):
            self.logits = tf.matmul(self.feature, self.W) + self.b

        with tf.name_scope("Accuracy"):
            prediction = tf.argmax(self.logits, 1, output_type=tf.int32)
            self.accuracy = tf.reduce_mean(
                                tf.cast(tf.equal(prediction, self.label),
                                        dtype=tf.float32))

        with tf.name_scope("Loss"):
            losses = tf.nn.sparse_softmax_cross_entropy_with_logits(
                        labels=self.label,
                        logits=self.logits,
                        name="loss")
            self.loss = tf.reduce_mean(losses)

        with tf.name_scope("Summary"):
            if is_training:
                loss_summary = tf.summary.scalar("loss", self.loss, family="train_loss")
                acc_summary = tf.summary.scalar("accuracy", self.accuracy, family="train_loss")
                lr_summary = tf.summary.scalar("lr", self.lr, family="parameters")

                self.merged_summary = tf.summary.merge([loss_summary, lr_summary, acc_summary])

            else:
                loss_summary = tf.summary.scalar("valid_loss", self.loss, family="val_loss")
                acc_summary = tf.summary.scalar("valid_accuracy", self.accuracy, family="val_loss")

                self.merged_summary = tf.summary.merge([loss_summary, acc_summary])

        if self.is_training:
            tvars = tf.trainable_variables()
            with tf.name_scope("Optimizer"):
                tvars = tf.trainable_variables()
                grads, _ = tf.clip_by_global_norm(tf.gradients(self.loss, tvars),
                                                  FLAGS.MAX_GRAD)
                optimizer = tf.train.AdamOptimizer(self.lr, beta1=0.5)

                self.train_op = optimizer.apply_gradients(zip(grads, tvars))


from modules.sem_tocar_config import *
from modules.toolbox import *
import time
import os
from urllib.parse import quote
import webbrowser as web
import pyautogui as pg

def notify_whatsapp (event):
    for phone_no in event["description"].split(","):
        wait_time = 60
        close_time = 15
        event_time = format_human_date(event['start'])
        message = f"Olá, você tem um evento em {COMPANY_NAME} às {event_time['time']} do dia {event_time['day']} de {event_time['month']} ({event_time['weekday']}), apresente o QR na entrada"
        #phone_no = event["description"]
        image = make_path(f"QR_{event['id']}.png", "QR")

        parsed_message = quote(message)
        web.open('https://web.whatsapp.com/send?phone=' +
                phone_no + '&text=' + parsed_message)
        print(message, phone_no)
        time.sleep(wait_time)
        width, height = pg.size()
        pg.click(width/2, height/2)
        time.sleep(wait_time/4)
        pg.press('enter')
        os.system(
        f"xclip -selection clipboard -target image/png -i {image}")
        time.sleep(wait_time/4)
        pg.hotkey("ctrl", "v")
        time.sleep(wait_time/4)
        pg.press('enter')

        time.sleep(close_time)
        pg.hotkey("ctrl", "w")


#boj.kr/9466
#내코드 = 현재 사이클을 dict에 저장 후 읽어오는 방식 사용
import sys
t = int(sys.stdin.readline())
for _ in range(t):
    n = int(sys.stdin.readline())
    graph = [0]+ list(map(int,sys.stdin.readline().split()))
    visited = [0]*(n+1)
    visit_now = {}
    result = 0

    for x in range(1,n+1):
        if visited[x] == 0:
            visited[x] = 1
            cnt = 1
            visit_now[x] = cnt
            nxt = graph[x]
            while True:
                if visited[nxt] == 0:
                    if nxt == graph[nxt]:
                        result +=len(visit_now)
                        visited[nxt] = 1
                        break
                    else:
                        cnt+=1
                        visit_now[nxt] = cnt
                        visited[nxt] = 1
                        nxt = graph[nxt]
                else: #방문했던노드일때
                    if visit_now.get(nxt): #루프 성립시
                        visited[nxt] = 1
                        result += visit_now.get(nxt)-1
                    else: #루프성립X
                        result +=len(visit_now)
                    break
            visit_now.clear()
    print(result)

#다른사람코드
#발상은 같지만 backtrack 변수로 풀어내는 방식이 더 세련된거같다..
    """def solution(n, students):
    visited = [False] * (n + 1)
    count = 0

    for i in range(1, n+1):
        if not visited[i]:

            # 사이클 발견시까지 진행
            current = i
            while not visited[current]:
                visited[current] = True
                current = students[current]

            # current 는 사이클을 완성하는 학생
            #backrack 은 사이클의 최초 학생
            # 사이클 최초~ 마무리 학생까지는 팀을 이룰수없음
            backtrack = i
            while backtrack != current:
                backtrack = students[backtrack]
                count += 1

    return count

T = int(input())

for _ in range(T):
    n = int(input())
    students = [0] + list(map(int, input().split()))

    print(solution(n, students))
    """


from distutils.command.config import config
from Classes import Event, Timeline, delete_event, delete_timeline
from Session import *
from GlobalVariables import CONFIG
import unittest

class TestStringMethods(unittest.TestCase):

    def test_init_event(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event1 = Event()
        self.assertEqual(event1.get_date(),0)
        self.assertEqual(event1.get_id(),0)
        self.assertEqual(event1.timelines,[])

    def test_setting_id_event(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event1 = Event()
        self.assertEqual(event1.get_id(),0)
        event1.set_id(52)
        self.assertEqual(event1.get_id(),52)

    def test_setting_short_desc_event(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event1 = Event()
        event1.set_short_description("At this point in time, it just so happened that something extremely surprising occurred.")
        self.assertEqual(event1.get_short_description(),"At this point in time, it just so happened that so")

    def test_increment_id_events(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event1 = Event()
        self.assertEqual(event1.get_id(),0)
        event2 = Event()
        self.assertEqual(event2.get_id(),1)
        self.assertEqual(event2.get_date(),0)
        self.assertEqual(event2.timelines,[])

    def test_comparison_events(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event1 = Event(date=10)
        event2 = Event(date=8)
        self.assertGreater(event1, event2)
        self.assertGreaterEqual(event1, event2)
        self.assertLess(event2, event1)
        self.assertLessEqual(event2, event1)

    def test_init_timeline(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        timeline1 = Timeline()
        self.assertEqual(timeline1.get_id(), 0)
        self.assertEqual(timeline1.events, [])

    def test_setting_id_timeline(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        timeline1 = Timeline()
        timeline1.set_id(52)
        self.assertEqual(timeline1.get_id(),52)

    def test_increment_id_timeline(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        timeline1 = Timeline()
        timeline2 = Timeline()
        timeline3 = Timeline()
        self.assertEqual(timeline1.get_id(), 0)
        self.assertEqual(timeline2.get_id(), 1)
        self.assertEqual(timeline3.get_id(), 2)

    def test_init_items_by_timeline(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event1 = Event(date=10)
        event2 = Event(date=8)
        timeline1 = Timeline()
        timeline2 = Timeline(events=[event1.get_id(), event2.get_id()])
        self.assertEqual(len(Event.event_dict),2)
        self.assertEqual(timeline1.events, [])
        self.assertEqual(timeline2.events, [1,0])

    def test_insert_event_in_timelines(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event0 = Event(date=0)
        event1 = Event(date=5)
        event2 = Event(date=3)
        event3 = Event(date=-3)
        event4 = Event(date=8)
        timeline1 = Timeline(events=[event0.get_id(), event1.get_id()])
        self.assertEqual(timeline1.events, [0,1])
        timeline1.insert_event(event2.get_id())
        self.assertEqual(timeline1.events, [0,2,1])
        timeline1.insert_event(event4.get_id())
        self.assertEqual(timeline1.events, [0,2,1,4])
        timeline1.insert_event(event3.get_id())
        self.assertEqual(timeline1.events, [3,0,2,1,4])
        self.assertEqual(event0.timelines, [0])
        self.assertEqual(event1.timelines, [0])
        self.assertEqual(event2.timelines, [0])
        self.assertEqual(event3.timelines, [0])
        self.assertEqual(event4.timelines, [0])

    def test_delete_event_free_id(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event0 = Event(date=0)
        event1 = Event(date=5)
        event2 = Event(date=3)
        self.assertEqual(Event.free_ids,[])
        delete_event(event2)
        self.assertEqual(Event.free_ids,[2])
        delete_event(event1)
        self.assertEqual(Event.free_ids,[2,1])

    def test_delete_event_free_dict(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event0 = Event(date=0)
        event1 = Event(date=5)
        event2 = Event(date=3)
        event3 = Event(date=-3)
        event4 = Event(date=8)
        self.assertEqual(len(Event.event_dict), 5)
        delete_event(event2)
        self.assertEqual(len(Event.event_dict), 4)
        delete_event(event1)
        self.assertEqual(len(Event.event_dict), 3)
        self.assertEqual([e.get_id() for e in Event.event_dict.values()],[0,3,4])
        self.assertEqual([e.get_date() for e in Event.event_dict.values()],[0,-3,8])

    def test_timeline_dies_if_fewer_than_2_events(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event0 = Event(date=0)
        event1 = Event(date=5)
        event2 = Event(date=3)
        event3 = Event(date=10)
        timeline0 = Timeline(events=[event0.get_id(), event1.get_id()])
        timeline1 = Timeline(events=[event0.get_id(), event1.get_id()])
        timeline0.insert_event(event2.get_id())
        timeline0.insert_event(event3.get_id())
        timeline1.insert_event(event2.get_id())
        delete_event(event0)
        delete_event(event1)
        self.assertEqual(Timeline.free_ids,[1])
        self.assertEqual(list(Timeline.timeline_dict.keys()),[0])

    def test_new_events_take_free_ids(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event0 = Event(date=0)
        event1 = Event(date=5)
        event2 = Event(date=3)
        event3 = Event(date=10)
        delete_event(event3)
        delete_event(event1)
        event4 = Event(date=10)
        self.assertEqual(event4.get_id(),3)
        event5 = Event(date=11)
        self.assertEqual(event5.get_id(),1)
        event6 = Event(date=11)
        self.assertEqual(event6.get_id(),4)

    def test_delete_timeline(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event0 = Event(date=0)
        event1 = Event(date=5)
        event2 = Event(date=3)
        event3 = Event(date=10)
        timeline0 = Timeline(events = [event0.get_id(), event1.get_id()])
        timeline1 = Timeline(events = [event0.get_id(), event1.get_id()])
        timeline0.insert_event(event2.get_id())
        timeline0.insert_event(event3.get_id())
        timeline1.insert_event(event2.get_id())
        self.assertEqual(event0.timelines,[0,1])
        self.assertEqual(event1.timelines,[0,1])
        self.assertEqual(event2.timelines,[0,1])
        self.assertEqual(event3.timelines,[0])
        delete_timeline(timeline1.get_id())
        self.assertEqual(event0.timelines,[0])
        self.assertEqual(event1.timelines,[0])
        self.assertEqual(event2.timelines,[0])
        self.assertEqual(event3.timelines,[0])
        delete_timeline(timeline0.get_id())
        self.assertEqual(Timeline.free_ids,[1,0])
        self.assertEqual(event0.timelines,[])
        self.assertEqual(event1.timelines,[])
        self.assertEqual(event2.timelines,[])
        self.assertEqual(event3.timelines,[])

    def test_load_save_event(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event0 = Event(date=0)
        event1 = Event(date=5)
        event2 = Event(date=3)
        event3 = Event(date=10)
        delete_event(event2)
        conf = CONFIG()
        json_save("session_test", conf.DATA_PATH)
        Event.event_dict = {}
        Event.free_ids = []
        json_load("session_test", conf.DATA_PATH)
        self.assertEqual(list(Event.event_dict.keys()), [0,1,3])
        self.assertEqual([ev.get_date() for ev in Event.event_dict.values()], [0,5,10])

    def test_load_save_timeline(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event0 = Event(date=10)
        event1 = Event(date=5)
        event2 = Event(date=3)
        event3 = Event(date=0)
        delete_event(event2)
        timeline1 = Timeline()
        timeline2 = Timeline(id_nb=5)
        Timeline.free_ids = [1,2,3,4]
        timeline3 = Timeline(events=[0,3])
        conf = CONFIG()
        json_save("session_test", conf.DATA_PATH)
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        json_load("session_test", conf.DATA_PATH)
        self.assertEqual(set(list(Timeline.timeline_dict.keys())), set([0,5,1]))
        self.assertEqual(Timeline.timeline_dict[0].events, [])
        self.assertEqual(Timeline.timeline_dict[5].events, [])
        self.assertEqual(Timeline.timeline_dict[1].events, [3,0])

    def test_update_tl(self):
        Event.event_dict = {}
        Event.free_ids = []
        Timeline.timeline_dict = {}
        Timeline.free_ids = []
        event0 = Event(date=10)
        event1 = Event(date=5)
        event2 = Event(date=3)
        event3 = Event(date=0)
        timeline1 = Timeline(events=[0,1,3])
        timeline2 = Timeline(events=[0,3,2])
        timeline3 = Timeline(events=[1,3,2])
        self.assertEqual(timeline1.events, [3,1,0])
        self.assertEqual(timeline2.events, [3,2,0])
        self.assertEqual(timeline3.events, [3,2,1])
        event0.set_date(4)
        self.assertEqual(timeline1.events, [3,0,1])
        self.assertEqual(timeline2.events, [3,2,0])
        self.assertEqual(timeline3.events, [3,2,1])

if __name__ == '__main__':
    unittest.main()


class GameStats:
    """Estadísticas del juego"""
    def __init__(self, ai_game):
        """Inicializa las estadísticas"""
        self.settings = ai_game.settings
        self.reset_stats()
        # inicia el juego en un estado inactivo
        self.game_active = False
        # La puntuación más alta no se debe restablecer
        self.high_score = 0

    def reset_stats(self):
        """Inicializa las estadísticas que pueden cambiar durante el juego"""
        self.ships_left = self.settings.ship_limit
        self.score = 0
        self.level = 1


import numpy as np
import scipy.misc

import pickle

import tensorflow as tf

import overlap_input
from constants import FLAGS

images_batch, labels_batch = overlap_input.inputs(normalize=True, reshape=True, rotation=FLAGS.ROTATE)

with open('/data/affinity/2d/overlap_tiny/OVERLAP_AREAS') as fp:
    overlap_areas = pickle.load(fp)
    print(overlap_areas[:FLAGS.BATCH_SIZE])

with tf.Session() as sess:
    # Start populating the filename queue.
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(coord=coord, sess=sess)

    tf.global_variables_initializer().run()

    images = images_batch.eval()
    labels = labels_batch.eval()

    print("Labels: " + str(labels))

    # TESTING THAT THE BATCHES ARE CORRECT
    reshaped_images = np.reshape(images,
                                 newshape=[FLAGS.BATCH_SIZE, FLAGS.IMAGE_SIZE, FLAGS.IMAGE_SIZE,
                                           FLAGS.NUM_LAYERS])
    lock_image = reshaped_images[0, :, :, 0]
    lock_image_r = reshaped_images[0, :, :, 1]
    key_image = reshaped_images[0, :, :, 2]

    scipy.misc.imsave('lock.png', lock_image)
    scipy.misc.imsave('key.png', key_image)

    print("Overlap: " + str(labels[0]))


# 더하기 사이클

a = b = int(input())
count = 0
while True:
    ten = a // 10
    one = a % 10
    total = ten + one
    count = count + 1
    a = int(str(a % 10) + str(total % 10))
    if(b == a):
        break
print(count)


import numpy as np, pandas as pd
import healpy as hp
import six
import os
from healpy.sphtfunc import Alm
from healpy import cookbook as cb
import astropy.io.fits as pf
from numpy import asarray as ar

def index_of(arrval, value):
    if value < min(arrval): return 0
    return max(np.where(arrval<=value)[0])

def nearest_idx(array, value):
    return (np.abs(array-value)).argmin()

def decon_beam(Commap, cl, ell ,noise_range=None, add_back=True):
    '''
        deconvolution from beam transfer function and
        cancel out the noise based on the input l range
        assume-> Signal = W*Sky + Nise; W = BeamTF**2 * (pixel window function)**2
        Parameters:
        -----
        Commap: given compoenet map's fits filename
        noise: (lmin, lmax) tuple, average this range of noise
        ell: given ell you want to deconvolve

        reutrn cl
        '''
    # get bin width from ells
    bin = np.int(ell[1] - ell[0])

    # subtract the noise
    if noise_range:
        noise = cl[index_of(ell, noise_range[0]):index_of(ell, noise_range[1])].mean()
        cl = cl - noise

        if add_back==True:
            # artifact add back the mean power of transfered best-fit cl on noise range
            cl = artifact_addback(Commap, cl, ell, noise_range)

    # get beamTF from commap fits
    beam = np.float64(hp.mrdfits(Commap,hdu=2))[0]

    # pixel window function
    NSIDE = 2048;
    pl = hp.pixwin(NSIDE); pl = pl[0:4001]

    ## resample based on given ell
    lmax = ell[-1]; lmax = np.int(lmax)
    pl = pl[0:lmax+bin:bin]
    beam = beam[0:lmax+bin:bin]

    ## becareful about the index here,
    ## by python's convention it will ignore the last element.
    return cl/beam**2./pl**2.

def artifact_addback(Commap, cl, ell, noise_range):
    '''artificially put back the mean power of cls on the tail of noise subtraction,
    the putting back mean power is the mean of the same range from transfer-function convolved best-fit ΛCDM model.
    Parameters:
    Commap: Component map's path
    cl: cl (not dl) you want to add back, assume to be numpy array
    ell: angular size
    noise_range: a tuple (lmin, lmax)

    return cl
    '''
    # beam transfer function
    beam = np.float64(hp.mrdfits(Commap,hdu=2))[0]

    # read best-fit
    best = np.loadtxt('/Users/cicero/Documents/Physics/PLANCK/Release2/COM_PowerSpect_CMB-base-plikHM-TT-lowTEB-minimum-theory_R2.02.txt')
    L = best.T[0]
    CL = (best.T[1] / L / (L + 1) * 2 * np.pi) * beam[2:len(L)+2]**2.

    # rescalling
    L = L[0:int(max(ell) + ell[1]-ell[0]):int(ell[1]-ell[0])]
    CL = CL[0:int(max(ell) + ell[1]-ell[0]):int(ell[1]-ell[0])]

    # mean power on noise range
    artifact_power = np.mean(CL[index_of(L, noise_range[0]):index_of(L, noise_range[1])])

    try: cl = cl + artifact_power
    except: cl = [c + artifact_power for c in cl]
    return cl

def read_glesp(filename, return_cos=False):
    import pyfits
    '''
        filename: str, glesp format map
        return_cos: if True, return cos(theta(rad)), too. default is False.
        return
        gcanvas: gleasp map in matrix format (x = phi,y = -theta)
    '''
    glesp = pyfits.open(filename)
    data = glesp[1].data
    gtheta = data['COS(THETA)'][0]
    gcanvas = data['TEMPERATURE'][0]
    dim = len(gtheta)                       # get the (half) length
                                            # of pixel for one theta
    gcanvas = gcanvas.reshape((dim, dim*2))
    if return_cos:
        return gcanvas, gtheta
    else:
        return gcanvas

def read_alm(filename, return_mmax=False):
    table = pd.read_csv(filename,
                        delim_whitespace=True, header = None).values
    table = table.T;
    l = table[0].astype(np.long);
    m = table[1].astype(np.long);
    almr = table[2]; almi = table[3];
    if (m<0).any():
        raise ValueError('Negative m value encountered !')
    lmax = l.max()
    mmax = m.max()
    alm = almr*(0+0j)
    i = hp.Alm.getidx(lmax,l,m)
    alm.real[i] = almr
    alm.imag[i] = almi
    if return_mmax:
        return alm, mmax
    else:
        return alm

def radialprofile(imgdata, center=None):
    y, x = np.indices((imgdata.shape))
    if not center:
        center = np.array([(x.max()-x.min())/2.0, (x.max()-x.min())/2.0])
    r = np.sqrt((x - center[0])**2 + (y - center[1])**2)
    r = np.around(r)
    r = np.int64(r)
    tbin = np.bincount(r.ravel(), imgdata.ravel())
    nr = np.bincount(r.ravel())
    radialprofile = tbin / nr
    radialprofile = np.nan_to_num(radialprofile)
    return radialprofile

def patch2cl(image, phi_size):
    ## Fast Fourier Transform
    ak = np.fft.ifft2(image)                    # FFT scaling convention
    akshift = np.fft.fftshift(ak)               # shift low k to the center
    akabs = abs(akshift)**2.                    # take absolute square

    ## average to get Ck
    ck = radialprofile(akabs)                   # make use of radial profile
    k = np.arange(len(ck))

    ## scaling relation
    L = 2.*np.pi/(360./phi_size);
    period = np.int64(360./phi_size);
    ell = 2*np.pi/L*k
    cl = L**2.*ck

    ## set limit
    ell = ell[0:(np.int(2500/period))+1]
    cl = cl[0:(np.int(2500/period))+1]
    return ell, cl

def cart_healpix(cartview, nside):
    '''read in an matrix and return a healpix pixelization map'''
    # Generate a flat Healpix map and angular to pixels
    healpix = np.zeros(hp.nside2npix(nside), dtype=np.double)
    hptheta = np.linspace(0, np.pi, num=cartview.shape[0])[:, None]
    hpphi = np.linspace(-np.pi, np.pi, num=cartview.shape[1])
    pix = hp.ang2pix(nside, hptheta, hpphi)

    # re-pixelize
    healpix[pix] = np.fliplr(np.flipud(cartview))

    return healpix

def healpix_rotate(healpix_map, rot):
    # pix-vec
    ipix = np.arange(len(healpix_map))
    nside = np.sqrt(len(healpix_map) / 12)
    if int(nside) != nside: return print('invalid nside');
    nside = int(nside)
    vec = hp.pix2vec(int(nside), ipix)
    rot_vec = (hp.rotator.Rotator(rot=rot)).I(vec)
    irotpix = hp.vec2pix(nside, rot_vec[0], rot_vec[1], rot_vec[2])
    return np.copy(healpix_map[irotpix])

def list_rebuild(index, b):
    '''
    index: list, the list you want to rebuild
    b: list, the base list you want to take it as template to rebuid a list
    return:
    rebuild_list: list, reconstructed list based on b.
    '''
    # generate aggregate index
    length = [len(B) for B in b]
    if len(index) == np.sum(length):
        sum_length = np.insert(np.add.accumulate(length), 0, 0) # accumulate is cool
        return [index[sum_length[i]:sum_length[i+1]] for i,l in enumerate(sum_length[:-1])]
    else: print ('Not the same length!')

def ones_circle(length):
    circle = np.zeros((length, length))
    y, x = np.indices((circle.shape))
    center = np.array([(x.max() - x.min()) / 2.0, (x.max() - x.min()) / 2.0])
    r = np.sqrt((x - center[0]) ** 2 + (y - center[1]) ** 2)
    circle[r < length / 2] = 1
    return circle

def ones_number_circle(length, number):

    from PIL import Image, ImageDraw, ImageFont
    from matplotlib.image import pil_to_array

    # decide your font
    fnt = ImageFont.truetype('/Library/Fonts/Georgia.ttf', 150) # 135
    circle = ones_circle(length)
    txt = Image.new('L', size=(length, length), color=1)
    draw = ImageDraw.Draw(txt)

    w, h = draw.textsize(number, font=fnt)

    draw.text(((length - w) / 2, (length - h) / 4), text=number, font=fnt, align='center')
    txt = pil_to_array(txt)
    return circle * txt

def mask_cart(corner_x, cmbmap, phisize):
    # Healpix patch catch process
    img   = cmbmap.copy()
    NSIDE = hp.get_nside(img)
    pixtheta, pixphi = hp.pix2ang(NSIDE,
                                  np.arange(hp.nside2npix(NSIDE)))
    # rescaling rad to degree
    pixtheta *= 180 / np.pi
    pixphi   *= 180 / np.pi
    # 0 1 mask making
    img[ar(pixtheta > 90 - phisize / 2) & ar(pixtheta < 90 + phisize / 2)  &
        ar(pixphi > corner_x)           & ar(pixphi < corner_x + phisize)] = 1

    return img