#coding: utf-8
import utils
from scipy import sparse
import numpy as np

class Edge(object):

    def __init__(self, v1, v2, ids):
        """
        Construct an Edge from two vertex index

        :param v1:  First vertex index
        :param v2:  Second vertex index
        :param ids:  Edge ID
        """
        self._v1 = v1
        self._v2 = v2
        self.ids = ids
        self._f1 = None
        self._f2 = None

    def add_face(self, f_idx):
        if self._f1 is None:
            self._f1 = f_idx
        elif self._f2 is None:
            self._f2 = f_idx
        else:
            raise ValueError('Error, edge can not be linked to more than two faces')

    def num_face(self):
        """
        Indicate how many faces are attached to this edge

        :return: Number of face assigned
        """
        nf = 0
        nf += 1 if self._f1 else 0
        nf += 1 if self._f2 else 0
        return nf

    @property
    def f1(self):
        """
        Return first face associated to this edge

        :return: Face 1
        """
        return self._f1

    @property
    def f2(self):
        """
        Return second face associated to this edge

        :return: Face 2
        """
        return self._f2

class Mesh(object):

    def __init__(self, vertices=None, trilist=None):
        """
        Create a Mesh object from an array of vertices [Nx3] and an array of triangle [Kx3]

        :param vertices: Array of vertices
        :param trilist: Array of triangles
        """
        # List of vertices
        self.vertex = vertices
        # List of triangles
        self.tri = trilist
        # List of vertices connect to each vertices (neighbours)
        self.neighbour = None
        # List of edges connected to a given vertex
        self._eneighbour = dict()
        # List of edges
        self.edge = None
        # Adjacency matrix
        self._adj = None
        # Voronoi area
        self._voronoi_area = self._compute_voronoi_area()

    def save(self, filename):
        """
        Dump current mesh into an *.obj file

        :param filename:    Filename
        """
        utils.save_obj(self.vertex, self.tri, filename)

    def load(self, filename):
        """
        Load mesh from an *.obj file

        :param filename:    Path to obj file
        """
        self.vertex, self.tri = utils.load_obj(filename)
        self.neighbour = None
        self._eneighbour = dict()
        self.edge = None
        self._voronoi_area = self._compute_voronoi_area()

    def compute_laplacian(self, type):
        """
        Compute Laplacian operator for this mesh

        :param type:    Type of Laplacian (Combinatorial, Normalized, Cotan, ...)
        :return:    Degree, Adjacency matrix, Laplacian operator
        """
        if type == 'combinatorial':
            return self._combinatorial_laplacian()
        elif type == 'normalized':
            return self._normalized_laplacian()
        elif type == 'cotan':
            return self._cotan_laplacian()
        elif type == 'cotan_normalized':
            return self._cotan_laplacian(True)
        else:
            return None

    def _combinatorial_laplacian(self):
        """
        Compute combinatorial Laplacian, L = D - A

        :return:    Degree, Adjacency matrix, Laplacian operator
        """
        deg = self._compute_degree(self.tri)
        N = len(deg)
        adj = self._compute_adjacency_matrix()
        lap = sparse.spdiags(deg.T, [0], N, N, format='coo')
        lap -= adj
        return deg, adj, lap

    def _normalized_laplacian(self):
        """
        Compute combinatorial Laplacian, L = I - D ** -0.5 @ A @ D ** -0.5

        :return:    Degree, Adjacency matrix, Laplacian operator
        """
        deg = self._compute_degree(self.tri)
        N = len(deg)
        adj = self._compute_adjacency_matrix()
        lap = sparse.identity(N, dtype=np.float32, format='coo')
        D = sparse.spdiags(deg.T, [0], N, N, format='coo')
        D = D.power(-0.5)
        lap -= D.dot(adj.dot(D)) #@ (adj @ np.diagflat(deg ** -0.5))
        return deg, adj, lap

    def _cotan_laplacian(self, normalized=False):
        """
        Compute cotan laplacian operator

        :param normalized:  Indicate if the operator is normalized by the voronoi area
        :return:            Degree, Adjacency matrix, Laplacian
        """
        # Find edge's index between two vertex neighbour
        def _edge_common(v1, n1):
            for ve, idx in self._eneighbour[v1]:
                if ve == n1:
                    return self.edge[idx]
        # Compute degree
        deg = self._compute_degree(self.tri)
        # Adjacency
        adj = self._compute_adjacency_matrix()
        # Laplacian
        data = []
        ridx = []
        cidx = []
        for key, value in self._eneighbour.items():
            vi = key
            weights = []
            for neighbour, idx in value:
                edge = _edge_common(vi, neighbour)
                faces = [edge.f1, edge.f2]
                cotan = []
                ridx.append(vi)
                cidx.append(neighbour)
                for face in faces:
                    if face is not None:
                        # Compute cotan
                        v_idx = [v for v in self.tri[face, :] if v != vi and v != neighbour][0]
                        vp = self.vertex[v_idx, :]
                        u = self.vertex[vi, :] - vp
                        v = self.vertex[neighbour, :] - vp
                        cot = np.dot(u, v) / np.linalg.norm(np.cross(u, v))
                        cotan.append(cot)
                num = (self._voronoi_area[vi] * self._voronoi_area[neighbour])** 0.5 if normalized else len(cotan)
                w = -1.0 / num * np.sum(cotan)
                data.append(w)
                weights.append(w)
            # Add diag element
            ridx.append(vi)
            cidx.append(vi)
            data.append(-1.0 * np.sum(weights))
        # Create laplacian
        lap = sparse.coo_matrix((data, (ridx, cidx)), shape=adj.shape, dtype=adj.dtype)
        return deg, adj, lap

    def _compute_degree(self, trilist):
        """
        Compute node's degree

        :param trilist: Array of triangle
        :return:        Vertex degrees
        """
        return np.asarray(list(map(len, self.neighbour)),
                          dtype=np.float32).reshape((len(self.neighbour), 1))

    def _compute_adjacency_matrix(self):
        """
        Compute adjacency matrix

        :return:        Adjacency matrix
        """
        if self._adj is None:
            N = len(self.neighbour)

            ridx = []
            cidx = []
            data = []
            visited = []
            for idx, n_list in enumerate(self.neighbour):
                for n in n_list:
                    if (idx, n) and (n, idx) not in visited:
                        ridx.append(idx)
                        cidx.append(n)
                        data.append(1.0)
                        cidx.append(idx)
                        ridx.append(n)
                        data.append(1.0)
                        visited.append((idx, n))
                        visited.append((n, idx))
                    else:
                        a=0
            self._adj = sparse.coo_matrix((data, (ridx, cidx)), shape=(N,N), dtype=np.float32)
        return self._adj

    def _compute_edges(self, trilist):
        """
        Compute for each vertex the edges that are connected to them

        :param trilist: List of triangles
        :return:        Edge list
        """
        def _in_list(collections, value):
            f = [v for v in collections if v[0] == value]
            return f

        def _processed(p, v1, v2, ids):
            v1p = v1 in p
            v2p = v2 in p
            if v1p or v2p:
                f1 = []
                f2 = []
                if v1p:
                    f1 = _in_list(p[v1], v2)
                if v2p:
                    f2 = _in_list(p[v2], v1)
                if f1 and f2:
                    assert f1[0][1] == f2[0][1]
                    return f1[0][1]
                elif not f1 and not f2:
                    if v1p:
                        p[v1].append((v2, ids))
                    else:
                        p[v1] = [(v2, ids)]
                    if v2p:
                        p[v2].append((v1, ids))
                    else:
                        p[v2] = [(v1, ids)]
                    return -1
                else:
                    a = 0
                    raise ValueError('Should not reach this points')
            else:
                # New entry
                p[v1] = [(v2, ids)]
                p[v2] = [(v1, ids)]
                return -1

        edges = []
        self._eneighbour = dict()
        spoly = trilist.shape[1]
        # Iterate over all triangle
        for k, tri in enumerate(trilist):
            for i in range(spoly):
                v0 = tri[i]
                v1 = tri[(i + 1) % spoly]
                # Edge already in added ?
                idx = _processed(self._eneighbour, v0, v1, len(edges))
                if idx == -1:
                    # Create edge
                    idx = len(edges)
                    e = Edge(v0, v1, idx)
                    # Update list
                    edges.append(e)
                # Add face
                edges[idx].add_face(k)
        return edges

    def _compute_voronoi_area(self):
        """
        Compute for each vertex the voronoi cell's area
        :return:    Area
        """
        areas = []
        if self.vertex is not None:
            N = self.vertex.shape[0]
            for vidx in range(N):
                # Get all triangles connected to this vertex
                faces = set()
                for neighbour, eidx in self._eneighbour[vidx]:
                    edge = self.edge[eidx]
                    if edge.f1 is not None:
                        faces.add(edge.f1)
                    if edge.f2 is not None:
                        faces.add(edge.f2)
                # Compute area over all tri
                area = 0.0
                for f in faces:
                    # Query face
                    tri = self.tri[f,:]
                    v0 = tri[tri == vidx][0]
                    verts = np.where(tri != vidx)[0].tolist()
                    assert len(verts) == 2
                    v1 = tri[verts[0]]
                    v2 = tri[verts[1]]
                    # Compute Centroid + mid edges
                    centroid = np.mean(self.vertex[[v0, v1, v2], :], axis=0) - self.vertex[v0, :]
                    v1m = (self.vertex[v1, :] - self.vertex[v0, :]) / 2.0
                    v2m = (self.vertex[v2, :] - self.vertex[v0, :]) / 2.0
                    # Compute area
                    a = np.linalg.norm(np.cross(centroid, v1m)) / 2.0
                    a += np.linalg.norm(np.cross(centroid, v2m)) / 2.0
                    area += a
                # add to the list
                areas.append(area)
        return areas

    @property
    def vertex(self):
        """
        Access vertex storage

        :return: Array of vertices
        """
        return self.__vertex

    @property
    def tri(self):
        """
        Access triangle storage

        :return: Array of triangle
        """
        return self.__tri

    @property
    def neighbour(self):
        """
        Return list of vertices index connected to each node

        :return:    Adjacent vertex for each vertices
        """
        return self.__neighbour

    @property
    def edge(self):
        """
        Return list of edges

        :return:    Edges
        """
        return self.__edge

    @vertex.setter
    def vertex(self, vertices):
        """
        Set a new vertices

        :param vertices: Array of vertices to overwrite with
        """
        self.__vertex = vertices

    @tri.setter
    def tri(self, trilist):
        """
        Set a new list of triangles

        :param trilist: Array of triangles to overwrite with
        """
        self.__tri = trilist

    @neighbour.setter
    def neighbour(self, trilist):
        """
        Initialize neighbour structure

        :param trilist: Triangle list
        """
        self.__neighbour = utils.gather_neighbour(self.tri) if self.tri is not None else None

    @edge.setter
    def edge(self, edge):
        """
        Initialize edge list

        :param edge: edge
        """
        self.__edge = self._compute_edges(self.tri) if self.tri is not None else None


"""
@inproceedings{al2018adversarial,
  title={Adversarial deep learning for robust detection of binary encoded malware},
  author={Al-Dujaili, Abdullah and Huang, Alex and Hemberg, Erik and O'Reilly, Una-May},
  booktitle={2018 IEEE Security and Privacy Workshops (SPW)},
  pages={76--82},
  year={2018},
  organization={IEEE}
}
"""

import os
import sys

project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
sys.path.append(project_root)
from attacker.methods.attack_method import *

DEFAULT_PARAM = {
    'max_iteration': 50, # parameter k
    'batch_size': 50,
    'force_iteration': True # do not terminate the iteration even the miss-classification happens
}

class BCA_K(Attack):
    # Multi-step bit coordinate ascent
    # We modify bck_k a little bit to accommodate the non-binary feature

    def __init__(self, targeted_model, input_dim, insertion_perm_array, removal_perm_array, normalizer = None, verbose = False, **kwargs):
        super(BCA_K, self).__init__(targeted_model, input_dim, insertion_perm_array, removal_perm_array, normalizer, verbose)
        self.force_iteration = DEFAULT_PARAM['force_iteration']
        self.batch_size = DEFAULT_PARAM['batch_size']
        self.iterations = DEFAULT_PARAM['max_iteration']
        self.is_init_graph = False
        self.parse(**kwargs)

        # TF tensor
        self.clip_max_input = tf.placeholder(dtype=tf.float32, shape=(None, input_dim), name='CLIP_MAX')

    def graph(self, x_tensor, y_tensor = None):
        nb_classes = self.model.output_dim
        nb_features = self.model.input_dim
        scaled_max_extended = tf.maximum(
            tf.multiply(self.scaled_clip_max, tf.to_float(self.insertion_perm_array)) +
            tf.multiply(self.scaled_clip_min, 1. - tf.to_float(self.insertion_perm_array)),
            x_tensor
        )
        # Compute our initial search domain. We optimize the initial search domain
        # by removing all features that are already at their maximum values (if
        # increasing input features).
        # search_domain = tf.reshape(tf.cast(x_tensor < self.clip_max_input, tf.float32),
        #                            [-1, nb_features])
        search_domain = tf.reshape(tf.cast(x_tensor < scaled_max_extended, tf.float32),
                                   [-1, nb_features])

        # y_in_init = tf.reshape(tf.one_hot(self.targed_y_input, depth=nb_classes), [-1, nb_classes])

        # Loop variables
        # x_in: the tensor that holds the latest adversarial outputs that are in
        #       progress.
        # y_in: the tensor for target labels
        # domain_in: the tensor that holds the latest search domain
        # cond_in: the boolean tensor to show if more iteration is needed for
        #          generating adversarial samples
        def _cond(x_in, domain_in, i, cond_in):
            # Repeat the loop until we have achieved misclassification or
            # reaches the maximum iterations
            return tf.logical_and(tf.less(i, self.iterations), cond_in)

        def _body(x_in, domain_in, i, cond_in):
            logits = self.model.get_logits(x_in)
            preds = tf.nn.softmax(logits)
            preds_onehot = tf.one_hot(tf.argmax(preds, axis=1), depth=nb_classes)

            # get corresponding grads
            loss = tf.reduce_mean(
                tf.nn.sparse_softmax_cross_entropy_with_logits(
                    logits= logits,
                    labels= y_tensor
                )
            )
            grads, = tf.gradients(loss, x_in)

            # Remove the already-used input features from the search space
            # Subtract 2 times the maximum value from those value so that
            # they won't be picked later
            increase_coef = 2 * tf.cast(tf.equal(domain_in, 0), tf.float32)

            grads -= increase_coef \
                          * tf.reduce_max(tf.abs(grads), axis=1, keepdims=True)
            grads = tf.reshape(grads, shape=[-1, nb_features])

            # Create a mask to only keep features that match conditions
            scores_mask = tf.greater(grads, tf.to_float(0.))

            # Extract the best malware feature
            scores = tf.cast(scores_mask, tf.float32) * grads
            best = tf.argmax(scores, axis=1)
            p1_one_hot = tf.one_hot(best, depth=nb_features)

            # Check if more modification is needed for each sample
            # mod_not_done = tf.equal(tf.reduce_sum(y_in * preds_onehot, axis=1), 0)
            mod_not_done = tf.equal(preds_onehot[:,0], 0)

            if self.force_iteration:
                cond = (tf.reduce_sum(domain_in * tf.cast(scores_mask, tf.float32), axis=1) >= 1)
            else:
                cond = mod_not_done & (tf.reduce_sum(domain_in * tf.cast(scores_mask, tf.float32), axis=1) >= 1)

            cond_float = tf.reshape(tf.cast(cond, tf.float32), shape=[-1, 1])
            to_mod = p1_one_hot * cond_float

            domain_out = domain_in - to_mod

            to_mod_reshape = tf.reshape(
                to_mod, shape=([-1] + x_in.shape[1:].as_list()))
            x_out = tf.minimum(x_in + to_mod_reshape, self.scaled_clip_max)

            # Increase the iterator, and check if all miss-classifications are done
            i_out = tf.add(i, 1)
            cond_out = tf.reduce_any(cond)

            return x_out, domain_out, i_out, cond_out

        x_adv_batch, _2, _3, _4 = tf.while_loop(
            _cond,
            _body,
            [x_tensor, search_domain, 0, True]
        )

        return x_adv_batch

    def parse(self, iterations=50, batch_size=50, force_iteration = True, **kwargs):
        self.iterations = iterations
        self.batch_size = batch_size
        self.force_iteration = force_iteration
        if len(kwargs) > 0:
            print("unused hyper parameters.")

    def project_perturbations(self, x_input, perturbations):
        # boundary
        scaled_max_extended = tf.maximum(
            tf.multiply(self.scaled_clip_max, tf.to_float(self.insertion_perm_array)) + # upper bound for positions allowing perturbations
            tf.multiply(self.scaled_clip_min, 1. - tf.to_float(self.insertion_perm_array)), # may be useful to reset the lower bound
            x_input # upper bound for positions no perturbations allowed
        ) # broadcast leveraged
        return tf.clip_by_value(x_input + perturbations,
                                clip_value_min= x_input,
                                clip_value_max= scaled_max_extended)

    def init_graph(self):
        # TF tensor
        self.batch_x_adv = self.graph(self.model.x_input, self.model.y_input)
        self.is_init_graph = True

    def perturb(self, dataX, ground_truth_labels, sess = None):
        """
        generate the adversarial feature vectors for dataX
        :param dataX: feature vectors
        :param ground_truth_labels: label
        :param kwargs: parameters
        :return: tuple of pristine feature vectors, adversarial feature vectors, labels
        """
        if not self.is_init_graph:
            self.batch_x_adv = self.graph(self.model.x_input, self.model.y_input)

        try:
            input_data = utils.DataProducer(dataX, ground_truth_labels, batch_size= self.batch_size, name = 'test')
            # load model parameters
            sess_close_flag = False
            if sess is None:
                cur_checkpoint = tf.train.latest_checkpoint(self.model.save_dir)
                config_gpu = tf.ConfigProto(log_device_placement=True)
                config_gpu.gpu_options.allow_growth = True
                sess = tf.Session(config=config_gpu)
                saver = tf.train.Saver()
                saver.restore(sess, cur_checkpoint)
                sess_close_flag = True
        except IOError as ex:
            raise IOError("Failed to load data and model parameters.")

        with sess.as_default():
            x_adv = []

            for idx, X_batch, y_batch in input_data.next_batch():
                # update upper bound for each sample
                scaled_max_expand = np.tile(self.scaled_clip_max, (X_batch.shape[0], 1))  # exchange time with space
                scaled_max_expand[:, ~(self.insertion_perm_array == 1)] = X_batch[:, ~(
                            self.insertion_perm_array == 1)]
                _batch_x_adv = sess.run(self.batch_x_adv, feed_dict={
                    self.model.x_input: X_batch,
                    self.model.y_input: y_batch,
                    self.model.is_training: False
                })

                # accuracy
                if self.verbose:
                    curr_accuracy = utils.test_func(sess, self.model, _batch_x_adv, y_batch, batch_size=50)
                    print("\t Batch {}/{}, the current accuracy is {:.5} on adversarial examples".format(
                        idx,
                        input_data.mini_batches,
                        curr_accuracy)
                    )
                x_adv.append(_batch_x_adv)

            x_adv = np.concatenate(x_adv)
            if self.normalizer is not None:
                x_adv = np.rint(normalize_inverse(x_adv, self.normalizer))
                # check again
                x_adv_normalized = normalize_transform(x_adv, self.normalizer)
            else:
                x_adv_normalized = np.rint(x_adv)

            if self.verbose:
                accuracy = utils.test_func(sess, self.model, x_adv_normalized, ground_truth_labels, batch_size=50)
                print("The classification accuracy is {:.5} on adversarial feature vectors.".format(accuracy))

                perturbations_amount_l0 = np.mean(np.sum(np.abs(x_adv_normalized - dataX) > 1e-6, axis=1))
                perturbations_amount_l1 = np.mean(np.sum(np.abs(x_adv_normalized - dataX), axis=1))
                perturbations_amount_l2 = np.mean(np.sqrt(np.sum(np.square(x_adv_normalized - dataX), axis=1)))
                print("\t The average l0 norm of perturbations is {:.5}".format(perturbations_amount_l0))
                print("\t The average l1 norm of perturbations is {:.5}".format(perturbations_amount_l1))
                print("\t The average l2 norm of perturbations is {:.5}".format(perturbations_amount_l2))
            if sess_close_flag:
                sess.close()

        return dataX, x_adv_normalized, ground_truth_labels


# decompyle3 version 3.8.0
# Python bytecode 3.7.0 (3394)
# Decompiled from: Python 3.8.9 (default, Mar 30 2022, 13:51:17)
# [Clang 13.1.6 (clang-1316.0.21.2.3)]
# Embedded file name: output/Live/mac_64_static/Release/python-bundle/MIDI Remote Scripts/ableton/v3/control_surface/skin.py
# Compiled at: 2022-01-27 16:28:17
# Size of source mod 2**32: 1375 bytes
from __future__ import absolute_import, print_function, unicode_literals
from itertools import chain
from . import BasicColors
ON_SUFFIXES = ('enabled', 'on', 'pressed', 'selected')

class Skin:

    def __init__(self, colors=None, *a, **k):
        (super().__init__)(*a, **k)
        self._colors = {}
        if colors is not None:
            self._fill_colors(colors)

    def _fill_colors(self, colors, pathname=''):
        if getattr(colors, '__bases__', None):
            for base in colors.__bases__:
                self._fill_colors(base, pathname=pathname)

        for k, v in vars(colors).items():
            if k[:1] != '_':
                if callable(v):
                    self._fill_colors(v, '{}{}.'.format(pathname, k))
                else:
                    self._colors['{}{}'.format(pathname, k)] = v

    def __getitem__(self, key):
        if key not in self._colors:
            if key.lower().endswith(ON_SUFFIXES):
                return BasicColors.ON
            return BasicColors.OFF
        return self._colors[key]

def merge_skins(*skins):
    skin = Skin()
    skin._colors = dict(chain(*map(lambda s: s._colors.items(), skins)))
    return skin


ppr = float(input("Enter the player's PPR value: "))
value = float(input("Enter the player's projected value: "))
teams = int(input("Enter the number of teams in the draft: "))

total_budget = teams * 200  # total budget for all teams
remaining_budget = total_budget - 1  # subtract 1 for the current team
remaining_players = (teams - 1) * 16  # number of remaining roster spots

# calculate the average cost per remaining player
if remaining_players > 0:
    avg_cost = remaining_budget / remaining_players
else:
    avg_cost = 0

# calculate the player's value above replacement
par = avg_cost * 16  # projected auction value of a replacement-level player
var = value - par  # value above replacement

# calculate the player's optimal price
optimal_price = var + par + (ppr * 0.1)  # add 10 cents for each PPR point

print(f"The optimal price for this player is ${optimal_price:.2f}")


import glob
import pickle
from collections import deque
from itertools import product

import cv2
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from moviepy.editor import VideoFileClip
from natsort import natsorted
from sklearn.linear_model import ElasticNet

sns.set_style("whitegrid", {'axes.grid': False})

def get_obj_img_pts(img_cal_names, num_x=9, num_y=6):
    '''Generate object/image points given filenames of calibration images

    params:
        num_x = the number of inner corner points along the x-axis of the test grid
        num_y = the number of inner corner points along the y-axis of the test grid
    '''

    # generate object points
    obj_pt = np.array(list(product(range(num_y), range(num_x), range(1))), np.float32)
    obj_pt[:, [0, 1]] = obj_pt[:, [1, 0]]

    obj_pts = []
    img_pts = []
    img_cals = []
    img_cal_names_ret = []

    for idx, img_cal_name in enumerate(img_cal_names):
        img_cal = mpimg.imread(img_cal_name)
        img_gray = cv2.cvtColor(img_cal, cv2.COLOR_RGB2GRAY)

        ret, img_pt = cv2.findChessboardCorners(img_gray, (num_x, num_y), None)
        if ret:
            print('corners_found: {}'.format(img_cal_name))
            obj_pts.append(obj_pt)
            img_pts.append(img_pt)

            # visualize the image points on calibration images
            cv2.drawChessboardCorners(img_cal, (num_x, num_y), img_pt, ret)

            img_cals.append(img_cal)
            img_cal_names_ret.append(img_cal_name)

    return obj_pts, img_pts, img_cals, img_cal_names_ret

def correct_dist(img, obj_pts, img_pts):
    '''Undistort an image given object/image points
    '''

    img_size = (img.shape[1], img.shape[0])
    ret, mtx, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(obj_pts, img_pts, img_size, None, None)

    return cv2.undistort(img, mtx, dist_coeffs, None, mtx)

def img_subplots(imgs, img_names=None, f_size=(12, 10), f_cols=4):
    '''Create subplots of images and return figure handle
    '''

    assert (len(imgs) == len(img_names))

    f_rows = np.ceil(len(imgs) / f_cols).astype('int')

    fig, f_axes = plt.subplots(f_rows, f_cols, figsize=f_size)
    fig.set_tight_layout(True)

    for idx, f_ax in enumerate(f_axes.reshape(-1)):
        f_ax.axis("off")
        if idx < len(imgs):
            img = imgs[idx]

            color_map = "gray" if len(img.shape) == 2 else None
            f_ax.imshow(img, cmap=color_map)

            if img_names is not None:
                f_ax.set_title(img_names[idx])

    return fig

def canny(img, low_threshold, high_threshold):
    """Applies the Canny transform"""
    return cv2.Canny(img, low_threshold, high_threshold)

def region_of_interest(img, roi_vertex_scales):
    """Applies an image mask.

    Only keeps the region of the image defined by the polygon
    formed from `vertices`. The rest of the image is set to black.

    vertices shall have the shape (num_of_polygon, num_of_vertices, 2)
    eg: vertices = np.array([[(wd*.45, ht*.53),(wd*.05, ht), (wd*.98, ht), (wd*.55, ht*.53)]], dtype=np.int32)
    """
    # defining a blank mask to start with
    mask = np.zeros_like(img)

    # defining a 3 channel or 1 channel color to fill the mask with depending on the input image
    if len(img.shape) > 2:
        channel_count = img.shape[2]  # i.e. 3 or 4 depending on your image
        mask_color = (255,) * channel_count
        ht, wd, _ = img.shape
    else:
        mask_color = 255
        ht, wd = img.shape

    vertices = np.int32([[(wd * wd_scale, ht * ht_scale) for (wd_scale, ht_scale) in roi_vertex_scales]])

    # filling pixels inside the polygon defined by "vertices" with the fill color
    cv2.fillPoly(mask, vertices, mask_color)

    # returning the image only where mask pixels are nonzero
    masked_image = cv2.bitwise_and(img, mask)

    return masked_image

def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap, line_thickness=2, line_color=(0, 255, 0)):
    """Returns an image with hough lines drawn.

    `img` should be the output of a Canny transform.
    """
    lines = cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength=min_line_len, maxLineGap=max_line_gap)
    line_img = np.zeros((*img.shape, 3), dtype=np.uint8)
    draw_lines(line_img, lines, thickness=line_thickness, color=line_color)

    return line_img

def draw_lines(img, lines, thickness=2, color=(255, 0, 0)):
    """ Draw interpolated lanes on img

    """

    lane_1st, lane_2nd = [], []
    height, width, _ = img.shape

    # separate the line segments based on slope and their position in the image
    for line in lines:
        for x1, y1, x2, y2 in line:
            if ((x2 - x1) != 0) and ((y2 - y1) / (x2 - x1) < 0) and ((x1 + x2) / 2 / width < 0.55):
                lane_1st.append(line)
            elif ((x2 - x1) != 0) and ((y2 - y1) / (x2 - x1) > 0) and ((x1 + x2) / 2 / width > 0.55):
                lane_2nd.append(line)

    # fit the left and right lane separately with ElasticNet
    x_pred = np.arange(img.shape[1]).reshape(-1, 1)
    for lane in [np.array(lane_1st), np.array(lane_2nd)]:
        lane = lane.reshape(lane.shape[0] * 2, 2)
        X, y = lane[:, 0], lane[:, 1]
        reg = ElasticNet().fit(X.reshape(-1, 1), y)
        y_pred = np.hstack((x_pred, reg.predict(x_pred).reshape(-1, 1)))

        cv2.polylines(img, np.int32([y_pred]), False, color, thickness)

def gaussian_blur(img, kernel_size):
    """Applies a Gaussian Noise kernel"""

    return cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)

def select_color(img, colors):
    ''' Return img with specified color selected

        colors is a list of (color_lower, color_upper) tuples
    '''

    img_color_select = np.zeros_like(img)

    for color_lower, color_upper in colors:
        color_mask = cv2.inRange(img, color_lower, color_upper)
        img_color_select += cv2.bitwise_and(img, img, mask=color_mask)

    return img_color_select

def sobel_thresh(img, th=(30, 100), kernel_size=3, op_dirs=(1, 0), debug=False):
    '''Absolute gradient thresholding
    '''

    if len(img.shape) == 3:
        img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    img_sobel = np.absolute(cv2.Sobel(img, cv2.CV_64F, *op_dirs, ksize=kernel_size))
    img_sobel_scaled = np.uint8(255 * img_sobel / np.max(img_sobel))
    img_bin = img2binary(img_sobel_scaled, th)

    if debug:
        return img_sobel_scaled, img_bin
    else:
        return img_bin

def mag_thresh(img, th=(30, 100), kernel_size=3, debug=False):
    '''Gradient magnitude thresholding
    '''

    if len(img.shape) == 3:
        img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    img_sobel_x = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=kernel_size)
    img_sobel_y = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=kernel_size)
    img_sobel_mag = np.sqrt(img_sobel_x ** 2 + img_sobel_y ** 2)
    img_sobel_scaled = np.uint8(255 * img_sobel_mag / np.max(img_sobel_mag))
    img_bin = img2binary(img_sobel_scaled, th)

    if debug:
        return img_sobel_scaled, img_bin
    else:
        return img_bin

def img2binary(img, th=(75, 225)):
    '''Covert an image to a binary mask given thresholds

    '''
    if len(img.shape) == 3:
        img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    img_bin = np.zeros_like(img)
    img_bin[(img > th[0]) & (img <= th[1])] = 1

    return img_bin

def threshold_multi(img, roi_vertex_scales, colors_rgb, colors_hls, sobel_th=(80, 150), debug=False):
    img = gaussian_blur(img, kernel_size=3)

    img_rgb = img
    img_hls = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2HLS)
    img_yuv = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2YUV)

    # color selection in RGB and HLS space
    img_rgb_bin = img2binary(select_color(img_rgb, colors_rgb), th=[0, 255])
    img_hls_bin = img2binary(select_color(img_hls, colors_hls), th=(0, 255))
    img_color_bin = img_rgb_bin | img_hls_bin

    # U abs gradient th (YUV)
    img_channel = img_yuv[:, :, 1]
    img_u_sobel = sobel_thresh(img_channel, th=sobel_th, kernel_size=9, op_dirs=[1, 0])

    # combine thresholded binary images
    img_bin_combined = img_color_bin | img_u_sobel
    img_bin_combined_roi = region_of_interest(img_bin_combined, roi_vertex_scales)

    if debug:
        return img_color_bin, img_u_sobel, img_bin_combined, img_bin_combined_roi
    else:
        return img_bin_combined_roi

def get_perspective_matrix(img, src_scales, dst_scales):
    if len(img.shape) == 3:
        ht, wd, _ = img.shape
    elif len(img.shape) == 2:
        ht, wd = img.shape
    else:
        raise Exception("Only 2D images are supported.")

    src = np.float32([(wd * wd_scale, ht * ht_scale) for (wd_scale, ht_scale) in src_scales])
    dst = np.float32([(wd * wd_scale, ht * ht_scale) for (wd_scale, ht_scale) in dst_scales])

    M = cv2.getPerspectiveTransform(src, dst)
    inv_M = cv2.getPerspectiveTransform(dst, src)

    return M, inv_M, src

def get_binary_lane(img_strips, window_scale, offset=0.10):
    '''Return a segmented lane using the sliding window method'''
    lane = []
    window = np.array(window_scale) * img_strips[0].shape[1]
    for img_strip in reversed(img_strips):

        img_windowed = np.zeros_like(img_strip)
        img_windowed[:, window[0]:window[1]] = img_strip[:, window[0]:window[1]]

        lane_pts_x = np.where(np.sum(img_windowed, axis=0))
        if len(lane_pts_x[0]) > 5:
            lane.append(img_windowed)
            lane_mean = np.mean(lane_pts_x)
            lane_offset = offset * img_strip.shape[1]
            window = [int(lane_mean - lane_offset), int(lane_mean + lane_offset)]

        else:
            lane.append(np.zeros_like(img_windowed))

    return np.vstack(reversed(lane))

def fit_lane_pts(pts, y_fit_range=None, num_pts_y_fit=300):
    '''Return fitted points or coefficeints of 2nd order fitting x = F(y).

    params:
        pts: tuple of x_array and y_array `(x_array, y_array)`
    '''

    pts_x, pts_y = reversed(pts)
    coeffs = np.polyfit(pts_y, pts_x, 2)

    if y_fit_range is not None:
        pts_y_fit = np.linspace(0, y_fit_range, num=num_pts_y_fit)
        pts_x_fit = np.polyval(coeffs, pts_y_fit)

        return pts_x_fit, pts_y_fit

    else:
        return coeffs

# def fit_lane_pts_with_coeffs(coeffs, y_fit_range, num_pts_y_fit=300):
#     '''Return fitted points given coefficients
#
#     params:
#         coeffs: 2nd order fitting coefficients with the highest order first
#     '''
#
#     pts_y_fit = np.linspace(0, y_fit_range, num=num_pts_y_fit)
#     pts_x_fit = np.polyval(coeffs, pts_y_fit)
#
#     return pts_x_fit, pts_y_fit

def calc_curvature(pts, xm_per_pix=3.7 / 700, ym_per_pix=30 / 720):
    '''Calculate curvature given scales from pixel space to real physical space'''

    pts = np.array(pts).T * np.array([ym_per_pix, xm_per_pix])
    pts = (pts[:, 0], pts[:, 1])
    coeffs = fit_lane_pts(pts)

    y_eval = np.max(pts[1])
    curve_radius = ((1 + (2 * coeffs[0] * y_eval + coeffs[1]) ** 2) ** 1.5) / np.absolute(2 * coeffs[0])

    return curve_radius

def lane_detection(img, ROI_vertex_scales,
                   src_scales, dst_scales,
                   colors_rgb,
                   colors_hls,
                   sobel_th=(80, 150),
                   num_bins=20,
                   window_L=(0.1, 0.45),
                   window_R=(0.6, 0.90),
                   draw_lane_color=(0, 255, 0),
                   debug=False):
    img_corr = correct_dist(img, obj_pts, img_pts)

    ht, wd, _ = img.shape
    M, inv_M, pts_src = get_perspective_matrix(img, src_scales, dst_scales)
    img_warped = cv2.warpPerspective(img_corr, M, (wd, ht), flags=cv2.INTER_LINEAR)

    # thresholding corrected image and
    # perspective transformation of the resulting binary image
    img_bin = threshold_multi(img_corr, ROI_vertex_scales, colors_rgb=colors_rgb, colors_hls=colors_hls, sobel_th=sobel_th)
    img_bin_warped = cv2.warpPerspective(img_bin, M, (wd, ht), flags=cv2.INTER_LINEAR)

    # split perpective transformed binary image into multiple horizontal strips
    # img_bin_blurred = gaussian_blur(img_bin_warped, kernel_size=blur_kernel_size)
    img_bin_splits = np.vsplit(img_bin_warped, num_bins)

    # isolate the left and right lane with sliding windows
    lane_L = get_binary_lane(img_bin_splits, window_L)
    lane_R = get_binary_lane(img_bin_splits, window_R)

    pts_L = np.where(lane_L)
    pts_R = np.where(lane_R)

    if (len(pts_L[0]) < 3) | (len(pts_R[0]) < 3):
        return img_corr

    # calculate curvature for left/right lane
    pts_fit_L = fit_lane_pts(pts_L, y_fit_range=img_bin.shape[0], num_pts_y_fit=300)
    curve_radius_L = calc_curvature(pts_L)

    pts_fit_R = fit_lane_pts(pts_R, y_fit_range=img_bin.shape[0], num_pts_y_fit=300)
    curve_radius_R = calc_curvature(pts_R)

    # [curve_radius_L, curve_radius_R]

    # create an image to draw the lines on
    lane_warped_color = np.zeros_like(img_corr, dtype=np.uint8)

    # draw fitted points to a lane image
    pts_draw = np.hstack([pts_fit_L, np.fliplr(pts_fit_R)]).T.reshape(-1, 1, 2).astype(np.int)
    cv2.fillPoly(lane_warped_color, [pts_draw], draw_lane_color)

    # inverse perspective transform of lane image
    lane_color = cv2.warpPerspective(lane_warped_color, inv_M, (wd, ht), flags=cv2.INTER_LINEAR)

    # overlay detected lanes with the undistorted image
    img_combined = cv2.addWeighted(img_corr, 1, lane_color, 0.3, 0)

    if debug:
        print("The left  curvature is {:.1f} m".format(curve_radius_L))
        print("The right curvature is {:.1f} m".format(curve_radius_R))
        print("")

        pts_warp_roi = np.int32(pts_src.reshape([-1, 1, 2]))
        pts_roi = np.int32([[(wd * wd_scale, ht * ht_scale) for (wd_scale, ht_scale) in ROI_vertex_scales]])

        img_warp_roi = cv2.polylines(img_corr, [pts_warp_roi], True, (0, 255, 0), 5)  # draw the warp region in green
        img_warp_roi = cv2.polylines(img_warp_roi, [pts_roi], True, (0, 0, 255), 5)  # draw the roi selection in blue

        return img_warp_roi, img_warped, img_bin, img_bin_warped, lane_L, lane_R, lane_warped_color, img_combined
    else:
        return img_combined

# Define a class to receive the characteristics of each line detection
class LaneDetector:
    def __init__(self, N=30, TOLERANCE_CURVATURE=2, TOLERANCE_PTS=100):

        # append to history if the current curvature is less than TOLERANCE_CURVATURE
        # compared to the average curvature over N confident frames
        self.TOLERANCE_CURVATURE = TOLERANCE_CURVATURE

        # proceed with lane curve fitting if detected points are greater than TOLERANCE_PTS
        self.TOLERANCE_PTS = TOLERANCE_PTS

        # x,y values and fitted polynomial coeffs of the last n fits
        # assuming 30 frames per second
        self.N = N
        self.pts_fit_L_last_n = deque(maxlen=self.N)
        self.pts_fit_R_last_n = deque(maxlen=self.N)

        # average x,y values of the fitted lanes over the last n fit
        self.pts_L_last = None
        self.pts_R_last = None

        # radius of curvature of the line in some units
        self.curve_radius = 0
        self.curve_radius_last_n = deque(maxlen=self.N)
        self.curve_radius_avg = 0
        self.curve_radius_diff = 0

        # distance in meters of vehicle center from the line
        self.vehicle_offset = None
        self.vehicle_offset_last_n = deque(maxlen=self.N)
        self.vehicle_offset_avg = None

        # # difference in fit coefficients between last and new fits
        # self.coeffs_L_last_n = deque(maxlen=self.N)
        # self.coeffs_R_last_n = deque(maxlen=self.N)
        # self.coeffs_L_avg = None
        # self.coeffs_R_avg = None
        # self.fit_coeffs_diffs = np.array([0, 0, 0], dtype='float')

        # lane mask
        self.lane_mask = None
        self.lane_masks = []

        # problematic frames
        self.frame_N = 0
        self.error_frame_N = 0
        self.error_frames = []

    def get_binary_lane(self, img_strips, window_scale, offset=0.10):
        '''Return a segmented lane using the sliding window method'''

        lane = []
        img_window_masks = []
        window = (np.array(window_scale) * img_strips[0].shape[1]).astype(np.int)
        for img_strip in reversed(img_strips):

            img_windowed = np.zeros_like(img_strip)
            img_windowed[:, window[0]:window[1]] = img_strip[:, window[0]:window[1]]

            img_window_mask = np.zeros_like(img_strip)
            img_window_mask[:, window[0]:window[1]] = 1
            img_window_masks.append(img_window_mask)

            lane_pts_x = np.where(np.sum(img_windowed, axis=0))
            if len(lane_pts_x[0]) > 5:
                lane.append(img_windowed)
                lane_mean = np.mean(lane_pts_x)
                lane_offset = offset * img_strip.shape[1]
                window = [int(lane_mean - lane_offset), int(lane_mean + lane_offset)]

            else:
                lane.append(np.zeros_like(img_windowed))

        return np.vstack(reversed(lane)), np.vstack(reversed(img_window_masks))

    def calc_curvature(self, pts, xm_per_pix=3.7 / 700, ym_per_pix=30 / 720):
        '''Calculate curvature given scales from pixel space to real physical space'''

        pts = np.array(pts).T * np.array([ym_per_pix, xm_per_pix])
        pts = (pts[:, 0], pts[:, 1])
        coeffs = fit_lane_pts(pts)

        y_eval = np.max(pts[1])
        curve_radius = ((1 + (2 * coeffs[0] * y_eval + coeffs[1]) ** 2) ** 1.5) / np.absolute(2 * coeffs[0])

        return curve_radius, coeffs

    def lane_detection(self, img, ROI_vertex_scales,
                       src_scales, dst_scales,
                       colors_rgb,
                       colors_hls,
                       sobel_th=(80, 150),
                       num_bins=20,
                       window_L=(0.1, 0.45),
                       window_R=(0.6, 0.90),
                       draw_lane_color=(0, 255, 0),
                       debug=False):

        img_corr = correct_dist(img, obj_pts, img_pts)

        ht, wd, _ = img.shape
        M, inv_M, pts_src = get_perspective_matrix(img, src_scales, dst_scales)
        img_warped = cv2.warpPerspective(img_corr, M, (wd, ht), flags=cv2.INTER_LINEAR)

        # thresholding corrected image and
        # perspective transformation of the resulting binary image
        img_bin = threshold_multi(img_corr, ROI_vertex_scales, colors_rgb=colors_rgb, colors_hls=colors_hls, sobel_th=sobel_th)
        img_bin_warped = cv2.warpPerspective(img_bin, M, (wd, ht), flags=cv2.INTER_LINEAR)

        # split perpective transformed binary image into multiple horizontal strips
        img_bin_splits = np.vsplit(img_bin_warped, num_bins)

        # isolate the left and right lane with masks generated with sliding windows
        # if lane_mask is not defined, search the lane lines from scratch
        # else use the previous window for lane lines detection
        if not self.lane_mask:
            lane_L, mask_L = self.get_binary_lane(img_bin_splits, window_L)
            lane_R, mask_R = self.get_binary_lane(img_bin_splits, window_R)
            self.lane_mask = [mask_L, mask_R]

        else:
            mask_L, mask_R = self.lane_mask
            lane_L = cv2.bitwise_and(img_bin_warped, mask_L)
            lane_R = cv2.bitwise_and(img_bin_warped, mask_R)

        # get (i,j) coordinates for the lane points
        pts_L = np.where(lane_L)
        pts_R = np.where(lane_R)

        # if the number of lane points detected is less than TOLERANCE_PTS for either lane,
        # use the detected points from the last and current frame for subsequent fitting
        if (len(pts_L[0]) < self.TOLERANCE_PTS) | (len(pts_R[0]) < self.TOLERANCE_PTS):
            self.lane_mask = None
            self.error_frame_N += 1
            self.error_frames.append(img)

            if self.pts_L_last is not None:
                # concatenate (i,j) coordinates of points detected for the last and current frame
                pts_L = [pts_last + pts for (pts_last, pts) in zip(self.pts_L_last, pts_L)]
                pts_R = [pts_last + pts for (pts_last, pts) in zip(self.pts_R_last, pts_R)]
            else:
                return img_corr
        else:
            self.pts_L_last = pts_L
            self.pts_R_last = pts_R

        # calculate curvature for left/right lane
        # the curve radius is estimated as the mean of left/right lane, which is smoothed over the last n frames
        pts_fit_L = fit_lane_pts(pts_L, y_fit_range=img_bin.shape[0], num_pts_y_fit=ht)
        curve_radius_L, coeffs_L = self.calc_curvature(pts_L)

        pts_fit_R = fit_lane_pts(pts_R, y_fit_range=img_bin.shape[0], num_pts_y_fit=ht)
        curve_radius_R, coeffs_R = self.calc_curvature(pts_R)

        self.curve_radius = np.mean([curve_radius_L, curve_radius_R])
        self.curve_radius_diff = np.abs((self.curve_radius - self.curve_radius_avg) / self.curve_radius_avg)

        # if the lane curve difference is less than TOLERANCE_CURVATURE or is the first frame
        # append the current curvature and coefficients to their respective double ended queue
        if (self.curve_radius_diff < self.TOLERANCE_CURVATURE) or (self.frame_N == 0):
            self.curve_radius_last_n.append(self.curve_radius)
            self.curve_radius_avg = np.mean(self.curve_radius_last_n)

            # self.coeffs_L_last_n.append(coeffs_L)
            # self.coeffs_R_last_n.append(coeffs_R)
            # self.coeffs_L_avg = np.mean(self.coeffs_L_last_n, axis=0)
            # self.coeffs_R_avg = np.mean(self.coeffs_R_last_n, axis=0)

        else:
            self.lane_mask = None

        # estimate vehicle offset from the center of the road
        # using the x coordinates of the last 10 points from the bottom of the frame
        xm_per_pix = 3.7 / 700  # meters per pixel

        # here a negative sign is needed to measure offsets with respect to the center of the road
        self.vehicle_offset = -xm_per_pix * (np.mean(pts_fit_L[0][-10:]) + np.mean(pts_fit_R[0][-10:]) - wd) / 2
        self.vehicle_offset_last_n.append(self.vehicle_offset)
        self.vehicle_offset_avg = np.mean(self.vehicle_offset_last_n)

        # create an image to draw fitted points on
        lane_warped_color = np.zeros_like(img_corr, dtype=np.uint8)

        # draw fitted points to a lane image
        pts_draw = np.hstack([pts_fit_L, np.fliplr(pts_fit_R)]).T.reshape(-1, 1, 2).astype(np.int)
        cv2.fillPoly(lane_warped_color, [pts_draw], draw_lane_color)

        # inverse perspective transform of lane image
        lane_color = cv2.warpPerspective(lane_warped_color, inv_M,
                                         (wd, ht), flags=cv2.INTER_LINEAR)
        lane_color = region_of_interest(lane_color, ROI_vertex_scales)

        # overlay detected lanes with the undistorted image
        img_combined = cv2.addWeighted(img_corr, 1, lane_color, 0.3, 0)

        # draw text onto the image
        img_txt = "Radius of curvature: {:7.1f}m  Offset from road center: {:7.3f}m  Errors: {:3.0f} /{:5.0f}".format(self.curve_radius_avg,
                                                                                self.vehicle_offset_avg,
                                                                                self.error_frame_N,
                                                                                self.frame_N)
        img_txt_offset = (int(wd * 0.01), int(ht * 0.04))

        pts_txt_bounding_box = np.int32([(0, 0), (wd, 0), (wd, ht * 0.05), (0, ht * 0.05)]).reshape([-1, 1, 2])
        img_combined = cv2.fillPoly(img_combined, [pts_txt_bounding_box], (43, 43, 43))

        cv2.putText(img_combined,
                    img_txt,
                    img_txt_offset,
                    cv2.FONT_HERSHEY_COMPLEX, 0.8,
                    (250, 250, 250), 1)

        self.frame_N += 1

        if debug:
            print("The left  curvature is {:.1f} m".format(curve_radius_L))
            print("The right curvature is {:.1f} m".format(curve_radius_R))
            print("")

            # draw perspective warp and ROI bounding box
            pts_warp_roi = np.int32(pts_src.reshape([-1, 1, 2]))
            pts_roi = np.int32([[(wd * wd_scale, ht * ht_scale) for (wd_scale, ht_scale) in ROI_vertex_scales]])

            img_warp_roi = cv2.polylines(img_corr, [pts_warp_roi], True, (0, 255, 0), 5)  # blue for perspective transform bounding box
            img_warp_roi = cv2.polylines(img_warp_roi, [pts_roi], True, (0, 0, 255), 5)  # green for ROI bounding box

            return img_warp_roi, img_warped, img_bin, img_bin_warped, lane_L, lane_R, lane_warped_color, img_combined
        else:
            return img_combined

def process_image(img):
    # NOTE: The output you return should be a color image (3 channel) for processing video below
    # the solution is detailed in the function lane_detection above

    return lane_detector.lane_detection(img, ROI_vertex_scales, src_scales, dst_scales,
                                        colors_rgb, colors_hls,
                                        window_L=window_L, window_R=window_R, debug=False)

NUM_X = 9
NUM_Y = 6
LOAD_OBJ_IMG_PTS = True
img_cal_names = natsorted(glob.glob('camera_cal/*.jpg'))

if not LOAD_OBJ_IMG_PTS:
    obj_pts, img_pts, img_cals, img_cal_names_ret = get_obj_img_pts(img_cal_names, num_x=NUM_X, num_y=NUM_Y)

    with open(r"obj_img_pts", "wb") as file_output:
        pickle.dump([obj_pts, img_pts], file_output)

else:
    with open(r"obj_img_pts", "rb") as file_input:
        obj_pts, img_pts = pickle.load(file_input)

ROI_vertex_scales = [(0.48, 0.59), (0.52, 0.59), (0.65, 0.65), (0.95, 1), (0.05, 1), (0.35, 0.65)]

src_x_top, src_x_bot = 0.42, 0.065  # counting from the left edge
src_y_top, src_y_bot = 0.67, 0

dst_x_top, dst_x_bot = 0.2, 0.2  # counting from the left edge
dst_y_top, dst_y_bot = 0.2, 0

src_scales = [(src_x_top, src_y_top), (1 - src_x_top, src_y_top),
              (1 - src_x_bot, 1 - src_y_bot), (src_x_bot, 1 - src_y_bot)]
dst_scales = [(dst_x_top, dst_y_top), (1 - dst_x_top, dst_y_top),
              (1 - dst_x_top, 1 - dst_y_top), (dst_x_top, 1 - dst_y_bot)]

colors_rgb = [(np.uint8([190, 190, 190]), np.uint8([255, 255, 255]))]

colors_hls = [(np.uint8([0, 120, 150]), np.uint8([75, 255, 255])),
              (np.uint8([75, 180, 0]), np.uint8([120, 255, 35]))]

window_L = (0.1, 0.45)
window_R = (0.6, 0.90)

lane_detector = LaneDetector()

# img_test_names = natsorted(glob.glob("test_images/*.jpg"))
# for img_test_name in img_test_names[0:2]:
#     print(img_test_name)
#     img = mpimg.imread(img_test_name)
#
#     imgs = lane_detector.lane_detection(img, ROI_vertex_scales, src_scales, dst_scales,
#                                         colors_rgb, colors_hls,
#                                         window_L=window_L, window_R=window_R, debug=True)
#
#     img_names = ["undistorted", "perspective warped", "thresholded", "thresholded warped",
#                  "left lane", "right lane", "warped detected lanes", "combined with detected lanes"]
#
#     fig = img_subplots(imgs, img_names, f_size=(10, 12), f_cols=2)
#     fig.suptitle(img_test_name, y=1.05, fontsize=16)
#     plt.axis("on")
#
# plt.show()

# clip_files = ["test.mp4", "challenge_video.mp4", "harder_challenge_video.mp4"]
clip_files = ["project_video.mp4", "challenge_video.mp4", "harder_challenge_video.mp4"]
for clip_file in clip_files[0:1]:
    clip = VideoFileClip(clip_file)
    clip_out = clip.fl_image(process_image)
    clip_out.write_videofile("z_sol_" + clip_file, audio=False)
    print("======================================================")


from typing import List

def daily_temperatures(t: List[int]) -> List[int]:
    result = [0] * len(t)
    stack: List[int] = []
    for i in range(len(t)):
        while stack and t[stack[-1]] < t[i]:
            last = stack.pop()
            result[last] = i - last
        stack.append(i)
    return result


import json
from flask import Blueprint, request, redirect, render_template
from bs4 import BeautifulSoup
import requests
from news.models import WorldNews
from news import db
from sqlalchemy import text, engine

cbsnews = Blueprint('cbsnews', __name__)

def get_cbs_news():

    world_url = "https://www.cbsnews.com/latest/world/"

    news_page = requests.get(world_url).text

    item = BeautifulSoup(news_page, 'lxml')

    world_page = item.find('div', {'class':'component__item-wrapper'})
    article_page = world_page.find_all('article', {'class':'item lazyload item--type-article item--topic-world'})

    cbs_news = []

    for item in article_page:
        title = item.find('div', {'class':'item__title-wrapper'}).h4.text.strip()
        link = item.a.get('href')
        image = item.span.img.get('src')
        description = item.find('div', {'class':'item__title-wrapper'}).p.text.strip()

        news_items = {'Title':title, 'Link':link, 'Image':image, 'Description':description}
        news_items.update(news_items)

        cbs_news.append({
            'Title': title,
            'Link': link,
            'Image': image,
            'Description':description
        })

    return cbs_news

@cbsnews.route('/cbs-news', methods=['GET', 'POST'])
def cbs_news():
    if request.method == 'POST':
        return redirect('/')

    # new data from world news
    data = get_cbs_news()

    # existing data from database
    wnews =WorldNews.get_all_news()

    # loop through data
    for news in data:
        # check if news already exists in database
        # news['title'] is the title of the news
        if news.get('title').lower() not in [wn.title.lower() for wn in wnews]:
            wnews = WorldNews(title=news['title'], link=news['link'])
            wnews.save()
        else:
            continue

    return render_template('world_news.html', data=wnews)

@cbsnews.route('/world-news/search', methods=['GET', 'POST'])
def search_cbs_news():
    # /world-news/search?q=python
    query = request.args['q']
    # search in database for anythign that matches the query
    # SELECT * FROM world_news WHERE title LIKE '%python%'
    # data = db.session.query(worldNews).filter(worldNews.title.like(f'%{query}%')).all()
    # sql = text('SELECT * FROM world_news WHERE title LIKE :query')
    # data = db.engine.execute(sql, query=f'%{query}%').fetchall()
    data = WorldNews.query.filter(WorldNews.title.like(f'%{query}%')).all()
    # print(data)
    # [<HackNews 1>, <HackNews 2>]
    return {'data': [news.serialize() for news in data]}

@cbsnews.route('/search')
def search():
    return render_template('search.html')


import os
import sys
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(os.path.dirname(SCRIPT_DIR))

from typing import final

from task_9.task_9 import Any

class FirstClass(Any):

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)

class SecondClass(Any):

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)

@final
class NoneType(FirstClass, SecondClass):

    @final
    def __new__(cls, **kwargs):
        return None

if __name__ == '__main__':
    a: FirstClass = FirstClass(content=1)
    b: SecondClass = SecondClass(content=2)
    x: Any = NoneType()

    for obj in [a, b, x]:
        if obj is None:
            continue
        print(obj.attributes())


from django.db import models
from django.contrib.auth.models import User

class Comment(models.Model):
    class Meta(object):
        verbose_name = 'Comment'
        verbose_name_plural = 'Comments'

    id = models.AutoField(
    primary_key = True)
    text = models.TextField(
        max_length=2000,
        help_text='Please, your Comment',
        verbose_name='Comment',
        blank=True)
    seq=models.PositiveSmallIntegerField(default=0)
    public_date = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(
    User,
    on_delete= models.CASCADE,)

class TreeOrderField(models.CharField):
	def pre_save(self, model_instance, add):
		if add:
			parent=(model_instance.parent or model_instance.coment);
			if parent.seq<1000:
				parent.seq+=1;
				parent.save();
			else:
				pass
			value=('%s%03d'%(getattr(parent,
                                    self.attname, ''),
                                    parent.seq,))[:255]
			setattr(model_instance,
                    self.attname,
                    value)
			return value
		return models.CharField.pre_save(self, model_instance, add)

class SubComment(models.Model):
    coment=models.ForeignKey(
                            Comment,
                            on_delete = models.CASCADE)
    parent=models.ForeignKey(
                            'self',
                            blank=True,
                            null=True,
                            related_name='child_set',
                            on_delete = models.CASCADE)
    author=models.ForeignKey(
                            User,
                            on_delete= models.CASCADE,)
    text=models.TextField()
    pub_date=models.DateTimeField(
                                'date published',
                                auto_now_add=True)
    path=TreeOrderField(
                        max_length=255,
                        blank=True)
    seq=models.PositiveSmallIntegerField(default=0)

    class Meta:
        ordering=[ 'pub_date', ]


from platform import node
import collections

import numpy as np
from detectron2.evaluation import DatasetEvaluator

from mung.io import read_nodes_from_file
# from mung.graph import NotationGraph
# from mung.node import Node
from muscima.cropobject import CropObject

from data_handling.detectron2_muscima import get_muscima_classid_mapping

from models.detr.util.box_ops import box_xyxy_to_cxcywh
from data_handling.evaluate_notation_assembly_from_mung import evaluate_result

class MuNGEvaluator(DatasetEvaluator):
    def reset(self):
        self.precisions = []
        self.recalls = []
        self.f1_scores = []

    def process(self, inputs, outputs):
        # Class to id mappings
        class_mappings = get_muscima_classid_mapping()
        # Iterate images in batch
        for img in range(0, len(outputs)):
            # Get data for img
            instances = outputs[img]["instances"]
            boxes = instances.pred_boxes.tensor
            classes = instances.pred_classes
            relations = instances.pred_relations

            # Create CropObjects for predictions
            pred_crop_obj = []
            for pred in range(0, len(classes)):
                # BBox (left, top, width, height)
                [left, top, _, _] = boxes[pred]
                [_, _, width, height] = box_xyxy_to_cxcywh(boxes[pred])

                # Reverse class->id dictionary (+1 since we did -1 in detectron)
                class_name = list(class_mappings.keys())[list(class_mappings.values()).index(classes[pred]+1)]

                # Get relations (select row; if col > threshold: rel from row to col=yes)
                outlinks = []
                for id, rel in enumerate(relations[pred]):
                    if rel > 0.5:
                        outlinks.append(id)
                # Create CropObject
                pred_crop_obj.append(CropObject(pred, clsname=class_name, top=top, left=left, height=height, width=width, outlinks=outlinks))

            # Load mung from file based on img name
            img_file = inputs[img]["file_name"]
            annotaion_file = img_file.replace("images", "annotations")[:-3] + "xml"
            gt_mung = (read_nodes_from_file(annotaion_file))
            # Convert mung Node to CropObject
            gt_crop_obj = []
            for i, mung in enumerate(gt_mung):
                gt_crop_obj.append(CropObject(i, clsname=mung.class_name, top=mung.top, left=mung.left, height=mung.height, width=mung.width, outlinks=mung.outlinks))

            # Calculate scores for all objects in one image using munglinker code
            precision, recall, f1_score, true_positives, false_positives, false_negatives = \
                evaluate_result(gt_crop_obj, pred_crop_obj)
            self.precisions.append(precision)
            self.recalls.append(recall)
            self.f1_scores.append(f1_score)

    def evaluate(self):
        results = {
            "precision": np.average(self.precisions),
            "recall": np.average(self.recalls),
            "f1_score": np.average(self.f1_scores)
        }
        return collections.OrderedDict({"relations": results})


import Sastrawi
from Sastrawi.Stemmer.StemmerFactory import StemmerFactory
from Sastrawi.StopWordRemover.StopWordRemoverFactory import StopWordRemoverFactory

StopFactory = StopWordRemoverFactory()
stopwords = StopFactory.get_stop_words()
stopwords2 = StopFactory.get_stop_words()

# Add stopword
stopwords2.append('tambahannich')
# Remove stopword
stopwords.remove('ok')

print(stopwords)
print(stopwords2)


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

from rest_framework.views import APIView
from models import Reservation
from serializer import ReservationSerializer
from rest_framework.response import Response
from rest_framework.status import *
from django.core.exceptions import ObjectDoesNotExist
from datetime import datetime
from api_exceptions import NoReservations
from django.contrib.auth.models import User
from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie

class ReservationAPI(APIView):
    # Gets a reservation
    # Within a range provided request has
    # start_date and end_date args
    # A specific reservation based on uuid
    # A reservations with default values.
    def get(self, request, uuid, format = None):
        start = request.GET.get("start")
        end = request.GET.get("end")
        if start != None and end != None:
            list_reservations = Reservation.get_reservation_range(start, end)
            serialized_list = ReservationSerializer(list_reservations, many=True)
            return Response(serialized_list.data, status=HTTP_200_OK)
        elif uuid:
            try:
                reservation = Reservation.objects.get(uuid=uuid)
            except ObjectDoesNotExist as E:
                raise NoReservations()
            serialized = ReservationSerializer(reservation)
            return Response(serialized.data, status=HTTP_200_OK)
        else:
            serialized = ReservationSerializer(Reservation.get_next_n_day(), many=True)
            if len(serialized.data) == 0:
                raise NoReservations()
            return Response(serialized.data, status=HTTP_200_OK)

    # Post is to confirm the reservation.
    def post(self, request, uuid, format = None):
        print(request.data)
        fullname, email = request.data["fullname"], request.data["email"]
        try:
            user = User.objects.get(email = email)
        except ObjectDoesNotExist as E:
            user = User.objects.create_user(
                username = email,
                first_name = fullname.split(" ")[0],
                last_name = fullname.split(" ")[-1],
                email = email
            )
        serialized = ReservationSerializer(data = request.data)
        print(serialized.is_valid())
        if serialized.is_valid():
            try:
                reserved = serialized.save()
                reserved = reserved.reserve(user)
                serialized = ReservationSerializer(reserved)
                return Response(serialized.data, status=HTTP_201_CREATED)
            except Exception as E:
                print(E.message)
                return Response(E.message, status=HTTP_406_NOT_ACCEPTABLE)
        print(serialized.errors)
        return Response(serialized.errors, status=HTTP_417_EXPECTATION_FAILED)

    # Modifies the reservation.
    # Need to add check if modification can is valid
    def put(self, request, uuid, format = None):
        try:
            reservation = Reservation.objects.get(uuid = uuid)
        except ObjectDoesNotExist as E:
            raise NoReservations

        serialized = ReservationSerializer(reservation, data = request.data)
        if serialized.is_valid():
            serialized.save()
            return Response(serialized.data, status=HTTP_202_ACCEPTED)
        return Response(serialized.errors, status= HTTP_417_EXPECTATION_FAILED)

    # Deletes the reservation
    def delete(self, request, uuid, format = None):
        try:
            reservation = Reservation.objects.get(uuid = uuid)
            reservation.delete()
            return Response({"message": "Reservation {uuid} deleted.".format(uuid = uuid)}, status=HTTP_200_OK)
        except ObjectDoesNotExist as E:
            return Response({"error":ObjectDoesNotExist.message}, status=HTTP_404_NOT_FOUND)

@ensure_csrf_cookie
def serve_app(request):
    return render(request, 'index.html')


import glob
path="/home/sumant/sumant/*.txt"
files=glob.glob(path)

for f in files:
  f_read = open(f,"r+")
  lines = [line.rstrip('\n') for line in f_read]
  f_read.close()
  data_new = ""
  print(lines)
  try:
    for line in lines:
        print("LINE" + line)
        if(line is not "\n"):
            num, rest = line.split(" ", 1)
            if num=='15':
                    new_num='0'
            elif num=='16':
                    new_num='2'
            elif num=='17':
                    new_num='1'
            print(new_num)
            print()
            new_line = new_num + " " + rest + "\n"
            data_new = data_new + new_line
            print("DATA_NEW" + data_new)
    f_write = open(f, "w")
    f_write.write(data_new)
    f_write.close()
  except:
    print("No line:")


import re

text= open("output.txt")

def clean_text(text):
    # remove emojis
    emoji_pattern = re.compile("["
        u"\U0001F600-\U0001F64F"  # emoticons
        u"\U0001F300-\U0001F5FF"  # symbols & pictographs
        u"\U0001F680-\U0001F6FF"  # transport & map symbols
        u"\U0001F1E0-\U0001F1FF"  # flags (iOS)
        u"\U00002702-\U000027B0"
        u"\U000024C2-\U0001F251"
                           "]+", flags=re.UNICODE)
    text = emoji_pattern.sub(r'', text)

    # remove certain symbols
    text = re.sub(r'[^\w\s#]+', '', text)

    return text


class BinaryTree:
    class Node:
        """Binary tree node"""
        def __init__(self, key):
            self.key = key
            self.left = self.right = None

        def __str__(self):
            res = []
            if self.left is not None:
                res.append(str(self.left))
            res.append(str(self.key))
            if self.right is not None:
                res.append(str(self.right))
            return ' '.join(res)

    def __init__(self):
        """Creating new empty tree"""
        self.root = None

    def __current_and_parent(self, key):
        """
        Return node with given :param key: and its parent
        :return: (current, parent)
        """
        parent = None
        current = self.root
        while current is not None and current.key != key:
            parent = current
            if current.key > key:
                current = current.left
            else:
                current = current.right

        return current, parent

    def insert(self, key):
        """
        Add an element to a tree.

        This has no effect if the element is already present.

        """
        current, parent = self.__current_and_parent(key)

        if current is not None:
            return

        if parent is None:
            self.root = self.Node(key)
            return

        if parent.key > key:
            parent.left = self.Node(key)
        else:
            parent.right = self.Node(key)

    def remove(self, key):
        """
        Remove an key from a tree; it must be in the tree.

        If the key is not in the tree, raise a KeyError.

        """
        current, parent = self.__current_and_parent(key)

        if current is None:
            raise KeyError(key)

        if current.left is None or current.right is None:
            if current.left is None:
                current = current.right
            else:
                current = current.left

            if parent is None:
                self.root = current
            elif parent.key > key:
                parent.left = current
            else:
                parent.right = current
        else:
            for_del = current.right
            while for_del.left is not None:
                for_del = for_del.left
            new_key = for_del.key
            self.remove(new_key)
            current.key = new_key

    def __str__(self):
        return str(self.root)

    def __contains__(self, key):
        return self.__current_and_parent(key)[0] is not None

    def __iter__(self):
        return iter(str(self).split())

def test():
    from random import randint
    from tqdm import tqdm
    for test_num in tqdm(range(10000)):
        a = BinaryTree()
        for i in range(50):
            a.insert(randint(0, 100))
        r = randint(0, 100)
        try:
            a.remove(r)
        except KeyError:
            pass
        finally:
            assert r not in a, test_num

        str_a = str(a)
        list_a = [int(x) for x in str(a).split()]
        list_a.sort()
        assert str_a == ' '.join([str(x) for x in list_a]), test_num
    print('passed')

if __name__ == '__main__':
    test()


from django.shortcuts import render, redirect
from .forms import ContactForm
from django.core.mail import send_mail, BadHeaderError
from django.http import HttpResponse

# Create your views here.
def index(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            subject = 'مشاوره با ما '
            body = {
                'first_name': form.cleaned_data['first_name'],
                'last_name': form.cleaned_data['last_name'],
                'phone_number': form.cleaned_data['phone_number'],
                'email': form.cleaned_data['email_address'],
                'message': form.cleaned_data['message'],
            }
            message = "\n".join(body.values())

            try:
                send_mail(subject, message, 'work.moshavereh@gmail.com', ['rmaysam958@gmail.com'])
            except BadHeaderError:
                return HttpResponse('اشکال در ارسال ایمیل.')
            return HttpResponse('ایمیل شما با موفقیت ارسال شد ')
    form = ContactForm
    return render(request, 'index.html', {'form': form})


from django.shortcuts import render
from django.shortcuts import redirect
from rest_framework import generics
from ..serializers import ContactInfoSerializer
from ..models import ContactInfo
from rest_framework.response import Response
from django.shortcuts import get_object_or_404

# ContactInfo CRUD
# ContactInfo Read One Instance
class ContactInfoDetailAPIView(generics.RetrieveAPIView):
  queryset = ContactInfo.objects.all()
  serializer_class = ContactInfoSerializer

# ContactInfo Read All Instances and Create
class ContactInfoAPIView(generics.ListCreateAPIView):
  queryset = ContactInfo.objects.all()
  serializer_class = ContactInfoSerializer

  def perform_create(self, serializer):
    user_id = serializer.validated_data.get('user_id')
    address = serializer.validated_data.get('address')
    phone = serializer.validated_data.get('phone')
    serializer.save()

# ContactInfo Update
class ContactInfoUpdateAPIView(generics.UpdateAPIView):
  queryset = ContactInfo.objects.all()
  serializer_class = ContactInfoSerializer
  lookup_field = 'pk'

  def perform_update(self, serializer):
    instance = serializer.save()

# ContactInfo Destroy
class ContactInfoDestroyAPIView(generics.DestroyAPIView):
    queryset = ContactInfo.objects.all()
    serializer_class = ContactInfoSerializer
    lookup_field = 'pk'

    def perform_destroy(self, instance):
        super().perform_destroy(instance)


with open("inputs/1.txt") as file:
    entries = [int(entry) for entry in file.readlines()]

def part_1():
    seen = set()
    for entry in entries:
        if 2020 - entry in seen:
            return entry * (2020 - entry)
        seen.add(entry)

def part_2():
    entries.sort()
    for i in range(len(entries) - 2):
        left = i + 1
        right = len(entries)-1
        while (left < right):
            if(entries[i] + entries[left] + entries[right] == 2020):
                return entries[i] * entries[left] * entries[right]
            elif (entries[i] + entries[left] + entries[right] < 2020):
                left += 1
            else:
                right -= 1
    return "Uh oh! No matching entries found ðŸ˜±"

print(f"Part 1: {part_1()}")
print(f"Part 2: {part_2()}")


# Programa que recebe a velocidade média de um carro, e diz se o mesmo irá angariar uma punição
# se o mesmo tiver ultrapassado a velocidade média imposta naquela região

velocidade = float(input("Informe a velocidade média de seu carro em km por hora: "))
kmAcima = velocidade - 80

if velocidade > 80:
    multa = kmAcima * 5
    print ("Sua multa foi de "+str(multa)+" reais")
else:
    print("Você não recebeu nenhuma multa")


class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        preMap = {i:[] for i in range(numCourses)}
        for crs, pre in prerequisites:
            preMap[crs].append(pre)

        visiting = set()
        def dfs(crs):
            if crs in visiting:
                return False
            if preMap[crs] == []:
                return True

            visiting.add(crs)
            for pre in preMap[crs]:
                if not dfs(pre):
                    return False
            visiting.remove(crs)
            preMap[crs] = []
            return True

        for crs in range(numCourses):
            if not dfs(crs):
                return False

        return True


#!/usr/bin/env python3
import unittest
from csv_merger import *

class TestStringMethods(unittest.TestCase):
    def test_name_is_same(self):
        self.assertTrue(name_is_same("a", "a"))
        self.assertTrue(name_is_same("aaaaa", "baaaa"))
        self.assertFalse(name_is_same("a", "b"))
        self.assertFalse(name_is_same("aaaa", "baaa"))

    def test_get_columns(self):
        with open("test.txt", "w") as f:
            f.write("siret;numero\nphone\nkey;siret")
        self.assertEqual(get_columns("test.txt"), {'siret': ('numero',), 'phone': (), 'key': ('siret',)})
        os.remove("test.txt")

    def test_check_key(self):
        columns = {'First Name': (), 'Last Name': (), 'Number': (), "key": ("First Name",)}
        column_name = ["First Name", "Last Name", "Number"]
        df = pd.DataFrame(columns = column_name)
        df.loc[len(df)] = ["Michel", "Faure", "0672312589"]
        new_row = ["Michel", "Pablo", "06421523569"]
        self.assertFalse(check_key(columns, column_name, new_row, df, "source"))
        new_row = ["Paul", "Pablo", "06421523569"]
        self.assertTrue(check_key(columns, column_name, new_row, df, "source"))

    def test_file_to_utf(self):
        if os.path.isfile(".source_test.csv") == False:
            print("cannot test this function, missing test file: .source_test.csv")
            return False
        file_to_utf(["test", "model", ".source_test.csv"])
        self.assertTrue(os.path.isfile("final_.source_test.csv"))
        os.remove("final_.source_test.csv")

    def test_cut_path(self):
        self.assertEqual(cut_path("../lala"), "lala")
        self.assertEqual(cut_path("../../lala"), "lala")
        self.assertEqual(cut_path("./lala"), "lala")

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


# enable default uniaxial tester for this module
from opensees.physical_properties.utils.tester.EnableTester1D import *

import PyMpc.Units as u
from PyMpc import *
from mpc_utils_html import *
import opensees.utils.tcl_input as tclin

def makeXObjectMetaData():

	def mka(name, group, descr, atype, adim = None, dval = None):
		a = MpcAttributeMetaData()
		a.type = atype
		a.name = name
		a.group = group
		a.description = (
			html_par(html_begin()) +
			html_par(html_boldtext(name)+'
') +
			html_par(descr) +
			html_par(html_href('https://opensees.berkeley.edu/OpenSees/manuals/usermanual/2426.htm','Steel03')+'
') +
			html_end()
			)
		if adim is not None:
			a.dimension = adim
		if dval is not None:
			a.setDefault(dval)
		return a

	# uniaxialMaterial Steel03 $matTag $Fy $E $b $R0 $cR1 $cR2 $a1 $a2 $a3 $a4
	Fy = mka("Fy", "Group", "yield strength", MpcAttributeType.QuantityScalar, adim=u.F/u.L**2)
	E = mka("E", "Group", "", MpcAttributeType.QuantityScalar, adim=u.F/u.L**2)
	b = mka("b", "Group", "Strain-hardening ratio (ratio between post-yield tangent and initial elastic tangent)", MpcAttributeType.Real)
	R0 = mka("R0", "Group", "Initial elastic tangent", MpcAttributeType.Real)
	cR1 = mka("cR1", "Group", "Initial elastic tangent", MpcAttributeType.Real)
	cR2 = mka("cR2", "Group", "Initial elastic tangent", MpcAttributeType.Real)

	Optional = mka("Optional", "Optional parameters", "", MpcAttributeType.Boolean)
	a1 = mka("a1", "Optional parameters", "Isotropic hardening parameter, increase of compression yield envelope as proportion of yield strength after a plastic strain of a2*(Fy/E0).", MpcAttributeType.Real)
	a1.setDefault(0.0)
	a2 = mka("a2", "Optional parameters", "Isotropic hardening parameter (see explanation under a1). (optional default = 1.0).", MpcAttributeType.Real)
	a2.setDefault(1.0)
	a3 = mka("a3", "Optional parameters", "Isotropic hardening parameter, increase of tension yield envelope as proportion of yield strength after a plastic strain of a4*(Fy/E0).", MpcAttributeType.Real)
	a3.setDefault(0.0)
	a4 = mka("a4", "Optional parameters", "Isotropic hardening parameter (see explanation under a3). (optional default = 1.0)", MpcAttributeType.Real)
	a4.setDefault(1.0)

	xom = MpcXObjectMetaData()
	xom.name = 'Steel03'
	xom.Xgroup = 'Steel and Reinforcing-Steel Materials'
	xom.addAttribute(Fy)
	xom.addAttribute(E)
	xom.addAttribute(b)
	xom.addAttribute(R0)
	xom.addAttribute(cR1)
	xom.addAttribute(cR2)

	# use_Optional-dep
	xom.addAttribute(Optional)
	xom.addAttribute(a1)
	xom.addAttribute(a2)
	xom.addAttribute(a3)
	xom.addAttribute(a4)

	# use_Optional-dep
	xom.setVisibilityDependency(Optional, a1)
	xom.setVisibilityDependency(Optional, a2)
	xom.setVisibilityDependency(Optional, a3)
	xom.setVisibilityDependency(Optional, a4)

	return xom

def writeTcl(pinfo):
	# uniaxialMaterial Steel03 $matTag $Fy $E $b $R0 $cR1 $cR2 $a1 $a2 $a3 $a4
	xobj = pinfo.phys_prop.XObject
	tag = xobj.parent.componentId

	# utils
	def geta(name):
		at = xobj.getAttribute(name)
		if at is None:
			raise Exception('Error: cannot find "{}" attribute'.format(name))
		return at

	# uniaxialMaterial Steel03 $matTag $Fy $E $b $R0 $cR1 $cR2 $a1 $a2 $a3 $a4
	# optional paramters
	sopt = ''
	if geta('Optional').boolean:
		sopt += ' {} {} {} {}'.format(geta('a1').real,
		geta('a2').real,
		geta('a3').real,
		geta('a4').real)

	str_tcl = '{}uniaxialMaterial Steel03 {} {} {} {} {} {} {}{}\n'.format(
			pinfo.indent,
			tag,
			geta('Fy').quantityScalar.value,
			geta('E').quantityScalar.value,
			geta('b').real,
			geta('R0').real,
			geta('cR1').real,
			geta('cR2').real,
			sopt
			)

	# now write the string into the file
	pinfo.out_file.write(str_tcl)


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import random

def loadDataSet(file):
    dataSet= pd.read_table(file,header = None)
    xMat=np.mat(dataSet.iloc[:,:-1].values)
    yMat=np.mat(dataSet.iloc[:,-1].values).T
    return xMat, yMat

def showDataSet(xMat, yMat):
    data_p = []  # 正样本
    data_n = []  # 负样本
    m = xMat.shape[0]  # 样本总数
    for i in range(m):
        if yMat[i] > 0:
            data_p.append(xMat[i])
    else:
        data_n.append(xMat[i])
    data_p_ = np.array(data_p)  # 转换为numpy矩阵
    data_n_ = np.array(data_n)  # 转换为numpy矩阵
    plt.scatter(data_p_.T[0], data_p_.T[1])  # 正样本散点图
    plt.scatter(data_n_.T[0], data_n_.T[1])  # 负样本散点图
    plt.show()

def selectJrand(i,m):
    j = i
    while (j == i):
        j = int(random.uniform(0, m))
    return j

def clipAlpha(aj,H,L):
    if aj > H:
        aj = H
    if L > aj:
        aj = L
    return aj

def smoSimple(xMat,yMat,C,toler,maxIter):
    b = 0  # 初始化b参数
    m, n = xMat.shape  # m为数据集的总行数，n为特征的数量
    alpha = np.mat(np.zeros((m, 1)))  # 初始化alpha参数，设为0
    iters = 0  # 初始化迭代次数
    while (iters < maxIter):
        alpha_ = 0  # 初始化alpha优化次数
        for i in range(m):
        # 步骤1：计算误差Ei
            fXi = np.multiply(alpha, yMat).T * (xMat * xMat[i, :].T) + b
            Ei = fXi - yMat[i]
        # 优化alpha，设定容错率
        if ((yMat[i] * Ei < -toler) and (alpha[i] < C)) or ((yMat[i] * Ei > toler) and (alpha[i] > 0)):
            # 随机选择一个与alpha_i成对优化的alpha_j
            j = selectJrand(i, m)
            # 步骤1：计算误差Ej
            fXj = np.multiply(alpha, yMat).T * (xMat * xMat[j, :].T) + b
            Ej = fXj - yMat[j]
            # 保存更新前的alpha_i和alpha_j
            alphaIold = alpha[i].copy()
            alphaJold = alpha[j].copy()
            # 步骤2：计算上下界H和L
            if (yMat[i] != yMat[j]):
                L = max(0, alpha[j] - alpha[i])
                H = min(C, C + alpha[j] - alpha[i])
            else:
                L = max(0, alpha[j] + alpha[i] - C)
                H = min(C, C + alpha[j] + alpha[i])
            if L == H:
                # print('L==H')
                continue
            # 步骤3：计算学习率eta(eta是alpha_j的最优修改量)
            eta = 2 * xMat[i, :] * xMat[j, :].T - xMat[i, :] * xMat[i, :].T - xMat[j, :] * xMat[j, :].T
            if eta >= 0:
                # print('eta>=0')
                continue
            # 步骤4：更新alpha_j
            alpha[j] -= yMat[j] * (Ei - Ej) / eta
            # 步骤5：修剪alpha_j
            alpha[j] = clipAlpha(alpha[j], H, L)
            if abs(alpha[j] - alphaJold) < 0.00001:
                # print('alpha_j 变化太小')
                continue
            # 步骤6：更新alpha_i
            alpha[i] += yMat[j] * yMat[i] * (alphaJold - alpha[j])
            # 步骤7：更新b_1和b_2
            b1 = b - Ei - yMat[i] * (alpha[i] - alphaIold) * xMat[i, :] * xMat[i, :].T - yMat[j] * (alpha[j] - alphaJold) * xMat[i, :] * xMat[j, :].T
            b2 = b - Ej - yMat[i] * (alpha[i] - alphaIold) * xMat[i, :] * xMat[j, :].T - yMat[j] * (alpha[j] - alphaJold) * xMat[j, :] * xMat[j, :].T
            # 步骤8：根据b_1和b_2更新b
            if (0 < alpha[i]) and (C > alpha[i]):
                b = b1
            elif (0 < alpha[j]) and (C > alpha[j]):
                b = b2
            else:
                b = (b1 + b2) / 2
            alpha_ += 1
        if alpha_ == 0:
            iters += 1
        else:
            iters = 0
    return b, alpha
def get_sv(xMat,yMat,alpha):
    m = xMat.shape[0]
    sv_x = []
    sv_y = []
    for i in range(m):
        if alpha[i] > 0:
            sv_x.append(xMat[i])
            sv_y.append(yMat[i])
    sv_x1 = np.array(sv_x).T
    sv_y1 = np.array(sv_y).T
    return sv_x1, sv_y1
def showPlot(xMat, yMat,alpha):
    data_p = []  # 正样本
    data_n = []  # 负样本
    m = xMat.shape[0]  # 样本总数
    for i in range(m):
        if yMat[i] > 0:
            data_p.append(xMat[i])
        else:
            data_n.append(xMat[i])
    data_p_ = np.array(data_p)  # 转换为numpy矩阵
    data_n_ = np.array(data_n)  # 转换为numpy矩阵
    # 样本散点图
    plt.scatter(data_p_.T[0], data_p_.T[1])  # 正样本散点图
    plt.scatter(data_n_.T[0], data_n_.T[1])  # 负样本散点图
    # 绘制支持向量
    sv_x, sv_y = get_sv(xMat, yMat, alpha)
    plt.scatter(sv_x[0], sv_x[1], s=150, c='none', alpha=0.7, linewidth=1.5,edgecolor='red')
if __name__ == '__main__':
    x,y = loadDataSet('iris.txt')
    showDataSet(x,y)