# covid 19 pick money game
import pygame
import random
import os
from settings import *

FPS = 60
WIDTH = 500
HEIGHT = 600

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)

# 遊戲初始化 and 創建視窗
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("covid 19 pick money game")
clock = pygame.time.Clock()

# 載入圖片
background_img = pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "blood.png")).convert(), (500, 600))
player_img = pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "life.png")).convert(), (80, 100))
player_img.set_colorkey(BLACK)
life_img = pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "head.png")).convert(), (25, 25))
life_img.set_colorkey(BLACK)

# 載入掉落物圖片（病毒、錢），並改變大小
virus_imgs = []
virus_imgs.append(pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "enemy3.png")).convert(), (58, 58)))
virus_imgs.append(pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "enemy1.png")).convert(), (75, 75)))
virus_imgs.append(pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "enemy2.png")).convert(), (80, 80)))
virus_imgs.append(pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "enemy3.png")).convert(), (50, 50)))

money_imgs = []
money_imgs.append(pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "bigmoney.png")).convert(), (40, 40)))
money_imgs.append(pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "midmoney.png")).convert(), (30, 30)))
money_imgs.append(pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "smallmoney.png")).convert(), (20, 20)))

virus_img3 = pygame.transform.scale(pygame.image.load(
    os.path.join(IMG_PATH, "enemy3.png")).convert(), (58, 58))
pygame.display.set_icon(virus_img3)

# 設定爆炸
expl_anim = {}
expl_anim['lg'] = []
expl_anim['sm'] = []
expl_anim['player'] = []
for i in range(9):
    expl_img = pygame.image.load(os.path.join(
        IMG_PATH, f"expl{i}.png")).convert()
    expl_img.set_colorkey(BLACK)
    expl_anim['lg'].append(pygame.transform.scale(expl_img, (75, 75)))
    expl_anim['sm'].append(pygame.transform.scale(expl_img, (30, 30)))
    player_expl_img = pygame.image.load(
        os.path.join(IMG_PATH, f"player_expl{i}.png")).convert()
    player_expl_img.set_colorkey(BLACK)
    expl_anim['player'].append(player_expl_img)

# 載入音樂、音效
die_sound = pygame.mixer.Sound(os.path.join(
    SOUND_PATH, "rumble.ogg"))
die_sound.set_volume(0.1)
expl0_sound = pygame.mixer.Sound(os.path.join(SOUND_PATH, "expl0.wav"))
expl0_sound.set_volume(0.1)
gun_sound = pygame.mixer.Sound(os.path.join(SOUND_PATH, "pow1.wav"))
gun_sound.set_volume(0.1)
pygame.mixer.music.load(os.path.join(
    SOUND_PATH, "pick_money_background.wav"))
pygame.mixer.music.set_volume(0.3)
pygame.mixer.music.play(-1)

font_name = os.path.join(os.path.dirname(__file__), "font.ttf")

def draw_text(surf, text, size, x, y):
    font = pygame.font.Font(font_name, size)
    text_surface = font.render(text, True, WHITE)
    text_rect = text_surface.get_rect()
    text_rect.centerx = x
    text_rect.top = y
    surf.blit(text_surface, text_rect)

def new_virus():
    v = Virus()
    all_sprites.add(v)
    virus.add(v)

def new_money():
    m = Ｍoney()
    all_sprites.add(m)
    money.add(m)

def draw_health(surf, hp, x, y):
    if hp < 0:
        hp = 0
    BAR_LENGTH = 130
    BAR_HEIGHT = 15
    fill = (hp / 100) * BAR_LENGTH
    outline_rect = pygame.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
    fill_rect = pygame.Rect(x, y, fill, BAR_HEIGHT)
    pygame.draw.rect(surf, GREEN, fill_rect)
    pygame.draw.rect(surf, WHITE, outline_rect, 2)

def draw_lives(surf, lives, img, x, y):
    for i in range(lives):
        img_rect = img.get_rect()
        img_rect.x = x + 32 * i
        img_rect.y = y
        surf.blit(img, img_rect)

def draw_init():
    screen.blit(background_img, (0, 0))
    draw_text(screen, '撿金幣遊戲!', 64, WIDTH / 2, HEIGHT / 4)
    draw_text(screen, '上下左右移動人物 蒐集5000金幣即可過關~', 22, WIDTH / 2, HEIGHT / 2)
    draw_text(screen, '按任意鍵開始遊戲!', 18, WIDTH / 2, HEIGHT * 3 / 4)
    pygame.display.update()
    waiting = True
    while waiting:
        clock.tick(FPS)
        # 取得輸入
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return True
            elif event.type == pygame.KEYDOWN:
                waiting = False
                return False

class Player(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.transform.scale(player_img, (45, 60))
        self.rect = self.image.get_rect()
        self.radius = 20
        self.rect.centerx = WIDTH / 2
        self.rect.centery = HEIGHT / 2
        self.speedx = 5
        self.speedy = 5
        self.health = 100
        self.lives = 3
        self.hidden = False
        self.hide_time = 0

    def update(self):
        now = pygame.time.get_ticks()

        if self.hidden and now - self.hide_time > 1000:
            self.hidden = False
            self.rect.centerx = WIDTH / 2
            self.rect.centery = HEIGHT / 2

        key_pressed = pygame.key.get_pressed()
        if key_pressed[pygame.K_RIGHT]:
            self.rect.x += self.speedx
        if key_pressed[pygame.K_LEFT]:
            self.rect.x -= self.speedx
        if key_pressed[pygame.K_UP]:
            self.rect.y -= self.speedy
        if key_pressed[pygame.K_DOWN]:
            self.rect.y += self.speedy

        if self.rect.right > WIDTH:
            self.rect.right = WIDTH
        if self.rect.left < 0:
            self.rect.left = 0
        if self.rect.top < 0:
            self.rect.top = 0
        if self.rect.bottom > HEIGHT:
            self.rect.bottom = HEIGHT

    def hide(self):
        self.hidden = True
        self.hide_time = pygame.time.get_ticks()
        self.rect.center = (WIDTH / 2, HEIGHT / 2)

# 設定落下的病毒
class Virus(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.ran_image = random.choice(virus_imgs)
        self.ran_image.set_colorkey(BLACK)
        self.image = self.ran_image.copy()
        self.rect = self.image.get_rect()
        self.radius = int(self.rect.width * 0.85 / 2)
        # 隨機選擇從地圖的哪個位置出現
        self.position = random.randrange(0, 4)
        # 地圖上方
        if self.position == 0:
            self.rect.x = random.randrange(0, WIDTH - self.rect.width)
            self.rect.y = random.randrange(-180, -100)
            self.speedy = random.randrange(2, 5)
            self.speedx = random.randrange(-3, 3)
            self.total_degree = 0
            self.rot_degree = random.randrange(-3, 3)
        # 地圖下方
        if self.position == 1:
            self.rect.x = random.randrange(0, WIDTH - self.rect.width)
            self.rect.y = random.randrange(700, 780)
            self.speedy = random.randrange(-5, -2)
            self.speedx = random.randrange(-3, 3)
            self.total_degree = 0
            self.rot_degree = random.randrange(-3, 3)
        # 地圖左方
        if self.position == 2:
            self.rect.x = random.randrange(-180, -100)
            self.rect.y = random.randrange(0, HEIGHT - self.rect.height)
            self.speedy = random.randrange(-3, 3)
            self.speedx = random.randrange(2, 5)
            self.total_degree = 0
            self.rot_degree = random.randrange(-3, 3)
        # 地圖右方
        if self.position == 3:
            self.rect.x = random.randrange(700, 780)
            self.rect.y = random.randrange(0, HEIGHT - self.rect.height)
            self.speedy = random.randrange(-3, 3)
            self.speedx = random.randrange(-5, -2)
            self.total_degree = 0
            self.rot_degree = random.randrange(-3, 3)

    def rotate(self):
        self.total_degree += self.rot_degree
        self.total_degree = self.total_degree % 360
        self.image = pygame.transform.rotate(self.ran_image, self.total_degree)
        center = self.rect.center
        self.rect = self.image.get_rect()
        self.rect.center = center

    def update(self):
        self.rotate()
        self.rect.y += self.speedy
        self.rect.x += self.speedx
        if self.position == 0:
            if self.rect.top > HEIGHT or self.rect.left > WIDTH or self.rect.right < 0:
                self.rect.x = random.randrange(0, WIDTH - self.rect.width)
                self.rect.y = random.randrange(-100, -40)
                self.speedy = random.randrange(2, 5)
                self.speedx = random.randrange(-3, 3)
        if self.position == 1:
            if self.rect.bottom < 0 or self.rect.left > WIDTH or self.rect.right < 0:
                self.rect.x = random.randrange(0, WIDTH - self.rect.width)
                self.rect.y = random.randrange(700, 780)
                self.speedy = random.randrange(-5, -2)
                self.speedx = random.randrange(-3, 3)
        if self.position == 3:
            if self.rect.right < 0 or self.rect.top < 0 or self.rect.bottom > HEIGHT:
                self.rect.x = random.randrange(700, 780)
                self.rect.y = random.randrange(0, HEIGHT - self.rect.height)
                self.speedy = random.randrange(-3, 3)
                self.speedx = random.randrange(-5, -2)
        if self.position == 2:
            if self.rect.left > WIDTH or self.rect.top < 0 or self.rect.bottom > HEIGHT:
                self.rect.x = random.randrange(-180, -100)
                self.rect.y = random.randrange(0, HEIGHT - self.rect.height)
                self.speedy = random.randrange(-3, 3)
                self.speedx = random.randrange(2, 5)

# 設定落下的金錢
class Ｍoney(pygame.sprite.Sprite):
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.image_ori = random.choice(money_imgs)
        self.image_ori.set_colorkey(BLACK)
        self.image = self.image_ori.copy()
        self.rect = self.image.get_rect()
        self.radius = int(self.rect.width * 0.85 / 2)
        # 隨機選擇從地圖的哪個位置出現
        self.position = random.randrange(0, 4)
        # 地圖上方
        if self.position == 0:
            self.rect.x = random.randrange(0, WIDTH - self.rect.width)
            self.rect.y = random.randrange(-180, -100)
            self.speedy = random.randrange(2, 5)
            self.speedx = random.randrange(-3, 3)
            self.total_degree = 0
            self.rot_degree = random.randrange(-3, 3)
        # 地圖下方
        if self.position == 1:
            self.rect.x = random.randrange(0, WIDTH - self.rect.width)
            self.rect.y = random.randrange(700, 780)
            self.speedy = random.randrange(-5, -2)
            self.speedx = random.randrange(-3, 3)
            self.total_degree = 0
            self.rot_degree = random.randrange(-3, 3)
        # 地圖左方
        if self.position == 2:
            self.rect.x = random.randrange(-180, -100)
            self.rect.y = random.randrange(0, HEIGHT - self.rect.height)
            self.speedy = random.randrange(-3, 3)
            self.speedx = random.randrange(2, 5)
            self.total_degree = 0
            self.rot_degree = random.randrange(-3, 3)
        # 地圖右方
        if self.position == 3:
            self.rect.x = random.randrange(700, 780)
            self.rect.y = random.randrange(0, HEIGHT - self.rect.height)
            self.speedy = random.randrange(-3, 3)
            self.speedx = random.randrange(-5, -2)
            self.total_degree = 0
            self.rot_degree = random.randrange(-3, 3)

    def rotate(self):
        self.total_degree += self.rot_degree
        self.total_degree = self.total_degree % 360
        self.image = pygame.transform.rotate(self.image_ori, self.total_degree)
        center = self.rect.center
        self.rect = self.image.get_rect()
        self.rect.center = center

    def update(self):
        self.rotate()
        self.rect.y += self.speedy
        self.rect.x += self.speedx
        if self.position == 0:
            if self.rect.top > HEIGHT or self.rect.left > WIDTH or self.rect.right < 0:
                self.rect.x = random.randrange(0, WIDTH - self.rect.width)
                self.rect.y = random.randrange(-100, -40)
                self.speedy = random.randrange(2, 5)
                self.speedx = random.randrange(-3, 3)
        if self.position == 1:
            if self.rect.bottom < 0 or self.rect.left > WIDTH or self.rect.right < 0:
                self.rect.x = random.randrange(0, WIDTH - self.rect.width)
                self.rect.y = random.randrange(700, 780)
                self.speedy = random.randrange(-5, -2)
                self.speedx = random.randrange(-3, 3)
        if self.position == 3:
            if self.rect.right < 0 or self.rect.top < 0 or self.rect.bottom > HEIGHT:
                self.rect.x = random.randrange(700, 780)
                self.rect.y = random.randrange(0, HEIGHT - self.rect.height)
                self.speedy = random.randrange(-3, 3)
                self.speedx = random.randrange(-5, -2)
        if self.position == 2:
            if self.rect.left > WIDTH or self.rect.top < 0 or self.rect.bottom > HEIGHT:
                self.rect.x = random.randrange(-180, -100)
                self.rect.y = random.randrange(0, HEIGHT - self.rect.height)
                self.speedy = random.randrange(-3, 3)
                self.speedx = random.randrange(2, 5)

class Explosion(pygame.sprite.Sprite):
    def __init__(self, center, size):
        pygame.sprite.Sprite.__init__(self)
        self.size = size
        self.image = expl_anim[self.size][0]
        self.rect = self.image.get_rect()
        self.rect.center = center
        self.frame = 0
        self.last_update = pygame.time.get_ticks()
        self.frame_rate = 50

    def update(self):
        now = pygame.time.get_ticks()
        if now - self.last_update > self.frame_rate:
            self.last_update = now
            self.frame += 1
            if self.frame == len(expl_anim[self.size]):
                self.kill()
            else:
                self.image = expl_anim[self.size][self.frame]
                center = self.rect.center
                self.rect = self.image.get_rect()
                self.rect.center = center

def draw_win():
    screen.blit(background_img, (0, 0))
    draw_text(screen, 'YOU WIN！', 70, WIDTH / 2, HEIGHT / 5)
    draw_text(screen, '按esc鍵離開遊戲!', 18, WIDTH / 2, HEIGHT * 4 / 5)
    pygame.display.update()
    waiting1 = True
    while waiting1:
        clock.tick(FPS)
        # 取得輸入
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return True
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    waiting1 = False
                    return False
def draw_lose():
    screen.blit(background_img, (0, 0))
    draw_text(screen, 'YOU LOSE！', 70, WIDTH / 2, HEIGHT / 5)
    draw_text(screen, '按esc鍵離開遊戲!', 18, WIDTH / 2, HEIGHT * 4 / 5)
    pygame.display.update()
    waiting2 = True
    while waiting2:
        clock.tick(FPS)
        # 取得輸入
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                return True
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    waiting2 = False
                    return False
pygame.mixer.music.play(-1)

# 遊戲迴圈
end_result = 0
show_init = True
running = True
pause = False
show_lose = False
show_win = False
while running:
    if show_init:
        close = draw_init()
        if close:
            break
        show_init = False
        all_sprites = pygame.sprite.Group()
        money = pygame.sprite.Group()
        virus = pygame.sprite.Group()
        bullets = pygame.sprite.Group()
        powers = pygame.sprite.Group()
        bollets_sibling = pygame.sprite.Group()
        player = Player()
        all_sprites.add(player)
        for i in range(4):
            new_money()
        for j in range(6):
            new_virus()
        score = 0

    clock.tick(FPS)
    # 取得輸入
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            # pygame.mixer.music.stop()
            running = False
        if player.lives == 0 and not(death_expl.alive()):
            # pygame.mixer.music.stop()
            show_lose = True
            #running = False
            # money_end_result += 0
        # adjust this for different difficulty levels
        if score >= 5000:
            # pygame.mixer.music.stop()
            #running = False
            end_result += 1
            show_win = True
        if pause == False and event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                pause = True
        elif pause == True and event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:
                pause = False

    # 更新遊戲
    all_sprites.update()
    # 判斷金錢 人物相撞
    hits = pygame.sprite.spritecollide(
        player, money, True, pygame.sprite.collide_circle)
    for hit in hits:
        gun_sound.play()
        new_money()
        if hit.radius == 8:
            score += 100
        elif hit.radius == 12:
            score += 300
        elif hit.radius == 17:
            score += 500

    # 判斷病毒 人物相撞
    hits = pygame.sprite.spritecollide(
        player, virus, True, pygame.sprite.collide_circle)
    for hit in hits:
        expl0_sound.play()
        new_virus()
        player.health -= hit.radius
        expl = Explosion(hit.rect.center, 'sm')
        all_sprites.add(expl)
        if player.health <= 0:
            death_expl = Explosion(player.rect.center, 'player')
            all_sprites.add(death_expl)
            die_sound.play()
            player.lives -= 1
            player.health = 100
            player.hide()

    # 畫面顯示
    screen.fill(BLACK)
    screen.blit(background_img, (0, 0))
    all_sprites.draw(screen)
    draw_text(screen, "目前分數：", 18, WIDTH / 2 - 35, 10)
    draw_text(screen, str(score), 18, WIDTH / 2 + 35, 10)
    draw_health(screen, player.health, 5, 15)
    draw_lives(screen, player.lives, life_img, WIDTH - 100, 15)
    pygame.display.update()

    if show_win :
        close = draw_win()
        if close:
            break
        show_win = False
        running = False

    if show_lose:
        close = draw_lose()
        if close:
            break
        show_lose = False
        running = False

# pygame.quit()


import numpy as np
import torch
import torch.nn as nn
import os
from torch.autograd import Variable
#from util.image_pool import ImagePool
# BaseModel, save_network & load_network
from .base_model import BaseModel
from . import networks

class OurModel(BaseModel):
    def name(self):
        return 'OurModel'

    def get_num_params(self):
        return self.num_params_G, self.num_params_D

    def init_loss_filter(self, use_gan_feat_loss, use_vgg_loss):
        flags = (True, True, True, True, use_gan_feat_loss, use_vgg_loss, True, True, True)
        def loss_filter(g_gan, g_coarse_l1, g_out_l1, g_style, g_gan_feat, g_vgg, g_tv, d_real, d_fake):
            return [l for (l, f) in zip((g_gan, g_coarse_l1, g_out_l1, g_style, g_gan_feat, g_vgg, g_tv, d_real, d_fake), flags) if f]
        return loss_filter

    def initialize(self, opt):
        BaseModel.initialize(self, opt)
        self.isTrain = opt.isTrain
        input_nc = opt.input_nc ### masked_img + mask + seg (RGB + gray + Seg) 7 channels

        ### define networks
        # Generator
        netG_input_nc = input_nc
        self.netG, self.num_params_G = networks.define_G(netG_input_nc, opt.output_nc, ngf=64, gpu_ids=self.gpu_ids)

        # Discriminator
        if self.isTrain:
            use_sigmoid = opt.no_lsgan
            netD_input_nc = opt.output_nc * 2
            self.netD, self.num_params_D = networks.define_D(netD_input_nc, opt.ndf, opt.n_layers_D, opt.norm, use_sigmoid, opt.num_D, not opt.no_ganFeat_loss, gpu_ids=self.gpu_ids)
            # for param in self.netD.parameters():
            #     param.requires_grad = False
        else:
            self.num_params_D = 0

        print('-------------------- Networks initialized --------------------')

        ### load networks
        if not self.isTrain or opt.continue_train or opt.load_pretrain:
            pretrained_path = '' if not self.isTrain else opt.load_pretrain
            print(pretrained_path)

            self.load_network(self.netG, 'G', opt.which_epoch, pretrained_path)
            if self.isTrain:
                self.load_network(self.netD, 'D', opt.which_epoch, pretrained_path)

        ### set loss functions and optimizers
        if self.isTrain:
            if opt.pool_size > 0 and (len(self.gpu_ids)) > 1:
                raise NotImplementedError("Fake Pool Not Implemented for MultiGPU")
            # self.fake_pool = ImagePool(opt.pool_size)
            self.old_lr = opt.lr

            # define loss functions
            self.loss_filter = self.init_loss_filter(not opt.no_ganFeat_loss, not opt.no_vgg_loss)

            self.criterionGAN_D = networks.GANLoss_D_v2(use_lsgan=not opt.no_lsgan, tensor=self.Tensor)
            self.criterionGAN_G = networks.GANLoss_G_v2(use_lsgan=not opt.no_lsgan, tensor=self.Tensor)

            self.criterionFeat = torch.nn.L1Loss()
            if not opt.no_vgg_loss:
                self.criterionVGG = networks.VGGLoss(self.gpu_ids)
            self.criterionL1 = torch.nn.L1Loss()
            self.down = nn.UpsamplingBilinear2d(scale_factor=0.25)
            self.resize = nn.UpsamplingBilinear2d(size=(224, 224))
            self.criterionTV = networks.TVLoss()

            # Names so we can breakout loss
            self.loss_names = self.loss_filter('G_GAN', 'G_COARSE_L1', 'G_OUT_L1', 'G_STYLE', 'G_GAN_Feat', 'G_VGG', 'G_TV', 'D_real', 'D_fake')

            # initialize optimizers
            # optimizer G
            if opt.niter_fix_global > 0:
                import sys
                if sys.version_info >= (3,0):
                    finetune_list = set()
                else:
                    from sets import Set
                    finetune_list = Set()

                params_dict = dict(self.netG.named_parameters())
                params = []
                for key, value in params_dict.items():
                    if key.startswith('model' + str(opt.n_local_enhancers)):
                        params += [value]
                        finetune_list.add(key.split('.')[0])
                print('--------------- Only training the local enhancer network (for %d epochs) ---------------' % opt.niter_fix_global)
                print('The layers that are finetuned are ', sorted(finetune_list))
            else:
                params = list(self.netG.parameters())
            self.optimizer_G = torch.optim.Adam(params, lr=opt.lr, betas=(opt.beta1, 0.999))

            # optimizer D
            params = list(self.netD.parameters())
            self.optimizer_D = torch.optim.Adam(params, lr=opt.lr * 4.0, betas=(opt.beta1, 0.999))

    def encode_input(self, masked_image, mask, real_image, real_seg, infer=False):
        input_msked_img = masked_image.data.cuda()
        input_msk = mask.data.cuda()

        input_msked_img = Variable(input_msked_img, volatile=infer)
        input_msk = Variable(input_msk, volatile=infer)

        real_image = Variable(real_image.data.cuda(), volatile=infer)
        real_seg = Variable(real_seg.data.cuda(), volatile=infer)

        return input_msked_img, input_msk, real_image, real_seg

    def encode_input_test(self, masked_image, mask, real_seg, infer=False):
        input_msked_img = masked_image.data.cuda()
        input_msk = mask.data.cuda()
        input_seg = real_seg.data.cuda()

        input_msked_img = Variable(input_msked_img)
        input_msk = Variable(input_msk)
        input_seg = Variable(input_seg)

        return input_msked_img, input_msk, input_seg

    def discriminate(self, input_image, test_image, use_pool=False):
        input_concat = torch.cat((input_image, test_image.detach()), dim=1)
        return self.netD.forward(input_concat)

    def forward(self, masked_img, mask, real_img=None, real_seg=None, infer=False, mode=None):
        # inference
        if mode == 'inference':
            compltd_img, reconst_img, lr_x = self.inference(masked_img, mask, real_seg)
            return compltd_img, reconst_img, lr_x

        # Encode inputs
        input_msked_img, input_msk, real_image, real_seg = self.encode_input(masked_img, mask, real_img, real_seg)

        # Fake Generation
        compltd_img, reconst_img, lr_x, lr_img = self.netG.forward(input_msked_img, input_msk, real_image, real_seg)
        lr_msk = input_msk

        msk025 = self.down(input_msk)
        img025 = self.down(real_image)

        # Fake Detection and Loss
        pred_fake = self.discriminate(input_msked_img, compltd_img)
        loss_D_fake = self.criterionGAN_D(pred_fake, False)

        # Real Detection and Loss
        pred_real = self.discriminate(input_msked_img, real_image)
        loss_D_real = self.criterionGAN_D(pred_real, True)

        # GAN Loss (Fake pass-ability loss)
        pred_fake = self.netD.forward(torch.cat((input_msked_img, compltd_img), dim=1))
        loss_G_GAN = self.criterionGAN_G(pred_fake, True)
        loss_G_GAN *= self.opt.lambda_gan

        # GAN L1 Loss
        loss_G_GAN_L1 = self.criterionL1(reconst_img * input_msk, real_image * input_msk) * self.opt.lambda_l1
        loss_G_GAN_L1 += self.criterionL1(reconst_img * (1 - input_msk), real_image * (1 - input_msk))
        loss_G_GAN_L1 += self.criterionL1(lr_img * msk025, img025 * msk025) * self.opt.lambda_l1
        loss_G_GAN_L1 += self.criterionL1(lr_img * (1 - msk025), img025 * (1 - msk025))

        loss_G_COARSE_L1 = self.criterionL1(lr_x * lr_msk, real_image * lr_msk) * self.opt.lambda_l1
        loss_G_COARSE_L1 += self.criterionL1(lr_x * (1 - lr_msk), real_image * (1 - lr_msk))

        loss_G_TV = self.criterionTV(compltd_img) * self.opt.lambda_tv

        # GAN feature matching loss
        loss_G_GAN_Feat = 0
        if not self.opt.no_ganFeat_loss:
            feat_weights = 4.0 / (self.opt.n_layers_D + 1)
            D_weights = 1.0 / self.opt.num_D
            for i in range(self.opt.num_D):
                for j in range(len(pred_fake[i]) - 1):
                    loss_G_GAN_Feat += D_weights * feat_weights * self.criterionFeat(pred_fake[i][j], pred_real[i][j].detach())
        loss_G_GAN_Feat *= self.opt.lambda_feat

        # VGG feature matching loss
        loss_G_VGG = 0
        if not self.opt.no_vgg_loss:
            resized_reconst_img = self.resize(reconst_img)
            resized_compltd_img = self.resize(compltd_img)
            resized_real_img = self.resize(real_image)
            loss_G_VGG, loss_G_VGGStyle = self.criterionVGG(resized_reconst_img, resized_real_img)
            loss_G_VGG2, loss_G_VGGStyle2 = self.criterionVGG(resized_compltd_img, resized_real_img)
        loss_G_VGG += loss_G_VGG2
        loss_G_VGGStyle += loss_G_VGGStyle2
        loss_G_VGG *= self.opt.lambda_vgg
        loss_G_VGGStyle *= self.opt.lambda_style

        # only return the fake image if necessary
        return [ self.loss_filter( loss_G_GAN, loss_G_COARSE_L1, loss_G_GAN_L1, loss_G_VGGStyle, loss_G_GAN_Feat, loss_G_VGG, loss_G_TV, loss_D_real, loss_D_fake ), None if not infer else compltd_img, reconst_img, lr_x ]

    def inference(self, masked_img, mask, real_seg):
        # Encode inputs
        input_msked_img, input_msk, input_seg = self.encode_input_test(Variable(masked_img), Variable(mask), Variable(real_seg), infer=True)

        # Fake Generation
        if torch.__version__.startswith('0.4'):
            with torch.no_grad():
                compltd_img, reconst_img, lr_x, lr_img = self.netG.forward(input_msked_img, input_msk, real_seg=input_seg)
        else:
            compltd_img, reconst_img, lr_x, lr_img = self.netG.forward(input_msked_img, input_msk, real_seg=input_seg)
        return compltd_img, reconst_img, lr_x

    def save(self, which_epoch):
        self.save_network(self.netG, 'G', which_epoch, self.gpu_ids)
        self.save_network(self.netD, 'D', which_epoch, self.gpu_ids)

    def update_fixed_params(self):
        # after fixing the global generator for a # of iterations, also start finetuning it
        params = list(self.netG.parameters())
        self.optimizer_G = torch.optim.Adam(params, lr=self.opt.lr, betas=(self.opt.beta1, 0.999))
        print('----------------- Now also finetuning global generator -----------------')

    def update_learning_rate(self):
        lrd = self.opt.lr / self.opt.niter_decay
        lr = self.old_lr - lrd
        for param_group in self.optimizer_D.param_groups:
            param_group['lr'] = lr * 4.0
        for param_group in self.optimizer_G.param_groups:
            param_group['lr'] = lr
        print('update learning rate: %f -> %f' % (self.old_lr, lr))
        self.old_lr = lr

class InferenceModel(OurModel):
    def forward(self, inp1, inp2, inp3):
        masked_img = inp1
        mask = inp2
        seg = inp3
        return self.inference(masked_img, mask, seg)

2020ECCV-DeepGIN Deep Generative Inpainting Network for Extreme Image Inpainting/Task2-Pytorch/models/our_model.py


import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt, cm
from sklearn.metrics import roc_curve, roc_auc_score

def plot_feature_scatter(df1, df2, features):
    sns.set_style('whitegrid')
    plt.figure()
    fig, ax = plt.subplots(4, 4, figsize=(14, 14))

    i = 0
    for feature in features:
        i += 1
        plt.subplot(4, 4, i)
        plt.scatter(df1[feature], df2[feature], marker='+')
        plt.xlabel(feature, fontsize=9)
    plt.show()

def plot_feature_distribution(df1, df2, label1, label2, features):
    sns.set_style('whitegrid')
    plt.figure()
    fig, ax = plt.subplots(5, 4, figsize=(18, 18))

    i = 0
    for feature in features:
        i += 1
        plt.subplot(5, 4, i)
        sns.kdeplot(df1[feature], bw=0.5, label=label1)
        sns.kdeplot(df2[feature], bw=0.5, label=label2)
        plt.xlabel(feature, fontsize=9)
        locs, labels = plt.xticks()
        plt.tick_params(axis='x', which='major', labelsize=6, pad=-6)
        plt.tick_params(axis='y', which='major', labelsize=6)
    plt.show()

def plot_new_feature_distribution(df1, df2, label1, label2, features):
    sns.set_style('whitegrid')
    plt.figure()
    fig, ax = plt.subplots(2, 4, figsize=(18, 22))

    i = 0
    for feature in features:
        i += 1
        plt.subplot(2, 4, i)
        sns.kdeplot(df1[feature], bw=0.5, label=label1)
        sns.kdeplot(df2[feature], bw=0.5, label=label2)
        plt.xlabel(feature, fontsize=11)
        locs, labels = plt.xticks()
        plt.tick_params(axis='x', which='major', labelsize=8)
        plt.tick_params(axis='y', which='major', labelsize=8)
    plt.show()

def plot_roc_curve(results):
    colors = iter(cm.rainbow(np.linspace(0, 1, len(results))))
    fig = plt.figure()
    plt.plot([0, 1], [0, 1], linestyle='--')
    for (model_name, y, y_pred) in results:
        fpr, tpr, thresholds = roc_curve(y, y_pred)
        auc = round(roc_auc_score(y, y_pred), 4)
        plt.plot(fpr, tpr, label=f"{model_name} AUC: {auc}", c=next(colors))
    plt.legend(loc="lower right")
    plt.show()
    fig.savefig('roc-comparison.png')


#!/usr/bin/python
# -*- codding: utf-8 -*-
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
from common.execute_command import write_three_parameter

# url : https://awscli.amazonaws.com/v2/documentation/api/latest/reference/quicksight/update-data-source.html
if __name__ == '__main__':
    """
	create-data-source : https://awscli.amazonaws.com/v2/documentation/api/latest/reference/quicksight/create-data-source.html
	delete-data-source : https://awscli.amazonaws.com/v2/documentation/api/latest/reference/quicksight/delete-data-source.html
	describe-data-source : https://awscli.amazonaws.com/v2/documentation/api/latest/reference/quicksight/describe-data-source.html
	list-data-sources : https://awscli.amazonaws.com/v2/documentation/api/latest/reference/quicksight/list-data-sources.html
    """

    parameter_display_string = """
    # aws-account-id : The AWS account ID.
    # data-source-id : The ID of the data source. This ID is unique per AWS Region for each AWS account.
    # name : A display name for the data source.
    """
    add_option_dict = {}
    add_option_dict["parameter_display_string"] = parameter_display_string
    # ex: add_option_dict["no_value_parameter_list"] = "--single-parameter"
    write_three_parameter("quicksight", "update-data-source", "aws-account-id", "data-source-id", "name", add_option_dict)


##########################################################################
#
#  Copyright (c) 2020, Cinesite VFX Ltd. All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are
#  met:
#
#      * Redistributions of source code must retain the above
#        copyright notice, this list of conditions and the following
#        disclaimer.
#
#      * Redistributions in binary form must reproduce the above
#        copyright notice, this list of conditions and the following
#        disclaimer in the documentation and/or other materials provided with
#        the distribution.
#
#      * Neither the name of John Haddon nor the names of
#        any other contributors to this software may be used to endorse or
#        promote products derived from this software without specific prior
#        written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
#  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
#  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
#  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
##########################################################################

import functools
import sys
import traceback

import imath

from collections import OrderedDict, namedtuple

import IECore
import IECoreScene

import Gaffer
import GafferUI
import GafferScene
import GafferSceneUI

from Qt import QtCore
from Qt import QtGui
from Qt import QtWidgets

__all__ = [ "_SceneViewInspector", "_ParameterInspector" ]

# Conceptually this is an embedded context-sensitive SceneInspector for
# the SceneView. In practice it is implemented completely separately from
# the SceneInspector though, because the SceneInspector doesn't yet support
# editing and/or EditScopes. Our intention is to unify the two so that the
# SceneInspector gains editing features and this can become just a thin
# wrapper to do the configuration and embedding for the Viewer. We also
# intend to move many of the components into GafferUI, so that they can
# be used in the development of an ImageInspector too.
#
# Future development should consider the following :
#
# - It may be better for the inspector classes to track their
#   own dirtiness, rather than rely on the outer code for that.
#   And it may be better for each `_ParameterWidget/DiffRow` to manage
#   its own background updates based on inspector dirtiness.
#   This would allow individual widgets to be used standalone in
#   any other UI.
# - Moving tracking and updates to individual widgets/inspectors
#   would make it harder to share work. All parameter inspectors
#   need the same `AttributeHistory` to work from, and we don't
#   want to compute that more than once. The SceneInspector uses
#   an inspector hierarchy, so maybe this would allow us to cache
#   shared values on a parent inspector?
# - We want to extend our inspection/editing features into the
#   HierarchyView and a new LightEditor panel. This will likely
#   mean coupling inspectors with PathListingWidget somehow. Or
#   perhaps we should be using Path properties to provide values
#   for all UIs, and the editing functionality should be provided
#   separately?
# - It's not clear how the SceneInspector's `Target` class fits
#   into a general scheme that could include images, because it
#   contains scene-specific data. Perhaps we should ditch
#   `Target` entirely, and instead say that inspectors always
#   operate on the plug they are constructed with and in the
#   Context they are invoked in.
#

# \todo Create a central renderer/attribute registry that we can
# query for this information, this is also duplicated in EditScopeAlgo.cpp
_rendererAttributePrefixes = {
	"ai" : "Arnold",
	"dl" : "Delight",
	"as" : "Appleseed",
	"gl" : "OpenGL",
	"osl" : "OSL",
	"ccl" : "Cycles"
};

__registeredShaderParameters = OrderedDict()

##########################################################################
# Shader Parameter Registration API
##########################################################################

def registerShaderParameter( attribute, parameter ) :

	__registeredShaderParameters.setdefault( attribute, [] ).append( parameter )

def deregisterShaderParameter( attribute, parameter ) :

	try :
		__registeredShaderParameters[ attribute ].remove( parameter )
	except :
		pass

def _registeredShaderAttributes() :

	return [ a for a in __registeredShaderParameters.keys() if __registeredShaderParameters[ a ] ]

def _registeredShaderParameters( attribute ) :

	return __registeredShaderParameters[ attribute ]

##########################################################################
# _SceneViewInspector
##########################################################################

class _SceneViewInspector( GafferUI.Widget ) :

	def __init__( self, sceneView ) :

		self.__frame = GafferUI.Frame( borderWidth = 4 )
		GafferUI.Widget.__init__( self, self.__frame )

		self.__attachToView( sceneView )

		with self.__frame :

			with GafferUI.ListContainer( spacing = 8 ) :

				with GafferUI.ListContainer(
					orientation = GafferUI.ListContainer.Orientation.Horizontal,
					spacing = 8
				) :
					GafferUI.Spacer( imath.V2i( 12 ), imath.V2i( 12 ) ) # hideButton
					GafferUI.Spacer( imath.V2i( 20 ), imath.V2i( 20 ) ) # BusyWidget
					GafferUI.Spacer( imath.V2i( 1 ) )
					GafferUI.Label( "
Inspector
" )
					GafferUI.Spacer( imath.V2i( 1 ) )
					self.__busyWidget = GafferUI.BusyWidget( size = 20, busy = False )
					hideButton = GafferUI.Button( image="deleteSmall.png", hasFrame=False )
					hideButton.clickedSignal().connect( Gaffer.WeakMethod( self.__closeButtonClicked ), scoped = False )

				with GafferUI.ScrolledContainer( horizontalMode = GafferUI.ScrollMode.Never ) :
					with GafferUI.ListContainer( spacing = 20 ) :
						self.__groups = { a : _ParameterGroup(a) for a in _registeredShaderAttributes() }

		# The on/off state of the Inspector is managed by setVisible, which also disables lazy updates when
		# we're turned off. We desire to hide ourselves when we have nothing to show, but still need background
		# updates so we can re-appear as needed. We achieve this by managing the visibility of our frame,
		# independently of the widget.
		self.__frame.setVisible( False )

		self.keyPressSignal().connect( Gaffer.WeakMethod( self.__keyPress ), scoped = False)

		Gaffer.Metadata.nodeValueChangedSignal().connect( Gaffer.WeakMethod( self.__nodeMetadataChanged ), scoped = False )
		Gaffer.Metadata.plugValueChangedSignal().connect( Gaffer.WeakMethod( self.__plugMetadataChanged ), scoped = False )

	def setVisible( self, visible ) :

		# The view.inspector.visible plug is the authority on whether the
		# inspector should be visible. setVisible may be called by our parent
		# layout as its visibility changes. We need to ensure we don't appear
		# if the user has disabled us. Considering the plug state here allows
		# allows us to take advantage of @lazymethod deferring updates whilst
		# we have been turned off.
		if visible == self.__sceneView["inspector"]["visible"].getValue() :
			GafferUI.Widget.setVisible( self, visible )
			if visible :
				self.__updateLazily()
		else :
			GafferUI.Widget.setVisible( self, False )

	def __attachToView( self, sceneView ) :

		# We add plugs to manage our visibility as per grid/gnomon. This plug is the
		# authoritative source for visibility, and is checked in setVisible.
		sceneView.addChild( Gaffer.ValuePlug( "inspector" ) )
		sceneView["inspector"].addChild( Gaffer.BoolPlug( "visible", Gaffer.Plug.Direction.In, True ) )
		Gaffer.NodeAlgo.applyUserDefaults( sceneView["inspector"] )

		sceneView.plugDirtiedSignal().connect( Gaffer.WeakMethod( self.__plugDirtied ), scoped = False )
		sceneView.getContext().changedSignal().connect( Gaffer.WeakMethod( self.__contextChanged ), scoped = False )
		sceneView.viewportGadget().keyPressSignal().connect( Gaffer.WeakMethod( self.__keyPress ), scoped = False )

		self.__sceneView = sceneView

	def __closeButtonClicked( self, *unused ) :

		self.__sceneView["inspector"]["visible"].setValue( False )

	def __plugDirtied( self, plug ) :

		if plug in ( self.__sceneView["in"]["attributes"], self.__sceneView["editScope"] ) :
			self.__updateLazily()
		elif plug.isSame( self.__sceneView["inspector"]["visible"] ) :
			self.setVisible( plug.getValue() )

	def __contextChanged( self, context, name ) :

		if GafferSceneUI.ContextAlgo.affectsSelectedPaths( name ) or not name.startswith( "ui:" ) :
			self.__updateLazily()

	def __plugMetadataChanged( self, typeId, plugPath, key, plug  ) :

		# As we don't fully understand which nodes/plugs affect our ability to edit (as the network
		# inside the edit scope is effectively opaque to us), we only need to know if the plugs
		# node was affected.
		self.__nodeMetadataChanged( typeId, key, plug.node() if plug is not None else None )

	def __nodeMetadataChanged( self, typeId, key, node ) :

		# Early out with an easy comparison if we can
		if not Gaffer.MetadataAlgo.readOnlyAffectedByChange( key ) :
			return

		scope = self.__sceneView.editScope()
		if scope is None :
			# The ability to add an edit is only enabled if we have an edit scope, in all other
			# cases we always show plugs, whether they're read only or not.
			return

		if Gaffer.MetadataAlgo.readOnlyAffectedByChange( scope, typeId, key, node ) or scope.isAncestorOf( node ) :
			self.__updateLazily()

	def __keyPress( self, gadget, event ) :

		if event.key == "I" and event.modifiers == GafferUI.ModifiableEvent.Modifiers.None_ :
			visible = self.__sceneView["inspector"]["visible"].getValue()
			self.__sceneView["inspector"]["visible"].setValue( not visible )
			return True

		return False

	@GafferUI.LazyMethod()
	def __updateLazily( self ) :

		with self.__sceneView.getContext() :
			self.__backgroundUpdate()

	@GafferUI.BackgroundMethod()
	def __backgroundUpdate( self ) :

		selectedPaths = GafferSceneUI.ContextAlgo.getSelectedPaths( Gaffer.Context.current() )

		parameterInspectors = {}

		for path in selectedPaths.paths() :
			if not self.__sceneView["in"].exists( path ) :
				continue
			history = GafferScene.SceneAlgo.history( self.__sceneView["in"]["attributes"], path )
			for attribute, group in self.__groups.items() :
				attributeHistory = GafferScene.SceneAlgo.attributeHistory( history, attribute )
				if attributeHistory is not None :
					for parameter in group.parameters() :
						parameterInspectors.setdefault( attribute, {} ).setdefault( parameter, [] ).append(
							_ParameterInspector( attributeHistory, parameter, self.__sceneView.editScope() )
						)

		return parameterInspectors

	@__backgroundUpdate.plug
	def __backgroundUpdatePlug( self ) :

		return self.__sceneView["in"]

	@__backgroundUpdate.preCall
	def __backgroundUpdatePreCall( self ) :

		self.__busyWidget.setBusy( True )

	@__backgroundUpdate.postCall
	def __backgroundUpdatePostCall( self, backgroundResult ) :

		if isinstance( backgroundResult, IECore.Cancelled ) or backgroundResult is None :
			# Cancellation or some other exception
			self.__updateLazily()
		elif isinstance( backgroundResult, Gaffer.ProcessException ) :
			# Computation error. Rest of the UI can deal with
			# displaying this.
			self.__frame.setVisible( False )
		elif isinstance( backgroundResult, Exception ) :
			# Possible error in our code.
			IECore.msg(
				IECore.Msg.Level.Error, "_SceneViewInspector",
				"".join( traceback.format_exception_only( type( backgroundResult ), backgroundResult ) )
			)
		else :
			# Success.
			visible = False
			for attribute, group in self.__groups.items() :
				visible = group.update( backgroundResult.get( attribute, {} ) ) or visible
			self.__frame.setVisible( visible )

		self.__busyWidget.setBusy( False )

# \todo Check how this relates to DiffColumn in the SceneInspector
class _ParameterGroup( GafferUI.ListContainer ) :

	def __init__( self, attribute, **kwargs ) :

		GafferUI.ListContainer.__init__( self, spacing = 8, **kwargs )
		self.__attribute = attribute
		self.__widgets = {}

		with self :

			self.__label = GafferUI.Label( self.__attributeLabel( attribute ) )

			GafferUI.Divider()

			for parameter in _registeredShaderParameters( self.__attribute ) :
				self.__widgets[ parameter ] = _ParameterWidget( parameter )

	def parameters( self ) :

		return self.__widgets.keys()

	def update( self, parameterInspectors ) :

		numValues = 0

		for parameter, inspectors in parameterInspectors.items() :
			self.__widgets[ parameter ].update( inspectors )
			numValues = max( numValues, len( [ i for i in inspectors if i.value() is not None ] ) )

		visible = numValues > 0

		self.setVisible( visible )
		if visible :
			self.__label.setText( "{} {}{}".format(
				numValues,
				self.__attributeLabel( self.__attribute ),
				"s" if numValues != 1 else ""
			) )

		return visible

	def __attributeLabel( self, attribute ) :

		prefix, name = attribute.split( ":", 1 )
		prefix = _rendererAttributePrefixes.get( prefix, prefix )
		name = " ".join( [ IECore.CamelCase.toSpaced( n ) for n in name.split( ":" ) ] )
		return "{} {}".format( prefix, name )

# EditType defines discrete parameter edit types. The inspector UI assumes that all
# locations with the same edit type can be edited simultaneously using a single
# PlugValueWidget set to hold all applicable plugs.
_EditType = IECore.Enum.create( "Source", "Tweak" )

# Defines a (potential) edit for a single location.
#  - acquireEdit [callable] will return the plug that performs the edit, creating it if necessary.
#  - editType [_EditType] the type of edit (or potential edit if one doesn't exist yet) facilitated by the plug.
#  - inEditScope [Bool] True if the node affecting the edit is a child of an EditScope.
#  - hasEdit [Bool] True if the plugs for the edit already exist, and in the case of tweaks,
#       if they are enabled.
_EditInfo = namedtuple( "_EditInfo", [ "acquireEdit", "editType", "inEditScope", "hasEdit" ] )

## \todo Figure out how this relates to the inspectors in the SceneInspector.
## \see _EditInfo for the meaning of the various accessors.
class _ParameterInspector( object ) :

	def __init__( self, attributeHistory, parameter, editScope ) :

		self.__parameter = parameter
		self.__errorMessage = None
		self.__warningMessages = []

		shader = attributeHistory.attributeValue.outputShader()
		self.__value = shader.parameters.get( parameter )
		if self.__value is not None :
			if hasattr( self.__value, "value" ) :
				self.__value = self.__value.value
			self.__editInfo = self.__findEdit( attributeHistory, editScope )
		else :
			self.__editInfo = None

	def value( self ) :

		return self.__value

	def editable( self ) :

		return self.__editInfo is not None

	def acquireEdit( self ) :

		return self.__editInfo.acquireEdit() if self.__editInfo else None

	def editType( self ) :

		return self.__editInfo.editType  if self.__editInfo else None

	def hasEdit( self ) :

		return self.__editInfo.hasEdit if self.__editInfo else None

	def inEditScope( self ) :

		return self.__editInfo.inEditScope if self.__editInfo else False

	def warningMessage( self ) :

		return "\n".join( self.__warningMessages )

	def errorMessage( self ) :

		return self.__errorMessage if self.__errorMessage is not None else ""

	## \todo This is a modified copy of TransformTool::spreadsheetAwareSource,
	# and is eerily similar to Dispatcher::computedSource and others. This needs
	# factoring out into some general contextAwareSource method that can be
	# shared by all these.
	# In this case, we also need to make sure we never return an output plug,
	# such as when it is connected to an anim curve.
	def __spreadsheetAwareSource( self, plug ) :

		source = plug.source()
		spreadsheet = source.node()

		if not isinstance( spreadsheet, Gaffer.Spreadsheet ) :
			return sourceInput( plug )

		if not spreadsheet["out"].isAncestorOf( source ) :
			return sourceInput( plug )

		valuePlug = spreadsheet.activeInPlug( source )
		if valuePlug.ancestor( Gaffer.Spreadsheet.RowPlug ).isSame( spreadsheet["rows"].defaultRow() ) :
			return None

		return sourceInput( valuePlug )

	def __sourceShader( self, plug ) :

		node = plug.source().node()
		if isinstance( node, Gaffer.Switch ) :
			node = node.activeInPlug().source().node()
		return node if isinstance( node, GafferScene.Shader ) else None

	def __editFromSceneNode( self, attributeHistory ) :

		node = attributeHistory.scene.node()
		if not isinstance( node, ( GafferScene.ShaderTweaks, GafferScene.Light, GafferScene.LightFilter, GafferScene.ShaderAssignment ) ) :
			return None

		if attributeHistory.scene != node["out"] :
			return None

		with attributeHistory.context :

			if not node["enabled"].getValue() :
				return None

			if "filter" in node and not ( node["filter"].match( attributeHistory.scene ) & IECore.PathMatcher.Result.ExactMatch ) :
				return None

			if isinstance( node, ( GafferScene.Light, GafferScene.LightFilter ) ) :

				plug = self.__spreadsheetAwareSource( node["parameters"][ self.__parameter ] )
				if plug is not None :
					return _EditInfo(
						acquireEdit = lambda : plug,
						editType = _EditType.Source,
						inEditScope = node.ancestor( Gaffer.EditScope ) is not None,
						hasEdit = True
					)
				return None

			elif isinstance( node, GafferScene.ShaderAssignment ) :

				# \todo use `computedSource` or similar when we have it to consider switches, etc...
				shader = self.__sourceShader( node["shader"] )
				if shader is not None :
					plug = shader["parameters"].getChild( self.__parameter )
					if plug is not None :
						return _EditInfo(
							acquireEdit = lambda : plug,
							editType = _EditType.Source,
							inEditScope = node.ancestor( Gaffer.EditScope ) is not None,
							hasEdit = True
						)
				return None

			elif isinstance( node, GafferScene.ShaderTweaks ) :

				for tweak in node["tweaks"] :
					if tweak["name"].getValue() == self.__parameter :
						tweak = self.__spreadsheetAwareSource( tweak )
						if tweak is not None :
							return _EditInfo(
								acquireEdit = lambda : tweak,
								editType = _EditType.Tweak,
								inEditScope = node.ancestor( Gaffer.EditScope ) is not None,
								hasEdit = tweak["enabled"].getValue()
							)
						return None

		return None

	def __editFromEditScope( self, attributeHistory ) :

		editScope = attributeHistory.scene.node()

		# \todo This misses cases where the processor has been disabled.
		# Migrate to EditScopeAlgo functionality once it's available.
		with attributeHistory.context :

			if not editScope["enabled"].getValue() :
				self.__error( "The target Edit Scope '{}' is disabled.", editScope )
				return None

			readOnlyReason = GafferScene.EditScopeAlgo.parameterEditReadOnlyReason(
				editScope,
				attributeHistory.context["scene:path"],
				attributeHistory.attributeName,
				IECoreScene.ShaderNetwork.Parameter( "", self.__parameter )
			)
			if readOnlyReason is not None :
				# If we don't have an edit and the scope is locked, we error,
				# as we can't add an edit. Other cases where we already _have_
				# an edit will have been found by _editFromSceneNode).
				self.__error( "'{}' is locked.", readOnlyReason )
				return None

		def editScopeEdit( attributeHistory, parameter ) :

			with attributeHistory.context :
				return GafferScene.EditScopeAlgo.acquireParameterEdit(
					editScope,
					attributeHistory.context["scene:path"],
					attributeHistory.attributeName,
					IECoreScene.ShaderNetwork.Parameter( "", parameter )
				)

		return _EditInfo(
			acquireEdit = functools.partial( editScopeEdit, attributeHistory, self.__parameter ),
			editType = _EditType.Tweak,
			inEditScope = True,
			hasEdit = self.__editScopeHasEdit( attributeHistory )
		)

	def __editScopeHasEdit( self, attributeHistory ) :

		with attributeHistory.context :

			tweak = GafferScene.EditScopeAlgo.acquireParameterEdit(
				attributeHistory.scene.node(),
				attributeHistory.context["scene:path"],
				attributeHistory.attributeName,
				IECoreScene.ShaderNetwork.Parameter( "", self.__parameter ),
				createIfNecessary = False
			)

			if tweak is None :
				return False

			return tweak["enabled"].getValue()

	def __findEdit( self, attributeHistory, editScope ) :

		# Walk through the attributeHistory, looking for a suitable edit, based on the user's chosen editScope.

		node = attributeHistory.scene.node()

		if isinstance( node, Gaffer.EditScope ) and attributeHistory.scene == node["in"] :

			# We are leaving an EditScope. We consider EditScopes on the way out to allow other
			# nodes within the scope to take precedence. An existing edit in the scope will
			# be picked up via __editFromSceneNode, which is spreadsheet aware.
			if node.isSame( editScope ) :
				return self.__editFromEditScope( attributeHistory )

		else :

			# Some other node affecting a change, see if we understand it.

			edit = self.__editFromSceneNode( attributeHistory )
			if edit is not None :

				# We can potentially make use of this node

				# Check the first parent scope of the edit, as they may be nested
				parentEditScope = attributeHistory.scene.ancestor( Gaffer.EditScope )

				if edit.hasEdit and ( editScope is None or editScope.isSame( parentEditScope ) ) :
					# If no editScope has been specified, or we're inside the target edit scope,
					# then we can safely use this edit if we have one.
					# The hasEdit check is deliberate to ensure that we don't include 'potential' targets, such
					# as disabled tweak nodes. Our semantics for editScope == None is that we only consider nodes
					# actually making a change to the scene at the time.
					if edit.hasEdit and isinstance( node, GafferScene.ShaderAssignment ) :
						self.__warn( "Edits to '{}' may affect other locations in the scene.", edit.acquireEdit().node() )
					return edit

				elif edit.hasEdit and editScope is not None :
					# If an edit scope has been specified, but we're not inside of it, then we shouldn't
					# use this node. We are downstream of any potential edit though, so do however need
					# to warn people that any edits they do make may be overridden by this node.
					displayNode = node.ancestor( Gaffer.EditScope ) or node
					self.__warn( "Parameter has edits downstream in '{}'.", displayNode )

		# If we haven't found an edit here, walk up the history
		for p in attributeHistory.predecessors :
			edit = self.__findEdit( p, editScope )
			if edit is not None :
				return edit

		if editScope is not None and not self.__errorMessage:
			# If we've not been able to find an edit, and we have a scope
			# specified, then we musn't have passed through the edit scope
			self.__error( "The target Edit Scope '{}' is not in the scene history.", editScope )

		return None

	def __warn( self, message, node ) :

		self.__warningMessages.append( message.format( self.__displayName( node ) ) )

	def __error( self, message, node ) :

		self.__errorMessage = message.format( self.__displayName( node ) )
		# We clear out the warnings in the case of an error to prevent confusion in the UI
		self.__warningMessages = []

	@staticmethod
	def __displayName( node ) :

		return node.relativeName( node.ancestor( Gaffer.ScriptNode ) )

## \todo Figure out how this relates to the DiffRow in the SceneInspector.
class _ParameterWidget( GafferUI.Widget ) :

	def __init__( self, parameter ) :

		self.__parameter = parameter

		grid = GafferUI.GridContainer( spacing = 2 )
		GafferUI.Widget.__init__( self, grid )

		self.__inspectors = []

		with grid :

			label = GafferUI.Label(
				## \todo Prettify label text (remove snake case)
				text = "
" + IECore.CamelCase.toSpaced( parameter ) + "
",
				parenting = { "index" : ( slice( 0, 2 ), 0 ) }
			)
			label._qtWidget().setMaximumWidth( 140 )

			self.__editButton = GafferUI.Button( image = "editOff.png", hasFrame = False,
				parenting = {
					"index" : ( 0, 1 ),
					"alignment" : ( GafferUI.HorizontalAlignment.Center, GafferUI.VerticalAlignment.Center )
				}
			)
			self.__editButton.clickedSignal().connect( Gaffer.WeakMethod( self.__editButtonClicked ), scoped = False )

			self.__valueWidget = _ValueWidget( parenting = { "index" : ( 1, 1 ) } )
			self.__valueWidget.buttonReleaseSignal().connect( Gaffer.WeakMethod( self.__valueWidgetClicked ), scoped = False )

		self.update( [] )

	def update( self, inspectors ) :

		# The somewhat intricate logic here attempts to determine whether we should present the user
		# with an edit button for the parameter for the selected locations. This needs to consider:
		#
		# - Is the whole selection editable, we deliberately don't allow any edits, if any locations aren't.
		# - Is there only on type of edit. We can't concurrently edit tweaks and standard numeric node plugs.
		# - Do all locations currently have edits. We allow editing in this case, but only if we can create the edits.
		# - In 'Automatic' Edit Scope mode, is an existing edit in a scope or not.
		#
		# Based on this, we update the styling of the UI to attempt to clue the user into the current state.

		inspectors = [ i for i in inspectors if i.value() is not None ]

		# Only show something if we have any inspectors for this parameter
		self.setVisible( inspectors )

		# Display the current values
		self.__valueWidget.setValues( [ i.value() for i in inspectors ] )

		# We can only edit if all of them are editable
		self.__editable = bool( inspectors ) and all( i.editable() for i in inspectors )

		errors = [ i.errorMessage() for i in inspectors if i.errorMessage() ]
		warnings = [ i.warningMessage() for i in inspectors if i.warningMessage() ]

		# Ensure we aren't trying to present multiple types of edit
		editType = sole( i.editType() for i in inspectors if i.editable() )
		if self.__editable and editType is None :
			# None implies there are multiple edit types, disable editing.
			errors.append( "Selected locations have mixed Tweak and Source edits, the Inspector can only edit one kind at once." )
			self.__editable = False

		# Will be None if there are a mixture of edited and unedited locations. We won't be visible if there are none.
		hasEdit = sole( i.hasEdit() for i in inspectors )

		warningStr = "\n" + "\n".join( warnings ) if warnings and not errors else ""

		if self.__editable:
			self.__editButton.setToolTip( ( "Click to edit" if hasEdit else "Click to add an edit" ) + warningStr )
		else :
			self.__editButton.setToolTip( "\n".join( errors ) if errors else "" )

		# We don't disable the button, as we need more control over the disabled icon.
		# We just don't do anything when clicked if we're not editable.
		image = "editDisabled.png"
		if self.__editable :
			image = "editOn.png" if hasEdit else "editOff.png"
		self.__editButton.setImage( image )

		self.__valueWidget.setToolTip( "\n".join( errors ) if errors else ( ( "Click to Edit" if hasEdit else "The current value" ) + warningStr )  )

		# Determine the logical state of the UI element, so we can apply appropriate styling
		valueState = "Uneditable"
		if self.__editable :
			if hasEdit is None :
				# A mix of edited and unedited.
				# We can only make edits for the user if we are using an EditScope, so self.__editable will be
				# False for any unedited locations when Automatic is set. As such we'll only get here if there
				# the user has chosen EditScope, so we show a mixed state of Editable + EditScopeEdit decoration.
				valueState = "SomeEdited"
			elif hasEdit == False :
				valueState = "Editable"
			else :
				# Every location has edits, but they may be a mixture of GenericEdits and EditScopeEdits
				allInEditScopes = sole( i.inEditScope() for i in inspectors )
				valueState = "MixedEdits" if allInEditScopes is None else "{}Edit".format( "EditScope" if allInEditScopes else "Generic" )

		self.__valueWidget._qtWidget().setProperty( "inspectorValueState", valueState )
		self.__valueWidget._qtWidget().setProperty( "inspectorValueHasWarnings", warnings and not errors )
		self.__valueWidget._repolish()

		self.__inspectors = inspectors

	def __editButtonClicked( self, button ) :

		if self.__editable :
			self.__edit()

	def __valueWidgetClicked( self, *unused ) :

		# Only edit via the value label all locations already have edits to help avoid accidental edit creation
		if self.__editable and bool( self.__inspectors ) and all( i.hasEdit() for i in self.__inspectors ) :
			self.__edit()

	def __edit( self ) :

		plugs = [ i.acquireEdit() for i in self.__inspectors ]
		warnings = [ i.warningMessage() for i in self.__inspectors if i.warningMessage() ]

		for p in plugs :
			r = Gaffer.MetadataAlgo.readOnlyReason( p )
			if r is not None :
				# We still allow viewing of read-only plugs to people can inspect the nature of an edit
				warnings.append( "'{}' is locked.".format( r.relativeName( r.ancestor( Gaffer.ScriptNode ) ) ) )

		self.__editWindow = _EditWindow( plugs, warning = "\n".join( warnings ) )
		self.__editWindow.resizeToFitChild()
		self.__editWindow.popup( self.bound().center() + imath.V2i( 0, 45 ) )

## \todo How does this relate to PopupWindow and SpreadsheetUI._EditWindow?
class _EditWindow( GafferUI.Window ) :

	def __init__( self, plugs, warning=None, **kw ) :

		assert( len(plugs) > 0 )

		container = GafferUI.ListContainer( spacing = 4 )
		GafferUI.Window.__init__( self, "", child = container, borderWidth = 8, sizeMode=GafferUI.Window.SizeMode.Automatic, **kw )

		self.visibilityChangedSignal().connect( Gaffer.WeakMethod( self.__visibilityChanged ), scoped = False )

		for p in plugs :
			## \todo Figure out when/if this is about to happen, and disable
			# editing beforehand.
			assert( isinstance( p, plugs[0].__class__ ) )

		self._qtWidget().setWindowFlags( QtCore.Qt.Popup )
		self._qtWidget().setAttribute( QtCore.Qt.WA_TranslucentBackground )
		self._qtWidget().paintEvent = Gaffer.WeakMethod( self.__paintEvent )

		with container :

			# Label to tell folks what they're editing.
			# TODO: This could do with some of the niceties TransformToolUI applies finding
			# common ancestors, as well as more general beautifying.

			script = plugs[0].ancestor( Gaffer.ScriptNode )
			labels = { p.relativeName( script ) for p in plugs }
			label = GafferUI.Label()

			if len( labels ) == 1 :
				message = next( iter( labels ) )
				toolTip = ""
			else :
				editScope = self.__commonEditScope( plugs )
				if editScope is not None :
					message ="{} ({} plugs)".format( editScope.relativeName( script ), len( plugs ) )
				else :
					message ="{} plugs".format( len( plugs ) )
				toolTip = "\n".join( "- " + l for l in labels )

			label.setText( "
{}
".format( message ) )
			label.setToolTip( toolTip )

			with GafferUI.ListContainer( spacing = 4, orientation = GafferUI.ListContainer.Orientation.Horizontal ) :

				# An alert (if required)

				if warning :
					warningBadge = GafferUI.Image( "warningSmall.png" )
					warningBadge.setToolTip( warning )

				# Widget for editing plugs

				self.__plugValueWidget = GafferUI.PlugValueWidget.create( plugs )
				if isinstance( self.__plugValueWidget, GafferSceneUI.TweakPlugValueWidget ) :
					## \todo We have this same hack in SpreadsheetUI. Should we instead
					# do something with metadata when we add the column to the spreadsheet?
					self.__plugValueWidget.setNameVisible( False )

		self.keyPressSignal().connect( Gaffer.WeakMethod( self.__keyPress ), scoped = False )

	def __commonEditScope( self, plugs ) :

		scopes = { p.ancestor( Gaffer.EditScope ) for p in plugs }
		if len(scopes) == 1 :
			return next( iter( scopes ) )
		return None

	def popup( self, position ) :

		self.setVisible( True )

		# Attempt to focus the first text widget. This is done after making
		# the window visible, as we check child widget visibility to avoid
		# attempting to focus upon hidden widgets.

		size = self._qtWidget().sizeHint()
		self.setPosition( position - imath.V2i( size.width() / 2, size.height() / 2 ) )

		textWidget = self.__textWidget( self.__plugValueWidget )
		if textWidget is not None :
			if isinstance( textWidget, GafferUI.TextWidget ) :
				textWidget.grabFocus()
				textWidget.setSelection( 0, len( textWidget.getText() ) )
			else :
				textWidget.setFocussed( True )

	def __visibilityChanged( self, unused ) :

		if self.visible() :
			return

		# The modal popup will hide the window without de-focusing the
		# current widget. Ultimately this results in un-committed edits
		# from being lost. Force the active widget to resign focus.
		focusWidget = QtWidgets.QApplication.focusWidget()
		if focusWidget is not None :
			gafferWidget = GafferUI.Widget._owner( focusWidget )
			if gafferWidget is not None and self.isAncestorOf( gafferWidget ) :
				gafferWidget._qtWidget().clearFocus()

	def __paintEvent( self, event ) :

		painter = QtGui.QPainter( self._qtWidget() )
		painter.setRenderHint( QtGui.QPainter.Antialiasing )

		painter.setBrush( QtGui.QColor( 35, 35, 35 ) )
		painter.setPen( QtGui.QColor( 0, 0, 0, 0 ) )

		radius = self._qtWidget().layout().contentsMargins().left()
		size = self.size()
		painter.drawRoundedRect( QtCore.QRectF( 0, 0, size.x, size.y ), radius, radius )

	def __keyPress( self, widget, event ) :

		if event.key == "Return" :
			self.close()

	# \todo This is duplicated from SpreadsheetUI, is this something we can generalise?
	@classmethod
	def __textWidget( cls, plugValueWidget ) :

		def widgetUsable( w ) :
			return w.visible() and w.enabled() and w.getEditable()

		widget = None

		if isinstance( plugValueWidget, GafferUI.StringPlugValueWidget ) :
			widget = plugValueWidget.textWidget()
		elif isinstance( plugValueWidget, GafferUI.NumericPlugValueWidget ) :
			widget = plugValueWidget.numericWidget()
		elif isinstance( plugValueWidget, GafferUI.PathPlugValueWidget ) :
			widget = plugValueWidget.pathWidget()
		elif isinstance( plugValueWidget, GafferUI.MultiLineStringPlugValueWidget ) :
			widget = plugValueWidget.textWidget()

		if widget is not None and widgetUsable( widget ) :
			return widget

		for childPlug in Gaffer.Plug.Range( next( iter( plugValueWidget.getPlugs() ) ) ) :
			childWidget = plugValueWidget.childPlugValueWidget( childPlug )
			if childWidget is not None :
				childTextWidget = cls.__textWidget( childWidget )
				if childTextWidget is not None :
					return childTextWidget

		return None

# Widget for displaying any value type.
## \todo Figure out relationship with SceneInspector's Diff widgets.
# It seems like they may all be subclasses of the same abstract base?
class _ValueWidget( GafferUI.Widget ) :

	def __init__( self, values = [], **kw ) :

		GafferUI.Widget.__init__( self, QtWidgets.QLabel(), **kw )

		self._qtWidget().setFixedHeight( 20 )
		self._qtWidget().setFixedWidth( 140 )

		self._qtWidget().setStyleSheet( "padding-left: 4px; padding-right: 4px;" )

		self.buttonPressSignal().connect( Gaffer.WeakMethod( self.__buttonPress ), scoped = False )
		self.dragBeginSignal().connect( Gaffer.WeakMethod( self.__dragBegin ), scoped = False )
		self.dragEndSignal().connect( Gaffer.WeakMethod( self.__dragEnd ), scoped = False )
		GafferUI.DisplayTransform.changedSignal().connect( Gaffer.WeakMethod( self.__updateLabel ), scoped = False )

		self.__values = []
		self.setValues( values )

	def setValues( self, values ) :

		if self.__values == values :
			return

		self.__values = values
		self.__updateLabel()

	def getValues( self ) :

		return self.__values

	def getToolTip( self ) :

		result = GafferUI.Widget.getToolTip( self )
		if result :
			return result

		if not self.__values or all( v == self.__values[0] for v in self.__values ) :
			# No values, or values all the same. No need for a tooltip.
			return ""

		return "\n".join(
			self.__formatValue( v ) for v in self.__values
		)

	def __updateLabel( self ) :

		if not len( self.__values ) :
			self._qtWidget().setText( "" )
			return

		if any( v != self.__values[0] for v in self.__values ) :
			# Mixed values
			self._qtWidget().setText( "---" )
			return

		# All values the same
		self._qtWidget().setText( self.__formatValue( self.__values[0] ) )

	@classmethod
	def __formatValue( cls, value ) :

		if isinstance( value, ( int, float ) ) :
			return GafferUI.NumericWidget.valueToString( value )
		elif isinstance( value, imath.Color3f ) :
			color = GafferUI.Widget._qtColor( GafferUI.DisplayTransform.get()( value ) ).name()
			return "

	{}

".format(
				color,
				cls.__formatValue( imath.V3f( value ) )
			)
		elif isinstance( value, ( imath.V3f, imath.V2f, imath.V3i, imath.V2i ) ) :
			return " ".join( GafferUI.NumericWidget.valueToString( x ) for x in value )
		elif value is None :
			return ""
		else :
			return str( value )

	def __dragData( self ) :

		if not self.__values :
			return None

		if all( v == self.__values[0] for v in self.__values ) :
			return self.__values[0]

		## \todo Where all values are of the same type, pack them
		# into `IECore.VectorData`.
		return None

	def __buttonPress( self, widget, event ) :

		return self.__dragData() is not None and event.buttons == event.Buttons.Left

	def __dragBegin( self, widget, event ) :

		if event.buttons != event.Buttons.Left :
			return None

		data = self.__dragData()
		if data is None :
			return None

		GafferUI.Pointer.setCurrent( "values" )
		return data

	def __dragEnd( self, widget, event ) :

		GafferUI.Pointer.setCurrent( None )

# Determines the source _input_ to an input plug. Plug.source() may give us an
# output in the case of an animation node, etc.
## \todo Factor in when implementing contextAwareSource.
def sourceInput( plug ) :

	result = plug
	while plug is not None :
		if plug.direction() is Gaffer.Plug.Direction.In :
			result = plug
		plug = plug.getInput()
	return result

# Utility in the spirit of `all()` and `any()`. If all values in `sequence`
# are equal, returns that value, otherwise returns `None`.
## \todo Copied from Gaffer.PlugValueWidget. Is there somewhere more sensible we can put this? Cortex perhaps?
def sole( sequence ) :

	result = None
	for i, v in enumerate( sequence ) :
		if i == 0 :
			result = v
		elif v != result :
			return None

	return result


import discord
import json
import random
import asyncio
import logging
from logging.handlers import RotatingFileHandler
from discord.ext import commands
from os import path, walk

"""Discord bot to learn Rainbow Six Siege maps callouts"""

__author__ = "Sergei Vorobev <s.vorobev101@gmail.com>"
__version__ = "0.4"

# logging config
log_file = "files/log/discord_callouts_bot.log"
max_log_file_size = 10  # max log file size in MB
max_log_files = 5
logger = logging.getLogger('discord_r6_callouts')
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s -  %(levelname)s - %(message)s', datefmt='%d.%m.%Y %H:%M:%S')
handler = logging.handlers.RotatingFileHandler(log_file, mode='a', maxBytes=max_log_file_size*1024*1024,
                                               backupCount=max_log_files, encoding='utf-8', delay=False)
handler.setFormatter(formatter)
logger.addHandler(handler)

class R6Callouts:
    def __init__(self):
        self.TOKEN = "your token"
        self.bot_cmd_prefix = "$"
        self.quiz_file = "files/quiz.txt"
        self.maps_dir = "files/maps/"
        try:
            with open(self.quiz_file, "r") as of:
                self.quiz_separate_data = json.load(of)
        except (FileNotFoundError, json.decoder.JSONDecodeError):
            self.quiz_separate_data = {}
        self.bot = commands.Bot(command_prefix=self.bot_cmd_prefix)
        self.active_quizzes = {}
        self.emoji_dict = {0: "\U00000030\U000020E3",
                           1: "\U00000031\U000020E3",
                           2: "\U00000032\U000020E3",
                           3: "\U00000033\U000020E3",
                           4: "\U00000034\U000020E3",
                           5: "\U00000035\U000020E3",
                           6: "\U00000036\U000020E3",
                           7: "\U00000037\U000020E3",
                           8: "\U00000038\U000020E3",
                           9: "\U00000039\U000020E3"
                           }
        self.emoji_dict.setdefault("cross", "\U0000274C")
        self.b_all_maps = None
        self.all_maps_val = "all maps!"
        self.quiz_question_timer = 15
        self.quiz_start_timer = 3
        self.default_quiz_question_amount = 5
        self.max_quiz_timer = 30
        try:
            with open(self.quiz_file, "r") as of:
                self.quiz_separate_data = json.load(of)
        except (FileNotFoundError, json.decoder.JSONDecodeError):
            self.quiz_separate_data = {}
        self.read_cfg()
        self.bot_name = "r6_callouts_bot"

        @self.bot.command(name="maps")
        async def map_quiz(ctx):
            """List maps, available for quiz"""
            embed = discord.Embed(title="R6 callouts quiz")
            nm = "The following maps are available now"
            val = "\n".join(list(self.b_all_maps)[:-1])
            embed.add_field(name=nm, value=val)
            await ctx.send(embed=embed)

        @self.bot.command(name="view")
        async def view_map_schematics(ctx):
            """shows schematics for a certain map"""
            message_split = ctx.message.content.split()
            map_name = self.list_get(message_split, 1, None)
            if not map_name:
                await ctx.send(f"{ctx.message.author.mention}, please specify the map you need."
                               f"\n Example: '{self.bot_cmd_prefix}view KANAL'")
                return
            if map_name.upper() not in self.b_all_maps:
                logger.info(f"{ctx.channel.id} requested non-existent map {map_name}")
                await ctx.send(f"Yeah, sorry, {ctx.message.author.mention}, I've no idea what you mean. Pretty sure "
                               f"there is no {map_name} or it has been misspelled somehow. Try {self.bot_cmd_prefix}maps "
                               f"to list all available maps")
            else:
                await self.view_map(ctx, map_name.upper())

        @self.bot.command(aliases=["stop", "cancel"])
        async def stop_quiz(ctx):
            """Cancel currently running quiz. Alias: stop, cancel"""
            await self.cancel_processor(ctx)

        @self.bot.command(name="quiz")
        async def start_quiz_polling(ctx):
            """Try naming all spots on R6 maps. Use '$help quiz' for detailed info
            Usage: '$quiz KAFE 7'. You can pick any map from '$maps' pool
            or go for a random set of questions using 'all' instead of map name.
            To answer a question just react on the emoji number you believe is correct.
            You'll have 10 seconds to answer each question, or if you pass the 3rd parameter
            you can set your own timer. E.g. '$quiz KANAL 10 5'
            will start the quiz on KANAL for 10 questions and 5 second window to answer"""
            # Quiz polling only allowed in DM and text channels
            message_channel = ctx.channel
            if isinstance(message_channel, discord.channel.TextChannel):
                chat_type = "channel"
            elif isinstance(message_channel, discord.channel.DMChannel):
                chat_type = "DM"
            else:
                await ctx.send("Sorry, this command only supported in text channels and DMs")
                logger.info(f"An attempt to use bot in {message_channel}")
                return
            message_split = ctx.message.content.split()
            map_name = self.list_get(message_split, 1, None)
            amount_of_questions = self.list_get(message_split, 2, None)
            quiz_timer = self.list_get(message_split, 3, None)
            if not map_name:
                await ctx.send(f"Not like this.\nThe bot has 2 parameters: map name (check out {self.bot_cmd_prefix}maps"
                               f" command) and the amount of questions in the quiz.\nExample: '{self.bot_cmd_prefix}quiz"
                               f" BANK 5'")
                return
            # a bit more civil way to start quiz for the whole map pool
            elif map_name.upper() in ("RANDOM", "ALL", "ANY", "RND", "EVERYTHING"):
                map_name = self.all_maps_val
            elif map_name.upper() not in self.b_all_maps:
                logger.info(f"{ctx.channel.id} attempted quiz on non-existent map {map_name}")
                await ctx.send(f"Sorry, {ctx.message.author.mention} I don't know {map_name}.\n"
                               f"{self.bot_cmd_prefix}maps command will list all the available quizzes")
                return
            else:
                map_name = map_name.upper()
            if not amount_of_questions:  # notify user of the 2nd parameter
                await ctx.send(f"By the way, you can also add 2nd parameter: the amount of questions in a quiz. "
                               f"e.g. '{self.bot_cmd_prefix}quiz BANK 10'.\nFor now we'll start with {self.default_quiz_question_amount} "
                               f"questions")
                amount_of_questions = self.default_quiz_question_amount
            if self.is_positive_integer(amount_of_questions):
                amount_of_questions = int(amount_of_questions)
            else:
                await ctx.send(f"Kudos for exploratory testing, but no, the 2nd parameter should be"
                               f"a positive integer\nWe'll start with {self.default_quiz_question_amount} questions")
                amount_of_questions = self.default_quiz_question_amount
            if self.is_positive_integer(quiz_timer):
                quiz_timer = min(int(quiz_timer), self.max_quiz_timer)  # limit max question time
            else:
                quiz_timer = self.quiz_question_timer
            # check if quiz is already running in this channel
            chat_id = message_channel.id
            if not self.active_quizzes.get(chat_id, False):
                self.active_quizzes[chat_id] = True
            else:
                await ctx.send(f"{ctx.message.author.mention} chill! We already have a quiz running")
                logger.info(f"{chat_id} attempted multiple instance of quiz! shame!")
                return
            quiz_questions = self.create_list_of_quiz_questions(amount_of_questions, map_name, 5)
            embed = discord.Embed(title="Starting quiz!", color=0x00ff00)
            av = f"You're playing on {map_name}"
            res = f" You'll have to answer {len(quiz_questions)} questions within {quiz_timer} seconds " \
                f"timer window.\n Quiz starts in {self.quiz_start_timer} seconds.\nGood luck!"
            embed.add_field(name=av, value=res, inline=False)
            await ctx.send(embed=embed)
            end_output = "Quiz done!"
            await asyncio.sleep(self.quiz_start_timer)
            logger.info(f"{ctx.message.author} started quiz on {map_name} for {amount_of_questions} questions")
            for i, question in enumerate(quiz_questions):
                # check if user cancels the quiz
                if self.active_quizzes[chat_id] == "cancel":
                    end_output = "Quiz stopped.\n~~The mission, the nightmares... they're finally... over~~"
                    break
                await self.quiz_polling(ctx=ctx, map_name=map_name, quiz_question=question,
                                        question_number=(i + 1, len(quiz_questions)), chat_id=chat_id,
                                        chat_type=chat_type, quiz_timer=quiz_timer)
            self.active_quizzes[chat_id] = False  # exclude from active quizzes
            await ctx.send(end_output)

    def read_cfg(self):
        """read all main self.* variables outside of __init__ to be able to re-read config after it was changed
        without restarting the bot."""
        self.b_all_maps = self.quiz_separate_data.keys()
        maps_buffer = {}
        for key, val in self.quiz_separate_data.items():
            for k, v in val.items():
                maps_buffer[k] = f"{key}/{v}"
        self.quiz_separate_data[self.all_maps_val] = maps_buffer

    async def cancel_processor(self, ctx):
        """raises cancel flag that stops current quiz for user or channel"""
        message_channel = ctx.channel
        chat_id = ctx.channel.id
        if type(message_channel) in (discord.channel.TextChannel, discord.channel.DMChannel):
            if not self.active_quizzes.get(chat_id, False):
                output = "Quiz isn't running here"
            elif self.active_quizzes.get(chat_id, None) == "cancel":  # already called for stop
                output = f"{ctx.message.author.mention} yeah, this quiz is being stopped now. It'll be over soon"
            else:
                self.active_quizzes[chat_id] = "cancel"
                output = "Ok, stopping the quiz..."
                logger.info(f"{ctx.message.author} cancelled their quiz")
        else:
            output = "Sorry, this command only supported in text channels and DMs"
        await ctx.send(output)

    def create_list_of_quiz_questions(self, quiz_length, map_name, total_options):
        """composes list of quiz question options. First element is always the correct answer. The're total 1 +
        self.quiz_options_amount options for each question and total of self.quiz_questions_amount questions per quiz.
        Each quiz run each question is randomized from the quiz pull (for any chosen map all for all supported maps"""
        all_questions = []
        chosen_options = []
        true_quiz_length = min(quiz_length, len(self.quiz_separate_data[map_name].items()))
        for _ in range(true_quiz_length):
            # get random option
            try:
                correct_answer = random.choice(
                    [i for i in self.quiz_separate_data[map_name].keys() if i not in chosen_options])
            except IndexError:
                return all_questions
            if correct_answer:
                quiz_options = [i for i in self.quiz_separate_data[map_name].keys() if i != correct_answer]
                true_total_options = min(total_options, len(quiz_options))
                quiz_options = random.sample(quiz_options, true_total_options)
                # correct answer is always the first element
                quiz_options.insert(0, correct_answer)
                chosen_options.append(correct_answer)
                all_questions.append(quiz_options)
            else:
                pass
        return all_questions

    async def quiz_polling(self, ctx, map_name, quiz_question, question_number, chat_id, chat_type, quiz_timer):
        """poll quiz for a DM chat or channel"""
        output = f"Question # {question_number[0]}/{question_number[1]}."
        correct_answer = quiz_question[0]  # always first option
        random.shuffle(quiz_question)
        pic_name = self.quiz_separate_data[map_name][correct_answer]
        try:
            if map_name == self.all_maps_val:
                with open(f"files/quiz/{pic_name}", 'rb') as pic:
                    quiz_pic = discord.File(pic)
            else:
                with open(f"files/quiz/{map_name}/{pic_name}", 'rb') as pic:
                    quiz_pic = discord.File(pic)
        except FileNotFoundError:
            logger.warning(f"Picture missing for files/quiz/{map_name}/{pic_name}")
            output = f"huh... I couldn't find proper picture for files/quiz/{map_name}/{pic_name}"
            with open("files/misc/not_found.png", "rb") as pic:
                quiz_pic = discord.File(pic)
        emoji_options = self.num_to_emoji(range(1, len(quiz_question) + 1))
        output += "\n"*2
        pretty_options = dict(zip(emoji_options, quiz_question))
        pretty_options = [f" {emoji}   {opt}" for emoji, opt in pretty_options.items()]
        output += "\n".join(pretty_options)
        msg = await ctx.send(content=output, file=quiz_pic)
        for option in emoji_options:
            await msg.add_reaction(option)
        reactions = []
        # in channels only evaluate overall statistics. No need for mentions
        if chat_type == "channel":
            await asyncio.sleep(quiz_timer)
            msg_after = await ctx.fetch_message(msg.id)
            for reaction in msg_after.reactions:
                reactions.append(reaction.count - 1)
            total_reactions = sum(reactions)
            correct_reactions = reactions[quiz_question.index(correct_answer)]
            if correct_reactions == 0:
                output = f"No one guessed it right!\nIt was # {quiz_question.index(correct_answer) + 1}: " \
                         f"{correct_answer}"
            elif correct_reactions == 1:
                output = f"Just one person got it right!\nIt was " \
                         f"# {quiz_question.index(correct_answer) + 1}: {correct_answer}"
            elif correct_reactions > 1:
                output = f"{correct_reactions} out of {total_reactions} got it right!\nIt was " \
                         f"# {quiz_question.index(correct_answer) + 1}: {correct_answer}"
            await ctx.send(output)
            return
        # proceed with DMs and reaction evaluation
        await self.wait_for_reaction(ctx, msg.id, chat_id, quiz_timer)
        if self.active_quizzes.get(chat_id, None) == "cancel":  # don't show correct answer if quiz was cancelled
            return
        msg_after = await ctx.fetch_message(msg.id)

        for reaction in msg_after.reactions:
            reactions.append(reaction.count - 1)
        if reactions.count(1) > 1:
            await ctx.send("No-no, just 1 answer allowed!")
            return
        elif reactions.count(1) == 0:
            await ctx.send(f"Time's out!\nIt was # {quiz_question.index(correct_answer) + 1}: {correct_answer}")
            return
        chosen_answer = reactions.index(1)
        if quiz_question[chosen_answer] == correct_answer:
            await ctx.send("Good job!")
        else:
            await ctx.send(f"Nope, sorry, not {quiz_question[chosen_answer]}. It's actually {correct_answer}")

    async def wait_for_reaction(self, ctx, message_id, user_id, wait_time):
        """a primitive way to speed up quiz so user won't have to wait 10 seconds after each question"""
        for _ in range(1, wait_time + 1):
            await asyncio.sleep(1)
            msg_later = await ctx.fetch_message(message_id)
            if any(reaction.count == 2 for reaction in msg_later.reactions) or \
                    self.active_quizzes.get(user_id, None) == "cancel":
                break

    async def view_map(self, ctx, map_name):
        logger.info(f"{ctx.channel.id} requested {map_name} schematics")
        map_path = f"{self.maps_dir}{map_name}"
        if path.exists(map_path):
            # send all related pics
            for root, dirs, files in walk(map_path):
                for file in files:
                    if file.endswith('png'):
                        pic_name = f"{map_path}/{file}"
                        with open(pic_name, 'rb') as pic:
                            map_schema = discord.File(pic)
                        await ctx.send(file=map_schema)
            await ctx.send("Here you go")
        else:
            logger.warning(f"Failed to locate files for {map_name} map!")
            await ctx.send(f"Sorry, {ctx.message.author.mention}, I couldn't find files for {map_name}.\nThis will"
                           f"be reported, so someone would fix it one day. I hope")

    def num_to_emoji(self, iterator):
        """converts numbers into emoji representation"""
        emoji_numbers = []
        for entry in iterator:
            if isinstance(entry, int):
                emoji_number = ""
                for digit in str(entry):
                    emoji_number += self.emoji_dict.get(int(digit))
                emoji_numbers.append(emoji_number)
            else:
                logger.warning(f"{entry} doesn't look like a number with that pesky {type(entry)} type")
        return emoji_numbers

    @staticmethod
    def is_positive_integer(val=0):
        """quick check if passed value is a positive integer for whenever we need to check for quiz amount, timer and
        other parameters."""
        try:
            val = int(val)  # cut floats. It's fine
            if val > 0:
                return True
            else:
                return False
        except (ValueError, TypeError):
            return False

    @staticmethod
    def reaction_reader(reactions, allow_multiple=False, allow_none=False):
        """parses message reactions and runs basic checks"""
        if reactions.count(1) > 1 and not allow_multiple:
            multiple_answers = True
        else:
            multiple_answers = False
        if reactions.count(1) == 0 and not allow_none:
            no_answer = True
        else:
            no_answer = False
        return multiple_answers, no_answer

    @staticmethod
    def list_get(list_in, index, default):
        """a version of .get() method for lists"""
        try:
            return list_in[index]
        except IndexError:
            return default

    def run_bot(self):
        self.bot.run(self.TOKEN)

logger.info("Start the bot!")
bot = R6Callouts()
bot.run_bot()


from random import sample
from random import randint
import numpy as np
from scipy import stats
from skimage import io
import matplotlib.pyplot as plt

from superpixel_segmentation_SLIC import MaskEnhancer

class OptimizeSLIC:

	def __init__(self, truths=None, masks=None, images=None, num_training_points=1):
		self.truths = truths
		self.masks = masks
		self.images = images
		self.n = num_training_points

	def optimize(self, max_points = 500):

		#Similar to Peak Search Algorithm
		best_resolution = 0
		best_loss = float('inf')
		data = []

		resolution_max = 1000
		max_learning_rate = 100
		resA = int(resolution_max*0.3)
		previous_resolution = resA
		previous_loss = self.evaluate(resA)

		resB = int(resolution_max*0.7)
		second_previous_resolution = resB
		second_previous_loss = self.evaluate(resB)
		resolutions_sampled = 0
		while resolutions_sampled < max_points:

			case_increase = second_previous_resolution < previous_resolution and second_previous_loss > previous_loss
			case_increase = case_increase or (second_previous_resolution > previous_resolution and second_previous_loss < previous_loss)

			case_decrease = second_previous_resolution < previous_resolution and second_previous_loss < previous_loss
			case_decrease = case_decrease or (second_previous_resolution > previous_resolution and second_previous_loss > previous_loss)

			new_resolution = max(min(previous_resolution + randint(-20,20), resolution_max), 5)
			if case_increase:
				new_resolution = min(previous_resolution + randint(1,max_learning_rate), resolution_max)
			if case_decrease:
				new_resolution = max(previous_resolution - randint(1,max_learning_rate), 20)

			new_avg_loss = self.evaluate(new_resolution)
			data.append((new_resolution, new_avg_loss))
			if new_avg_loss < best_loss:
				best_loss = new_avg_loss
				best_resolution = new_resolution

			second_previous_resolution = previous_resolution
			second_previous_loss = previous_loss
			previous_resolution = new_resolution
			previous_loss = new_avg_loss
			print("Resolution: " + str(new_resolution) + " Loss: " + str(new_avg_loss) +  "  Count: " + str(resolutions_sampled))
			resolutions_sampled += 1
			if resolutions_sampled % 50 == 0:
				print("Best Resolution: " + str(best_resolution))
				print("Best Loss: " + str(best_loss))
		return data, best_resolution, best_loss

	def evaluate(self, resolution, batch_size=100):
		points = sample(range(0,self.n-1), batch_size)
		losses = [self.calculate_loss(truths[i], masks[i], images[i], resolution) for i in points]
		losses = [loss for loss in losses if loss >= 0]
		return np.mean(losses)

	def calculate_loss(self, truth_string, mask_string, image_string, resolution):
		truth = io.imread(truth_string)[:,:,0]
		total_pixels = truth.size

		enhancer = MaskEnhancer(io.imread(image_string), io.imread(mask_string), resolution, string=False)
		enhancer.get_SLIC_segments(show_segments=False)
		enhanced_mask, original_mask_pixels = enhancer.tune_mask_expand(show_masks=False)
		if len(original_mask_pixels) == 0:
			return -1 #ERROR

		#get ground truth mask pixel value to look for
		mask_value = self.find_ground_truth_mask_value(truth, original_mask_pixels)

		#Calculate missing and extra pixels
		missed_pixels = 0
		extra_pixels = 0
		for r in range(len(enhanced_mask)):
			for c in range(len(enhanced_mask[0])):
				if truth[r][c] == mask_value and enhanced_mask[r][c] == 0:
					missed_pixels += 1
				if truth[r][c] != mask_value and enhanced_mask[r][c] > 0:
					extra_pixels += 1
		#calculate loss
		loss = missed_pixels + extra_pixels*1.0/total_pixels
		return loss

	def find_ground_truth_mask_value(self,truth, mask_pixels):
		locations = sample(mask_pixels, min(100, len(mask_pixels)-1))
		values = [truth[r][c] for r,c in locations]
		value = stats.mode(values)[0][0]
		return value

	def plot(self, data):
		data2 = sorted(data, key = lambda x: x[0])
		resolutions, losses = zip(*data2)

		plt.figure()
		plt.plot(resolutions, losses)
		plt.show()

import glob
images = sorted(glob.glob("../2000_validation_cropped_dataset_time551/rgb_crop/*.jpg"))
truths = sorted(glob.glob("../2000_validation_cropped_dataset_time551/gt_mask/*.jpg"))
masks = sorted(glob.glob("../2000_validation_cropped_dataset_time551/predicted_mask/*.jpg"))

optimizer = OptimizeSLIC(truths = truths, masks=masks, images = images, num_training_points=len(images))
data, best_resolution, best_loss = optimizer.optimize()
optimizer.plot(data)
print("Best Resolution: " + str(best_resolution))
print("Best Loss: " + str(best_loss))

# mask = "crop_mask.jpg"
# image = "crop.jpg"
# truth = "actual_mask.jpg"
# loss = optimizer.calculate_loss(truth, mask, image)
# print("LOSS")
# print(loss)


"""

Домашнее задание №1

Цикл while: hello_user

* Напишите функцию hello_user(), которая с помощью функции input() спрашивает
  пользователя “Как дела?”, пока он не ответит “Хорошо”

"""

def hello_user():
    """
    Замените pass на ваш код
    """
    while True:
        hello = str(input('Как дела чувак? =)'))
        if hello != 'Хорошо':
            print('Я буду задавать вопрос, пока не получу нужный ответ, - "Хорошо"')
            continue
        else:
            print('Рад, что  у тебя все хорошо')
            break

if __name__ == "__main__":
    hello_user()