import random
import math
import numpy as np
from gurobipy import *
import sys
from enum import Enum
from scipy import stats

### Enumerate experimenting methods

class Methods(Enum):
    BAYES = "Bayes Simple"
    CENTROID = "Mean Transition Probability"
    HOEFF = "Hoeffding"
    HOEFFTIGHT = "Hoeffding Tight"
    EM = "Expectation Maximization"
    INCR_REPLACE_V = "Incrementally Replace Value Function"
    INCR_ADD_V = "Incrementally Add Value Function"
    #For single state-action, KNOWNV is the same as INCR_ADD_V

    NUM_METHODS = 7 #Number of methods

LI_METHODS = [Methods.BAYES, Methods.CENTROID, Methods.HOEFF, Methods.HOEFFTIGHT, Methods.EM,\
                Methods.INCR_REPLACE_V, Methods.INCR_ADD_V]

###Dirichlet Threshold
def compute_bayesian_threshold(points, nominal_point, confidence_level):
    """
    Computes an empirical thresholds from samples from a posterior distriL1 threshold'bution
    Must adjust the confidence level when the MDP has more states
    and actions using the union bound
    """
    assert(abs(np.sum(nominal_point) - 1) < 0.001)
    for p in points:
        assert(abs(np.sum(p) - 1) < 0.001)

    distances = [np.linalg.norm(p - nominal_point, ord = 1) for p in points]
    confidence_rank = math.ceil(len(points) * confidence_level)
    dist = np.partition(distances, confidence_rank)[confidence_rank]

    return dist

###Process Gaussian distribution
def discretize_gaussian(min_value, max_value, mean, std):
    """
    Computes a discrete approximation of the Gaussian distribution. The distribution
    is bounded by min_value and max_value.
    Both bounds are inclusive.
    """
    d = stats.norm(mean, std)
    dist = np.array([d.pdf(v) for v in range(min_value, max_value + 1)])
    # normalize it
    dist /= np.sum(dist)
    return dist

def normal_aposteriori(values, weights, std, prior_mean, prior_std):
    """
    Estimates the aposteriori Gaussian distribution:
    see: https://en.wikipedia.org/wiki/Conjugate_prior#Continuous_distributions

    Assumes that the standard deviation of the demands is known but the
    mean is distributed according to the prior Gaussian distribution. std is the
    standard deviation of the demand (known).

    The parameter weights represents an un-normalized weight on each value.
    """
    n = np.sum(weights)
    sum = np.dot(weights, values)
    precision = (1 / prior_std**2 + n / std**2)
    expected_mean = (prior_mean / prior_std**2 + sum / std**2) / precision
    expected_std = 1/precision
    return expected_mean, expected_std

print(discretize_gaussian(0, 10, 3, 2))

### Apply Linear Program to compute the nominal point & apply quick select to find the uncertainty set with the required confidence
def get_uset(points, nominal_point, conf_rank):
    """
    Sort the points based on the distance from nominal point in ascending order.
    Taking N*confidence will  give the uncertainty set with nearest points

    @param conf_rank: how many points to take
    """

    distances = [( p, np.linalg.norm(p - nominal_point, ord = 1)) for p in points]
    distances.sort(key = lambda x : x[1])
    res_list = np.asarray([distances[i][0] for i in range(conf_rank)])

    if res_list.shape[0]>1:
        res_list = np.squeeze(res_list)

    psi = distances[conf_rank-1][1]
    return res_list, psi

def find_nominal_point(p):
    """
    Find nominal point for the uncertainty set using LP
    """
    num_p = p.shape[0]
    num_d = p.shape[1]
    m = Model("nominal")
    u = m.addVar(name="u", lb=0)

    y = m.addVars(range(num_p*num_d), vtype=GRB.CONTINUOUS, obj=0.0, name="y")
    # new nominal point
    beta = m.addVars(range(num_d), vtype=GRB.CONTINUOUS, obj=0.0, name="beta", lb=0)

    m.setObjective(u, GRB.MINIMIZE)

    for i in range(num_p):
        m.addConstr(u, GRB.GREATER_EQUAL, quicksum(y[i*num_d+j] for j in range(num_d)), "u_"+str(i))

    for i in range(num_p):
        for j in range(num_d):
            m.addConstr(y[i*num_d+j], GRB.GREATER_EQUAL, p[i,j]-beta[j], "c1"+str(i))
            m.addConstr(y[i*num_d+j], GRB.GREATER_EQUAL, beta[j]-p[i,j], "c2"+str(i))

    m.setParam( 'OutputFlag', False )
    m.optimize()

    #print('Obj: %g' % m.objVal)

    #for v in m.getVars():
    #    print('%s %g' % (v.varName, v.x))

    threshold = 0
    for v in m.getVars():
        if v.varName == "u":
            threshold = v.x
            break

    nominal_params = m.getAttr('x', beta)

    nominal_p = []
    for i in range(num_d):
        nominal_p.append(nominal_params[i])

    return nominal_p, threshold#tuple(nominal_p)

### Test EM
if __name__ == "__main__":
    num_points = 100
    confidence = 0.8
    confidence_rank = math.ceil(num_points * confidence)
    x = np.random.rand(num_points,1)*100
    y = np.random.rand(num_points,1)*100
    points = np.squeeze(np.stack((x,y),axis=1))

    num_iter = 20
    nominal_list = []
    psi_list = []

    rand_nominal_pos = np.random.randint(num_points)

    nominal_p = points[rand_nominal_pos]
    print("rand_nominal_pos",rand_nominal_pos,"nominal_p",nominal_p)
    prev_psi = sys.float_info.max
    for i in range(num_iter):
        nominal_list.append(nominal_p)
        u_set, psi = get_uset(points, nominal_p, confidence_rank)

        assert(psi <= prev_psi + 1e-5)

        prev_psi = psi
        psi_list.append(psi)
        nominal_p, _ = find_nominal_point(u_set)

    nominal_list = np.squeeze(nominal_list)

    print("nominal_list",nominal_list)
    print("psi_list",psi_list)

### calculate EM

def calc_EM(points, confidence_level, nominal_p):
    num_iter = 100
    num_count = 0
    nominal_list = []
    psi_list = []

    confidence_rank = math.ceil(len(points) * confidence_level)

    prev_psi = np.inf
    for i in range(num_iter):
        u_set, psi = get_uset(points, nominal_p, confidence_rank)
        if psi >= prev_psi or num_count>=100:
            break

        assert(psi <= prev_psi + 1e-5)

        #print("i",i,"psi",psi)
        num_count += 1
        prev_psi = psi
        psi_list.append(psi)
        nominal_list.append(nominal_p)
        nominal_p, _ = find_nominal_point(u_set)

    if len(nominal_list) > 1:
        nominal_list = np.squeeze(nominal_list)
    #print(nominal_list)
    return nominal_list[-1], psi_list[-1]

def calc_EM_rand(points, confidence_level, nominal_p):
    """
    A randomized version
    """

    new_nominal, new_psi = calc_EM(points, confidence_level, nominal_p)

    for i in range(10):
        next_nominal_p = points[np.random.randint(points.shape[0])]

        next_nominal, next_psi = calc_EM(points, confidence_level, next_nominal_p)
        if(next_psi < new_psi):
            new_psi = next_psi
            new_nominal = next_nominal

    return new_nominal, new_psi

### Construct Uset for known value function
import operator

def construct_uset_known_value_function(transition_points, value_function, confidence):
    """
    Computes the robust return and a threshold that achieves the desired confidence level
    for a single state and action.

    @param transition_points Samples from the posterior distribution of the transition
                             probabilities
    @param value_function Assumed optimal value function
    @param confidence Desired confidence level, such as 0.99
    """
    points = []

    for p in transition_points:
        points.append( (p,p@value_function) )
    points.sort(key=lambda x: x[1])

    conf_rank = math.ceil(len(transition_points)*confidence)
    #print("confidence_rank", conf_rank, "len(trans_points)", len(transition_points), "confidence",confidence,"product",confidence*len(transition_points))
    robust_ret = points[-conf_rank][1]
    robust_th = 0 #np.linalg.norm(points[-int(conf_rank)][0]-points[-int(conf_rank/2)][0], ord=1)
    nominal_point = points[-conf_rank][0]

    return (robust_ret, robust_th, nominal_point)


import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy.optimize as opt

plt.style.use('fivethirtyeight')

def sigmoid(z):
    """
    sigmoid 函数
    g 代表一个常用的逻辑函数（logistic function）为S形函数（Sigmoid function）
    :param z:
    :return:
    """
    return 1 / (1 + np.exp(-z))

def cost(theta, x, y):
    """
    代价函数
    cost fn is -l(theta) for you to minimize
    :param theta:
    :param x:
    :param y:
    :return:
    """
    return np.mean(-y * np.log(sigmoid(x @ theta)) - (1 - y) * np.log(1 - sigmoid(x @ theta)))

def gradient(theta, x, y):
    """
    梯度下降
    :param theta:
    :param x:
    :param y:
    :return:
    """
    return (1 / len(x)) * x.T @ (sigmoid(x @ theta) - y)

def predict(x, theta):
    """
    用训练集预测和验证
    :param x:
    :param theta:
    :return: 0/1
    """
    prob = sigmoid(x @ theta)
    return (prob >= 0.5) + 0

def feature_mapping(x, y, power, as_ndarray=False):
    """
    特征映射 https://www.zhihu.com/question/65020904
    :param x:
    :param y:
    :param power:
    :param as_ndarray:
    :return:
    """
    data = {"f{}{}".format(i - p, p): np.power(x, i - p) * np.power(y, p)
            for i in np.arange(power + 1)
            for p in np.arange(i + 1)
            }

    if as_ndarray:
        return pd.DataFrame(data).as_matrix()
    else:
        return pd.DataFrame(data)

def regularized_cost(theta, x, y, l=1):
    """
    正则化代价函数
    :param theta:
    :param x:
    :param y:
    :param l:
    :return:
    """
    theta_j1_to_n = theta[1:]
    regularized_term = (l / (2 * len(x))) * np.power(theta_j1_to_n, 2).sum()
    return cost(theta, x, y) + regularized_term

def regularized_gradient(theta, x, y, l=1):
    """
    正则化梯度
    :param theta:
    :param x:
    :param y:
    :param l:
    :return:
    """
    theta_j1_to_n = theta[1:]
    regularized_theta = (l / len(x)) * theta_j1_to_n

    # by doing this, no offset is on theta_0
    regularized_term = np.concatenate([np.array([0]), regularized_theta])

    return gradient(theta, x, y) + regularized_term

def logistic_regression(x, y, l=1):
    """
    generalized logistic regression
    args:
        X: feature matrix, (m, n+1) # with incercept x0=1
        y: target vector, (m, )
        l: lambda constant for regularization

    return: trained parameters
    """
    # init theta
    theta = np.zeros(x.shape[1])

    # train it
    res = opt.minimize(fun=regularized_cost,
                       x0=theta,
                       args=(x, y, l),
                       method='TNC',
                       jac=regularized_gradient,
                       options={'disp': True})
    # get trained parameters
    final_theta = res.x

    return final_theta


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

# 压缩
import zipfile
import os

# 压缩文件
# def zip_files(files, zip_name):
#     zip = zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED)
#     for file in files:
#         print ('compressing', file)
#         zip.write(file)
#     zip.close()
#     print ('compressing finished')

# 压缩文件夹
def make_zip(source_dir, output_filename):
    zipf = zipfile.ZipFile(output_filename, 'w')
    pre_len = len(os.path.dirname(source_dir))
    for parent, dirnames, filenames in os.walk(source_dir):
        for filename in filenames:
            pathfile = os.path.join(parent, filename)
            arcname = pathfile[pre_len:].strip(os.path.sep)   #相对路径
            zipf.write(pathfile, arcname)
    zipf.close()


#!/usr/bin/env python

import sys
from time import time
from psana.detector.Utils import info_parser_arguments
from psana.detector.UtilsEpix10kaCalib import pedestals_calibration, CALIB_REPO_EPIX10KA

import logging
logger = logging.getLogger(__name__)
DICT_NAME_TO_LEVEL = logging._nameToLevel

SCRNAME = sys.argv[0].rsplit('/')[-1]

USAGE = 'Usage:'\
      + '\n  %s -e <experiment> -d <detector> -r <run-number(s)>' % SCRNAME\
      + '\n     [-x <xtc-directory>] [-o <output-result-directory>] [-L <logging-mode>] [...]'\
      + '\nExamples:'\
      + '\n  %s -e ueddaq02 -d epixquad -r27' % SCRNAME\
      + '\n  mpirun -n 5 %s -e ueddaq02 -d epixquad -r27 -o ./work -L DEBUG' % SCRNAME\
      + '\n  %s -e ueddaq02 -d epixquad -r83 -o ./work' % SCRNAME\
      + '\n  %s -e ueddaq02 -d epixquad -r27 -i15 -o ./work -L DEBUG' % SCRNAME\
      + '\n  %s -e rixx45619 -d epixhr -r121' % SCRNAME\
      + '\n\n  Try: %s -h' % SCRNAME

def do_main():

    t0_sec = time()

    parser = argument_parser()
    args = parser.parse_args()
    kwa = vars(args)
    #defs = vars(parser.parse_args([])) # dict of defaults only

    if len(sys.argv)<3: exit('\n%s\n' % USAGE)

    assert args.exp is not None, 'WARNING: option "-e <experiment>" MUST be specified.'
    assert args.det is not None, 'WARNING: option "-d <detector-name>" MUST be specified.'
    assert args.runs is not None, 'WARNING: option "-r <run-number(s)>" MUST be specified.'

    fmt = '[%(levelname).1s] %(name)s %(message)s' if args.logmode=='DEBUG' else '[%(levelname).1s] %(message)s'
    logging.basicConfig(format=fmt, level=DICT_NAME_TO_LEVEL[args.logmode])
    #logging.basicConfig(format='%(asctime)s %(name)s %(levelname)s %(filename)s: %(message)s', datefmt='%H:%M:%S', level=logging.DEBUG)
    #logging.basicConfig(filename='log.txt', filemode='w', format=fmt, level=DICT_NAME_TO_LEVEL[args.logmode])

    logger.debug('%s\nIn epix10ka_pedestals_calibration' % (50*'_'))
    logger.debug('Command line:%s' % ' '.join(sys.argv))
    logger.info(info_parser_arguments(parser))

    pedestals_calibration(**kwa)

    logger.info('DONE, consumed time %.3f sec' % (time() - t0_sec))

def argument_parser():
    from argparse import ArgumentParser

    d_fname   = None # '/cds/data/psdm/ued/ueddaq02/xtc/ueddaq02-r0027-s000-c000.xtc2'
    d_exp     = None # 'ueddaq02'
    d_det     = None # 'epixquad'
    d_runs    = None # 1021 or 1021,1022-1025
    d_nrecs   = 800  # number of records to collect and process
    d_nrecs1  = 200  # number of records to process at 1st stage
    d_idx     = None # 0-15 for epix10ka2m, 0-3 for epix10kaquad
    d_dirxtc  = None # '/cds/data/psdm/ued/ueddaq02/xtc'
    d_dirrepo = CALIB_REPO_EPIX10KA # './myrepo'
    d_usesmd  = True
    d_logmode = 'INFO'
    d_errskip = True
    d_stepnum    = None
    d_stepmax    = 5
    d_evskip     = 100     # number of events to skip in the beginning of each step
    d_events     = 1000    # last event number in the step to process
    d_dirmode    = 0o774
    d_filemode   = 0o664
    d_int_lo     = 1       # lowest  intensity accepted for dark evaluation
    d_int_hi     = 16000   # highest intensity accepted for dark evaluation
    d_intnlo     = 6.0     # intensity ditribution number-of-sigmas low
    d_intnhi     = 6.0     # intensity ditribution number-of-sigmas high
    d_rms_lo     = 0.001   # rms ditribution low
    d_rms_hi     = 16000   # rms ditribution high
    d_rmsnlo     = 6.0     # rms ditribution number-of-sigmas low
    d_rmsnhi     = 6.0     # rms ditribution number-of-sigmas high
    d_fraclm     = 0.1     # allowed fraction limit
    d_fraclo     = 0.05    # fraction of statistics [0,1] below low limit
    d_frachi     = 0.95    # fraction of statistics [0,1] below high limit

    h_fname   = 'input xtc file name, default = %s' % d_fname
    h_exp     = 'experiment name, default = %s' % d_exp
    h_det     = 'detector name, default = %s' % d_det
    h_runs    = 'run number or list of runs e.g. 12,14-18, default = %s' % str(d_runs)
    h_nrecs   = 'number of records to calibrate pedestals, default = %s' % str(d_nrecs)
    h_nrecs1  = 'number of records to process at 1st stage, default = %s' % str(d_nrecs1)
    h_idx     = 'segment index (0-15 for epix10ka2m, 0-3 for quad) or all by default for processing, default = %s' % str(d_idx)
    h_dirxtc  = 'non-default xtc directory, default = %s' % d_dirxtc
    h_dirrepo = 'repository for calibration results, default = %s' % d_dirrepo
    h_usesmd  = 'add "smd" in dataset string, default = %s' % d_usesmd
    h_logmode = 'logging mode, one of %s, default = %s' % (' '.join(DICT_NAME_TO_LEVEL.keys()), d_logmode)
    h_errskip = 'flag to skip errors and keep processing, stop otherwise, default = %s' % d_errskip
    h_stepnum    = 'step number to process or None for all steps, default = %s' % str(d_stepnum)
    h_stepmax    = 'maximum number of steps to process, default = %s' % str(d_stepmax)
    h_evskip     = 'number of events to skip in the beginning of each step, default = %s' % str(d_evskip)
    h_events     = 'number of events to process from the beginning of each step, default = %s' % str(d_events)
    h_dirmode    = 'directory access mode, default = %s' % oct(d_dirmode)
    h_filemode   = 'file access mode, default = %s' % oct(d_filemode)
    h_int_lo     = 'lowest  intensity accepted for dark evaluation, default = %d' % d_int_lo
    h_int_hi     = 'highest intensity accepted for dark evaluation, default = %d' % d_int_hi
    h_intnlo     = 'intensity ditribution number-of-sigmas low, default = %f' % d_intnlo
    h_intnhi     = 'intensity ditribution number-of-sigmas high, default = %f' % d_intnhi
    h_rms_lo     = 'rms ditribution low, default = %f' % d_rms_lo
    h_rms_hi     = 'rms ditribution high, default = %f' % d_rms_hi
    h_rmsnlo     = 'rms ditribution number-of-sigmas low, default = %f' % d_rmsnlo
    h_rmsnhi     = 'rms ditribution number-of-sigmas high, default = %f' % d_rmsnhi
    h_fraclm     = 'allowed fraction limit, default = %f' % d_fraclm
    h_fraclo     = 'fraction of statistics [0,1] below low  limit of the gate, default = %f' % d_fraclo
    h_frachi     = 'fraction of statistics [0,1] below high limit of the gate, default = %f' % d_frachi

    parser = ArgumentParser(description='Proceses dark run xtc data for epix10ka')
    parser.add_argument('-f', '--fname',   default=d_fname,      type=str,   help=h_fname)
    parser.add_argument('-e', '--exp',     default=d_exp,        type=str,   help=h_exp)
    parser.add_argument('-d', '--det',     default=d_det,        type=str,   help=h_det)
    parser.add_argument('-r', '--runs',    default=d_runs,       type=str,   help=h_runs)
    parser.add_argument('-n', '--nrecs',   default=d_nrecs,      type=int,   help=h_nrecs)
    parser.add_argument('--nrecs1',        default=d_nrecs1,     type=int,   help=h_nrecs1)
    parser.add_argument('-i', '--idx',     default=d_idx,        type=int,   help=h_idx)
    parser.add_argument('-x', '--dirxtc',  default=d_dirxtc,     type=str,   help=h_dirxtc)
    parser.add_argument('-o', '--dirrepo', default=d_dirrepo,    type=str,   help=h_dirrepo)
    parser.add_argument('-S', '--usesmd',  action='store_false',             help=h_usesmd)
    parser.add_argument('-L', '--logmode', default=d_logmode,    type=str,   help=h_logmode)
    parser.add_argument('-E', '--errskip', action='store_false',             help=h_errskip)
    parser.add_argument('--stepnum',       default=d_stepnum,    type=int,   help=h_stepnum)
    parser.add_argument('--stepmax',       default=d_stepmax,    type=int,   help=h_stepmax)
    parser.add_argument('--evskip',        default=d_evskip,     type=int,   help=h_evskip)
    parser.add_argument('--events',        default=d_events,     type=int,   help=h_events)
    parser.add_argument('--dirmode',       default=d_dirmode,    type=int,   help=h_dirmode)
    parser.add_argument('--filemode',      default=d_filemode,   type=int,   help=h_filemode)
    parser.add_argument('--int_lo',        default=d_int_lo,     type=int,   help=h_int_lo)
    parser.add_argument('--int_hi',        default=d_int_hi,     type=int,   help=h_int_hi)
    parser.add_argument('--intnlo',        default=d_intnlo,     type=float, help=h_intnlo)
    parser.add_argument('--intnhi',        default=d_intnhi,     type=float, help=h_intnhi)
    parser.add_argument('--rms_lo',        default=d_rms_lo,     type=float, help=h_rms_lo)
    parser.add_argument('--rms_hi',        default=d_rms_hi,     type=float, help=h_rms_hi)
    parser.add_argument('--rmsnlo',        default=d_rmsnlo,     type=float, help=h_rmsnlo)
    parser.add_argument('--rmsnhi',        default=d_rmsnhi,     type=float, help=h_rmsnhi)
    parser.add_argument('--fraclm',        default=d_fraclm,     type=float, help=h_fraclm)
    parser.add_argument('--fraclo',        default=d_fraclo,     type=float, help=h_fraclo)
    parser.add_argument('--frachi',        default=d_frachi,     type=float, help=h_frachi)

    return parser

if __name__ == "__main__":
    do_main()
    exit('End of %s'%SCRNAME)

# EOF


import numpy as np
from matplotlib import pyplot as plt

x = np.array([1,2,4,5,1,2,3])
y = np.array([2,2,3,3,4,5,6])

dx = x[1:] - x[:-1]
dy = y[1:] - y[:-1]
dy_dx = dy/dx
centersX = (x[1:] + x[:-1])/2.0
centersY = (y[1:] + y[:-1])/2.0

plt.subplot(1,2,1)
plt.plot(centersX,dy_dx,'rx',centersX,np.sin(centersX),'b-')
plt.show()


#!/usr/bin/env python3
#    Copyright (C) 2017 Alexandre Teyar

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
#    limitations under the License.

import logging

from libnmap.parser import NmapParser

def get_host_services(input_file):
    results = {}

    nmap = NmapParser.parse_fromfile(input_file)

    for host in nmap.hosts:
        if host.is_up():
            services = []

            for port in host.get_ports():
                service = host.get_service(port[0], port[1])

                services.append(
                    {
                        "banner":   service.banner,
                        "file":     input_file,
                        "port":     service.port,
                        "protocol": service.protocol,
                        "reason":   service.reason,
                        "service":  service.service,
                        "state":    service.state
                    }
                )

            results[host.address] = services

    return results

def get_host_oss(input_file):
    results = {}

    nmap = NmapParser.parse_fromfile(input_file)

    for host in nmap.hosts:
        if host.is_up() and host.os_fingerprinted:
            operating_systems = host.os_match_probabilities()

            # the first match has the highest accuracy
            if operating_systems:
                results[host.address] = {
                        "file":     input_file,
                        "name":     operating_systems[0].name,
                        "accuracy": operating_systems[0].accuracy
                }
        else:
            logging.debug(
                "OS fingerprinting has not been performed for {}".format(
                    host.address
                )
            )

    return results

def get_os_hosts(input_file):
    results = {}

    nmap = NmapParser.parse_fromfile(input_file)

    for host in nmap.hosts:
        if host.is_up() and host.os_fingerprinted:
            operating_systems = host.os_match_probabilities()

            if operating_systems:
                # the first match has the highest accuracy
                if operating_systems[0].name in list(results.keys()):
                    results[operating_systems[0].name]["file"].extend(
                        [input_file]
                    )
                    results[operating_systems[0].name]["host_ip"].extend(
                        [host.address]
                    )
                else:
                    results[operating_systems[0].name] = {
                            "file":     [input_file],
                            "host_ip":  [host.address],
                    }
        else:
            logging.debug(
                "OS fingerprinting has not been performed for {}".format(
                    host.address
                )
            )

    return results


from django.urls import path, include
from .views import *

app_name = 'Usermanagement'

urlpatterns = [
    path('edit/<int:id>/', profile_edit, name='edit'),
    path('login/', user_login, name='login'),
    path('logout/', user_logout, name='logout'),
    path('register/', user_register, name='register'),
    path('userdelete/<int:id>/', user_delete, name='delete'),
    ]


# Copyright Contributors to the Amundsen project.
# SPDX-License-Identifier: Apache-2.0

from enum import Enum
from typing import (
    Dict, List, Optional,
)

from amundsen_common.models.search import Filter, SearchResponse
from elasticsearch import Elasticsearch
from elasticsearch_dsl import (
    MultiSearch, Q, Search,
)
from elasticsearch_dsl.query import MultiMatch
from elasticsearch_dsl.response import Response
from werkzeug.exceptions import InternalServerError

BOOL_QUERY = 'bool'
WILDCARD_QUERY = 'wildcard'
TERM_QUERY = 'term'
TERMS_QUERY = 'terms'

class Resource(Enum):
    TABLE = 0
    DASHBOARD = 1
    FEATURE = 2
    USER = 3

RESOURCE_STR_MAPPING = {
    'table': Resource.TABLE,
    'dashboard': Resource.DASHBOARD,
    'feature': Resource.FEATURE,
    'user': Resource.USER,
}

class ElasticsearchProxy():
    PRIMARY_ENTITIES = [Resource.TABLE, Resource.DASHBOARD, Resource.FEATURE, Resource.USER]

    # mapping to translate request for table resources
    TABLE_MAPPING = {
        'key': 'key',
        'badges': 'badges',
        'tag': 'tags',
        'schema': 'schema.raw',
        'table': 'name.raw',
        'column': 'column_names.raw',
        'database': 'database.raw',
        'cluster': 'cluster.raw'
    }

    # mapping to translate request for dashboard resources
    DASHBOARD_MAPPING = {
        'group_name': 'group_name.raw',
        'name': 'name.raw',
        'product': 'product',
        'tag': 'tags',
    }

    # mapping to translate request for feature resources
    FEATURE_MAPPING = {
        'key': 'key',
        'feature_group': 'feature_group.raw',
        'feature_name': 'feature_name.raw',
        'entity': 'entity',
        'status': 'status',
        'version': 'version',
        'availability': 'availability.raw',
        'tags': 'tags',
        'badges': 'badges'
    }

    USER_MAPPING = {
        'full_name': 'full_name',
        'first_name': 'first_name',
        'last_name': 'last_name',
        'email': 'email'
    }

    RESOUCE_TO_MAPPING = {
        Resource.TABLE: TABLE_MAPPING,
        Resource.DASHBOARD: DASHBOARD_MAPPING,
        Resource.FEATURE: FEATURE_MAPPING,
        Resource.USER: USER_MAPPING,
    }

    def __init__(self, *,
                 host: str = None,
                 user: str = '',
                 password: str = '',
                 client: Elasticsearch = None) -> None:
        if client:
            self.elasticsearch = client
        else:
            http_auth = (user, password) if user else None
            self.elasticsearch = Elasticsearch(host, http_auth=http_auth)

    def _build_term_query(self, resource: Resource, query_term: str) -> Optional[Q]:
        """
        Builds the query object for the inputed search term
        """
        if not query_term:
            # We don't want to create multi_match query for ""
            # because it will result in no matches even with filters
            return None

        fields = []

        if resource == Resource.TABLE:
            fields = ["display_name^1000",
                      "name.raw^75",
                      "name^5",
                      "schema^3",
                      "description^3",
                      "column_names^2",
                      "column_descriptions",
                      "tags",
                      "badges",
                      "programmatic_descriptions"]
        elif resource == Resource.DASHBOARD:
            fields = ["name.raw^75",
                      "name^7",
                      "group_name.raw^15",
                      "group_name^7",
                      "description^3",
                      "query_names^3",
                      "chart_names^2"]
        elif resource == Resource.FEATURE:
            fields = ["feature_name.raw^25",
                      "feature_name^7",
                      "feature_group.raw^15",
                      "feature_group^7",
                      "version^7",
                      "description^3",
                      "status",
                      "entity",
                      "tags",
                      "badges"]
        elif resource == Resource.USER:
            fields = ["full_name.raw^30",
                      "full_name^5",
                      "first_name.raw^5",
                      "last_name.raw^5",
                      "first_name^3",
                      "last_name^3",
                      "email^3"]
        else:
            # TODO if you don't specify a resource match for all generic fields in the future
            raise ValueError(f"no fields defined for resource {resource}")

        return MultiMatch(query=query_term, fields=fields, type='most_fields')

    def _build_filters(self, resource: Resource, filters: List[Filter]) -> List:
        """
        Builds the query object for all of the filters given in the search request
        """
        mapping = self.RESOUCE_TO_MAPPING.get(resource)

        filter_queries: List = []

        for filter in filters:
            filter_name = mapping.get(filter.name) if mapping is not None \
                and mapping.get(filter.name) is not None else filter.name

            queries_per_term = [Q(WILDCARD_QUERY, **{filter_name: term}) for term in filter.values]

            if filter.operation == 'OR':
                filter_queries.append(Q(BOOL_QUERY, should=queries_per_term, minimum_should_match=1))

            elif filter.operation == 'AND':
                for q in queries_per_term:
                    filter_queries.append(q)

            else:
                msg = f"Invalid operation {filter.operation} for filter {filter_name} with values {filter.values}"
                raise ValueError(msg)

        return filter_queries

    def _build_elasticsearch_query(self, *,
                                   resource: Resource,
                                   query_term: str,
                                   filters: List[Filter]) -> Q:

        term_query = self._build_term_query(resource=resource,
                                            query_term=query_term)
        filters = self._build_filters(resource=resource, filters=filters)

        es_query = None

        if filters and term_query:
            es_query = Q('bool', should=[term_query], filter=filters)
        elif not filters and term_query:
            es_query = Q('bool', should=[term_query])
        elif filters and not term_query:
            es_query = Q('bool', filter=filters)
        else:
            raise ValueError("Invalid search query")

        return es_query

    def _format_response(self, page_index: int,
                         results_per_page: int,
                         responses: List[Response],
                         resource_types: List[Resource]) -> SearchResponse:
        resource_types_str = [r.name.lower() for r in resource_types]
        no_results_for_resource = {
            "results": [],
            "total_results": 0
        }
        results_per_resource = {resource: no_results_for_resource for resource in resource_types_str}

        for r in responses:
            if r.success():
                results_count = r.hits.total.value
                if results_count > 0:
                    resource_type = r.hits.hits[0]._type
                    results = []
                    for search_result in r.hits.hits:
                        # mapping gives all the fields in the response
                        result = {}
                        fields = self.RESOUCE_TO_MAPPING[Resource[resource_type.upper()]]
                        for f in fields.keys():
                            # remove "raw" from mapping value
                            field = fields[f].split('.')[0]
                            result[f] = search_result._source[field]
                        result["search_score"] = search_result._score
                        results.append(result)
                    # replace empty results with actual results
                    results_per_resource[resource_type] = {
                        "results": results,
                        "total_results": results_count
                    }
            else:
                raise InternalServerError(f"Request to Elasticsearch failed: {r.failures}")

        return SearchResponse(msg="Success",
                              page_index=page_index,
                              results_per_page=results_per_page,
                              results=results_per_resource,
                              status_code=200)

    def execute_queries(self, queries: Dict[Resource, Q],
                        page_index: int,
                        results_per_page: int) -> List[Response]:
        multisearch = MultiSearch(using=self.elasticsearch)

        for resource in queries.keys():
            resource_str = resource.name.lower()
            resource_index = f"{resource_str}_search_index"
            query_for_resource = queries.get(resource)
            search = Search(index=resource_index).query(query_for_resource)

            # pagination
            start_from = page_index * results_per_page
            search = search[start_from:results_per_page]

            multisearch = multisearch.add(search)

        return multisearch.execute()

    def search(self, *,
               query_term: str,
               page_index: int,
               results_per_page: int,
               resource_types: List[Resource],
               filters: List[Filter]) -> SearchResponse:
        if resource_types == []:
            # if resource types are not defined then search all resources
            resource_types = self.PRIMARY_ENTITIES

        queries: Dict[Resource, Q] = {}
        for resource in resource_types:
            # build a query for each resource to search
            queries[resource] = self._build_elasticsearch_query(resource=resource,
                                                                query_term=query_term,
                                                                filters=filters)

        responses = self.execute_queries(queries=queries,
                                         page_index=page_index,
                                         results_per_page=results_per_page)

        formatted_response = self._format_response(page_index=page_index,
                                                   results_per_page=results_per_page,
                                                   responses=responses,
                                                   resource_types=resource_types)

        return formatted_response


import argparse
import logging

import falcon

from falcon_cors import CORS
import json
import waitress

logging.basicConfig(level=logging.INFO, format='%(asctime)-18s %(message)s')
logger = logging.getLogger()
cors_allow_all = CORS(allow_all_origins=True,
                      allow_origins_list=['*'],
                      allow_all_headers=True,
                      allow_all_methods=True,
                      allow_credentials_all_origins=True
                      )

parser = argparse.ArgumentParser()
parser.add_argument(
    '-p', '--port', default=58100,
    help='falcon server port')

args = parser.parse_args()

class RootResource:

    def __init__(self):
        logger.info("...")

    def on_get(self, req, resp, filename="default.html"):
        # do some sanity check on the filename

        resp.status = falcon.HTTP_200
        resp.content_type = 'text/html'
        with open(filename, 'r', encoding='utf-8') as f:
            resp.body = f.read()

class BinaryResource:

    def __init__(self):
        logger.info("...")

    def on_get(self, req, resp, filename):
        # do some sanity check on the filename

        resp.status = falcon.HTTP_200
        resp.content_type = 'image/png'
        with open(filename, 'rb') as f:
            resp.body = f.read()

class TorchResource:

    def __init__(self):
        logger.info("...")

    def on_get(self, req, resp, filename):
        # do some sanity check on the filename

        resp.status = falcon.HTTP_200
        resp.content_type = 'text/html'
        with open(filename, 'r', encoding='utf-8') as f:
            resp.body = f.read()

if __name__=="__main__":
    api = falcon.API(middleware=[cors_allow_all.middleware])
    api.req_options.auto_parse_form_urlencoded = True
    api.add_route('/', RootResource())
    api.add_route('/img/{filename}', BinaryResource())
    api.add_route('/static/{filename}', TorchResource())
    waitress.serve(api, port=args.port, threads=48, url_scheme='http')


from django.urls import path, include

# views syllabus
from . import views
from .views import (
    PostListView,
    PostDetailView,
    PostCreateView,
    PostUpdateView,
    PostDeleteView,
    search
)

# filters
from .filters import NotesFilter, SyllabusFilter
from django_filters.views import FilterView

urlpatterns = [
    path('', PostListView.as_view(), name='notes-home'),
    path('notes/new/', PostCreateView.as_view(), name='notes-create'),
    path('filter/', FilterView.as_view(filterset_class=NotesFilter, template_name='notes/templates/notes/notes/notes_model_filter.html'), name='notes-filter'),
    path('notes/<int:pk>', PostDetailView.as_view(), name='notes-detail'),
    path('notes/<int:pk>/update/', PostUpdateView.as_view(), name='notes-update'),
    path('notes/<int:pk>/delete/', PostDeleteView.as_view(), name='notes-delete'),
    path('syllabus/', FilterView.as_view(filterset_class=SyllabusFilter), name='syllabus'),
    path('about/', views.about, name='notes-about'),
]


from django import forms
from django.core.exceptions import ValidationError

from lists.models import Item, List

EMPTY_ITEM_ERROR = 'Can`t have an empty list item,'
DUPLICATE_ITEM_ERROR = 'You`ve already entered this item. Can`t have duplicates.'

class ItemForm(forms.models.ModelForm):

    class Meta:
        model = Item
        fields = ('text',)
        widgets = {
            'text' : forms.fields.TextInput(attrs={
                'placeholder' : "Enter a to-do item",
                'class' : "form-controll input-lg",
            })
        }
        error_messages={
            'text' : {'required' : EMPTY_ITEM_ERROR}
        }

class NewListForm(ItemForm):

    def save(self, owner):
        self.is_valid()
        if owner.is_authenticated:
            return List.create_new(first_item_text = self.cleaned_data['text'], owner = owner)
        else:
            return List.create_new(first_item_text = self.cleaned_data['text'])

class ExistingListItemForm(ItemForm):

    def __init__(self, list_,  *args, **kwargs):
        ret = super().__init__(*args, **kwargs)
        self.instance.list = list_
        return ret

    def validate_unique(self):
        try:
            self.instance.validate_unique()
        except ValidationError as e:
            e.error_dict = {'text': [DUPLICATE_ITEM_ERROR]}
            self._update_errors(e)


"""
Line-intersection-based fitting.

Contact: Tino Michael <Tino.Michael@cea.fr>
"""

from ctapipe.reco.reco_algorithms import (
    Reconstructor,
    InvalidWidthException,
    TooFewTelescopesException
)
from ctapipe.containers import ReconstructedShowerContainer
from itertools import combinations

from ctapipe.coordinates import (
    CameraFrame,
    GroundFrame,
    TiltedGroundFrame,
    project_to_ground,
    MissingFrameAttributeWarning,
)
from astropy.coordinates import (
    SkyCoord,
    spherical_to_cartesian,
    cartesian_to_spherical,
)
import warnings

import numpy as np

from astropy import units as u

__all__ = ['HillasReconstructor', 'HillasPlane']

def angle(v1, v2):
    """ computes the angle between two vectors
        assuming cartesian coordinates

    Parameters
    ----------
    v1 : numpy array
    v2 : numpy array

    Returns
    -------
    the angle between v1 and v2 as a dimensioned astropy quantity
    """
    norm = np.linalg.norm(v1) * np.linalg.norm(v2)
    return np.arccos(np.clip(v1.dot(v2) / norm, -1.0, 1.0))

def normalise(vec):
    """ Sets the length of the vector to 1
        without changing its direction

    Parameters
    ----------
    vec : numpy array

    Returns
    -------
    numpy array with the same direction but length of 1
    """
    try:
        return vec / np.linalg.norm(vec)
    except ZeroDivisionError:
        return vec

def line_line_intersection_3d(uvw_vectors, origins):
    """
    Intersection of many lines in 3d.
    See https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
    """
    C = []
    S = []
    for n, pos in zip(uvw_vectors, origins):
        n = n.reshape((3, 1))
        norm_matrix = (n @ n.T) - np.eye(3)
        C.append(norm_matrix @ pos)
        S.append(norm_matrix)

    S = np.array(S).sum(axis=0)
    C = np.array(C).sum(axis=0)
    return np.linalg.inv(S) @ C

class HillasReconstructor(Reconstructor):
    """
    class that reconstructs the direction of an atmospheric shower
    using a simple hillas parametrisation of the camera images it
    provides a direction estimate in two steps and an estimate for the
    shower's impact position on the ground.

    so far, it does neither provide an energy estimator nor an
    uncertainty on the reconstructed parameters

    """

    def __init__(self, config=None, parent=None, **kwargs):
        super().__init__(config=config, parent=parent, **kwargs)
        self.hillas_planes = {}
        self.divergent_mode = False
        self.corrected_angle_dict = {}

    def predict(self, hillas_dict, subarray, array_pointing, telescopes_pointings=None):
        """
        The function you want to call for the reconstruction of the
        event. It takes care of setting up the event and consecutively
        calls the functions for the direction and core position
        reconstruction.  Shower parameters not reconstructed by this
        class are set to np.nan

        Parameters
        -----------
        hillas_dict: dict
            dictionary with telescope IDs as key and
            HillasParametersContainer instances as values
        inst : ctapipe.io.InstrumentContainer
            instrumental description
        array_pointing: SkyCoord[AltAz]
            pointing direction of the array
        telescopes_pointings: dict[SkyCoord[AltAz]]
            dictionary of pointing direction per each telescope

        Raises
        ------
        TooFewTelescopesException
            if len(hillas_dict) < 2
        InvalidWidthException
            if any width is np.nan or 0
        """

        # filter warnings for missing obs time. this is needed because MC data has no obs time
        warnings.filterwarnings(action='ignore', category=MissingFrameAttributeWarning)

        # stereoscopy needs at least two telescopes
        if len(hillas_dict) < 2:
            raise TooFewTelescopesException(
                "need at least two telescopes, have {}"
                .format(len(hillas_dict)))

        # check for np.nan or 0 width's as these screw up weights
        if any([np.isnan(hillas_dict[tel]['width'].value) for tel in hillas_dict]):
            raise InvalidWidthException(
                "A HillasContainer contains an ellipse of width==np.nan")

        if any([hillas_dict[tel]['width'].value == 0 for tel in hillas_dict]):
            raise InvalidWidthException(
                "A HillasContainer contains an ellipse of width==0")

        # use the single telescope pointing also for parallel pointing: code is more general
        if telescopes_pointings is None:
            telescopes_pointings = {tel_id: array_pointing for tel_id in hillas_dict.keys()}
        else:
            self.divergent_mode = True
            self.corrected_angle_dict = {}

        self.initialize_hillas_planes(
            hillas_dict,
            subarray,
            telescopes_pointings,
            array_pointing
        )

        # algebraic direction estimate
        direction, err_est_dir = self.estimate_direction()

        # array pointing is needed to define the tilted frame
        core_pos = self.estimate_core_position(hillas_dict, array_pointing)

        # container class for reconstructed showers
        _, lat, lon = cartesian_to_spherical(*direction)

        # estimate max height of shower
        h_max = self.estimate_h_max()

        # astropy's coordinates system rotates counter-clockwise.
        # Apparently we assume it to be clockwise.
        # that's why lon get's a sign
        result = ReconstructedShowerContainer(
            alt=lat,
            az=-lon,
            core_x=core_pos[0],
            core_y=core_pos[1],
            tel_ids=[h for h in hillas_dict.keys()],
            average_intensity=np.mean([h.intensity for h in hillas_dict.values()]),
            is_valid=True,
            alt_uncert=err_est_dir,
            h_max=h_max
        )

        return result

    def initialize_hillas_planes(
            self,
            hillas_dict,
            subarray,
            telescopes_pointings,
            array_pointing
    ):
        """
        Creates a dictionary of :class:`.HillasPlane` from a dictionary of
        hillas parameters

        Parameters
        ----------
        hillas_dict : dictionary
            dictionary of hillas moments
        subarray : ctapipe.instrument.SubarrayDescription
            subarray information
        telescopes_pointings: dictionary
            dictionary of pointing direction per each telescope
        array_pointing: SkyCoord[AltAz]
            pointing direction of the array
        """

        self.hillas_planes = {}
        k = next(iter(telescopes_pointings))
        horizon_frame = telescopes_pointings[k].frame
        for tel_id, moments in hillas_dict.items():
            # we just need any point on the main shower axis a bit away from the cog
            p2_x = moments.x + 0.1 * u.m * np.cos(moments.psi)
            p2_y = moments.y + 0.1 * u.m * np.sin(moments.psi)
            focal_length = subarray.tel[tel_id].optics.equivalent_focal_length

            pointing = SkyCoord(
                alt=telescopes_pointings[tel_id].alt,
                az=telescopes_pointings[tel_id].az,
                frame=horizon_frame,
            )

            camera_frame = CameraFrame(
                focal_length=focal_length,
                telescope_pointing=pointing
            )

            cog_coord = SkyCoord(
                x=moments.x,
                y=moments.y,
                frame=camera_frame,
            )
            cog_coord = cog_coord.transform_to(horizon_frame)

            p2_coord = SkyCoord(x=p2_x, y=p2_y, frame=camera_frame)
            p2_coord = p2_coord.transform_to(horizon_frame)

            # re-project from sky to a "fake"-parallel-pointing telescope
            # then recalculate the psi angle
            if self.divergent_mode:
                camera_frame_parallel = CameraFrame(
                    focal_length=focal_length,
                    telescope_pointing=array_pointing
                )
                cog_sky_to_parallel = cog_coord.transform_to(camera_frame_parallel)
                p2_sky_to_parallel = p2_coord.transform_to(camera_frame_parallel)
                angle_psi_corr = np.arctan2(cog_sky_to_parallel.y - p2_sky_to_parallel.y,
                                            cog_sky_to_parallel.x - p2_sky_to_parallel.x)
                self.corrected_angle_dict[tel_id] = angle_psi_corr

            circle = HillasPlane(
                p1=cog_coord,
                p2=p2_coord,
                telescope_position=subarray.positions[tel_id],
                weight=moments.intensity * (moments.length / moments.width),
            )
            self.hillas_planes[tel_id] = circle

    def estimate_direction(self):
        """calculates the origin of the gamma as the weighted average
        direction of the intersections of all hillas planes

        Returns
        -------
        gamma : shape (3) numpy array
            direction of origin of the reconstructed shower as a 3D vector
        crossings : shape (n,3) list
            an error estimate
        """

        crossings = []
        for perm in combinations(self.hillas_planes.values(), 2):
            n1, n2 = perm[0].norm, perm[1].norm
            # cross product automatically weighs in the angle between
            # the two vectors: narrower angles have less impact,
            # perpendicular vectors have the most
            crossing = np.cross(n1, n2)

            # two great circles cross each other twice (one would be
            # the origin, the other one the direction of the gamma) it
            # doesn't matter which we pick but it should at least be
            # consistent: make sure to always take the "upper" solution
            if crossing[2] < 0:
                crossing *= -1
            crossings.append(crossing * perm[0].weight * perm[1].weight)

        result = normalise(np.sum(crossings, axis=0))
        off_angles = [angle(result, cross) for cross in crossings] * u.rad

        err_est_dir = np.average(
            off_angles,
            weights=[len(cross) for cross in crossings]
        )

        return result, err_est_dir

    def estimate_core_position(self, hillas_dict, array_pointing):
        """
        Estimate the core position by intersection the major ellipse lines of each telescope.

        Parameters
        -----------
        hillas_dict: dict[HillasContainer]
            dictionary of hillas moments
        array_pointing: SkyCoord[HorizonFrame]
            Pointing direction of the array

        Returns
        -----------
        core_x: u.Quantity
            estimated x position of impact
        core_y: u.Quantity
            estimated y position of impact

        """
        if self.divergent_mode:
            psi = u.Quantity(list(self.corrected_angle_dict.values()))
        else:
            psi = u.Quantity([h.psi for h in hillas_dict.values()])

        z = np.zeros(len(psi))
        uvw_vectors = np.column_stack([np.cos(psi).value, np.sin(psi).value, z])

        tilted_frame = TiltedGroundFrame(pointing_direction=array_pointing)
        ground_frame = GroundFrame()

        positions = [
            (
                SkyCoord(*plane.pos, frame=ground_frame)
                .transform_to(tilted_frame)
                .cartesian.xyz
            )
            for plane in self.hillas_planes.values()
        ]

        core_position = line_line_intersection_3d(uvw_vectors, positions)

        core_pos_tilted = SkyCoord(
            x=core_position[0] * u.m,
            y=core_position[1] * u.m,
            frame=tilted_frame
        )

        core_pos = project_to_ground(core_pos_tilted)

        return core_pos.x, core_pos.y

    def estimate_h_max(self):
        """
        Estimate the max height by intersecting the lines of the cog directions of each telescope.

        Returns
        -----------
        astropy.unit.Quantity
            the estimated max height
        """
        uvw_vectors = np.array([plane.a for plane in self.hillas_planes.values()])
        positions = [plane.pos for plane in self.hillas_planes.values()]

        # not sure if its better to return the length of the vector of the z component
        return np.linalg.norm(line_line_intersection_3d(uvw_vectors, positions)) * u.m

class HillasPlane:
    """
    a tiny helper class to collect some parameters for each great great
    circle

    Stores some vectors a, b, and c

    These vectors are euclidean [x, y, z] where positive z values point towards the sky
    and x and y are parallel to the ground.
    """

    def __init__(self, p1, p2, telescope_position, weight=1):
        """The constructor takes two coordinates in the horizontal
        frame (alt, az) which define a plane perpendicular
        to the camera.

        Parameters
        -----------
        p1: astropy.coordinates.SkyCoord
            One of two direction vectors which define the plane.
            This coordinate has to be defined in the ctapipe.coordinates.AltAz
        p2: astropy.coordinates.SkyCoord
            One of two direction vectors which define the plane.
            This coordinate has to be defined in the ctapipe.coordinates.AltAz
        telescope_position: np.array(3)
            Position of the telescope on the ground
        weight : float, optional
            weight of this plane for later use during the reconstruction

        Notes
        -----
        c: numpy.ndarray(3)
            :math:`\vec c = (\vec a \times \vec b) \times \vec a`
            :math:`\rightarrow` a and c form an orthogonal base for the
            great circle
            (only orthonormal if a and b are of unit-length)
        norm: numpy.ndarray(3)
            normal vector of the circle's plane,
            perpendicular to a, b and c
        """

        self.pos = telescope_position

        # astropy's coordinates system rotates counter clockwise. Apparently we assume it to
        # be clockwise
        self.a = np.array(spherical_to_cartesian(1, p1.alt, -p1.az)).ravel()
        self.b = np.array(spherical_to_cartesian(1, p2.alt, -p2.az)).ravel()

        # a and c form an orthogonal basis for the great circle
        # not really necessary since the norm can be calculated
        # with a and b just as well
        self.c = np.cross(np.cross(self.a, self.b), self.a)
        # normal vector for the plane defined by the great circle
        self.norm = normalise(np.cross(self.a, self.c))
        # some weight for this circle
        # (put e.g. uncertainty on the Hillas parameters
        # or number of PE in here)
        self.weight = weight


import torch
#from torch.autograd import Function
from ..box_utils import decode, nms, center_size
from config import cfg_refinedet

#class Detect_RefineDet(Function):
class Detect_RefineDet():
    """At test time, Detect is the final layer of SSD.  Decode location preds,
    apply non-maximum suppression to location predictions based on conf
    scores and threshold to a top_k number of output predictions for both
    confidence score and locations.
    """
    def __init__(self, num_classes, size, bkg_label, top_k, conf_thresh, nms_thresh,
                objectness_thre, keep_top_k):
        self.num_classes = num_classes
        self.background_label = bkg_label
        self.top_k = top_k
        self.keep_top_k = keep_top_k
        # Parameters used in nms.
        self.nms_thresh = nms_thresh
        if nms_thresh <= 0:
            raise ValueError('nms_threshold must be non negative.')
        self.conf_thresh = conf_thresh
        self.objectness_thre = objectness_thre
        self.variance = cfg_refinedet['variance']
        self.device = 'cuda:0'

    def __call__(self, arm_loc_data, arm_conf_data, odm_loc_data, odm_conf_data, prior_data):
        """
        Args:
            loc_data: (tensor) Loc preds from loc layers
                Shape: [batch,num_priors*4]
            conf_data: (tensor) Shape: Conf preds from conf layers
                Shape: [batch*num_priors,num_classes]
            prior_data: (tensor) Prior boxes and variances from priorbox layers
                Shape: [1,num_priors,4]
        """
        loc_data = odm_loc_data
        conf_data = odm_conf_data

        # ADD:change size
        print("model:", conf_data.size())
        print("anc:", prior_data.size())
        assert False
        #prior_data = prior_data[:loc_data.size(1), :].to(self.device)
        prior_data = prior_data.to(self.device)

        arm_object_conf = arm_conf_data.data[:, :, 1:]
        no_object_index = arm_object_conf <= self.objectness_thre
        conf_data[no_object_index.expand_as(conf_data)] = 0

        num = loc_data.size(0)  # batch size
        num_priors = prior_data.size(0)
        conf_preds = conf_data.view(num, num_priors,
                                    self.num_classes).transpose(2, 1)
        output = []

        # Decode predictions into bboxes.
        for i in range(num):
            default = decode(arm_loc_data[i], prior_data, self.variance)
            default = center_size(default)
            decoded_boxes = decode(loc_data[i], default, self.variance)
            # For each class, perform nms
            conf_scores = conf_preds[i].clone()
            #print(decoded_boxes, conf_scores)
            output_cls = dict()
            for cl in range(1, self.num_classes):
                c_mask = conf_scores[cl].gt(self.conf_thresh)
                scores = conf_scores[cl][c_mask]
                #print(scores.dim())
                if scores.size(0) == 0:
                    continue
                l_mask = c_mask.unsqueeze(1).expand_as(decoded_boxes)
                boxes = decoded_boxes[l_mask].view(-1, 4)
                # idx of highest scoring and non-overlapping boxes per class
                #print(boxes, scores)
                ids, count = nms(boxes, scores, self.nms_thresh, self.top_k)
                output_cls[cl-1] = {"score":scores[ids[:count]].unsqueeze(1).data.cpu().numpy(),
                        "boxes":boxes[ids[:count]].data.cpu().numpy()};
            output.append(output_cls)
        #flt = output.contiguous().view(num, -1, 5)
        #_, idx = flt[:, :, 0].sort(1, descending=True)
        #_, rank = idx.sort(1)
        #flt[(rank < self.keep_top_k).unsqueeze(-1).expand_as(flt)].fill_(0)
        return output


# most of this code are from https://github.com/choosehappy/HistoQC
import os
import sys
import numpy
from ast import literal_eval as make_tuple
import skimage
from skimage import io, color, img_as_ubyte, morphology
from skimage.filters import sobel
from skimage.filters import gabor_kernel, frangi, gaussian, median, laplace
from skimage.color import convert_colorspace, rgb2gray, rgb2hsv, separate_stains, hed_from_rgb
from skimage.morphology import remove_small_objects, disk, binary_opening, dilation
from distutils.util import strtobool
import numpy as np
import scipy
from scipy import ndimage as ndi
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier

def getTissueMask(img):
    upper_thresh = .9
    lower_var = 10
    lower_thresh = -float('inf')
    upper_var = float('inf')
    img_var = img.std(axis=2)
    map_var = np.bitwise_and(img_var > lower_var, img_var < upper_var)
    img = color.rgb2gray(img)
    map = np.bitwise_and(img > lower_thresh, img < upper_thresh)
    map = np.bitwise_and(map, map_var)
    map = (map >0)
    #map = ~map
    #map = map.astype(np.uint8)
    return map

def getDarkTissueMask(img):
    upper_thresh = .15
    lower_var = -float('inf')
    lower_thresh = -float('inf')
    upper_var = float('inf')
    img_var = img.std(axis=2)
    map_var = np.bitwise_and(img_var > lower_var, img_var < upper_var)
    img = color.rgb2gray(img)
    map = np.bitwise_and(img > lower_thresh, img < upper_thresh)
    map = np.bitwise_and(map, map_var)
    map = (map >0)
    #map = ~map
    #map = map.astype(np.uint8)
    return map

def getBrightness(img, tmask):
    a = getBrightnessGray(img, tmask)
    b = getBrightnessOtherColor(img, tmask)
    return {**a, **b}

def getBrightnessGray(img, tmask):
    img_g = rgb2gray(img)
    img_gm = img_g[tmask]

    return {'Gray Brightness mean (W)': img_g.mean(),
            'Gray Brightness std (W)':img_g.std(),
            'Gray Brightness mean (T)':img_gm.mean(),
            'Gray Brightness std (T)':img_gm.std()}

def getBrightnessOtherColor(img, tmask):
    a = {}
    #color_spaces = ['RGB', 'HSV', 'RGB CIE', 'XYZ', 'YUV', 'YIQ',
    #                'YPbPr', 'YCbCr']
    color_spaces = ['RGB', 'HSV', 'YUV']
    for color in color_spaces:
        if color != 'RGB':
            img2 = convert_colorspace(img, 'RGB', color)
        else:
            img2 = img
        for chan in range(0, 3):
            vals = img2[:, :, chan]
            vals_t = vals[tmask]
            a[f'{color} {chan+1} Brightness mean (W)'] = vals.mean()
            a[f'{color} {chan+1} Brightness std (W)'] = vals.std()
            a[f'{color} {chan+1} Brightness mean (T)'] = vals_t.mean()
            a[f'{color} {chan+1} Brightness std (T)'] = vals_t.std()

    return a

def getHueSaturation(img, tmask):
    a = {}
    hsv = rgb2hsv(img)

    a[f'Hue mean (W)'] = hsv[:,:,0].mean()
    a[f'Hue std (W)'] = hsv[:,:,0].std()
    a[f'Saturation mean (W)'] = hsv[:,:,1].mean()
    a[f'Saturation std (W)'] = hsv[:,:,1].std()
    a[f'Hue mean (T)'] = hsv[:,:,0][tmask].mean()
    a[f'Hue std (T)'] = hsv[:,:,0][tmask].std()
    a[f'Saturation mean (T)'] = hsv[:,:,1][tmask].mean()
    a[f'Saturation std (T)'] = hsv[:,:,1][tmask].std()

    return a

def getHE(img, tmask):
    a = {}

    stain_matrix = hed_from_rgb
    dimg = separate_stains(img, stain_matrix)

    a[f'Stain H mean (W)'] = dimg[:,:,0].mean()
    a[f'Stain H std (W)'] = dimg[:,:,0].std()
    a[f'Stain E mean (W)'] = dimg[:,:,1].mean()
    a[f'Stain E std (W)'] = dimg[:,:,1].std()
    a[f'Stain H mean (T)'] = dimg[:,:,0][tmask].mean()
    a[f'Stain H std (T)'] = dimg[:,:,0][tmask].std()
    a[f'Stain E mean (T)'] = dimg[:,:,1][tmask].mean()
    a[f'Stain E std (T)'] = dimg[:,:,1][tmask].std()

    return a

def getContrast(img, tmask):
    a = {}

    img = rgb2gray(img)
    sobel_img = sobel(img) ** 2
    tenenGrad_contrast = np.sqrt(np.sum(sobel_img)) / img.size
    max_img = img.max()
    min_img = img.min()
    michelson_contrast = (max_img - min_img) / (max_img + min_img)
    rms_contrast = np.sqrt(pow(img - img.mean(), 2).sum() / img.size)
    a[f'Contrast TenenGrad (W)'] = tenenGrad_contrast
    a[f'Contrast Michelson (W)'] = michelson_contrast
    #a[f'Contrast RMS (W)'] = rms_contrast

    img = img[tmask]
    sobel_img = sobel_img[tmask]
    tenenGrad_contrast = np.sqrt(np.sum(sobel_img)) / img.size
    max_img = img.max()
    min_img = img.min()
    michelson_contrast = (max_img - min_img) / (max_img + min_img)
    rms_contrast = np.sqrt(pow(img - img.mean(), 2).sum() / img.size)
    a[f'Contrast TenenGrad (T)'] = tenenGrad_contrast
    a[f'Contrast Michelson (T)'] = michelson_contrast
    #a[f'Contrast RMS (T)'] = rms_contrast

    return a

def getSmoothness(img, tmask):
    thresh = .01
    kernel_size = 10
    min_object_size = 500
    img = color.rgb2gray(img)
    avg = np.ones((kernel_size, kernel_size)) / (kernel_size**2)
    imf = scipy.signal.convolve2d(img, avg, mode="same")
    mask_flat = abs(imf - img) < thresh
    mask_flat = remove_small_objects(mask_flat, min_size=min_object_size)
    mask_flat = ~remove_small_objects(~mask_flat, min_size=min_object_size)
    ratio = np.sum(mask_flat) / (mask_flat.shape[0]*mask_flat.shape[1])
    a = {}
    a['Smoothness Ratio'] = ratio
    return a

def remove_large_objects(img, max_size):
    # code taken from morphology.remove_small_holes, except switched < with >
    selem = ndi.generate_binary_structure(img.ndim, 1)
    ccs = np.zeros_like(img, dtype=np.int32)
    ndi.label(img, selem, output=ccs)
    component_sizes = np.bincount(ccs.ravel())
    too_big = component_sizes > max_size
    too_big_mask = too_big[ccs]
    img_out = img.copy()
    img_out[too_big_mask] = 0
    return img_out

def getFatlikeTissue(img, tmask):
    a = {}
    kernel_size = 10
    max_keep_size = 1000
    fat_cell_size = 64
    img_reduced = morphology.remove_small_holes(tmask, area_threshold=fat_cell_size)
    img_small = img_reduced & np.invert(tmask)
    img_small = ~morphology.remove_small_holes(~img_small, area_threshold=9)
    mask_dilate = morphology.dilation(img_small, selem=np.ones((kernel_size, kernel_size)))
    mask_dilate_removed = remove_large_objects(mask_dilate, max_keep_size)
    mask_fat = mask_dilate & ~mask_dilate_removed
    mask_fat = (mask_fat * 255) >0
    ratio = np.sum(mask_fat) / (mask_fat.shape[0] * mask_fat.shape[1])
    a = {'Fat Like Tissue Ratio': ratio}
    return a

def compute_features(img, params):
    features = params.get("features", "")
    feats=[]
    for feature in features:
        func = getattr(sys.modules[__name__], f'compute_{feature}')
        feats.append(func(img, params))
    return np.concatenate(feats, axis=2)

def compute_rgb(img, params):
    return img

def compute_laplace(img, params):
    laplace_ksize = int(params.get("laplace_ksize", 3))
    return laplace(rgb2gray(img), ksize=laplace_ksize)[:, :, None]

def compute_lbp(img, params):
    lbp_radius = float(params.get("lbp_radius", 3))
    lbp_points = int(params.get("lbp_points", 24))  # example sets radius * 8
    lbp_method = params.get("lbp_method", "default")
    return local_binary_pattern(rgb2gray(img), P=lbp_points, R=lbp_radius, method=lbp_method)[:, :, None]

def compute_gaussian(img, params):
    gaussian_sigma = int(params.get("gaussian_sigma", 1))
    gaussian_multichan = strtobool(params.get("gaussian_multichan", False))

    if (gaussian_multichan):
        return gaussian(img, sigma=gaussian_sigma, multichannel=gaussian_multichan)
    else:
        return gaussian(rgb2gray(img), sigma=gaussian_sigma)[:, :, None]

def compute_median(img, params):
    median_disk_size = int(params.get("median_disk_size", 3))
    return median(rgb2gray(img), selem=disk(median_disk_size))[:, :, None]

def compute_gabor(img, params):
    if not params["shared_dict"].get("gabor_kernels", False):
        gabor_theta = int(params.get("gabor_theta", 4))
    gabor_sigma = make_tuple(params.get("gabor_sigma", "(1,3)"))
    gabor_frequency = make_tuple(params.get("gabor_frequency", "(0.05, 0.25)"))

    kernels = []
    for theta in range(gabor_theta):
        theta = theta / 4. * np.pi
        for sigma in gabor_sigma:
            for frequency in gabor_frequency:
                kernel = np.real(gabor_kernel(frequency, theta=theta,
                        sigma_x=sigma, sigma_y=sigma))
                kernels.append(kernel)
    params["shared_dict"]["gabor_kernels"] = kernels

    kernels = params["shared_dict"]["gabor_kernels"]
    imgg = rgb2gray(img)
    feats = np.zeros((imgg.shape[0], imgg.shape[1], len(kernels)), dtype=np.double)
    for k, kernel in enumerate(kernels):
        filtered = ndi.convolve(imgg, kernel, mode='wrap')
        feats[:, :, k] = filtered
    return feats

def compute_frangi(img, params):
    frangi_scale_range = make_tuple(params.get("frangi_scale_range", "(1, 10)"))
    frangi_scale_step = float(params.get("frangi_scale_step", 2))
    frangi_beta1 = float(params.get("frangi_beta1", .5))
    frangi_beta2 = float(params.get("frangi_beta2", 15))
    frangi_black_ridges = strtobool(params.get("frangi_black_ridges", "True"))
    feat = frangi(rgb2gray(img), scale_range = frangi_scale_range, scale_step =frangi_scale_step, beta =frangi_beta1, gamma=frangi_beta2, black_ridges  =frangi_black_ridges)
    return feat[:, :, None]  # add singleton dimension

def trainModelPen(example_path, example_mask_path):
    model_vals = []
    model_labels = np.empty([0, 1])
    img = io.imread(example_path)
    params = dict(
                threshold = .5,
                area_threshold = 100,
                features = ['frangi', 'laplace', 'rgb'],
                laplace_ksize = 3,
                frangi_scale_range = '(1,10)',
                frangi_scale_step = 2,
                frangi_beta1 = .5,
                frangi_beta2= 15,
                frangi_black_ridges= 'True',

                gabor_theta= 4,
                gabor_sigma= '(1,3)',
                gabor_frequency= '(0.05, 0.25)',

                lbp_radius= 3,
                lbp_points= 24,
                lbp_method= 'default',

                median_disk_size= 3,
             )
    eximg = compute_features(img, params)
    eximg = eximg.reshape(-1, eximg.shape[2])
    model_vals.append(eximg)
    mask = io.imread(example_mask_path, as_gray=True).reshape(-1,1)
    model_labels = np.vstack((model_labels, mask))

    model_vals = np.vstack(model_vals)
    clf = RandomForestClassifier(n_jobs=-1)
    clf.fit(model_vals, model_labels.ravel())
    return clf

def trainCoverslipEdge(example_path, example_mask_path):
    params = dict(
                area_threshold = 15,
                features =  ['frangi', 'laplace', 'rgb'],
                dilate_kernel_size = 5,
    )
    model_vals = []
    model_labels = np.empty([0, 1])
    img = io.imread(example_path)
    eximg = compute_features(img, params)
    eximg = eximg.reshape(-1, eximg.shape[2])
    model_vals.append(eximg)
    mask = io.imread(example_mask_path, as_gray=True).reshape(-1,1)
    model_labels = np.vstack((model_labels, mask))

    model_vals = np.vstack(model_vals)
    clf = RandomForestClassifier(n_jobs=-1)
    clf.fit(model_vals, model_labels.ravel())
    return clf

def getBlurryRegion(src_img, tmask):
    #img_work_size = 2.5x
    blur_radius = 100
    blur_threshold = .15
    img = rgb2gray(src_img)
    img_laplace = np.abs(skimage.filters.laplace(img))
    mask = skimage.filters.gaussian(img_laplace, sigma=blur_radius) <= blur_threshold
    mask = skimage.transform.resize(mask, src_img.shape, order=0)[:,:,1]
    mask = tmask & (mask >0)
    ratio = np.sum(mask) / mask.size
    a = {'Blurry Region Ratio': ratio}
    return a

def getPenMarking(img, tmask, model):
    params = dict(
                threshold = .5,
                area_threshold = 100,
                features = ['frangi', 'laplace', 'rgb'],
                laplace_ksize = 3,
                frangi_scale_range = '(1,10)',
                frangi_scale_step = 2,
                frangi_beta1 = .5,
                frangi_beta2= 15,
                frangi_black_ridges= 'True',

                gabor_theta= 4,
                gabor_sigma= '(1,3)',
                gabor_frequency= '(0.05, 0.25)',

                lbp_radius= 3,
                lbp_points= 24,
                lbp_method= 'default',

                median_disk_size= 3,
    )
    thresh = float(params.get("threshold", .5))
    clf = model
    feats = compute_features(img, params)
    cal = clf.predict_proba(feats.reshape(-1, feats.shape[2]))
    cal = cal.reshape(img.shape[0], img.shape[1], 2)
    mask = cal[:, :, 1] > thresh
    area_thresh = int(params.get("area_threshold", "5"))
    if area_thresh > 0:
        mask = remove_small_objects(mask, min_size=area_thresh, in_place=True)
    dilate_kernel_size = int(params.get("dilate_kernel_size", "0"))
    if dilate_kernel_size > 0:
        mask = dilation(mask, selem=np.ones((dilate_kernel_size, dilate_kernel_size)))
    mask = tmask & (mask > 0)
    ratio = np.sum(mask)/np.sum(tmask)
    return ratio

def getCoverslipEdge(img, tmask, model):
    params = dict(
                area_threshold = 15,
                features =  ['frangi', 'laplace', 'rgb'],
                dilate_kernel_size = 5,
    )
    thresh = float(params.get("threshold", .5))
    clf = model
    feats = compute_features(img, params)
    cal = clf.predict_proba(feats.reshape(-1, feats.shape[2]))
    cal = cal.reshape(img.shape[0], img.shape[1], 2)
    mask = cal[:, :, 1] > thresh
    area_thresh = int(params.get("area_threshold", "5"))
    if area_thresh > 0:
        mask = remove_small_objects(mask, min_size=area_thresh, in_place=True)
    dilate_kernel_size = int(params.get("dilate_kernel_size", "0"))
    if dilate_kernel_size > 0:
        mask = dilation(mask, selem=np.ones((dilate_kernel_size, dilate_kernel_size)))
    mask = tmask & (mask > 0)
    ratio = np.sum(mask)/np.sum(tmask)
    return ratio


# Vehicle Telemetry System for Aston University Shell Eco Marathon Team
#
# Rev 9
# To be used with Arduino sketch above v14
#
# Contact:
# Edward Hull
# ed.n.hull@gmail.com
# hulle@aston.ac.uk
# Msc Software Engineering Major Project
# 104308055
#

from pyqtgraph.Qt import QtGui, QtCore
import numpy as np
import pyqtgraph as pg
import time
import pyqtgraph.functions as fn
import serial
import sys

win = None

def display():
    global win
    xMin = 0
    xMax = 60
    yMin = 0
    yMax = 30
    xDelta = 0.02

    app = QtGui.QApplication.instance()
    if app is None:
        pg.setConfigOption( 'background', 'k')
        pg.setConfigOption( 'foreground', 'w')
        app = pg.mkQApp()

    if win is None:
        win = pg.GraphicsWindow(title="Aston University - Shell Eco Marathon")
        win.resize(1600,1200)
    win.clear()

        #win = pg.GraphicsLayoutWidget()
    #win.setBackground( QtGui.QBrush(QtGui.QColor(240,240,240)))
    win.addLabel( "Vehicle Telemetry", row = 1, col = 1, colspan=2, bold=True, size='20pt')
    win.addLabel( "Fuel Cell", row = 1, col = 3, colspan=1, bold=True, size='20pt')

    speed_graph = win.addPlot( row=2, col=1, colspan=2, rowspan=3)
    speed_graph.showGrid( x = True, y = True)
    speed_graph.setTitle( title = "Speed (MPH)")
    speed_graph.setLabel( 'left', 'MPH')
    speed_graph.setLabel( 'bottom', 'Time (Seconds)')
    speed_graph.setXRange( xMin - 0.25, xMax + 0.25)
    speed_graph.showButtons()
    speed_graph.enableAutoRange('y', True)
    speed_graph.enableAutoRange('x', True)
    lr = pg.LinearRegionItem([20,40], movable=True,)
    lr.setZValue(-10)
    speed_graph.addItem(lr)

    average_graph = win.addPlot( row=5, col=1, rowspan=2)
    average_graph.showGrid( x = True, y = True)
    average_graph.setTitle(title = "Average Speed (MPH)")
    average_graph.setLabel( 'left', 'MPH')
    average_graph.setLabel( 'bottom', 'Time')
    average_graph.enableAutoRange( x = True, y = True)
    average_graph.setXRange( xMin - 0.25, xMax + 0.25)
    average_graph.enableAutoRange('y', True)
    average_graph.enableAutoRange('x', True)
    #lr = pg.LinearRegionItem(average_graph.getViewBox().viewRange()[0], movable=True,)
    lr.setZValue(-10)

    dist_graph = win.addPlot( row=5, col=2, rowspan=2)
    dist_graph.showGrid( x = True, y = True)
    dist_graph.setTitle( title = "Distance covered (Meters)")
    dist_graph.setLabel( 'left', 'M')
    dist_graph.setLabel( 'bottom', 'Time')
    dist_graph.enableAutoRange( x = True, y = True)
    dist_graph.setXRange( xMin - 0.25, xMax + 0.25)
    dist_graph.enableAutoRange('y', True)
    dist_graph.enableAutoRange('x', True)

    power_graph = win.addPlot( row=2, col=3, colspan=1)
    power_graph.showGrid( x = True, y = True)
    power_graph.setTitle( title = "Power")
    power_graph.setLabel( 'left', 'Watts')
    power_graph.setLabel( 'bottom', 'Time')
    power_graph.setXRange( xMin - 0.25, xMax + 0.25)
    power_graph.setRange(yRange=(50,1000))
    power_graph.showButtons()
    #power_graph.enableAutoRange('y', True)
    power_graph.enableAutoRange('x', True)

    stackcurrent_graph = win.addPlot( row=3, col=3, colspan=1)
    stackcurrent_graph.showGrid( x = True, y = True)
    stackcurrent_graph.setTitle( title = "Stack Current")
    stackcurrent_graph.setLabel( 'left', 'Amps')
    stackcurrent_graph.setLabel( 'bottom', 'Time')
    stackcurrent_graph.setXRange( xMin - 0.25, xMax + 0.25)
    stackcurrent_graph.setRange(yRange=(10,65))
    stackcurrent_graph.showButtons()
    #stackcurrent_graph.enableAutoRange('y', True)
    stackcurrent_graph.enableAutoRange('x', True)

    stackvoltage_graph = win.addPlot( row=4, col=3, colspan=1)
    stackvoltage_graph.showGrid( x = True, y = True)
    stackvoltage_graph.setTitle( title = "Stack Voltage")
    stackvoltage_graph.setLabel( 'left', 'Volts')
    stackvoltage_graph.setLabel( 'bottom', 'Time')
    stackvoltage_graph.setXRange( xMin - 0.25, xMax + 0.25)
    stackvoltage_graph.setRange(yRange=(25,55))
    stackvoltage_graph.showButtons()
    #stackvoltage_graph.enableAutoRange('y', True)
    stackvoltage_graph.enableAutoRange('x', True)

    stacktemperature_graph = win.addPlot( row=5, col=3, colspan=1)
    stacktemperature_graph.showGrid( x = True, y = True)
    stacktemperature_graph.setTitle( title = "Stack Temperature")
    stacktemperature_graph.setLabel( 'left', 'Celcius')
    stacktemperature_graph.setLabel( 'bottom', 'Time')
    stacktemperature_graph.setXRange( xMin - 0.25, xMax + 0.25)
    stacktemperature_graph.setRange(yRange=(20,55))
    stacktemperature_graph.showButtons()
    #stacktemperature_graph.enableAutoRange('y', True)
    stacktemperature_graph.enableAutoRange('x', True)

    batteryvoltage_graph = win.addPlot( row=6, col=3, colspan=1)
    batteryvoltage_graph.showGrid( x = True, y = True)
    batteryvoltage_graph.setTitle( title = "Battery Voltage")
    batteryvoltage_graph.setLabel( 'left', 'Volts')
    batteryvoltage_graph.setLabel( 'bottom', 'Time')
    batteryvoltage_graph.setXRange( xMin - 0.25, xMax + 0.25)
    batteryvoltage_graph.setRange(yRange=(12,18))
    batteryvoltage_graph.showButtons()
    #batteryvoltage_graph.enableAutoRange('y', True)
    batteryvoltage_graph.enableAutoRange('x', True)

    def updatePlot():
        average_graph.setXRange(*lr.getRegion(), padding=0)
        dist_graph.setXRange(*lr.getRegion(), padding=0)
        power_graph.setXRange(*lr.getRegion(), padding=0)
        stackcurrent_graph.setXRange(*lr.getRegion(), padding=0)
        stackvoltage_graph.setXRange(*lr.getRegion(), padding=0)
        stacktemperature_graph.setXRange(*lr.getRegion(), padding=0)
        batteryvoltage_graph.setXRange(*lr.getRegion(), padding=0)
    def updateRegion():
        lr.setRegion(average_graph.getViewBox().viewRange()[0])

    win.addLabel( "Speed", row = 7, col = 1, colspan=1, bold=False, italic=True, size='12pt', color='3333FF')
    win.addLabel( "Average", row = 8, col = 1, colspan=1, bold=False, italic=True, size='12pt', color='FF1919')
    win.addLabel( "Dist", row = 9, col = 1, colspan=1, bold=False, italic=True, size='12pt', color='00CC00')

    win.addLabel( "Serial device: " + serialDev, row = 7, col = 2, colspan=1, bold=False, italic=False, size='12pt', color='CCFF00')
    win.addLabel( "Fuel Cell Status: ", row = 8, col = 2, colspan=1, bold=False, italic=False, size='12pt', color='CCFF00')
    win.addLabel( "Currently saving to SD card: ", row = 9, col = 2, colspan=1, bold=False, italic=False, size='12pt', color='CCFF00')

    win.addLabel( "Power: ", row = 7, col = 3, colspan=1, bold=False, italic=True, size='12pt', color='CCFF00')
    win.addLabel( "Stack Current: ", row = 8, col = 3, colspan=1, bold=False, italic=True, size='12pt', color='CCFF00')
    win.addLabel( "Stack Voltage: ", row = 9, col = 3, colspan=1, bold=False, italic=True, size='12pt', color='CCFF00')
    win.addLabel( "Stack Temperature: ", row = 10, col = 3, colspan=1, bold=False, italic=True, size='12pt', color='CCFF00')
    win.addLabel( "Battery Voltage: ", row = 11, col = 3, colspan=1, bold=False, italic=True, size='12pt', color='CCFF00')

    millis_array = []
    speed_array = []
    average_array = []
    dist_array = []
    power_array = []
    stackcurrent_array = []
    stackvoltage_array = []
    stacktemperature_array = []
    batteryvoltage_array = []
    ambienttemperature_array = []
    stackstatus_array = []
    incomingDataArray = []
    lastSpeed = 0
    lastAverageSpeed = 0
    lastDistCovered = 0
    lastPower = 0
    lastCurrent = 0
    lastStackVoltage = 0
    lastStackTemperature = 0
    lastBatteryVoltage = 0
    lastAmbientTemperature = 0
    lastStackStatus = 0
    fuelCellStatus = -1
    p = 0
    firstReading = True
    fuelCellStatusOutput = "UNKNOWN"

    while True:
        updatePlot()
        data = ser.readline().rstrip().decode("utf-8")
        if len(data) > 0: # If data has been received

            incomingDataArray = data.split(';')

            # System uptime in milliseconds, distance, speed, averagespeed, Power(watts), Current(amps), Stackvoltage(volts), Stacktemperature (Celcius), Batteryvoltage(volts), AmbientTemperature(Celcius), StackStatus ,SavingToSDCard(True/False),
            if len(incomingDataArray) == 12: # Check to see if data was received in-tact

                #print('Rx data : ' + ', '.join(incomingDataArray))
                if firstReading == True:
                    firstReading = False
                    lr = pg.LinearRegionItem([(float(incomingDataArray[0])/1000)-4,(float(incomingDataArray[0])/1000)+4], movable=True,)

                currentMillis = (float(incomingDataArray[0])/1000)
                millis_array.append(currentMillis)

                ## Attempt to clear graph
                if currentMillis < millis_array[-1] :
                    millis_array[:] = []
                    speed_array[:] = []
                    speed_graph.plot(x=0, y=0, pen=( 0, 0, 255), fillLevel=0, brush=(50,50,200,50))
                    speed_graph.clear()

                lastSpeed = float(incomingDataArray[1])
                #speed_graph.setTitle( title = "Speed (MPH) | Last : " + str(lastSpeed))
                speed_graph.clear()
                speed_graph.addItem(lr)
                speed_array.append(float(incomingDataArray[1]))
                speed_graph.plot(x=millis_array[:p], y=speed_array[:p], pen=( 0, 0, 255), fillLevel=0, brush=(50,50,200,50))
                win.getItem(7,1).setText("Current speed: " + str(lastSpeed) + " mph")

                lastAverageSpeed = float(incomingDataArray[2])
                #average_graph.setTitle(title = "Average Speed (MPH) | Last : " + str(lastAverageSpeed))
                average_array.append(float(incomingDataArray[2]))
                average_graph.clear()
                average_graph.plot( x=millis_array[:p], y=average_array[:p], pen=( 255, 0, 0),fillLevel=0, brush=(200,50,50,50))
                win.getItem(8,1).setText("Moving average speed: " + str(lastAverageSpeed) + " mph")

                lastDistCovered = float(incomingDataArray[3])
                #dist_graph.setTitle( title = "Distance covered (Meters) | Last : " + str(lastDistCovered))
                dist_array.append(float(incomingDataArray[3]))
                dist_graph.clear()
                dist_graph.plot( x=millis_array[:p], y=dist_array[:p], pen=( 0, 255, 0),fillLevel=0, brush=(50,200,50,50))
                win.getItem(9,1).setText("Distance covered: " + str(lastDistCovered) + " meters")

                lastPower = float(incomingDataArray[4])
                power_array.append(float(incomingDataArray[4]))
                power_graph.clear()
                power_graph.plot( x=millis_array[:p], y=power_array[:p], pen=( 55,230,70),fillLevel=0, brush=(55,230,70,50))
                win.getItem(7,3).setText("Power: " + str(lastPower) + "W")

                lastCurrent = float(incomingDataArray[5])
                stackcurrent_array.append(float(incomingDataArray[5]))
                stackcurrent_graph.clear()
                stackcurrent_graph.plot( x=millis_array[:p], y=stackcurrent_array[:p], pen=( 15,125,200),fillLevel=0, brush=(15,125,200,50))
                win.getItem(8,3).setText("Amps: " + str(lastCurrent) + "A")

                lastStackVoltage = float(incomingDataArray[6])
                stackvoltage_array.append(float(incomingDataArray[6]))
                stackvoltage_graph.clear()
                stackvoltage_graph.plot( x=millis_array[:p], y=stackvoltage_array[:p], pen=(205,55,40),fillLevel=0, brush=(205,55,40,50))
                win.getItem(9,3).setText("Volts: " + str(lastStackVoltage) + "v")

                lastStackTemperature = float(incomingDataArray[7])
                stacktemperature_array.append(float(incomingDataArray[7]))
                stacktemperature_graph.clear()
                stacktemperature_graph.plot( x=millis_array[:p], y=stacktemperature_array[:p], pen=(20,65,80),fillLevel=0, brush=(20,65,80,50))
                win.getItem(10,3).setText("Stack Temperature: " + str(lastStackTemperature) + "c")

                lastBatteryVoltage = float(incomingDataArray[8])
                batteryvoltage_array.append(float(incomingDataArray[8]))
                batteryvoltage_graph.clear()
                batteryvoltage_graph.plot( x=millis_array[:p], y=batteryvoltage_array[:p], pen=(255,255,0),fillLevel=0, brush=(50,200,50,50))
                win.getItem(11,3).setText("Battery Voltage: " + str(lastBatteryVoltage) + "v")

                savingToSDCard = incomingDataArray[11]
                if savingToSDCard == '1':
                    savingToSDCard = "Yes"
                else:
                    savingToSDCard = "No"
                win.getItem(9,2).setText("Saving to SD card: " + savingToSDCard)

                fuelCellStatus = incomingDataArray[10]
                if fuelCellStatus == '-1':
                    fuelCellStatusOutput = "UNKNOWN"
                if fuelCellStatus == '0':
                    fuelCellStatusOutput = "OK"
                if fuelCellStatus == '1':
                    fuelCellStatusOutput = "BATTERY LOW"
                if fuelCellStatus == '2':
                    fuelCellStatusOutput = "FC VOLTAGE LOW"
                if fuelCellStatus == '3':
                    fuelCellStatusOutput = "H2% HIGH"
                if fuelCellStatus == '4':
                    fuelCellStatusOutput = "CURRENT HIGH"
                if fuelCellStatus == '5':
                    fuelCellStatusOutput = "TEMPERATURE HIGH"
                if fuelCellStatus == '20':
                    fuelCellStatusOutput = "MANUAL TURN OFF"
                win.getItem(8,2).setText("Fuel Cell Status: " + fuelCellStatusOutput)

                p = p + 1
                #speed_graph.autoRange()
        app.processEvents()

def find_serial():
    passed = len(sys.argv)
    if passed != 2:
        print(passed)
        print('Incorrect number of arguments')
        print('Please pass the serial device as the first argument')
        print('Eg. sudo python3 eco.py /dev/ttyUSB0')
        print('or C:/pythondir/python3.exe eco.py COM1 on Windows')
        sys.exit(1)
    serialDev = str(sys.argv[1])
    return serialDev

def check_serial_device_exists():
	try:
		file = open(serialDev, 'r')
	except IOError:
		print
		print('Could not find/open a serial device at ' + serialDev)
		print('Check file permissions, check radio receiver is plugged in, and check serial device location. Close any existing instances of this application (can accidentally be running with the window closed!)')
		print
		sys.exit(1)

#def get_data():
	#self.data = float(ser.readline().rstrip())
serialDev = find_serial()
print("Using serial device: "+ serialDev)
check_serial_device_exists()
ser = serial.Serial(serialDev, 57600, timeout = 0.1)
display()
exit()


# Most of the code in this file comes from PBNT by Elliot Cohen. See
# separate file in this project with PBNT license.

import itertools as it

from inference.InferenceEngine import *
from potentials.DiscreteUniPot import *

class EnumerationEngine(InferenceEngine):
    """
    EnumerationEngine is an InferenceEngine that enumerates all (brute force
    method) possible instantiations (aka stories, histories, Feynman paths)
    consistent with the active states of each node.

    Attributes
    ----------
    bnet_ord_nodes : list[BayesNode]

    bnet : BayesNet
    do_print : bool
    is_quantum : bool

    """

    def __init__(self, bnet, do_print=False, is_quantum=False):
        """
        Constructor

        Parameters
        ----------
        bnet : BayesNet
        do_print : bool
        is_quantum : bool

        Returns
        -------

        """
        InferenceEngine.__init__(self, bnet, do_print, is_quantum)
        self.bnet_ord_nodes = list(self.bnet.nodes)

    def get_unipot_list(self, node_list):
        """
        For each node in node_list, this method returns a uni-potential that
        gives the probabilities for the states of that node. Obviously,
        such a PD has the active states of the node as support.

        Parameters
        ----------
        node_list : list[BayesNode]

        Returns
        -------
        list[DiscreteUniPot]

        """
        assert(set(node_list) <= self.bnet.nodes)
        pot_list = [DiscreteUniPot(self.is_quantum, node, bias=0)
                    for node in node_list]
        story_counter = 0
        total_pot_val = 0.
        for cur_story in self.story_generator():
            story_counter += 1
            annotated_story = dict(zip(
                    self.bnet_ord_nodes, cur_story))
            pot_val = self.get_story_potential_val(
                annotated_story)
            total_pot_val += pot_val
            if self.do_print:
                print("[", story_counter, "] pot_val=", pot_val)
                InferenceEngine.print_annotated_story(
                        annotated_story)
                print("\n")
            state_list = [annotated_story[v] for v in node_list]
            for (pot, state) in zip(pot_list, state_list):
                pot[state] += pot_val
        if self.do_print:
            print("tot_pot_val= ", total_pot_val,
                  "# equals 1 if you comment out the evidence")
            print("\n")
        pot_list1 = []
        for pot in pot_list:
            pot.normalize_self()
            if self.is_quantum:
                pot1 = pot.get_probs_from_amps()
            else:
                pot1 = pot
            pot_list1.append(pot1)
        return pot_list1

    def story_generator(self):
        """
        Generate the next possible story constrained by the evidence (i.e.,
        by the active states of each node).

        Returns
        -------
        itertools.product

        """

        x = [node.active_states for node in self.bnet_ord_nodes]
        return it.product(*x)

    def get_story_potential_val(self, annotated_story):
        """
        Given an annotated story (i.e., a dictionary that maps all nodes to
        their current state), it returns a float for CBnets and a complex
        for QBnet. The returned value is the pot value for that particular
        annotated story.

        Parameters
        ----------
        annotated_story : dict[Node, int]

        Returns
        -------
        complex

        """
        pot_val = 1
        for node in self.bnet.nodes:
            pot = node.potential
            states = tuple(annotated_story[v] for v in pot.ord_nodes)
            pot_val *= pot[states]
        return pot_val

from examples_cbnets.HuaDar import *
if __name__ == "__main__":

    bnet = HuaDar.build_bnet()

    # introduce some evidence
    # bnet.get_node_named("D").active_states = [0]
    # bnet.get_node_named("G").active_states = [1]

    inf_eng = EnumerationEngine(bnet, do_print=True)
    id_nums = sorted([node.id_num for node in bnet.nodes])
    node_list = [bnet.get_node_with_id_num(k) for k in id_nums]

    # this is simpler but erratic
    # node_list = list(bnet.nodes)

    pot_list = inf_eng.get_unipot_list(node_list)
    for pot in pot_list:
        print(pot, "\n")


import pyfiglet
import time
import os
import ctypes
import random

def welcome():
    print(pyfiglet.Figlet(font = 'slant').renderText('     Wallpaper'));

    print("\nWelcome to Wallpaper modifier !");
    print("   Author: Valentin Le Lièvre \n   Version: 0.1 \n   License: MIT \n");

def bye():
    print("Thanks to use wallpaper modifier. Hope to see you soon ! \n");

    time.sleep(2);

def checkIfWindows():
    if os.name != "nt":
            print("\u001b[31m\nYour are not on Windows operating system. Unfortunately this programm does not support other operating system than Windows.\u001b[0m \n");
            return False;
    else:
        return True;

def getInput():
    command = input(">>> ");

    return command;

def excecuteCommand(command):
    if command == "setPath" or command == "setpath":
        setPath(command);
    elif command == "next":
        changeWallpaper();
    elif command == "help":
        help();
    elif command == "quit":
        return False;
    else:
        error(command);

def setPath(command):
    checkIfDataFileAlreadyExist();

    if checkPathAlreadySet() == True:
        print('fd');

    return True;

def checkIfDataFileAlreadyExist():
    if os.path.isfile("data.txt") == False:
        file = open("data.txt", "a+");
        file.close();

        print("\u001b[32mThe data file does not exist or have been deleted. Creating new one ! Your settings are be reset to default make sure you restore your settings.\u001b[0m \n");
    else:
        return True;

def checkPathAlreadySet():
    file = open("data.txt", "r");

    content = file.read();
    data = content.split(';');
    pathData = data[0].split(',');

    file.close();

    if pathData[1] != " ":
        answer = input("\u001b[33mThe path to your wallpaper folder as already been set. Are you sure you want to change it ?\u001b[0m [yes][no]: ");

        if answer == "yes" or answer == "y":
            path = input("\nWhat is the new path of your wallpaper folder? : ");
        else:
            return True;
    else:
        path = input("\nWhat is the path of your wallpaper folder? : ");

    if path[0] == '"':
        path = path.split(path)[1];

    writeInFile([pathData[0], ",", path, ";"]);

    print();
    return True;

def writeInFile(data):
    file = open('data.txt', 'w');

    for i in data:
        file.write(i);

    file.close();

def changeWallpaper():
    file = open("data.txt", "r");

    content = file.read();
    data = content.split(';');
    pathData = data[0].split(',');
    path = pathData[1];

    file.close();

    files = os.listdir(path);

    wallpaperName = files[random.randint(0, len(files) - 1)];
    ctypes.windll.user32.SystemParametersInfoW(20, 0, path + "\\" + wallpaperName, 0);

def help():
    text = ["Welcome in the wallpaper modifier documentation. Type help --function name for more infos about a specific function.", "", "How to use:", " _ <function name> [arguments] --optinal arguments", "", "List of functions:", " _ test [number to print]", " _ quit", ""];

    for i in text:
        print(i);

def error(command):
    print("\u001b[31mThe function: " + command + " does not exist ! Try to correct the function or use 'help' for more information about the avaible functions.\u001b[0m \n");

def main():
    welcome();

    if checkIfWindows() == False:
        return True;

    print("Type help to see command options.");
    while True:
        command = getInput();
        returnedValue = excecuteCommand(command);

        if returnedValue == False:
            bye();
            break

    """for i in range(50):
        print("\u001b[" + str(i) + "mHi, coders. This is pink output.\u001b[0m", " " + str(i));"""

if __name__ == "__main__":
    main();