"""
Tic Tac Toe game made for learning minmax algorithm.

To play this game you need Pygame and Numpy. Check the `requirements.py` for more informations.
"""
import sys
import pygame
import argparse
import numpy as np
from pygame.locals import *

## OPTIONS ############################################################################################################

# COLORS
WHITE = (250,   250,    250 )
BLACK = (0,     0,      0   )

# GRID
GRID = np.zeros((3, 3), dtype=int)

## MINMAX #############################################################################################################

def heuristic(state, depth, player):
    """Compute the score for minmax algorithm."""
    winner = check_state(state)
    if winner == player:
        return 10 - depth
    elif winner == -player:
        return depth - 10
    else:
        return 0

def minmax(state, maxplayer, depth, player):
    """Minmax algorithm.

    Args:
        state: numpy array (3, 3) representing the current state
        maxplayer: bool corresponding to max player turn (True) or not (False)
        depth: the current depth, it increases till it reach MAX_DEPTH
        player: -1 or 1, depending on which started

    Return:
        evals: the score for one state given
    """
    if check_state(state) != 0 or depth == MAX_DEPTH:
        return heuristic(state, depth, player)

    coordX, coordY = np.where(state == 0)

    if maxplayer:
        evals = -np.inf
        for X, Y in zip(coordX, coordY):
            new_state = np.copy(state)
            new_state[X, Y] = player
            heur = minmax(new_state, False, (depth + 1), player)
            evals = np.maximum(evals, heur)
    else:
        evals = np.inf
        for X, Y in zip(coordX, coordY):
            new_state = np.copy(state)
            new_state[X, Y] = -player
            heur = minmax(new_state, True, (depth + 1), player)
            evals = np.minimum(evals, heur)

    return evals

def get_best_move(player):
    """Return the best move using the minmax algorithm."""
    global GRID, MAX_DEPTH

    best_eval = -np.inf
    best_move = list()

    coordX, coordY = np.where(GRID == 0)

    for X, Y in zip(coordX, coordY):
        new_state = np.copy(GRID)
        new_state[X, Y] = player
        evals = minmax(new_state, False, 0, player)

        if evals > best_eval:
            best_eval = evals
            best_move = [X, Y]

    return best_move

## TICTACTOE ##########################################################################################################

def init_board(ttt):
    """Initialize the board for Pygame.

    Args:
        ttt: the properly initialized Pygame display variable

    Return:
        board: board initialized with background color and lines
    """
    # Initialize background
    board = pygame.Surface(ttt.get_size())
    board = board.convert()
    board.fill(WHITE)
    # Draw vertical lines
    pygame.draw.line(board, BLACK, (100, 0), (100, 300), 1)
    pygame.draw.line(board, BLACK, (200, 0), (200, 300), 1)
    # Draw horizontal lines
    pygame.draw.line(board, BLACK, (0, 100), (300, 100), 1)
    pygame.draw.line(board, BLACK, (0, 200), (300, 200), 1)
    return board

def check_state(state):
    """Check if there is a winner."""
    # Check horizontal
    sum_h = np.sum(state, axis=1)
    # Check vertical
    sum_v = np.sum(state, axis=0)
    # Check diagonals
    sum_d1 = state[0, 0] + state[1, 1] + state[2, 2]
    sum_d2 = state[2, 0] + state[1, 1] + state[0, 2]
    # Test if sums corresponds to a winner
    if np.any(sum_h == -3) or np.any(sum_v == -3) or sum_d1 == -3 or sum_d2 == -3:
        # Player 1 won!
        return -1
    elif np.any(sum_h == 3) or np.any(sum_v == 3) or sum_d1 == 3 or sum_d2 == 3:
        # Player 2 won!
        return 1
    elif np.any(state == 0):
        # Still
        return 0
    else:
        # Draw
        return 10

def drawstatus(board, player):
    """Display a message to indicate which player is currently playing."""
    if player is -1:
        message = "Player 1"
    else:
        message = "Player 2"
    font = pygame.font.Font(None, 24)
    text = font.render(message, 1, (0,0,0))
    board.fill(WHITE, (0, 300, 300, 25))
    board.blit(text,( 10, 300))

def draw(board, X, Y, player):
    """Draw a circle or a cross onto the board canvas and modify the grid."""
    centerY = (X * 100) + 50
    centerX = (Y * 100) + 50
    if player is -1:
        pygame.draw.circle(board, BLACK, (centerX, centerY), 44, 2)
    else:
        pygame.draw.line(board, BLACK, (centerX - 33, centerY - 33),
                        (centerX + 33, centerY + 33), 2)
        pygame.draw.line(board, BLACK, (centerX + 33, centerY - 33),
                        (centerX - 33, centerY + 33), 2)

def display(ttt, board, player, X, Y):
    """Display the game.

    Call draw if we have set X and Y. Otherwise just draw the background and the status.

    Args:
        ttt: is the actual game
        board: is the Surface background
    """
    if X is not None or Y is not None:
        draw(board, X, Y, player)
    drawstatus(board, player)
    ttt.blit(board, (0, 0))
    pygame.display.flip()

def mouse_position(mouseX, mouseY):
    """Transform mouses coordinates into array indexes.

    Args:
        mouseX: mouse position on X, from 0 to 300
        mouseY: mouse position on Y, from 0 to 300

    Return:
        X, Y: position on the board. Each one is either 0, 1 or 2.
    """
    if mouseY < 100:
        X = 0
    elif mouseY < 200:
        X = 1
    else:
        X = 2
    if mouseX < 100:
        Y = 0
    elif mouseX < 200:
        Y = 1
    else:
        Y = 2
    return X, Y

def play(board, player):
    """Get human player turn.

    Args:
        board: the Surface background
        player: should be -1 (Player 1) or 1 (Player 2)

    Returns:
        X, Y: array indexes for the grid array
    """
    mouseX, mouseY = pygame.mouse.get_pos()
    X, Y = mouse_position(mouseX, mouseY)
    return X, Y

def check_winner():
    global GRID
    winner = check_state(GRID)
    if winner != 0:
        if winner == 1:
            print("Player 1 won!")
        elif winner == 2:
            print("Player 2 won!")
        elif winner == -1:
            print("Draw game")
        sys.exit()

def main(args):
    """The game loop function."""
    global GRID

    # Initialize some Pygame
    pygame.init()
    ttt = pygame.display.set_mode((300, 325))
    pygame.display.set_caption = ('Tic Tac Toe')
    board = init_board(ttt)

    # Initialize some game variables
    loop = True
    if args['startscd']:
        player = 1
    else:
        player = -1
    X, Y = None, None

    # Loop game
    while (loop):
        display(ttt, board, player, X, Y)
        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit()
            elif player == -1:
                if event.type != MOUSEBUTTONDOWN: continue
                X, Y = play(board, player)
            elif player == 1:
                X, Y = get_best_move(player)
            GRID[X, Y] = player
            player = (-1) * player
            check_winner()

    # Quit
    return

## MAIN ###############################################################################################################

if __name__ == '__main__':
    # Parse arguments
    parser = argparse.ArgumentParser(description="This is a Tic Tac Toe game made for learning minmax algorithm. Enjoy!")
    parser.add_argument("--maxdepth", type=int, default=100, help="Define the max depth for minmax algorithm. The more the better the AI is. Set it very low (1 or 2 for example) for a dumb AI.")
    parser.add_argument("--startscd", default=False, action='store_true', help="The human player start second.")
    args = vars(parser.parse_args())

    # Set the global max depth
    MAX_DEPTH = args['maxdepth']

    # Call the main game
    main(args)


import enum
import math
import os
import pickle
import random

import einops
import numpy as np
import torch
from omegaconf import OmegaConf

class NodeType(enum.IntEnum):
    NORMAL = 0
    OBSTACLE = 1
    OBSTACLE_OMIT = 2
    HANDLE = 3
    SIZE = 9

class EdgeType(enum.IntEnum):
    NORMAL = 0
    BUTTON = 1
    LONG_RANGE = 2
    SIZE = 3

def move2device(data, device):
    """
    Move the given data to the given device (e.g. `cuda:0`).
    """
    if hasattr(data, 'to'):
        return data.to(device)
    elif type(data) == dict:
        out = {}
        for k, v in data.items():
            out[k] = move2device(v, device)
    elif type(data) == list:
        out = [move2device(x, device) for x in data]
    else:
        out = data

    return out

def detach_dict(data):
    """
    Detach all items in the given object (dict, list, etc.) from the graph.
    """
    if hasattr(data, 'detach'):
        return data.detach()
    elif type(data) == dict:
        out = {}
        for k, v in data.items():
            out[k] = detach_dict(v)
    elif type(data) == list:
        out = [detach_dict(x) for x in data]
    else:
        out = data

    return out

def set_manual_seed(seed: int):
    """
    Set the random seed for all possible random number generators.
    """
    torch.random.manual_seed(seed)
    np.random.seed(seed)
    random.seed(seed)

def triangles_to_edges(faces: torch.LongTensor, links: torch.LongTensor = None):
    """Computes mesh edges from triangles."""

    # collect edges from triangles
    edges_list = [faces[..., 0:2],
                  faces[..., 1:3],
                  torch.stack([faces[..., 2], faces[..., 0]], dim=-1)]
    edges = torch.cat(edges_list, dim=1)

    # those edges are sometimes duplicated (within the mesh) and sometimes
    # single (at the mesh boundary).
    # sort & pack edges as single tf.int64
    receivers = edges.min(dim=-1)[0]
    senders = edges.max(dim=-1)[0]

    if links is not None:
        senders_links, receivers_links = torch.unbind(links, dim=-1)
        senders = torch.cat([senders, senders_links], dim=1)
        receivers = torch.cat([receivers, receivers_links], dim=1)

    packed_edges = torch.stack([senders, receivers], dim=-1)
    unique_edges = torch.unique(packed_edges, dim=1)

    sortvals = unique_edges[..., 0] * 10000 + unique_edges[..., 1]
    sort_idx = torch.sort(sortvals, dim=1).indices[0]
    unique_edges = unique_edges[:, sort_idx]
    senders, receivers = torch.unbind(unique_edges, dim=-1)

    # create two-way connectivity
    all_senders = torch.cat([senders, receivers], dim=1)
    all_receivers = torch.cat([receivers, senders], dim=1)

    edges = torch.cat([all_senders, all_receivers], dim=0)
    return edges

def make_einops_str(ndims, insert_k=None):
    linds = ['l', 'm', 'n', 'o', 'p']

    if insert_k is None:
        symbols = linds[:ndims]
    else:
        symbols = linds[:insert_k]
        symbols.append('k')
        symbols += linds[insert_k:ndims]

    out_str = ' '.join(symbols)
    return out_str

def make_repeat_str(tensor, dim):
    ndims = len(tensor.shape)

    out_str = []
    out_str.append(make_einops_str(ndims))
    out_str.append('->')
    out_str.append(make_einops_str(ndims, insert_k=dim))

    out_str = ' '.join(out_str)

    return out_str

def gather(data: torch.Tensor, index: torch.LongTensor, dim_gather: int, dim_data: int, dim_index: int):
    input_repeat_str = make_repeat_str(data, dim_data)
    index_repeat_str = make_repeat_str(index, dim_index + 1)

    data_repeat = einops.repeat(data, input_repeat_str, k=index.shape[dim_index])
    index_repeat = einops.repeat(index, index_repeat_str, k=data.shape[dim_data])

    out = torch.gather(data_repeat, dim_gather, index_repeat)

    return out

def unsorted_segment_sum(data, index, dim_sum: int, dim_input: int, dim_index: int, n_verts=None):
    input_repeat_str = make_repeat_str(data, dim_input)
    index_repeat_str = make_repeat_str(index, dim_index + 1)

    data_repeat = einops.repeat(data, input_repeat_str, k=index.shape[dim_index])
    index_repeat = einops.repeat(index, index_repeat_str, k=data.shape[dim_input])

    B = data.shape[:dim_sum]
    n_verts = n_verts or index.max().item() + 1

    out = torch.zeros(*B, n_verts, index.shape[dim_index], data.shape[dim_input]).to(data.device)
    out = out.scatter_add(dim_sum, index_repeat, data_repeat)
    out = out.sum(dim=-2)

    return out

def save_checkpoint(runner, aux_modules, config, file):
    """
    Save a checkpoint of the training state.
    :param runner: Runner object
    :param aux_modules: a dictionary of auxiliary modules (optimizer, scheduler) to save
    :param config: OmegaConf config object
    :param file: path to save the checkpoint
    """

    os.makedirs(os.path.dirname(file), exist_ok=True)
    out_dict = dict()
    out_dict['training_module'] = runner.state_dict()
    out_dict['config'] = OmegaConf.to_container(config)
    for k, v in aux_modules.items():
        if hasattr(v, 'state_dict'):
            out_dict[k] = v.state_dict()

    torch.save(out_dict, file)

def make_pervertex_tensor_from_lens(lens, val_tensor):
    val_list = []
    for i, n in enumerate(lens):
        val_list.append(val_tensor[i].repeat(n).unsqueeze(-1))
    val_stack = torch.cat(val_list)
    return val_stack

def add_field_to_pyg_batch(batch, new_key: str, value: torch.Tensor, node_key: str, reference_key: str = None,
                           one_per_sample: bool = False, zero_inc: bool = False):
    """
    Add a new field to a pytorch geometric Batch object.

    Updates the batch[node_key]._mapping dictionary to include the new field.
    Updates the batch._slice_dict[node_key] dictionary to include slice indices for the new field.
    Updates the batch._inc_dict[node_key] dictionary to include the increment values for the new field.

    :param batch: Batch object
    :param new_key: a key for the new field
    :param value: a tensor to add
    :param node_key: a key for the node field to which the new field will be added (e.g. `cloth` or `obstacle`)
    :param reference_key: a field to use as a reference for the size of the new field
    :param one_per_sample: if True and reference_key is None, the new field will have only one value per sample in the batch
    :param zero_inc: if True, the increment values for the new field will be set to zero
    :return: updated Batch object
    """
    batch[node_key]._mapping[new_key] = value
    B = batch.num_graphs

    if reference_key is None:
        if one_per_sample:
            device = value.device
            slice = torch.arange(B + 1).to(device)
            inc = torch.zeros(B).long().to(device)
        else:
            slice = []
            inc = []

        batch._slice_dict[node_key][new_key] = slice
        batch._inc_dict[node_key][new_key] = inc
    else:
        batch._slice_dict[node_key][new_key] = batch._slice_dict[node_key][reference_key]

        if zero_inc:
            device = value.device
            inc = torch.zeros(B).long().to(device)
        else:
            inc = batch._inc_dict[node_key][reference_key]
        batch._inc_dict[node_key][new_key] = inc
    return batch

def random_between(fr, to, shape, return_norm=False, device=None):
    """
    Generate a random tensor with values between `fr` and `to`.
    :param fr: minimum value
    :param to: maximum value
    :param shape: shape of the output tensor
    :param return_norm: if True, return the normalized tensor (with values in [0,1]) as well
    :param device: torch device
    :return: a random tensor
    """
    if fr == to:
        rand_norm = torch.zeros(*shape)
    else:
        rand_norm = torch.rand(*shape)
    if device is not None:
        rand_norm = rand_norm.to(device)

    rand = rand_norm * (to - fr)
    rand += fr

    if return_norm:
        return rand, rand_norm
    return rand

def relative_between(fr, to, value: torch.Tensor):
    """
    Normalize a tensor with values between `fr` and `to` to the range [0,1].
    :param fr: minimum value
    :param to: maximum value
    :param value: tensor to normalize
    :return: normalized tensor
    """
    if fr == to:
        return torch.zeros_like(value)

    value_norm = value - fr
    value_norm = value_norm / (to - fr)
    return value_norm

def random_between_log(fr, to, shape, return_norm=False, device=None):
    """
    Generate a random tensor with values between `fr` and `to` sampled from a log scale.
    :param fr: minimum value
    :param to: maximum value
    :param shape: shape of the output tensor
    :param return_norm: if True, return the normalized tensor (with values in [0,1]) as well
    :param device: torch device
    :return: a random tensor
    """
    if fr == to:
        rand_norm = torch.zeros(*shape)
    else:
        rand_norm = torch.rand(*shape)
    if device is not None:
        rand_norm = rand_norm.to(device)

    fr_log = math.log(fr)
    to_log = math.log(to)

    rand_log = rand_norm * (to_log - fr_log)
    rand_log += fr_log

    rand = torch.exp(rand_log)

    if return_norm:
        return rand, rand_norm
    return rand

def relative_between_log(fr, to, value: torch.Tensor):
    """
    Normalize a tensor with values between `fr` and `to` to the range [0,1] using a log scale.
    :param fr: minimum value
    :param to: maximum value
    :param value: tensor to normalize
    :return: normalized tensor
    """
    if fr == to:
        return torch.zeros_like(value)

    fr_log = math.log(fr)
    to_log = math.log(to)

    if type(value) == torch.Tensor:
        value_log = torch.log(value)
    else:
        value_log = math.log(value)

    value_norm = value_log - fr_log
    value_norm = value_norm / (to_log - fr_log)

    return value_norm

from scipy.spatial.transform import Rotation as R

def separate_arms(poses: np.ndarray, angle=20, left_arm=17, right_arm=16):
    """
    Modify the SMPL poses to avoid self-intersections of the arms and the body.
    Adapted from the code of SNUG (https://github.com/isantesteban/snug/blob/main/snug_utils.py#L93)

    :param poses: [Nx72] SMPL poses
    :param angle: angle to rotate the arms
    :param left_arm: index of the left arm in the SMPL model
    :param right_arm: index of the right arm in the SMPL model
    :return:
    """
    num_joints = poses.shape[-1] // 3

    poses = poses.reshape((-1, num_joints, 3))
    rot = R.from_euler('z', -angle, degrees=True)
    poses[:, left_arm] = (rot * R.from_rotvec(poses[:, left_arm])).as_rotvec()
    rot = R.from_euler('z', angle, degrees=True)
    poses[:, right_arm] = (rot * R.from_rotvec(poses[:, right_arm])).as_rotvec()

    poses[:, 23] *= 0.1
    poses[:, 22] *= 0.1

    return poses.reshape((poses.shape[0], -1))

def pickle_load(file):
    """
    Load a pickle file.
    """
    with open(file, 'rb') as f:
        loadout = pickle.load(f)

    return loadout

def pickle_dump(loadout, file):
    """
    Dump a pickle file. Create the directory if it does not exist.
    """
    os.makedirs(os.path.dirname(str(file)), exist_ok=True)

    with open(file, 'wb') as f:
        pickle.dump(loadout, f)


"""
Optuna example that demonstrates a pruner for LightGBM.
In this example, we optimize the validation accuracy of cancer detection using LightGBM.
We optimize both the choice of booster model and their hyperparameters. Throughout
training of models, a pruner observes intermediate results and stop unpromising trials.
You can run this example as follows:
    $ python lightgbm_integration.py
"""
import lightgbm as lgb
import numpy as np
import optuna
import pickle
from sklearn.metrics import mean_squared_error
from math import sqrt
from utils import load_data
import json

cat_feats = ['item_id', 'dept_id','store_id', 'cat_id', 'state_id'] + \
            ["event_name_1", "event_name_2", "event_type_1", "event_type_2"]

def objective(trial):
    train_x, train_y, valid_x, valid_y = load_data()
    dtrain = lgb.Dataset(train_x, label=train_y, categorical_feature=cat_feats, free_raw_data=False)
    dvalid = lgb.Dataset(valid_x, label=valid_y, categorical_feature=cat_feats, free_raw_data=False)

    param = {
        "objective": "poisson",
        "metric": "rmse",
        "verbosity": -1,
        "boosting_type": "goss", #default gbdt
        "lambda_l1": trial.suggest_float("lambda_l1", 1e-8, 10.0, log=True),
        "lambda_l2": trial.suggest_float("lambda_l2", 1e-8, 10.0, log=True),
        "num_leaves": trial.suggest_int("num_leaves", 2, 256),
        "feature_fraction": trial.suggest_float("feature_fraction", 0.4, 1.0),
        # "bagging_fraction": trial.suggest_float("bagging_fraction", 0.4, 1.0),
        # "bagging_freq": trial.suggest_int("bagging_freq", 1, 7),
        "min_child_samples": trial.suggest_int("min_child_samples", 5, 100),
        # 'num_iterations' : 500,
    }

    gbm = lgb.train(
        param, dtrain, valid_sets=[dvalid], verbose_eval=100
    )

    preds = gbm.predict(valid_x)
    pred_labels = np.rint(preds)
    rmse = sqrt(mean_squared_error(valid_y, pred_labels))

    return rmse

if __name__ == "__main__":

    study = optuna.create_study()
    study.optimize(objective, n_trials=1)

    print("Number of finished trials: {}".format(len(study.trials)))

    print("Best trial:")
    trial = study.best_trial

    print("  Value: {}".format(trial.value))

    print("  Params: ")
    for key, value in trial.params.items():
        print("    {}: {},".format(key, value))

    with open("params.json") as f:
        js = json.load(f)

    new_trained_param = {key:value for key, value in trial.params.items()}
    new_trained_param["objective"]= "poisson"
    new_trained_param["metric"]= "rmse"
    new_trained_param["verbosity"]= -1
    new_trained_param["boosting_type"]= "goss"
    js["trial_{}".format(trial.value)] = new_trained_param

    with open("params.json", "w") as f:
        json.dump(js, f, indent=4)


from abc import ABCMeta
from abc import abstractproperty
from logging import getLogger
import enum

from plainbox.i18n import gettext as _

logger = getLogger("plainbox.depmgr")

class DependencyError(Exception, metaclass=ABCMeta):

    """ Exception raised when a dependency error is detected. """

    @abstractproperty
    def affected_job(self):
        """ job that is affected by the dependency error. """

    @abstractproperty
    def affecting_job(self):
        """
        job that is affecting the :attr:`affected_job`.

        This may be None in certain cases (eg, when the job does not exist and
        is merely referred to by id). If this job exists removing it SHOULD
        fix this problem from occurring.

        This may be the same as :attr:`affected_job`
        """

class DependencyUnknownError(DependencyError):

    """
    Exception raised when an unknown job is mentioned.

    .. note::
        This class differs from :class:`DependencyMissingError` in that the
        unknown job is not a dependency of anything. It can only happen when
        the job is explicitly mentioned in the list of jobs to visit.
    """

    def __init__(self, job):
        """ Initialize a new DependencyUnknownError with a given job. """
        self.job = job

    @property
    def affected_job(self):
        """
        job that is affected by the dependency error.

        Here it's a job that on the ``visit_list`` but not on the ``job_list``.
        """
        return self.job

    @property
    def affecting_job(self):
        """
        job that is affecting the :attr:`affected_job`.

        Here, it is always None.
        """

    def __str__(self):
        """ Get a printable description of an error. """
        return _("unknown job referenced: {!a}").format(self.job.id)

    def __repr__(self):
        """ Get a debugging representation of an error. """
        return "<{} job:{!r}>".format(self.__class__.__name__, self.job)

    def __eq__(self, other):
        """ Check if one error is equal to another. """
        if not isinstance(other, DependencyUnknownError):
            return NotImplemented
        return self.job == other.job

    def __hash__(self):
        """ Calculate the hash of an error. """
        return hash((self.job,))

class DependencyCycleError(DependencyError):

    """ Exception raised when a cyclic dependency is detected. """

    def __init__(self, job_list):
        """
        Initialize with a list of jobs that form a dependency loop.

        The dependencies satisfy the given expression:

            job_list[n - 1] depends-on job_list[n]

        The error exists because job_list[0] is job_list[-1].
        Each item is a JobDefinition instance.
        """
        assert len(job_list) > 1
        assert job_list[0] is job_list[-1]
        self.job_list = job_list

    @property
    def affected_job(self):
        """
        job that is affected by the dependency error.

        Here it is the job that has a cyclic dependency on itself.
        """
        return self.job_list[0]

    @property
    def affecting_job(self):
        """
        job that is affecting the :attr:`affected_job`.

        Here it's always the same as :attr:`~DependencyCycleError.affected_job`
        """
        return self.affected_job

    def __str__(self):
        """ Get a printable description of an error. """
        return _("dependency cycle detected: {}").format(
            " -> ".join([job.id for job in self.job_list]))

    def __repr__(self):
        """ Get a debugging representation of an error. """
        return "<{} job_list:{!r}>".format(
            self.__class__.__name__, self.job_list)

class DependencyMissingError(DependencyError):

    """ Exception raised when a job has an unsatisfied dependency.  """

    DEP_TYPE_RESOURCE = "resource"
    DEP_TYPE_DIRECT = "direct"
    DEP_TYPE_ORDERING = "ordering"

    def __init__(self, job, missing_job_id, dep_type):
        """ Initialize a new error with given data. """
        self.job = job
        self.missing_job_id = missing_job_id
        self.dep_type = dep_type

    @property
    def affected_job(self):
        """
        job that is affected by the dependency error.

        Here it is the job that has a missing dependency.
        """
        return self.job

    @property
    def affecting_job(self):
        """
        job that is affecting the :attr:`affected_job`.

        Here it is always None as we have not seen this job at all and that's
        what's causing the problem in the first place.
        """

    def __str__(self):
        """ Get a printable description of an error. """
        return _("missing dependency: {!r} ({})").format(
            self.missing_job_id, self.dep_type)

    def __repr__(self):
        """ Get a debugging representation of an error. """
        return "<{} job:{!r} missing_job_id:{!r} dep_type:{!r}>".format(
            self.__class__.__name__,
            self.job, self.missing_job_id, self.dep_type)

    def __eq__(self, other):
        """ Check if one error is equal to another. """
        if not isinstance(other, DependencyMissingError):
            return NotImplemented
        return (self.job == other.job
                and self.missing_job_id == other.missing_job_id
                and self.dep_type == other.dep_type)

    def __hash__(self):
        """ Calculate the hash of an error. """
        return hash((self.job, self.missing_job_id, self.dep_type))

class DependencyDuplicateError(DependencyError):

    """ Exception raised when two jobs have the same id.  """

    def __init__(self, job, duplicate_job):
        """ Initialize a new error with given data. """
        assert job.id == duplicate_job.id
        self.job = job
        self.duplicate_job = duplicate_job

    @property
    def affected_job(self):
        """
        job that is affected by the dependency error.

        Here it is the job that is already known by the system.
        """
        return self.job

    @property
    def affecting_job(self):
        """
        job that is affecting the :attr:`affected_job`.

        Here it is the job that is clashing with another job already present in
        the system.
        """
        return self.duplicate_job

    def __str__(self):
        """ Get a printable description of an error. """
        return _("duplicate job id: {!r}").format(self.affected_job.id)

    def __repr__(self):
        """ Get a debugging representation of an error. """
        return "<{} job:{!r} duplicate_job:{!r}>".format(
            self.__class__.__name__, self.job, self.duplicate_job)

class Color(enum.Enum):

    """
    Three classic colors for recursive graph visitor.

    WHITE:
        For nodes have not been visited yet.
    GRAY:
        For nodes that are currently being visited but the visit is not
        complete.
    BLACK:
        For nodes that have been visited and are complete.
    """

    WHITE = 'white'
    GRAY = 'gray'
    BLACK = 'black'

class DependencySolver:

    """
    Dependency solver for Jobs.

    Uses a simple depth-first search to discover the sequence of jobs that can
    run. Use the resolve_dependencies() class method to get the solution.
    """

    COLOR_WHITE = Color.WHITE
    COLOR_GRAY = Color.GRAY
    COLOR_BLACK = Color.BLACK

    @classmethod
    def resolve_dependencies(cls, job_list, visit_list=None):
        """
        Solve the dependency graph expressed as a list of job definitions.

        :param list job_list: list of known jobs
        :param list visit_list: (optional) list of jobs to solve

        The visit_list, if specified, allows to consider only a part of the
        graph while still having access and knowledge of all jobs.

        :returns list: the solution (a list of jobs to execute in order)
        :raises DependencyDuplicateError:
            if a duplicate job definition is present
        :raises DependencyCycleError:
            if a cyclic dependency is present.
        :raises DependencyMissingErorr:
            if a required job does not exist.
        """
        return cls(job_list)._solve(visit_list)

    def __init__(self, job_list):
        """
        Instantiate a new dependency solver with the specified list of jobs.

        :raises DependencyDuplicateError:
            if the initial job_list has any duplicate jobs
        """
        # Remember the jobs that were passed
        self._job_list = job_list
        # Build a map of jobs (by id)
        self._job_map = self._get_job_map(job_list)
        # Job colors, maps from job.id to COLOR_xxx
        self._job_color_map = {job.id: self.COLOR_WHITE for job in job_list}
        # The computed solution, made out of job instances. This is not
        # necessarily the only solution but the algorithm computes the same
        # value each time, given the same input.
        self._solution = []

    def _solve(self, visit_list=None):
        """
        Internal method of DependencySolver.

        Solves the dependency graph and returns the solution.

        Calls _visit() on each of the initial nodes/jobs
        """
        # Visit the visit list
        logger.debug(_("Starting solve"))
        logger.debug(_("Solver job list: %r"), self._job_list)
        logger.debug(_("Solver visit list: %r"), visit_list)
        if visit_list is None:
            visit_list = self._job_list
        for job in visit_list:
            self._visit(job)
        logger.debug(_("Done solving"))
        # Return the solution
        return self._solution

    def _visit(self, job, trail=None):
        """
        Internal method of DependencySolver.

        Called each time a node is visited. Nodes already seen in _visited are
        skipped. Attempts to enumerate all dependencies (both direct and
        resource) and resolve them. Missing jobs cause DependencyMissingError
        to be raised. Calls _visit recursively on all dependencies.
        """
        try:
            color = self._job_color_map[job.id]
        except KeyError:
            logger.debug(_("Visiting job that's not on the job_list: %r"), job)
            raise DependencyUnknownError(job)
        logger.debug(_("Visiting job %s (color %s)"), job.id, color)
        if color == self.COLOR_WHITE:
            # This node has not been visited yet. Let's mark it as GRAY (being
            # visited) and iterate through the list of dependencies
            self._job_color_map[job.id] = self.COLOR_GRAY
            # If the trail was not specified start a trail for this node
            if trail is None:
                trail = [job]
            for dep_type, job_id in job.controller.get_dependency_set(job):
                # Dependency is just an id, we need to resolve it
                # to a job instance. This can fail (missing dependencies)
                # so let's guard against that.
                try:
                    next_job = self._job_map[job_id]
                except KeyError:
                    logger.debug(_("Found missing dependency: %r from %r"),
                                 job_id, job)
                    raise DependencyMissingError(job, job_id, dep_type)
                else:
                    # For each dependency that we visit let's reuse the trail
                    # to give proper error messages if a dependency loop exists
                    logger.debug(_("Visiting dependency: %r"), next_job)
                    # Update the trail as we visit that node
                    trail.append(next_job)
                    self._visit(next_job, trail)
                    trail.pop()
            # We've visited (recursively) all dependencies of this node,
            # let's color it black and append it to the solution list.
            logger.debug(_("Appending %r to solution"), job)
            self._job_color_map[job.id] = self.COLOR_BLACK
            self._solution.append(job)
        elif color == self.COLOR_GRAY:
            # This node is not fully traced yet but has been visited already
            # so we've found a dependency loop. We need to cut the initial
            # part of the trail so that we only report the part that actually
            # forms a loop
            trail = trail[trail.index(job):]
            logger.debug(_("Found dependency cycle: %r"), trail)
            raise DependencyCycleError(trail)
        else:
            assert color == self.COLOR_BLACK
            # This node has been visited and is fully traced.
            # We can just skip it and go back

    @staticmethod
    def _get_job_map(job_list):
        """
        Internal method of DependencySolver.

        Computes a map of job.id => job
        Raises DependencyDuplicateError if a collision is found
        """
        job_map = {}
        for job in job_list:
            if job.id in job_map:
                raise DependencyDuplicateError(job_map[job.id], job)
            else:
                job_map[job.id] = job
        return job_map


#!/usr/bin/env python
# coding: utf-8

# In[1]:

from PyQt5 import QtCore, QtGui, QtWidgets
from pynput import mouse
import pyautogui
import os
import tensorflow.keras as k
from tensorflow.keras.models import Model
import cv2
import numpy as np
import shutil

class MyModel():
    """
    Ideally we'll have our preprocessing steps in our Model class instead of stuffing it inside our
    """
    def __init__(self, model_name):
        self.estimator = k.models.load_model("./models/" + model_name)
        self.INPUT_DIMS = self.estimator.layers[0].input_shape
        self.PREVIEW_DIMS = (299, 299)

    def preview_candidate(self, path):
        """
        Resize the candidate into a 299x299 square.
        """
        img = cv2.imread(path)
        resize = cv2.resize(img, self.PREVIEW_DIMS)
        cv2.imwrite("last_candidate.png", resize)

    def transform_candidate(self):
        """
        Preprocess a single image into a readable array
        """
        img = cv2.imread("last_screenshot.png")
        resize = cv2.resize(img, (self.INPUT_DIMS[1], self.INPUT_DIMS[2]))
        gray = cv2.cvtColor(resize, cv2.COLOR_BGR2GRAY)
        arr = gray/255
        return np.expand_dims(arr, 2)

    def predict(self):
        inputs = np.expand_dims(self.transform_candidate(), 0)
        preds = self.estimator(inputs)
        return preds

class PredictionApp(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        # Initial Calibration
        self.num_clicks = 0
        self.MAX_CLICKS = 2

        if "config.txt" in os.listdir():
            with open("config.txt", "r") as f:
                self.X1, self.Y1, self.X2, self.Y2 = tuple(map(int, f.read().splitlines()))
        else:
            self.X1 = 0
            self.Y1 = 0
            self.X2 = 1
            self.Y2 = 1

        # For selection of model
        self.model_list = [model for model in os.listdir("./models/") if "model" in model]
        if self.model_list:
            self.model_name = self.model_list[0]
        else:
            self.model_name = None
        self.model = MyModel(self.model_name)

        # For saving/loading/deleting model
        self.saved_studies = [file for file in os.listdir("./saved_studies/")]
        self.prediction = ""
        self.probability = 0.0

        self.setupUi()

        # Connect to calibration
        self.SelectSnapArea.clicked.connect(self.select_snap_area)
        self.SnapArea.clicked.connect(self.snap_candidate)
        self.SelectModel.currentIndexChanged.connect(self.model_selection_change)
        self.Evaluate.clicked.connect(self.evaluate_func)
        self.SaveStudy.clicked.connect(self.save_study)
        self.LoadStudy.currentIndexChanged.connect(self.load_study)

    def setupUi(self):

        # Main Window
        self.setObjectName("MainWindow")
        self.resize(627, 700)
        self.centralwidget = QtWidgets.QWidget(self)
        self.centralwidget.setObjectName("centralwidget")

        # Candidate Preview Window
        self.Candidate = QtWidgets.QLabel(self.centralwidget)
        self.Candidate.setGeometry(QtCore.QRect(20, 20, 299, 299))
        self.Candidate.setFrameShape(QtWidgets.QFrame.Panel)
        self.Candidate.setText("")
        if "last_candidate.png" in os.listdir():
            self.Candidate.setPixmap(QtGui.QPixmap("last_candidate.png"))
        else:
            self.Candidate.setPixmap(QtGui.QPixmap("placeholder.png"))
        self.Candidate.setObjectName("Candidate")

        # Candidate Heatmap Window
        self.CandidateHeatmap = QtWidgets.QLabel(self.centralwidget)
        self.CandidateHeatmap.setGeometry(QtCore.QRect(20, 350, 299, 299))
        self.CandidateHeatmap.setFrameShape(QtWidgets.QFrame.Panel)
        self.CandidateHeatmap.setText("")
        self.CandidateHeatmap.setPixmap(QtGui.QPixmap("placeholder.png"))
        self.CandidateHeatmap.setObjectName("CandidateHeatmap")

        # Select Snap Area Button
        self.SelectSnapArea = QtWidgets.QPushButton(self.centralwidget)
        self.SelectSnapArea.setGeometry(QtCore.QRect(350, 20, 240, 50))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SelectSnapArea.setFont(font)
        self.SelectSnapArea.setObjectName("SelectSnapArea")

        # Select Model ComboBox
        self.SelectModel = QtWidgets.QComboBox(self.centralwidget)
        self.SelectModel.setGeometry(QtCore.QRect(350, 140, 240, 50))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SelectModel.setFont(font)
        self.SelectModel.setObjectName("SelectModel")
        self.SelectModel.addItems(self.model_list)

        # Snap Area Button
        self.SnapArea = QtWidgets.QPushButton(self.centralwidget)
        self.SnapArea.setGeometry(QtCore.QRect(350, 220, 240, 99))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SnapArea.setFont(font)
        self.SnapArea.setStyleSheet("background-color: rgb(0, 255, 0);")
        self.SnapArea.setObjectName("SnapArea")

        # Save Study button
        self.SaveStudy = QtWidgets.QPushButton(self.centralwidget)
        self.SaveStudy.setGeometry(QtCore.QRect(350, 410, 240, 40))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SaveStudy.setFont(font)
        self.SaveStudy.setObjectName("SaveStudy")

        # Display Model Name Label
        self.SaveStudyConfirmation = QtWidgets.QLabel(self.centralwidget)
        self.SaveStudyConfirmation.setGeometry(QtCore.QRect(350, 450, 240, 20))
        self.SaveStudyConfirmation.setObjectName("SaveStudyConfirmation")

        # Load Study Button
        self.LoadStudy = QtWidgets.QComboBox(self.centralwidget)
        self.LoadStudy.setGeometry(QtCore.QRect(350, 490, 240, 40))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.LoadStudy.setFont(font)
        self.LoadStudy.setObjectName("LoadStudy")
        self.LoadStudy.addItems(self.saved_studies)

        # Evaluate Button
        self.SelectSnapArea.setObjectName("SelectSnapArea")
        self.Evaluate = QtWidgets.QPushButton(self.centralwidget)
        self.Evaluate.setGeometry(QtCore.QRect(350, 350, 240, 50))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.Evaluate.setFont(font)
        self.Evaluate.setStyleSheet("background-color: rgb(0, 255, 0);")
        self.Evaluate.setObjectName("Evaluate")

        # Display Model Name Label
        self.DisplayModelName = QtWidgets.QLabel(self.centralwidget)
        self.DisplayModelName.setGeometry(QtCore.QRect(350, 190, 241, 16))
        self.DisplayModelName.setObjectName("DisplayModelName")

        # Probability Output Label
        self.ProbabilityOutput = QtWidgets.QLabel(self.centralwidget)
        self.ProbabilityOutput.setGeometry(QtCore.QRect(350, 540, 240, 50))
        self.ProbabilityOutput.setStyleSheet("background-color: rgb(255, 255, 255);")
        self.ProbabilityOutput.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
        self.ProbabilityOutput.setObjectName("ProbabilityOutput")

        # Config Output Label
        self.ConfigOutput = QtWidgets.QLabel(self.centralwidget)
        self.ConfigOutput.setGeometry(QtCore.QRect(350, 80, 241, 41))
        self.ConfigOutput.setStyleSheet("background-color: rgb(255, 255, 255);")
        self.ConfigOutput.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
        self.ConfigOutput.setObjectName("ConfigOutput")
        self.setCentralWidget(self.centralwidget)

        # MenuBar
        self.menubar = QtWidgets.QMenuBar(self)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 627, 21))
        self.menubar.setObjectName("menubar")
        self.menu = QtWidgets.QMenu(self.menubar)
        self.menu.setObjectName("menu")
        self.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(self)
        self.statusbar.setObjectName("statusbar")
        self.setStatusBar(self.statusbar)
        self.actionAbout = QtWidgets.QAction(self)
        self.actionAbout.setObjectName("actionAbout")
        self.menu.addAction(self.actionAbout)
        self.menubar.addAction(self.menu.menuAction())

        self.retranslateUi(self)

        QtCore.QMetaObject.connectSlotsByName(self)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        self.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.SelectSnapArea.setText(_translate("MainWindow", "Configure Snap Area"))
        self.SnapArea.setText(_translate("MainWindow", "Snap Area"))
        self.Evaluate.setText(_translate("MainWindow", "Evaluate"))
        self.SaveStudy.setText(_translate("MainWindow", "Save Study"))
        self.SaveStudyConfirmation.setText(_translate("MainWindow", ""))
        self.DisplayModelName.setText(_translate("MainWindow", self.adjust_model_label()))
        self.ProbabilityOutput.setText(_translate("MainWindow", self.adjust_probability_label()))
        self.ConfigOutput.setText(_translate("MainWindow", self.adjust_config_label()))
        self.menu.setTitle(_translate("MainWindow", "..."))
        self.actionAbout.setText(_translate("MainWindow", "About"))

    """
    ************************************************************************************
    THESE ARE FOR ADJUSTING DIALOGUE BOXES
    ************************************************************************************
    """

    def adjust_config_label(self, override=None):
        BASE_TEXT = "X1,Y1: ({},{})\nX2,Y2: ({},{})"
        if override:
            return override
        return BASE_TEXT.format(self.X1, self.Y1, self.X2, self.Y2)

    def adjust_probability_label(self, override=None):
        BASE_TEXT = "Prediction: {}\nProbability: {}"
        if override:
            return override
        return BASE_TEXT.format(self.prediction, self.probability)

    def adjust_model_label(self, override=None):
        if override:
            return override
        BASE_TEXT = "Current Model: {}"
        return BASE_TEXT.format(self.model_name)
    def save_study_confirmation(self, filename, override=None):
        if override:
            return overrride
        BASE_TEXT = "Saved study: "
        return BASE_TEXT + filename

    """
    ************************************************************************************
    THESE ARE FOR CONFIGURING THE READER
    ************************************************************************************
    """
    def get_coords(self):
        '''
        Use to get coordinates upon mouse click and mouse release.
        For use in "Select Snap Area"
        '''
        def on_click(x, y, button, pressed, self=self):
            if self.num_clicks == 0:
                self.X1 = x
                self.Y1 = y
                self.num_clicks += 1
            else:
                self.X2 = x
                self.Y2 = y
                self.num_clicks = 0
                return False

        with mouse.Listener(on_click=on_click) as listener:
            try:
                listener.join()
            except:
                print(self.X1, self.Y1, self.X2, self.Y2)
                listener.stop

    def select_snap_area(self):
        self.ConfigOutput.setText(self.adjust_config_label(override="****CALIBRATING****"))
        self.get_coords()
        self.ConfigOutput.setText(self.adjust_config_label())
        with open("config.txt", "w") as f:
            f.write(f"{self.X1}\n{self.Y1}\n{self.X2}\n{self.Y2}")

    """
    ************************************************************************************
    THESE ARE FOR CHANGING THE MODEL
    ************************************************************************************
    """

    def model_selection_change(self):
        self.model_name = self.SelectModel.currentText()
        self.model = MyModel(self.model_name)
        self.DisplayModelName.setText(self.adjust_model_label())

    """
    ************************************************************************************
    THESE ARE FOR SNAPPING SHOTS
    ************************************************************************************
    """

    def snap_candidate(self):
        width = abs(self.X2 - self.X1)
        height = abs(self.Y2 - self.Y1)
        screenshot = pyautogui.screenshot(region=(self.X1, self.Y1, width, height))
        screenshot.save("last_screenshot.png")
        self.model.preview_candidate("last_screenshot.png")
        self.Candidate.setPixmap(QtGui.QPixmap("last_candidate.png"))

    """
    ************************************************************************************
    THESE ARE FOR EVALUATING
    ************************************************************************************
    """

    def evaluate_func(self):
        mapping = {0: "COVID NEGATIVE", 1: "COVID POSITIVE"}
        model_output = self.model.predict()[0]
        self.prediction = mapping[np.argmax(model_output)]
        self.probability = np.max(model_output)
        self.ProbabilityOutput.setText(self.adjust_probability_label())

    def gradcam(self):
        pass
    """
    ************************************************************************************
    THESE ARE FOR SAVING, LOADING, AND DELETING STUDIES
    ************************************************************************************
    """

    def save_study(self):
        custom_studies = [file for file in self.saved_studies if "study_" in file]
        if custom_studies:
            last_file = self.saved_studies[-1]
            last_int = last_file.split('_')[-1].replace('.png', '').strip('0')
            last_int = int(last_int) + 1
        else:
            last_int = 1
        filename = f"study_{last_int:05}.png"
        shutil.copy("last_screenshot.png", "./saved_studies/" + filename)
        self.saved_studies = os.listdir("./saved_studies/")
        self.LoadStudy.addItem(filename)
        self.SaveStudyConfirmation.setText(self.save_study_confirmation(filename))

    def load_study(self):
        filename = self.LoadStudy.currentText()
        shutil.copy(f"./saved_studies/{filename}", "last_screenshot.png")
        self.model.preview_candidate("last_screenshot.png")
        self.Candidate.setPixmap(QtGui.QPixmap("last_candidate.png"))
        self.evaluate_func()

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    ui = PredictionApp()
    ui.show()
    sys.exit(app.exec_())

# In[ ]:


from flask import abort, jsonify
from services.rating_service import RatingService

__rating_service = RatingService()

def ratingsByMovie(id):
    if id < 1:
        abort(403, "Invalid identifer")

    return __rating_service.getByMovie(id)

def voteMovie(id, rating):
    if id < 1:
        abort(403, "Invalid identifer")

    if rating < 0 or rating > 10:
        abort(403, "Invalid rating!")

    try:
        __rating_service.vote_rating(id, rating)
    except IndexError as e:
        abort(404, e)

    return jsonify(success="Filme votado com sucesso!")


from constance.admin import Config, ConstanceAdmin
from django.contrib import admin, messages
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from elnure_api.admin import elnure_admin_site
from elnure_config import models
from elnure_config.admin import forms
from elnure_core.strategies import run_strategy, StrategyError

@admin.register(models.Semester, site=elnure_admin_site)
class SemesterAdmin(admin.ModelAdmin):
    ordering = ["id"]
    exclude = ["study_year"]
    form = forms.SemesterForm

@admin.register(models.ApplicationWindow, site=elnure_admin_site)
class ApplicationWindowAdmin(admin.ModelAdmin):
    readonly_fields = ["id"]
    fields = ["id", "start_date", "end_date"]
    ordering = ["-start_date"]

    def response_change(self, request, obj):
        response = super().response_change(request, obj)

        if "_save_and_run" in request.POST:
            try:
                run_snapshot = run_strategy(obj)
            except StrategyError as exc:
                self.message_user(
                    request, f"Algorithm error: {str(exc)}", messages.ERROR
                )

                opts = self.model._meta
                return HttpResponseRedirect(
                    reverse(
                        f"admin:{opts.app_label}_{opts.model_name}_change",
                        args=(obj.pk,),
                        current_app=self.admin_site.name,
                    )
                )

            opts = run_snapshot._meta
            return HttpResponseRedirect(
                reverse(
                    f"admin:{opts.app_label}_{opts.model_name}_change",
                    args=(run_snapshot.id,),
                    current_app=self.admin_site.name,
                )
            )

        return response

elnure_admin_site.register([Config], admin_class=ConstanceAdmin)


from typing import List

from pyreach.common.python import types_gen
from pyreach.impl.test_utils import TestResponder

class TestPingResponder(TestResponder):

  def step(self, cmd: types_gen.CommandData) -> List[types_gen.DeviceData]:
    if (cmd.device_type == "ping" and not cmd.device_name and
        cmd.data_type == "ping"):
      return [
          types_gen.DeviceData(
              device_type="ping",
              data_type="cmd-status",
              status="done",
              ts=cmd.ts,
              local_ts=cmd.ts,
              tag=cmd.tag)
      ]
    return []

  def start(self) -> List[types_gen.DeviceData]:
    return []

class TestSessionManager(TestResponder):
  _active: bool = False

  def _get_state(self, ts: int, tag: str) -> types_gen.DeviceData:
    return types_gen.DeviceData(
        ts=ts,
        tag=tag,
        device_type="session-manager",
        data_type="connected-clients",
        connected_clients=types_gen.ConnectedClients(clients=[
            types_gen.ConnectedClient(
                uid="ac375ffb-601a-4f4f-a7b8-a0cd20c09f64",
                is_current=True,
                control_session_active=self._active)
        ]))

  def step(self, cmd: types_gen.CommandData) -> List[types_gen.DeviceData]:
    if (cmd.device_type == "session-info" and not cmd.device_name and
        cmd.data_type == "session-info"):
      self._active = False
      return [self._get_state(cmd.ts, "")]
    if cmd.device_type == "operator" and cmd.data_type == "session-info":
      self._active = True
      return [self._get_state(cmd.ts, "")]
    if (cmd.device_type == "session-manager" and not cmd.device_name and
        cmd.data_type == "connected-clients-request"):
      return [self._get_state(cmd.ts, "")]
    return []

  def start(self) -> List[types_gen.DeviceData]:
    return []

class TestPipelineDescription(TestResponder):

  def step(self, cmd: types_gen.CommandData) -> List[types_gen.DeviceData]:
    """Generate one pipeline description.

    Args:
      cmd: The CommandData to get the device type/name from.

    Returns:
      A list containing a rejected Device Data message if
      the cmd is untagged; otherwise an empty list is returned.
    """
    if (cmd.device_type == "discovery-aggregator" and not cmd.device_name and
        cmd.data_type == "machine-interfaces-request"):
      return [
          types_gen.DeviceData(
              ts=cmd.ts,
              tag=cmd.tag,
              device_type="discovery-aggregator",
              data_type="machine-interfaces",
              machine_interfaces=types_gen.MachineInterfaces(interfaces=[
                  types_gen.MachineInterface(
                      data_type="prediction",
                      device_type="oracle",
                      device_name="pick-points",
                      py_type="inference-request"),
                  types_gen.MachineInterface(
                      data_type="color-depth",
                      device_type="depth-camera",
                      py_type="frame-request"),
                  types_gen.MachineInterface(
                      data_type="color-depth",
                      device_name="wrist",
                      device_type="depth-camera",
                      py_type="frame-request"),
                  types_gen.MachineInterface(
                      data_type="color",
                      device_name="invoice",
                      device_type="color-camera",
                      py_type="frame-request"),
                  types_gen.MachineInterface(
                      data_type="color",
                      device_type="color-camera",
                      py_type="frame-request"),
                  types_gen.MachineInterface(
                      data_type="color",
                      device_name="vnc0",
                      device_type="vnc",
                      py_type="frame-request"),
                  types_gen.MachineInterface(
                      data_type="robot-state",
                      device_type="robot",
                      py_type="publish"),
              ]))
      ]
    return []

  def start(self) -> List[types_gen.DeviceData]:
    return []


import unittest
from parameterized import parameterized
from app.KontoOsobiste import KontoOsobiste

class TestLoan(unittest.TestCase):

    def setUp(self):
        self.konto = KontoOsobiste("Marek", "Papszun", "02225432100")

    @parameterized.expand([
        ([100, 100, 100, 400, 500], 500, True, 500),
        ([-100, 100, 100, -100], 500, False, 0),
        ([100, 100, 200, -100, 500], 500, True, 500),
        ([-100, 100, -100, 100, -500], 500, False, 0),
        ([500, 200], 500, False, 0),
        ([-100], 500, False, 0)
    ])
    def test_loan(self, historia, suma, werdykt, saldo):
        self.konto.historia = historia
        is_loan = self.konto.zaciagnij_kredyt(suma)
        self.assertEqual(is_loan, werdykt)
        self.assertEqual(self.konto.saldo, saldo)


import matplotlib.pyplot as plt
import numpy as np
from functools import wraps
# from . import rc_params
from matplotlib.collections import LineCollection
import scipy

prop_cycle = plt.rcParams['axes.prop_cycle']
COLORS = prop_cycle.by_key()['color']
LINE_STYLES = ["-", "--", ":", "--."]

class fig_saver():
    def __init__(self, output_dir = ".", show=True):
        self.show = show
        if output_dir[-1] == "/":
            self.output_dir = output_dir
        else:
            self.output_dir = output_dir + "/"

    def save(self, name, fig=None):
        if fig is None:
            fig = plt.gcf()
        fig.savefig(self.output_dir + name + ".pdf", facecolor="white", bbox_inches='tight', dpi=150)
        fig.savefig(self.output_dir + name + ".jpeg", facecolor="white", bbox_inches='tight', dpi=150)
        if self.show:
            plt.show()

    def __call__(self, name, fig=None):
        self.save(name, fig=fig)

def legend_outside(bbox = (1,1), **kwargs):
    return plt.legend(bbox_to_anchor=bbox, loc="upper left", **kwargs)

def fancy_legend(ax=None, colors=COLORS, **kwargs):
    if ax is None:
        ax = plt.gca()

    leg = ax.legend(frameon = False, handlelength = 0, columnspacing = 0.8, **kwargs)
    for i in range(len(leg.get_texts())):
        leg.get_texts()[i].set_color(colors[i % len(colors)])
        leg.legendHandles[i].set_visible(False)

def arg(name, arg_type=object, value_constraint=True, default_value="None"):
    """A wrapper funcion to check arguments"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if type(name) == str:
                if name in kwargs.keys():
                    x = kwargs[name]
                else:
                    x = exec(default_value)
                print(kwargs)
                print(globals())
                print(locals())
                x = exec(name)

            elif type(name) == int:
                if name < len(args) and name >= 0:
                    x = args[name]
                else:
                    raise ValueError("If name is an integer, it must be between 0 and len(args)-1")

            else:
                raise TypeError("Type of name must be an integer or string. Got %s instead" % str(type(name)))

            if not isinstance(x, arg_type):
                raise TypeError("Argument %s must be of type %s. Got %s instead" %(name, arg_type, type(x)))
            if not value_constraint:
                raise ValueError("Argument %s must satisfy %s" %(name, value_constraint))

            return func(*args, **kwargs)

        return wrapper
    return decorator

# @arg("ylim", default_value="(min(y), max(y))")
# @arg("xlim", default_value="(min(x), max(x))")
def density_scatter(x, y, xlim=None, ylim=None, n_bins=100, fig=None, ax=None, dropna=True, density=True, **kwargs):
    """
    Plots the density of the data in each bin provided there is data in the bin.
    The function wrapps matplotlib.pyplot.hist2d, calculating the bins for the histogram.

    Parameters
    ----------
    x: listlike
        The x values of each data point to plot
    y: listlike
        The y values of each data point to plot
    xlim: (float, float)
        The lower and upper bounds on the x axis
    ylim: (float, float)
        The lower and upper bounds of the y axis
    n_bins: int
        The number of bins to divide each axis into
    dropna: bool

    Returns
    -------
    The four outputs from hist2d
    """
    if xlim is None:
        xlim = (min(x), max(x))
    if ylim is None:
        ylim = (min(y), max(y))

    if fig is None or ax is None:
        fig, ax = plt.subplots()

    x_bins = np.linspace(xlim[0], xlim[1], n_bins)
    y_bins = np.linspace(ylim[0], ylim[1], n_bins)

    if dropna:
        filt = np.isnan(x) | np.isnan(y)

    R =  ax.hist2d(x[~filt], y[~filt], bins=[x_bins, y_bins], cmin=1, density=density, **kwargs)

    _, _, _, f = R

    if density:
        fig.colorbar(f, label="Density", ax=ax)
    else:
        fig.colorbar(f, label="Count", ax=ax)

    return R

def plot_thick_line(x, y, w, i=0, xlim=None, ylim=None, ax=None, **kwargs):
    w_max = np.nanmax(w)
    points = np.array([x, y]).T.reshape(-1, 1, 2)
    segments = np.concatenate([points[:-1], points[1:]], axis=1)

    if ax is None:
        ax = plt.gca()

    lwidths = w/w_max * 5
    lc = LineCollection(segments, linewidths=lwidths, color=COLORS[i], **kwargs)
    ax.add_collection(lc)

    if xlim is None:
        ax.set_xlim(np.nanmin(x), np.nanmax(x))
    if ylim is None:
        ax.set_ylim(np.nanmin(y), np.nanmax(y))

def plot_density_line(x, y, **kwargs):
    """
    This method is like plt.plot except plots
    the line with a variable width which represents how
    clustered the data are

    Parameters
    ----------
    x: list like
    y: list like
    **kwargs:
        Passed to plt.plot

    """
    lw_max = 10
    lw_min = 1

    ds_min = 1e-9

    dx = differential(x)
    dy = differential(y)
    ds = np.sqrt(dx**2 + dy**2)
    w = 1/ds
    w_scaled = (w - np.nanmin(w))/(np.nanmax(w) - np.nanmin(w))
    lwidths = (-lw_min + lw_max)*w_scaled + lw_min

    plot_thick_line(x, y, lwidths, **kwargs)

def dual_plot():
    fig = plt.figure(figsize=(10, 5))
    gs = fig.add_gridspec(1, 2, wspace=0)
    axs = gs.subplots(sharey=True)

    return fig, axs

def differential(l):
    """
    Calculates the differentail of a list l.

    Parameters
    ----------
    l:  list like
        A list of which to calculate the differential

    Returns
    -------
    dl:     ``np.list``
        A list of the change between each value of l
    """

    li = np.array(l)[:-1]
    lf = np.array(l)[1:]
    dl = li-lf

    d_end = dl[-1]
    return np.append(dl, d_end)
def plot_median_track(x_vals, y_vals, bins=30, xlim=None, shade_width=False, ax=None, dropna=False, s=0.1, plot_points=False, min_count=1, **kwargs):
    """
    Plots the mean of the data as a line
    with a shaded region representing the standard deviation

    Parameters
    ----------

    x_vals: np.array like
        The x values of the data

    y_vals: np.array like
    bins: ``int`` [default: 50]
        The number of bins to bin the data by
    xlim: ``(int, int)`` [default: None]
        The limits of the bins of the data
        if None, uses the minimum and maximum values
    err_mean: ``bool`` [default: False]
        If true, plots the error of the mean instead
        of the standard deviation for the shaded regions
    min_count: ``int`` [default: 1]
        The minimum number of points in a bin required to plot a point

    Returns
    -------
    medians
    bins
    deviations
    """

    if ax is None:
        ax = plt.gca()

    if dropna:
        filt = ~(np.isnan(x_vals) | np.isnan(y_vals))
        x_vals = x_vals[filt]
        y_vals = y_vals[filt]
    medians, bins, _ = scipy.stats.binned_statistic(x_vals, y_vals, statistic="mean", bins=bins, range=xlim)
    nums, _, _ = scipy.stats.binned_statistic(x_vals, y_vals, statistic="count", bins=bins, range=xlim)
    x_bins = 0.5*(bins[1:] + bins[:-1])
    # p = plot_thick_line(x_bins, means, nums/30, ax=ax, **kwargs)

    per16, _, _ = scipy.stats.binned_statistic(x_vals, y_vals,
            statistic=lambda x: np.percentile(x, 16), bins=bins, range=xlim)

    per84, _, _ = scipy.stats.binned_statistic(x_vals, y_vals,
            statistic=lambda x: np.percentile(x, 84), bins=bins, range=xlim)

    dy_low = medians - per16
    dy_high = per84 - medians

    filt = nums > min_count

    medians = medians[filt]
    x_bins = x_bins[filt]
    nums = nums[filt]
    dy_low = dy_low[filt]
    dy_high = dy_high[filt]

    if plot_points:
        p = err_scatter(x_bins, medians, yerr=dy, ax=ax, **kwargs)
    else:
        p = ax.plot(x_bins, medians, **kwargs)

    if shade_width:
        ax.fill_between(x_bins, medians - dy_low, medians + dy_high, alpha=0.3, color=p[0].get_color())

    return medians, x_bins, 0.5*(dy_low + dy_high)

def plot_mean_track(x_vals, y_vals, bins=30, xlim=None, shade_width=False,
        err_mean = False, ax=None, dropna=False, s=0.1, plot_points=False,
        plot_errorbar=True, plot_alt=False, min_count=1, **kwargs):
    """
    Plots the mean of the data as a line
    with a shaded region representing the standard deviation

    Parameters
    ----------

    x_vals: np.array like
        The x values of the data

    y_vals: np.array like
    bins: ``int`` [default: 50]
        The number of bins to bin the data by
    xlim: ``(int, int)`` [default: None]
        The limits of the bins of the data
        if None, uses the minimum and maximum values
    err_mean: ``bool`` [default: False]
        If true, plots the error of the mean instead
        of the standard deviation for the shaded regions
    min_count: ``int`` [default: 1]
        The minimum number of points in a bin required to plot a point

    Returns
    -------
    """

    if ax is None:
        ax = plt.gca()

    if dropna:
        filt = ~(np.isnan(x_vals) | np.isnan(y_vals))
        x_vals = x_vals[filt]
        y_vals = y_vals[filt]
    means, bins, _ = scipy.stats.binned_statistic(x_vals, y_vals, statistic="mean", bins=bins, range=xlim)
    nums, _, _ = scipy.stats.binned_statistic(x_vals, y_vals, statistic="count", bins=bins, range=xlim)
    x_bins = 0.5*(bins[1:] + bins[:-1])
    # p = plot_thick_line(x_bins, means, nums/30, ax=ax, **kwargs)

    std, _, _ = scipy.stats.binned_statistic(x_vals, y_vals, statistic="std", bins=bins, range=xlim)

    filt = nums > min_count

    means = means[filt]
    x_bins = x_bins[filt]
    nums = nums[filt]
    std = std[filt]

    if err_mean:
        dy = std / np.sqrt(nums)
    else:
        dy = std

    if plot_points:
        if plot_errorbar:
            p = err_scatter(x_bins, means, yerr=dy, ax=ax, **kwargs)
        else:
            p = ax.plot(x_bins, means, ".", **kwargs)
    else:
        p = ax.plot(x_bins, means, **kwargs)

    if plot_alt:
        ax.scatter(x_bins, means - dy, alpha=0.3, marker="_",
                color=p[0].get_color(), zorder=-1)
        ax.scatter(x_bins, means + dy, alpha=0.3, marker="_",
                color=p[0].get_color(), zorder=-1)
    if shade_width:
        ax.fill_between(x_bins, means - dy, means + dy, alpha=0.3,
                color=p[0].get_color(), zorder=-1)

    return means, bins, nums

def err_scatter(x, y, yerr=None, xerr=None, fmt=None, ax=None, capsize=0, marker="o", alpha_bars=1, **kwargs):
    """
    A wrapper around plt.errorbar which defaults to a
    scatter plot and enables changing the alpha of the
    error bars
    """

    if ax is None:
        ax = plt.gca()
    if fmt is not None:
        markers, caps, bars = ax.errorbar(x, y, xerr=xerr, yerr=yerr, fmt=fmt,capsize=capsize, **kwargs)
    else:
        markers, caps, bars = ax.errorbar(x, y, xerr=xerr, yerr=yerr, ls="", marker=marker,
                capsize=capsize, **kwargs)

    for bar in bars:
        bar.set_alpha(alpha_bars)
    for cap in caps:
        cap.set_alpha(alpha_bars)

    return markers, caps, bars

def smooth_hist(x, range=None, bins=20, orientation="vertical", **kwargs):
    if range is None:
        range = (np.nanmin(x),
                np.nanmax(x))

    counts, bin_edges = np.histogram(x, bins, range)
    bin_widths = bin_edges[1:] - bin_edges[:-1]
    densities = counts/bin_widths/len(x)
    bin_means = (bin_edges[1:] + bin_edges[:-1])/2

    if orientation=="vertical":
        plt.plot(bin_means, densities, **kwargs)
    else:
        plt.plot(densities, bin_means, **kwargs)


"""used for train 81k"""
import os
import h5py
import numpy as np
import pandas as pd
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, models
from PIL import Image
from tqdm import tqdm
import pickle
import zipfile
from io import BytesIO
import pdb
import csv

# The number of categories is the same for 81k and 40k.
def generate_category_list():
    file_path = 'VggsoundAVEL40kCategories.txt'
    category_list = []
    with open(file_path, 'r') as fr:
        for line in fr.readlines():
            category_list.append(line.strip())
    return category_list

class VGGSoundDataset(Dataset):
    def __init__(self, meta_csv_path, audio_fea_base_path, video_fea_base_path, avc_label_base_path, split='train'):
        super(VGGSoundDataset, self).__init__()
        self.audio_fea_base_path = audio_fea_base_path
        self.video_fea_base_path = video_fea_base_path
        self.avc_label_base_path = avc_label_base_path
        all_df = pd.read_csv(meta_csv_path)
        self.split_df = all_df
        # Output the proportion of train, test, and valid.
        print(f'{len(self.split_df)}/{len(all_df)} videos are used for {split}')
        self.all_categories = generate_category_list()
        print(f'total {len(self.all_categories)} classes in VggsoundAVEL81k')

    def __getitem__(self, index):
        one_video_df = self.split_df.iloc[index]
        video_id = one_video_df['id'][:-4]# drop '.mp4'

        audio_fea = self._load_fea(self.audio_fea_base_path, video_id) # [10, 128]
        video_fea = self._load_fea(self.video_fea_base_path, video_id) # [10, 7, 7, 512]

        if audio_fea.shape[0] < 10:
            cur_t = audio_fea.shape[0]
            add_arr = np.tile(audio_fea[-1, :], (10-cur_t, 1))
            audio_fea = np.concatenate([audio_fea, add_arr], axis=0)
        elif audio_fea.shape[0] > 10:
            audio_fea = audio_fea[:10, :]

        return torch.from_numpy(video_fea), \
               torch.from_numpy(audio_fea)

    def _load_fea(self, fea_base_path, video_id):
        fea_path = os.path.join(fea_base_path, "%s.zip"%video_id)
        with zipfile.ZipFile(fea_path, mode='r') as zfile:
            for name in zfile.namelist():
                if '.pkl' not in name:
                    continue
                with zfile.open(name, mode='r') as fea_file:
                    content = BytesIO(fea_file.read())
                    fea = pickle.load(content)
        return fea

    def __len__(self,):
        return len(self.split_df)


import os
import torch
import shutil
import numpy as np
from itertools import cycle
import time

from sklearn.metrics import confusion_matrix, multilabel_confusion_matrix, classification_report
from sklearn.preprocessing import label_binarize
from sklearn.metrics import roc_auc_score, roc_curve, auc
from scipy import interp

import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable

def inplace_relu(m):
    classname = m.__class__.__name__
    if classname.find('ReLU') != -1:
        m.inplace = True

def accuracy(output, target, is_multilabel=False):
    """Computes the accuracy. Return the num of correct"""
    if is_multilabel:
        pred = output.clone()
        thresh = 0.5
        pred[pred < thresh] = 0
        pred[pred >= thresh] = 1
        correct = (pred == target).sum().float() / target.shape[1] /target.shape[0]
    else:
        pred = torch.argmax(pred, 1)
        correct = (pred == target).sum().float()
    return correct

def save_checkpoint(state, is_best, dst_path='.', filename='ckpt.pth.tar'):
    dst_path = os.path.join(dst_path,)
    if not os.path.exists(dst_path):
        os.makedirs(dst_path)
    filename = os.path.join(dst_path, filename)
    current_time = time.strftime("%Y%m%d_%H%M", time.localtime())
    best_name = os.path.join(dst_path, 'best@ep{}_{}.pth.tar'.format(state['epoch'], current_time))
    torch.save(state, filename)
    if is_best:
        print('\n\n=> Best val @epoch {}, saving model'.format(state['epoch']))
        shutil.move(filename, best_name)
        # shutil.copyfile(filename, best_name)

def format_time(secs):
    """Given seconds, return hours:minutes:seconds"""
    hours, rem = divmod(secs, 3600)
    minutes, seconds = divmod(rem, 60)
    return hours, minutes, seconds

def generate_cm_figure(cm, labels, title, fig_size=10):
    font = {
        # 'family': 'serif',
        'weight': 'normal',
        'size': 18,
    }
    plt.rc('font', **font)
    h, w = cm.shape
    fig, ax = plt.subplots(figsize=(fig_size, fig_size))
    im = ax.imshow(cm, cmap=plt.get_cmap('viridis'))
    ax.set_title(title, fontdict=font)
    xlocations = np.array(range(len(labels)))
    ax.set_ylabel('True label', fontdict=font)
    ax.set_xlabel('Predicted label', fontdict=font)
    ax.set_xticks(xlocations)
    ax.set_yticks(xlocations)
    ax.set_yticklabels(labels, fontdict=font)
    ax.set_xticklabels(labels, fontdict=font)

    # ind_array = np.arange(len(labels))
    # x, y = np.meshgrid(ind_array, ind_array)
    # for x_val, y_val in zip(x.flatten(), y.flatten()):
    for x in range(h):
        for y in range(w):
            c = cm[x][y]
            ax.text(y, x, "%0.2f" % (c,), color='white', fontsize=18, va='center', ha='center')

    # offset the tick
    tick_marks = np.array(range(len(labels))) + 0.5
    ax.set_xticks(tick_marks, minor=True)
    ax.set_yticks(tick_marks, minor=True)
    ax.xaxis.set_ticks_position('none')
    ax.yaxis.set_ticks_position('none')
    ax.grid(True, which='minor', linestyle='-')
    #     plt.gcf().subplots_adjust(bottom=0.15)

    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", size="5%", pad=0.05)
    ax.figure.colorbar(im, cax=cax)
    fig.tight_layout()  # remove paddings
    # plt.subplots_adjust(top=1,bottom=0,left=0,right=1,hspace=0,wspace=0)
    # plt.margins(0,0）
    #     plt.show()
    # plt.savefig('/media/newhd/ysong/project/LGP/summary/t.png', dpi=200)
    return fig

def plot_confusion_matrix(y_true, y_pred, writer, phase='train', epoch=0, labels=['0', '1', '2', '3']):
    """Computer confusion_matrix and plot it into writer every epoch
    Args:
        cm: the confusion matrix to plot using matplotlib
        """
    if y_true.shape != y_pred.shape:
        y_pred = labels.reshape(y_true.shape)
    assert y_true.size == y_pred.size, 'Sizes not match! y_true: {}, y_pred: {}'.format(y_true.size, y_pred.size)

    cm = confusion_matrix(y_true, y_pred)
    cm_normalized = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
    # h, _ = cm.shape
    # confusion matrix symmetric along the diagonal
    # symmetric_cm = cm + np.transpose(cm)
    # diag = np.diag_indices(h)
    # cm[diag] -= cm[diag]
    fig_cm = generate_cm_figure(cm, labels, '[{}] confusion matrix@ep{}'.format(phase, epoch))
    fig_cm_normalized = generate_cm_figure(cm_normalized, labels,
                                           '[{}] normalized confusion matrix@ep{}'.format(phase, epoch))

    writer.add_figure('Confusion Matrix/{}/epoch_{}'.format(phase, epoch), fig_cm, global_step=epoch)
    writer.add_figure('Normalized Confusion Matrix/{}/epoch_{}'.format(phase, epoch),
                      fig_cm_normalized,
                      global_step=epoch)
    writer.flush()
    return cm, cm_normalized

def plot_confusion_matrix_multilabel(y_true, y_pred, writer, phase='train', epoch=0, labels=['0', '1', '2', '3']):
    """Computer confusion_matrix and plot it into writer every epoch
    Args:
        cm: the confusion matrix to plot using matplotlib
        """
    if y_true.shape != y_pred.shape:
        y_pred = labels.reshape(y_true.shape)
    assert y_true.size == y_pred.size, 'Sizes not match! y_true: {}, y_pred: {}'.format(y_true.size, y_pred.size)

    # cm = multilabel_confusion_matrix(y_true[:, label_col], y_pred[:, label_col])
    cm = multilabel_confusion_matrix(y_true, y_pred)
    for label_col in range(len(labels)):
        fig_cm = generate_cm_figure(cm[label_col], ['0', '1'], f'confusion matrix of label{label_col}', fig_size=6)
        writer.add_figure(f'Confusion Matrix/{phase}/epoch_{epoch}/label_{label_col}', fig_cm, global_step=epoch)

    cls_report = classification_report(y_true, y_pred)
    writer.add_text(tag='{}/{}'.format(phase, epoch), text_string=cls_report, global_step=epoch)
    writer.flush()
    return cm

# def compute_auc(y_true, y_pred, labels=[0, 1, 2, 3]):
#     y_true_one_hot = label_binarize(y_true)
#     auc = roc_auc_score(y_true_one_hot, y_pred)
#     return auc

def plot_roc_curve_and_compute_auc(y_true, y_pred, writer, phase, epoch, labels=[0, 1, 2, 3], is_multilabel=False):
    """Plot roc_curve for each class and writes into writer.
    From: https://scikit-learn.org/stable/auto_examples/model_selection/plot_roc.html
    Args:
        labels: The list for the ground truth labels
    """
    if not is_multilabel:
        y_true_bin = label_binarize(y_true, classes=labels)
    n_classes = len(labels)
    # Compute ROC curve and ROC area for each class
    fpr = dict()
    tpr = dict()
    roc_auc = dict()
    for i in range(n_classes):
        fpr[i], tpr[i], _ = roc_curve(y_true_bin[:, i], y_pred[:, i])
        roc_auc[i] = auc(fpr[i], tpr[i])

    # Compute micro-average ROC curve and ROC area
    fpr["micro"], tpr["micro"], _ = roc_curve(y_true_bin.ravel(), y_pred.ravel())
    # equal to: roc_auc = roc_auc_score(y_true_one_hot, y_pred)
    roc_auc["micro"] = auc(fpr["micro"], tpr["micro"])
    """Plot ROC curves for the multilabel"""
    # First aggregate all false positive rates
    all_fpr = np.unique(np.concatenate([fpr[i] for i in range(n_classes)]))

    # Then interpolate all ROC curves at this points
    mean_tpr = np.zeros_like(all_fpr)
    for i in range(n_classes):
        mean_tpr += interp(all_fpr, fpr[i], tpr[i])

    # Finally average it and compute AUC
    mean_tpr /= n_classes

    fpr["macro"] = all_fpr
    tpr["macro"] = mean_tpr
    roc_auc["macro"] = auc(fpr["macro"], tpr["macro"])

    font = {
        # 'family': 'serif',
        'weight': 'normal',
        'size': 18,
    }
    plt.rc('font', **font)
    fig = plt.figure(figsize=(10, 10))
    linewidth = 6
    # Plot all ROC curves
    plt.plot(fpr["micro"],
             tpr["micro"],
             label='micro-average ROC curve (area = {0:0.4f})'
             ''.format(roc_auc["micro"]),
             color='deeppink',
             linestyle=':',
             linewidth=linewidth)

    plt.plot(fpr["macro"],
             tpr["macro"],
             label='macro-average ROC curve (area = {0:0.4f})'
             ''.format(roc_auc["macro"]),
             color='purple',
             linestyle=':',
             linewidth=linewidth)

    colors = cycle(['aqua', 'darkorange', 'limegreen', 'cornflowerblue', 'red', 'blue', 'gold', 'darkkhaki'])
    for i, color in zip(range(n_classes), colors):
        # show the original 1/2/3/4 label
        plt.plot(fpr[i],
                 tpr[i],
                 color=color,
                 lw=linewidth,
                 label='ROC curve of class {0} (area = {1:0.4f})'
                 ''.format(i + 1, roc_auc[i]))

    plt.plot([0, 1], [0, 1], 'k--', lw=linewidth)
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('False Positive Rate', fontdict=font)
    plt.ylabel('True Positive Rate', fontdict=font)
    plt.title('[{}] ROC of multi-class@{}'.format(phase, epoch), fontdict=font)
    plt.legend(loc="lower right")
    fig.tight_layout()  # remove paddings
    writer.add_figure('ROC_all_lable/{0}/epoch_{1}'.format(phase, epoch), fig, global_step=epoch)
    writer.flush()
    return roc_auc["micro"], roc_auc["macro"]


import argparse
import logging
import sys

from stevedore import extension

logging.basicConfig(
    format='%(levelname)s:%(module)s:%(message)s',
    level=logging.INFO,
)

class JMConfig(object):
    def __init__(self, arguments_dict):
        self.arguments = arguments_dict
        self.__config_parser = None

    @property
    def config_parser(self):
        # if self.__config_parser is None:
        pass

class Jankman(object):
    """ Entry point class for the 'jankman' command line tool.
    """

    def __init__(self, args):
        parser = self.create_parser()
        arguments = parser.parse_args(args)

        if (arguments.log_level is not None):
            arguments.log_level = getattr(logging,
                                          arguments.log_level.upper(),
                                          'INFO')
            logger = logging.getLogger()
            logger.setLevel(arguments.log_level)
            logging.debug("hello")

        self.jm_config = JMConfig(vars(arguments))

    def create_parser(self):
        parser = argparse.ArgumentParser()

        parser.add_argument(
            '--conf',
            dest='conf',
            default=None,
            help='configuration file'
        )
        parser.add_argument(
            '-l',
            '--log_level',
            dest='log_level',
            default='info',
            help="log level (default: %(default)s)"
        )
        parser.add_argument(
            '--use-cache',
            action='store_true',
            dest='use_cache',
            default=False,
            help='ignore the cache and update the jobs anyhow (that will only '
            'flush the specified jobs cache)'
        )
        # parser.add_argument(
        #     '--flush-cache',
        #     action='store_true',
        #     dest='flush_cache',
        #     default=False,
        #     help='flush all the cache entries before updating'
        # )
        # parser.add_argument(
        #     '--version',
        #     dest='version',
        #     action='version',
        #     version=__version__(),
        #     help='show version'
        # )

        subparser = parser.add_subparsers(
            # help='deploy jenkins configuration',
            dest='command'
        )

        extension_manager = extension.ExtensionManager(
            namespace='jenkins_manager.cli.subcommands',
            invoke_on_load=True,
        )

        def parse_subcommand_args(ext, subparser):
            ext.obj.parse_args(subparser)

        extension_manager.map(parse_subcommand_args, subparser)

        return parser

    def execute(self):
        arguments = self.jm_config.arguments

        extension_manager = extension.ExtensionManager(
            namespace='jenkins_manager.cli.subcommands',
            invoke_on_load=True,)

        ext = extension_manager[arguments['command']]
        ext.obj.execute(self.jm_config)
        pass

def main():
    argv = sys.argv[1:]
    j = Jankman(argv)
    j.execute()

if __name__ == '__main__':
    main()


import numpy as np
import os
import glob
from sklearn.utils import shuffle
import cv2

def load_train(train_path,image_size,classes):
    images = []
    labels = []
    img_names = []
    cls = []
    print('读取训练图片')
    #classes 传入列表['dogs','cats']
    for fields in classes:
        index = classes.index(fields)
        print('Now going to read{} files (Index:{})'.format(fields,index))
        #路径读取格式：'D:/Users/16522/PycharmProjects/untitled/training_data\\dogs\\*g'
        path = os.path.join(train_path,fields,'*g')
        # 找到路径D:/Users...../dogs或者cats 且以g结尾的文件，即dogs文件夹下所有图片路径或cats文件夹下所有图片路径
        files = glob.glob(path)
        print(files)

        for f1 in files:
            print(f1)
            #image_size为图片大小，将图片转化为统一格式
            image= cv2.imread(f1)
            #统一转换为（64，64，3），通道数为3
            image = cv2.resize(image,(image_size,image_size),0,0,cv2.INTER_LINEAR)
            image = image.astype(np.float32)
            #归一化处理，将数据乘以1/255，转换为0-1之间的范围u
            image = np.multiply(image,1.0/255.0)
            images.append(image)
            label = np.zeros(len(classes))
            #猫狗二分类打标签 如【1，0】
            label[index] = 1.0 #label初始化为[0,0],当为狗时[1,0],当为猫时[0,1]
            labels.append(label)
            f1base = os.path.basename(f1)  #返回path最后文件名，这里为图片名
            img_names.append(f1base)
            cls.append(fields)
    images = np.array(images)
    labels = np.array(labels)
    img_names = np.array(img_names)
    cls = np.array(cls)
    return images,labels,img_names,cls

class DataSet():
    def __init__(self,images,labels,img_names,cls):
        self._num_examples = images.shape[0]
        self._images = images
        self._labels = labels
        self._img_names = img_names
        self._cls = cls
        self._epochs_done = 0
        self._index_in_epoch = 0

    def images(self):
        return self._images

    def labels(self):
        return self._labels

    def img_name(self):
        return self._img_names

    def cls(self):
        return self._cls

    def num_examples(self):
        return self._num_examples

    def epochs_done(self):
        return self._epochs_done

    def next_batch(self,batch_size):
        start = self._index_in_epoch
        self._index_in_epoch += batch_size

        if self._index_in_epoch >self._num_examples:
            self._epochs_done += 1 #训练全部数据多少次
            start = 0
            self._index_in_epoch = batch_size
            assert  batch_size <=self._num_examples
        end = self._index_in_epoch
        return self._images[start:end],self._labels[start:end],self._img_names[start:end],self._cls[start:end]

def read_train_sets(train_path,image_size,classes,validation_size):
    class DataSets():
        pass

    data_sets = DataSets()
    images,labels,img_names,cls = load_train(train_path,image_size,classes)
    #调用sklearn.utils的shuffle方法，打散猫狗图片
    images, labels, img_names, cls = shuffle(images, labels, img_names, cls)
    #这里读入2002张猫狗图片，validation_size = 0.2,因此验证集validation_size = 400
    #images (2002,64,64,3)
    if isinstance(validation_size,float):
        validation_size = int(validation_size*images.shape[0])
        validation_images = images[:validation_size]
        validation_lables = labels[:validation_size]
        validation_img_names = img_names[:validation_size]
        validation_cls = cls[:validation_size]

        train_images = images[validation_size:]
        train_labels = labels[validation_size:]
        train_img_names = img_names[validation_size:]
        train_cls = cls[validation_size:]

        data_sets.train = DataSet(train_images,train_labels,train_img_names,train_cls)
        data_sets.valid = DataSet(validation_images,validation_lables,validation_img_names,validation_cls)
        return data_sets


from typing import Dict, Optional

from Piece import Color, Piece

class Empty(Piece):
    _instance: Optional['Empty'] = None
    unicodes = {Color.EMPTY_SQUARE: ' '}

    def __new__(cls) -> 'Empty':
        if cls._instance is None:
            cls._instance = super().__new__(Empty)
        return cls._instance

    def __init__(self) -> None:
        super().__init__(Color.EMPTY_SQUARE)