# env
# conda install cudatoolkit=10.0.130
# conda install cudnn=7.6.0=cuda10.0_0
# pip install --upgrade tensorflow-gpu

# conda create --name tf22 tensorflow-gpu=2.2 cudatoolkit=10.1 cudnn=7.6 python=3.8 pip=20.0
# conda install -c conda-forge keras matplotlib
# conda install -c anaconda scikit-learn
# pip install Pillow xgboost seaborn tqdm

from keras.callbacks import ModelCheckpoint
from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten, Conv2D, MaxPooling2D, AveragePooling2D, add, BatchNormalization, Dropout
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error
from matplotlib import pyplot as plt
import seaborn as sb
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
warnings.filterwarnings('ignore', category=DeprecationWarning)
from xgboost import XGBRegressor
from keras import backend as K
from keras.layers.merge import concatenate
from PIL import Image

# from sklearn.datasets import load_iris
# from tensorflow.keras.layers import Dense
# from tensorflow.keras import Input, Model
import tensorflow as tf

from sklearn.datasets import load_iris
from keras import Input, Model

physical_devices = tf.config.experimental.list_physical_devices('GPU')
assert len(physical_devices) > 0, "Not enough GPU hardware devices available"
config = tf.config.experimental.set_memory_growth(physical_devices[0], True)

def vgg_block(layer_in, n_filters, n_conv):
	# add convolutional layers
	for _ in range(n_conv):
		layer_in = Conv2D(n_filters, (3,3), padding='same', activation='relu')(layer_in)
	# add max pooling layer
	layer_in = MaxPooling2D((2,2), strides=(2,2))(layer_in)
	return layer_in

def naive_inception_module(layer_in, f1, f2, f3):
	# 1x1 conv
	conv1 = Conv2D(f1, (1,1), padding='same', activation='relu')(layer_in)
	# 3x3 conv
	conv3 = Conv2D(f2, (3,3), padding='same', activation='relu')(layer_in)
	# 5x5 conv
	conv5 = Conv2D(f3, (5,5), padding='same', activation='relu')(layer_in)
	# 3x3 max pooling
	pool = MaxPooling2D((3,3), strides=(1,1), padding='same')(layer_in)
	# concatenate filters, assumes filters/channels last
	layer_out = concatenate([conv1, conv3, conv5, pool], axis=-1)
	return layer_out

def residual_module(layer_in, n_filters):
	merge_input = layer_in
	# check if the number of filters needs to be increase, assumes channels last format
	if layer_in.shape[-1] != n_filters:
		merge_input = Conv2D(n_filters, (1,1), padding='same', activation='relu', kernel_initializer='he_normal')(layer_in)
	# conv1
	conv1 = Conv2D(n_filters, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(layer_in)
	# conv2
	conv2 = Conv2D(n_filters, (3,3), padding='same', activation='linear', kernel_initializer='he_normal')(conv1)
	# add filters, assumes filters/channels last
	layer_out = add([conv2, merge_input])
	# activation function
	layer_out = Activation('relu')(layer_out)
	return layer_out

def get_model(v_len, patch_len):

	inputs_i = Input(shape=(patch_len, patch_len, 3,), name='image')
	inputs_v = Input(shape=(v_len * 2,), name='velocity')

	v = Dense(128, kernel_initializer='normal', activation='relu', name='v_1')(inputs_v)
	# v = BatchNormalization()(v)
	v = Dropout(0.5)(v)
	v = Dense(64, kernel_initializer='normal', activation='relu', name='v_2')(v)
	v = BatchNormalization()(v)

	img = Conv2D(64, (3,3), padding='same', activation='relu', kernel_initializer='he_normal', name='conv_1')(inputs_i)

	img = residual_module(img, 64)
	img = residual_module(img, 64)
	img = BatchNormalization()(img)
	img = MaxPooling2D((2,2), strides=(2,2))(img)

	# img_shape = K.int_shape(img)
	# print(img_shape)
	# from IPython import embed;embed()

	img = residual_module(img, 128)
	img = residual_module(img, 128)
	img = BatchNormalization()(img)
	# img = MaxPooling2D((2,2), strides=(2,2))(img)

	# img = residual_module(img, 256)
	# img = residual_module(img, 256)
	# img = BatchNormalization()(img)

	img_shape = K.int_shape(img)
	pool = AveragePooling2D(pool_size=(img_shape[1], img_shape[2]),
								strides=(1, 1))(img)
	flatten = Flatten()(pool)
	img = Dense(units=128, kernel_initializer="he_normal",
					activation="softmax")(flatten)

	# img_shape = K.int_shape(img)
	# print(img_shape)
	# from IPython import embed;embed()
	layer_out = concatenate([img, v], axis=-1)
	layer_out = Dense(500, activation='softmax')(layer_out)
	layer_out = BatchNormalization()(layer_out)

	output = Dense(4, activation='linear', name='out')(layer_out)

	model = Model(inputs=[inputs_i, inputs_v], outputs=output)

	model.compile(loss='mean_absolute_error',
				optimizer='adam',
				metrics=['mean_absolute_error'])

	model.summary()

	return model

v_len = 50
patch_len = 100
model = get_model(v_len, patch_len)
# v = np.load("./dataset/asphalt/input_velocity_1600.npy")[-1 * v_len:]
# v = v.reshape(-1)
# # from IPython import embed;embed()
# i = Image.open("./dataset/asphalt/1600_patch100.png")
# i = np.asarray(i)
# print(model.predict([np.array([i, i, i]), np.array([v, v, v])]))


from data_structures.binary_tree import BinaryTree
from data_structures.kary_tree import KaryTree, Node
from code_challenges.queue import Queue

def fizz_buzz_tree(tree):
    # Make a copy of the KaryTree using Clone
    fizzy_tree = KaryTree.clone(tree)
    # Check to see if the KaryTree is Empty
    if tree.root is None:
        return None
    queue = Queue()
    # Add the root of the tree to the queue
    queue.enqueue(fizzy_tree.root)
    # We enter the while loop if there is something in the queue
    while not queue.is_empty():
        temp_dequeue_node = queue.dequeue()
        temp_dequeue_node.value = fizz_buzz_helper(temp_dequeue_node.value)
        #print(f"The result of fizz_buzz_result: {node_value}")

        # add children to queue
        child_node_list = temp_dequeue_node.children
        for node in child_node_list:
            queue.enqueue(node)
    for node in fizzy_tree.root.children:
        print(node.value)
    #print(fizzy_tree.root.children)
    return fizzy_tree

def fizz_buzz_helper(value):
    if value % 3 == 0 and value % 5 == 0:
        return "FizzBuzz"
    elif value % 3 == 0:
        return "Fizz"
    elif value % 5 == 0:
        return "Buzz"
    else:
        return str(value)


import os

import pytest

from app.main import app as create_app

@pytest.fixture
def test_file_archive():
    file_path = 'files/123.tar.gz'
    with open(file_path, 'w+'):
        pass
    yield
    os.remove(file_path)

@pytest.fixture
def app():
    app = create_app
    return app

def test_get_archive_404(client):
    url = 'get_archive/123'
    assert client.get(url).status_code == 404

def test_get_archive_200(client, test_file_archive):
    url = 'get_archive/123'
    assert client.get(url).status_code == 200

@pytest.mark.parametrize(
    'parameters',
    (
        #  (data, status),
        ({'q': 'q'}, 500),
        ({'url': ''}, 500),
        ({'url': 'http:'}, 500),
    )
)
def test_create_task_handler(client, parameters):
    url = '/api/v1/task'
    data, status = parameters
    res = client.post(url, data=data)
    assert res.status_code == status

def test_get_task_status(client):
    url = '/api/v1/task/123'
    res = client.get(url)
    assert res.status_code == 200


#순열 조합 , :https://shoark7.github.io/programming/algorithm/Permutations-and-Combinations

import itertools
mylist = [1,2,3]
result = itertools.combinations(mylist, r=2)
result = itertools.combinations_with_replacement(mylist, r=2)

from collections import deque
que = deque(['bb','cc'])
que.append('aaa')
que.popleft()

#회전
def rotate_90(map):
    N = len(map)
    rot = [[0] *N for _ in range(N)]
    for r in range(N):
        for c in range(N):
            rot[c][N-1-r] = map[r][c]

    return rot

def rotate_180(map):
    N = len(map)
    rot = [[0] * N for _ in range(N)]
    for r in range(N):
        for c in range(N):
            rot[N-1-r][N - 1 - c] = map[r][c]

    return rot

def rotate_270(map):
    N = len(map)
    rot = [[0] * N for _ in range(N)]
    for r in range(N):
        for c in range(N):
            rot[N-1-c][r] = map[r][c]

    return rot

# DFS
def dfs(graph, v, visited):
    visited[v] = True
    for i in graph[v]:
        if not visited[i]:
            dfs(graph, i, visited)

# BFS
def bfs(graph, start, visited):
    from collections import deque
    queue = deque([start])
    visited[start] = True
    while queue:
        v = queue.popleft()
        for i in graph[v]:
            if not visited[i]:
                queue.append(i)
                visited[i] = True

#heapq
import heapq

# 정렬 quick sort
def quick_sort(array, start, end):
    if start>= end:
        return
    pivot = start
    left = start +1
    right = end

    while left <= right:
        while left <= end and array[left] <= array[pivot]:
            left +=1

        while right > start and array[right] >= array[pivot]:
            right -=1

        if left >right:
            array[right], array[pivot] = array[pivot], array[right]
        else:
            array[left], array[right] = array[right], array[left]

        quick_sort(array, start, right-1)
        quick_sort(array, right+1, end)

# 최단거리 _ 다익스트라 알고리즘
n =10
distance = [1e9] * n # 10: 노드 개수
graph = [[] for i in range(n+1)]
def dijksta(start):
    import heapq
    q = []
    heapq.heappush(q, (0,start))
    while q:
        now, dist = heapq.heappop(q)
        if distance[now] < dist:
            continue
        for i in graph[now]:
            cost = dist + i[1]
            if cost < distance[i[0]]:
                distance[i[0]] = cost
                heapq.heappush(q, (cost,i[0]))
"""
#최소 신장 트리
"""
# 특정 원소가 속한 집합을 찾기
def find_parent(parent, x):
    # 루트 노드가 아니라면, 루트 노드를 찾을 때까지 재귀적으로 호출
    if parent[x] != x:
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

# 두 원소가 속한 집합을 합치기
def union_parent(parent, a, b):
    a = find_parent(parent, a)
    b = find_parent(parent, b)
    if a < b:
        parent[b] = a
    else:
        parent[a] = b

# 노드의 개수와 간선(Union 연산)의 개수 입력 받기
v, e = map(int, input().split())
parent = [0] * (v + 1) # 부모 테이블 초기화하기

# 모든 간선을 담을 리스트와, 최종 비용을 담을 변수
edges = []
result = 0

# 부모 테이블상에서, 부모를 자기 자신으로 초기화
for i in range(1, v + 1):
    parent[i] = i

# 모든 간선에 대한 정보를 입력 받기
for _ in range(e):
    a, b, cost = map(int, input().split())
    # 비용순으로 정렬하기 위해서 튜플의 첫 번째 원소를 비용으로 설정
    edges.append((cost, a, b))

# 간선을 비용순으로 정렬
edges.sort()

# 간선을 하나씩 확인하며
for edge in edges:
    cost, a, b = edge
    # 사이클이 발생하지 않는 경우에만 집합에 포함
    if find_parent(parent, a) != find_parent(parent, b):
        union_parent(parent, a, b)
        result += cost

"""
# 진수 변환 - 210524_01
"""
#10 진수 -> 2, 8, 16 진수
value = 30
b = format(value, 'b')
o = format(value, 'o')
h = format(value, 'x')

#2,8,16 -> 10 진수
value = 101010
int_b = int(str(value), 2)
int_o = int(str(value), 8)
int_x = int(str(value), 16)


"""
mnist_loader
~~~~~~~~~~~~

A library to load the MNIST image data.  For details of the data
structures that are returned, see the doc strings for ``load_data``
and ``load_data_wrapper``.  In practice, ``load_data_wrapper`` is the
function usually called by our neural network code.
"""

#### Libraries
# Standard library
import _pickle as cPickle
import gzip

# Third-party libraries
import numpy as np

def load_data():
    """Return the MNIST data as a tuple containing the training data,
    the validation data, and the test data.

    The ``training_data`` is returned as a tuple with two entries.
    The first entry contains the actual training images.  This is a
    numpy ndarray with 50,000 entries.  Each entry is, in turn, a
    numpy ndarray with 784 values, representing the 28 * 28 = 784
    pixels in a single MNIST image.

    The second entry in the ``training_data`` tuple is a numpy ndarray
    containing 50,000 entries.  Those entries are just the digit
    values (0...9) for the corresponding images contained in the first
    entry of the tuple.

    The ``validation_data`` and ``test_data`` are similar, except
    each contains only 10,000 images.

    This is a nice data format, but for use in neural networks it's
    helpful to modify the format of the ``training_data`` a little.
    That's done in the wrapper function ``load_data_wrapper()``, see
    below.
    """

    print("Loading datasets...")

    # On ouvre le fichier contenant le dataset d'images MNIST en mode lecture
    f = gzip.open('../data/mnist.pkl.gz', 'rb')

    # On utilise cPickle afin de segmenter le dataset en 3 partie :
    # training_data   : couple (x, y)
    #   - Où x est une liste de taille 50 000, comprenant pour chaque entrée un numpy ndarray de taille 28 * 28 = 784, oû chaque entrée corresponds à un pixel ;
    #   - Où y est une liste de taille 50 000, comprenant pour chaque entrée le nombre correspondant à l'image (donc la prédiction à faire) ;
    # validation_data : pareil, avec une taille de 10 000.
    # test_data       : pareil, avec une taille de 10 000.
    training_data, validation_data, test_data = cPickle.load(f, encoding="latin1")

    # On ferme le fichier
    f.close()

    print("Datasets loaded...")

    # On retourne les valeurs
    return (training_data, validation_data, test_data)

def load_data_wrapper():
    """Return a tuple containing ``(training_data, validation_data,
    test_data)``. Based on ``load_data``, but the format is more
    convenient for use in our implementation of neural networks.

    In particular, ``training_data`` is a list containing 50,000
    2-tuples ``(x, y)``.  ``x`` is a 784-dimensional numpy.ndarray
    containing the input image.  ``y`` is a 10-dimensional
    numpy.ndarray representing the unit vector corresponding to the
    correct digit for ``x``.

    ``validation_data`` and ``test_data`` are lists containing 10,000
    2-tuples ``(x, y)``.  In each case, ``x`` is a 784-dimensional
    numpy.ndarry containing the input image, and ``y`` is the
    corresponding classification, i.e., the digit values (integers)
    corresponding to ``x``.

    Obviously, this means we're using slightly different formats for
    the training data and the validation / test data.  These formats
    turn out to be the most convenient for use in our neural network
    code."""

    # On charge le jeu de données d'entrainement, de validation et de test
    tr_d, va_d, te_d = load_data()

    print("Formatting datasets...")

    # > Dataset d'entrainement

    # On transforme la manière dont les images sont stockées en mémoire : on passe d'une dimention de (784) à (784, 1), cad une liste de taille 1 pour chaque pixel
    # Ex : la liste [ 1, 2, 3, ..., 784 ] devient [ [1], [2], [3], ..., [784] ]
    training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]]

    # On transforme la manière dont les nombres à prédire sont stockées en mémoire : on passe d'une valeur d'entier à une liste de booléens ou un seul indice vaut 1
    # Ex: le nombre 1 devient [ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 ] et le 9 devient [ 0, 0, 0, 0, 0, 0, 0, 0, 9, 0 ]
    training_results = [vectorized_result(y) for y in tr_d[1]]

    # On transforme la manière dont le jeu d'entraînement est stocké : on passe à une liste de 50 000 entrées de la forme [ [ti1, tr1], [ti2, tr2], ..., [trn, trn] ]
    # tel que tin corresponds à training_inputs[n] et trn à training_results[n] et n = 50 000. En clair, on obtient une liste de taille 50 000, contenant à chaque
    # indice un couple (x, y) où x est l'image en entrée de dimention (784, 1), et où y est la représentation du nombre à prédire sous la forme d'un tableau de booléens.
    training_data = list(zip(training_inputs, training_results))

    # > Dataset de validation

    # On transforme la manière dont les images sont stockées en mémoire : on passe d'une dimention de (784) à (784, 1), cad une liste de taille 1 pour chaque pixel
    # Ex : la liste [ 1, 2, 3, ..., 784 ] devient [ [1], [2], [3], ..., [784] ]
    validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]]

    # On transforme la manière dont le jeu d'entraînement est stocké : on passe à une liste de 10 000 entrées de la forme [ [ti1, tr1], [ti2, tr2], ..., [trn, trn] ]
    # tel que tin corresponds à validation_inputs[n] et trn à va_d[1][n] et n = 10 000. En clair, on obtient une liste de taille 10 000, contenant à chaque indice un
    # couple (x, y) où x est l'image en entrée de dimention (784, 1), et où y est la valeur du nombre à prédire (sous la forme d'un entier).
    validation_data = list(zip(validation_inputs, va_d[1]))

    # > Dataset de test

    # On transforme la manière dont les images sont stockées en mémoire : on passe d'une dimention de (784) à (784, 1), cad une liste de taille 1 pour chaque pixel
    # Ex : la liste [ 1, 2, 3, ..., 784 ] devient [ [1], [2], [3], ..., [784] ]
    test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]

    # On transforme la manière dont le jeu d'entraînement est stocké : on passe à une liste de 10 000 entrées de la forme [ [ti1, tr1], [ti2, tr2], ..., [trn, trn] ]
    # tel que tin corresponds à test_inputs[n] et trn à te_d[1][n] et n = 10 000. En clair, on obtient une liste de taille 10 000, contenant à chaque indice un
    # couple (x, y) où x est l'image en entrée de dimention (784, 1), et où y est la valeur du nombre à prédire (sous la forme d'un entier).
    test_data = list(zip(test_inputs, te_d[1]))

    # On affiche le nombre de données chargées
    n_training_data, n_validation_data, n_test_data = len(training_data), len(validation_data), len(test_data)
    print("Datasets formatted : {0} images for training / {1} images for validation / {2} images for test.".format(n_training_data, n_validation_data, n_test_data))

    # On retourne les valeurs
    return (training_data, validation_data, test_data)

def vectorized_result(j):
    """Return a 10-dimensional unit vector with a 1.0 in the jth
    position and zeroes elsewhere.  This is used to convert a digit
    (0...9) into a corresponding desired output from the neural
    network."""

    # On créée la liste [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
    e = np.zeros((10, 1))

    # On mets un 1 sur la valeur associée
    e[j] = 1.0

    return e


import socket
import sys
import os

port = int(sys.argv[1])

# Create a connection
s = socket.socket()
s.bind(('', port))
s.listen()

extension_mapping = {'.txt': 'text/plain', '.ico': 'image/x-icon', '.html': 'text/html'}

def get_req(message_socket):
    encoded_req = message_socket.recv(4096)
    request = ""
    while True:
        decoded_req = encoded_req.decode("ISO-8859-1")
        request = request + decoded_req
        if decoded_req.find('\r\n\r\n'):
            print(request)
            return request
        encoded_req = message_socket.recv(4096)

while True:
    new_conn = s.accept()
    print(new_conn)
    new_socket = new_conn[0]
    req = get_req(new_socket)

    # Parse request
    parsed_req = req.split('\r\n')
    request_method = parsed_req[0].split()[0]
    request_path = parsed_req[0].split()[1]
    request_protocol = parsed_req[0].split()[2]
    file_path_tuple = os.path.split(request_path)
    file_name = file_path_tuple[1]
    file_extension_tuple = os.path.splitext(file_name)
    file_extension = file_extension_tuple[1]

    try:
        with open(file_name) as fp:
            data = fp.read()  # Read entire file
            data_len = data.encode("ISO-8859-1")
            content_length = len(data_len)

            resp = 'HTTP/1.1 200 OK\r\n\
            Content-Type: {}\r\n\
            Content-Length: {}\r\n\
            Connection: close\r\n\r\n\
            {}'.format(extension_mapping[file_extension], content_length, data).encode("ISO-8859-1")
            new_socket.sendall(resp)
    except:
        resp = 'HTTP/1.1 404 Not Found\r\n\
        Content-Type: {}\r\n\
        Content-Length: 13\r\n\
        Connection: close\r\n\r\n\
        404 not found'.format(extension_mapping[file_extension]).encode("ISO-8859-1")
        new_socket.sendall(resp)

    print("Found end of line. Closing new socket")
    new_socket.close()


import bs4
import copy
import json
import numpy as np

from dataclasses import dataclass
from typing import Optional, List
from bs4 import BeautifulSoup

@dataclass
class TableCell:
    text: str
    width: Optional[np.int8] = 1
    height: Optional[np.int8] = 1
    linked_top: Optional[bool] = False  # judge if the current cell belongs to the above multi-row cell
    linked_left: Optional[bool] = False  # judge if the current cell belongs to the left multi-column cell

class TableRow:
    def __init__(self, cells: List[TableCell]):
        self._cells = cells
        self._width = self._get_width()
        self._expanded_cells = self._expand_cells()

    def __getitem__(self, i):
        return self.expanded_cells[i]

    def __len__(self):
        return self.width

    def _get_width(self):
        cell_widths = [cell.width for cell in self._cells]
        return np.sum(cell_widths)

    @property
    def width(self):
        return self._width

    @property
    def cells(self):
        return self._cells

    @property
    def expanded_cells(self):
        return self._expanded_cells

    @cells.setter
    def cells(self, cells):
        new_widths = [cell.width for cell in cells]
        assert self._width == new_widths, ValueError('The width must equal to the old one!')
        self._cells = cells
        self._expanded_cells = self._expand_cells()

    @expanded_cells.setter
    def expanded_cells(self, cells):
        raise AttributeError("Assigning values to `expanded_cells` is illegal!")

    def __str__(self):
        cell_line = '\t'.join([cell.text for cell in self._cells]) + '\n'
        return cell_line

    def __repr__(self):
        return self.__str__()

    def text(self):
        return self.__str__()

    def _expand_cells(self):
        expanded_cells = list()
        multicolumn_cache = list()
        cell_idx = 0
        while True:
            if multicolumn_cache:
                cached_cell = copy.deepcopy(multicolumn_cache[0])
                cached_cell.width = 1
                cached_cell.linked_left = True
                expanded_cells.append(cached_cell)
                multicolumn_cache[1] -= 1
                if multicolumn_cache[1] == 0:
                    multicolumn_cache = list()
            else:
                expanded_cells.append(self._cells[cell_idx])
                if self._cells[cell_idx].width > 1:
                    multicolumn_cache = [self._cells[cell_idx], self._cells[cell_idx].width - 1]
                cell_idx += 1
            if cell_idx == len(self._cells) and not multicolumn_cache:
                break
        return expanded_cells

class Table:
    def __init__(self,
                 label: Optional[str] = None,
                 idx: Optional[str] = None,
                 caption: Optional[str] = None,
                 rows: Optional[List[TableRow]] = None,
                 footnotes: Optional[List[str]] = None):

        self._label = label
        self._id = idx
        self._caption = caption
        self._rows = rows
        self._footnotes = footnotes

    @property
    def label(self):
        return self._label if self._label is not None else "<EMPTY>"

    @property
    def id(self):
        return self._id if self._id is not None else "<EMPTY>"

    @property
    def caption(self):
        return self._caption if self._caption is not None else ''

    @property
    def rows(self):
        return self._rows if self._rows is not None else []

    @property
    def footnotes(self):
        return self._footnotes if self._footnotes is not None else []

    @property
    def shape(self):
        return len(self), len(self._rows[0]) if self._rows else 0

    @property
    def width(self):
        return len(self._rows[0]) if self._rows else 0

    @property
    def n_columns(self):
        return self.width

    @property
    def height(self):
        return len(self)

    @property
    def n_rows(self):
        return self.height

    @label.setter
    def label(self, x):
        self._label = x

    @id.setter
    def id(self, x):
        self._id = x

    @caption.setter
    def caption(self, x):
        self._caption = x

    @rows.setter
    def rows(self, x):
        self._rows = x

    @footnotes.setter
    def footnotes(self, x):
        self._footnotes = x

    def __getitem__(self, idx):
        if isinstance(idx, int):
            return self.rows[idx]
        elif isinstance(idx, tuple) and len(idx) == 2:
            r, c = idx
            return self.rows[r].expanded_cells[c]

    def __str__(self):
        lines = self._label if self._label else ''
        lines += f' {self._caption}\n' if self._caption else ''
        rows = ''.join([row.__str__() for row in self._rows])
        lines += rows
        lines += '\n'.join(self._footnotes) if self._footnotes else ''
        return lines

    def __len__(self):
        return len(self._rows)

    def __repr__(self):
        return self.__str__()

    def _repr_html_(self):
        return self.write_html().prettify()

    def text(self):
        return self.__str__()

    def format_rows(self):
        """
        Deal with multi-row cells by appending dummy cells below them in each row
        """
        multirow_cache = dict()
        formatted_rows = list()
        for row in self.rows:
            cells = list()
            cell_idx = 1
            for cell in row.cells:
                while True:
                    if cell_idx in multirow_cache:
                        _, width, text = multirow_cache[cell_idx]
                        multirow_cell = TableCell(text, width, linked_top=True)

                        multirow_cache[cell_idx][0] -= 1
                        assert multirow_cache[cell_idx][0] >= 0
                        if multirow_cache[cell_idx][0] == 0:
                            multirow_cache.pop(cell_idx)

                        # check if we have already appended the multi-row cells before
                        if cell.text == text and cell.linked_top is True:
                            pass
                        else:
                            cell_idx += width
                            cells.append(multirow_cell)
                    else:
                        break

                # cache the properties of multi-row cells
                if cell.height > 1:
                    multirow_cache[cell_idx] = [cell.height - 1, cell.width, cell.text]

                cell_idx += cell.width
                cells.append(cell)
            formatted_rows.append(TableRow(cells))
        self._rows = formatted_rows
        return self

    def body_to_lists(self):
        element_list = list()
        for i in range(self.height):
            row_elements = list()
            for j in range(self.width):
                row_elements.append(self[i, j].text)
            element_list.append(row_elements)
        return element_list

    def write_html(self, root: Optional[bs4.element.Tag] = None):
        soup = BeautifulSoup()

        if root is None:
            root = BeautifulSoup()
        html_table = soup.new_tag('table')
        root.insert(len(root), html_table)

        if self.caption:
            caption = soup.new_tag('caption')
            html_table.insert(len(html_table), caption)
            if self.label and self.label != '<EMPTY>':
                cap_txt = f'{self.label} {self.caption}'
            else:
                cap_txt = self.caption
            caption.insert(0, cap_txt)

        tbody = soup.new_tag('tbody')
        html_table.insert(len(html_table), tbody)
        for row in self.rows:
            table_row = soup.new_tag('tr')
            tbody.insert(len(tbody), table_row)

            for entry in row.cells:
                if entry.linked_top:
                    continue
                table_data = soup.new_tag('td', colspan=str(entry.width), rowspan=str(entry.height))
                table_row.insert(len(table_row), table_data)
                table_data.insert(0, entry.text)

        if self.footnotes:
            tfoot = soup.new_tag('tfoot')
            html_table.insert(len(html_table), tfoot)
            for footnote in self.footnotes:
                table_row = soup.new_tag('tr')
                tfoot.insert(len(tfoot), table_row)
                table_data = soup.new_tag('td', colspan=str(self.rows[0].width) if self.rows else 1)
                table_row.insert(0, table_data)
                table_data.insert(0, footnote)

        return root

    def save_json(self, file_name):
        json_elements = {
            'caption': self.caption,
            'body': self.body_to_lists(),
            'footnotes': self.footnotes
        }
        with open(file_name, 'w', encoding='UTF-8') as f:
            json.dump(json_elements, f, indent=2, ensure_ascii=False)

def set_table_style(root: bs4.element.Tag):
    soup = BeautifulSoup()
    style = soup.new_tag('style')
    root.insert(len(root), style)
    style_string = \
        """
        table {
          font-family: arial, sans-serif;
          border-collapse: collapse;
          width: 90%;
          margin-left: auto;
          margin-right: auto;
          margin-top: 1.5em;
          margin-bottom: 1.5em;
        }

        caption {
          margin-bottom: 0.5em;
        }

        tbody td, th {
          border: 1px solid #dddddd;
          text-align: left;
          padding: 8px;
          font-size: 80%;
        }

        tbody tr:nth-child(even) {
          background-color: #dddddd;
        }

        tfoot td, th{
          border: none;
          text-align: left;
          padding: 8px;
          font-size: 70%;
          line-height: 100%;
        }

        tfoot tr {
          background-color: #ffffff;
        }
        """
    style.insert(0, style_string)
    return root


from settings import *
import numpy
import pygame

class Player:
	def __init__(self) -> None:
		self.x, self.y = 1.5, 1.5
		self.movement_speed = 2.0
		self.rotation_speed = 0.0005
		self.angle = 0

		self.shooting_state = False

	def move(self, fps: float) -> None:
		mouse_pressed = pygame.mouse.get_pressed()
		if mouse_pressed[2]:
			x = self.x + numpy.cos(self.angle) * self.movement_speed / fps
			y = self.y + numpy.sin(self.angle) * self.movement_speed / fps

			if not (map[int(x - 0.1)][int(y)] or map[int(x + 0.1)][int(y)] or map[int(x)][int(y - 0.1)] or map[int(x)][int(y + 0.1)]):
				self.x, self.y = x, y
			if not (map[int(self.x - 0.1)][int(y)] or map[int(self.x + 0.1)][int(y)] or map[int(self.x)][int(y - 0.1)] or map[int(self.x)][int(y + 0.1)]):
				self.y = y
			if not (map[int(x - 0.1)][int(self.y)] or map[int(x + 0.1)][int(self.y)] or map[int(x)][int(self.y - 0.1)] or map[int(x)][int(self.y + 0.1)]):
				self.x = x

	def shoot(self) -> None:
		mouse_pressed = pygame.mouse.get_pressed()
		if mouse_pressed[0]:
			self.shooting_state = True

	def rotate(self, screen: pygame.surface.Surface) -> None:
		mouse_pos = pygame.mouse.get_pos()
		if screen.get_rect().collidepoint(mouse_pos):
			pygame.mouse.set_pos(HALF_WIDTH, HALF_HEIGHT)

		mouse_rel = pygame.mouse.get_rel()
		if mouse_rel[0] != 0:
			self.angle += mouse_rel[0]*self.rotation_speed

	def update(self, screen: pygame.surface.Surface, fps: float) -> None:
		self.move(fps)
		self.shoot()
		self.rotate(screen)


# -*- coding: utf-8 -*-
#
# Copyright (C) 2009 Alexey Kinyov <rudy@relishgames.com>
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
#

from genshi.builder import tag
from trac.core import *
from trac.util.html import Markup
from trac.wiki.macros import WikiMacroBase

class TracFlexWikiChildrenMacro(WikiMacroBase):
    """Prints children pages on currently opened page.
    """

    revision = "$Rev$"

    url = "$URL$"

    def expand_macro(self, formatter, name, content):
        """Prints children pages on currently opened page.
        """
        # parse args
        class_="flex-children"
        if content:
            args = content.split(',')
            for arg in args:
                arg = arg.strip()
                if arg.startswith('class='):
                    class_ = arg.split('=', 1)[1].strip()
        # print output
        node = self.get_start_node(formatter, name)
        if node:
            return tag.ul(self.render_node(node, formatter), class_=class_)
        else:
            return ''

    def get_start_node(self, formatter, name):
        return formatter.req.args.get('node')

    def render_node(self, node, formatter):
        html = tag.li(tag.a(node.title,href=node.href))
        if node.children and len(node.children):
            html_children = [self.render_node(child, formatter) for child in node.children];
            if node.isroot:
                html += html_children
            else:
                html += tag.ul(html_children)
        return html

class TracFlexWikiTreeMacro(TracFlexWikiChildrenMacro):
    """Prints expanded navigation tree.
    """

    def get_start_node(self, formatter, name):
        node = formatter.req.args.get('node')
        if node:
            return node.root


import pandas as pd
import os
os.chdir(r'C:\Users\shaya\OneDrive\Desktop\Antibiotics project\Effects of different amoxicillin\Data')
data = pd.read_excel('ASV_table.xlsx')
from EranElinav import normalize_cohort, recovery, species_richness
from Recovery import correlation_plot_recovery

# Separate the data into groups
A_columns = [col for col in data.columns if 'A' in col]
B_columns = [col for col in data.columns if 'B' in col]
C_columns = [col for col in data.columns if 'C' in col]
D_columns = [col for col in data.columns if 'D' in col]

group_A_cohort = data[A_columns]
group_B_cohort = data[B_columns]
group_C_cohort = data[C_columns]
group_D_cohort = data[D_columns]

# Separete each group into individuals
A1_columns = [col for col in group_A_cohort.columns if 'A1' in col]
A2_columns = [col for col in group_A_cohort.columns if 'A2' in col]
A3_columns = [col for col in group_A_cohort.columns if 'A3' in col]
A4_columns = [col for col in group_A_cohort.columns if 'A4' in col]
A5_columns = [col for col in group_A_cohort.columns if 'A5' in col]

A1_cohort = group_A_cohort[A1_columns]
A2_cohort = group_A_cohort[A2_columns]
A3_cohort = group_A_cohort[A3_columns]
A4_cohort = group_A_cohort[A4_columns]
A5_cohort = group_A_cohort[A5_columns]

B1_columns = [col for col in group_B_cohort.columns if 'B1' in col]
B2_columns = [col for col in group_B_cohort.columns if 'B2' in col]
B3_columns = [col for col in group_B_cohort.columns if 'B3' in col]
B4_columns = [col for col in group_B_cohort.columns if 'B4' in col]
B5_columns = [col for col in group_B_cohort.columns if 'B5' in col]

B1_cohort = group_B_cohort[B1_columns]
B2_cohort = group_B_cohort[B2_columns]
B3_cohort = group_B_cohort[B3_columns]
B4_cohort = group_B_cohort[B4_columns]
B5_cohort = group_B_cohort[B5_columns]

C1_columns = [col for col in group_C_cohort.columns if 'C1' in col]
C2_columns = [col for col in group_C_cohort.columns if 'C2' in col]
C3_columns = [col for col in group_C_cohort.columns if 'C3' in col]
C4_columns = [col for col in group_C_cohort.columns if 'C4' in col]
C5_columns = [col for col in group_C_cohort.columns if 'C5' in col]

C1_cohort = group_C_cohort[C1_columns]
C2_cohort = group_C_cohort[C2_columns]
C3_cohort = group_C_cohort[C3_columns]
C4_cohort = group_C_cohort[C4_columns]
C5_cohort = group_C_cohort[C5_columns]

D1_columns = [col for col in group_D_cohort.columns if 'D1' in col]
D2_columns = [col for col in group_D_cohort.columns if 'D2' in col]
D3_columns = [col for col in group_D_cohort.columns if 'D3' in col]
D4_columns = [col for col in group_D_cohort.columns if 'D4' in col]
D5_columns = [col for col in group_D_cohort.columns if 'D5' in col]

D1_cohort = group_D_cohort[D1_columns]
D2_cohort = group_D_cohort[D2_columns]
D3_cohort = group_D_cohort[D3_columns]
D4_cohort = group_D_cohort[D4_columns]
D5_cohort = group_D_cohort[D5_columns]

# Order the columns by the time of sampling

def order_columns(df):
    # Extract the suffix numbers from column names and convert to integers
    suffix_numbers = [int(col.split('S')[-1]) for col in df.columns]

    # Sort columns based on suffix numbers
    sorted_columns = [col for _, col in sorted(zip(suffix_numbers, df.columns))]

    # Create a new DataFrame with sorted columns
    ordered_df = df[sorted_columns]

    return ordered_df

A1_cohort = order_columns(A1_cohort)
A2_cohort = order_columns(A2_cohort)
A3_cohort = order_columns(A3_cohort)
A4_cohort = order_columns(A4_cohort)
A5_cohort = order_columns(A5_cohort)

B1_cohort = order_columns(B1_cohort)
B2_cohort = order_columns(B2_cohort)
B3_cohort = order_columns(B3_cohort)
B4_cohort = order_columns(B4_cohort)
B5_cohort = order_columns(B5_cohort)

C1_cohort = order_columns(C1_cohort)
C2_cohort = order_columns(C2_cohort)
C3_cohort = order_columns(C3_cohort)
C4_cohort = order_columns(C4_cohort)
C5_cohort = order_columns(C5_cohort)

D1_cohort = order_columns(D1_cohort)
D2_cohort = order_columns(D2_cohort)
D3_cohort = order_columns(D3_cohort)
D4_cohort = order_columns(D4_cohort)
D5_cohort = order_columns(D5_cohort)

days_A = [1, 5, 9, 13, 17, 21, 25, 29, 37]
days_B = [1, 5, 9, 13, 17, 21, 25]
days_C = [1, 5, 9, 13, 17, 21, 25, 29]
days_D = [1, 5, 9, 13, 17, 21, 25, 29, 37]

A1_cohort = normalize_cohort(A1_cohort.values.T)
A2_cohort = normalize_cohort(A2_cohort.values.T)
A3_cohort = normalize_cohort(A3_cohort.values.T)
A4_cohort = normalize_cohort(A4_cohort.values.T)
A5_cohort = normalize_cohort(A5_cohort.values.T)

B1_cohort = normalize_cohort(B1_cohort.values.T)
B2_cohort = normalize_cohort(B2_cohort.values.T)
B3_cohort = normalize_cohort(B3_cohort.values.T)
B4_cohort = normalize_cohort(B4_cohort.values.T)
B5_cohort = normalize_cohort(B5_cohort.values.T)

C1_cohort = normalize_cohort(C1_cohort.values.T)
C2_cohort = normalize_cohort(C2_cohort.values.T)
C3_cohort = normalize_cohort(C3_cohort.values.T)
C4_cohort = normalize_cohort(C4_cohort.values.T)
C5_cohort = normalize_cohort(C5_cohort.values.T)

D1_cohort = normalize_cohort(D1_cohort.values.T)
D2_cohort = normalize_cohort(D2_cohort.values.T)
D3_cohort = normalize_cohort(D3_cohort.values.T)
D4_cohort = normalize_cohort(D4_cohort.values.T)
D5_cohort = normalize_cohort(D5_cohort.values.T)

Effects_baseline_samples_list = [[A1_cohort[0, :]], [A2_cohort[0, :]], [A3_cohort[0, :]],
                                 [A4_cohort[0, :]], [A5_cohort[0, :]], [B1_cohort[0, :]],
                                 [B2_cohort[0, :]], [B3_cohort[0, :]], [B4_cohort[0, :]],
                                 [B5_cohort[0, :]], [C1_cohort[0, :]], [C2_cohort[0, :]],
                                 [C3_cohort[0, :]], [C4_cohort[0, :]], [C5_cohort[0, :]],
                                 [D1_cohort[0, :]], [D2_cohort[0, :]], [D3_cohort[0, :]],
                                 [D4_cohort[0, :]], [D5_cohort[0, :]]]

fig_1 = recovery(Effects_baseline_samples_list, B1_cohort[1:, :], labels=days_B[1:],
                 title='B1 recovery', index=5)
fig_2 = recovery(Effects_baseline_samples_list, B2_cohort[1:, :], labels=days_B[1:],
                 title='B2 recovery', index=6)
fig_3 = recovery(Effects_baseline_samples_list, B3_cohort[1:, :], labels=days_B[1:],
                 title='B3 recovery', index=7)
fig_4 = recovery(Effects_baseline_samples_list, B4_cohort[1:, :], labels=days_B[1:],
                 title='B4 recovery', index=8)
fig_5 = recovery(Effects_baseline_samples_list, B5_cohort[1:, :], labels=days_B[1:],
                 title='B5 recovery', index=9)
fig_6 = recovery(Effects_baseline_samples_list, C1_cohort[1:, :], labels=days_C[1:],
                 title='C1 recovery', index=10)
fig_7 = recovery(Effects_baseline_samples_list, C2_cohort[1:, :], labels=days_C[1:],
                 title='C2 recovery', index=11)
fig_8 = recovery(Effects_baseline_samples_list, C3_cohort[1:, :], labels=days_C[1:],
                 title='C3 recovery', index=12)
fig_9 = recovery(Effects_baseline_samples_list, C4_cohort[1:, :], labels=days_C[1:],
                 title='C4 recovery', index=13)
fig_10 = recovery(Effects_baseline_samples_list, C5_cohort[1:, :], labels=days_C[1:],
                 title='C5 recovery', index=14)
fig_11 = recovery(Effects_baseline_samples_list, D1_cohort[1:, :], labels=days_D[1:],
                 title='D1 recovery', index=15)
fig_12 = recovery(Effects_baseline_samples_list, D2_cohort[1:, :], labels=days_D[1:],
                 title='D2 recovery', index=16)
fig_13 = recovery(Effects_baseline_samples_list, D3_cohort[1:, :], labels=days_D[1:],
                 title='D3 recovery', index=17)
fig_14 = recovery(Effects_baseline_samples_list, D4_cohort[1:, :], labels=days_D[1:],
                 title='D4 recovery', index=18)
fig_15 = recovery(Effects_baseline_samples_list, D5_cohort[1:, :], labels=days_D[1:],
                 title='D5 recovery', index=19)

baseline = [B1_cohort[0, :], B2_cohort[0, :], B3_cohort[0, :], B4_cohort[0, :],
            B5_cohort[0, :], C1_cohort[0, :], C2_cohort[0, :], C3_cohort[0, :],
            C4_cohort[0, :], C5_cohort[0, :], D1_cohort[0, :], D2_cohort[0, :],
            D3_cohort[0, :], D4_cohort[0, :], D5_cohort[0, :]]

ABX = [B1_cohort[1, :], B2_cohort[1, :], B3_cohort[1, :], B4_cohort[1, :],
       B5_cohort[1, :], C1_cohort[2, :], C2_cohort[2, :], C3_cohort[2, :],
       C4_cohort[2, :], C5_cohort[2, :], D1_cohort[3, :], D2_cohort[3, :],
       D3_cohort[3, :], D4_cohort[3, :], D5_cohort[3, :]]

follow_up = [B1_cohort[-1, :], B2_cohort[-1, :], B3_cohort[-1, :], B4_cohort[-1, :],
             B5_cohort[-1, :], C1_cohort[-1, :], C2_cohort[-1, :], C3_cohort[-1, :],
             C4_cohort[-1, :], C5_cohort[-1, :], D1_cohort[-1, :], D2_cohort[-1, :],
             D3_cohort[-1, :], D4_cohort[-1, :], D5_cohort[-1, :]]

# Create the correlation plots
fig_16 = correlation_plot_recovery(baseline, ABX, follow_up, change=False)
fig_17 = correlation_plot_recovery(baseline, ABX, follow_up, change=True)
fig_18 = correlation_plot_recovery(baseline, ABX, follow_up, change=False, survived=True)

# Calculate lists of the species richness at baseline, follow-up and ABX
num_species_base_effects = [species_richness(smp) for smp in baseline]
num_species_follow_effects = [species_richness(smp) for smp in follow_up]
num_species_ABX_effects = [species_richness(smp) for smp in ABX]

# Save the lists
import os
os.chdir(r'C:\Users\shaya\OneDrive\Desktop\Antibiotics project\Effects of different amoxicillin\Data')

df_num_species_base_recovery = pd.DataFrame({'Species richness': num_species_base_effects })
df_num_species_follow_recovery = pd.DataFrame({'Species richness': num_species_follow_effects })
df_num_species_ABX_recovery = pd.DataFrame({'Species richness': num_species_ABX_effects })

df_num_species_base_recovery.to_excel('num_species_base_effects.xlsx', index=False)
df_num_species_follow_recovery.to_excel('num_species_follow_effects.xlsx', index=False)
df_num_species_ABX_recovery.to_excel('num_species_ABX_effects.xlsx', index=False)


import random
from itertools import zip_longest
from typing import List, Union, Optional, Tuple

import matplotlib as mpl
import numpy as np
import pandas as pd
from PySide6.QtCore import Qt, QSettings, QSize, QAbstractTableModel, QModelIndex, QObject, QEvent, QRect, QRectF
from PySide6.QtGui import (QColor, QStandardItemModel, QIcon, QDropEvent, QKeyEvent, QBrush, QImage, QPixmap,
                           QPainter, QTransform, QPen, QPolygonF, QGradient, QPalette)
from PySide6.QtWidgets import (QDialog, QColorDialog, QListWidgetItem, QFileDialog, QMessageBox, QInputDialog,
                             QAbstractItemView, QDialogButtonBox, QFormLayout, QDoubleSpinBox, QAbstractSpinBox, QLabel,
                             QStyledItemDelegate, QStyleOptionViewItem, QWidget, QHBoxLayout, QButtonGroup,
                             QToolButton)

from PySide6MolecularNetwork.node import NODE_POLYGON_MAP, NodePolygon
from metgem.models.metadata import ColorMarkRole, ColumnDataRole
from metgem.utils import pairwise
from metgem.ui.color_mapping_dialog_ui import Ui_ColorMappingDialog

ColumnRole = Qt.UserRole + 1
ValueRole = Qt.UserRole + 2

MOVE_COLUMNS_SELECT = 0
MOVE_COLUMNS_UNSELECT = 1

NODE_BRUSH_STYLES = {
    Qt.NoBrush,
    Qt.Dense1Pattern,
    Qt.Dense2Pattern,
    Qt.Dense3Pattern,
    Qt.Dense4Pattern,
    Qt.Dense5Pattern,
    Qt.Dense6Pattern,
    Qt.Dense7Pattern,
    Qt.HorPattern,
    Qt.VerPattern,
    Qt.CrossPattern,
    Qt.BDiagPattern,
    Qt.FDiagPattern,
    Qt.DiagCrossPattern,
}

# https://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/
# noinspection PyCallByClass,PyTypeChecker
def generate_colors(n, seed=None):
    """Generate list of `n` colors"""

    colors = []
    if seed is None:
        h = random.random()
    else:
        h = seed

    golden_ratio = 0.618033988749895  # 1/phi
    for i in range(n):
        colors.append(QColor.fromHsv(h * 360, 150, 254, 255))
        h += golden_ratio
        h %= 1
    return colors

def get_colors(n, cmap='auto'):
    if cmap == 'auto':
        return generate_colors(n)
    else:
        try:
            cm = mpl.colormaps.get_cmap(cmap)
        except ValueError:
            return []
        if (isinstance(cm, (mpl.colors.ListedColormap, mpl.colors.LinearSegmentedColormap)) and cm.N < 256) \
                or n == 1:
            return [QColor(*cm(i, bytes=True)) for i in range(cm.N)]
        else:
            step = 256 // (n - 1)
            return [QColor(*cm(i * step, bytes=True)) for i in range(n)]

# https://gist.github.com/ChrisBeaumont/4025831
# noinspection PyArgumentList
def cmap2pixmap(cmap, steps=50):
    """Convert a maplotlib colormap into a QPixmap
    :param cmap: The colormap to use
    :type cmap: Matplotlib colormap instance (e.g. matplotlib.cm.gray)
    :param steps: The number of color steps in the output. Default=50
    :type steps: int
    :rtype: QPixmap
    """
    if cmap == 'auto':
        colors = generate_colors(steps)
    else:
        try:
            norm = mpl.colors.Normalize(vmin=0., vmax=1.)
            sm = mpl.cm.ScalarMappable(norm=norm, cmap=cmap)
            inds = np.linspace(0, 1, steps)
            colors = [QColor(*c) for c in sm.to_rgba(inds, bytes=True)]
        except ValueError:
            return

    im = QImage(steps, 1, QImage.Format_Indexed8)
    im.setColorTable([c.rgba() for c in colors])
    for i in range(steps):
        im.setPixel(i, 0, i)
    im = im.scaled(72, 18)
    return QPixmap.fromImage(im)

def nodePolygonToPixmap(polygon: NodePolygon, size: QSize = QSize(48, 48), zoom: int = 1.2) -> QPixmap:
    w, h = size.width(), size.height()
    image = QImage(w*zoom, h*zoom, QImage.Format_ARGB32)
    image.fill(Qt.transparent)
    painter = QPainter(image)
    painter.setPen(QPen(QPalette().color(QPalette.Text), 5))
    painter.setTransform(QTransform().translate(w/2*zoom, h/2*zoom).scale(w/100., h/100.))
    if polygon == NodePolygon.Circle:
        painter.drawEllipse(QRectF(-50*zoom, -50*zoom, 100., 100.))
    else:
        polygon = NODE_POLYGON_MAP.get(polygon)
        painter.drawPolygon(polygon)
    painter.end()
    return QPixmap(image)

def nodeBrushStyleToPixmap(style: Qt.BrushStyle, size: QSize = QSize(48, 48), zoom: int = 1.2) -> QPixmap:
    w, h = size.width(), size.height()
    image = QImage(w*zoom, h*zoom, QImage.Format_ARGB32)
    image.fill(Qt.transparent)
    painter = QPainter(image)
    painter.setBrush(QBrush(style))
    painter.drawRect(QRectF(zoom, zoom, w, h))
    painter.end()
    return QPixmap(image)

class ColorDialog(QColorDialog):

    @staticmethod
    def getColorPolygonAndBrushStyle(
            initial_color: Union[QColor, Qt.GlobalColor, QGradient] = QColor(),
            initial_polygon: NodePolygon = NodePolygon.Circle,
            initial_brushstyle: Qt.BrushStyle = Qt.NoBrush,
            parent: Optional[QWidget] = None, title: str = '',

            options: Union[QColorDialog.ColorDialogOptions,
                           QColorDialog.ColorDialogOption] = QColorDialog.ColorDialogOptions()) \
            -> (QColor, NodePolygon):

        dlg = QColorDialog(parent)

        # Add buttons to select polygon
        polygons_group = QButtonGroup(parent)
        polygons_layout = QHBoxLayout(parent)
        for p in NodePolygon:
            if p == NodePolygon.Custom:
                continue

            button = QToolButton(parent)
            button.setCheckable(True)
            button.setText(p.name)
            button.setIcon(QIcon(nodePolygonToPixmap(p, button.iconSize())))

            if p == initial_polygon:
                button.setChecked(True)
            polygons_group.addButton(button)
            polygons_group.setId(button, p.value)
            polygons_layout.addWidget(button)
        dlg.layout().insertLayout(dlg.layout().count()-1, polygons_layout)

        # Add buttons to select brush style
        brushstyle_group = QButtonGroup(parent)
        brushstyle_layout = QHBoxLayout(parent)
        for p in NODE_BRUSH_STYLES:
            button = QToolButton(parent)
            button.setCheckable(True)
            button.setIcon(QIcon(nodeBrushStyleToPixmap(p, button.iconSize())))

            if p == initial_brushstyle:
                button.setChecked(True)
            brushstyle_group.addButton(button)
            brushstyle_group.setId(button, p.value)
            brushstyle_layout.addWidget(button)
        dlg.layout().insertLayout(dlg.layout().count()-1, brushstyle_layout)

        if title:
            dlg.setWindowTitle(title)
        dlg.setOptions(options)
        dlg.setCurrentColor(initial_color)
        dlg.exec_()
        return (dlg.selectedColor(),
                NodePolygon(polygons_group.checkedId()),
                brushstyle_group.checkedId(),
                )

class RangeInputDialog(QDialog):
    def __init__(self, parent=None, low=None, high=None):
        super().__init__(parent)

        self.sbLowValue = QDoubleSpinBox(self)
        self.sbLowValue.setMinimum(0)
        self.sbLowValue.setStepType(QAbstractSpinBox.StepType.AdaptiveDecimalStepType)
        if low is not None:
            self.sbLowValue.setValue(low)

        self.sbHighValue = QDoubleSpinBox(self)
        self.sbHighValue.setMinimum(0)
        self.sbLowValue.setStepType(QAbstractSpinBox.StepType.AdaptiveDecimalStepType)
        if high is not None:
            self.sbHighValue.setValue(high)

        button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self)

        layout = QFormLayout(self)
        layout.addRow("Low value", self.sbLowValue)
        layout.addRow("High value", self.sbHighValue)
        layout.addWidget(button_box)

        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)

    def getRange(self):
        return self.sbLowValue.value(), self.sbHighValue.value()

class WidgetItem(QListWidgetItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setSizeHint(QSize(self.sizeHint().width(), 32))
        self.setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

# noinspection PyUnresolvedReferences
class ColorMixin:
    __metaclass__ = WidgetItem

    def setBackground(self, background: Union[QBrush, QColor]):
        if background is not None:
            if not isinstance(background, QBrush):
                background = QBrush(background)
            color = background.color()
            luma = (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255
            text_color = QColor(Qt.black) if luma > 0.5 else QColor(Qt.white)

            super().setBackground(color)
            self.setForeground(text_color)
        else:
            self.setData(Qt.BackgroundRole, None)
            self.setData(Qt.ForegroundRole, None)

class GroupListWidgetItem(ColorMixin, WidgetItem):
    def __init__(self, data, *args, **kwargs):
        super().__init__(data, *args, **kwargs)
        self.setData(ValueRole, data)

class BoolListWidgetItem(ColorMixin, WidgetItem):
    def __init__(self, data, *args, **kwargs):
        super().__init__(str(data), *args, **kwargs)
        self.setData(ValueRole, data)

class RangeListWidgetItem(ColorMixin, WidgetItem):
    def __init__(self, low, high, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setData(ValueRole, (low, high))

    def setData(self, role, value):
        super().setData(role, value)
        if role == ValueRole:
            self.setText('[{0:.2f} - {1:.2f}]'.format(*value))

    def __lt__(self, other):
        sval = self.data(ValueRole)
        oval = other.data(ValueRole)
        if sval is not None and oval is not None:
            return sval < oval
        return super().__lt__(other)

class ColumnListWidgetItem(ColorMixin, WidgetItem):
    def __init__(self, *args, column: int = None, **kwargs):
        super().__init__(*args, **kwargs)

        self.setData(ColumnRole, column)

    def __lt__(self, other):
        scol = self.data(ColumnRole)
        ocol = other.data(ColumnRole)
        if scol is not None and ocol is not None:
            return scol < ocol
        return super().__lt__(other)

class ColorListWidgetItem(ColorMixin, WidgetItem):
    def setBackground(self, background: QBrush):
        if not isinstance(background, QBrush):
            background = QBrush(background)

        color = background.color()
        self.setData(Qt.DisplayRole,
                     f"RGB{color.red(), color.green(), color.blue()} [{color.name()}]")
        super().setBackground(background)

class ItemDelegate(QStyledItemDelegate):
    def paint(self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex) -> None:
        super().paint(painter, option, index)
        polygon_id = index.data(BaseColorMappingDialog.PolygonRole)
        brushstyle = index.data(BaseColorMappingDialog.BrushStyleRole)
        h = option.rect.height()

        painter.save()
        painter.setRenderHint(QPainter.Antialiasing)
        painter.setClipRect(option.rect)
        painter.setPen(QPen(Qt.lightGray, h/5.))
        if brushstyle is not None:
            painter.setBrush(QBrush(Qt.lightGray, Qt.BrushStyle(brushstyle)))
        zoom = .9
        painter.setTransform(QTransform()
                             .translate(option.rect.x()+h/2+10, option.rect.y()+h/2)
                             .rotate(-5.)
                             .scale(h/100.*zoom, h/100.*zoom))
        if polygon_id is None or polygon_id == NodePolygon.Circle:
            painter.drawEllipse(QRect(-50, -50, 100, 100))
        else:
            polygon = NODE_POLYGON_MAP.get(polygon_id, QPolygonF())
            painter.drawPolygon(polygon)
        painter.restore()

class BaseColorMappingDialog(QDialog, Ui_ColorMappingDialog):

    PolygonRole = Qt.UserRole + 3
    BrushStyleRole = Qt.UserRole + 4

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setupUi(self)
        self.setWindowFlags(Qt.Tool | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint)

        current_cmap = QSettings().value('ColorMap', 'auto')
        cmaps = ['auto'] + sorted([k for k in mpl.colormaps.keys() if not k.endswith('_r')], key=lambda s: s.casefold())
        for i, cmap in enumerate(cmaps):
            pixmap = cmap2pixmap(cmap)
            self.cbColorMap.addItem(QIcon(pixmap), cmap)
            if cmap == current_cmap:
                self.cbColorMap.setCurrentIndex(i)

        self.lstColumns.viewport().installEventFilter(self)
        self.lstUsedColumns.viewport().installEventFilter(self)
        self.lstUsedColumns.installEventFilter(self)

        self.lstUsedColumns.itemDoubleClicked.connect(self.get_color)
        self.lstColors.itemDoubleClicked.connect(self.get_color)
        self.btEditColor.clicked.connect(lambda: self.get_color(self.lstColors.currentItem()))
        self.btAddColor.clicked.connect(self.add_color)
        self.btRemoveColors.clicked.connect(self.remove_selected_colors)
        self.btGenerateColors.clicked.connect(lambda: self.generate_new_colors(self.cbColorMap.currentText()))
        self.btAutoAssignColors.clicked.connect(self.auto_assign_colors)
        self.cbColorMap.currentIndexChanged.connect(lambda x: self.generate_new_colors(self.cbColorMap.itemText(x)))
        self.btRemoveSelectedColumnsColors.clicked.connect(self.remove_selected_columns_colors)
        self.btLoadColorList.clicked.connect(self.load_color_list)
        self.btSaveColorList.clicked.connect(self.save_color_list)

    def get_color(self, item: ColorListWidgetItem = None):
        current_color = item.data(Qt.BackgroundRole).color() \
            if item is not None and item.data(Qt.BackgroundRole) is not None else QColor()
        color = QColorDialog.getColor(initial=current_color, parent=self,
                                      options=QColorDialog.ColorDialogOption.ShowAlphaChannel)
        if color.isValid():
            item.setBackground(color)

    def add_color(self):
        color = QColorDialog.getColor(parent=self, options=QColorDialog.ColorDialogOption.ShowAlphaChannel)
        if color.isValid():
            item = ColorListWidgetItem()
            item.setBackground(color)
            self.lstColors.addItem(item)

    def remove_selected_colors(self):
        for item in self.lstColors.selectedItems():
            self.lstColors.takeItem(self.lstColors.row(item))

    def set_colors(self, colors: List[Union[str, QColor]],
                   polygons: List[Union[int, str, NodePolygon]] = [],
                   brush_styles: List[Union[str, Qt.BrushStyle]] = []):
        self.lstColors.clear()
        for color, poly, brush_style in zip_longest(colors, polygons, brush_styles):
            if isinstance(color, str):
                color = QColor(color)

            if isinstance(poly, str):
                try:
                    poly = NodePolygon[poly]
                except KeyError:
                    poly = NodePolygon.Circle
            if isinstance(poly, int):
                poly = NodePolygon(poly)

            if isinstance(brush_style, str):
                brush_style = Qt.BrushStyle(int(brush_style))

            if color.isValid():
                item = ColorListWidgetItem()
                item.setBackground(color)
                if poly is not None:
                    item.setData(BaseColorMappingDialog.PolygonRole, poly)
                if brush_style is not None:
                    item.setData(BaseColorMappingDialog.BrushStyleRole, Qt.BrushStyle(brush_style))
                self.lstColors.addItem(item)

    def generate_new_colors(self, cmap: str = 'auto'):
        self.set_colors(get_colors(self.lstUsedColumns.count(), cmap=cmap))

    def auto_assign_colors(self):
        column_items = self.lstUsedColumns.selectedItems()
        if not column_items:
            column_items = [self.lstUsedColumns.item(row) for row in range(self.lstUsedColumns.count())]
        color_items = self.lstColors.selectedItems()
        if not color_items:
            color_items = [self.lstColors.item(row) for row in range(self.lstColors.count())]

        for i, column_item in enumerate(column_items):
            try:
                color_item = color_items[i]
            except IndexError:
                color_item = color_items[-1]

            if color_item:
                column_item.setBackground(color_item.data(Qt.BackgroundRole).color())
                column_item.setData(BaseColorMappingDialog.PolygonRole,
                                    color_item.data(BaseColorMappingDialog.PolygonRole))
                column_item.setData(BaseColorMappingDialog.BrushStyleRole,
                                    color_item.data(BaseColorMappingDialog.BrushStyleRole))

    def remove_selected_columns_colors(self):
        for item in self.lstUsedColumns.selectedItems():
            item.setBackground(None)

    def import_color_item(self, row: List[str]) -> Tuple:
        color = QColor(row[1][0])
        if color.isValid():
            return color,

    def load_color_list(self):
        filter_ = ["Color list (*.txt)"]
        filename, filter_ = QFileDialog.getOpenFileName(self, "Load color list",
                                                        filter=";;".join(filter_))
        if filename:
            data = []
            try:
                df = pd.read_csv(filename, header=None)

                for row in df.iterrows():
                    d = self.import_color_item(row)
                    if d is not None:
                        data.append(d)

                self.set_colors(*zip(*data))
            except FileNotFoundError:
                QMessageBox.warning(self, None, "Selected file does not exists.")
            except IOError:
                QMessageBox.warning(self, None, "Load failed (I/O Error).")

    def export_color_item(self, item: QListWidgetItem) -> List[str]:
        color = item.data(Qt.BackgroundRole).color()
        if color is not None and color.isValid():
            return color.name(),

    def save_color_list(self):
        filter_ = ["Color list (*.txt)"]
        filename, filter_ = QFileDialog.getSaveFileName(self, "Save color list",
                                                        filter=";;".join(filter_))
        if filename:
            try:
                with open(filename, 'w') as f:
                    for row in range(self.lstColors.count()):
                        f.write(",".join([str(_) for _ in self.export_color_item(self.lstColors.item(row))]) + '\n')
            except FileNotFoundError:
                QMessageBox.warning(self, None, "Selected file does not exists.")
            except IOError:
                QMessageBox.warning(self, None, "Save failed (I/O Error).")

    def eventFilter(self, obj: QObject, event: Union[QDropEvent, QKeyEvent, QEvent]):
        if event.type() == QEvent.KeyPress and obj == self.lstUsedColumns:
            if event.key() == Qt.Key_Delete or event.key() == Qt.Key_Backspace:
                self.remove_selected_columns_colors()
                event.accept()
        elif event.type() == QEvent.DragEnter and obj == event.source().viewport():
            event.setDropAction(Qt.IgnoreAction)  # Do not allow internal drag'n'drop
        elif event.type() == QEvent.Drop and obj == self.lstUsedColumns.viewport():
            if event.source() == self.lstColors:
                data = event.mimeData()
                if data.hasFormat('application/x-qabstractitemmodeldatalist'):
                    first_item = self.lstUsedColumns.itemAt(event.pos())

                    if first_item is not None:
                        model = QStandardItemModel()
                        model.dropMimeData(data, Qt.CopyAction, 0, 0, QModelIndex())
                        for i in range(model.rowCount()):
                            color = model.item(i, 0).data(Qt.BackgroundRole).color()
                            polygon_id = model.item(i, 0).data(BaseColorMappingDialog.PolygonRole)
                            brushstyle = model.item(i, 0).data(BaseColorMappingDialog.BrushStyleRole)
                            item = self.lstUsedColumns.item(self.lstUsedColumns.row(first_item) + i)
                            if item:
                                item.setBackground(color)
                                item.setData(BaseColorMappingDialog.PolygonRole, polygon_id)
                                item.setData(BaseColorMappingDialog.BrushStyleRole, brushstyle)
                    event.accept()
            elif event.source() == self.lstColumns:
                data = event.mimeData()
                if data.hasFormat('application/x-qabstractitemmodeldatalist'):
                    event.setDropAction(Qt.MoveAction)
                    model = QStandardItemModel()
                    model.dropMimeData(data, Qt.CopyAction, 0, 0, QModelIndex())

                    for i in range(model.rowCount()):
                        item = model.item(i, 0)
                        new_item = ColumnListWidgetItem(item.data(Qt.DisplayRole),
                                                        column=item.data(ColumnRole))
                        bg = item.data(Qt.BackgroundRole)
                        polygon_id = item.data(BaseColorMappingDialog.PolygonRole)
                        brushstyle = item.data(BaseColorMappingDialog.BrushStyleRole)
                        if bg is not None and bg.color().isValid():
                            new_item.setBackground(bg.color())
                        new_item.setData(BaseColorMappingDialog.PolygonRole, polygon_id)
                        new_item.setData(BaseColorMappingDialog.BrushStyleRole, brushstyle)
                        self.lstUsedColumns.addItem(new_item)
                    self.lstUsedColumns.sortItems()
                    event.accept()

        return super().eventFilter(obj, event)

class PieColorMappingDialog(BaseColorMappingDialog):

    def __init__(self, *args, model: QAbstractTableModel = None, selected_columns: List[int] = None, **kwargs):
        super().__init__(*args, **kwargs)

        for i in range(self.layoutBins.count()):
            w = self.layoutBins.itemAt(i).widget()
            if w is not None and not isinstance(w, QLabel):
                w.hide()

        if model is not None:
            for col in range(model.columnCount()):
                text = model.headerData(col, Qt.Horizontal, Qt.DisplayRole)
                color = model.headerData(col, Qt.Horizontal, ColorMarkRole)
                item = ColumnListWidgetItem(text, column=col)
                if color is not None:
                    item.setBackground(color)

                if selected_columns is not None and col in selected_columns:
                    self.lstUsedColumns.addItem(item)
                else:
                    self.lstColumns.addItem(item)

        colors = QSettings().value('NetworkView/pie_colors',
                                   [c.name() for c in generate_colors(8)])
        self.set_colors(colors)

        self.btUseSelectedColumns.clicked.connect(lambda: self.move_selected_columns(MOVE_COLUMNS_SELECT))
        self.btRemoveSelectedColumns.clicked.connect(lambda: self.move_selected_columns(MOVE_COLUMNS_UNSELECT))

    def move_selected_columns(self, direction=MOVE_COLUMNS_SELECT):
        if direction == MOVE_COLUMNS_SELECT:
            source = self.lstColumns
            dest = self.lstUsedColumns
        else:
            source = self.lstUsedColumns
            dest = self.lstColumns

        for item in source.selectedItems():
            source.takeItem(source.row(item))
            new_item = ColumnListWidgetItem(item.data(Qt.DisplayRole), column=item.data(ColumnRole))
            bg = item.data(Qt.BackgroundRole)
            if bg is not None and bg.color().isValid():
                new_item.setBackground(bg.color())
            dest.addItem(new_item)
            new_item.setSelected(True)

    def done(self, r):
        if r == QDialog.DialogCode.Accepted:
            colors = [self.lstColors.item(row).data(Qt.BackgroundRole).color().name()
                      for row in range(self.lstColors.count())]
            QSettings().setValue('NetworkView/pie_colors', colors)
        super().done(r)

    def getValues(self):
        columns = []
        colors = []
        for row in range(self.lstUsedColumns.count()):
            item = self.lstUsedColumns.item(row)
            column = item.data(ColumnRole)
            bg = item.data(Qt.BackgroundRole)
            columns.append(column)
            if bg is not None and bg.color().isValid():
                colors.append(bg.color())
            else:
                colors.append(QColor(Qt.transparent))
        return columns, colors

class ColorMappingDialog(BaseColorMappingDialog):

    def __init__(self, *args, model: QAbstractTableModel = None, column_id: int = None, mapping: dict = None, **kwargs):
        self._model = model
        self._column_id = column_id

        super().__init__(*args, **kwargs)

        self.lstUsedColumns.setItemDelegate(ItemDelegate())
        self.lstColors.setItemDelegate(ItemDelegate())

        data = self._model.headerData(self._column_id, Qt.Horizontal, role=ColumnDataRole)
        if data is not None and not isinstance(data, pd.Series):
            data = pd.Series(data)
        self._data = data.fillna(0).squeeze()

        if model is not None and column_id is not None:
            for i in range(model.columnCount()):
                index = model.index(0, i)
                text = model.headerData(i, Qt.Horizontal, Qt.DisplayRole)
                color = model.headerData(i, Qt.Horizontal, ColorMarkRole)
                item = ColumnListWidgetItem(text, column=index.column())
                if color is not None:
                    item.setBackground(color)

                if index.column() != column_id:
                    self.lstColumns.addItem(item)

        self.lstUsedColumns.setSelectionMode(QAbstractItemView.SelectionMode.ContiguousSelection)
        self.lstColumns.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
        self.populate_bins(mapping)

        colors = QSettings().value('NetworkView/node_colors',
                                   [c.name() for c in generate_colors(8)])
        self.set_colors(colors)

        self.btUseSelectedColumns.clicked.connect(self.on_use_selected_column)
        self.btRemoveSelectedColumns.clicked.connect(self.on_unuse_selected_column)

    def populate_bins(self, mapping=None):
        try:
            self.btGenerateBins.clicked.disconnect()
            self.btMergeBins.clicked.disconnect()
            self.btSplitBins.clicked.disconnect()
            self.btEditBin.clicked.disconnect()
            self.btAddBin.clicked.disconnect()
            self.btRemoveBins.clicked.disconnect()
        except (TypeError, RuntimeError):
            pass

        data = self._data

        if data is not None:
            if data.dtype == bool:
                self.lblUsedColumns.setText('Groups')
                for val in (True, False):
                    item = BoolListWidgetItem(val)
                    self.lstUsedColumns.addItem(item)
                    if mapping is not None:
                        color = mapping.get(val, None)
                        if color is None:
                            color = mapping.get(str(val).lower(), None)  # True -> 'true', False -> 'false'
                        if color is not None:
                            item.setBackground(QColor(color))
            elif data.dtype == object:
                self.lblUsedColumns.setText('Groups')
                if mapping is not None:
                    for group, color in mapping:
                        item = GroupListWidgetItem(str(group))
                        item.setBackground(QColor(color))
                        self.lstUsedColumns.addItem(item)
                else:
                    groups = data.dropna().unique()
                    for group in groups:
                        item = GroupListWidgetItem(str(group))
                        self.lstUsedColumns.addItem(item)
            else:
                self.lblUsedColumns.setText('Bins')
                if mapping is not None:
                    values, colors = mapping
                    try:
                        for low, high, color in zip(values[:-1], values[1:], colors):
                            item = RangeListWidgetItem(low, high)
                            item.setBackground(QColor(color))
                            self.lstUsedColumns.addItem(item)
                    except ValueError:
                        self.generate_new_bins(8)
                else:
                    self.generate_new_bins(8)

                self.btGenerateBins.clicked.connect(self.on_generate_new_bins)
                self.btMergeBins.clicked.connect(self.on_merge_bins)
                self.btSplitBins.clicked.connect(self.on_split_bins)
                self.btEditBin.clicked.connect(self.on_edit_bin)
                self.btAddBin.clicked.connect(self.on_add_bin)
                self.btRemoveBins.clicked.connect(self.on_remove_bins)

    def on_unuse_selected_column(self):
        self.lstUsedColumns.clear()

        if self._column_id is not None:
            index = self._model.index(0, self._column_id)
            text = self._model.headerData(self._column_id, Qt.Horizontal, Qt.DisplayRole)
            color = self._model.headerData(self._column_id, Qt.Horizontal, ColorMarkRole)
            item = ColumnListWidgetItem(text, column=index.column())
            if color is not None:
                item.setBackground(color)

            self.lstColumns.addItem(item)

    def on_use_selected_column(self):
        self.on_unuse_selected_column()

        selected_items = self.lstColumns.selectedItems()
        if len(selected_items) != 1:
            return

        self.lstUsedColumns.clear()
        item = selected_items[0]
        self.lstColumns.takeItem(self.lstColumns.row(item))
        self._column_id = item.data(ColumnRole)

        self.populate_bins()

    def on_remove_bins(self):
        for item in self.lstUsedColumns.selectedItems():
            self.lstUsedColumns.takeItem(self.lstUsedColumns.row(item))

    def on_add_bin(self):
        dlg = RangeInputDialog(self)
        if dlg.exec():
            item = RangeListWidgetItem(*dlg.getRange())
            self.lstUsedColumns.addItem(item)

    def on_edit_bin(self):
        for item in self.lstUsedColumns.selectedItems():
            low, high = item.data(ValueRole)
            dlg = RangeInputDialog(self, low=low, high=high)
            if dlg.exec():
                item.setData(ValueRole, dlg.getRange())
            break

    def on_split_bins(self):
        for item in self.lstUsedColumns.selectedItems():
            low, high = item.data(ValueRole)
            self.lstUsedColumns.takeItem(self.lstUsedColumns.row(item))
            diff = (low + high) / 2
            item = RangeListWidgetItem(low, low + diff)
            self.lstUsedColumns.addItem(item)
            item = RangeListWidgetItem(low + diff, high)
            self.lstUsedColumns.addItem(item)

    def on_merge_bins(self):
        bins = []
        for item in self.lstUsedColumns.selectedItems():
            bins.extend(item.data(ValueRole))
            self.lstUsedColumns.takeItem(self.lstUsedColumns.row(item))

        if bins:
            item = RangeListWidgetItem(min(bins), max(bins))
            self.lstUsedColumns.addItem(item)

    def on_generate_new_bins(self):
        bins, ok = QInputDialog.getInt(self, None, "Enter a number of bins:", value=8, min=1)

        if ok:
            self.generate_new_bins(bins)

    def generate_new_bins(self, bins=8):
        self.lstUsedColumns.clear()
        for low, high in pairwise(np.histogram_bin_edges(self._data, bins=bins)):
            item = RangeListWidgetItem(low, high)
            self.lstUsedColumns.addItem(item)

    def import_color_item(self, row: List[str]) -> Tuple:
        color = QColor(row[1][0])
        try:
            polygon_id = NodePolygon[row[1][1]] if len(row[1]) > 1 else NodePolygon.Circle
        except KeyError:
            polygon_id = NodePolygon.Circle
        brushstyle = Qt.BrushStyle(row[1][2]) if len(row[1]) > 2 else Qt.NoBrush

        if color.isValid():
            return color, polygon_id, brushstyle

    def export_color_item(self, item: QListWidgetItem) -> Tuple[str, str, Qt.BrushStyle]:
        color = item.data(Qt.BackgroundRole).color()
        polygon_id = item.data(BaseColorMappingDialog.PolygonRole)
        polygon_id = polygon_id if polygon_id is not None else NodePolygon.Circle
        brushstyle: Qt.BrushStyle = item.data(BaseColorMappingDialog.BrushStyleRole)
        brushstyle = brushstyle if brushstyle is not None else Qt.NoBrush

        if color is not None and color.isValid():
            return color.name(), polygon_id.name, brushstyle

    def done(self, r):
        if r == QDialog.DialogCode.Accepted:
            colors = [self.lstColors.item(row).data(Qt.BackgroundRole).color().name()
                      for row in range(self.lstColors.count())]
            QSettings().setValue('NetworkView/node_colors', colors)
        super().done(r)

    def get_color(self, item: ColorListWidgetItem = None):
        current_color = item.data(Qt.BackgroundRole).color() \
            if item is not None and item.data(Qt.BackgroundRole) is not None else QColor()
        current_polygon = item.data(BaseColorMappingDialog.PolygonRole) \
            if item is not None and item.data(BaseColorMappingDialog.PolygonRole) is not None else NodePolygon.Circle
        current_brushstyle = item.data(BaseColorMappingDialog.BrushStyleRole) \
            if item is not None and item.data(BaseColorMappingDialog.BrushStyleRole) is not None else Qt.NoBrush

        color, polygon_id, brushstyle = ColorDialog.getColorPolygonAndBrushStyle(
            initial_color=current_color,
            initial_polygon=current_polygon,
            initial_brushstyle=current_brushstyle,
            parent=self,
            options=QColorDialog.ColorDialogOption.ShowAlphaChannel)

        if color.isValid():
            item.setBackground(color)
        item.setData(BaseColorMappingDialog.PolygonRole, polygon_id)
        item.setData(BaseColorMappingDialog.BrushStyleRole, Qt.BrushStyle(brushstyle))

    def add_color(self):
        color, polygon_id, brushstyle = ColorDialog.getColorPolygonAndBrushStyle(
            parent=self,
            options=QColorDialog.ColorDialogOption.ShowAlphaChannel)

        if color.isValid():
            item = ColorListWidgetItem()
            item.setBackground(color)
            item.setData(BaseColorMappingDialog.PolygonRole, polygon_id)
            item.setData(BaseColorMappingDialog.BrushStyleRole, Qt.BrushStyle(brushstyle))
            self.lstColors.addItem(item)

    def getValues(self):
        item = self.lstUsedColumns.item(0)
        if isinstance(item, BoolListWidgetItem):
            mapping = {}
            for row in range(self.lstUsedColumns.count()):
                item = self.lstUsedColumns.item(row)
                group = item.data(ValueRole)
                bg = item.data(Qt.BackgroundRole)
                polygon_id = item.data(BaseColorMappingDialog.PolygonRole)
                polygon_id = polygon_id if polygon_id is not None else NodePolygon.Circle
                brushstyle = item.data(BaseColorMappingDialog.BrushStyleRole)
                brushstyle = brushstyle if brushstyle is not None else Qt.NoBrush

                if bg is not None and bg.color().isValid():
                    mapping[group] = (bg.color(), polygon_id.value, brushstyle)
                else:
                    mapping[group] = (QColor(Qt.transparent), polygon_id.value, brushstyle)
            return self._column_id, mapping
        elif isinstance(item, GroupListWidgetItem):
            mapping = {}
            for row in range(self.lstUsedColumns.count()):
                item = self.lstUsedColumns.item(row)
                group = str(item.data(ValueRole))
                bg = item.data(Qt.BackgroundRole)
                polygon_id = item.data(BaseColorMappingDialog.PolygonRole)
                polygon_id = polygon_id if polygon_id is not None else NodePolygon.Circle
                brushstyle = item.data(BaseColorMappingDialog.BrushStyleRole)
                brushstyle = Qt.BrushStyle(brushstyle) if brushstyle is not None else Qt.NoBrush

                if bg is not None and bg.color().isValid():
                    mapping[group] = (bg.color(), polygon_id.value, brushstyle)
                else:
                    mapping[group] = (QColor(Qt.transparent), polygon_id.value, brushstyle)
            return self._column_id, mapping
        else:
            bins = []
            colors = []
            polygons = []
            styles = []
            for row in range(self.lstUsedColumns.count()):
                item = self.lstUsedColumns.item(row)
                low, high = item.data(ValueRole)
                bg = item.data(Qt.BackgroundRole)
                polygon_id = item.data(BaseColorMappingDialog.PolygonRole)
                polygon_id = polygon_id if polygon_id is not None else NodePolygon.Circle
                brushstyle = item.data(BaseColorMappingDialog.BrushStyleRole)
                brushstyle = Qt.BrushStyle(brushstyle) if brushstyle is not None else Qt.NoBrush

                bins.append(low)
                polygons.append(polygon_id.value)
                styles.append(brushstyle)
                if bg is not None and bg.color().isValid():
                    colors.append(bg.color())
                else:
                    colors.append(QColor(Qt.transparent))

            try:
                # noinspection PyUnboundLocalVariable
                bins.append(high)
            except UnboundLocalError:
                pass

            return self._column_id, (bins, colors, polygons, styles)


#
# @lc app=leetcode.cn id=435 lang=python
#
# [435] 无重叠区间
#

# 贪心方法，按照右边界排序，如果相交就剔除
# @lc code=start
class Solution(object):
    def eraseOverlapIntervals(self, intervals):
        """
        :type intervals: List[List[int]]
        :rtype: int
        """
        res = 0
        if len(intervals) == 0:
            return res
        intervals.sort(key=lambda x: x[1])
        right_bound = intervals[0][1]
        for i in range(1, len(intervals)):
            if intervals[i][0] >= right_bound:
                right_bound = intervals[i][1]
            else:
                res += 1
        return res
# @lc code=end


from actions import actions
from text_process import text_process
# restore all of our data structures
import pickle

# things we need for Tensorflow
import numpy as np
import tflearn
import random
# import our chat-bot intents file
import json

with open('intents.json') as json_data:
    intents = json.load(json_data)

data = pickle.load( open("training_data", "rb"))
words = data['words']
classes = data['classes']
train_x = data['train_x']
train_y = data['train_y']

#load our saved model
net = tflearn.input_data(shape=[None, len(train_x[0])])
net = tflearn.fully_connected(net, 15)
net = tflearn.fully_connected(net, 15)
net = tflearn.fully_connected(net, len(train_y[0]), activation = 'softmax')
net = tflearn.regression(net)
model = tflearn.DNN(net, tensorboard_dir='tflearn_logs')
model.load('model.tfl')

# create a data structure to hold user context
context = {}

ERROR_THRESHOLD = 0
#classify the sentence in wich class is to get the right response
def classify(sentence):
    # generate probabilities from the model
    results = model.predict([text_process.bow(sentence, words)])[0]
    # filter out predictions below a threshold
    results = [[i,r] for i,r in enumerate(results) ]#if r>ERROR_THRESHOLD]

    # sort by strength of probability
    results.sort(key=lambda x: x[1], reverse=True)
    return_list = []
    for r in results:
        return_list.append((classes[r[0]], r[1])) # that's why we used enumerate to have the order of the result
    # return tuple of intent and probability
    return return_list

# the fonction that control the given actions by the classfication
def control_actions(sentence, i):
    print("control action nw")
    action = i['action']
    x = actions()
    if action == "search":
        x.search(sentence)
    #elif action == "news":     for the news
        #x.news()
    elif action == "services":
        print('services')
        x.get_services()
    elif action == "hosting":
        x.search("hosting")
        print("In our world you encounter 4 hosting types:")
        print("- Pro\n- Lite\n- Rebuy\n- Info-Managed")
        print("You can choose one to talk more")
    #elif action == "server":
    elif action == "dmn_nm":
        x.get_domain_nm(input("Enter please your domain name as (.com, .ma, ...)"))
    elif action == "host-pro" or action == "host-lite" or action == "host-info" or action == "host-rebuy":
        x.get_host_plan(i['path'])

# this little function uses classify() to know how to respond and then she does what she have and create the context structure
def response(sentence, userID='123', show_details=False):
    results = classify(sentence)
    # if we have a classification then find the matching intent tag
    # loop as long as there are matches to process
    while results:
        for i in intents['intents']:
            # find a tag matching the first result
            if i['tag'] == results[0][0]:
                if 'action' in i:
                    print("action="+i['action'])
                    control_actions(sentence, i)
                 # set context for this intent if necessary
                if 'context_set' in i:
                    if show_details: print('context:', i['context_set'])
                    context[userID] = i['context_set']

                    # check if this intent is contextual and applies to this user's conversation
                if not 'context_filter' in i or \
                    (userID in context and 'context_filter' in i and i['context_filter'] == context[userID]):
                    if show_details: print ('tag:', i['tag'])
                    # a random response from the intent
                    return print(random.choice(i['responses']))
            results.pop(0)

message = input("YOU: ")
while(message):
    print(classify(message)[0])
    response(message)
    message = input("YOU: ")


# -*- coding: utf-8 -*-
"""
Created on Tue Nov  5 14:25:10 2019

@author: user4
"""

# Importing the Libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Importing the Dataset
df = pd.read_csv('Social_Network_Ads.csv')
X = df.iloc[:,2:4].values
y = df.iloc[:,4].values

# Missing Values
df.isna().sum() #we have no issing values, hence no imputation required

#from sklearn.impute import SimpleImputer
#imputer = SimpleImputer(missing_values = np.nan, strategy = "mean")
#X.iloc[:,1:3] = imputer.fit_transform(X.iloc[:,1:3])

# Categorical Data
# We have one categorical data that is Gender. let us label it and one hot encode it
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
label_encoder_x = LabelEncoder()
X[:,0] = label_encoder_x.fit_transform(X[:,0])
# As it has only two labels one hot encoding is not required
#one_hot_encoder = OneHotEncoder(categorical_features = [0])
#X = one_hot_encoder.fit_transform(X).toarray()
#label_encoder_y = LabelEncoder()
#y = label_encoder_y.fit_transform(y)

# Train and Test Split the Data
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y, train_size = 0.8, random_state = 1234)

# Feature Scaling them all
from sklearn.preprocessing import StandardScaler
standard_scaler_x = StandardScaler()
X_train = standard_scaler_x.fit_transform(X_train)
X_test = standard_scaler_x.transform(X_test)

# Building the classifier
from sklearn.neighbors import KNeighborsClassifier

#Using euclidean distance in the algorithm
classifier = KNeighborsClassifier(n_neighbors=5, metric='minkowski', p = 2)

classifier.fit(X_train, y_train)

y_pred = classifier.predict(X_test)

# Let's use a confusion matrix to see the accuracy
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred)
accuracy = (cm[0,0] + cm[1,1])/sum(sum(cm))
accuracy
# hence we observe around 86.2%

def knn_optimizer(n):
    optimum = np.zeros((n,2))
    for i in np.arange(1,n+1,1):
        classifier = KNeighborsClassifier(n_neighbors=i, metric='minkowski', p = 2)

        classifier.fit(X_train, y_train)

        y_pred = classifier.predict(X_test)

        # Let's use a confusion matrix to see the accuracy
        from sklearn.metrics import confusion_matrix
        cm = confusion_matrix(y_test, y_pred)
        accuracy = (cm[0,0] + cm[1,1])/sum(sum(cm))
        optimum[i-1,0] = i
        optimum[i-1,1] = accuracy
    return optimum

visual = knn_optimizer(15)
plt.plot(visual[:,0],visual[:,1])

#From the plot we can see that we have peaks at K value of 7 and 9

classifier = KNeighborsClassifier(n_neighbors=7, metric='minkowski', p = 2)

classifier.fit(X_train, y_train)

y_pred = classifier.predict(X_test)

#Following is a much better visualization of the prediction
# Visualising the Training set results
from matplotlib.colors import ListedColormap
X_set, y_set = X_train, y_train
X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 1, stop = X_set[:, 0].max() + 1, step = 0.01),
                     np.arange(start = X_set[:, 1].min() - 1, stop = X_set[:, 1].max() + 1, step = 0.01))
plt.contourf(X1, X2, classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape),
             alpha = 0.75, cmap = ListedColormap(('red', 'green')))
plt.xlim(X1.min(), X1.max())
plt.ylim(X2.min(), X2.max())
for i, j in enumerate(np.unique(y_set)):
    plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1],
                c = ListedColormap(('red', 'green'))(i), label = j)
plt.title('Logistic Regression (Training set)')
plt.xlabel('Age')
plt.ylabel('Estimated Salary')
plt.legend()
plt.show()#we see that the predictions are much better than logistic regression

# Visualising the test set results
from matplotlib.colors import ListedColormap
X_set, y_set = X_test, y_test
X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 1, stop = X_set[:, 0].max() + 1, step = 0.01),
                     np.arange(start = X_set[:, 1].min() - 1, stop = X_set[:, 1].max() + 1, step = 0.01))
plt.contourf(X1, X2, classifier.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape),
             alpha = 0.75, cmap = ListedColormap(('red', 'green')))
plt.xlim(X1.min(), X1.max())
plt.ylim(X2.min(), X2.max())
for i, j in enumerate(np.unique(y_set)):
    plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1],
                c = ListedColormap(('red', 'green'))(i), label = j)
plt.title('Logistic Regression (Training set)')
plt.xlabel('Age')
plt.ylabel('Estimated Salary')
plt.legend()
plt.show()

Part 3 - Classification/Section 15 - K-Nearest Neighbors (K-NN)/K_Nearest_Neighbors/knn_JMR.py


from numpy.core import numeric
import pandas as pd
import numpy as np
import os

dir_rpt = "change path to the RPT files"

dir_csv = "change path to the output folder"

dir_sensorlist = "change to path to the selected_sensors_bigger_list.csv"

directory = os.fsencode(dir_rpt)

i=1
for file in os.listdir(directory):
    filename = os.fsdecode(file)
    print(filename)
    df = pd.read_csv('{}/{}'.format(dir_rpt,filename),names=["ignore1", "time", "sensor", "value", "ignore2"],encoding = "ISO-8859-1")
    df = df.filter(items=['time', 'sensor', 'value'])
    sensors = pd.read_csv('{}/selected_sensors_bigger_list.csv'.format(dir_sensorlist))
    sensors_id_list = sensors['Sensor_id'].tolist()
    df = df[df['sensor'].isin(sensors_id_list)]
    df = df.apply(pd.to_numeric, errors='coerce')
    df = df.dropna()
    df = df.pivot_table(values="value", index="time", columns="sensor")
    df.to_csv('{}/{}.csv'.format(dir_csv,i))
    i=i+1


from io import BytesIO
from datetime import datetime
from edfpy.header import Header

def test_read(test_header_bytes):
    """test read header-field bytes from file"""
    expected = {
        'version': '0',
        'patient_id': 'brux2',
        'recording_id': '',
        'startdate': '04.02.02',
        'starttime': '22.07.23',
        'num_header_bytes': 1536,
        'reserved': '',
        'num_records': 100,
        'record_duration': 1.0,
        'num_channels': 5,
    }
    expected_dt = datetime(2002, 2, 4, 22, 7, 23)
    file = BytesIO(test_header_bytes)
    fields = Header.read(file)
    for key in expected:
        assert getattr(fields, key) == expected[key], key

    assert fields.startdatetime == expected_dt

def test_read_edfp(test_edfp_header_bytes):
    file = BytesIO(test_edfp_header_bytes)
    fields = Header.read(file)
    assert fields.filetype == 'EDF+C'

def test_write(test_header_bytes):
    expected = {
        'version': '0',
        'patient_id': 'brux2',
        'recording_id': '',
        'startdate': '04.02.02',
        'starttime': '22.07.23',
        'num_header_bytes': 1536,
        'reserved': '',
        'num_records': 100,
        'record_duration': 1,
        'num_channels': 5,
    }
    fields = Header(**expected)
    file = BytesIO()
    fields.write(file)
    file.seek(0)
    content = file.read(256)
    assert content == test_header_bytes

def test_roundtrip():
    expected = {
        'version': '13',
        'patient_id': 'some-id',
        'recording_id': 'another-id',
        'startdate': '18.02.84',
        'starttime': '11.11.00',
        'num_header_bytes': 1000,
        'reserved': 'abracadabra',
        'num_records': 100,
        'record_duration': 2,
        'num_channels': 3,
    }
    expected_dt = datetime(1984, 2, 18, 11, 11, 0)
    fields = Header(**expected)
    assert expected_dt == fields.startdatetime
    file = BytesIO()
    fields.write(file)
    file.seek(0)
    fields = Header.read(file)
    for key in expected:
        assert getattr(fields, key) == expected[key], key