import numpy as np
import qtawesome as qta
from qtpy import QtCore, QtWidgets

import clickpoints
from clickpoints.includes.QtShortCuts import AddQSpinBox, AddQOpenFileChoose
from clickpoints.includes import QtShortCuts

from inspect import getdoc
from PenguTrack import Detectors
import traceback
import os
import sys
from importlib import import_module, reload

def loadModule(path, module=None):
    folder, filename = os.path.split(path)
    path, folder = os.path.split(folder)
    basefilename, ext = os.path.splitext(filename)

    sys.path.insert(0, path)

    try:
        if module is None:
            try:
                print("import", folder + "." + basefilename)
                addon_module = import_module(folder + "." + basefilename)
            except Exception as err:
                raise err
            loaded = True
        else:
            addon_module = reload(module)
    finally:
        sys.path.pop(0)
    return addon_module

def getClassDefinitions(module, baseclass):
    class_definitions = []
    for name in dir(module):
        current_class_definition = getattr(module, name)
        try:
            if issubclass(current_class_definition, baseclass) and current_class_definition != baseclass:
                class_definitions.append(current_class_definition)
        except TypeError:
            pass
    return class_definitions

def getEvalValuesGT(groundtruth_pos, pos_program, distance_cost_parameter):
    from scipy.optimize import linear_sum_assignment
    from scipy.spatial import distance

    # if no detections are found
    if len(pos_program) == 0:
        tp_ind = np.array([], dtype="uint64")
        fp_ind = np.array([], dtype="uint64")
        fn_ind = np.arange(len(groundtruth_pos), dtype="uint64")
    elif len(groundtruth_pos) == 0:
        tp_ind = np.array([], dtype="uint64")
        fp_ind = np.arange(len(pos_program), dtype="uint64")
        fn_ind = np.array([], dtype="uint64")
    else:
        # calculate all distances
        cost = distance.cdist(pos_program, groundtruth_pos, 'sqeuclidean')

        # solve the assignment to optimize the pairing
        # row_ind, col_ind = linear_sum_assignment(cost)
        from PenguTrack.Assignment import network_assignment
        row_ind, col_ind = network_assignment(cost, threshold=distance_cost_parameter**2)

        # get the distances for the matches
        distance_cost = cost[row_ind, col_ind]

        # filter by distance
        row_ind = row_ind[np.array(distance_cost) < distance_cost_parameter**2]
        col_ind = col_ind[np.array(distance_cost) < distance_cost_parameter**2]

        # true positives = objects that have both been clicked by the user and were found by the detector
        tp_ind = row_ind.astype("uint64")
        # false positive = objects, which were found by the detector, but were not clicked by the user
        fp_ind = np.array(list(set(np.arange(0, len(pos_program))) - set(tp_ind)), dtype="uint64")
        # false negatives = objects, which were clicked by the user, but were not found by the detector
        fn_ind = np.array(list(set(np.arange(0, len(groundtruth_pos))) - set(col_ind)), dtype="uint64")

    # count
    fp = len(fp_ind)
    fn = len(fn_ind)
    tp = len(tp_ind)

    # calculate the values
    try:
        FAR = fp / (tp + fp)  # False Alarm Rate - Anzahl, wie oft ein Objekt falsch vom Detektor erkannt wird
    except ZeroDivisionError:
        FAR = 0
    try:
        DR = tp / (tp + fn)  # Detection Rate - Anzahl, wie viele Objektive richtig detektiert wurden (Prozent)
    except ZeroDivisionError:
        DR = 0
    try:
        PR = tp / (tp + fp)  # precision - Anzahl, wie viele Objekte von Detektor gefunden wurde, aber nicht geklickt
    except ZeroDivisionError:
        PR = 0
    eval_values = {}
    eval_values.update(False_Alarm_Rate=np.round(FAR, decimals=2), Detection_Rate=np.round(DR, decimals=2),
                       Precision=np.round(PR, decimals=2), fp=fp, fn=fn, tp=tp)

    return eval_values, tp_ind, fp_ind, fn_ind

class Addon(clickpoints.Addon):
    auto_apply = False
    scheduled_run = True

    detector = None
    parameter_widget = None

    display_ranges = False

    def __init__(self, *args, **kwargs):
        clickpoints.Addon.__init__(self, *args, **kwargs)

        self.layout = QtWidgets.QVBoxLayout(self)

        # Check if the marker type is present
        self.marker_type_truth = self.db.setMarkerType("ground truth", "#0a2eff", self.db.TYPE_Normal)
        self.marker_type_truth_region = self.db.setMarkerType("ground truth region", "#035bff", self.db.TYPE_Rect)
        self.marker_type_true_positive = self.db.setMarkerType("true positive", "#19ff66", self.db.TYPE_Normal)
        self.marker_type_false_positive = self.db.setMarkerType("false positive", "#ff1150", self.db.TYPE_Normal)
        self.marker_type_false_negative = self.db.setMarkerType("false negative", "#ff730f", self.db.TYPE_Normal, style='{"shape":"ring", "scale":2}')
        self.cp.reloadTypes()

        self.input_groundtruth_marktertype = QtShortCuts.QInputChoice(self.layout, "Groundtruth Markertype", value="ground truth", value_names=[n.name for n in self.db.getMarkerTypes()], values=[n for n in self.db.getMarkerTypes()])

        self.distance_cost_parameter = QtShortCuts.QInputNumber(self.layout, "Max Distance to Groundtruth", 10, False)

        self.detector_file = QtShortCuts.QInputFilename(self.layout, "Detector File:", "", file_type="Python File (*.py)", existing=True)
        self.detector_file.valueChanged.connect(self.detectorFileSelected)
        self.detector_file_button_reload = QtWidgets.QPushButton()
        self.detector_file_button_reload.setIcon(qta.icon("fa.repeat"))
        self.detector_file_button_reload.clicked.connect(self.detectorFileSelected)
        self.detector_file.layout().addWidget(self.detector_file_button_reload)

        self.detector_classes = getClassDefinitions(Detectors, Detectors.Detector)#[DetectorThreshold, DetectorRandom]

        self.comboBox = QtWidgets.QComboBox()
        self.layout.addWidget(self.comboBox)

        self.detectorModuleChanged(Detectors)

        self.comboBox.currentTextChanged.connect(self.selectDetector)

        self.label_description = QtWidgets.QLabel()
        self.layout.addWidget(self.label_description)

        self.layout_parameters = QtWidgets.QVBoxLayout()
        self.layout.addLayout(self.layout_parameters)
        self.layout.addStretch()

        self.layout_buttons = QtWidgets.QHBoxLayout()
        self.layout.addLayout(self.layout_buttons)

        self.optimization_count = QtShortCuts.QInputNumber(self.layout, "Optimizer iterations", 100, float=False)
        self.optimization_count.setHidden(True)

        self.button = QtWidgets.QPushButton("apply")
        self.layout_buttons.addWidget(self.button)
        self.button.clicked.connect(self.start_detect_and_show)

        self.button2 = QtWidgets.QPushButton("auto apply")
        self.button2.setCheckable(True)
        self.layout_buttons.addWidget(self.button2)
        self.button2.clicked.connect(self.autoApply)

        self.button4 = QtWidgets.QPushButton("run optimisation")
        self.button4.setHidden(True)
        self.layout_buttons.addWidget(self.button4)
        self.button4.clicked.connect(self.start_optimize)

        self.button3 = QtWidgets.QPushButton("optimize")
        self.button3.setCheckable(True)
        self.layout_buttons.addWidget(self.button3)
        self.button3.clicked.connect(self.display_optimize)

        self.progressbar = QtWidgets.QProgressBar()
        self.layout.addWidget(self.progressbar)

    def detectorModuleChanged(self, module):
        for i in range(len(self.detector_classes)+1):
            self.comboBox.removeItem(0)
        self.detector_classes = getClassDefinitions(module, Detectors.Detector)
        self.comboBox.addItem("-- select detector --")
        for value in self.detector_classes:
            self.comboBox.addItem(value.__name__)

    def detectorFileSelected(self):
        filename = self.detector_file.value()
        module = loadModule(filename)
        module = loadModule(filename, module)
        self.detectorModuleChanged(module)

    def selectDetector(self):
        print("-->", self.detector_classes[self.comboBox.currentIndex() - 1])
        if self.comboBox.currentIndex() == 0:
            self.detector = None
            self.label_description.setText("No detector selected.")
        else:
            try:
                self.detector = self.detector_classes[self.comboBox.currentIndex() - 1]()
                self.detector.ParameterList.valueChangedEvent = self.valueChanged
            except Exception:
                traceback.print_exc()
                self.detector = None
                self.label_description.setText("Detector cannot be loaded.")

        if self.parameter_widget:
            self.parameter_widget.setParent(None)
            self.layout_parameters.removeWidget(self.parameter_widget)
            self.parameter_widget = None

        if self.detector is not None:
            self.label_description.setText(getdoc(self.detector))
            self.parameter_widget = self.detector.ParameterList.addWidgets(self.layout_parameters)
            self.detector.ParameterList.displayRanges(self.display_ranges)

    def autoApply(self):
        # toggle the auto-apply status
        self.auto_apply = not self.auto_apply
        # and (de)activate the apply button
        self.button.setDisabled(self.auto_apply)

    def valueChanged(self):
        # if the parameters were changed and we have auto-apply enabled, start a detection
        if self.auto_apply:
            # if the thread is not running, start to detect
            if not self.is_running():
                self.start_detect_and_show()
            # if not, schedule it
            else:
                self.scheduled_run = True

    def getGroundTruthPositions(self, frame):
        self.marker_type_truth = self.input_groundtruth_marktertype.value()
        return np.array([x for x in self.db.getMarkers(frame=frame, type=self.marker_type_truth).select(self.db.table_marker.x, self.db.table_marker.y).tuples().execute()])

    def checkGroundTruth(self, detections, frame):
        # get the ground-truth data
        groundtruth_pos = self.getGroundTruthPositions(frame)
        # compare it to the detections
        eval_values, tp_ind, fp_ind, fn_ind = getEvalValuesGT(groundtruth_pos, detections, self.distance_cost_parameter.value())
        # print the evaluation
        print(eval_values)
        return eval_values, detections[tp_ind], detections[fp_ind], groundtruth_pos[fn_ind]

    def buttonPressedEvent(self):
        self.show()

    def run_stopped(self):
        super(Addon, self).run_stopped()
        print("run_stopped", self.scheduled_run)
        if self.scheduled_run:
            # if we have scheduled to run a detection, start it with a single shot timer
            self.timer = QtCore.QTimer()
            self.timer.setSingleShot(0)
            self.timer.timeout.connect(self.start_detect_and_show)
            self.timer.start()
        else:
            if self.detector is not None:
                self.detector.ParameterList.updateWidgets()

    def start_detect_and_show(self):
        print("start_detect_and_show")
        self.run_threaded(function=self.detect_and_show)

    def filter_inside(self, positions, rectangles):
        if rectangles.count() == 0:
            return np.ones(len(positions), dtype="bool")
        valid = np.zeros(len(positions), dtype="bool")
        for rect in rectangles:
            valid += (rect.x <= positions[:, 0]) * (rect.x+rect.width >= positions[:, 0]) * \
                     (rect.y <= positions[:, 1]) * (rect.y+rect.height >= positions[:, 1])
        return valid

    def prepareDetectionParameters(self, current_frame, current_layer, detect):
        arguments = {}
        for parameter in detect.detection_parameters:
            name = parameter
            parameter = detect.detection_parameters[name]
            if "layer" in parameter:
                layer = parameter["layer"]
            else:
                layer = current_layer
            if "frame" in parameter:
                frame = parameter["frame"]
            else:
                frame = 0
            image = self.db.getImage(frame=(current_frame+frame), layer=layer)
            if image is None:
                arguments[name] = None
                continue
            if "mask" in parameter and parameter["mask"]:
                if image.mask is not None:
                    arguments[name] = image.mask.data
                else:
                    arguments[name] = None
            else:
                arguments[name] = image.data
        return arguments

    def detect_and_show(self, start_frame=0):
        self.progressbar.setRange(0, 0)
        # clear the scheduled run
        self.scheduled_run = False

        # get the current frame
        frame = self.cp.getCurrentFrame()
        layer = self.cp.window.layer

        # remove all previous markers
        self.db.deleteMarkers(frame=frame, type=self.marker_type_true_positive)
        self.db.deleteMarkers(frame=frame, type=self.marker_type_false_positive)
        self.db.deleteMarkers(frame=frame, type=self.marker_type_false_negative)

        # detect the current image with the detector
        arguments = self.prepareDetectionParameters(frame, layer, self.detector.detect)
        try:
            positions, mask = self.detector.detect(**arguments)
            positions = np.array(positions[["PositionX", "PositionY"]])
        except Exception:
            traceback.print_exc()
            positions = []
            mask = None

        # if the detector has returned positions, display them
        if len(positions):
            rectangles = self.db.getRectangles(frame=frame, type=self.marker_type_truth_region)
            inside = self.filter_inside(positions, rectangles)
            positions = positions[inside]
            eval_values, tp, fp, fn = self.checkGroundTruth(positions, frame)
            if len(tp):
                self.db.setMarkers(frame=frame, x=tp[:, 0], y=tp[:, 1], type=self.marker_type_true_positive)
            if len(fp):
                self.db.setMarkers(frame=frame, x=fp[:, 0], y=fp[:, 1], type=self.marker_type_false_positive)
            if len(fn):
                self.db.setMarkers(frame=frame, x=fn[:, 0], y=fn[:, 1], type=self.marker_type_false_negative)
            self.cp.reloadMarker()

        # if the detector has returned a mask, draw the mask
        if mask is not None:
            self.db.setMask(frame=frame, data=mask.astype("uint8"))
            self.cp.reloadMask()

        self.progressbar.setRange(0, 1)

    def start_optimize(self):
        self.run_threaded(function=self.do_optimize)

    def display_optimize(self):
        # toggle the auto-apply status
        self.display_ranges = not self.display_ranges
        # and (de)activate the apply button
        self.button.setHidden(self.display_ranges)
        self.button2.setHidden(self.display_ranges)
        self.button4.setHidden(not self.display_ranges)
        self.optimization_count.setHidden(not self.display_ranges)
        self.detector.ParameterList.displayRanges(self.display_ranges)

    def do_optimize(self, start=0):
        import skopt
        # get the current frame
        frame = self.cp.getCurrentFrame()
        layer = self.cp.window.layer
        arguments = self.prepareDetectionParameters(frame, layer, self.detector.detect)

        # get the ground-truth data
        groundtruth_pos = self.getGroundTruthPositions(frame)

        rectangles = self.db.getRectangles(frame=frame, type=self.marker_type_truth_region)

        self.iteration = 0
        self.max_iterations = self.optimization_count.value()
        #self.progressbar.setRange(0, self.max_iterations)

        def error(p):
            self.detector.ParameterList.setOptimisationValues(p)
            detections, mask = self.detector.detect(**arguments)
            detections = np.array(detections[["PositionX", "PositionY"]])

            if len(detections):
                inside = self.filter_inside(detections, rectangles)
                detections = detections[inside]

            # compare it to the detections
            eval_values, tp_ind, fp_ind, fn_ind = getEvalValuesGT(groundtruth_pos, detections,
                                                                  self.distance_cost_parameter.value())

            print("Iteration:", self.iteration, "Parameters:", p, "Precision", eval_values["Precision"])
            self.iteration += 1
            #self.progressbar.setValue(self.iteration)
            print("progress set")
            return 1-eval_values["Precision"]

        print("ranges", self.detector.ParameterList.getRanges())
        res = skopt.gp_minimize(error, self.detector.ParameterList.getRanges(), verbose=False, n_calls=self.max_iterations)
        print("update values")
        if self.auto_apply:
            self.auto_apply = False
        self.detector.ParameterList.setOptimisationValues(res.x, update_widgets=True)
        print("Result:", "Parameters:", res.x, "Precision", 1-res.fun)
        self.detect_and_show()
        #print(res)


from namespaces_handler import *
from rdflib.namespace import RDF, RDFS, XSD
from rdflib import Literal, Graph

__author__ = 'miguel'

class RDFService(object):

    def __init__(self, area_repository, indicator_repository):
        self._area_repository = area_repository
        self._indicator_repository = indicator_repository

    def generate_dataset(self, observations):
        graph = Graph()
        for observation in observations:
            self._add_observation_triples(observation, graph)
        serialized = graph.serialize(format='application/rdf+xml')
        with open("dataset.rdf", 'w') as dataset:
            dataset.write(serialized)
        return graph  # We will see if this is the thing to return or not

    def _add_observation_triples(self, observation, graph):
        #### Initializing observation term
        observation_term = cex.term(self._build_observation_id(observation))

        #### Adding Literal and type triples
        graph.add((observation_term,
                  RDF.type,
                  cex.term("Observation")))

        graph.add((observation_term,
                  RDFS.label,
                  Literal(self._build_observation_label(observation), lang="en")))

        graph.add((observation_term,
                  base_obs_uri.term("uri_api"),
                  Literal(observation['uri'])))

        graph.add((observation_term,
                  cex.term("Raw"),
                  Literal(observation['value'], datatype=XSD.double)))

        if observation['normalized'] is not None:
            graph.add((observation_term,
                      cex.term("Normalized"),
                      Literal(observation['normalized'], datatype=XSD.double)))

        if observation['scored'] is not None:
            graph.add((observation_term,
                      cex.term("RankingScore"),
                      Literal(observation['scored'], datatype=XSD.double)))

        if observation['ranked'] is not None:
            graph.add((observation_term,
                      cex.term("Ranked"),
                      Literal(observation['ranked'], datatype=XSD.integer)))

        #### Linking observation with other subjects

            graph.add((observation_term,
                      cex.term("ref-indicator"),
                      base_ind.term(observation['indicator'])))

            graph.add((observation_term,
                      cex.term("ref-year"),
                      base_time.term(observation['year'])))

            graph.add((observation_term,
                      cex.term("ref-area"),
                      base_area.term(observation['area'])))

            graph.add((observation_term,
                       schema.term("Continent"),
                       Literal(observation['continent'])))

    @staticmethod
    def _build_observation_id(observation_dict):
        return "OBS_{}_{}_{}".format(observation_dict["indicator"],
                                     observation_dict["area"],
                                     observation_dict["year"])

    @staticmethod
    def _build_observation_label(observation_dict):
        return "Observation for {} over the indicator {} during {}".format(observation_dict["area"],
                                                                           observation_dict['indicator'],
                                                                           observation_dict['year'])


import tqdm
import pandas as pd
from cfuzzyset import cFuzzySet as FuzzySet
import pickle
from wordfreq import word_frequency
import os
import string
import sys

def load_dictionary():
    print("loading dutch dictionary...")
    opentaal_dict_file = "data/Dutch.dic"
    fasttext_vocab_file = "data/dutch_vocabulary.txt"
    words = FuzzySet()

    for counter, line in tqdm.tqdm(enumerate(open(opentaal_dict_file))):
        if counter == 0:
            continue
    words.add(line.split("/")[0].strip())

    for counter, line in tqdm.tqdm(enumerate(open(fasttext_vocab_file))):
        if counter ==0:
            continue
        words.add(line.strip())
    return words

dutch_vocabulary = pickle.load(open("fuzzy_dict.pickle", 'rb'))

def get_clean_word(x):

    if len(x)<1:
        return None
    if ' ' in x:
        candidate = dutch_vocabulary.get(x)[0]
        words_score = []
        _words = x.split()
        for w in _words:
            words_score.append(dutch_vocabulary.get(w)[0])
        probability_sum = sum([x[0]*word_frequency(x[1], "nl") for x in words_score])/len(words_score)
        full_word_probability = word_frequency(candidate[1], "nl") * candidate[0]
        if probability_sum > full_word_probability:
            return " ".join([x[1] for x in words_score])
        else:
            return candidate[1]
    candidate = dutch_vocabulary.get(x)[0]
    if candidate[0]< 0.6:
        return -1
    return candidate[1]

def main():
    bad_chars =  string.punctuation+"'„”‘…»—“‘‚"
    translator = str.maketrans(bad_chars, ' '*len(bad_chars))
    if len(sys.argv) > 2 and sys.argv[1] == "line":
        home_folder = sys.argv[2]
        print(home_folder)
        outfolder = "data/processed_ocr/{}/".format(home_folder.split("/")[-1])
        try:
            os.mkdir(outfolder)
        except Exception as e:
            print(e)
            pass
        for counter, f in enumerate(tqdm.tqdm(os.listdir(home_folder))):
            list_of_dicts = []
            for line_number, line in enumerate(open(os.path.join(home_folder, f))):
                line_dict = {}
                if line_number == 0:
                    header = line[:-1].split("\t")
                    continue
                l = line[:-1].split("\t")
                if len(l) != 12:
                    continue
                text = l[-1].strip()
                #empty string, low confidence, small font
                if len(text.strip()) == 0 or int(l[-2]) <10 or int(l[8])<20:
                    continue
                line_dict = {k:v for k, v in zip(header, l)}
                #remove punctuation marks
                text = text.strip().translate(translator).strip()
                #empty?
                if len(text)<1:
                    continue
                #get words from dictionary
                clean_text = " ".join([get_clean_word(x) for x in text.split() if get_clean_word(x)!=-1])
                line_dict["clean_text"] = clean_text
                list_of_dicts.append(line_dict.copy())
            if len(list_of_dicts) == 0:
                continue
            final_df = pd.DataFrame(list_of_dicts)
            final_df.to_csv(outfolder+f, sep="\t")
        return
    home_folder = sys.argv[1]
    print(home_folder)
    outfolder = "data/processed_ocr/{}/".format(home_folder.split("/")[-1])
    try:
        os.mkdir(outfolder)
    except Exception as e:
        print(e)
        pass
    for counter, f in enumerate(tqdm.tqdm(os.listdir(home_folder))):
        df = pd.read_csv(os.path.join(home_folder, f), sep="\t", doublequote=False, quotechar="'")
        df = df[(-df.text.isna()) & (df.conf>10) & (df.height>20)]
        df.text = df.text.apply(lambda x: x.translate(translator).strip())
        df['actual_word'] = df.text.apply(get_clean_word)
        df = df[-df.actual_word.isna()]
        df.to_csv(outfolder+f, sep="\t")

if __name__=="__main__":
    main()


import sys
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import numpy as np
import math

from scipy import signal

import struct
import serial
import numpy

import os
import re
import pickle
import random
import binascii
import csv

def smooth(x,window_len=11,window='hanning'):

  if x.ndim != 1:
    raise ValueError('smooth only accepts 1 dimension arrays.')

  if x.size < window_len:
    raise ValueError('Input vector needs to be bigger than window size.')

  if window_len<3:
    return x

  if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']:
    raise ValueError("Window is on of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'")

  s=numpy.r_[x[window_len-1:0:-1],x,x[-2:-window_len-1:-1]]
  #print(len(s))
  if window == 'flat': #moving average
    w=numpy.ones(window_len,'d')
  else:
    w=eval('numpy.'+window+'(window_len)')

  y=numpy.convolve(w/w.sum(),s,mode='valid')
  return y

CMD_CONNECT = 0xAA
CMD_BUFFER = 0xAB
CMD_DECODER = 0xAC
CMD_IMPEDANCE = 0xAD
CMD_RESET = 0xDE
RESET2 = 0xAD
CMD_UPLOAD_FILTER_BAND = 0xBC
CMD_UPLOAD_FILTER_NOTCH = 0xBD
CMD_UPLOAD_FILTER_IMP = 0xBE

CMD_USER_FILTER_BAND = 0xCA
CMD_USER_FILTER_NOTCH = 0xCB
CMD_USER_FILTER_IMP  = 0xCC

userFilterBand = []
userBandGain = 0.0
userFilterNotch = []
userNotchGain = 0.0
userFilterImp = []
userImpGain = 0.0
class serialReceiver(QtCore.QThread):

  newSample = QtCore.pyqtSignal(int,int, tuple, tuple, tuple)
  serialConn = QtCore.pyqtSignal(bool)
  ackResult = QtCore.pyqtSignal(bool)

  def __init__(self, baud, port, serialEnabled, dataBuffer1, dataBuffer2, writeVal,
              accelBufferX):
    QtCore.QThread.__init__(self)
    self.serialEnabled = serialEnabled
    self.dataBuffer1 = dataBuffer1
    self.dataBuffer2 = dataBuffer2
    self.writeVal = writeVal
    self.accelBufferX = accelBufferX
    self.commandActive = False
    self.loopRun = 1 #variable to stop the loop when entering the game looprun = 0
    self.stopThread = False
    self.ser = serial.Serial()

    self.ser.baudrate = baud
    self.ser.port = port
    #self.ser.open()
    #self.ser.write(str.encode("wb"))
    self.a=0
    #self.serialConnect()

  @staticmethod
  def floatToByteArray(floatNumber):
    floatBytes = bytearray(struct.pack('<f',floatNumber))
    return floatBytes

  @staticmethod
  def formatCmd(commandType,data):
    dataSize = len(data);
    cmdArray = bytearray([commandType])
    cmdHeaderSize = len(cmdArray)
    cmdArray.extend(data)
    cmdArray += str.encode('\r')
    cmdArray += str.encode('\n')
    #cmdPacket = struct.pack('B' * (cmdHeaderSize+dataSize), cmdArray)
    cmdPacket = cmdArray
    #print(cmdPacket)
    return cmdPacket

  @staticmethod
  def formatDataMsg(commandType,command, value):
    pkt = struct.pack('BBBBf',commandType,command,0,0,value)
    return pkt

  def serialConnect(self):
    print("serial Open")
    try:
      self.ser.open()
      self.serialEnabled = True
      self.serialConn.emit(self.serialEnabled)
    except:
      print("serial open exception")
      self.serialConn.emit(self.serialEnabled)
    #if not self.serialEnabled :
    #  self.serialEnabled = True
    #  self.serialConn.emit(self.serialEnabled)
    return

  def serialDisconnect(self):
    print("serial close")
    self.ser.close()
    self.serialEnabled = False
    self.serialConn.emit(self.serialEnabled)
    return

  def unpackReceivedData1(self,data,channel):
    try:
      s = struct.Struct('125f')
      self.dataBuffer1 = s.unpack(data)
      self.writeVal = 2
      chanCounter = channel
      self.newSample.emit(channel, self.writeVal, self.dataBuffer1, self.dataBuffer2, self.accelBufferX)
    except:
      print("receive exception1")

  def unpackReceivedData2(self,data,channel):
    try:
      s = struct.Struct('125f')
      self.dataBuffer2 = s.unpack(data)
      self.writeVal = 1
      chanCounter = channel
      self.newSample.emit(channel, self.writeVal, self.dataBuffer1, self.dataBuffer2, self.accelBufferX)
    except:
      print("receive exception2")

  def run(self):
    s = struct.Struct('125f')
    #s = struct.Struct('f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f f')
    s2 = struct.Struct('f')
    sAccel = struct.Struct('3f')
    #sAccel = struct.Struct('f f f')

    while 1:
      try:
        if self.loopRun:
          data = self.ser.read(1)
          if(ord(data) == 255):
            data = self.ser.read(3)
            #if(data[0] == 255 and data[1] == 0):
            if(data[0] == 255 and data[1] == 0):
              #channel = data[2]
              channel = data[2]
              if(channel == 6):
                #self.loopRun = 0
                print("")
                data = self.ser.read(4)
                log = s2.unpack(data)
                print("log:"+str(log))
                print("")
              elif(channel == 7):
                data = self.ser.read(12)
                self.accelBufferX = sAccel.unpack(data)
                self.newSample.emit(7,7, self.dataBuffer1, self.dataBuffer2, self.accelBufferX)
              elif(channel > 0) and (channel < 6):
                data = self.ser.read(500)
                if(self.writeVal == 1):
                  self.unpackReceivedData1(data,channel)
                  # self.dataBuffer1 = s.unpack(data)
                  # self.writeVal = 2
                  # chanCounter = channel
                  # self.newSample.emit(channel, self.writeVal, self.dataBuffer1, self.dataBuffer2, self.accelBufferX)
                else:
                  self.unpackReceivedData2(data,channel)
                  # self.dataBuffer2 = s.unpack(data)
                  # self.writeVal = 1
                  # chanCounter = channel
                  # self.newSample.emit(channel, self.writeVal, self.dataBuffer1, self.dataBuffer2, self.accelBufferX)
            else:
              pass
              #self.ser.flush()
          #elif(data[0] == 5):
          #  if not self.serialEnabled :
          #    self.serialConn.emit(self.serialEnabled)
          #  self.enabled = 1
          #  print("Raw signals enabled!")
          #elif(data[0] == 20):
            #if not self.serialEnabled :
            #  self.serialConn.emit(self.serialEnabled)
            #self.enabled = 1
          else:
            if(self.commandActive):
              self.ackResult.emit(self.waitForAck(data))

              print("loop command is active")
              #print(data)
              #ack = self.ser.read()
              #print(ack)

        else:
          self.a +=1
          print(self.a)
      except:
        if self.stopThread:
          print("stopping serial")
          #self.ser.close()
          return
        else:
          continue

  def waitForAck(self,ackHeader):
    print("waitForAck")
    ack = self.ser.read(1)
    #self.printBytes(ackHeader)
    #self.printBytes(ack)
    if(ord(ackHeader) == 0xFA):
      if(ord(ack) == 0x47):
        print("ack")
        self.ser.flush()
        self.commandActive = False
        if(not self.serialEnabled):
          self.serialEnabled = True
          self.serialConn.emit(self.serialEnabled)
        return True
    else:
      print("nack")
      self.ser.flush()
      self.commandActive = False
      return False

  def connect(self):
      print("sending connect")
      self.commandActive = True
      self.ser.cancel_read()
      self.ser.flush()
      firstAck = self.ser.read(1)
      self.ser.write(serialReceiver.formatCmd(CMD_CONNECT,[0x01]))
      return self.waitForAck(firstAck)

  def disconnect(self):
    self.stopThread = True
    self.ser.cancel_read()
    self.ser.flush()
    self.serialDisconnect()

  def toggleRawAcquisition(self, enable):
    print("toggle buffer Acquisition: " + str(enable))
    self.commandActive = True
    #self.ser.cancel_read()
    #self.ser.flush()
    self.ser.write(serialReceiver.formatCmd(CMD_BUFFER,[enable]))
    #return self.waitForAck()

  def toggleDecoderAcquisition(self, enable):
    print("toggle decoder Acquisition: " + str(enable))
    self.commandActive = True
    #self.ser.cancel_read()
    #self.ser.flush()
    self.ser.write(serialReceiver.formatCmd(CMD_DECODER,[enable]))
    #return self.waitForAck()

  def printBytes(self, bytearrayPrint):
    print(binascii.hexlify(bytearrayPrint).decode('ascii'))

  def sendBandPassParams(self,filterList,gain):
    self.commandActive = True
    #floatlist = [random.random() for _ in range(5*7)]
    #filterParamBytes = struct.pack('%sf' % len(floatlist), *floatlist)
    filterParamBytes = struct.pack('%sf' % len(filterList), *filterList)
    filterGainBytes = struct.pack('f', gain)
    filterArray = bytearray(filterParamBytes)
    gainArray = bytearray(filterGainBytes)
    padArray = bytearray([CMD_UPLOAD_FILTER_BAND,0x00,0x00])
    filterData = padArray + filterArray + gainArray
    msg = serialReceiver.formatCmd(CMD_UPLOAD_FILTER_BAND,filterData)
    self.printBytes(msg)
    print("sending bandpass")
    #self.ser.cancel_read()
    #self.ser.flush()
    self.ser.write(msg)

  def sendNotchParams(self,filterList,gain):
    self.commandActive = True
    filterParamBytes = struct.pack('%sf' % len(filterList), *filterList)
    filterGainBytes = struct.pack('f', gain)
    print("sendgain")
    print(gain)
    filterArray = bytearray(filterParamBytes)
    gainArray = bytearray(filterGainBytes)
    padArray = bytearray([CMD_UPLOAD_FILTER_NOTCH,0x00,0x00])
    filterData = padArray + filterArray + gainArray
    msg = serialReceiver.formatCmd(CMD_UPLOAD_FILTER_NOTCH,filterData)
    self.printBytes(msg)
    print("sending Notch")
    #self.ser.cancel_read()
    #self.ser.flush()
    self.ser.write(msg)

  def sendImpParams(self):
    self.commandActive = True
    floatlist = [random.random() for _ in range(5*6)]
    print(floatlist)
    filterParamBytes = struct.pack('%sf' % len(floatlist), *floatlist)
    filterGainBytes = struct.pack('f', 3.14)
    filterArray = bytearray(filterParamBytes)
    gainArray = bytearray(filterGainBytes)
    padArray = bytearray([CMD_UPLOAD_FILTER_IMP,0x00,0x00])
    filterData = padArray + filterArray + gainArray
    msg = serialReceiver.formatCmd(CMD_UPLOAD_FILTER_IMP,filterData)
    self.printBytes(msg)
    print("sending Impedance")
    #self.ser.cancel_read()
    self.ser.flush()
    self.ser.write(msg)

  def toggleUserBandpass(self,enable):
    self.commandActive = True
    print("toggle user band: " + str(enable))
    #self.ser.cancel_read()
    #self.ser.flush()
    self.ser.write(serialReceiver.formatCmd(CMD_USER_FILTER_BAND,[enable]))

  def toggleUserNotch(self,enable):
    self.commandActive = True
    print("toggle user notch: " + str(enable))
    #self.ser.cancel_read()
    #self.ser.flush()
    self.ser.write(serialReceiver.formatCmd(CMD_USER_FILTER_NOTCH,[enable]))

  def toggleUserImp(self,enable):
    self.commandActive = True
    print("toggle user imp: " + str(enable))
    #self.ser.cancel_read()
    #self.ser.flush()
    self.ser.write(serialReceiver.formatCmd(CMD_USER_FILTER_IMP,[enable]))

  def setRawAcquisition(self):
    self.commandActive = True
    self.loopRun = 1
    self.ser.write(str.encode("wb"))

  def setDecoderAcquisition(self):
    self.commandActive = True
    self.loopRun = 10
    self.ser.write(str.encode("wa"))

class tabdemo(QtGui.QTabWidget):

  def __init__(self, parent = None):

    self.PROGRAM_VER = 4.5

    self.after_exit = 0

    self.dataBuffer1 = (0,0)
    self.dataBuffer2 = (0,0)
    self.accelBufferX = (0,0)

    self.writeVal = 1
    self.serialEnabled = False

    self.DOWNSAMPLE_WIND = 1
    self.FFT_WINDOW = 5000
    self.SPECT_WINDOW = 5000
    self.spectBin = 500
    self.SAMPLES = 5000
    self.FS = 500

    self.BGCOLOR = pg.mkColor(220,220,220)
    self.FGCOLOR = pg.mkColor(100,100,100)
    self.plotcolor = QtGui.QColor(76,100,230)
    self.PLTWIDTH = 2

    super(tabdemo, self).__init__(parent)

    self.activeTab = 1
    self.activeChannel = 1
    self.counter1 = 0
    self.counter2 = 0
    self.counter3 = 0
    self.counter4 = 0
    self.counter5 = 0
    self.counter = 0
    self.counterAccel = 0
    self.channelone = [0]*5000
    self.channeltwo = [0]*5000
    self.channelthree = [0]*5000
    self.channelfour = [0]*5000
    self.channelfive = [0]*5000
    self.channeldata = [0]*5000
    self.channelAccelX = [0]*1000
    self.channelAccelY = [0]*1000
    self.channelAccelZ = [0]*1000
    self.fftsamples = [0]*5000
    self.bufferSpect = [0]*self.SPECT_WINDOW
    self.spectCounter = 0

    self.serialBaud = 115200
    self.serialPort = str('/dev/tty.mindreachBTv4_2-Bluetoo')

    self.username = "nuno"
    self.usernameGame = "nuno"

    self.disableYAutorange = False
    self.yLimitSpinbox_value = 00100.0 #uV
    self.yLimit_value = self.yLimitSpinbox_value / 1000000

    self.stdMultiplier_value = 2.5
    self.stdMultiplierBase_value = 1.0
    self.stateThresholds = 0
    self.active_file = 0
    self.realthresholds = {}

    # -------------------------------
    # RAW DATA GRAPHS
    # -------------------------------

    self.limitCheckbox = QtGui.QCheckBox("Disable Y Autorange",self)
    self.limitCheckbox.stateChanged.connect(self.checklimit)

    self.limitLabel1 = QtGui.QLabel("limit peak (uV):")
    self.limitLabel1.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter)

    self.limitSpinbox = QtGui.QDoubleSpinBox()
    self.limitSpinbox.setMaximum(1000.0)
    self.limitSpinbox.setMinimum(10.0)
    self.limitSpinbox.setAlignment(QtCore.Qt.AlignRight)
    self.limitSpinbox.setMinimumSize(90,20)
    self.limitSpinbox.setMaximumSize(90,20)
    self.limitSpinbox.setValue(self.yLimitSpinbox_value)

    pg.setConfigOption('background', self.BGCOLOR)
    pg.setConfigOption('foreground', self.FGCOLOR)
    pg.setConfigOptions(useOpenGL=True)

    pen = pg.mkPen(color = self.plotcolor, width = 3)

    self.graph1 = pg.PlotWidget()
    self.rawData1 = pg.PlotCurveItem(pen=pen)
    self.graph1.addItem(self.rawData1)
    self.graph2 = pg.PlotWidget()
    self.rawData2 = pg.PlotCurveItem(pen=pen)
    self.graph2.addItem(self.rawData2)
    self.graph3 = pg.PlotWidget()
    self.rawData3 = pg.PlotCurveItem(pen=pen)
    self.graph3.addItem(self.rawData3)
    self.graph4 = pg.PlotWidget()
    self.rawData4 = pg.PlotCurveItem(pen=pen)
    self.graph4.addItem(self.rawData4)
    self.graph5 = pg.PlotWidget()
    self.rawData5 = pg.PlotCurveItem(pen=pen)
    self.graph5.addItem(self.rawData5)

    self.graphAccel_x = pg.PlotWidget()
    self.accelDataX = pg.PlotCurveItem()
    self.graphAccel_x.addItem(self.accelDataX)
    self.graphAccel_y = pg.PlotWidget()
    self.accelDataY = pg.PlotCurveItem()
    self.graphAccel_y.addItem(self.accelDataY)
    self.graphAccel_z = pg.PlotWidget()
    self.accelDataZ = pg.PlotCurveItem()
    self.graphAccel_z.addItem(self.accelDataZ)
    #axis_1x = self.graph1.getAxis('left')

    self.graph1.setLabel('left', "Amplitude", "V")
    self.graph1.setLabel('bottom', "Number of points")
    self.graph1.setTitle("Channel 1")
    self.graph2.setLabel('left', "Amplitude", "V")
    self.graph2.setLabel('bottom', "Number of points")
    self.graph2.setTitle("Channel 2")
    self.graph3.setLabel('left', "Amplitude", "V")
    self.graph3.setLabel('bottom', "Number of points")
    self.graph3.setTitle("Channel 3")
    self.graph4.setLabel('left', "Amplitude", "V")
    self.graph4.setLabel('bottom', "Number of points")
    self.graph4.setTitle("Channel 4")
    self.graph5.setLabel('left', "Amplitude", "V")
    self.graph5.setLabel('bottom', "Number of points")
    self.graph5.setTitle("Channel 5")

    self.graphAccel_x.setLabel('left', "Acceleration", "ms^-2")
    self.graphAccel_x.setLabel('bottom', "Number of points")
    self.graphAccel_x.setTitle("Acceleration X")

    self.graphAccel_y.setLabel('left', "Acceleration", "ms^-2")
    self.graphAccel_y.setLabel('bottom', "Number of points")
    self.graphAccel_y.setTitle("Acceleration Y")

    self.graphAccel_z.setLabel('left', "Acceleration", "ms^-2")
    self.graphAccel_z.setLabel('bottom', "Number of points")
    self.graphAccel_z.setTitle("Acceleration Z")

    self.graph1.setMouseEnabled(x=False, y=True)
    self.graph2.setMouseEnabled(x=False, y=True)
    self.graph3.setMouseEnabled(x=False, y=True)
    self.graph4.setMouseEnabled(x=False, y=True)
    self.graph5.setMouseEnabled(x=False, y=True)

    # -------------------------------
    # SPECTROGRAM GRAPHS
    # -------------------------------

    self.ddMenu = QtGui.QComboBox()

    self.graph = pg.PlotWidget()
    self.graphData = pg.PlotCurveItem(pen = pen)
    self.graph.addItem(self.graphData)
    self.graph.setLabel('left', "Amplitude", "V")
    self.graph.setLabel('bottom', "Number of points")
    self.graph.setMouseEnabled(x=False, y=True)
    self.fft = pg.PlotWidget()
    self.fftData = pg.PlotCurveItem(pen = pen)
    self.fft.addItem(self.fftData)
    self.fft.setLabel('left', "Amplitude", "V")
    self.fft.setLabel('bottom', "Frequency (Hz)")
    bx = self.fft.getAxis('bottom')
    bx.setScale(0.1)

    self.spectrogramImage = pg.PlotWidget()
    self.spectrogram = pg.ImageItem()
    self.spectrogramImage.addItem(self.spectrogram)
    self.spectrogramImage.setLabel('left', 'Frequency', units='Hz')
    self.spectrogramImage.setLabel('bottom', 'Time', units='s')

    self.tabConnect = QtGui.QWidget()	#connect
    self.tabEEG = QtGui.QWidget()
    self.tabSpect = QtGui.QWidget()
    self.tabGame = QtGui.QWidget()
    self.tabDist = QtGui.QWidget()
    self.tabAccel = QtGui.QWidget() #accel
    self.connectTabIndex = self.addTab(self.tabConnect,"Connect") + 1
    self.eegTabIndex = self.addTab(self.tabEEG,"EEG Data") + 1
    self.spectTabIndex = self.addTab(self.tabSpect,"Spectogram") + 1
    self.accelTabIndex = self.addTab(self.tabAccel,"Accel Data") + 1
    self.distTabIndex = self.addTab(self.tabDist,"Distributions") + 1
    self.gameTabIndex = self.addTab(self.tabGame,"Game") + 1
    print("connectIndex",self.connectTabIndex)
    print("eegIndex",self.eegTabIndex)

    # -------------------------------
    # Distribution GRAPHS
    # -------------------------------

    self.positionFileMenu = QtGui.QComboBox()

    self.dist_pg = pg.PlotWidget()
    self.plot_pg = pg.PlotWidget()

    # vals = np.hstack([np.random.normal(size=500), np.random.normal(size=260, loc=4)])
    # y,x = np.histogram(vals, bins=np.linspace(-3, 8, 40))
    # self.dist = self.dist_pg.plot(x,y,stepMode=True, fillLevel=0, brush=(0,0,255,150))
    #self.plot = self.plot_pg.plot(vals)

    # mean_pos = np.mean(x)
    # std_pos = np.std(x)
    # vert_line = pg.InfiniteLine(mean_pos, angle = 90)
    # self.dist_pg.addItem(vert_line)

    #self.dist.addItem(self.distData)

    self.plot_pg.setLabel('left', "Counts", "")
    self.plot_pg.setLabel('bottom', "time(s)")
    self.dist_pg.setLabel('bottom', "Position")

    self.showThresholdsCheckbox = QtGui.QCheckBox("Show current thresholds",self)
    self.showThresholdsCheckbox.stateChanged.connect(self.toggleShowThresholds)

    self.stdMultiplierSpinbox = QtGui.QDoubleSpinBox()
    self.stdMultiplierSpinbox.setAlignment(QtCore.Qt.AlignRight)
    self.stdMultiplierSpinbox.setMinimumSize(90,20)
    self.stdMultiplierSpinbox.setMaximumSize(90,20)
    self.stdMultiplierSpinbox.setValue(self.stdMultiplier_value)
    self.stdMultiplierSpinbox.setSingleStep(0.1)

    self.stdMultiplierBaseSpinbox = QtGui.QDoubleSpinBox()
    self.stdMultiplierBaseSpinbox.setAlignment(QtCore.Qt.AlignRight)
    self.stdMultiplierBaseSpinbox.setMinimumSize(90,20)
    self.stdMultiplierBaseSpinbox.setMaximumSize(90,20)
    self.stdMultiplierBaseSpinbox.setValue(self.stdMultiplierBase_value)
    self.stdMultiplierBaseSpinbox.setSingleStep(0.1)

    self.connectTabUI()
    self.graphTabUI()
    self.spectTabUI()
    self.distTabUI()
    self.gameTabUI()
    self.accelTabUI()
    self.connectTabs()

    self.setWindowTitle("VisionVolt - Mindreach EEG Headset GUI, version: " + str(self.PROGRAM_VER))

  def setAutoRange(self,graph, enable):
    axY = graph.getAxis('left')
    if(enable == True):
      graph.enableAutoRange(axY,True,None, 'y')
    else:
      graph.enableAutoRange(axY,False,None, 'y')

  def setGraphRange(self,graph,rangeMax,rangeMin):
    graph.setYRange(rangeMax,rangeMin, padding=0)

  def changeLimitValue(self):

    self.yLimitSpinbox_value = self.limitSpinbox.value()
    self.yLimit_value = self.yLimitSpinbox_value / 1000000
    print("current value:"+str(self.yLimit_value))
    if(self.disableYAutorange):
      self.setGraphRange(self.graph1, 0-self.yLimit_value, self.yLimit_value)
      self.setGraphRange(self.graph2, 0-self.yLimit_value, self.yLimit_value)
      self.setGraphRange(self.graph3, 0-self.yLimit_value, self.yLimit_value)
      self.setGraphRange(self.graph4, 0-self.yLimit_value, self.yLimit_value)
      self.setGraphRange(self.graph5, 0-self.yLimit_value, self.yLimit_value)

  def changeStdMultiplier(self):
    self.stdMultiplier_value = self.stdMultiplierSpinbox.value()
    self.changedPositionFile()
    self.showThresholds()

  def changeStdBaseMultiplier(self):
    self.stdMultiplierBase_value = self.stdMultiplierBaseSpinbox.value()
    self.changedPositionFile()
    self.showThresholds()

  def readCSVFilters(self):
    global userFilterBand
    global userBandGain
    global userFilterNotch
    global userNotchGain
    with open('filters.csv') as csv_file:
        csv_reader = csv.reader(csv_file, delimiter=',')
        line_count = 0
        #rowS = []
        for row in csv_reader:
            #if any(x.strip() for x in row):
            #  rowS.append(row)
            rowS = row
            if line_count == 0:
                print(f'Column names are {", ".join(rowS)}')
                line_count += 1
            else:
              #iterRow = iter(row)
              #next(iterRow)
              if (int(rowS[0],16) == CMD_UPLOAD_FILTER_BAND):
                print("bandpass")
                #userBandGain = float(next(iterRow))
                userBandGain = float(rowS[1])
                for i in range(2, 2+(5*7)):
                  userFilterBand.append(float(rowS[i]))
                #for val in iterRow:
                #  userFilterBand.append(float(val))
                print(userFilterBand)

              elif (int(rowS[0],16) == CMD_UPLOAD_FILTER_NOTCH):
                print("notch")
                #userNotchGain = float(next(iterRow))
                userNotchGain = float(rowS[1])
                print(userNotchGain)
                for i in range(2, 2+(5*1)):
                  userFilterNotch.append(float(rowS[i]))
                #for val in iterRow:
                #  userFilterNotch.append(float(val))
                print(userFilterNotch)
              elif (int(rowS[0],16) == CMD_UPLOAD_FILTER_IMP):
                print("impedance")
              line_count += 1
        print(f'Processed {line_count} lines.')

  def checklimit(self, state):
    if state == QtCore.Qt.Checked:
      print('disable autorange')
      self.disableYAutorange = True
      self.setAutoRange(self.graph1,False)
      self.setAutoRange(self.graph2,False)
      self.setAutoRange(self.graph3,False)
      self.setAutoRange(self.graph4,False)
      self.setAutoRange(self.graph5,False)
      self.setGraphRange(self.graph1, 0-self.yLimit_value, self.yLimit_value)
      self.setGraphRange(self.graph2, 0-self.yLimit_value, self.yLimit_value)
      self.setGraphRange(self.graph3, 0-self.yLimit_value, self.yLimit_value)
      self.setGraphRange(self.graph4, 0-self.yLimit_value, self.yLimit_value)
      self.setGraphRange(self.graph5, 0-self.yLimit_value, self.yLimit_value)
    else:
      print('enable autorange')
      self.disableYAutorange = False
      self.setAutoRange(self.graph1,True)
      self.setAutoRange(self.graph2,True)
      self.setAutoRange(self.graph3,True)
      self.setAutoRange(self.graph4,True)
      self.setAutoRange(self.graph5,True)

  def changeDDMenu(self):
    self.activeChannel = self.ddMenu.currentIndex() + 1
    self.counter = 0
    self.channeldata = [0]*5000
    self.fftsamples = [0]*self.FFT_WINDOW
    self.fftData.setData(self.channeldata)

  def toggleShowThresholds(self, state):
    if state == QtCore.Qt.Checked:
      self.stateThresholds = 1
      self.showThresholds()

    elif state != QtCore.Qt.Checked:
      self.stateThresholds = 0
      try:
        self.dist_pg.removeItem(self.mean_vert_current)
        self.dist_pg.removeItem(self.target_up_current)
        self.dist_pg.removeItem(self.target_down_current)
        self.dist_pg.removeItem(self.base_up_current)
        self.dist_pg.removeItem(self.base_down_current)
      except:
        pass

  def showThresholds(self):
    try:
      self.dist_pg.removeItem(self.mean_vert_current)
      self.dist_pg.removeItem(self.target_up_current)
      self.dist_pg.removeItem(self.target_down_current)
      self.dist_pg.removeItem(self.base_up_current)
      self.dist_pg.removeItem(self.base_down_current)
    except:
      pass

    if self.stateThresholds:
      g = open( self.pathUser +'thresholds.txt', 'r')
      thresholds_curr = eval(g.read())
      g.close()

      mean_curr = (thresholds_curr['upReward'] + thresholds_curr['downReward'])/2

      self.mean_vert_current = pg.InfiniteLine(mean_curr, angle = 90,
                                  pen = pg.mkPen(color=(10, 200, 0), width=1, style=QtCore.Qt.DotLine))
      self.target_up_current = pg.InfiniteLine(thresholds_curr['upReward'], angle = 90,
                                  pen = pg.mkPen(color=(10, 200, 0), width=2, style=QtCore.Qt.SolidLine))
      self.target_down_current = pg.InfiniteLine(thresholds_curr['downReward'], angle = 90,
                                  pen = pg.mkPen(color=(10, 200, 0), width=2, style=QtCore.Qt.SolidLine))
      self.base_up_current = pg.InfiniteLine(thresholds_curr['upBase'], angle = 90,
                                  pen = pg.mkPen(color=(10, 200, 0), width=2, style=QtCore.Qt.DashLine))
      self.base_down_current = pg.InfiniteLine(thresholds_curr['downBase'], angle = 90,
                                  pen = pg.mkPen(color=(10, 200, 0), width=2, style=QtCore.Qt.DashLine))

      self.dist_pg.addItem(self.mean_vert_current)
      self.dist_pg.addItem(self.target_up_current)
      self.dist_pg.addItem(self.target_down_current)
      self.dist_pg.addItem(self.base_up_current)
      self.dist_pg.addItem(self.base_down_current)

  def changedPositionFile(self):
    self.activeFile = self.positionFileMenu.currentIndex() + 1
    try:
      self.active_file = self.all_pos_files[self.activeFile-1]
    except:
      self.active_file = "No files to show"
      print('no file to show')
    if self.active_file != "No files to show":
      try:
        with open(self.pathUser + self.active_file, 'rb') as filehandle:
            # read the data as binary data stream
            position_array = pickle.load(filehandle)
        y,x = np.histogram(position_array, bins=int(len(position_array)/10))
        self.dist_pg.clear()
        self.dist_pg.plot(x,y,stepMode=True, fillLevel=0, brush=(0,0,255,150))

        mean_pos = np.mean(x)
        std_pos = np.std(x)

        mean_vert = pg.InfiniteLine(mean_pos, angle = 90,
                                    pen = pg.mkPen(color=(200, 0, 0), width=1, style=QtCore.Qt.DotLine))

        self.target_up = mean_pos+self.stdMultiplier_value*std_pos
        self.target_down = mean_pos-self.stdMultiplier_value*std_pos
        self.base_up = mean_pos+(self.stdMultiplierBase_value)*std_pos
        self.base_down = mean_pos-(self.stdMultiplierBase_value)*std_pos
        self.range_up = mean_pos-(self.stdMultiplier_value+0.3)*std_pos
        self.range_down = mean_pos+(self.stdMultiplier_value+0.3)*std_pos

        self.realthresholds['upReward'] = self.target_up
        self.realthresholds['downReward'] = self.target_down
        self.realthresholds['upBase'] = self.base_up
        self.realthresholds['downBase'] = self.base_down
        self.realthresholds['upRange'] = self.range_up
        self.realthresholds['downRange'] = self.range_down

        mean_vert = pg.InfiniteLine(mean_pos, angle = 90,
                                    pen = pg.mkPen(color=(200, 0, 0), width=1, style=QtCore.Qt.DotLine))
        target_up = pg.InfiniteLine(self.target_up, angle = 90,
                                    pen = pg.mkPen(color=(200, 0, 0), width=2, style=QtCore.Qt.SolidLine))
        target_down = pg.InfiniteLine(self.target_down, angle = 90,
                                    pen = pg.mkPen(color=(200, 0, 0), width=2, style=QtCore.Qt.SolidLine))
        base_up = pg.InfiniteLine(self.base_up, angle = 90,
                                    pen = pg.mkPen(color=(200, 0, 0), width=2, style=QtCore.Qt.DashLine))
        base_down = pg.InfiniteLine(self.base_down, angle = 90,
                                    pen = pg.mkPen(color=(200, 0, 0), width=2, style=QtCore.Qt.DashLine))
        self.dist_pg.addItem(mean_vert)
        self.dist_pg.addItem(target_up)
        self.dist_pg.addItem(target_down)
        self.dist_pg.addItem(base_up)
        self.dist_pg.addItem(base_down)

        self.showThresholds()
      except:
        self.dist_pg.clear()

  def tabChanged(self):
    lastActiveTab = self.activeTab
    print("lastActive:", lastActiveTab)
    self.activeTab = self.currentIndex() + 1
    print("Active:", self.activeTab)
    if(self.activeTab == self.eegTabIndex):
      print("tab eeg")
      self.counter1 = 0
      self.counter2 = 0
      self.counter3 = 0
      self.counter4 = 0
      self.counter5 = 0
      self.channelone = [0]*5000
      self.channeltwo = [0]*5000
      self.channelthree = [0]*5000
      self.channelfour = [0]*5000
      self.channelfive = [0]*5000
      if(lastActiveTab == self.gameTabIndex):
        try:
          self.thread.toggleRawAcquisition(0x01)
        except:
          print("No Serial connection")

    elif(self.activeTab == self.spectTabIndex):
      print("tab spect")
      self.counter = 0
      self.channeldata = [0]*5000
      self.fftsamples = [0]*self.FFT_WINDOW
      self.fftData.setData(self.channeldata)
      if(lastActiveTab == self.gameTabIndex):
        try:
          self.thread.toggleRawAcquisition(0x01)
        except:
          print("No Serial connection")

    elif(self.activeTab == self.gameTabIndex):
      print('Game Tab')
      try:
          self.thread.toggleDecoderAcquisition(0x01)
      except:
        print("No Serial connection")

    elif(self.activeTab == self.accelTabIndex):
      print("tab tabAccel")
      self.channelAccelX = [0]*1000
      self.channelAccelY = [0]*1000
      self.channelAccelZ = [0]*1000
      self.counterAccel = 0
      if(lastActiveTab == self.gameTabIndex):
        try:
          self.thread.toggleRawAcquisition(0x01)
        except:
          print("No Serial connection")

    elif(self.activeTab == self.distTabIndex):
      print("tab histogram")

    else:
      self.counter = 0

  def connectTabs(self):
    self.currentChanged.connect(self.tabChanged)
    print('here')
    self.portedit.editingFinished.connect(self.port_change)
    self.baudedit.editingFinished.connect(self.baud_change)
    self.connectbutton.clicked.connect(self.start_serial)
    self.disconnectbutton.clicked.connect(self.stop_serial)

    #self.testButton.clicked.connect(self.testFunction)
    self.rawAqButton.clicked.connect(self.toggleRawFunction)
    self.decoderAqButton.clicked.connect(self.toggleDecoderFunction)
    self.uploadBandpassButton.clicked.connect(self.uploadBandpassFunction)
    self.uploadNotchButton.clicked.connect(self.uploadNotchFunction)
    self.enableUserBandpassButton.clicked.connect(self.enableUserBandpassFunction)
    self.enableUserNotchButton.clicked.connect(self.enableUserNotchFunction)

  def enableUserBandpassFunction(self):
    if self.thread.isRunning():
      self.thread.toggleUserBandpass(0x01)

  def enableUserNotchFunction(self):
    if self.thread.isRunning():
      self.thread.toggleUserNotch(0x01)

  def uploadBandpassFunction(self):
    global userFilterBand
    global userBandGain
    self.readCSVFilters()
    if self.thread.isRunning():
      self.thread.sendBandPassParams(userFilterBand, userBandGain)
      self.enableUserBandpassButton.setEnabled(True)

  def uploadNotchFunction(self):
    global userFilterNotch
    global userNotchGain
    self.readCSVFilters()
    if self.thread.isRunning():
      self.thread.sendNotchParams(userFilterNotch, userNotchGain)
      self.enableUserNotchButton.setEnabled(True)

  def toggleRawFunction(self):
    if self.thread.isRunning():
      self.thread.toggleRawAcquisition(0x01)

  def toggleDecoderFunction(self):
    if self.thread.isRunning():
      self.thread.toggleDecoderAcquisition(0x01)

  def testFunction(self):
    global userFilterBand
    self.readCSVFilters()
    if self.thread.isRunning():
      self.thread.sendBandPassParams(userFilterBand)

  def updateGraphs(self,channel, buffer, dataBuffer1, dataBuffer2, accelBufferX):

    if(self.activeTab == self.eegTabIndex): #raw Data
      if(channel == 1):
        for i in range(0,125):
          if(buffer == 1):
            sample = dataBuffer2[i]/10000
          else:
            sample = dataBuffer1[i]/10000
          self.channelone[self.counter1] = sample
          self.counter1 += 1
          if(self.counter1 == 5000):
            #print(self.channelone)
            self.counter1 = 0
        self.rawData1.setData(self.channelone,pen=self.plotcolor)
      elif(channel == 2):
        for i in range(0,125):
          if(buffer == 1):
            sample = dataBuffer2[i]/10000
          else:
            sample = dataBuffer1[i]/10000
          self.channeltwo[self.counter2] = sample
          self.counter2 += 1
          if(self.counter2 == 5000):
            self.counter2 = 0
        self.rawData2.setData(self.channeltwo,pen=self.plotcolor)
      elif(channel == 3):
        for i in range(0,125):
          if(buffer == 1):
            sample = dataBuffer2[i]/10000
          else:
            sample = dataBuffer1[i]/10000
          self.channelthree[self.counter3] = sample
          self.counter3 += 1
          if(self.counter3 == 5000):
            self.counter3 = 0
        self.rawData3.setData(self.channelthree,pen=self.plotcolor)
      elif(channel == 4):
        for i in range(0,125):
          if(buffer == 1):
            sample = dataBuffer2[i]/10000
          else:
            sample = dataBuffer1[i]/10000
          self.channelfour[self.counter4] = sample
          self.counter4 += 1
          if(self.counter4 == 5000):
            self.counter4 = 0
        self.rawData4.setData(self.channelfour,pen=self.plotcolor)
      elif(channel == 5):
        for i in range(0,125):
          if(buffer == 1):
            sample = dataBuffer2[i]/10000
          else:
            sample = dataBuffer1[i]/10000
          self.channelfive[self.counter5] = sample
          self.counter5 += 1
          if(self.counter5 == 5000):
            self.counter5 = 0
        self.rawData5.setData(self.channelfive,pen=self.plotcolor)

    elif(self.activeTab == self.spectTabIndex): #spectogram
      if(channel == self.activeChannel):
        for i in range(0,125):
          if(buffer == 1):
            sample = dataBuffer2[i]/10000
          else:
            sample = dataBuffer1[i]/10000
          self.channeldata[self.counter] = sample
          self.counter += 1
          if(self.counter % self.spectBin == 0 and self.counter != 0):
            self.spectCounter += 1
            self.bufferSpect = np.roll(self.bufferSpect, -self.spectBin, 0)
            self.bufferSpect[-self.spectBin:] = self.channeldata[self.counter-self.spectBin:self.counter]
            ft = np.fft.rfft(self.channeldata,self.FFT_WINDOW)
            f2, Pxx_den2 = signal.welch(self.channeldata, self.FS*10, 'flattop', 1024, scaling='spectrum')
            fftsamples = np.abs(ft[:int(self.FFT_WINDOW/2)])
            fftsamples = fftsamples*10
            fftsamples = fftsamples[::self.DOWNSAMPLE_WIND]
            self.fftData.setData(f2,Pxx_den2)
            self.img_array = np.roll(self.img_array, -1, 0)
            testArray = smooth(fftsamples)
            self.img_array[-1:] = testArray
            self.spectrogram.setImage(self.img_array, autoLevels=False)
          if(self.counter == self.FFT_WINDOW):
            self.counter = 0
        try:
          chn_data_logged = [10*math.log10(y) for y in self.channeldata]
          self.graphData.setData(chn_data_logged)
        except:
          self.graphData.setData(self.channeldata)
    elif(self.activeTab == self.accelTabIndex): #accel
      if(channel == 7):
        self.channelAccelX[self.counterAccel] = accelBufferX[0]
        self.channelAccelY[self.counterAccel] = accelBufferX[1]
        self.channelAccelZ[self.counterAccel] = accelBufferX[2]
        self.counterAccel += 1
        if(self.counterAccel == 1000):
          self.counterAccel = 0
        self.accelDataX.setData(self.channelAccelX,pen= self.plotcolor)
        self.accelDataY.setData(self.channelAccelY,pen= self.plotcolor)
        self.accelDataZ.setData(self.channelAccelZ,pen= self.plotcolor)

    else: # game
      channel = self.activeChannel

  def accelTabUI(self):
    layoutAccel = QtGui.QVBoxLayout()
    layoutAccel.addWidget(self.graphAccel_x)
    layoutAccel.addWidget(self.graphAccel_y)
    layoutAccel.addWidget(self.graphAccel_z)

    self.tabAccel.setLayout(layoutAccel)

  def graphTabUI(self):
    layout = QtGui.QVBoxLayout()
    configLayout = QtGui.QHBoxLayout()
    configLayout.setSpacing(0)
    configLayout.addWidget(self.limitCheckbox)
    configLayout.addWidget(self.limitLabel1)
    configLayout.addWidget(self.limitSpinbox)
    layout.addLayout(configLayout)
    layout.addWidget(self.graph1)
    layout.addWidget(self.graph2)
    layout.addWidget(self.graph3)
    layout.addWidget(self.graph4)
    layout.addWidget(self.graph5)

    self.tabEEG.setLayout(layout)
    self.limitSpinbox.valueChanged.connect(self.changeLimitValue)

  def spectTabUI(self):
    layout = QtGui.QGridLayout()

    self.ddMenu.addItem("Channel 1")
    self.ddMenu.addItem("Channel 2")
    self.ddMenu.addItem("Channel 3")
    self.ddMenu.addItem("Channel 4")
    self.ddMenu.addItem("Channel 5")
    self.ddMenu.currentIndexChanged.connect(self.changeDDMenu)

    # ------------------------
    # SPECTROGRAM ITEMS
    # ------------------------

    self.img_array = np.zeros((10, int(2500/self.DOWNSAMPLE_WIND) + 10))

    pos = np.array([0., 1.])
    color = np.array([[255,255,255,255],[0,0,0,255]], dtype=np.ubyte)
    cmap = pg.ColorMap(pos,color)
    lut = cmap.getLookupTable(0.0,1.0,256)

    self.spectrogram.setLookupTable(lut)
    self.spectrogram.setLevels([0,0.07])

    freq = np.arange((self.SAMPLES/2)+1/float(self.SAMPLES)/self.FS)
    #yscale = 1.0/(self.img_array.shape[1]/freq[-1])
    yscale = 0.1
    self.spectrogram.scale((1./self.FS)*self.SAMPLES,yscale)

    layout.addWidget(self.ddMenu,0,0,1,2)
    layout.addWidget(self.graph,1,0,1,2)
    layout.addWidget(self.fft,2,0,1,1)
    layout.addWidget(self.spectrogramImage,2,1,1,1)

    #self.setTabText(1,"Spectrogram")
    self.tabSpect.setLayout(layout)

  def distTabUI(self):
    layout = QtGui.QGridLayout()

    self.pathData = '/Users/nunoloureiro/mindreach/software_eeg/mindreach_demo/data/'

    self.userText = QtGui.QLabel()
    self.userText.setText("Username:")
    self.userEdit = QtGui.QLineEdit()
    self.userEdit.setText(str(self.username))
    self.StdMultiplierText = QtGui.QLabel()
    self.StdMultiplierText.setText("Set std Multiplier:")
    self.StdMultiplierBaseText = QtGui.QLabel()
    self.StdMultiplierBaseText.setText("Set stdBase Multiplier:")

    self.fileText = QtGui.QLabel()
    self.fileText.setText("Choose file to load:")

    self.pathUser = self.pathData + 'subj_' + self.username + "/"

    if os.path.isdir(self.pathUser):
      self.all_pos_files = [f for f in os.listdir(self.pathUser) if f.startswith('positions')]
      self.all_pos_files.sort(key=lambda f: int(re.sub('\D', '', f)))
      for file in self.all_pos_files:
        self.positionFileMenu.addItem(file)
    else:
      self.positionFileMenu.addItem("No files to show")

    self.setNewThresholds = QtGui.QPushButton("Set thresholds as current")

    layout.addWidget(self.userText,0,0,1,1)
    layout.addWidget(self.userEdit,0,1,1,3)
    layout.addWidget(self.positionFileMenu,1,0,1,4)
    layout.addWidget(self.dist_pg,2,0,1,4)
    layout.addWidget(self.StdMultiplierText,6,0,1,1)
    layout.addWidget(self.stdMultiplierSpinbox,6,1,1,1)
    layout.addWidget(self.StdMultiplierBaseText,7,0,1,1)
    layout.addWidget(self.stdMultiplierBaseSpinbox,7,1,1,1)
    layout.addWidget(self.setNewThresholds,6,3,1,1)
    layout.addWidget(self.showThresholdsCheckbox,6,2,1,1)
    self.stdMultiplierSpinbox.valueChanged.connect(self.changeStdMultiplier)
    self.stdMultiplierBaseSpinbox.valueChanged.connect(self.changeStdBaseMultiplier)

    self.changedPositionFile()

    self.tabDist.setLayout(layout)
    self.userEdit.textEdited.connect(self.user_change)

    self.positionFileMenu.currentIndexChanged.connect(self.changedPositionFile)
    self.setNewThresholds.clicked.connect(self.setThresholdsInFile)

  def launchGame(self):
    try:
      self.thread.terminate()
      self.app_after_exit = 1
      self.close()

    except:
      #self.thread.setDecoderAcquisition()
      print("No Serial connection")

  def toggleAcqEegData(self, state):
    if state == QtCore.Qt.Checked:
      self.stateThresholds = 1
      self.showThresholds()

    elif state != QtCore.Qt.Checked:
      self.stateThresholds = 0
      try:
        self.dist_pg.removeItem(self.mean_vert_current)
        self.dist_pg.removeItem(self.target_up_current)
        self.dist_pg.removeItem(self.target_down_current)
        self.dist_pg.removeItem(self.base_up_current)
        self.dist_pg.removeItem(self.base_down_current)
      except:
        pass

  def gameTabUI(self):
    layout = QtGui.QVBoxLayout()

    self.userText = QtGui.QLabel()
    self.userText.setText("Username:")
    self.userEditGame = QtGui.QLineEdit()
    self.userEditGame.setText(str(self.username))

    self.launchGameBtn = QtGui.QPushButton("Launch Game")

    layout.addWidget(self.userText)
    layout.addWidget(self.userEditGame)
    layout.addWidget(self.launchGameBtn)

    layout.addSpacing(800)

    self.tabGame.setLayout(layout)
    self.userEditGame.textEdited.connect(self.user_change_game)
    self.launchGameBtn.clicked.connect(self.launchGame)

  def connectTabUI(self):
    layout = QtGui.QVBoxLayout()

    self.baudtext = QtGui.QLabel()
    self.baudtext.setText("Baudrate")
    self.baudedit = QtGui.QLineEdit()
    self.baudedit.setText(str(self.serialBaud))
    self.baudedit.setValidator(QtGui.QIntValidator())

    self.porttext = QtGui.QLabel()
    self.porttext.setText("COM Port")
    self.portedit = QtGui.QLineEdit()
    self.portedit.setText(self.serialPort)
    self.connectbutton = QtGui.QPushButton("Connect")
    self.disconnectbutton = QtGui.QPushButton("Disconnect")
    #self.testButton = QtGui.QPushButton("test")
    self.rawAqButton = QtGui.QPushButton("raw Acquisition")
    self.decoderAqButton = QtGui.QPushButton("decoder Acquisition")
    self.uploadBandpassButton = QtGui.QPushButton("Upload Bandpass")
    self.enableUserBandpassButton = QtGui.QPushButton("Enable User Bandpass")
    self.uploadNotchButton = QtGui.QPushButton("Upload Notch")
    self.enableUserNotchButton = QtGui.QPushButton("Enable User Notch")

    self.rawAqButton.setEnabled(False)
    self.decoderAqButton.setEnabled(False)
    self.enableUserBandpassButton.setEnabled(False)
    self.enableUserNotchButton.setEnabled(False)
    self.disconnectbutton.setEnabled(False)
    self.uploadBandpassButton.setEnabled(False)
    self.uploadNotchButton.setEnabled(False)
    layout.addWidget(self.baudtext)
    layout.addWidget(self.baudedit)
    layout.addWidget(self.porttext)
    layout.addWidget(self.portedit)
    layout.addWidget(self.connectbutton)
    layout.addWidget(self.disconnectbutton)
    #layout.addWidget(self.testButton)
    layout.addWidget(self.rawAqButton)
    layout.addWidget(self.decoderAqButton)

    layout.addWidget(self.uploadBandpassButton)
    layout.addWidget(self.enableUserBandpassButton)

    layout.addWidget(self.uploadNotchButton)
    layout.addWidget(self.enableUserNotchButton)
    layout.addSpacing(800)
    self.tabConnect.setLayout(layout)

  def onSerialConnect(self,enabled):

    self.rawAqButton.setEnabled(enabled)
    self.decoderAqButton.setEnabled(enabled)
    self.disconnectbutton.setEnabled(enabled)
    self.uploadBandpassButton.setEnabled(enabled)
    self.uploadNotchButton.setEnabled(enabled)
    self.serialEnabled = enabled
    #if not enabled:
      #self.setCurrentIndex(self.eegTabIndex-1)
      #self.serialEnabled = True

  def port_change(self):
    self.serialPort = self.portedit.text()

  def baud_change(self):
    self.serialBaud = int(self.baudedit.text())

  def user_change(self):
    self.username = self.userEdit.text()

    self.positionFileMenu.clear()

    self.pathUser = self.pathData + 'subj_' + self.username + "/"

    if os.path.isdir(self.pathUser):
      self.all_pos_files = [f for f in os.listdir(self.pathUser) if f.startswith('positions')]
      self.all_pos_files.sort(key=lambda f: int(re.sub('\D', '', f)))
      for file in self.all_pos_files:
        self.positionFileMenu.addItem(file)
    else:
      self.positionFileMenu.addItem("No files to show")

  def user_change_game(self):
    self.usernameGame = self.userEditGame.text()

  def setThresholdsInFile(self):

    self.pathUser = self.pathData + 'subj_' + self.username + "/"
    print(self.pathUser)
    if self.realthresholds != {}:
      g = open( self.pathUser +'thresholds.txt', 'r')
      thresholds_old = eval(g.read())
      # print(thresholds_old)
      g.close()

      f = open(self.pathUser + 'thresholds_old.txt', 'w')
      f.write(str(thresholds_old))
      f.close()
      g = open( self.pathUser +'thresholds.txt', 'w')
      g.write(str(self.realthresholds))
      g.close()
      self.showThresholds()
    else:
      print('Cannot write file because there are no thresholds')

  def start_serial(self):
    serial = serialReceiver(self.serialBaud, str(self.serialPort), self.serialEnabled, self.dataBuffer1,
     self.dataBuffer2, self.writeVal, self.accelBufferX)
    serial.newSample.connect(self.updateGraphs)
    serial.serialConn.connect(self.onSerialConnect)
    self.thread = serial
    serial.start()
    self.thread.serialConnect()

  def stop_serial(self):
    self.thread.disconnect()
    self.disconnectbutton.setEnabled(False)
    #serial.newSample.connect(self.updateGraphs)
    #serial.serialConn.connect(self.onSerialConnect)
    #serial.start()

  def keyPressEvent(self, event):
    # Did the user press the Escape key?
    if event.key() == QtCore.Qt.Key_Escape: # QtCore.Qt.Key_Escape is a value that equates to what the operating system passes to python from the keyboard when the escape key is pressed.
      # Yes: Close the window
      self.app_after_exit = 0
      self.close()

def main():
  app = QtGui.QApplication(sys.argv)
  ex = tabdemo()
  ex.show()
  sys.exit(app.exec_())

if __name__ == '__main__':
  main()


# -*- coding: utf-8 -*-
# @File    : train.py
# @Author  : AaronJny
# @Time    : 2020/03/13
# @Desc    :
import os
import numpy as np
from tqdm import tqdm
import tensorflow as tf
from model import NeuralStyleTransferModel
import settings
import utils

# 创建模型
model = NeuralStyleTransferModel()

# 加载内容图片
content_image = utils.load_images(settings.CONTENT_IMAGE_PATH)
# 风格图片
style_image = utils.load_images(settings.STYLE_IMAGE_PATH)

# 计算出目标内容图片的内容特征备用
target_content_features = model([content_image, ])['content']
# 计算目标风格图片的风格特征
target_style_features = model([style_image, ])['style']

M = settings.WIDTH * settings.HEIGHT
N = 3

def _compute_content_loss(noise_features, target_features):
    """
    计算指定层上两个特征之间的内容loss
    :param noise_features: 噪声图片在指定层的特征
    :param target_features: 内容图片在指定层的特征
    """
    content_loss = tf.reduce_sum(tf.square(noise_features - target_features))
    # 计算系数
    x = 2. * M * N
    content_loss = content_loss / x
    return content_loss

def compute_content_loss(noise_content_features):
    """
    计算并当前图片的内容loss
    :param noise_content_features: 噪声图片的内容特征
    """
    # 初始化内容损失
    content_losses = []
    # 加权计算内容损失
    for (noise_feature, factor), (target_feature, _) in zip(noise_content_features, target_content_features):
        layer_content_loss = _compute_content_loss(noise_feature, target_feature)
        content_losses.append(layer_content_loss * factor)
    return tf.reduce_sum(content_losses)

def gram_matrix(feature):
    """
    计算给定特征的格拉姆矩阵
    """
    # 先交换维度，把channel维度提到最前面
    x = tf.transpose(feature, perm=[2, 0, 1])
    # reshape，压缩成2d
    x = tf.reshape(x, (x.shape[0], -1))
    # 计算x和x的逆的乘积
    return x @ tf.transpose(x)

def _compute_style_loss(noise_feature, target_feature):
    """
    计算指定层上两个特征之间的风格loss
    :param noise_feature: 噪声图片在指定层的特征
    :param target_feature: 风格图片在指定层的特征
    """
    noise_gram_matrix = gram_matrix(noise_feature)
    style_gram_matrix = gram_matrix(target_feature)
    style_loss = tf.reduce_sum(tf.square(noise_gram_matrix - style_gram_matrix))
    # 计算系数
    x = 4. * (M ** 2) * (N ** 2)
    return style_loss / x

def compute_style_loss(noise_style_features):
    """
    计算并返回图片的风格loss
    :param noise_style_features: 噪声图片的风格特征
    """
    style_losses = []
    for (noise_feature, factor), (target_feature, _) in zip(noise_style_features, target_style_features):
        layer_style_loss = _compute_style_loss(noise_feature, target_feature)
        style_losses.append(layer_style_loss * factor)
    return tf.reduce_sum(style_losses)

def total_loss(noise_features):
    """
    计算总损失
    :param noise_features: 噪声图片特征数据
    """
    content_loss = compute_content_loss(noise_features['content'])
    style_loss = compute_style_loss(noise_features['style'])
    return content_loss * settings.CONTENT_LOSS_FACTOR + style_loss * settings.STYLE_LOSS_FACTOR

# 使用Adma优化器
optimizer = tf.keras.optimizers.Adam(settings.LEARNING_RATE)

# 基于内容图片随机生成一张噪声图片
noise_image = tf.Variable((content_image + np.random.uniform(-0.2, 0.2, (1, settings.HEIGHT, settings.WIDTH, 3))) / 2)

# 使用tf.function加速训练
@tf.function
def train_one_step():
    """
    一次迭代过程
    """
    # 求loss
    with tf.GradientTape() as tape:
        noise_outputs = model(noise_image)
        loss = total_loss(noise_outputs)
    # 求梯度
    grad = tape.gradient(loss, noise_image)
    # 梯度下降，更新噪声图片
    optimizer.apply_gradients([(grad, noise_image)])
    return loss

# 创建保存生成图片的文件夹
if not os.path.exists(settings.OUTPUT_DIR):
    os.mkdir(settings.OUTPUT_DIR)

# 共训练settings.EPOCHS个epochs
for epoch in range(settings.EPOCHS):
    # 使用tqdm提示训练进度
    with tqdm(total=settings.STEPS_PER_EPOCH, desc='Epoch {}/{}'.format(epoch + 1, settings.EPOCHS)) as pbar:
        # 每个epoch训练settings.STEPS_PER_EPOCH次
        for step in range(settings.STEPS_PER_EPOCH):
            _loss = train_one_step()
            pbar.set_postfix({'loss': '%.4f' % float(_loss)})
            pbar.update(1)
        # 每个epoch保存一次图片
        utils.save_image(noise_image, '{}/{}.jpg'.format(settings.OUTPUT_DIR, epoch + 1))


from utils import FirestoreHandler, BigQueryHandler
from datetime import datetime
import sys
import json

date = datetime.today().strftime('%Y-%m-%d')

def main (request):
    config = None

    config = json.loads(open("bot_config_local.json").read()) if "-local" in sys.argv else json.loads(open("bot_config.json").read())

    FH = FirestoreHandler()
    docs = FH.get_documents(
        collection='bot_data'
    )

    doc_ids_separated_by_date = {}
    data_separated_by_date = {}
    schema = []

    for doc in docs:

        doc_dict = doc.to_dict()

        # create a date on the data list
        if doc_dict["location_date"] not in data_separated_by_date:
            data_separated_by_date[doc_dict["location_date"]] = []
            doc_ids_separated_by_date[doc_dict["location_date"]] = []

        doc_ids_separated_by_date[doc_dict["location_date"]].append(doc.id)

        # inserts in the Big Query Schema order
        data_separated_by_date[doc_dict["location_date"]].append({
            "location_date": doc_dict["location_date"],
            "user_id_hash": doc_dict["user_id_hash"],
            "location_timestamp_ms": doc_dict["location_timestamp_ms"],
            "lat_long": doc_dict["lat_long"],
            "photo_date": doc_dict["photo_date"],
            "photo_timestamp_ms": doc_dict["photo_timestamp_ms"],
            "is_flood": doc_dict["is_flood"],
            "water_level": doc_dict["water_level"],
            "water_level_case": doc_dict["water_level_case"]
        })

    # Checks if it's empty
    if not bool(data_separated_by_date):
        print("No data to upload into bq")
        return

    # Starts BQ Handler, insert data and checks if data is upload successfuly
    BQ = BigQueryHandler(config)
    for date in data_separated_by_date:
        success = BQ.insert_data(data=data_separated_by_date[date], table_type="bot_data_fs",table_date=date)

        if success == True:
            # Update fs_state to sent to bq for all docs
            # FH.set_sent_to_bq_fs_state(collection='bot_data', doc_ids=doc_ids_separated_by_date[date])
            FH.delete_documents(collection='bot_data', doc_ids=doc_ids_separated_by_date[date])
            print(f"Done. Data from date {date} was sent to Big Query")
        else:
            print(f"Failed. Data from date {date} wasn't sent to Big Query")

    return '', 201

if "-local" in sys.argv:
    main(None)


import datetime
import os
import argparse
import codecs
from google.cloud import speech_v1p1beta1
from google.cloud.speech_v1p1beta1 import enums
from google.cloud.speech_v1p1beta1 import types

def transcribe_gcs(gcs_uri, hint_phrases, set_config):
    """Asynchronously transcribes the audio file specified by the gcs_uri."""
    client = speech_v1p1beta1.SpeechClient()

    audio = types.RecognitionAudio(uri=gcs_uri)

    # hint_phrase = []
    # set_config['enable_speaker_diarization'] = 'False'

    print(set_config.get('enable_automatic_punctuation'))
    # Set default values, check dict having each key and cast from str to each type.
    config = types.RecognitionConfig(
        encoding=eval(set_config.get('encoding', 'enums.RecognitionConfig.AudioEncoding.FLAC')),
        sample_rate_hertz=int(set_config.get('sample_rate_hertz', 16000)),
        language_code=set_config.get('language_code', 'en-US'),
        enable_automatic_punctuation=eval(set_config.get('enable_automatic_punctuation', True)),
        enable_speaker_diarization=eval(set_config.get('enable_speaker_diarization', False)),
        diarization_speaker_count=int(set_config.get('diarization_speaker_count', 1)),
        speech_contexts=[speech_v1p1beta1.types.SpeechContext(phrases=hint_phrases)])

    operation = client.long_running_recognize(config, audio)

    print('Waiting for operation to complete...')
    response = operation.result(timeout=900)
    return response

def get_stringlist_from_file(path):
    str_list = []
    f = open(path, 'r')
    for line in f:
        str_list.append(line.rstrip())
    f.close()
    return str_list

def get_hashmap_from_file(path):
    hashmap = {}
    f = open(path, 'r')
    for line in f:
        tmp = line.rstrip().split("=")
        hashmap[tmp[0]] = tmp[1]
    f.close()
    return hashmap

def get_transcription_from_response(response, is_compare = False):
    result_max_index = len(response.results) - 1

    timestamp = datetime.datetime.today().strftime("%Y%m%d-%H%M%S")
    fout = codecs.open('transcribe{}.txt'.format(timestamp), 'a', 'UTF-8')

    # Each result is for a consecutive portion of the audio. Iterate through
    # them to get the transcripts for the entire audio file.
    if is_compare:
        for index, result in enumerate(response.results):
            # The first alternative is the most likely one for this portion.
            alternative = result.alternatives[0]
            # Sometime transcript added "." even if enable_speaker_punctuation=False.
            fout.write(u'{}'.format(alternative.transcript.rstrip(".")))
        fout.close()
    else:
        for index, result in enumerate(response.results):
            # The first alternative is the most likely one for this portion.
            alternative = result.alternatives[0]
            fout.write(u'Transcript: {}\n'.format(alternative.transcript))
            fout.write('Confidence: {}\n'.format(alternative.confidence))
            if index == result_max_index:
                # When last loop, output speaker_tag with each word
                for word_info in alternative.words:
                    fout.write('Speaker: {}, '.format(word_info.speaker_tag))
                    fout.write(u'Word: {}\n'.format(word_info.word))
        fout.close()
    return 0

if __name__ == '__main__':
    abs_dirpath = os.path.abspath(os.path.dirname(__file__))
    # Setting of command-line parameters
    parser = argparse.ArgumentParser()
    parser.add_argument('gspath', help='A Google Cloud Storage path with .flac file')
    parser.add_argument('--compare', '-c', action="store_true", help='Output mode (Just result or add confidence, speaker_tag, and so on.)')

    HINT_FILE = os.path.join(abs_dirpath, '../resources/hint_list')
    print(HINT_FILE)
    hints = []
    if os.path.isfile(HINT_FILE):
        hints = get_stringlist_from_file(HINT_FILE)

    CONFIG_FILE = os.path.join(abs_dirpath, '../resources/recognition_config')

    config = {}
    if os.path.isfile(CONFIG_FILE):
        config = get_hashmap_from_file(CONFIG_FILE)

    is_compare = None
    if parser.parse_args().compare:
        is_compare = True
    else:
        is_compare = False

    get_transcription_from_response(transcribe_gcs(parser.parse_args().gspath, hints, config), is_compare)


from microprediction.config_private import EMBLOSSOM_MOTH as WRITE_KEY  # Burn your own with new_key(difficulty=13)
from microprediction.polling import MultiChangePoll
from pycoingecko import CoinGeckoAPI
import math
from pprint import pprint
import numpy as np
import time

# This is the actual code used to generate the 'c2' series
# Everything is driven off changes in bitcoin and ethereum prices
from microprediction import MicroWriter

COINS = 'bitcoin,ethereum'
C2_NAMES = ['c2_change_in_log_' + name + '.json' for name in COINS.split(',')]
WEIGHTS = [0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]

mw = MicroWriter(write_key=WRITE_KEY)
mw.set_repository('https://github.com/microprediction/microprediction/tree/master/stream_examples_crypto')

def func():
    """ Returns five large cap coins in USD """
    cg   = CoinGeckoAPI()
    data = cg.get_price(ids=COINS, vs_currencies='usd')
    pprint(data)
    raw  = [ data[coin]['usd'] for coin in COINS.split(',') ]
    return [ 1000*math.log(v) for v in raw ]

def quadratic_moments(changes):
    up_changes = [ c if c>0 else 0.1*c for c in changes ]
    dn_changes = [ c if c<0 else 0.1*c for c in changes ]
    dx = np.array([ changes ] )
    dxu = np.array([ up_changes ] )
    dxl = np.array([ dn_changes ] )
    dxx = np.matmul(np.transpose(dx),dx)
    dxxu = np.matmul(np.transpose(dxu), dxu)
    dxxl = np.matmul(np.transpose(dxl), dxl)
    print(dxx)
    n_dim = len(C2_NAMES)
    values = list()
    for i in range(n_dim):
        for j in range(i,n_dim):
            values.append(dxx[i,j]) # full
            values.append(dxxl[i, j])  # lower
            values.append(dxxu[i, j])  # upper
    assert len(values)==len(quadratic_moments_names())
    return values

def quadratic_moments_names():
    short_names = COINS.split(',')
    typs = ['full','lower','upper']
    names = list()
    n_dim = len(C2_NAMES)
    for i in range(n_dim):
        for j in range(i,n_dim):
            for typ in typs:
                nm = 'c2_quadratic_' + typ + '_' + short_names[i]+'_'+short_names[j]+'.json'
                names.append( nm )
    return names

def get_portfolio_changes(changes):
    values = list()
    for W in WEIGHTS:
        w0 = W/100.
        w1 = 1.0-w0
        change = 1000.*math.log( w0*math.exp(changes[0]/1000.) + w1*math.exp(changes[1]/1000.) )
        values.append(change)
    assert len(values)==len(portfolio_changes_names())
    return values

def portfolio_changes_names():
    return ['c2_rebalanced_'+str(W)+'.json' for W in WEIGHTS ]

def get_mean_quadratic_predictions():
    values = list()
    for name in quadratic_moments_names():
        if 'lower' in name:
            try:
                all_predictions = mw.get_own_predictions(name=name, delay=mw.DELAYS[-2],strip=True, consolidate=True )
                trimmed_values = [ v if abs(v)<100. else 100*v/abs(v) for v in all_predictions]
                n_chop = int(math.ceil(len(trimmed_values))/20)
                values.append( np.mean(trimmed_values[n_chop:-n_chop]) )
            except:
                print('No predictions yet or wrong key?')
                values.append(0)
    return values

def quadratic_prediction_names():
    return [nm.replace('lower_','lower_mean_') for nm in quadratic_moments_names() if 'lower' in nm]

# Portfolios based on hypothetical portfolio predictions

def get_portfolio_predictions():
    the_means = list()
    the_stds = list()
    for name in portfolio_changes_names():
        try:
            all_predictions = mw.get_own_predictions(name=name, delay=mw.DELAYS[2], strip=True, consolidate=True)
            trimmed_values = [v if abs(v) < 100. else 100 * v / abs(v) for v in all_predictions]
            n_chop = int(math.ceil(len(trimmed_values)) / 50)
            chopped = trimmed_values[n_chop:-n_chop]
            the_means.append(np.mean(chopped))
            the_stds.append(np.std(chopped))
        except AttributeError:
            print('Um...')
            the_means.append(0)
            the_stds.append(1)
    assert len(the_means)+len(the_stds)==len(portfolio_prediction_names())
    return the_means, the_stds

def portfolio_prediction_names():
    mean_portfolio = [nm.replace('.json', '_mean.json') for nm in portfolio_changes_names()]
    std_portfolio = [nm.replace('.json', '_std.json') for nm in portfolio_changes_names()]
    return mean_portfolio + std_portfolio

def get_best_weights_from_portfolio_predictions(mean_p,std_p):
    """ Pre-pends the normalized changes and predictions """
    info_p = [ numer/denom for numer, denom in zip(mean_p,std_p)]
    max_info = max(info_p)
    good_w = [ (math.exp(-5.0*abs(i_-max_info)),w) for w,i_ in zip(WEIGHTS,info_p) if i_>max_info-0.1 ]
    good_w_sum = sum( [m_ for m_,w_ in good_w ])
    good_w_wsum = sum( [m_*w_ for m_,w_ in good_w])
    w_best = good_w_wsum/good_w_sum

    # Compute ratio of weighted portfolio returns to the fixed weight portfolios

    def _low_high(offset):
        """ Represent a float offset as combination of two discrete ones """
        l = math.floor(offset)
        u = math.ceil(offset)
        r = offset - l
        return (l, 1 - r), (u, r)

    (l,lw), (h, hw) = _low_high(w_best / 5.0)
    low_per = int(5*l)
    high_per = int(5*h)
    low_per_index = WEIGHTS.index(low_per)
    high_per_index = WEIGHTS.index(high_per)

    return low_per_index, lw, high_per_index, hw, w_best

def best_info_portfolio_changes(mean_p, std_p, portfolio_changes):
    low_per_index, lw, high_per_index, hw, w_best = get_best_weights_from_portfolio_predictions(mean_p=mean_p, std_p=std_p)
    w_best_change = lw * portfolio_changes[low_per_index] + hw * portfolio_changes[high_per_index]
    w_best_diff = [w_best_change - pc for pc in portfolio_changes]
    values = [w_best_change, w_best, 100 - w_best] + w_best_diff
    assert len(values)==len(best_info_portfolio_names())
    return values

def best_info_portfolio_names():
    diff_portfolio = [nm.replace('rebalanced', 'info_minus_rebalanced') for nm in portfolio_changes_names()]
    return ['c2_info_return.json', 'c2_info_percent_bitcoin.json', 'c2_info_percent_ethereum.json'] + diff_portfolio

# Combine what we need into a change_func that will be triggered at the same time

def change_func(changes):
    portfolio_changes = get_portfolio_changes(changes)
    mean_p, std_p = get_portfolio_predictions()
    best_changes = best_info_portfolio_changes(mean_p=mean_p, std_p=std_p, portfolio_changes=portfolio_changes)
    q_moments = list( quadratic_moments(changes) )
    return changes + q_moments + list(portfolio_changes) + list(get_mean_quadratic_predictions()) + list(best_changes) + list(mean_p) + list(std_p)

def change_names():
    return quadratic_moments_names() + portfolio_changes_names() + quadratic_prediction_names() + best_info_portfolio_names() + portfolio_prediction_names()

if __name__=="__main__":
    print('testing',flush=True)
    f1 = func()
    time.sleep(70)
    f2 = func()
    changes = [ f2i-f1i for f1i, f2i in zip(f1,f2) ]
    cf = change_func(changes)

    print('starting',flush=True)
    names = C2_NAMES + change_names()
    assert len(f1)==len(C2_NAMES)
    assert len(cf)==len(names)

    pprint(dict(zip(names, list(changes)+list(cf))))

    poll = MultiChangePoll(write_key=WRITE_KEY, func=func, names=names, interval=10, with_copulas=False, verbose=True, change_func=change_func)

    # Attribution

    poll.run()


"""GRAFIMO redefinition of ArgumentParser and HelpFormatter

The ArgumentParser and HelpFormatter classes are customized to
assign a different style to the help with respect to the default
one and when a wrong argument is given GRAFIMO reminds the user
how to call the command-line help.
"""

from grafimo.grafimo import __version__

from argparse import SUPPRESS, ArgumentParser, HelpFormatter
from typing import Dict, Optional, Tuple
from colorama import Fore

import sys

class GRAFIMOArgumentParser(ArgumentParser):
    """
    This class redefines the ArgumentParser class from argparse module.

    The redefinition is required to change the argparse default definition
    of the help. So, when raising an error by argument parser will be
    shown the new GRAFIMO message

    ...

    Methods
    -------
    error(msg)
        Prints a new message instead of argparse deafault one when an error
        is raised parsing the arguments

    """

    class GRAFIMOHelpFormatter(HelpFormatter):
        """
        This class defines a new help format for GRAFIMO

        ...

        Methods
        -------
        add_usage(usage, actions, groups, prefix=None)
            style for GRAFIMO help
        """
        #---------------------------------------------------------------
        # GRAFIMOHelpFormatter methods
        #---------------------------------------------------------------
        def add_usage(
            self,
            usage: str,
            actions: str,
            groups: str,
            prefix: Optional[str] = 'None'
        ) -> None:

            if usage is not SUPPRESS:
                args = usage, actions, groups, ''
                self._add_item(self._format_usage, args)

    # end of GRAFIMOHelpFormatter

    #-------------------------------------------------------------------
    # GRAFIMOArgumentParser methods
    #-------------------------------------------------------------------
    def __init__(
        self,
        *args: Tuple,
        **kwargs: Dict
    ):
        """
        Parameters
        ----------
        args : tuple
            Arguments to initialize the ArgumentParser object
        kwargs : dict
            Help format restyling arguments
        """

        kwargs['formatter_class'] = self.GRAFIMOHelpFormatter
        kwargs['usage'] = kwargs['usage'].replace("{version}", __version__)
        super().__init__(*args, **kwargs)

    def error(
        self,
        msg: str
    ) -> None:
        """Print the given message when argparse raise an error.

        At the bottom is also printed a new message telling the
        user how to call the help.

        ...

        Parameters
        ----------
        msg : str
            Message to show when raising the error.

        Returns
        -------
        None
        """

        errmsg = Fore.RED + "\nERROR: " +  "{}." + Fore.RESET + "\n\nRun \"grafimo --help\" to see usage\n\n"
        sys.stderr.write(errmsg.format(msg))
        sys.exit(2)

    def error_noargs(
        self
    ) -> None:
        """Print the given message when argparse raise an error.

        This error message s raised only when no arguments is given when
        calling grafimo from command line. Prints the general help
        message.

        ...

        Parameters
        ----------
        None

        Returns
        -------
        None
        """

        self.print_help()
        sys.exit(2)

# end of GRAFIMOArgumentParser


# -*- coding: utf-8 -*-

from flask import Flask, render_template, session, redirect, url_for, flash
from flask.ext.bootstrap import Bootstrap
from flask.ext.moment import Moment
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask.ext.sqlalchemy import SQLAlchemy
import os
from flask.ext.script import Manager, Shell
from flask.ext.migrate import Migrate, MigrateCommand
from flask.ext.mail import Message, Mail
from threading import Thread

basedir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__) # <Flask 'hello'>
app.config['SECRET_KEY'] = "hei hei hei"
app.config['SQLALCHEMY_DATABASE_URI'] = \
        'sqlite:///' + os.path.join(basedir, 'data.sqlite')
# 每次请求结束后自动提交数据库的变动
app.config['SQLACHEMY_COMMIT_ON_TEARDOWN'] = True
#  UserWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant
#  overhead and will be disabled by default in the future.  Set it
# to True to suppress this warning.

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True

app.config['MAIL_SERVER'] = 'smtp.163.com'
app.config['MAIL_PORT'] = 25
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = 'tests@163.com'
app.config['MAIL_PASSWORD'] = 'tests'
app.config['FLASKY_MAIL_SUBJECT_FREFIX'] = '[Flasky]'
app.config['FLASKY_ADMIN'] = 'tests@163.com'
app.config['FLASKY_MAIL_SENDER'] = 'tests@163.com'

db = SQLAlchemy(app)
bootstrap = Bootstrap(app)
moment = Moment(app)
manager = Manager(app)
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
mail = Mail(app)

class NameForm(Form):
    name = StringField('What is your name?', validators=[DataRequired()])
    submit = SubmitField("Submit")

class Role(db.Model):
    # "__tablename__"定义数据库中的表名，若不定义则使用SQLAlchemy的默认名。
    __tablename__ = 'roles'
    # Flask-SQLAlchemy要求每个模型都定义主键
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    # 'users'属性代表这个关系的面向对象视角。对于一个Role类的实例，其users \
    # 属性将返回与角色关联的用户组成的列表。db.relationship的第一个参数 \
    # 'User'表明这个关系的另一端的模型是'User’。
    # backref参数像'User'模型中添加一个'role'属性，从而定义反向关系。这一 \
    # 属性可替代role_id访问Role模型，此时获取的是模型对象，而不是外键的值。
    users = db.relationship('User', backref='role', lazy='dynamic')

    # 注意这个'__repr__()'方法，常与'__str__()'方法比较
    def __repr__(self):
        return '<Role %r>' % self.name

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    # 'index'设置为True，将为此列创建索引，提升查询效率。
    username = db.Column(db.String(64), unique=True, index=True)
    # 'role_id'列被定义为外键，传给db.ForeignKey()的参数'roles.id'表明 \
    # 此列的值是'roles'表中行的id值。
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    def __repr__(self):
        return '<User %r>' % self.username

@app.route('/', methods=["GET", "POST"])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username=form.name.data)
            db.session.add(user)
            session['known'] = False
            if app.config['FLASKY_ADMIN']:
                send_email(app.config['FLASKY_ADMIN'], 'New User',
                           'new_user', user=user)
        else:
            session['known'] = True
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('index.html',
                           form = form, name = session.get('name'),
                           known = session.get('known', False))

def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)

def send_email(to, subject, template, **kwargs):
    msg = Message(app.config['FLASKY_MAIL_SUBJECT_FREFIX'] + subject,
                  sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
    msg.body = render_template(template + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    thr = Thread(target=send_async_email, args=[app, msg])
    thr.start()
    return thr

@app.route('/user/<name>')
def user(name):
    return render_template("user.html", username=name)

@app.errorhandler(404)
def page_not_found(e):
    return render_template("404.html"), 404

@app.errorhandler(500)
def internal_server_error(e):
    return render_template("500.html"), 500

def make_shell_context():
    return dict(app=app, db=db, User=User, Role=Role)

if __name__ == "__main__":
    app.debug = True
    manager.add_command("shell", Shell(make_context=make_shell_context))
    manager.run()