from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
import scipy.stats as stats
from datetime import datetime
import matplotlib.dates as mdates
import scipy.fft
import random
from ECG import ECG
import pingouin as pg
import sklearn.metrics
from csv import DictWriter

class Data:

    def __init__(self, subj_id, start_index=None, end_index=None, seg_length=15, filepath=None):
        self.subj_id = subj_id
        self.start_index = start_index
        self.end_index = end_index
        self.seg_length = seg_length

        # self.filepath = "/Users/kyleweber/Desktop/Data/OND07/EDF/OND07_WTL_{}_01_BF.edf".format(subj_id)

        if filepath is not None:
            self.filepath = filepath

        self.ecg = None
        self.nonwear = None
        self.ecg_cutoffs = None

        self.ecg_fft = None
        self.accel_fft = None

        self.start_ind = None

        self.df_ecg = None
        self.gs_log = None
        self.nonwear_stamps = None

        self.parameters_dict = {"ID": self.subj_id,
                                "ECG_Index": 0, "Valid_ECG": 0, "ECG_volt_range": 0, "ECG_dom_f": 0, "ECG_power": 0,
                                "SD_X": 0, "SD_Y": 0, "SD_Z": 0, "SVM": 0, "Accel_dom_f": 0,
                                "Accel_power_X": 0, "Accel_power_Y": 0, "Accel_power_Z": 0,
                                "Visual_nonwear": 0, "DataLength": self.seg_length, "VisualLength": 0}

    def import_ecg(self):

        self.ecg = ECG(subject_id=self.subj_id, filepath=self.filepath,
                       output_dir=None, processed_folder=None,
                       processed_file=None, ecg_downsample=2,
                       age=0, start_offset=self.start_index if self.start_index is not None else 0,
                       end_offset=self.end_index if self.end_index is not None else 0,
                       rest_hr_window=60, n_epochs_rest=10,
                       epoch_len=15, load_accel=True,
                       filter_data=False, low_f=1, high_f=30, f_type="bandpass",
                       load_raw=True, from_processed=False)

    def import_gold_standard_log(self):

        gs_log = pd.read_excel("/Users/kyleweber/Desktop/ECG Non-Wear/OND07_VisuallyInspectedECG_Nonwear.xlsx")
        self.gs_log = gs_log.loc[gs_log["ID"] == self.ecg.subject_id]

    def plot_raw(self, use_timestamps=True, write_dict=False):

        if use_timestamps:
            fig, (ax1, ax2) = plt.subplots(2, sharex='col', figsize=(10, 6))
            ax1.plot(self.ecg.timestamps[::5], self.ecg.raw[::5], color='red')
            ax2.plot(self.ecg.timestamps[::10], self.ecg.accel_vm, color='black')
            xfmt = mdates.DateFormatter("%Y/%m/%d\n%H:%M:%S")
            ax2.xaxis.set_major_formatter(xfmt)
            plt.xticks(rotation=45, fontsize=8)

        if not use_timestamps:
            fig, (ax1, ax2) = plt.subplots(2, sharex='col', figsize=(10, 6))
            ax1.plot(np.arange(0, len(self.ecg.raw[::5])) / 2, self.ecg.raw[::5], color='red')
            ax2.plot(np.arange(0, len(self.ecg.accel_vm)), self.ecg.accel_vm, color='black')
            plt.xticks(rotation=45, fontsize=8)

        plt.show()

        if write_dict:
            self.append_parameters_dict("/Users/kyleweber/Desktop/ECG Non-Wear/ECG_Nonwear_Parameters.csv")

    def run_ecg_fft(self, start=None, fft_filtered=True, show_plot=True, print_data=True):

        if print_data:
            print("\nPerforming FFT on ECG data starting at index {} "
                  "using {}-second windows...".format(start, self.seg_length))

        if start is None:
            start = random.randint(0, len(self.ecg.raw) - self.seg_length * self.ecg.sample_rate)

        end = start + self.seg_length * self.ecg.sample_rate

        self.start_ind = start

        raw_fft = scipy.fft.fft(self.ecg.raw[start:end])

        if fft_filtered:
            filt_fft = scipy.fft.fft(self.ecg.filtered[start:end])

        xf = np.linspace(0.0, 1.0 / (2.0 * (1 / self.ecg.sample_rate)), (self.seg_length * self.ecg.sample_rate) // 2)

        if show_plot:
            fig, (ax1, ax2, ax3) = plt.subplots(3, figsize=(10, 8))
            plt.suptitle("Index = {}".format(start))
            plt.subplots_adjust(hspace=.35)

            ax1.plot(np.arange(0, self.seg_length * self.ecg.sample_rate) / self.ecg.sample_rate,
                     self.ecg.raw[start:end],
                     color='red', label='Raw')
            ax1.plot(np.arange(0, self.seg_length * self.ecg.sample_rate) / self.ecg.sample_rate,
                     self.ecg.filtered[start:end],
                     color='black', label='Filt')
            ax1.legend()
            ax1.set_xlabel("Seconds")
            ax1.set_ylabel("Voltage")

            ax2.plot(xf, 2.0 / (self.seg_length * self.ecg.sample_rate) / 2 *
                     np.abs(raw_fft[0:(self.seg_length * self.ecg.sample_rate) // 2]),
                     color='red', label="Raw")

            ax2.set_ylabel("Power")
            ax2.set_xlabel("Frequency (Hz)")
            ax2.legend()

            ax3.plot(xf, 2.0 / (self.seg_length * self.ecg.sample_rate) / 2 *
                     np.abs(filt_fft[0:(self.seg_length * self.ecg.sample_rate) // 2]),
                     color='black', label="Filt")
            ax3.fill_between(x=[self.ecg.low_f, self.ecg.high_f], y1=plt.ylim()[0], y2=plt.ylim()[1],
                             color='green', alpha=.5, label="Bandpass")
            ax3.set_ylabel("Power")
            ax3.set_xlabel("Frequency (Hz)")
            ax3.legend()

        df_raw_fft = pd.DataFrame(list(zip(xf,
                                           2.0 / (self.seg_length * self.ecg.sample_rate) /
                                           2 * np.abs(raw_fft[0:(self.seg_length * self.ecg.sample_rate) // 2]))),
                                  columns=["Frequency", "Power"])

        df = df_raw_fft.loc[df_raw_fft["Frequency"] >= .05]
        dom_f = round(df.loc[df["Power"] == df["Power"].max()]["Frequency"].values[0], 3)

        if print_data:
            print("-Dominant frequency = {} Hz".format(dom_f))
        self.parameters_dict["ECG_dom_f"] = dom_f

        return df_raw_fft

    def run_accel_fft(self, start=None, show_plot=True, print_data=True):

        if start is not None:
            if print_data:
                print("\nPerforming FFT on accelerometer data from index {} using "
                      "{}-second epochs...".format(start, self.seg_length))
        if start is None:
            if print_data:
                print("\nPerforming FFT on accelerometer data from random segment using "
                      "{}-second epochs...".format(self.seg_length))

        if start is None:
            start = random.randint(0, len(self.ecg.accel_x) - self.seg_length * self.ecg.accel_sample_rate)

        end = start + self.seg_length * self.ecg.accel_sample_rate

        fft_x = scipy.fft.fft(self.ecg.accel_x[start:end])
        fft_y = scipy.fft.fft(self.ecg.accel_y[start:end])
        fft_z = scipy.fft.fft(self.ecg.accel_z[start:end])
        fft_vm = scipy.fft.fft(self.ecg.accel_vm[start:end])

        xf = np.linspace(0.0, 1.0 / (2.0 * (1 / self.ecg.accel_sample_rate)),
                         (self.seg_length * self.ecg.accel_sample_rate) // 2)

        df_accel_fft = pd.DataFrame(list(zip(xf,
                                             2.0 / (self.seg_length * self.ecg.accel_sample_rate) /
                                             2 * np.abs(fft_x[0:(self.seg_length * self.ecg.accel_sample_rate) // 2]),

                                             2.0 / (self.seg_length * self.ecg.accel_sample_rate) /
                                             2 * np.abs(fft_y[0:(self.seg_length * self.ecg.accel_sample_rate) // 2]),

                                             2.0 / (self.seg_length * self.ecg.accel_sample_rate) /
                                             2 * np.abs(fft_z[0:(self.seg_length * self.ecg.accel_sample_rate) // 2]),

                                             2.0 / (self.seg_length * self.ecg.accel_sample_rate) /
                                             2 * np.abs(fft_vm[0:(self.seg_length * self.ecg.accel_sample_rate) // 2])
                                             )),
                                    columns=["Frequency", "Power_X", "Power_Y", "Power_Z", "Power_VM"])

        df_accel_fft = df_accel_fft.loc[df_accel_fft["Frequency"] >= .05]

        dom_fx = round(df_accel_fft.loc[df_accel_fft["Power_X"] ==
                                        df_accel_fft["Power_X"].max()]["Frequency"].values[0], 3)
        dom_fy = round(df_accel_fft.loc[df_accel_fft["Power_Y"] ==
                                        df_accel_fft["Power_Y"].max()]["Frequency"].values[0], 3)
        dom_fz = round(df_accel_fft.loc[df_accel_fft["Power_Z"] ==
                                        df_accel_fft["Power_Z"].max()]["Frequency"].values[0], 3)
        dom_fvm = round(df_accel_fft.loc[df_accel_fft["Power_VM"] ==
                                        df_accel_fft["Power_VM"].max()]["Frequency"].values[0], 3)

        self.parameters_dict["Accel_dom_f"] = [dom_fx, dom_fy, dom_fz, dom_fvm]
        if print_data:
            print("-Dominant frequencies: X = {}Hz, Y = {}Hz, Z = {}Hz.".format(dom_fx, dom_fy, dom_fz))

        if show_plot:
            fig, (ax1, ax2) = plt.subplots(2, figsize=(10, 8))
            plt.suptitle("Index = {}".format(start))
            plt.subplots_adjust(hspace=.35)

            ax1.plot(np.arange(0, self.seg_length * self.ecg.accel_sample_rate) / self.ecg.accel_sample_rate,
                     self.ecg.accel_x[start:end],
                     color='red', label='X')
            ax1.plot(np.arange(0, self.seg_length * self.ecg.accel_sample_rate) / self.ecg.accel_sample_rate,
                     self.ecg.accel_y[start:end],
                     color='black', label='Y')
            ax1.plot(np.arange(0, self.seg_length * self.ecg.accel_sample_rate) / self.ecg.accel_sample_rate,
                     self.ecg.accel_z[start:end],
                     color='dodgerblue', label='Z')
            ax1.legend()
            ax1.set_xlabel("Seconds")
            ax1.set_ylabel("mG")

            ax2.plot(df_accel_fft["Frequency"], df_accel_fft["Power_X"],
                     color='red', label="X")

            ax2.plot(df_accel_fft["Frequency"], df_accel_fft["Power_Y"],
                     color='black', label="Y")

            ax2.plot(df_accel_fft["Frequency"], df_accel_fft["Power_Z"],
                     color='dodgerblue', label="Z")

            ax2.plot(df_accel_fft["Frequency"], df_accel_fft["Power_VM"],
                     color='grey', label="VM")

            ax2.set_ylabel("Power")
            ax2.set_xlabel("Frequency (Hz)")
            ax2.legend()

        return df_accel_fft

    def plot_ecg_cumulative_fft(self, threshold=.9, show_plot=True):

        # Plots cumulative power and FFT
        self.ecg_fft["Normalized_CumulativePower"] = [i / self.ecg_fft["Power"].sum() for
                                                      i in self.ecg_fft["Power"].cumsum()]

        # Calculates frequency that corresponds to cumulative power at threshold
        cutoff_freq = round(self.ecg_fft.loc[self.ecg_fft["Normalized_CumulativePower"]
                                             >= threshold].iloc[0].values[0], 1)

        # Calculates frequency that corresponds to 0, 10, 20, 30%...power
        power_decades = [round(self.ecg_fft.loc[self.ecg_fft["Normalized_CumulativePower"]
                                                >= threshold].iloc[0].values[0], 1) for threshold in
                         np.arange(0, 1.0, .1)]

        self.parameters_dict["ECG_power"] = power_decades

        if show_plot:
            fig, (ax1, ax2) = plt.subplots(2)
            ax1.set_title("ECG Cumulative Power FFT: index = {}".format(self.start_ind))
            ax1.plot(self.ecg_fft["Frequency"], self.ecg_fft["Power"], label="FFT", color='green')
            ax1.set_ylabel("Power")
            ax1.legend()
            ax2.plot(self.ecg_fft["Frequency"], [i / self.ecg_fft["Power"].sum() for i in self.ecg_fft["Power"].cumsum()],
                     label='Cumulative power', color='black')
            ax2.axvline(x=cutoff_freq, linestyle='dashed', color='red',
                        label="{}% of power by {} Hz".format(threshold * 100, cutoff_freq))
            ax2.legend()
            ax2.set_ylabel("Normalized power")
            ax2.set_xlabel("Hz")

        return cutoff_freq

    def plot_accel_cumulative_fft(self, threshold=.9, axis="Z", start=None, show_plot=True):

        if start is None:
            start = int(self.start_ind / 10)

        # Plots cumulative power and FFT
        self.accel_fft["Normalized_CumulativePower"] = [i / self.accel_fft["Power_{}".format(axis)].sum() for
                                                        i in self.accel_fft["Power_{}".format(axis)].cumsum()]

        cutoff_freq = round(self.accel_fft.loc[self.accel_fft["Normalized_CumulativePower"]
                                               >= threshold].iloc[0].values[0], 1)

        # Calculates frequency that corresponds to 0, 10, 20, 30%...power
        power_decades = []

        for i, threshold in enumerate(np.arange(0, 1.0, .1)):
            try:
                val = round(self.accel_fft.loc[self.accel_fft["Power_{}".format(axis)]
                                               >= threshold].iloc[0].values[0], 1)
                power_decades.append(val)
            except IndexError:
                power_decades.append(power_decades[i-1])

        """
        power_decades = [round(self.accel_fft.loc[self.accel_fft["Power_{}".format(axis)]
                                                  >= threshold].iloc[0].values[0], 1) for threshold in
                           np.arange(0, 1.0, .1)]"""

        self.parameters_dict["Accel_power_{}".format(axis)] = power_decades

        if show_plot:
            fig, (ax1, ax2) = plt.subplots(2)
            ax1.set_title("Accelerometer Cumulative Power FFT")
            ax1.plot(self.accel_fft["Frequency"], self.accel_fft["Power_{}".format(axis)],
                     label="FFT", color='dodgerblue')
            ax1.set_ylabel("Power")
            ax1.legend()

            ax2.plot(self.accel_fft["Frequency"], [i / self.accel_fft["Power_{}".format(axis)].sum() for
                                                   i in self.accel_fft["Power_{}".format(axis)].cumsum()],
                     label='Cumulative power', color='black')
            ax2.axvline(x=cutoff_freq, linestyle='dashed', color='red',
                        label="{}% of power by {} Hz".format(threshold * 100, cutoff_freq))
            ax2.legend()
            ax2.set_ylabel("Normalized power")
            ax2.set_xlabel("Hz")

    def compare_accel_ffts(self, ind1, ind2):

        fig, (ax1, ax2) = plt.subplots(2, sharex='col')
        df_accel_fft = self.run_accel_fft(ind1, show_plot=False)
        ax1.plot(df_accel_fft["Frequency"],
                 [i / df_accel_fft["Power_Z"].sum() for i in df_accel_fft["Power_Z"].cumsum()],
                 label="Z_Sleep", color='black')
        ax1.fill_between(x=[1 / (60 / 12), 1 / (60 / 20)], y1=0, y2=1, color='green', alpha=.5,
                         label='Sleep breath rate')
        ax1.set_ylabel("Power")
        ax1.legend()

        df_accel_fft = self.run_accel_fft(ind2, show_plot=False)
        ax2.plot(df_accel_fft["Frequency"],
                 [i / df_accel_fft["Power_Z"].sum() for i in df_accel_fft["Power_Z"].cumsum()],
                 label="Z_Nonwear", color='black')
        ax2.fill_between(x=[1 / (60 / 12), 1 / (60 / 20)], y1=0, y2=1, color='green', alpha=.5,
                         label='Sleep breath rate')
        ax2.set_ylabel("Power")
        ax2.set_xlabel("Frequency (Hz)")
        ax2.legend()

    def calculate_nonwear(self, plot_data=True, volt_thresh=400, accel_sd_thresh=10,
                          ecg_fft_dict={"Use": False, "Percent": 70, "F": 30},
                          n_minutes=5, window_percent=80, ignore_gap=10):

        def find_nonwear():

            # First accel check: SD and range below threshold calculations -------------------------------------------
            print("\nPerforming non-wear detection algorithm...")

            accel_nw = []
            accel_sd_list = []

            for i in np.arange(0, len(self.ecg.accel_x), self.ecg.accel_sample_rate * self.seg_length):

                # Calculates SD for each accel axis
                sd_x = np.std(self.ecg.accel_x[i:i + self.ecg.accel_sample_rate * self.seg_length])
                sd_y = np.std(self.ecg.accel_y[i:i + self.ecg.accel_sample_rate * self.seg_length])
                sd_z = np.std(self.ecg.accel_z[i:i + self.ecg.accel_sample_rate * self.seg_length])

                # How many axes below threshold
                axes_below_thresh = int(sd_x <= accel_sd_thresh) + \
                                    int(sd_y <= accel_sd_thresh) + \
                                    int(sd_z <= accel_sd_thresh)

                # Average SD across axes
                avg_sd = (sd_x + sd_y + sd_z) / 3
                accel_sd_list.append(round(avg_sd, 2))

                # Calculates accelerometer signal range
                range_x = max(self.ecg.accel_x[i:i + self.ecg.accel_sample_rate * self.seg_length]) - \
                          min(self.ecg.accel_x[i:i + self.ecg.accel_sample_rate * self.seg_length])
                range_y = max(self.ecg.accel_y[i:i + self.ecg.accel_sample_rate * self.seg_length]) - \
                          min(self.ecg.accel_y[i:i + self.ecg.accel_sample_rate * self.seg_length])
                range_z = max(self.ecg.accel_z[i:i + self.ecg.accel_sample_rate * self.seg_length]) - \
                          min(self.ecg.accel_z[i:i + self.ecg.accel_sample_rate * self.seg_length])

                # How many accel axes below range threshold (50mG)
                axes_below_range = int(range_x <= 50) + int(range_y <= 50) + int(range_z <= 50)

                # if axes_below_range >= 2 or axes_below_thresh >= 2:
                if axes_below_thresh >= 2 or avg_sd <= accel_sd_thresh:
                    accel_nw.append("Nonwear")
                else:
                    accel_nw.append("Wear")

            # ECG FFT data --------------------------------------------------------------------------------------------
            if ecg_fft_dict["Use"] and self.ecg_cutoffs is None:

                cutoff_freqs = []
                dom_freqs = []

                for epoch in np.arange(0, len(self.ecg.raw), self.seg_length * self.ecg.sample_rate):

                    if self.ecg.epoch_validity[epoch] == "Valid":
                        cutoff_freqs.append(0)

                    if self.ecg.epoch_validity[epoch] == "Invalid":

                        # Runs FFT
                        ecg_fft = self.run_ecg_fft(start=int(epoch * self.ecg.sample_rate), fft_filtered=False,
                                                   show_plot=False, print_data=False)

                        # Finds dominant frequency
                        dom_f_ecg = ecg_fft.loc[ecg_fft["Power"] == max(ecg_fft["Power"])]["Frequency"].iloc[0]
                        dom_freqs.append(dom_f_ecg)

                        # Calculates frequency that corresponds to cumulative power at ecg_fft_dict["Percent"]
                        ecg_fft["Normalized_CumulativePower"] = [i / ecg_fft["Power"].sum() for
                                                                 i in ecg_fft["Power"].cumsum()]

                        cutoff_f = ecg_fft.loc[ecg_fft["Normalized_CumulativePower"] >=
                                               ecg_fft_dict["Percent"]/100].iloc[0]["Frequency"]
                        cutoff_freqs.append(cutoff_f)

            if ecg_fft_dict["Use"] and self.ecg_cutoffs is not None:
                cutoff_freqs = self.ecg_cutoffs

                """PLACEHOLDER"""
                dom_freqs = [None for i in range(len(self.ecg.epoch_validity))]

            if not ecg_fft_dict["Use"]:
                cutoff_freqs = [None for i in range(len(self.ecg.epoch_validity))]
                dom_freqs = [None for i in range(len(self.ecg.epoch_validity))]

            # Combines accelerometer and ECG non-wear characteristics: epoch-by-epoch ---------------------------------
            df_ecg = pd.DataFrame(list(zip(self.ecg.epoch_timestamps, self.ecg.epoch_validity,
                                           self.ecg.avg_voltage, cutoff_freqs, dom_freqs,
                                           self.ecg.svm, accel_sd_list, accel_nw)),
                                  columns=["Stamp", "Validity", "VoltRange", "CutoffF", "DomF",
                                           "SVM", "AccelSD", "AccelNW"])

            # Checks data to see if below thresholds
            nw = []

            for epoch in df_ecg.itertuples():

                if ecg_fft_dict["Use"]:
                    if epoch.Validity == "Invalid" and epoch.AccelNW == "Nonwear" and epoch.VoltRange <= volt_thresh \
                            and epoch.CutoffF >= ecg_fft_dict["CutoffF"]:
                        nw.append("Nonwear")
                    else:
                        nw.append("Wear")

                if not ecg_fft_dict["Use"]:
                    if epoch.Validity == "Invalid" and epoch.AccelNW == "Nonwear" and epoch.VoltRange <= volt_thresh:

                        nw.append("Nonwear")
                    else:
                        nw.append("Wear")

            # X-minute windows ----------------------------------------------------------------------------------------
            t0 = datetime.now()
            final_nw = np.zeros(len(nw))

            n_epochs = int(n_minutes * 60 / self.seg_length)
            n_epochs_thresh = int(n_epochs * window_percent / 100)

            for i in range(len(nw)):

                if nw[i:i + n_epochs].count("Nonwear") >= n_epochs_thresh:
                    final_nw[i:i + n_epochs] = 1

                    for j in range(i, len(nw)):
                        """if nw[j] == "Nonwear":
                            pass
                        if nw[j] == "Wear":
                            stop_ind = j
                            if j > i:
                                final_nw[i:stop_ind] = 1"""
                        if "Nonwear" in nw[j:j+int(60/self.seg_length)]:
                            pass
                        if "Nonwear" not in nw[j:j+int(60/self.seg_length)]:
                            stop_ind = j
                            if j > i:
                                final_nw[i:stop_ind] = 1

                else:
                    final_nw[i] = 0

            final_nw = ["Nonwear" if i == 1 else "Wear" for i in final_nw]

            t1 = datetime.now()
            print("Algorithm time = {} seconds.".format(round((t1 - t0).total_seconds(), 1)))

            return final_nw, df_ecg

        if self.nonwear is None:
            final_nw, df_ecg = find_nonwear()

        if self.nonwear is not None:
            print("\nData already exists. Using previous data.")
            final_nw = self.nonwear
            df_ecg = self.df_ecg

        # Gets timestamps when device was removed and put back on ----------------------------------------------------
        on_stamps = []
        off_stamps = []

        for time, stamp1, stamp2 in zip(self.ecg.epoch_timestamps[1:], final_nw[:], final_nw[1:]):
            if stamp1 == "Wear" and stamp2 == "Nonwear":
                off_stamps.append(time)
            if stamp1 == "Nonwear" and stamp2 == "Wear":
                on_stamps.append(time)

        df_stamps = pd.DataFrame(list(zip(off_stamps, on_stamps)), columns=["start_time", "end_time"])

        # Combines consecutive nonwear periods that were too close together ------------------------------------------
        n_gap_epochs = ignore_gap * 60 / self.seg_length

        d = pd.DataFrame(list(zip(self.ecg.epoch_timestamps, final_nw)), columns=["Stamp", "Status"])

        # Gets indexes of off/on stamps
        off_ind = []
        on_ind = []

        for i in range(df_stamps.shape[0]):

            # Finds data in non-wear period
            nw = d.loc[(d["Stamp"] >= df_stamps.iloc[i]["start_time"]) &
                       (d['Stamp'] < df_stamps.iloc[i]["end_time"])]

            off_ind.append(nw.index[0])  # start index
            on_ind.append(nw.index[-1])  # stop index

        # Adds fake off stamp if only one period was found
        if len(off_ind) == 1:
            off_ind.insert(0, 0)

        # Changes data between consecutive periods to nonwear if too close together
        really_final_nw = np.asarray(final_nw)

        for off, on in zip(off_ind[1:], on_ind[:]):

            if (off - on) <= n_gap_epochs:
                really_final_nw[on:off] = "Nonwear"

        if plot_data:

            print("Generating plot...")

            fig, (ax1, ax2, ax3) = plt.subplots(3, sharex='col', figsize=(10, 7))
            plt.suptitle(self.ecg.subject_id)
            ax1.plot(self.ecg.timestamps[::int(5 * self.ecg.sample_rate / 250)],
                     self.ecg.raw[::int(5 * self.ecg.sample_rate / 250)], color='black')
            ax1.set_ylabel("ECG Voltage")

            ax2.plot(self.ecg.timestamps[::int(10 * self.ecg.sample_rate / 250)], self.ecg.accel_x, color='dodgerblue')
            ax2.set_ylabel("Accel VM")

            ax3.plot(self.ecg.epoch_timestamps[0:min([len(self.ecg.epoch_timestamps), len(self.ecg.epoch_validity)])],
                     self.ecg.epoch_validity[0:min([len(self.ecg.epoch_timestamps), len(self.ecg.epoch_validity)])],
                     color='black')
            ax3.fill_between(x=self.ecg.epoch_timestamps[0:min([len(self.ecg.epoch_timestamps), len(really_final_nw)])],
                             y1="Wear", y2=final_nw, color='grey')

            if self.gs_log.shape[0] >= 1:
                for row in self.gs_log.itertuples():
                    ax1.fill_between(x=[row.Start, row.Stop],
                                     y1=min(self.ecg.filtered[::5]), y2=max(self.ecg.filtered[::5]),
                                     color='red', alpha=.5)
                    ax2.fill_between(x=[row.Start, row.Stop], y1=min(self.ecg.accel_x), y2=max(self.ecg.accel_x),
                                     color='red', alpha=.5)

            xfmt = mdates.DateFormatter("%Y/%m/%d\n%H:%M:%S")

            ax3.xaxis.set_major_formatter(xfmt)
            plt.xticks(rotation=45, fontsize=8)

        return final_nw, df_stamps, df_ecg

    def calculate_nonwear_v2(self, volt_thresh=350, ecg_dom_f_thresh=5, ecg_power_thresh={"Percent": 70, "F": 30},
                             accel_dom_f_thresh=0.5, accel_sd_thresh=25,
                             n_conditions=2, plot_data=True, window_len=5, percent_nonwear=80, ignore_gap=10):

        print("\nPerforming non-wear detection algorithm...")
        t0 = datetime.now()

        nonwear_status = []

        # Loops through epochs
        for epoch in np.arange(0, len(self.ecg.epoch_validity)):

            # Calculates corresponding accelerometer data indexes
            accel_epoch_len = int(self.ecg.sample_rate / self.ecg.accel_sample_rate)
            raw_accel_ind = int(epoch * accel_epoch_len * self.ecg.accel_sample_rate)

            # Sets epoch as wear if ECG signal was valid
            if self.ecg.epoch_validity[epoch] == "Valid":
                nonwear_status.append("Wear")

            # Runs additional analyses if ECG signal is invalid =======================================================
            if self.ecg.epoch_validity[epoch] == "Invalid":

                # Calculates average axes SD if invalid ECG period ----------------------------------------------------
                sd_x = np.std(self.ecg.accel_x[raw_accel_ind:raw_accel_ind + accel_epoch_len])
                sd_y = np.std(self.ecg.accel_y[raw_accel_ind:raw_accel_ind + accel_epoch_len])
                sd_z = np.std(self.ecg.accel_z[raw_accel_ind:raw_accel_ind + accel_epoch_len])

                avg = (sd_x + sd_y + sd_z) / 3
                accel_sd_nonwear = True if avg <= accel_sd_thresh else False

                # Calculates dominant frequency in accel signal -------------------------------------------------------
                if accel_dom_f_thresh is not None:
                    accel_fft = self.run_accel_fft(start=raw_accel_ind, show_plot=False, print_data=False)
                    dom_f_x = accel_fft.loc[accel_fft["Power_X"] == max(accel_fft["Power_X"])]["Frequency"].iloc[0]

                    accel_f_nonwear = True if dom_f_x >= accel_dom_f_thresh else False

                if accel_dom_f_thresh is None:
                    accel_f_nonwear = False

                # Calculates dominant frequency in ECG signal --------------------------------------------------------
                if ecg_dom_f_thresh is not None:
                    ecg_fft = self.run_ecg_fft(start=int(epoch * self.ecg.sample_rate), fft_filtered=False,
                                               show_plot=False, print_data=False)
                    dom_f_ecg = ecg_fft.loc[ecg_fft["Power"] == max(ecg_fft["Power"])]["Frequency"].iloc[0]

                    ecg_f_nonwear = True if dom_f_ecg >= ecg_dom_f_thresh else False

                if ecg_dom_f_thresh is None:
                    ecg_f_nonwear = False

                # Calculates cumulative frequency power in ECG signal ------------------------------------------------
                if ecg_power_thresh["F"] is not None and ecg_power_thresh["Percent"] is not None:
                    ecg_fft = self.run_ecg_fft(start=int(epoch * self.ecg.sample_rate),
                                               show_plot=False, print_data=False)

                    ecg_fft["Normalized_CumulativePower"] = [i / ecg_fft["Power"].sum() for
                                                             i in ecg_fft["Power"].cumsum()]

                    cutoff_f = ecg_fft.loc[ecg_fft["Normalized_CumulativePower"] >=
                                           ecg_power_thresh["Percent"]/100].iloc[0]["Frequency"]

                    ecg_power_nonwear = True if cutoff_f >= ecg_power_thresh["F"] else False

                if ecg_power_thresh["F"] is None and ecg_power_thresh["Percent"] is None:
                    ecg_power_nonwear = False

                # Checks ECG voltage range ----------------------------------------------------------------------------
                volt_nonwear = True if self.ecg.avg_voltage[epoch] <= volt_thresh else False

                # Rule pass tally -------------------------------------------------------------------------------------
                if volt_nonwear + accel_sd_nonwear + accel_f_nonwear + ecg_f_nonwear + ecg_power_nonwear \
                        >= n_conditions:
                    nonwear_status.append("Nonwear")

                if volt_nonwear + accel_sd_nonwear + accel_f_nonwear + ecg_f_nonwear + ecg_power_nonwear \
                        < n_conditions:
                    nonwear_status.append("Wear")

        # Requiring x-minute windows ----------------------------------------------------------------------------------
        final_nw = np.zeros(len(nonwear_status))
        n_epochs = int(window_len * 60 / self.ecg.epoch_len)

        for i in range(len(nonwear_status)):

            if final_nw[i] == "Wear" or final_nw[i] == "Nonwear":
                pass

            if nonwear_status[i:i + n_epochs].count("Nonwear") >= int(n_epochs * percent_nonwear / 100):

                final_nw[i:i + n_epochs] = 1

                for j in range(i, len(nonwear_status)):
                    if nonwear_status[j] == "Nonwear":
                        pass
                    if nonwear_status[j] == "Wear":
                        stop_ind = j
                        if j > i:
                            final_nw[i:stop_ind] = 1

            else:
                final_nw[i] = 0

        final_nw = ["Nonwear" if i == 1 else "Wear" for i in final_nw]

        # Calculates on/off timestamps --------------------------------------------------------------------------------
        on_stamps = []
        off_stamps = []

        for time, stamp1, stamp2 in zip(self.ecg.epoch_timestamps[1:], final_nw[:], final_nw[1:]):
            if stamp1 == "Wear" and stamp2 == "Nonwear":
                off_stamps.append(time)
            if stamp1 == "Nonwear" and stamp2 == "Wear":
                on_stamps.append(time)

        df_stamps = pd.DataFrame(list(zip(off_stamps, on_stamps)), columns=["start_time", "end_time"])

        # Removes gaps that are close together ------------------------------------------------------------------------
        d = pd.DataFrame(list(zip(self.ecg.epoch_timestamps, final_nw)), columns=["Stamp", "Status"])

        n_gap_epochs = ignore_gap * 60 / self.ecg.epoch_len
        off_ind = []
        on_ind = []

        # Gets indexes of off/on stamps
        for i in range(df_stamps.shape[0]):

            # Finds data in non-wear period
            nw = d.loc[(d["Stamp"] >= df_stamps.iloc[i]["start_time"]) &
                       (d['Stamp'] < df_stamps.iloc[i]["end_time"])]

            off_ind.append(nw.index[0])  # start index
            on_ind.append(nw.index[-1])  # stop index

        # Adds fake off stamp if only one period was found
        if len(off_ind) == 1:
            off_ind.insert(0, 0)

        # Changes data between consecutive periods to nonwear if too close together
        really_final_nw = np.asarray(final_nw)

        for off, on in zip(off_ind[1:], on_ind[:]):

            if (off - on) <= n_gap_epochs:
                really_final_nw[on:off] = "Nonwear"

        t1 = datetime.now()

        print("-Complete ({} seconds).".format(round((t1-t0).total_seconds()), 1))

        # PLOTTING ----------------------------------------------------------------------------------------------------
        if plot_data:

            print("\nGenerating plot...")

            fig, (ax1, ax2, ax3) = plt.subplots(3, sharex='col', figsize=(10, 7))
            plt.suptitle(self.ecg.subject_id)
            ax1.plot(self.ecg.timestamps[::int(5 * self.ecg.sample_rate / 250)],
                     self.ecg.raw[::int(5 * self.ecg.sample_rate / 250)], color='black')
            ax1.set_ylabel("ECG Voltage")

            ax2.plot(self.ecg.timestamps[::int(self.ecg.sample_rate/self.ecg.accel_sample_rate)],
                     self.ecg.accel_x, color='dodgerblue')
            ax2.set_ylabel("Accel VM")

            ax3.plot(self.ecg.epoch_timestamps[0:min([len(self.ecg.epoch_timestamps), len(self.ecg.epoch_validity)])],
                     self.ecg.epoch_validity[0:min([len(self.ecg.epoch_timestamps), len(self.ecg.epoch_validity)])],
                     color='black')
            ax3.fill_between(x=self.ecg.epoch_timestamps[0:min([len(self.ecg.epoch_timestamps), len(really_final_nw)])],
                             y1="Wear", y2=really_final_nw, color='grey')

            if self.gs_log.shape[0] >= 1:
                for row in self.gs_log.itertuples():
                    ax1.fill_between(x=[row.Start, row.Stop],
                                     y1=min(self.ecg.filtered[::5]), y2=max(self.ecg.filtered[::5]),
                                     color='red', alpha=.5)
                    ax2.fill_between(x=[row.Start, row.Stop], y1=min(self.ecg.accel_x), y2=max(self.ecg.accel_x),
                                     color='red', alpha=.5)

            ax1.legend(labels=["Raw ECG", "GS Nonwear"])
            ax2.legend(labels=["Accel_VM", "GS Nonwear"])

            xfmt = mdates.DateFormatter("%Y/%m/%d\n%H:%M:%S")

            ax3.xaxis.set_major_formatter(xfmt)
            plt.xticks(rotation=45, fontsize=8)

        return really_final_nw, df_stamps, off_ind, on_ind

    def calculate_freq_cutoffs(self, epoch_len=15, percent=70):

        print("\nCalculating frequency of {}% cumulative FFT power for {}-second epochs...".format(percent, epoch_len))
        ecg_cutoffs = []

        t0 = datetime.now()

        percent_markers = [int(i) for i in np.arange(0, len(self.ecg.raw) * 1.1, len(self.ecg.raw)/10)]
        marker_tally = 0
        # Loops through all data
        for i in np.arange(0, len(self.ecg.raw), epoch_len * self.ecg.sample_rate):

            if percent_markers[marker_tally] <= i <= percent_markers[marker_tally + 1]:
                marker_tally += 1

                print("-{}% done ({} seconds)...".format(int(100 * i / len(self.ecg.raw)),
                                                         round((datetime.now() - t0).total_seconds(), 1)))

            try:

                ecg_fft = self.run_ecg_fft(start=i, show_plot=False, print_data=False, fft_filtered=False)
                ecg_fft["Normalized_CumulativePower"] = [i / ecg_fft["Power"].sum() for
                                                         i in ecg_fft["Power"].cumsum()]

                cutoff_freq = round(ecg_fft.loc[ecg_fft["Normalized_CumulativePower"]
                                                >= percent / 100].iloc[0].values[0], 1)

                ecg_cutoffs.append(cutoff_freq)

            except IndexError:
                ecg_cutoffs.append(None)

        t1 = datetime.now()
        print("Complete. Time =", round((t1 - t0).total_seconds(), 1), "seconds")

        return ecg_cutoffs

    def complete_parameter_dict(self):

        self.parameters_dict["ECG_Index"] = self.start_index + 15 * self.ecg.sample_rate
        self.parameters_dict["Valid_ECG"] = self.ecg.epoch_validity[0]
        self.parameters_dict["ECG_volt_range"] = self.ecg.avg_voltage[0]

        self.parameters_dict["SD_X"] = round(np.std(self.ecg.accel_x), 2)
        self.parameters_dict["SD_Y"] = round(np.std(self.ecg.accel_y), 2)
        self.parameters_dict["SD_Z"] = round(np.std(self.ecg.accel_z), 2)
        self.parameters_dict["SVM"] = round(sum(self.ecg.accel_vm), 2)

        self.parameters_dict["VisualLength"] = int(self.end_index / self.ecg.sample_rate)

    def append_parameters_dict(self, data_file):

        with open(data_file, "a", newline="\n") as write_obj:
            dict_writer = DictWriter(write_obj, fieldnames=self.parameters_dict.keys())

            dict_writer.writerow(self.parameters_dict)
            print("\nNew data appended to {}".format(data_file))

        df = pd.read_csv(data_file, usecols=["ID", "VisualNonwear"])

        print("-File contains {} records "
              "({} non-wear periods).".format(df.shape[0], df.loc[df["VisualNonwear"]=="Nonwear"].shape[0]))

"""x = Data(3028)
x.import_ecg()
x.import_gold_standard_log()

x.nonwear = None
x.nonwear, x.nonwear_stamps, x.df_ecg = x.calculate_nonwear(plot_data=True, volt_thresh=400, accel_sd_thresh=10,
                                                            ecg_fft_dict={"Use": True, "Percent": 70, "F": 30},
                                                            n_minutes=5, window_percent=80, ignore_gap=10)"""

# Sort of short period/no gap removal
# Fix errors when ecg_fft_dict["Use"] = True

class GoldStandardReview:

    def __init__(self, file="/Users/kyleweber/Desktop/ECG Non-Wear/ECG_Nonwear_Parameters.csv"):

        self.file = file

        self.df = None

        self.df_ecg_power = None
        self.df_accel_power = None

        self.df_nw = None
        self.df_valid = None
        self.df_invalid_wear = None

        self.anova = None
        self.posthoc = None

        self.import_file()

    def import_file(self):

        print("\nImporting data...")

        self.df = pd.read_csv(self.file)

        self.df["Accel_avg"] = (self.df["SD_X"] + self.df["SD_Y"] + self.df["SD_Z"]) / 3

        self.df_nw = self.df.loc[self.df["VisualNonwear"] == "Nonwear"]
        self.df_valid = self.df.loc[self.df["Valid_ECG"] == "Valid"]
        self.df_invalid_wear = self.df.loc[(self.df["Valid_ECG"] == "Invalid") &
                                           (self.df["VisualNonwear"] == "Wear")]

        self.df["Group"] = self.df["Valid_ECG"] + self.df["VisualNonwear"]

        self.format_accel_dom_f()
        self.format_ecg_power()

    def format_accel_dom_f(self):
        """Converts strings to list of floats."""

        self.df["Accel_dom_f_X"] = [self.df.iloc[i]["Accel_dom_f"][1:-1] for i in range(self.df.shape[0])]

        l = [self.df["Accel_dom_f_X"].iloc[i].split(",") for i in range(self.df.shape[0])]

        for window in l:
            if len(window) == 3:
                window.append(None)

        df = pd.DataFrame(l, columns=["Accel_dom_f_X", "Accel_dom_f_Y", "Accel_dom_f_Z", "Accel_dom_f_VM"],
                          dtype='float')

        self.df["Accel_dom_f_X"] = df["Accel_dom_f_X"]
        self.df["Accel_dom_f_Y"] = df["Accel_dom_f_Y"]
        self.df["Accel_dom_f_Z"] = df["Accel_dom_f_Z"]
        self.df["Accel_dom_f_VM"] = df["Accel_dom_f_VM"]

        self.df["Accel_dom_f_avg"] = df["Accel_dom_f_X"] + df["Accel_dom_f_Y"] + df["Accel_dom_f_Z"]

        dead_data = self.df.drop("Accel_dom_f", axis=1)

    def format_ecg_power(self):
        """Converts strings to list of floats. Makes new df of power data"""

        print("\nFormatting ECG signal power data...")

        data = []
        for row in range(self.df.shape[0]):
            l = [float(i) for i in list(self.df.iloc[row]["ECG_power"][1:-1].split(","))]

            data.append(l)

        self.df["ECG_power"] = data

        self.df_ecg_power = pd.DataFrame([i for i in self.df["ECG_power"]],
                                         columns=["{}%".format(p) for p in np.arange(0, 100, 10)])
        self.df_ecg_power["VisualNonwear"] = self.df["VisualNonwear"]
        self.df_ecg_power["Valid_ECG"] = self.df["Valid_ECG"]
        self.df_ecg_power["Group"] = self.df["Group"]

    def analyze_ecg_power(self, error_range="95%CI"):
        """Analyzes cumulative ECG frequency power data."""

        print("\nAnalyzing ECG cumulative frequency power data...")

        df = self.df_ecg_power

        validwear = df.groupby("Group").get_group("ValidWear")
        invalidwear = df.groupby("Group").get_group("InvalidWear")
        nonwear = df.groupby("Group").get_group("InvalidNonwear")

        nw_desc = nonwear.describe().loc[["mean", "std", "count"]].transpose()
        nw_desc["95%CI"] = nw_desc["std"] / np.sqrt(nw_desc["count"]) * scipy.stats.t.ppf(.95, nw_desc["count"] - 1)

        wear_desc = validwear.describe().loc[["mean", "std", "count"]].transpose()
        wear_desc["95%CI"] = wear_desc["std"] / np.sqrt(wear_desc["count"]) * \
                             scipy.stats.t.ppf(.95, wear_desc["count"] - 1)

        invalidwear_desc = invalidwear.describe().loc[["mean", "std", "count"]].transpose()
        invalidwear_desc["95%CI"] = invalidwear_desc["std"] / np.sqrt(invalidwear_desc["count"]) * \
                                    scipy.stats.t.ppf(.95, invalidwear_desc["count"] - 1)

        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=nw_desc['mean'] - nw_desc[error_range], y2=nw_desc['mean'],
                         label="Nonwear", color='red', alpha=.25)
        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=nw_desc['mean'], y2=nw_desc['mean'] + nw_desc[error_range],
                         color='red', alpha=.25)

        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=wear_desc['mean'] - wear_desc[error_range], y2=wear_desc['mean'],
                         label="Wear", color='green', alpha=.25)
        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=wear_desc['mean'], y2=wear_desc['mean'] + wear_desc[error_range], color='green', alpha=.25)

        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=invalidwear_desc['mean'] - invalidwear_desc[error_range], y2=invalidwear_desc['mean'],
                         label="Invalid", color='orange', alpha=.25)
        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=invalidwear_desc['mean'], y2=invalidwear_desc['mean'] + invalidwear_desc[error_range],
                         color='orange', alpha=.25)

        plt.legend(loc='upper left')
        plt.title("Cumulative ECG Frequency Power ({})".format(error_range))
        plt.xlabel("% of signal power ≤ value")
        plt.ylabel("Hz")
        plt.xlim(0, 100)
        plt.xticks(np.arange(0, 110, 10))
        plt.yticks(np.arange(0, plt.ylim()[1], 25))

    def analyze_accel_power(self, error_range="95%CI"):
        """Analyzes cumulative Accelerometer frequency power data."""

        print("\nAnalyzing Accelerometer cumulative frequency power data...")

        df = self.df_accel_power

        validwear = df.groupby("Group").get_group("ValidWear")
        invalidwear = df.groupby("Group").get_group("InvalidWear")
        nonwear = df.groupby("Group").get_group("InvalidNonwear")

        nw_desc = nonwear.describe().loc[["mean", "std", "count"]].transpose()
        nw_desc["95%CI"] = nw_desc["std"] / np.sqrt(nw_desc["count"]) * scipy.stats.t.ppf(.95, nw_desc["count"] - 1)

        wear_desc = validwear.describe().loc[["mean", "std", "count"]].transpose()
        wear_desc["95%CI"] = wear_desc["std"] / np.sqrt(wear_desc["count"]) * \
                             scipy.stats.t.ppf(.95, wear_desc["count"] - 1)

        invalidwear_desc = invalidwear.describe().loc[["mean", "std", "count"]].transpose()
        invalidwear_desc["95%CI"] = invalidwear_desc["std"] / np.sqrt(invalidwear_desc["count"]) * \
                                    scipy.stats.t.ppf(.95, invalidwear_desc["count"] - 1)

        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=nw_desc['mean'] - nw_desc[error_range], y2=nw_desc['mean'],
                         label="Nonwear", color='red', alpha=.25)
        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=nw_desc['mean'], y2=nw_desc['mean'] + nw_desc[error_range],
                         color='red', alpha=.25)

        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=wear_desc['mean'] - wear_desc[error_range], y2=wear_desc['mean'],
                         label="Wear", color='green', alpha=.25)
        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=wear_desc['mean'], y2=wear_desc['mean'] + wear_desc[error_range], color='green', alpha=.25)

        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=invalidwear_desc['mean'] - invalidwear_desc[error_range], y2=invalidwear_desc['mean'],
                         label="Invalid", color='orange', alpha=.25)
        plt.fill_between(x=np.arange(0, 100, 10),
                         y1=invalidwear_desc['mean'], y2=invalidwear_desc['mean'] + invalidwear_desc[error_range],
                         color='orange', alpha=.25)

        plt.legend(loc='upper left')
        plt.title("Cumulative ECG Frequency Power ({})".format(error_range))
        plt.xlabel("% of signal power ≤ value")
        plt.ylabel("Hz")
        plt.xlim(0, 100)
        plt.xticks(np.arange(0, 110, 10))
        plt.yticks(np.arange(0, plt.ylim()[1], 25))

    def generate_boxplot(self, col_name):

        boxprops = dict(linewidth=1.5)
        medianprops = dict(linewidth=1.5)

        units_dict = {"ECG_volt_range": "Voltage", "ECG_dom_f": "Hz", "SVM": "Counts", "Accel_avg": "Avg SD (mg)",
                      "Accel_dom_f_X": "Hz", "Accel_dom_f_Y": "Hz", "Accel_dom_f_Z": "Hz", "Accel_dom_f_VM": "Hz"}

        self.df.loc[self.df["VisualNonwear"] != "Unsure"].boxplot(column=[col_name],
                                                                  by=["Valid_ECG", "VisualNonwear"],
                                                                  grid=False,
                                                                  boxprops=boxprops, medianprops=medianprops,
                                                                  showfliers=False, showmeans=False,
                                                                  figsize=(10, 6))
        plt.ylabel(units_dict[col_name])

    def perform_anova(self, data, error_bars="SD", plot_type="bar", save_dir=None):

        # Runs one-way ANOVA ------------------------------------------------------------------------------------------

        df = self.df
        self.anova = pg.anova(data=df, dv=data, between="Group", detailed=True)
        print("\nOne-way ANOVA results:")
        print(self.anova.round(3)[["Source", "F", "p-unc"]])

        validwear = df.groupby("Group").get_group("ValidWear")[data]
        invalidwear = df.groupby("Group").get_group("InvalidWear")[data]
        nonwear = df.groupby("Group").get_group("InvalidNonwear")[data]

        print("\nUnpaired T-Test results:")
        pair1 = pg.ttest(validwear, invalidwear, paired=False)[["T", "dof", "p-val", "CI95%", "cohen-d", "power"]]
        pair2 = pg.ttest(validwear, nonwear, paired=False)[["T", "dof", "p-val", "CI95%", "cohen-d", "power"]]
        pair3 = pg.ttest(nonwear, invalidwear, paired=False)[["T", "dof", "p-val", "CI95%", "cohen-d", "power"]]

        df_ttest = pair1.append(other=(pair2, pair3))
        df_ttest.index = ["ValidWear-InvalidWear", "ValidWear-Nonwear", "Nonwear-InvalidWear"]
        df_ttest.insert(loc=0, column="Variable", value=[data, data, data])
        print(df_ttest.round(3)[["T", "p-val", "cohen-d"]])

        self.posthoc = df_ttest

        # PLOTTING ---------------------------------------------------------------------------------------------------

        tally = df.iloc[:, 2:].groupby(["Group"]).count().transpose()
        means = df.iloc[:, 2:].groupby(["Group"]).mean().transpose()
        stds = df.iloc[:, 2:].groupby(["Group"]).std().transpose()

        units_dict = {"ECG_volt_range": "Voltage", "ECG_dom_f": "Hz", "SVM": "Counts", "Accel_avg": "Avg SD (mg)",
                      "Accel_dom_f_X": "Hz", "Accel_dom_f_Y": "Hz", "Accel_dom_f_Z": "Hz",
                      "Accel_dom_f_VM": "Hz", "Accel_dom_f_avg": "Hz"}

        import scipy.stats
        plt.close("all")

        if error_bars == "SEM":
            plt.bar(means.columns, means.loc[data], color=["red", "grey", "darkorange", "green"],
                    edgecolor='black',
                    alpha=.7, yerr=stds.loc[data] / np.sqrt(tally.loc[data]), capsize=4)

        if error_bars == "STD" or error_bars == "SD":
            plt.bar(means.columns, means.loc[data], color=["red", "grey", "darkorange", "green"],
                    edgecolor='black',
                    alpha=.7, yerr=stds.loc[data], capsize=4)

        if "CI" in error_bars or "ci" in error_bars:
            t_crits = [scipy.stats.t.ppf(q=int(error_bars.split("%")[0])/100, df=i-1) for
                       i in [tally.loc[data].iloc[j] for j in range(tally.shape[1])]]

            cis = [stds.loc[data].iloc[i] / np.sqrt(tally.loc[data].iloc[i]) * t_crits[i]
                   for i in range(tally.shape[1])]

            if plot_type == "bar":
                plt.bar(means.columns, means.loc[data], color=["red", "grey", "darkorange", "green"],
                        edgecolor='black',
                        alpha=.7, yerr=cis, capsize=4)

        plt.title("{} data (mean ± {})".format(data, error_bars))
        plt.ylabel(units_dict[data])
        plt.xlabel("Data Description")

        if save_dir is not None:
            plt.savefig(save_dir + "ANOVA_Output_{}.png".format(data))
            self.anova.to_excel(save_dir + "ANOVA_{}.xlsx".format(data), index=False)
            self.posthoc.to_excel(save_dir + "Posthoc_{}.xlsx".format(data))

    def recalculate_nonwear(self, volt_thresh=None, ecg_dom_f_thresh=None, accel_dom_f_thresh=None,
                            accel_sd_thresh=None, ecg_perc_thresh=None, ecg_f_thresh=None,
                            n_conditions=None, print_data=True):

        if print_data:
            print("\nRunning algorithm:")
            if volt_thresh is not None:
                print("-Voltage range threshold = {} mV".format(volt_thresh))
            if ecg_dom_f_thresh is not None:
                print("-ECG dominant frequency threshold = {} Hz".format(ecg_dom_f_thresh))
            if ecg_perc_thresh is not None and ecg_f_thresh is not None:
                print("-ECG cumulative frequency threshold = {}% and {} Hz".format(ecg_perc_thresh, ecg_f_thresh))
            if accel_sd_thresh is not None:
                print("-Accelerometer SD threshold = {}".format(accel_sd_thresh))
            if accel_dom_f_thresh is not None:
                print("-Accelerometer dominant threshold = {} Hz".format(accel_dom_f_thresh))
            print("-Requires {} conditions to be classified as nonwear".format(n_conditions))

        var_use_dict = {"ECG_volt_range": True if volt_thresh is not None else False,
                        "ECG_dom_f": True if ecg_dom_f_thresh is not None else False,
                        "Accel_dom_f": True if accel_dom_f_thresh is not None else False,
                        "Accel_avg": True if accel_sd_thresh is not None else False,
                        "ECG_cf_perc": True if ecg_perc_thresh is not None else False,
                        "ECG_cf_f": True if ecg_f_thresh is not None else False}

        df = self.df.loc[self.df["Group"] != "InvalidUnsure"]
        df = df[["Valid_ECG", "VisualNonwear", "Group", "ECG_volt_range", "ECG_dom_f", "Accel_avg", "Accel_dom_f_avg"]]

        if ecg_perc_thresh is not None:
            df["ECG_cf_f"] = self.df_ecg_power[ecg_perc_thresh]
        if ecg_perc_thresh is None:
            df["ECG_cf_f"] = [None for i in range(df.shape[0])]

        outcome = []
        for tup in df.itertuples():

            var_dict = {"ECG_volt_range": True if volt_thresh is not None else False,
                        "ECG_dom_f": True if ecg_dom_f_thresh is not None else False,
                        "Accel_dom_f": True if accel_dom_f_thresh is not None else False,
                        "Accel_SD": True if accel_sd_thresh is not None else False,
                        "ECG_cf_perc": True if ecg_perc_thresh is not None else False,
                        "ECG_cf_f": True if ecg_f_thresh is not None else False}

            if volt_thresh is not None:
                if tup.ECG_volt_range > volt_thresh:
                    var_dict["ECG_volt_range"] = False

            if ecg_dom_f_thresh is not None:
                if tup.ECG_dom_f < ecg_dom_f_thresh:
                    var_dict["ECG_dom_f"] = False

            if accel_dom_f_thresh is not None:
                if tup.Accel_dom_f_avg < accel_dom_f_thresh:
                    var_dict["Accel_dom_f"] = False

            if accel_sd_thresh is not None:
                if tup.Accel_avg > accel_sd_thresh:
                    var_dict["Accel_SD"] = False

            if ecg_perc_thresh is not None and ecg_f_thresh is not None:
                if ecg_f_thresh <= tup.ECG_cf_f:
                    var_dict["ECG_cf_f"] = False

            # Determines how many conditions indicate non-wear
            if [i for i in var_dict.values()].count(True) >= n_conditions:
                outcome.append("Nonwear")
            if [i for i in var_dict.values()].count(True) < n_conditions:
                outcome.append("Wear")

        df["Outcome"] = outcome

        sens, spec = self.calculate_algorithm_performance(df=df, print_data=print_data)

        tp = df.loc[(df["VisualNonwear"] == "Wear") & (df["Outcome"] == "Wear")].shape[0]
        tn = df.loc[(df["VisualNonwear"] == "Nonwear") & (df["Outcome"] == "Nonwear")].shape[0]
        correct = tp + tn
        perc_accuracy = round(100 * correct / df.shape[0], 1)

        performance_dict = {"N conditions": n_conditions,
                            "ECG_volt_range": volt_thresh, "ECG_dom_f": ecg_dom_f_thresh,
                            "Accel_dom_f_avg": accel_dom_f_thresh, "Accel_avg": accel_sd_thresh,
                            "ECG_cf_perc": ecg_perc_thresh, "ECG_cf_f": ecg_f_thresh,
                            "% Accuracy": perc_accuracy,
                            "Sensitivity": sens, "Specificity": spec,
                            "AUC": round(sklearn.metrics.roc_auc_score(y_true=[0 if i == "Wear" else 1 for
                                                                               i in df["VisualNonwear"]],
                                                                       y_score=[0 if i == "Wear" else 1 for
                                                                                i in df["Outcome"]]), 3),
                            "Distance": np.sqrt((1 - sens) ** 2 + (1 - spec) ** 2),
                            "Youden": round(sens + spec - 1, 1)}

        return performance_dict, df

    @staticmethod
    def calculate_algorithm_performance(df, print_data=True):
        """Calculates sensitivity and specificity. A nonwear period is considered a 'positive'."""

        # False positive: algorithm says nonwear; gold standard says wear
        fp = df.loc[(df["VisualNonwear"] == "Wear") & (df["Outcome"] == "Nonwear")].shape[0]

        # False negative: algorithm says wear; gold standard says nonwear
        fn = df.loc[(df["VisualNonwear"] == "Nonwear") & (df["Outcome"] == "Wear")].shape[0]

        # True positive: algorithm says nonwear; gold standard says nonwear
        tp = df.loc[(df["VisualNonwear"] == "Nonwear") & (df["Outcome"] == "Nonwear")].shape[0]

        # True negative: algorithm says wear; gold standard says wear
        tn = df.loc[(df["VisualNonwear"] == "Wear") & (df["Outcome"] == "Wear")].shape[0]

        sens = round(tp / (tp+fn), 3)
        spec = round(tn / (tn+fp), 3)
        youden = round(sens + spec - 1, 3)
        perc_acc = round(100 * (tp + tn) / df.shape[0], 1)

        if print_data:
            print("\nAlgorithm performance on gold standard dataset:")
            print("-Accuracy = {}% \n-Sensitivity = {} "
                  "\n-Specificity = {} \n-Youden's index = {}".format(perc_acc, sens, spec, youden))

        return sens, spec

    def perform_roc(self, volt_ranges=np.arange(50, 750, 100), ecg_f=np.arange(2.5, 27.5, 2.5),
                    accel_f=np.arange(0.5, 3.5, .25), accel_sd=np.arange(1, 30, 3), n_conditions=3):
        """Loops through all combinations of parameters given as arguments.
           Returns df of best AUC and minimum distance to perfect accuracy.
        """

        print("\nRunning ROC analysis...Please wait a while...")

        t0 = datetime.now()

        n_combos = len(volt_ranges) * len(ecg_f) * len(accel_f) * len(accel_sd)

        data = []
        tally = 0
        percent_markers = np.arange(0, n_combos * 1.1, n_combos/10)

        for v in volt_ranges:
            for e in ecg_f:
                for accel in accel_f:
                    for sd in accel_sd:
                        tally += 1

                        if tally in percent_markers:
                            print("{}% done ({} seconds)...".format(int(100*tally/n_combos),
                                                                    round((datetime.now() - t0).total_seconds(), 1)))

                        perf, results = self.recalculate_nonwear(volt_thresh=v, ecg_dom_f_thresh=e,
                                                                 accel_dom_f_thresh=accel, accel_sd_thresh=sd,
                                                                 n_conditions=n_conditions, print_data=False)

                        data.append([i for i in perf.values()])

        df = pd.DataFrame(data, columns=[i for i in perf.keys()])

        best_auc = df.loc[df["AUC"] == max(df["AUC"])]
        best_dist = df.loc[df["Distance"] == min(df["Distance"])]
        best_youden = df.loc[df["Youden"] == max(df["Youden"])]

        t1 = datetime.now()
        print("\n=====================================================================================================")
        print("TOTAL PROCESSING TIME: {} seconds.".format(round((t1-t0).total_seconds(), 1)))
        print("-Tested {} combinations.".format(df.shape[0]))
        print("-Required {}/4 conditions to be met for non-wear.".format(n_conditions))
        print("    -Best AUC = {}, accuracy = {}%, "
              "sensitivity = {}, specificity = {}".format(best_auc.iloc[0]["AUC"].round(3),
                                                          best_auc.iloc[0]["% Accuracy"].round(3),
                                                          best_auc.iloc[0]["Sensitivity"].round(3),
                                                          best_auc.iloc[0]["Specificity"].round(3)))
        print("    -Best distance = {}, accuracy = {}%, "
              "sensitivity = {}, specificity = {}".format(best_dist.iloc[0]["Distance"].round(3),
                                                          best_dist.iloc[0]["% Accuracy"].round(3),
                                                          best_dist.iloc[0]["Sensitivity"].round(3),
                                                          best_dist.iloc[0]["Specificity"].round(3)))
        print("    -Best Youden's index = {}, accuracy = {}%, "
              "sensitivity = {}, specificity = {}".format(best_youden.iloc[0]["Youden"].round(3),
                                                          best_youden.iloc[0]["% Accuracy"].round(3),
                                                          best_youden.iloc[0]["Sensitivity"].round(3),
                                                          best_youden.iloc[0]["Specificity"].round(3)))

        return best_auc, best_dist, best_youden

# d = GoldStandardReview()
# d.generate_boxplot("Accel_avg")
# d.perform_anova(data="SVM", error_bars="95%CI", save_dir="/Users/kyleweber/Desktop/")
# d.perform_anova(data="ECG_volt_range", error_bars="95%CI", save_dir=None)
# d.analyze_ecg_power(error_range="95%CI")

"""
# BEST PARAMETERS SO FAR
perf, results = d.recalculate_nonwear(volt_thresh=350, ecg_dom_f_thresh=5, accel_dom_f_thresh=0.5, accel_sd_thresh=25,
                                      ecg_perc_thresh=None, ecg_f_thresh=None, n_conditions=3)
"""
# auc, dist, youden = d.perform_roc(n_conditions=3)


import kasparGUI.Model as Model
from robotActionController.Data.storage import StorageFactory
from robotActionController.Processor import TriggerProcessor
from robotActionController.ActionRunner import ActionManager
from robotActionController.Robot import Robot
import datetime
import logging
from gevent import spawn
from gevent.pool import Group
from gevent.lock import RLock

class InteractionManager(object):
    def __init__(self, interactionId):
        self._interactionId = interactionId
        self._logger = logging.getLogger(self.__class__.__name__)
        ds = StorageFactory.getNewSession()
        interaction = ds.query(Model.Interaction).get(interactionId)
        if not interaction.robot:
            robot = ds.query(Model.Robot).join(Model.Setting, Model.Robot.name==Model.Setting.value).filter(Model.Setting.key=='robot').first()
            interaction.robot = robot
            ds.commit()

        robot = Robot.getRunableRobot(interaction.robot)
        self._triggerProcessor = TriggerProcessor([], robot, datetime.timedelta(seconds=0.01))
        self._triggerProcessor.triggerActivated += self._triggerActivated
        self._actionManager = ActionManager.getManager(robot)
        self._handles = {}
        self._handleLock = RLock()

    def start(self):
        self._triggerProcessor.start()

    def stop(self):
        self._triggerProcessor.stop()

    def setTriggers(self, triggers):
        self._logger.debug("Settings triggers to: %s", triggers)
        self._actionManager.clearCache()
        self._actionManager.cacheActions([t.action for t in triggers])
        self._triggerProcessor.setTriggers(triggers)

    @property
    def activeActions(self):
        with self._handleLock:
            return self._handles.keys()

    def stopAction(self, actionId):
        with self._handleLock:
            if actionId in self._handles:
                self._handles[actionId].stop()

    def _handleComplete(self, handle, logId=None):
        ds = StorageFactory.getNewSession()
        if logId:
            iLog = ds.query(Model.InteractionLog).get(logId)
            iLog.finished = datetime.datetime.utcnow()
        log = Model.DebugLog()
        log.data = ''
        for timestamp, msg in handle.output:
            log.data += '%s: %s\n' % (timestamp.isoformat(), msg)
            self._logger.debug(log.data.strip())
        ds.add(log)
        if iLog:
            iLog.logs.append(log)
        ds.commit()
        with self._handleLock:
            self._handles.pop(handle.action.id, None)

    def _getTriggers(self, ds, user, robot):
        # TODO: This needs to filter by triggers that the robot supports (sensors, and user overrides)
        return ds.query(Model.Trigger).all()

    def _triggerActivated(self, source, triggerActivatedArg):
        source = 'USER' if triggerActivatedArg.type == 'ButtonTrigger' else 'AUTOMATIC'
        self.doTrigger(triggerActivatedArg.trigger_id, triggerActivatedArg.value, source, triggerActivatedArg.action)

    def doTrigger(self, triggerId, value, source, action=None):
        ds = StorageFactory.getNewSession()
        log = Model.InteractionLog()
        log.source = source
        log.interaction_id = self._interactionId
        log.trigger_id = triggerId
        log.trigger_value = value
        ds.add(log)
        ds.commit()
        if action == None:
            action = ds.query(Model.Action).join(Model.Trigger).filter(Model.Trigger.id == triggerId).first()

        if action:
            action = self._actionManager.getRunable(action)
            if self._handles:
                with self._handleLock:
                    handles = Group()
                    for handle in self._handles.values():
                        handles.add(spawn(handle.stop))
                handles.join()

            handler = self._actionManager.executeActionAsync(action, self._handleComplete, (log.id,))

            with self._handleLock:
                self._handles[action.id] = handler
        return log


from PyQt5.QtWidgets import QMainWindow, QApplication
from main_window import *
from addmask_main import *
import sys
import traceback

class Main_GUI(Ui_MainWindow):

    def init(self):
        self.pushButton.clicked.connect(self.openDir)
        self.pushButton_2.clicked.connect(self.openDir)
        self.pushButton_3.clicked.connect(self.addMask)
        self.pushButton_4.clicked.connect(self.close)
        self.setWindowTitle("WaterMask Add")
        self.input_file_path = None
        self.output_file_path = None

    def openDir(self):
        sender = self.sender()
        text = sender.text()
        fname = QFileDialog.getExistingDirectory(self,'open dir', '.')
        if text == self.pushButton.text():
            self.textEdit.setText(fname)
            self.input_file_path = fname
        if text == self.pushButton_2.text():
            self.textEdit_2.setText(fname)
            self.output_file_path = fname

    def close(self):
        self.close()

    def addMask(self):
        TEMP_DIR = os.path.join(os.getcwd(), 'temp')
        if os.path.exists(TEMP_DIR):
            rmtree(TEMP_DIR)
        if self.input_file_path and self.output_file_path:
            self.addwaterMask = AddWaterMask(self.input_file_path,self.output_file_path)
            try:
                self.addwaterMask.run()
            except Exception as e:
                print("error: ")
                traceback.print_exc()

        else:
            QMessageBox.critical(None, "错误", "未设定输入/输出文件所在文件夹位置")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    MainWindow = QMainWindow()
    main_ui = Main_GUI()
    main_ui.setupUi(MainWindow)
    main_ui.init()
    MainWindow.show()
    sys.exit(app.exec_())
    os.system('pause')


from django.urls import path

from . import views

from resulter import settings
from django.conf.urls.static import static

urlpatterns = [
    path('', views.home, name='dashboard_home'),
    path("subject_exam_division_plots",views.subject_exam_division_plots,name="subject_exam_division_plots"),
    path("assessments_with_notes",views.assessments_with_notes,name="assessments_with_notes"),
    path("intermediate_result",views.intermediate_result,name="intermediate_result"),
    path("student_performance",views.student_performance,name="student_performance"),
    path("assessment_log",views.assessment_log,name="assessment_log"),
    path("marksheet_bulk_update_log",views.marksheet_bulk_update_log,name="marksheet_bulk_update_log"),
    path("graded_assessment_log",views.graded_assessment_log,name="graded_assessment_log")
    ]

if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root = settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models

class Migration(migrations.Migration):

    dependencies = [
        ('ideas', '0002_remove_ideas_claim'),
    ]

    operations = [
        migrations.AddField(
            model_name='ideas',
            name='public',
            field=models.BooleanField(default=True, help_text='Allows to view idea for users from other condominiums', verbose_name='Public'),
        ),
    ]


from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Services
from .serializers import ServicesSerializer

# API TESTED OK ___________________________________________________________
@api_view(['GET', 'POST'])
def services_list(request):
    if request.method == 'GET':
        services = Services.objects.all()
        serializer = ServicesSerializer(services, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = ServicesSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=201)
        return Response(serializer.errors, status=400)
# API TESTED OK ___________________________________________________________

@api_view(['GET', 'PUT', 'DELETE'])
def services_detail(request, slug):
    try:
        service = Services.objects.get(slug=slug)
    except Services.DoesNotExist:
        return Response(status=404)

    if request.method == 'GET':
        serializer = ServicesSerializer(service)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = ServicesSerializer(service, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=400)

    elif request.method == 'DELETE':
        service.delete()
        return Response(status=204)


import pygame

# Settings
WIDTH = 640
HEIGHT = 480
FPS = 120
TITLE = "Digit Recognition"

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (150, 150, 150)
RED = (255, 0, 0)
GREEN = (0, 255, 0);

class Button():
    def __init__(self, dim, color, hover_color, font, text):
        self.dim = dim
        self.color = color
        self.hover_color = hover_color
        self.hover = False
        self.text = font.render(text, True, BLACK)

    def draw(self, surface):
        if self.hover:
            pygame.draw.rect(surface, self.hover_color, self.dim)
        else:
            pygame.draw.rect(surface, self.color, self.dim)

        x_pos = self.dim.center[0] - (self.text.get_width() / 2)
        y_pos = self.dim.center[1] - (self.text.get_height() / 2)
        surface.blit(self.text, (x_pos, y_pos))


from Equation import Expression
from definitions import EquationTypes

class Formula(Expression):
    def __init__(self, constraint_spec: str = "", variables: list = None):
        self._expression = self._make_expression(constraint_spec)
        super().__init__(self._expression, variables)

    def _make_expression(self, expression: str):
        equation_type, math_symbol = self._get_equation_type(expression)
        if equation_type == EquationTypes.equation:
            expression = expression.split(math_symbol)[1]
        return expression

    @staticmethod
    def _get_equation_type(math_expression: str) -> [EquationTypes, str]:
        if '>=' in math_expression:
            equation_type = EquationTypes.inequation_gr_eq
            math_symbol = '>='
        elif '<=' in math_expression:
            equation_type = EquationTypes.inequation_sm_eq
            math_symbol = '<='
        elif '>' in math_expression:
            equation_type = EquationTypes.inequation_gr_th
            math_symbol = '>'
        elif '<' in math_expression:
            equation_type = EquationTypes.inequation_sm_th
            math_symbol = '<'
        elif '=' in math_expression:
            equation_type = EquationTypes.equation
            math_symbol = '='
        else:
            equation_type = 'undef'
            math_symbol = 'undef'

        return equation_type, math_symbol


#-------------------------------------------------------------------------
# AUTHOR: Arturo Blandon
# FILENAME: knn.py
# SPECIFICATION: Reads binary points and outputs LOO-CV Error Rate for 1NN.
# FOR: CS 4200- Assignment #2
# TIME SPENT: 10 Minutes
#-----------------------------------------------------------*/

#IMPORTANT NOTE: DO NOT USE ANY ADVANCED PYTHON LIBRARY TO COMPLETE THIS CODE SUCH AS numpy OR pandas. You have to work here only with standard vectors and arrays

#importing some Python libraries
from sklearn.neighbors import KNeighborsClassifier
import csv

db = []

#reading the data in a csv file
with open('binary_points.csv', 'r') as csvfile:
  reader = csv.reader(csvfile)
  for i, row in enumerate(reader):
      if i > 0: #skipping the header
         db.append (row)

errors = 0

#loop your data to allow each instance to be your test set
for i, instance in enumerate(db):

    #add the training features to the 2D array X removing the instance that will be used for testing in this iteration. For instance, X = [[1, 3], [2, 1,], ...]]
    #--> add your Python code here
    # X =
    X = []
    Y = []

    for j in range(len(db)):
        if j == i:
            continue
        #X.append( [ ord(db[j][0]),ord(db[j][1]) ] )
        X.append( [ int(db[j][0]),int(db[j][1]) ] )

    #transform the original training classes to numbers and add to the vector Y removing the instance that will be used for testing in this iteration. For instance, Y = [1, 2, ,...]
    #--> add your Python code here
    # Y =
    for j in range(len(db)):
        if j == i:
            continue
        Y.append(ord(db[j][2]))

    #store the test sample of this iteration in the vector testSample
    #--> add your Python code here
    #testSample =
    testSample = instance
    testSample = [int(testSample[0]), int(testSample[1])]

    #fitting the knn to the data

    clf = KNeighborsClassifier(n_neighbors=1, p=2)
    clf = clf.fit(X, Y)

    #use your test sample in this iteration to make the class prediction. For instance:
    #class_predicted = clf.predict([[1, 2]])[0]
    #--> add your Python code here

    class_predicted = clf.predict([testSample])[0]

    #compare the prediction with the true label of the test instance to start calculating the error rate.
    #--> add your Python code here

    if(instance[2] != chr(class_predicted)):
        errors += 1

#print the error rate
#--> add your Python code here
output = "LOO-CV Error rate for 1NN = {}"
print(output.format(errors/10))


"""
Given a chess board of n rows ( top to bottom ) and n columns ( left to right ).
In each move, a knight moves either:
2 column positions and 1 row position 2 row positions an 1 column position

In other words, a move is 2 steps along one axis and 1 step along a perpendicular axis.
A bishop, on the other hand, moves any number of steps diagonaly.

Both the knight and bishop capture any piece that is on a position that it moves to.
A position that a piece can move to is said to be threatened by that piece,
because if another piece moves to that position , then it can be captured.

Given a starting position A and ending position B for the knight, and a bishop position C, calculate the minimum
number of moves needed by the knight to move from A to B while avoiding all positions threatened by the bishop.
If the knight captures the bishop on one of its moves, then it can move into positions
that were previously threatened by the bishop.

If there is no possible path to B, return -1. All moves must remain within the chess board.

Note: The knight may move to B even if B is threatened by the bishop.
"""

from typing import List

# bidirection BFS solution

def solution(n: int, A: List, B: List, C: List) -> int:
    from collections import deque

    def is_danger(x: int, y: int):
        return [x, y] != B and abs(x - C[0]) == abs(y - C[1])

    # the offsets in the eight directions
    offsets = [(1, 2), (2, 1), (2, -1), (1, -2),
               (-1, -2), (-2, -1), (-2, 1), (-1, 2)]

    odq = deque([(A[0], A[1], 0, True)])  # origin deque
    omp = {(A[0], A[1]): 0}  # origin map

    tdq = deque([(B[0], B[1], 0, True)])  # target deque
    tmp = {(B[0], B[1]): 0}  # target map

    while odq and tdq:
        ox, oy, osteps, oflag = odq.popleft()
        if (ox, oy) in tmp:
            return osteps + tmp[(ox, oy)]

        tx, ty, tsteps, tflag = tdq.popleft()
        if (tx, ty) in omp:
            return tsteps + omp[(tx, ty)]

        for dx, dy in offsets:
            new_ox, new_oy = ox + dx, oy + dy
            if oflag and [new_ox, new_oy] == C:
                oflag = False
            if oflag:
                if 0 <= new_ox < n and 0 <= new_oy < n and (not is_danger(new_ox, new_oy)) and (new_ox, new_oy) not in omp:
                    omp[(new_ox, new_oy)] = osteps + 1
                    odq.append((new_ox, new_oy, osteps + 1, True))
            else:
                if 0 <= new_ox < n and 0 <= new_oy < n and (new_ox, new_oy) not in omp:
                    omp[(new_ox, new_oy)] = osteps + 1
                    odq.append((new_ox, new_oy, osteps + 1, False))

            new_tx, new_ty = tx + dx, ty + dy
            if tflag and [new_tx, new_ty] == C:
                tflag = False
            if tflag:
                if 0 <= new_tx < n and 0 <= new_ty < n and (not is_danger(new_tx, new_ty)) and (new_tx, new_ty) not in tmp:
                    tmp[(new_tx, new_ty)] = tsteps + 1
                    tdq.append((new_tx, new_ty, tsteps + 1, True))
            else:
                if 0 <= new_tx < n and 0 <= new_ty < n and (new_tx, new_ty) not in tmp:
                    tmp[(new_tx, new_ty)] = tsteps + 1
                    tdq.append((new_tx, new_ty, tsteps + 1, False))

    return -1

print(solution(4, [0, 0], [1, 2], [-1, 2]))


# Начните работу над проектом «Склад оргтехники». Создайте класс,
# описывающий склад. А также класс «Оргтехника», который будет базовым
# для классов-наследников. Эти классы — конкретные типы оргтехники
# (принтер, сканер, ксерокс). В базовом классе определить параметры,
# общие для приведенных типов. В классах-наследниках реализовать
# параметры, уникальные для каждого типа оргтехники.
# Продолжить работу над первым заданием. Разработать методы, отвечающие
# за приём оргтехники на склад и передачу в определенное подразделение
# компании. Для хранения данных о наименовании и количестве единиц
# оргтехники, а также других данных, можно использовать любую подходящую
# структуру, например словарь.
# Продолжить работу над вторым заданием. Реализуйте механизм валидации
# вводимых пользователем данных. Например, для указания количества
# принтеров, отправленных на склад, нельзя использовать строковый тип
# данных.
# Подсказка: постарайтесь по возможности реализовать в проекте «Склад
# оргтехники» максимум возможностей, изученных на уроках по ООП.

class WarehouseExceededLimitError(Exception):
    pass

class OutOfWarehouseError(Exception):
    pass

class NotAWarehouseUnitError(Exception):
    pass

class NotOfficeEquipmentError(Exception):
    pass

class NotWarehouseError(Exception):
    pass

class Warehouse:
    def __init__(self, name, value=None):
        self.name = name
        self.value = None
        self.units = list()
        self.create(value)

    def create(self, value):
        if self.is_params_valid(value):
            self.value = value
        else:
            raise NotWarehouseError(
                'Can\'t create NotWarehouseError object. Parameters must be a num type.'
            )

    @staticmethod
    def is_params_valid(*params):
        return all([isinstance(param, (float, int)) for param in params])

    def take_to(self, unit):
        if self.is_unit_valid(unit):
            unit_value = unit.get_value()
            if self.value > unit_value:
                self.units.append(unit)
                self.value -= unit.get_value()
            else:
                raise WarehouseExceededLimitError(
                    f'Can\'t add {unit} to warehouse. '
                    f'Warehouse "{self.name}" capacity exceeded the limit.'
                )

    def take_off(self, unit):
        try:
            unit_idx = self.units.index(unit)
        except ValueError as error:
            raise OutOfWarehouseError(
                'Can\'t remove unit from warehouse. Unit not exists.'
            )
        else:
            self.units.pop(unit_idx)
            self.value += unit.get_value()

    @staticmethod
    def is_unit_valid(unit):
        try:
            unit.get_value()
            return True
        except AttributeError:
            raise NotAWarehouseUnitError(
                'Can\'t take to warehouse. Unit not a warehouse unit.'
            )

    def __repr__(self):
        return f'{self.name}'

class OfficeEquipment:
    def __init__(self, name, weight, length, width, height):
        self.name = name
        self.weight = None
        self.length = None
        self.width = None
        self.height = None
        self.create(weight, length, width, height)

    def create(self, weight, length, width, height):
        if self.is_params_valid(weight, length, width, height):
            self.weight = weight
            self.length = length
            self.width = width
            self.height = height
        else:
            raise NotOfficeEquipmentError(
                'Can\'t create OfficeEquipment object. Parameters must be a num type.'
            )

    @staticmethod
    def is_params_valid(*params):
        return all([isinstance(param, (float, int)) for param in params])

    def __repr__(self):
        return f'{self.name}'

    def __add__(self, other):
        if self.is_object_valid(other):
            return self.get_value() + other.get_value()

    @staticmethod
    def is_object_valid(obj):
        if isinstance(obj, OfficeEquipment):
            return True
        raise ValueError(
            f'Object must be a OfficeEquipment instance, got {type(obj)}.'
        )

    def get_value(self):
        return self.length * self.width * self.height

class Printer(OfficeEquipment):
    def __init__(self, name, weight, length, width, height, is_color=False):
        super().__init__(name, weight, length, width, height)
        self.is_color = is_color

class Scanner(OfficeEquipment):
    def __init__(self, name, weight, length, width, height, form='A3'):
        super().__init__(name, weight, length, width, height)
        self.form = form

class Xerox(OfficeEquipment):
    def __init__(self, name, weight, length, width, height, speed):
        super().__init__(name, weight, length, width, height)
        self.speed = speed

if __name__ == '__main__':
    WAREHOUSE_1 = Warehouse('warehouse #1', 20)
    WAREHOUSE_2 = Warehouse('warehouse #2', 13)

    print(f'{WAREHOUSE_1} created. Value: {WAREHOUSE_1.value}')
    print(f'{WAREHOUSE_2} created. Value: {WAREHOUSE_2.value}')

    PRINTER_PARAMS = (5, 0.5, 0.5, 0.3, True)
    SCANNER_PARAMS = (5, 0.5, 0.3, 0.1, 'A4')
    XEROX_PARAMS = (5, 0.5, 0.4, 0.2, 60)

    PRINTERS = [Printer(f'printer #{i}', *PRINTER_PARAMS) for i in range(100)]
    SCANNERS = [Scanner(f'scanner #{i}', *SCANNER_PARAMS) for i in range(70)]
    XEROXES = [Xerox(f'xerox #{i}', *XEROX_PARAMS) for i in range(120)]

    UNITS = (PRINTERS, SCANNERS, XEROXES)

    total_units_value = sum([unit.get_value() for unit_list in UNITS for unit in unit_list])
    print(f'Units created. Total units value: {total_units_value}')

    for unit_list in UNITS:
        for unit in unit_list:
            try:
                WAREHOUSE_1.take_to(unit)
            except WarehouseExceededLimitError as error:
                print(error)

    print(f'Units added to {WAREHOUSE_1}')
    print(f'{WAREHOUSE_1} free value: {WAREHOUSE_1.value}')

    for unit_list in UNITS:
        for unit in unit_list:
            try:
                WAREHOUSE_2.take_to(unit)
            except WarehouseExceededLimitError as error:
                print(error)

    print(f'Units added to {WAREHOUSE_2}')
    print(f'{WAREHOUSE_2} free value: {WAREHOUSE_2.value}')

    for unit in PRINTERS:
        WAREHOUSE_1.take_off(unit)

    print(f'Units removed from {WAREHOUSE_1}')
    print(f'{WAREHOUSE_1} value: {WAREHOUSE_1.value}')


num=int(input("Enter a Number : "))
temp=num
rev=0
while num>0:
    dig=num%10
    rev=rev*10+dig
    num//=10
if temp==rev:
    print(temp,"is a Pallindrome Number")
else:
    print(temp,"is not a Pallindrome Number")


import discord
from keep_alive import keep_alive
from discord import Message as DiscordMessage
import logging
import asyncio
import json
import os
from uuid import uuid4
from time import time
from datetime import datetime
from base import Message, Conversation
from constants import (
    BOT_INVITE_URL,
    DISCORD_BOT_TOKEN,
    EXAMPLE_CONVOS,
    MAX_MESSAGE_HISTORY,
    SECONDS_DELAY_RECEIVING_MSG,
    ACTIVATE_THREAD_PREFX,
)
keep_alive()
from utils import (
    logger,
    should_block,
    is_last_message_stale,
    discord_message_to_message,
)
import completion
from completion import generate_completion_response, process_response
from memory import (
    gpt3_embedding,
    gpt3_response_embedding,
    save_json,
    load_convo,
    add_notes,
    notes_history,
    fetch_memories,
    summarize_memories,
    load_memory,
    load_context,
    open_file,
    gpt3_completion,
    timestamp_to_datetime
)

logging.basicConfig(
    format="[%(asctime)s] [%(filename)s:%(lineno)d] %(message)s", level=logging.INFO
)

intents = discord.Intents.default()
intents.message_content = True

client = discord.Client(intents=intents)
tree = discord.app_commands.CommandTree(client)

@client.event
async def on_ready():
    logger.info(f"We have logged in as {client.user}. Invite URL: {BOT_INVITE_URL}")
    completion.MY_BOT_NAME = client.user.name
    completion.MY_BOT_EXAMPLE_CONVOS = []
    for c in EXAMPLE_CONVOS:
        messages = []
        for m in c.messages:
            if m.user == "Joyy":
                messages.append(Message(user=client.user.name, text=m.text))
            else:
                messages.append(m)
        completion.MY_BOT_EXAMPLE_CONVOS.append(Conversation(messages=messages))
    await tree.sync()

@tree.command(name="chat", description="Talk to the bot")
@discord.app_commands.checks.has_permissions(send_messages=True)
@discord.app_commands.checks.has_permissions(view_channel=True)
@discord.app_commands.checks.has_permissions(manage_threads=True)
@discord.app_commands.checks.bot_has_permissions(send_messages=True)
@discord.app_commands.checks.bot_has_permissions(view_channel=True)
@discord.app_commands.checks.bot_has_permissions(manage_threads=True)
async def chat_command(int: discord.Interaction, message: str):
    try:
        # only support creating thread in text channel
        if not isinstance(int.channel, discord.TextChannel):
            return

        # block servers not in allow list
        if should_block(guild=int.guild):
            return

        user = int.user
        logger.info(f"Chat command by {user} {message[:20]}")
        try:
            embed = discord.Embed(
                description=f"<@{user.id}> wants to chat! ðŸ¥°ðŸ’¬",
                color=discord.Color.magenta(),
            )

            embed.add_field(name=user.name, value=message)
            await int.response.send_message(embed=embed)
            response = await int.original_response()

        except Exception as e:
            logger.exception(e)
            await int.response.send_message(
                f"Failed to start chat {str(e)}", ephemeral=True
            )
            return

        # bot creates the thread
        thread = await response.create_thread(
            name=f"{ACTIVATE_THREAD_PREFX} {user.name[:20]}",
            slowmode_delay=0,
            reason="gpt-bot",
            auto_archive_duration=60,
        )

        async with thread.typing():
            # fetch completion
            messages = [Message(user=user.name, text=message)]
            response_data = await generate_completion_response(
                messages=messages, user=user
            )
            # send the result
            await process_response(
                user=user, thread=thread, response_data=response_data
            )

        # wait 24 hours and delete the thread
        await asyncio.sleep(86400)
        await thread.delete()

    except Exception as e:
        logger.exception(e)
        await int.response.send_message(
            f"Failed to start chat {str(e)}", ephemeral=True
        )

# calls for each message
@client.event
async def on_message(message: DiscordMessage):
    channel = message.channel
    try:
        # block servers not in allow list
        if should_block(guild=message.guild):
            return

        # ignore messages from the bot
        if message.author == client.user:
            return

        #ignore message not in the thread
        channel = message.channel
        if not isinstance(channel, discord.Thread):
            return

        #ignore any thread that is not created by the bot
        thread = channel
        if thread.owner_id != client.user.id:
            return

        # save message as embedding, vectorize
        vector = gpt3_embedding(message)
        timestamp = time()
        timestring = timestring = timestamp_to_datetime(timestamp)
        user = message.author.name
        extracted_message = '%s: %s - %s' % (user, timestring, message.content)
        info = {'speaker': user, 'timestamp': timestamp,'uuid': str(uuid4()), 'vector': vector, 'message': extracted_message, 'timestring': timestring}
        filename = 'log_%s_user' % timestamp
        save_json(f'./memory_storage/chat_logs/{filename}.json', info)

        # load past conversations
        history = load_convo()

        # fetch memories (histroy + current input)

        memories = fetch_memories(vector, history, 5)

        # create notes from memories

        current_notes, vector = summarize_memories(memories)

        print(current_notes)
        print('-------------------------------------------------------------------------------')

        add_notes(current_notes)

        if len(notes_history) >= 2:
            print(notes_history[-2])
        else:
            print("The list does not have enough elements to access the second-to-last element.")

        # create a Message object from the notes
        message_notes = Message(user='memories', text=current_notes)

        # create a Message object from the notes_history for context

        context_notes = None

        if len(notes_history) >= 2:
            context_notes = Message(user='context', text=notes_history[-2])
        else:
            print("The list does not have enough elements create context")

        # wait a bit in case user has more messages
        if SECONDS_DELAY_RECEIVING_MSG > 0:
            await asyncio.sleep(SECONDS_DELAY_RECEIVING_MSG)
            if is_last_message_stale(
                interaction_message=message,
                last_message=channel.last_message,
                bot_id=client.user.id,
            ):
                # there is another message, so ignore this one
                return

        logger.info(
            f"Thread message to process - {message.author}: {message.content[:50]} - {thread.name} {thread.jump_url}"
        )

        channel_messages = [
            discord_message_to_message(message)
            async for message in channel.history(limit=MAX_MESSAGE_HISTORY)
        ]
        channel_messages = [x for x in channel_messages if x is not None]
        channel_messages.reverse()
        channel_messages.insert(0, message_notes)
        if context_notes:
            channel_messages.insert(0, context_notes)

        # generate the response
        async with channel.typing():
            response_data = await generate_completion_response(
                messages=channel_messages, user=message.author
            )
            vector = gpt3_response_embedding(response_data)
            timestamp = time()
            timestring = timestring = timestamp_to_datetime(timestamp)
            user = client.user.name
            extracted_message = '%s: %s - %s' % (user, timestring, response_data.reply_text)
            info = {'speaker': user, 'timestamp': timestamp,'uuid': str(uuid4()), 'vector': vector, 'message': extracted_message, 'timestring': timestring}
            filename = 'log_%s_bot' % timestamp
            save_json(f'./memory_storage/chat_logs/{filename}.json', info)

        if is_last_message_stale(
            interaction_message=message,
            last_message=thread.last_message,
            bot_id=client.user.id,
        ):
            # there is another message and its not from us, so ignore this response
            return

        # send response
        await process_response(
            user=message.author, thread=thread, response_data=response_data
        )
    except Exception as e:
        logger.exception(e)

client.run(DISCORD_BOT_TOKEN)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

""" 解析gc日志 """
from entity.gc.GcInfoMessage import GcInfoMessage
from utils.analyzeFileUtils import analyzeFileUtils

__author__ = 'bokai'

import os
import csv

# 给一个gclog解压后的路径，解析里面的gcRecord文件
# zippath/result/gclog/gcRecord类文件
# 解析后合并的文件名
# zippath/result/treas201901.gc.log

def generate_gc_log_and_get_node_pid_gc_info_list_detail(gc_log_path, gc_log_fullname):
    if not os.path.exists(gc_log_path):
        print('这个版本的treas包没有gcRecord表. fu*k u')
        return
    if os.path.exists(gc_log_fullname):
        print(gc_log_fullname, ' - result gc log file already exist.')
        return
    result_gc_log_file = open(gc_log_fullname, 'w')
    result_gc_log_file_header = ['log', 'gcStartTime', 'node']
    result_gc_log_file_writer = csv.DictWriter(result_gc_log_file, result_gc_log_file_header)
    result_gc_log_file_writer.writeheader()
    #  {'node1':{'pid1':['gc_list']}}
    gc_info_message_list = []
    for parent, dir_name, file_names in os.walk(gc_log_path):
        for filename in file_names:
            if filename.startswith('gcRecord') and filename.endswith('.csv'):
                # 打开每个文件
                gc_csv_file = open(parent + os.sep + filename, 'r', encoding='UTF-8')
                gc_file_reader = csv.DictReader((line.replace('\0', '') for line in gc_csv_file))
                try:
                    for row in gc_file_reader:
                        month_gc_info_message = GcInfoMessage(row)
                        gc_info_message_list.append(month_gc_info_message)
                        gc_row = month_gc_info_message.to_print_gc_log()
                        result_gc_log_file_writer.writerow(gc_row)
                except Exception as e:
                    print("gcRecord row read failed.", filename, 'line:', gc_file_reader.line_num, e)

                gc_csv_file.close()

    result_gc_log_file.close()
    # 先按照节点排序，分隔开之后，在按照时间排序
    analyzeFileUtils.sort_file_message(gc_log_fullname, ['node', 'gcStartTime'])
    gc_info_message_list.sort(key=GcInfoMessage.get_timestamps)
    gc_info_message_node_pid_detail = {}
    for gc_info_message in gc_info_message_list:
        node = gc_info_message.get_node()
        pid = gc_info_message.get_pid()
        if node in gc_info_message_node_pid_detail:
            if pid in gc_info_message_node_pid_detail[node]:
                gc_info_message_node_pid_detail[node][pid].append(gc_info_message)
            else:
                gc_info_message_node_pid_detail[node][pid] = []
                gc_info_message_node_pid_detail[node][pid].append(gc_info_message)
        else:
            gc_info_message_node_pid_detail[node] = {}
            gc_info_message_node_pid_detail[node][pid] = []
            gc_info_message_node_pid_detail[node][pid].append(gc_info_message)
    return gc_info_message_node_pid_detail