import math
import time
from enum import IntEnum

import gym
from gym import error, spaces, utils
from gym.utils import seeding
from ctypes import *
import pymem
from win32process import CreateProcess, STARTUPINFO
import numpy as np

from gym_street_fighter_v.ActionSpaceRyu import ActionSpaceRyu, Input
from gym_street_fighter_v.KeyPressing import KeyPressing, VirtualKeyCode
from gym_street_fighter_v.MoveEmbedding import move_to_embedding
from gym_street_fighter_v.StandingCrouchingJumpingEmbedding import standing_crouching_jumping_to_embedding
from gym_street_fighter_v.read_pointer import read_pointer

RYU_TOTAL_HP = 500.0
MIN_X = -750
MAX_X = 750
MIN_Y = 0
MAX_Y = 215
MAX_X_DISTANCE = MAX_X - MIN_X
MAX_Y_DISTANCE = MAX_Y - MIN_Y
MAX_DISTANCE = math.sqrt((MAX_X_DISTANCE) ** 2 + (MAX_Y_DISTANCE) ** 2)

program_path = r'D:\SteamLibrary\steamapps\common\StreetFighterV\StreetFighterV.exe'

class WindowsAppEnv(gym.Env):
    metadata = {'render.modes': ['human']}

    def __init__(self):
        pass

    def step(self, action):
        pass

    def reset(self):
        pass

    def render(self, mode='human'):
        pass

    def close(self):
        pass

FRAMES_PER_SECOND = 60
DURATION_BETWEEN_FRAMES = 1.0 / FRAMES_PER_SECOND

actions = list(ActionSpaceRyu)

class StreetFighterVEnv(WindowsAppEnv):
    metadata = {'render.modes': ['human']}

    def __init__(self):
        self.action_space = spaces.Discrete(len(ActionSpaceRyu))

        self.key_pressing = KeyPressing()

        self.process = pymem.Pymem('StreetFighterV.exe')
        PROCESS_SUSPEND_RESUME = 0x0800
        self.process_handle = cdll.kernel32.OpenProcess(PROCESS_SUSPEND_RESUME, False, self.process.process_id)

        self.hwnd = cdll.user32.FindWindowW(None, 'StreetFighterV')

        self._set_previous_hp_to_current_hp()

        self.game_over = False

    def _set_previous_hp_to_current_hp(self):
        self.previous_hp_p1 = self._read_character_1_health_points()
        self.previous_hp_p2 = self._read_character_2_health_points()

    def step(self, action):
        # cdll.ntdll.NtResumeProcess(self.process_handle)
        resumption_time = time.time()
        while windll.user32.GetForegroundWindow() != self.hwnd:
            time.sleep(1)

        input_to_keys = {
            Input.Forward: lambda: (VirtualKeyCode.D,) if self.is_character_on_left() else (VirtualKeyCode.A,),
            Input.Backward: lambda: (VirtualKeyCode.A,) if self.is_character_on_left() else (VirtualKeyCode.D,),
            Input.Up: VirtualKeyCode.W,
            Input.Down: VirtualKeyCode.S,
            Input.LP: VirtualKeyCode.G,
            Input.MP: VirtualKeyCode.H,
            Input.HP: VirtualKeyCode.J,
            Input.LMHP: VirtualKeyCode.K,
            Input.LK: VirtualKeyCode.B,
            Input.MK: VirtualKeyCode.N,
            Input.HK: VirtualKeyCode.M,
            Input.LMHK: VirtualKeyCode.OEM_COMMA
        }

        input_to_keys = dict(
            self.convert_input_to_key_mapping_to_lambda_function(input, key) for input, key in input_to_keys.items())

        inputs, conditions = actions[action].value
        for inputs_entry in inputs:
            for input in inputs_entry:
                keys = input_to_keys[input]()
                for key in keys:
                    self.key_pressing.key_down(key)
            time.sleep(0.1)
            for input in inputs_entry:
                keys = input_to_keys[input]()
                for key in keys:
                    self.key_pressing.key_up(key)

        move_time_left = self._read_character_1_move_time_left()
        while move_time_left > 0 and not self._is_done():
            time.sleep(DURATION_BETWEEN_FRAMES)
            move_time_left = self._read_character_1_move_time_left()

        duration_to_sleep = max(0.0, DURATION_BETWEEN_FRAMES - (time.time() - resumption_time))
        if duration_to_sleep > 0.0:
            time.sleep(duration_to_sleep)

        # cdll.ntdll.NtSuspendProcess(self.process_handle)

        hp_p1 = self._read_character_1_health_points()
        hp_p2 = self._read_character_2_health_points()
        state = self._get_state()
        hp_p1_delta = self._calculate_hp_delta(hp_p1, self.previous_hp_p1)
        hp_p2_delta = self._calculate_hp_delta(hp_p2, self.previous_hp_p2)
        reward = self._normalize(hp_p1_delta, 0, RYU_TOTAL_HP) - self._normalize(hp_p2_delta, 0, RYU_TOTAL_HP)
        done = self._is_done()

        self.previous_hp_p1 = hp_p1
        self.previous_hp_p2 = hp_p2

        if done:
            self.game_over = True

        return state, reward, done, {}

    def _calculate_hp_delta(self, hp, previous_hp):
        return hp - previous_hp

    def _is_done(self):
        hp_p1 = self._read_character_1_health_points()
        hp_p2 = self._read_character_2_health_points()
        done = hp_p1 == 0.0 or hp_p2 == 0.0
        return done

    def convert_input_to_key_mapping_to_lambda_function(self, input, key):
        if isinstance(key, VirtualKeyCode):
            return input, lambda: (key,)
        elif isinstance(key, tuple):
            return input, lambda: key
        else:
            return input, key

    def is_character_on_left(self):
        character_1_x = self._read_character_1_x()
        character_2_x = self._read_character_2_x()
        return character_1_x <= character_2_x

    def _read_character_1_x(self):
        character_1_x_base_address = read_pointer(self.process, self.process.base_address + 0x03CFDCD0, (
            0x288,
            0x1F0,
            0x58,
            0x18,
            0x290,
            0x50
        ))
        character_1_x = self.process.read_float(character_1_x_base_address + 0xC0)
        return character_1_x

    def _read_character_1_y(self):
        character_1_y_base_address = read_pointer(self.process, self.process.base_address + 0x03CFDCD0, (
            0x288,
            0x1F0,
            0x58,
            0x18,
            0x290,
            0x50
        ))
        character_1_y = self.process.read_float(character_1_y_base_address + 0xC8)
        return character_1_y

    def _read_character_1_move_time_left(self):
        character_1_move_time_left_base_address = read_pointer(self.process, self.process.base_address + 0x03CFDCD0, (
            0x288,
            0x1F0,
            0x20,
            0x998,
            0x1B8,
            0x18
        ))
        character_1_move_time_left = self.process.read_int(character_1_move_time_left_base_address + 0x914)
        return character_1_move_time_left

    def _read_character_1_move_id(self):
        character_1_move_id_base_address = read_pointer(self.process, self.process.base_address + 0x03CFDCD0, (
            0x288,
            0x1F0,
            0x20,
            0x998,
            0x1B8,
            0x18
        ))
        character_1_move_id = self.process.read_int(character_1_move_id_base_address + 0x9E0)
        return character_1_move_id

    def _read_character_1_standing_crouching_jumping(self):
        character_1_standing_crouching_jumping_base_address = read_pointer(self.process,
                                                                           self.process.base_address + 0x03CFDCD0, (
                                                                               0x288,
                                                                               0x1F0,
                                                                               0x20,
                                                                               0x998,
                                                                               0x1B8,
                                                                               0x18
                                                                           ))
        character_1_standing_crouching_jumping = self.process.read_int(
            character_1_standing_crouching_jumping_base_address + 0xCBC)
        return character_1_standing_crouching_jumping

    def _read_character_1_critical_art_bar(self):
        character_1_critical_art_bar_base_address = read_pointer(self.process,
                                                                 self.process.base_address + 0x03CFDCD0, (
                                                                     0x288,
                                                                     0x1F0,
                                                                     0x20,
                                                                     0x998,
                                                                     0x1B8,
                                                                     0x18
                                                                 )
                                                                 )
        character_1_critical_art_bar = self.process.read_int(character_1_critical_art_bar_base_address + 0xCE4)
        return character_1_critical_art_bar

    def _read_character_1_v_bar(self):
        character_1_v_bar_base_address = read_pointer(self.process,
                                                      self.process.base_address + 0x03CFDCD0, (
                                                          0x288,
                                                          0x1F0,
                                                          0x20,
                                                          0x998,
                                                          0x1B8,
                                                          0x18
                                                      )
                                                      )
        character_1_v_bar = self.process.read_int(character_1_v_bar_base_address + 0xCEC)
        return character_1_v_bar

    def _read_character_1_v_trigger_active_time_left(self):
        character_1_v_trigger_active_time_left_base_address = read_pointer(self.process,
                                                                           self.process.base_address + 0x03CFDCD0, (
                                                                               0x288,
                                                                               0x1F0,
                                                                               0x20,
                                                                               0x998,
                                                                               0x1B8,
                                                                               0x18
                                                                           ))
        character_1_v_trigger_active_time_left = self.process.read_int(
            character_1_v_trigger_active_time_left_base_address + 0xCF0
        )
        return character_1_v_trigger_active_time_left

    def _read_character_1_health_points(self):
        character_1_health_points_base_address = read_pointer(self.process, self.process.base_address + 0x03CFDCD0, (
            0x170,
            0x0,
            0x70,
            0x50,
            0x258,
            0x30
        ))
        character_1_health_points = self.process.read_int(
            character_1_health_points_base_address + 0x47C
        )
        return character_1_health_points

    def _read_character_2_x(self):
        character_2_x_base_address = read_pointer(self.process, self.process.base_address + 0x03CFDCD0, (
            0x288,
            0x200,
            0x58,
            0x18,
            0x3E0,
            0x10
        ))
        character_2_x = self.process.read_float(character_2_x_base_address + 0xC0)
        return character_2_x

    def _read_character_2_y(self):
        character_2_y_base_address = read_pointer(self.process, self.process.base_address + 0x03CFDCD0, (
            0x288,
            0x200,
            0x58,
            0x18,
            0x3E0,
            0x10
        ))
        character_2_y = self.process.read_float(character_2_y_base_address + 0xC8)
        return character_2_y

    def _read_character_2_move_time_left(self):
        character_2_move_time_left_base_address = read_pointer(self.process, self.process.base_address + 0x03A15660, (
            0x8,
            0x48,
            0x28,
            0x10,
            0x98,
            0xF0
        ))
        character_2_move_time_left = self.process.read_int(character_2_move_time_left_base_address + 0x91C)
        return character_2_move_time_left

    def _read_character_2_move_id(self):
        character_2_move_id_base_address = read_pointer(self.process, self.process.base_address + 0x03A15660, (
            0x8,
            0x48,
            0x28,
            0x10,
            0x98,
            0xF0
        ))
        character_2_move_id = self.process.read_int(character_2_move_id_base_address + 0x9E8)
        return character_2_move_id

    def _read_character_2_standing_crouching_jumping(self):
        character_2_standing_crouching_jumping_base_address = read_pointer(
            self.process,
            self.process.base_address + 0x03A15660, (
                0x8,
                0x48,
                0x28,
                0x10,
                0x98,
                0xF0
            )
        )
        character_2_standing_crouching_jumping = self.process.read_int(
            character_2_standing_crouching_jumping_base_address + 0xCC4
        )
        return character_2_standing_crouching_jumping

    def _read_character_2_critical_art_bar(self):
        character_2_critical_art_bar_base_address = read_pointer(self.process,
                                                                 self.process.base_address + 0x03A15660, (
                                                                     0x8,
                                                                     0x48,
                                                                     0x28,
                                                                     0x10,
                                                                     0x98,
                                                                     0xF0
                                                                 )
                                                                 )
        character_2_critical_art_bar = self.process.read_int(character_2_critical_art_bar_base_address + 0xCEC)
        return character_2_critical_art_bar

    def _read_character_2_v_bar(self):
        character_2_v_bar_base_address = read_pointer(self.process,
                                                      self.process.base_address + 0x03A15660, (
                                                          0x8,
                                                          0x48,
                                                          0x28,
                                                          0x10,
                                                          0x98,
                                                          0xF0
                                                      )
                                                      )
        character_2_v_bar = self.process.read_int(character_2_v_bar_base_address + 0xCF4)
        return character_2_v_bar

    def _read_character_2_v_trigger_active_time_left(self):
        character_2_v_trigger_active_time_left_base_address = read_pointer(
            self.process,
            self.process.base_address + 0x03A15660, (
                0x8,
                0x48,
                0x28,
                0x10,
                0x98,
                0xF0
            )
        )
        character_2_v_trigger_active_time_left = self.process.read_int(
            character_2_v_trigger_active_time_left_base_address + 0xCF8
        )
        return character_2_v_trigger_active_time_left

    def _read_character_2_health_points(self):
        character_2_health_points_base_address = read_pointer(self.process, self.process.base_address + 0x03CFDCD0, (
            0x170,
            0x0,
            0x70,
            0x50,
            0x98,
            0xA8
        ))
        character_2_health_points = self.process.read_int(
            character_2_health_points_base_address + 0x11C
        )
        return character_2_health_points

    def _read_state(self):
        return {
            'character_1': {
                'x': self._read_character_1_x(),
                'y': self._read_character_1_y(),
                'move_time_left': self._read_character_1_move_time_left(),
                'move_id': self._read_character_1_move_id(),
                'standing_crouching_jumping': self._read_character_1_standing_crouching_jumping(),
                'critical_art_bar': self._read_character_1_critical_art_bar(),
                'v_bar': self._read_character_1_v_bar(),
                'v_trigger_active_time_left': self._read_character_1_v_trigger_active_time_left(),
                'health_points': self._read_character_1_health_points()
            },
            'character_2': {
                'x': self._read_character_2_x(),
                'y': self._read_character_2_y(),
                'move_time_left': self._read_character_2_move_time_left(),
                'move_id': self._read_character_2_move_id(),
                'standing_crouching_jumping': self._read_character_2_standing_crouching_jumping(),
                'critical_art_bar': self._read_character_2_critical_art_bar(),
                'v_bar': self._read_character_2_v_bar(),
                'v_trigger_active_time_left': self._read_character_2_v_trigger_active_time_left(),
                'health_points': self._read_character_2_health_points()
            }
        }

    def _generate_state_for_character(self, character):
        MAX_MOVE_TIME_LEFT = 65536000
        if character['move_time_left'] > MAX_MOVE_TIME_LEFT:
            print('bigger move time left:', character['move_time_left'])
        return (
                self._normalize(character['x'], MIN_X, MAX_X),
                self._normalize(character['y'], MIN_Y, MAX_Y),
                self._normalize(character['move_time_left'], 0, MAX_MOVE_TIME_LEFT)
            ) + \
            move_to_embedding(character['move_id']) + \
            standing_crouching_jumping_to_embedding(character['standing_crouching_jumping']) + \
            (
                self._normalize(character['critical_art_bar'], 0, 900),
                self._normalize(character['v_bar'], 0, 600),
                self._normalize(character['v_trigger_active_time_left'], 0, 1000),
                self._normalize(character['health_points'], 0, 500)
            )

    def _get_state(self):
        state = self._read_state()

        return np.array(
            self._generate_state_for_character(state['character_1']) +
            self._generate_state_for_character(state['character_2']) +
            (
                self._normalize(abs(state['character_2']['x'] - state['character_1']['x']), 0, MAX_X_DISTANCE),
                self._normalize(abs(state['character_2']['y'] - state['character_1']['y']), 0, MAX_Y_DISTANCE),
                self._normalize(
                    math.sqrt(
                        (state['character_2']['x'] - state['character_1']['x']) ** 2 +
                        (state['character_2']['y'] - state['character_1']['y']) ** 2
                    ),
                    0,
                    MAX_DISTANCE
                )
            )
            # TODO: Time left when using environment with round format where time left has a significance
            #       regarding decision making.
        )

    def _normalize(self, value, min, max):
        return (value - min) / float(max - min)

    def reset(self):
        while windll.user32.GetForegroundWindow() != self.hwnd:
            time.sleep(1)
        # cdll.ntdll.NtResumeProcess(self.process_handle)

        if (
            self._read_character_1_health_points() > 0 and
            self._read_character_2_health_points() > 0
        ):
            self.key_pressing.press(VirtualKeyCode.RETURN)
            time.sleep(1)
            self.key_pressing.press(VirtualKeyCode.B)

        while not (
            self._read_character_1_health_points() == RYU_TOTAL_HP and
            self._read_character_2_health_points() == RYU_TOTAL_HP
        ):
            time.sleep(DURATION_BETWEEN_FRAMES)

        self.game_over = False
        self._set_previous_hp_to_current_hp()

        # cdll.ntdll.NtSuspendProcess(self.process_handle)
        state = self._get_state()
        return state

    def render(self, mode='human'):
        pass

    def close(self):
        pass

env = StreetFighterVEnv()
time.sleep(3)
env.reset()

# suspend and resume
# PROCESS_SUSPEND_RESUME = 0x0800
# process = pymem.Pymem('StreetFighterV.exe')
# print(process)
# process_handle = cdll.kernel32.OpenProcess(PROCESS_SUSPEND_RESUME, False, process.process_id)
# cdll.ntdll.NtSuspendProcess(process_handle)
# print('')
# cdll.ntdll.NtResumeProcess(process_handle)

# starting process:
# startup_info = STARTUPINFO()
# CreateProcess(
#     None,
#     program_path,
#     None,
#     None,
#     False,
#     0,
#     None,
#     None,
#     startup_info
# )


# use the model to predict an image/set of images

import dlib
from tensorflow.keras.models import load_model
import cv2
import os
import numpy as np
from preprocessor import Preprocessor

races = {0: 'white', 1: 'black', 2: 'asian', 3: 'indian'}

model = load_model('models\cnn_vgg_256.h5')
file_path = os.path.abspath(os.path.dirname(__file__))
print(1)

input_folder = 'original'
output_folder = 'classified'
preprocessor = Preprocessor(file_path)
imgs_array = preprocessor.batch_preprocess(input_folder)
print(imgs_array)
print(2)

for img_path, img_array in imgs_array.items():
    img_name = os.path.basename(img_path)
    print(img_name)
    if img_array == 'faceless' or img_array == 'corrupted':
        os.rename(img_path, output_folder + '\\{}\\'.format(img_array) + img_name)
        continue

    prediction = model.predict(img_array, verbose=1)
    predicted_class = prediction.argmax(axis=-1)
    prediction = np.around(prediction[0], decimals=3)
    os.rename(img_path, output_folder + '\\{}\\'.format(races[predicted_class]) + img_name)


import numpy as np
import cv2
import os
import torch
from torch.utils.data import Dataset
from dataset import ssd_augmentations_LVD_net
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rcParams["font.sans-serif"] = ["SimHei"]
mpl.rcParams["axes.unicode_minus"]=False

# mean = np.array([0.40789654, 0.44719302, 0.47026115],
#                     dtype=np.float32).reshape(1, 1, 3)
# std = np.array([0.28863828, 0.27408164, 0.27809835],
#                    dtype=np.float32).reshape(1, 1, 3)
mean = (103, 113, 119)

class DetracDataset(Dataset):
	def __init__(self, list_path, ignore_region_path, labels_path, img_size, is_training, is_debug=False, batch_size=16):
		self.img_files = []
		self.label_files = []
		self.batch_size = batch_size
		self.mean = [103, 113, 119]
		for path in open(list_path, 'r'): #'vehecal/labels'
			path_split = path.split('/')
			index_jpg = path_split[-1][3:]
			video_name = path_split[-2]
			label_path = os.path.join(labels_path, video_name+index_jpg.replace('.jpg', '.txt')).strip()
			if os.path.isfile(label_path):
				self.img_files.append(path)
				self.label_files.append(label_path)
			else:
				print("no label found. skip it: {}".format(path))
		# 提取视频目录下的掩模
		self.ignore_region = dict()
		for ignore_region_line in open(ignore_region_path):
			line_split = ignore_region_line.strip().split(" ")
			key = line_split[0]
			len_box = (len(line_split) - 1) / 4
			remainder = len_box - int(len_box)
			assert remainder == 0, "ignore_region标签有误！"
			values = []
			for i in range(int(len_box)):
				ignore_box = line_split[1+4*i:5+4*i]
				x1, y1, w, h = [float(coor) for coor in ignore_box]
				x2 = x1 + w
				y2 = y1 + h
				values.append([int(x1), int(y1), int(x2), int(y2)])
			self.ignore_region[key] = values

		self.is_debug = is_debug

		#  transforms and augmentation
		self.total_transforms = ssd_augmentations_LVD_net.Compose(transforms=[])

		# self.transforms.add(data_transforms.KeepAspect())

		if is_training:
			self.total_transforms.add(ssd_augmentations_LVD_net.SSDAugmentation(mean=mean))
			if img_size is None:
				self.total_transforms.add(ssd_augmentations_LVD_net.ResizeImage_multi_scale(batch_size=self.batch_size, mean=mean))
			else:
				self.total_transforms.add(ssd_augmentations_LVD_net.ResizeImage_single_scale(new_size=img_size, mean=mean))
		else:
			self.total_transforms.add(ssd_augmentations_LVD_net.ToAbsoluteCoords())
			self.total_transforms.add(ssd_augmentations_LVD_net.xywh_to_xyxy())
			self.total_transforms.add(ssd_augmentations_LVD_net.ResizeImage_single_scale(new_size=img_size, mean=mean))
		self.total_transforms.add(ssd_augmentations_LVD_net.xyxy_to_xywh())
		self.total_transforms.add(ssd_augmentations_LVD_net.ToPercentCoords())
		self.total_transforms.add(ssd_augmentations_LVD_net.ToTensor(self.is_debug))

	def index_2_classes(self, classes):
		index = [i for i in range(len(classes))]
		index_2_class = dict(zip(index, classes))
		return index_2_class
	def classes_2_index(self, classes):
		index = [i for i in range(len(classes))]
		class_2_index = dict(zip(classes, index))
		return class_2_index

	def __getitem__(self, index):
		#print('index='+str(index))
		img_path = self.img_files[index].rstrip()
		img_path_split = img_path.split('/')
		video_name = img_path_split[-2]
		ignore_boxes = self.ignore_region[video_name]
		img = cv2.imread(img_path, cv2.IMREAD_COLOR)
		if img is None:
			raise Exception("Read image error: {}".format(img_path))
		img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)#加入训练的数据是RGB格式的

		ori_h, ori_w = img.shape[:2]
		###############加入掩模##########################################
		for boxes in ignore_boxes:
			x1, y1, x2, y2 = boxes
			image_mask = np.ones((y2 - y1, x2 - x1, 3)).astype(np.uint8)
			for i in range(3):
				image_mask[:, :, i] = image_mask[:, :, i] * mean[i]
				img[y1:y2, x1:x2, :] = image_mask

		label_path = self.label_files[index].rstrip()
		if os.path.exists(label_path):
			labels = np.loadtxt(label_path, dtype=np.float32).reshape(-1, 8)#np.loadtxt可以直接加载文本中具有固定格式的文字数据
			assert labels.shape[0] != 0
		else:
			print("label does not exist: {}".format(label_path))
			labels = np.zeros((1, 8), np.float32)
		image = img
		box = labels[:, 1:5]
		label = labels[:, 0].reshape(-1, 1)
		# truncation_ratio = labels[:, 5].reshape(-1, 1)
		# overlap_ratio = labels[:, 6].reshape(-1, 1)
		# iou_ratio = labels[:, 7].reshape(-1, 1)

		if self.total_transforms is not None:
			image, box, label = self.total_transforms(image, box, label)

		#######################################下面可以添加label中除了bbox，label的其他属性用于可视化###############3
		# truncation_ratio = torch.from_numpy(truncation_ratio).type(torch.FloatTensor)
		# overlap_ratio = torch.from_numpy(overlap_ratio).type(torch.FloatTensor)
		# iou_ratio = torch.from_numpy(iou_ratio).type(torch.FloatTensor)
		#label = torch.cat((label, box, truncation_ratio, overlap_ratio, iou_ratio), 1)
		label = torch.cat((label, box), 1)
		img_path_split = img_path.split('/')
		img_ind = img_path_split[-2]+img_path[-9:-4]
		origin_size = [ori_h, ori_w]
		return image, label, img_ind, origin_size

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

	def collate_fn(self, batch):
		'''Pad images and encode targets.

			As for images are of different sizes, we need to pad them to the same size.

			Args:
			  batch: (list) of images, cls_targets, loc_targets.

			Returns:
			  padded images, stacked cls_targets, stacked loc_targets.

			Reference:
			  https://github.com/rbgirshick/py-faster-rcnn/blob/master/lib/utils/blob.py
			'''
		batchsize = len(batch)
		image = [x[0] for x in batch]
		label = [x[1] for x in batch]
		img_ind = [x[2] for x in batch]
		origin_size = [x[3] for x in batch]
		max_objects = max([x.shape[0] for x in label])
		label_new = []
		for i in range(batchsize):
			label_len = label[i].shape[0]
			#填满label标签
			filled_labels = torch.zeros(max_objects, label[0].shape[1]).type(torch.FloatTensor)
			filled_labels[range(max_objects)[:label_len]] = label[i][:]
			label_new.append(filled_labels)
		sample = {'image': torch.stack(image), 'label': torch.stack(label_new), 'img_ind': img_ind, "origin_size": origin_size}
		return sample

class Statistic_scale:
	def __init__(self, continus_assign_scale, size_scale, index="_"):
		"""
		:param continus_assign_scale:
		:param size_scale:
		:param index: 代表所要保存的图片的特征标识
		"""
		self.index = index
		self.assign_scales = continus_assign_scale
		self.assign_area_scales = [[assign_scale[0]**2, assign_scale[1]**2] for assign_scale in self.assign_scales]
		self.size_scale= size_scale

		self.scaled_area_data_x = np.arange(1, len(continus_assign_scale)+1+1)
		self.scaled_max_length_data_x = np.arange(1, len(continus_assign_scale)+1+1)
		self.max_length_data_x = np.arange(1, 641)
		self.statistics_scaled_area_data = np.zeros(len(continus_assign_scale)+1)
		self.statistics_max_length_data = np.zeros(640)
		self.statistics_scaled_max_length_data = np.zeros(len(continus_assign_scale)+1)
		self.boxes_numbers = 0
		self.collection_unassign_length = []

	def statistics_max_length(self, max_length):
		max_length = int(max_length)
		if max_length < 1:
			pass
		else:
			if max_length == 640:
				max_length = max_length-1
			self.statistics_max_length_data[max_length] += 1

	def statistics_scaled_area(self, area):
		assign = False
		for i, assign_area_scale in enumerate(self.assign_area_scales):
			if assign_area_scale[0] <= area < assign_area_scale[1]:
				self.statistics_scaled_area_data[i] += 1
				assign = True
				break
		if assign == False:
			self.statistics_scaled_area_data[i] += 1
		pass

	def statistics_scaled_max_length(self, max_length):
		assign = False
		for i, assign_scale in enumerate(self.assign_scales):
			if assign_scale[0] <= max_length < assign_scale[1]:
				self.statistics_scaled_max_length_data[i] += 1
				assign = True
				break
		if assign == False:
			self.statistics_scaled_max_length_data[-1] += 1
			self.collection_unassign_length.append(max_length)

	def run(self, max_length, area):
		self.statistics_scaled_area(area)
		self.statistics_max_length(max_length)
		self.statistics_scaled_max_length(max_length)

	def only_run_max_length(self, max_length):
		self.statistics_max_length(max_length)

	def only_show_max_length(self):
		plt.figure(1, figsize=[5, 3])
		ax1 = plt.subplot(1,1,1)
		plt.sca(ax1)
		ax1.xaxis.set_major_locator(plt.MultipleLocator(128))
		plt.bar(self.max_length_data_x, self.statistics_max_length_data, align="center", color="c")
		plt.xlabel("Distribution of the longest side")
		plt.ylabel("Amount")
		plt.tight_layout()
		plt.savefig("./Distribution_of_the_longest_side"+self.index+".png", dpi=300)
		plt.show()
		plt.close()

	def show(self):
		self.show_percent()

		plt.figure(1, figsize=[9, 3])
		ax1 = plt.subplot(1, 3, 1)
		ax2 = plt.subplot(1, 3, 2)
		ax3 = plt.subplot(1, 3, 3)

		plt.sca(ax1)
		ax1.xaxis.set_major_locator(plt.MultipleLocator(128))
		plt.bar(self.max_length_data_x, self.statistics_max_length_data, align="center", color="c")
		plt.xlabel("Distribution of the longest side")
		plt.ylabel("Amount")

		plt.sca(ax2)
		ax2.xaxis.set_major_locator(plt.MultipleLocator(1))
		plt.bar(self.scaled_area_data_x, self.statistics_scaled_area_data, align="center", color="c")
		plt.xlabel("Area distribution in multiple ranges")
		plt.ylabel("Amount")

		plt.sca(ax3)
		ax3.xaxis.set_major_locator(plt.MultipleLocator(1))
		plt.bar(self.scaled_max_length_data_x, self.statistics_scaled_max_length_data, align="center", color="c")
		plt.xlabel("Long-side distribution in multiple ranges")
		plt.ylabel("Amount")

		plt.tight_layout()

		plt.savefig("./assign_scale_result"+self.index+".png", dpi=300)
		#plt.show()
		plt.close()

	def show_percent(self):
		print(list(self.statistics_scaled_area_data/np.sum(self.statistics_scaled_area_data)))

#  use for test dataloader
if __name__ == "__main__":
	import sys
	from tqdm import tqdm
	MY_DIRNAME = os.path.dirname(os.path.abspath(__file__))
	sys.path.insert(0, os.path.join(MY_DIRNAME, '..'))
	import train.Detrac_data_preprocess.params_init_detrac_LFFD as params_init
	classes_category = params_init.TRAINING_PARAMS["model"]["classes_category"]
	vocdataset = DetracDataset(list_path="../data/detrac/train.txt", ignore_region_path="../data/detrac/train_ignore_region.txt",
							   labels_path='../data/detrac/labels',
							   img_size=(640, 640), is_training=False, is_debug=True, batch_size=8)
	index_2_classes = vocdataset.index_2_classes(classes_category)
	dataloader = torch.utils.data.DataLoader(vocdataset,
											 batch_size=8,
											 shuffle=False, num_workers=0, pin_memory=False, collate_fn=vocdataset.collate_fn)
	print(len(vocdataset))

	#continue_assign_scale = [[10, 15], [15, 20], [20, 40], [40, 70], [70, 110], [110, 250], [250, 400], [400, 560]]
	continue_assign_scale_3_cluster = [[17.5, 44], [44, 72], [72, 300]]
	continue_assign_scale_4_cluster = [[17.5, 41.5], [41.5, 51.5], [51.5, 66], [66, 300]]
	continue_assign_scale_5_cluster = [[], [], [], [], []]
	continue_assign_scale_3_cluster_train = [[15, 50], [50, 100], [100, 260]]
	continue_assign_scale_4_cluster_train = [[15, 45], [45, 75], [75, 135], [135, 260]]
	continue_assign_scale_5_cluster_train = [[15, 40], [40, 60], [60, 100], [100, 150], [150, 260]]
	# continue_assign_scale_cluster_train = [continue_assign_scale_3_cluster_train, continue_assign_scale_4_cluster_train
	# 									   , continue_assign_scale_5_cluster_train]
	continue_assign_scale_cluster_train = [continue_assign_scale_4_cluster_train]
	size_scale = 640
	for i, continue_assign_scale in enumerate(continue_assign_scale_cluster_train):
		statisticser = Statistic_scale(continue_assign_scale, size_scale, index="_train_"+str(i+3)+"_cluster")
		for step, sample in tqdm(enumerate(dataloader)):
			#print(step)
			for i, (image, label) in enumerate(zip(sample['image'], sample['label'])):
				image = image.numpy()
				label = label.numpy()
				h, w = image.shape[:2]
				for l in label:
					if l.sum() == 0:
						continue
					gt_h = l[4] * h
					gt_w = l[3] * w

					area = gt_h * gt_w
					max_length = max(gt_h, gt_w)

					#statisticser.run(max_length, area)
					statisticser.only_run_max_length(max_length)
		#statisticser.show()
		statisticser.only_show_max_length()
		#print(statisticser.collection_unassign_length)


"""Test the enrichment of the entire dataset, or specific clusters against gene ontologies associated with complexes"""

import re
import os
import pandas as pd
import numpy as np

from collections import defaultdict
from scipy import stats
from utilities.database_map_and_filter import ortholog_map, uniprot_go_genes
from utilities.statistical_tests import fischers_test, apply_oneway_anova

from loguru import logger
from GEN_Utils import FileHandling

logger.info('Import OK')

cluster_path = f'results/lysate_denaturation/clustering/clustered.xlsx'
background_path = f'results/lysate_denaturation/normalised/normalised_summary.xlsx'
ontology_path = 'results/lysate_denaturation/gene_ontology_datasets/go_term_summary.xlsx'
size_path = 'results/lysate_denaturation/gene_ontology_datasets/size_summary.xlsx'
output_folder = 'results/lysate_denaturation/go_enrichment/'
resource_folder = 'resources/bioinformatics_databases/'

if not os.path.exists(output_folder):
    os.makedirs(output_folder)

# -----------------Read in standard components-----------------
# clustered data
clustered_data = pd.read_excel(f'{cluster_path}', sheet_name='summary')
clustered_data.drop([col for col in clustered_data.columns.tolist() if 'Unnamed: ' in col], axis=1, inplace=True)

# raw data as background
background_genes = pd.read_excel(f'{background_path}', sheet_name='raw')['Proteins'].unique()

# read in ontologies
ontology_genes = pd.read_excel(f'{ontology_path}')
ontology_genes.drop([col for col in ontology_genes.columns.tolist() if 'Unnamed: ' in col], axis=1, inplace=True)
ontology_genes.drop('homologue_id', inplace=True, axis=1)

# -----------Test ontologies against each filter type with Fischers-----------
filter_types = ['mixed', 'unique', 'count']
results = []
for col in ontology_genes.columns.tolist():
    go_genes = ontology_genes[col].dropna().tolist()
    contingencies = {}
    test_results = {}
    for filter_name in filter_types:
        for group, df in clustered_data.groupby(filter_name):
            hit_genes = df['Proteins'].dropna()
            oddsratio, pval, test_result = fischers_test(go_genes, hit_genes, background_genes, output_folder=None)
            test_results[f'{filter_name}_{group}'] = [oddsratio, pval]
            contingencies[f'{filter_name}_{group}'] = test_result
    test_results = pd.DataFrame(test_results, index=['oddsratio', 'pval']).T
    test_results['ontology'] = col
    results.append(test_results)

    FileHandling.df_to_excel(output_path=f'{output_folder}{col}_fisher.xlsx', data_frames=[test_results]+list(contingencies.values()), sheetnames=['test_results']+list(contingencies.keys()))

go_summary_results = pd.concat(results)
FileHandling.df_to_excel(output_path=f'{output_folder}clustered_goterm_enrichment.xlsx', data_frames=[go_summary_results], sheetnames=['test_results'])

# ----------------Perform Panther enrichment test----------------
from utilities.statistical_tests import apply_enrichment
# read in clustered data, and background data - this uses all the proteins that contained identified cys peptides
combinations = pd.read_excel(f'{cluster_path}', sheet_name=['proteins_mixed', 'proteins_unique', 'proteins_multiple'])
ref_list = pd.read_excel(f'{background_path}', sheet_name='raw')['Proteins'].unique()

# perform enrichment test
protein_enrichment = {}
for combination, df in combinations.items():
    df.drop([col for col in df if 'Unnamed: ' in str(col)], axis=1, inplace=True)
    protein_enrichment[combination] = apply_enrichment(df, searches=None, obo_path=f'{resource_folder}PANTHERGOslim.obo', organism='10090', refOrganism='10090', enrichmentTestType='FISHER', correction='FDR', min_proteins=2, reference=ref_list)

# Save all to excel
FileHandling.df_to_excel(
    output_path=f'{output_folder}cluster_enrichment.xlsx',
    sheetnames= list(protein_enrichment.keys()),
    data_frames= list(protein_enrichment.values())
    )


# ------------------------------------------------------------------------------
# Copyright (c) Microsoft
# Licensed under the MIT License.
# Written by Bin Xiao (Bin.Xiao@microsoft.com)
# ------------------------------------------------------------------------------
'''
gradnorm function
'''
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import time
import logging
import os

import numpy as np
import torch

from core.evaluate import accuracy
from core.inference import get_final_preds
from utils.transforms import flip_back
from utils.vis import save_debug_images

logger = logging.getLogger(__name__)

def train(config, train_loader, model, criterion, optimizer, epoch,
          output_dir, tb_log_dir, lossweight,writer_dict,):
    # print(lossweight,type(lossweight))
    batch_time = AverageMeter()  # 【l】AverageMeter计算平均值用
    data_time = AverageMeter()
    losses = AverageMeter()  # 一个epoch重置一次
    acc = AverageMeter()
    # lossweight = [1,1,1,1,1,1,1,1,1,1,1]#传进来吧
    alpha = config.MODEL.alpha
    n_tasks = config.MODEL.NUM_JOINTS
    # switch to train mode
    model.train()  # 【see】

    end = time.time()
    for i, (input, target, target_weight, meta) in enumerate(train_loader):
        # 本函数后面的全在for里
        # 【c】train_loader在哪儿定义的
        # measure data loading time
        data_time.update(time.time() - end)  #什么作用，和batchtime有啥不同:读取时间

        # compute output
        input = input.cuda()  # 【c】自己加的
        outputs = model(input)  # 由loader而得的input

        target = target.cuda(non_blocking=True)
        target_weight = target_weight.cuda(non_blocking=True)

#放在要debug的位置前
        # import os
        # debug_file = './tmp/debug'#怎么写到config里
        # if os.path.exists(debug_file):
        #     import ipdb;
        #     ipdb.set_trace()

        if isinstance(outputs, list):  # 模型输出是列表？：中继损失
            loss = criterion(outputs[0], target, target_weight)#如果是列表的话三分支对应不同的权重或者三个的append到一起
            for output in outputs[1:]:
                loss += criterion(output, target, target_weight)  # 【】对应位置相加
        else:
            output = outputs
            task_loss = criterion(output, target, target_weight)
            task_loss = torch.stack(task_loss)#[c]

        weighted_task_loss = torch.mul(lossweight, task_loss)
        if i == 0:
            # set L(0)
            initial_task_loss = task_loss.data
            initial_task_loss = initial_task_loss.cpu().numpy()  # 【】干嘛的
        loss = torch.sum(weighted_task_loss)# 权重loss和
        # compute gradient and do update step
        optimizer.zero_grad()
        loss.backward(retain_graph=True)
        print(type(lossweight))
        if i != 0:
            lossweight.grad.data = lossweight.grad.data * 0.0

        # get layer of shared weights
        W = model.module.get_last_shared_layer()  #【】就是最后一层特征图finallayer前的

        # get the gradient norms for each of the tasks
        # G^{(i)}_w(t)
        norms = []
        for i in range(len(task_loss)):  # 所以是列表？之前是
                # get the gradient of this task loss with respect to the shared parameters
            gygw = torch.autograd.grad(
                task_loss[i], W.parameters(), retain_graph=True)
            # compute the norm
            norms.append(torch.norm(
                torch.mul(lossweight[i], gygw[0])))  # weights怎来
        norms = torch.stack(norms)  # 压成一个
        # print('G_w(t): {}'.format(norms))

        # compute the inverse training rate r_i(t)
        # \curl{L}_i
        if torch.cuda.is_available():
            loss_ratio = task_loss.data.cpu().numpy() / initial_task_loss #【】为啥用cpu
        else:
            loss_ratio = task_loss.data.numpy() / initial_task_loss
            # r_i(t)
        inverse_train_rate = loss_ratio / np.mean(loss_ratio)
        # print('r_i(t): {}'.format(inverse_train_rate))

        # compute the mean norm \tilde{G}_w(t)
        if torch.cuda.is_available():
            mean_norm = np.mean(norms.data.cpu().numpy())
        else:
            mean_norm = np.mean(norms.data.numpy())
            # print('tilde G_w(t): {}'.format(mean_norm))

            # compute the GradNorm loss
            # this term has to remain constant
        # constant_term = torch.tensor(
            # mean_norm * (inverse_train_rate ** alpha), requires_grad=False)
        constant_term = torch.tensor(
            mean_norm * (inverse_train_rate ** alpha), requires_grad=False)
        if torch.cuda.is_available():
            constant_term = constant_term.cuda()
            # print('Constant term: {}'.format(constant_term))
            # this is the GradNorm loss itself
        # grad_norm_loss = torch.tensor(
        #     torch.sum(torch.abs(norms - constant_term)),requires_grad=True)
        grad_norm_loss = torch.sum(torch.abs(norms - constant_term))#[]gai
        # print('GradNorm loss {}'.format(grad_norm_loss))

        # compute the gradient for the weights
        print(grad_norm_loss.requires_grad,lossweight.requires_grad)
        lossweight.grad = torch.autograd.grad(
            grad_norm_loss, lossweight)[0]

        # lossweight是全局？
        optimizer.step()

        # measure accuracy and record loss
        losses.update(loss.item(), input.size(0))  # 添加损失和输入batch
        # 训练时一个batch就计算了下acc
        _, avg_acc, cnt, pred = accuracy(output.detach().cpu().numpy(),
                                    target.detach().cpu().numpy())
        acc.update(avg_acc, cnt)  # 【】统计到

        # measure elapsed time
        batch_time.update(time.time() - end)  # 一个batch模型训练的时间
        end = time.time()

        if i % config.PRINT_FREQ == 0:
            msg = 'Epoch: [{0}][{1}/{2}]\t' \
                'Time {batch_time.val:.3f}s ({batch_time.avg:.3f}s)\t' \
                'Speed {speed:.1f} samples/s\t' \
                'Data {data_time.val:.3f}s ({data_time.avg:.3f}s)\t' \
                'Loss {loss.val:.5f} ({loss.avg:.5f})\t' \
                'Accuracy {acc.val:.3f} ({acc.avg:.3f})'.format(
                  # 【see】batchtime的输出
                    epoch, i, len(train_loader), batch_time=batch_time,
                    speed=input.size(0)/batch_time.val,
                    data_time=data_time, loss=losses, acc=acc)
            # epoch-迭代-总迭代数 当前迭代所花时间，整个epoch累计平均每迭代所花时间；
            # len(train_loader)train_loader是把整个dataset分成迭代元素了，一次传一个迭代给网络。总的长度是总的迭代个数向上取整
            logger.info(msg)  # 【c】之前好像用的其他方式统计log

            writer = writer_dict['writer']  # 【c】这在哪儿定义的
            global_steps = writer_dict['train_global_steps']
            writer.add_scalar('train_loss', losses.val,
                          global_steps)  # 【c】writer是个什么类型
            writer.add_scalar('train_acc', acc.val, global_steps)
            writer_dict['train_global_steps'] = global_steps + 1  # 更新总步数

            prefix = '{}_{}'.format(os.path.join(
                output_dir, 'train'), i)  # prefix保存debug图的路径
            save_debug_images(config, input, meta, target, pred*4, output,
                          prefix)  # 【c】pred*4干嘛，加强颜色？
    # print(lossweight,i)
    normalize_coeff = n_tasks / torch.sum(lossweight.data, dim=0) #【see】写在迭代还是epoch里
    lossweight.data = lossweight.data * normalize_coeff  # 迭代完一次
    return lossweight

def validate(config, val_loader, val_dataset, model, criterion, output_dir,
             tb_log_dir,lossweight, writer_dict=None):
    batch_time = AverageMeter()
    losses = AverageMeter()
    acc = AverageMeter()

    # switch to evaluate mode
    model.eval()

    num_samples = len(val_dataset)  # 不用loader?用，这儿只是统计下总样本数
    all_preds = np.zeros(
        (num_samples, config.MODEL.NUM_JOINTS, 3),
        dtype=np.float32
    )  # 【c】size
    all_boxes = np.zeros((num_samples, 6))  # 【c】
    image_path = []
    filenames = []
    imgnums = []
    idx = 0
    with torch.no_grad():  # 后面的全是
        end = time.time()
        for i, (input, target, target_weight, meta) in enumerate(val_loader):
            # compute output
            outputs = model(input)
            if isinstance(outputs, list):
                output = outputs[-1]  # 为啥是-1:只取最后一个分支的输出
            else:
                output = outputs

            if config.TEST.FLIP_TEST:
                # this part is ugly, because pytorch has not supported negative index
                # input_flipped = model(input[:, :, :, ::-1])
                input_flipped = np.flip(input.cpu().numpy(), 3).copy()
                input_flipped = torch.from_numpy(input_flipped).cuda()
                outputs_flipped = model(input_flipped)  # 【】怎么不用cuda了

                if isinstance(outputs_flipped, list):
                    output_flipped = outputs_flipped[-1]  # 【】为啥只要最后一个？不可以平均？
                else:
                    output_flipped = outputs_flipped

                output_flipped = flip_back(output_flipped.cpu().numpy(),
                                           val_dataset.flip_pairs)  # 【see】将翻转过的输入的输出变成正常形式，左手-右手-右手(out)-左手（out）
                output_flipped = torch.from_numpy(output_flipped.copy()).cuda()

                # feature is not aligned, shift flipped heatmap for higher accuracy
                if config.TEST.SHIFT_HEATMAP:  # 【】output_flipped size
                    output_flipped[:, :, :, 1:] = \
                        output_flipped.clone()[:, :, :, 0:-1]  # 【】什么作用；翻转坐标？？

                output = (output + output_flipped) * 0.5  # 【see】上下翻转会不会提高

            target = target.cuda(non_blocking=True)
            target_weight = target_weight.cuda(non_blocking=True)

            loss = criterion(output, target, target_weight) #【c】单分支和多分支都一样
            loss = torch.stack(loss)
            loss = torch.mul(lossweight, loss)

            num_images = input.size(0)
            # measure accuracy and record loss
            losses.update(loss.item(), num_images)  # 一次迭代的
            _, avg_acc, cnt, pred = accuracy(output.cpu().numpy(),
                                             target.cpu().numpy())

            acc.update(avg_acc, cnt)

            # measure elapsed time
            batch_time.update(time.time() - end)
            end = time.time()

            # 【c】meta是从哪儿获得的:valoader里来的；只有val有这个:不是，来自于mpii里的datasets里的gt_db
            c = meta['center'].numpy()
            s = meta['scale'].numpy()
            score = meta['score'].numpy()

            preds, maxvals = get_final_preds(
                config, output.clone().cpu().numpy(), c, s)  # 预测的坐标和值

            all_preds[idx:idx + num_images, :,
                      0:2] = preds[:, :, 0:2]  # 【c】preds不就是0:2吗
            all_preds[idx:idx + num_images, :, 2:3] = maxvals
            # double check this all_boxes parts
            all_boxes[idx:idx + num_images, 0:2] = c[:, 0:2]
            all_boxes[idx:idx + num_images, 2:4] = s[:, 0:2]
            all_boxes[idx:idx + num_images, 4] = np.prod(s*200, 1)  # 【l】啥意思
            all_boxes[idx:idx + num_images, 5] = score  # 【】框的score?哪儿来的？什么作用？
            image_path.extend(meta['image'])

            idx += num_images  # 已训练图片数

            if i % config.PRINT_FREQ == 0:
                msg = 'Test: [{0}/{1}]\t' \
                      'Time {batch_time.val:.3f} ({batch_time.avg:.3f})\t' \
                      'Loss {loss.val:.4f} ({loss.avg:.4f})\t' \
                      'Accuracy {acc.val:.3f} ({acc.avg:.3f})'.format(
                          i, len(val_loader), batch_time=batch_time,
                          loss=losses, acc=acc)
                logger.info(msg)

                prefix = '{}_{}'.format(
                    os.path.join(output_dir, 'val'), i
                )
                save_debug_images(config, input, meta, target, pred*4, output,
                                  prefix)  # 【】？自动保存了？没看到啊
        # 整个epoch循环完了
        name_values, perf_indicator = val_dataset.evaluate(
            config, all_preds, output_dir, all_boxes, image_path,
            filenames, imgnums
        )  # 【c】val_dataset是个什么类,evaluate函数在哪儿定义的什么作用？？

        model_name = config.MODEL.NAME
        if isinstance(name_values, list):
            for name_value in name_values:  # 【c】name_values
                _print_name_value(name_value, model_name)  # 【】干嘛的
        else:
            _print_name_value(name_values, model_name)

        if writer_dict:
            writer = writer_dict['writer']
            global_steps = writer_dict['valid_global_steps']
            writer.add_scalar(
                'valid_loss',
                losses.avg, #【】
                global_steps
            )
            writer.add_scalar(
                'valid_acc',
                acc.avg,
                global_steps
            )
            if isinstance(name_values, list):
                for name_value in name_values:
                    writer.add_scalars(
                        'valid',
                        dict(name_value),
                        global_steps
                    )
            else:
                writer.add_scalars(
                    'valid',
                    dict(name_values),
                    global_steps
                )
            writer_dict['valid_global_steps'] = global_steps + 1

    return perf_indicator  # 【】这是个啥

# markdown format output
def _print_name_value(name_value, full_arch_name):
    names = name_value.keys()  # 【c】是writer_dict吗
    values = name_value.values()
    num_values = len(name_value)
    logger.info(
        '| Arch ' +
        ' '.join(['| {}'.format(name) for name in names]) +
        ' |'
    )  # 【c】names是模型？
    logger.info('|---' * (num_values+1) + '|')

    if len(full_arch_name) > 15:
        full_arch_name = full_arch_name[:8] + '...'
    logger.info(
        '| ' + full_arch_name + ' ' +
        ' '.join(['| {:.3f}'.format(value) for value in values]) +
        ' |'
    )  # 【c】参数？

class AverageMeter(object):
    """Computes and stores the average and current value"""

    def __init__(self):
        self.reset()

    def reset(self):
        self.val = 0
        self.avg = 0
        self.sum = 0
        self.count = 0

    def update(self, val, n=1):
        self.val = val  # 当前迭代的平均
        self.sum += val * n
        self.count += n  # 当前epoch的平均
        self.avg = self.sum / self.count if self.count != 0 else 0  # 【see】


import torch
import torch.nn as nn
import torchvision.models as models
from typing import *
import copy
import PIL
from PIL import Image
import numpy as np
from pathlib import Path
from .datamodule import transform_fn
from . import utils
import onnx
import onnxruntime as ort
import numpy as np

class MobileNetClassify(object):
    def __init__(self, weight: str = None, idx2class: str = None, pretrained: bool = True, num_classes: int = 2,
                 topk=1, device: str = 'cpu'):
        self.weight = weight
        self._idx2class = idx2class
        self.num_classes = num_classes
        self.device = device
        self.topk = topk
        self.transform = transform_fn(train=False)

        self.model = self._load_mobile_net(pretrained=pretrained, num_classes=num_classes)
        self._init_model()
        self._init_idx2class()

    def _init_model(self):
        if self.weight:
            self.state_dict = self._load_weight(self.weight)
            self.model.load_state_dict(self.state_dict)
        self.model.to(self.device)

    def _init_idx2class(self):
        if type(self._idx2class) == type(None):
            idx2class = {0: 'idcard', 1: 'other'}
        elif type(self._idx2class) == str:
            loaded_json = utils.load_json(self._idx2class)
            idx2class = dict()
            for key, val in loaded_json.items():
                idx2class[int(key)] = val
        elif type(self._idx2class) == dict:
            idx2class = self._idx2class
        else:
            idx2class = {0: 'idcard', 1: 'other'}

        self.idx2class: dict = copy.deepcopy(idx2class)

    def _load_mobile_net(self, pretrained: bool, num_classes: int) -> nn.Module:
        mnv2 = models.mobilenet_v2(pretrained=pretrained)
        mnv2.classifier = nn.Sequential(
            nn.Dropout(0.2),
            nn.Linear(mnv2.last_channel, num_classes),
        )

        nn.init.normal_(mnv2.classifier[1].weight, 0, 0.01)
        nn.init.zeros_(mnv2.classifier[1].bias)

        return mnv2

    def _load_weight(self, weight: str, key=None) -> OrderedDict:
        state_dict = torch.load(weight, map_location=torch.device('cpu'))
        return state_dict

    def _load_image(self, impath: str):
        impath = Path(impath)
        if impath.exists():
            image = Image.open(impath)
            image.convert("RGB")
            return image
        else:
            raise ValueError()

    def _clean_result(self, result: Tuple[torch.Tensor, torch.Tensor]):
        probability, classes = result
        probability, classes = probability.squeeze().numpy(), classes.squeeze().numpy()
        probability = probability.astype(np.float).tolist()
        classes = classes.astype(np.int).tolist()
        if self.topk>1:
            output = []
            for idx, (prob, clz) in enumerate(zip(probability, classes)):
                out = {'class': self.idx2class[clz], 'confidence': f'{prob*100:.0f}%'}
                output.append(out)
            return output
        else:
#             print(classes)
            return {'class': self.idx2class[classes], 'confidence': f'{probability*100:.0f}%'}

    def _predict(self, image: Union[str, Image.Image]):
        if type(image)==str:
            image = self._load_image(image)

        image = self.transform(image)
        image = image.unsqueeze(dim=0)
        self.model.eval()
        with torch.no_grad():
            preds = self.model(image)
            output = torch.log_softmax(preds, dim=1)
            ps = torch.exp(output)
            result = ps.topk(self.topk, dim=1, largest=True, sorted=True)
            result = self._clean_result(result)

        return result

    def predict(self, image: Union[str, Image.Image]):
        result = self._predict(image)
        return result

class MobileNetClassifyOnnx(object):
    def __init__(self, weight: str = None, idx2class: str = None,
                 num_classes: int = 2, topk=1, device: str = 'cpu'):
        self.weight = weight
        self._idx2class = idx2class
        self.num_classes = num_classes
        self.device = device
        self.topk = topk
        self.transform = transform_fn(train=False)

        self._load_check_model()
        self._init_session()
        self._init_idx2class()

    @property
    def _providers(self):
        return {"cpu":'CPUExecutionProvider', "cuda": 'CUDAExecutionProvider'}

    def _init_session(self):
        if self.weight:
            self.session = ort.InferenceSession(self.weight)
        providers = [self._providers.get(self.device, "cpu")]
        self.session.set_providers(providers)

    def _init_idx2class(self):
        if type(self._idx2class) == type(None):
            idx2class = {0: 'idcard', 1: 'other'}
        elif type(self._idx2class) == str:
            loaded_json = utils.load_json(self._idx2class)
            idx2class = dict()
            for key, val in loaded_json.items():
                idx2class[int(key)] = val
        elif type(self._idx2class) == dict:
            idx2class = self._idx2class
        else:
            idx2class = {0: 'idcard', 1: 'other'}

        self.idx2class: dict = copy.deepcopy(idx2class)

    def _load_check_model(self):
        self.onnx_model = onnx.load(self.weight)
        onnx.checker.check_model(self.onnx_model)

    def _to_numpy(self, tensor):
        if tensor.requires_grad:
            return tensor.detach().cpu().numpy()
        else:
            return tensor.cpu().numpy()

    def _load_image(self, impath: str):
        impath = Path(impath)
        if impath.exists():
            image = Image.open(impath)
            image.convert("RGB")
            return image
        else:
            raise ValueError()

    def _clean_result(self, result: Tuple[torch.Tensor, torch.Tensor]):
        probability, classes = result
        probability, classes = probability.squeeze().numpy(), classes.squeeze().numpy()
        probability = probability.astype(np.float).tolist()
        classes = classes.astype(np.int).tolist()
        if self.topk>1:
            output = []
            for idx, (prob, clz) in enumerate(zip(probability, classes)):
                out = {'class': self.idx2class[clz], 'confidence': f'{prob*100:.0f}%'}
                output.append(out)
            return output
        else:
            return {'class': self.idx2class[classes], 'confidence': f'{probability*100:.0f}%'}

    def _onnx_predict(self, inputs):
        sess_inputs = {self.session.get_inputs()[0].name: inputs}
        ort_outs = self.session.run(None, sess_inputs)
        onnx_predict = ort_outs[0]
        return onnx_predict

    def _preprocess(self, image: Union[str, Image.Image]):
        if type(image)==str: image = self._load_image(image)
        image = self.transform(image)
        image = image.unsqueeze(dim=0)
        inputs = self._to_numpy(image)

        return inputs

    def _postprocess(self, prediction:np.ndarray):
        preds = torch.from_numpy(prediction)
        output = torch.log_softmax(preds, dim=1)
        ps = torch.exp(output)
        result = ps.topk(self.topk, dim=1, largest=True, sorted=True)
        clean_result = self._clean_result(result)
        return clean_result

    def _predict(self, image: Union[str, Image.Image]):
        inputs = self._preprocess(image)
        predict = self._onnx_predict(inputs)
        result = self._postprocess(predict)
        return result

    def predict(self, image: Union[str, Image.Image]):
        result = self._predict(image)
        return result

if __name__ == "__main__":
    mnet = MobileNetClassify(weight='weights/mobilenet_v2-09_acc0.9989_loss0.0051.pth',topk=1)
    # impath = 'images/samples/idcard/55bgkl_561809_image.jpg'
    impath = 'images/samples/other/000000069480.jpg'
    result = mnet.predict(impath)
    print(result)


# Copyright 2019 Xilinx Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This code is based on: https://github.com/nutonomy/second.pytorch.git
#
# MIT License
# Copyright (c) 2018
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import warnings
warnings.filterwarnings("ignore")

import torch
import time
import os
import sys
import argparse
import numpy as np
from pathlib import Path
import pickle
from builder import voxel_builder
from builder import box_coder_builder
from builder import target_assigner_builder
from builder import second_builder
from builder import input_reader_builder
from datasets.dataset_tools import merge_second_batch
import datasets.kitti_common as kitti
from utils.progress_bar import ProgressBar
from utils.eval import get_official_eval_result, get_coco_eval_result
from second.protos import pipeline_pb2
from google.protobuf import text_format

if os.environ["W_QUANT"]=='1':
    from pytorch_nndct.apis import torch_quantizer, dump_xmodel

def example_convert_to_torch(example, dtype, device) -> dict:
    example_torch = {}
    float_names = [
        "voxels", "anchors", "reg_targets", "reg_weights", "bev_map", "rect",
        "Trv2c", "P2"
    ]

    for k, v in example.items():
        if k in float_names:
            example_torch[k] = torch.as_tensor(v, dtype=dtype, device=device)
        elif k in ["coordinates", "labels", "num_points"]:
            example_torch[k] = torch.as_tensor(
                v, dtype=torch.int32, device=device)
        elif k in ["anchors_mask"]:
            example_torch[k] = torch.as_tensor(
                v, dtype=torch.uint8, device=device)
        else:
            example_torch[k] = v
    return example_torch

def predict_kitti_to_anno(net,
                          preprocessor,
                          postprocessor,
                          example,
                          class_names,
                          center_limit_range=None,
                          lidar_input=False,
                          global_set=None):
    batch_image_shape = example['image_shape']
    batch_imgidx = example['image_idx']

    voxels = example["voxels"]
    num_points = example["num_points"]
    coors = example["coordinates"]
    # batch_anchors = example["anchors"]

    aug_voxels = preprocessor(voxels, num_points, coors)
    pred_tuples = net.forward(aug_voxels, coors)

    preds_dict = {}
    if len(pred_tuples) == 3:
        box_preds, cls_preds, dir_cls_preds = pred_tuples
        preds_dict['box_preds'] = box_preds
        preds_dict['cls_preds'] = cls_preds
        preds_dict['dir_cls_preds'] = dir_cls_preds
    else:
        box_preds, cls_preds = pred_tuples
        preds_dict['box_preds'] = box_preds
        preds_dict['cls_preds'] = cls_preds

    predictions_dicts = postprocessor(example, preds_dict)

    annos = []
    for i, preds_dict in enumerate(predictions_dicts):
        image_shape = batch_image_shape[i]
        img_idx = preds_dict["image_idx"]
        if preds_dict["bbox"] is not None:
            box_2d_preds = preds_dict["bbox"].detach().cpu().numpy()
            box_preds = preds_dict["box3d_camera"].detach().cpu().numpy()
            scores = preds_dict["scores"].detach().cpu().numpy()
            box_preds_lidar = preds_dict["box3d_lidar"].detach().cpu().numpy()
            # write pred to file
            label_preds = preds_dict["label_preds"].detach().cpu().numpy()
            # label_preds = np.zeros([box_2d_preds.shape[0]], dtype=np.int32)
            anno = kitti.get_start_result_anno()
            num_example = 0
            for box, box_lidar, bbox, score, label in zip(
                    box_preds, box_preds_lidar, box_2d_preds, scores,
                    label_preds):
                if not lidar_input:
                    if bbox[0] > image_shape[1] or bbox[1] > image_shape[0]:
                        continue
                    if bbox[2] < 0 or bbox[3] < 0:
                        continue
                if center_limit_range is not None:
                    limit_range = np.array(center_limit_range)
                    if (np.any(box_lidar[:3] < limit_range[:3])
                            or np.any(box_lidar[:3] > limit_range[3:])):
                        continue
                bbox[2:] = np.minimum(bbox[2:], image_shape[::-1])
                bbox[:2] = np.maximum(bbox[:2], [0, 0])
                anno["name"].append(class_names[int(label)])
                anno["truncated"].append(0.0)
                anno["occluded"].append(0)
                anno["alpha"].append(-np.arctan2(-box_lidar[1], box_lidar[0]) +
                                     box[6])
                anno["bbox"].append(bbox)
                anno["dimensions"].append(box[3:6])
                anno["location"].append(box[:3])
                anno["rotation_y"].append(box[6])
                if global_set is not None:
                    for i in range(100000):
                        if score in global_set:
                            score -= 1 / 100000
                        else:
                            global_set.add(score)
                            break
                anno["score"].append(score)

                num_example += 1
            if num_example != 0:
                anno = {n: np.stack(v) for n, v in anno.items()}
                annos.append(anno)
            else:
                annos.append(kitti.empty_result_anno())
        else:
            annos.append(kitti.empty_result_anno())
        num_example = annos[-1]["name"].shape[0]
        annos[-1]["image_idx"] = np.array(
            [img_idx] * num_example, dtype=np.int64)
    return annos

def predict_kitti_to_file(net,
                          preprocessor,
                          postprocessor,
                          example,
                          result_dir,
                          class_names,
                          center_limit_range=None,
                          lidar_input=False):
    batch_image_shape = example['image_shape']
    batch_imgidx = example['image_idx']
    voxels = example["voxels"]
    num_points = example["num_points"]
    coors = example["coordinates"]

    aug_voxels = preprocessor(voxels, num_points, coors)
    pred_tuples = net.forward(aug_voxels, coors)

    preds_dict = {}
    if len(pred_tuples) == 3:
        box_preds, cls_preds, dir_cls_preds = pred_tuples
        preds_dict['box_preds'] = box_preds
        preds_dict['cls_preds'] = cls_preds
        preds_dict['dir_cls_preds'] = dir_cls_preds
    else:
        box_preds, cls_preds = pred_tuples
        preds_dict['box_preds'] = box_preds
        preds_dict['cls_preds'] = cls_preds

    predictions_dicts = postprocessor(example, preds_dict)

    # t = time.time()
    for i, preds_dict in enumerate(predictions_dicts):
        image_shape = batch_image_shape[i]
        img_idx = preds_dict["image_idx"]
        if preds_dict["bbox"] is not None:
            box_2d_preds = preds_dict["bbox"].data.cpu().numpy()
            box_preds = preds_dict["box3d_camera"].data.cpu().numpy()
            scores = preds_dict["scores"].data.cpu().numpy()
            box_preds_lidar = preds_dict["box3d_lidar"].data.cpu().numpy()
            # write pred to file
            box_preds = box_preds[:, [0, 1, 2, 4, 5, 3,
                                      6]]  # lhw->hwl(label file format)
            label_preds = preds_dict["label_preds"].data.cpu().numpy()
            # label_preds = np.zeros([box_2d_preds.shape[0]], dtype=np.int32)
            result_lines = []
            for box, box_lidar, bbox, score, label in zip(
                    box_preds, box_preds_lidar, box_2d_preds, scores,
                    label_preds):
                if not lidar_input:
                    if bbox[0] > image_shape[1] or bbox[1] > image_shape[0]:
                        continue
                    if bbox[2] < 0 or bbox[3] < 0:
                        continue
                # print(img_shape)
                if center_limit_range is not None:
                    limit_range = np.array(center_limit_range)
                    if (np.any(box_lidar[:3] < limit_range[:3])
                            or np.any(box_lidar[:3] > limit_range[3:])):
                        continue
                bbox[2:] = np.minimum(bbox[2:], image_shape[::-1])
                bbox[:2] = np.maximum(bbox[:2], [0, 0])
                result_dict = {
                    'name': class_names[int(label)],
                    'alpha': -np.arctan2(-box_lidar[1], box_lidar[0]) + box[6],
                    'bbox': bbox,
                    'location': box[:3],
                    'dimensions': box[3:6],
                    'rotation_y': box[6],
                    'score': score,
                }
                result_line = kitti.kitti_result_line(result_dict)
                result_lines.append(result_line)
        else:
            result_lines = []
        result_file = f"{result_dir}/{kitti.get_image_index_str(img_idx)}.txt"
        result_str = '\n'.join(result_lines)
        with open(result_file, 'w') as f:
            f.write(result_str)

def restore(ckpt_path, model, device):
    if not Path(ckpt_path).is_file():
        raise ValueError("checkpoint {} not exist.".format(ckpt_path))
    model.to(device)
    model_dict = model.state_dict()
    ckpt_dict = torch.load(ckpt_path, map_location=device)
    for k in model_dict.keys():
        model_dict[k] = ckpt_dict[k]
    model.load_state_dict(model_dict)
    print("Restoring parameters from {}".format(ckpt_path))

def main(args):
    quant_mode = args.quant_mode
    pred_dir = Path(args.pred_dir)
    output_dir = args.quant_dir
    batch_size = args.batch_size

    if args.dump_xmodel:
        args.device='cpu'
        batch_size = 1

    if args.device=='cpu':
        device = torch.device("cpu")
    else:
        device = torch.device("cuda")

    config = pipeline_pb2.TrainEvalPipelineConfig()
    with open(args.config, "r") as f:
        proto_str = f.read()
        text_format.Merge(proto_str, config)

    input_cfg = config.eval_input_reader
    model_cfg = config.model.second
    train_cfg = config.train_config
    class_names = list(input_cfg.class_names)
    center_limit_range = model_cfg.post_center_limit_range

    voxel_generator = voxel_builder.build(model_cfg.voxel_generator)
    bv_range = voxel_generator.point_cloud_range[[0, 1, 3, 4]]
    box_coder = box_coder_builder.build(model_cfg.box_coder)
    target_assigner_cfg = model_cfg.target_assigner
    target_assigner = target_assigner_builder.build(target_assigner_cfg, bv_range, box_coder)

    net, preprocessor, postprocessor = second_builder.build(model_cfg, voxel_generator, target_assigner)

    if train_cfg.enable_mixed_precision:
        net.half()
        net.metrics_to_float()
        net.convert_norm_to_float(net)

    restore(args.weights, net, device)

    eval_dataset = input_reader_builder.build(
        input_cfg,
        model_cfg,
        training=False,
        voxel_generator=voxel_generator,
        target_assigner=target_assigner)
    eval_dataloader = torch.utils.data.DataLoader(
        eval_dataset,
        batch_size=batch_size,
        shuffle=False,
        num_workers=input_cfg.num_workers,
        pin_memory=False,
        collate_fn=merge_second_batch)

    if train_cfg.enable_mixed_precision:
        float_dtype = torch.float16
    else:
        float_dtype = torch.float32

    if quant_mode != 'float':
        max_voxel_num = config.eval_input_reader.max_number_of_voxels
        max_point_num_per_voxel = model_cfg.voxel_generator.max_number_of_points_per_voxel
        aug_voxels = torch.randn((1, 4, max_voxel_num, max_point_num_per_voxel)).to(device)
        coors = torch.randn((max_voxel_num, 4)).to(device)
        quantizer = torch_quantizer(quant_mode=quant_mode,
                                    module=net,
                                    input_args=(aug_voxels, coors),
                                    output_dir=output_dir,
                                    device=device,
                                    )
        net = quantizer.quant_model

    net.eval()

    pred_dir.mkdir(parents=True, exist_ok=True)
    dt_annos = []
    global_set = None
    print("Generate output labels...")
    bar = ProgressBar()
    bar.start(len(eval_dataset) // batch_size + 1)

    for example in iter(eval_dataloader):
        example = example_convert_to_torch(example, float_dtype, device)
        if args.pickle_result:
            dt_annos += predict_kitti_to_anno(
                net, preprocessor, postprocessor, example, class_names, center_limit_range,
                model_cfg.lidar_input, global_set)
        else:
            predict_kitti_to_file(net, preprocessor, postprocessor, example, pred_dir,
                class_names, center_limit_range, model_cfg.lidar_input)
        bar.print_bar()

        if quant_mode == 'test' and args.dump_xmodel:
            dump_xmodel(output_dir=output_dir, deploy_check=True)
            sys.exit()

    gt_annos = [info["annos"] for info in eval_dataset.dataset.kitti_infos]
    if not args.pickle_result:
        dt_annos = kitti.get_label_annos(pred_dir)

    result = get_official_eval_result(gt_annos, dt_annos, class_names)
    print(result)
    result = get_coco_eval_result(gt_annos, dt_annos, class_names)
    print(result)
    with open(pred_dir / "result.pkl", 'wb') as f:
        pickle.dump(dt_annos, f)

    if quant_mode == 'calib':
        quantizer.export_quant_config()

if __name__ == "__main__":

    parser = argparse.ArgumentParser(description='Script for testing accuracy of PointPillars on KITTI dataset.')
    parser.add_argument('-config', default='float/config.proto')
    parser.add_argument('-weights', default='float/float.tckpt')
    parser.add_argument('-pred_dir', default='data/result')
    parser.add_argument('-quant_dir', default='q_results')
    parser.add_argument('-quant_mode', default='calib', choices=['float', 'calib', 'test'], help='quantization mode. float: no quantization, evaluate float model, calib: quantize, test: evaluate quantized model')
    parser.add_argument('-dump_xmodel', dest='dump_xmodel', action='store_true', help='dump xmodel after test')
    parser.add_argument('-device', default='gpu', choices=['gpu, cpu'], help='assign runtime device')
    parser.add_argument('-pickle_result', default=True, type=bool)
    parser.add_argument('-batch_size',default=1, type=int)
    args = parser.parse_args()

    main(args)


import sys
import os
import os.path
import wave
import argparse
import math
from typing import Tuple

import numpy as np

sys.path.append(os.path.dirname(__file__)) #embeddable pythonにimpot用のパスを追加
import whd
import dat
import length_string

ARROW_ENVELOPE_VALUES = [2, 7, 8, 9, 11]

class WavTool:
    '''
    | WavToolはUTAU標準のwavtoolをエミュレートします。
    | wavを入力し、waveヘッダ(.wav.whd)とwaveデータ(.wav.dat)を出力します。
    | すでにwhdとdatがある場合、datの末尾にデータを加え、whdを更新します。

    Attributes
    ----------
    ourput : str
        出力するwavのパス
    input : str
        入力するwavのパス
    stp : float
        入力wavの先頭のオフセットをmsで指定する。
    length : float
        datに追加する長さ(ms)
    envelope : list
        エンベロープのパターンは以下のいずれかです。
            >>> p1 p2
            >>> p1 p2 p3 v1 v2 v3 v4
            >>> p1 p2 p3 v1 v2 v3 v4 ove
            >>> p1 p2 p3 v1 v2 v3 v4 ove p4
            >>> p1 p2 p3 v1 v2 v3 v4 ove p4 p5 v5
        | p1,p2,p3,p4,p5,ove : float
        | v1,v2,v3,v4,v5 : int
    header : whd.Whd
        .whdファイルを扱います。
    dat : dat.Dat
        .datファイルを扱います。
    '''
    _error: bool = False
    _output: str
    _input: str
    _stp: float
    _length: float
    _data: np.ndarray
    _range_data: np.ndarray
    _apply_data: np.ndarray
    _header: whd.Whd
    _dat: dat.Dat

    @property
    def error(self) -> bool:
        return self._error

    def __init__(self, output: str):
        '''
        Parameters
        ----------
        ourput : str
            出力するwavのパス
        '''
        self._error = False
        if os.path.split(output)[0] != "":
            os.makedirs(os.path.split(output)[0], exist_ok=True)
        self._header = whd.Whd(output + ".whd")
        self._dat = dat.Dat(output + ".dat")
        self._output = output

    def applyData(self,stp: float,length: float):
        '''
        Parameters
        ----------
        stp : float
            入力wavの先頭のオフセットをmsで指定する。
        length : float
            datに追加する長さ(ms)
        '''
        ove :float
        if len(self._envelope) >= 8:
            ove = self._envelope[7]
        else:
            ove = 0

        if not self._error:
            self._applyRange(stp, length)
            p, v = self._getEnvelopes(length)
            self._applyEnvelope(p, v)
        else:
            self._apply_data = np.zeros(math.ceil(length * self._header.framerate / 1000))
        nframes: int = self._dat.addframeAndWrite(self._apply_data, ove, self._header.samplewidth, self._header.framerate,self._output + ".dat")
        self._header.addframes(nframes)

    def inputCheck(self, input:str):
        '''
        | 入力値が正しいかチェックします。
        | 正常値の場合、self._dataにwavの中身を最大1に正規化したfloatに変換して代入します。
        | 異常値の場合、self._errorをTrueにします。

        Parameters
        ----------
        input : str
            入力するwavのパス
        '''
        self._error = False

        basedata :list
        if (not os.path.isfile(input)):
            print("input file is not found:{}".format(input))
            self._error = True
            return
        try:
            with wave.open(input,"rb") as wr:
                basedata = wr.readframes(wr.getnframes())
                samplewidth :int = wr.getsampwidth()
        except:
            print("file format error:{} is not wave.".format(input))
            self._error = True
            return
        if samplewidth == 1:
            data: np.ndarray = np.frombuffer(basedata, dtype=np.int8)
        elif samplewidth == 2:
            data: np.ndarray = np.frombuffer(basedata, dtype=np.int16)
        elif samplewidth == 3:
            data: np.ndarray = np.zeros(int(len(basedata)/3), dtype = "int32")
            for i in range(self._data.shape[0]):
                data[i] = int.from_bytes(basedata[i*3:(i+1)*3], "little", signed=True)
        elif samplewidth == 4:
            data: np.ndarray = np.frombuffer(basedata, dtype=np.int32)

        self._data = data.astype(np.float64)
        self._data /= (2 ** (samplewidth *8) /2) #正規化

    def setEnvelope(self, envelope: list):
        '''
        | 入力されたエンベロープが正しいかチェックします。
        | 正常値であれば、self._envelopeを更新します。
        | 異常値であれば、self._ErrorをTrueにします。

        Parameters
        ----------
        envelope : list
            エンベロープのパターンは以下のいずれかです。
                p1 p2
                p1 p2 p3 v1 v2 v3 v4
                p1 p2 p3 v1 v2 v3 v4 ove
                p1 p2 p3 v1 v2 v3 v4 ove p4
                p1 p2 p3 v1 v2 v3 v4 ove p4 p5 v5
            p1,p2,p3,p4,p5,ove : float
            v1,v2,v3,v4,v5 : int
        '''
        if (len(envelope) in ARROW_ENVELOPE_VALUES):
            # エンベロープがパターンにマッチしているか確認
            self._envelope = envelope
        else:
            print("value error:envelope patern is not matching.")
            self._error = True

    def _applyRange(self, stp:float, length :float):
        '''
        | stpを適用し、self._range_dataを返します。
        | 事前にself._dataに最大1に正規化したwavデータが格納されていることが条件です。

        Parameters
        ----------
        stp : float
            入力wavの先頭のオフセットをmsで指定する。
        length : float
            datに追加する長さ(ms)
        '''
        stp_frames = int(stp * self._header.framerate / 1000)
        length_frames = int(length * self._header.framerate / 1000)
        self._range_data:np.ndarray = self._data[stp_frames:stp_frames + length_frames]

    def _applyEnvelope(self, p :list, v :list):
        '''
        | エンベロープ・stpを適用し、self._apply_dataを返します。
        | 事前にself._range_dataに最大1に正規化したwavデータが格納されていることが条件です。

        Parameters
        ----------
        p :list of float
            Pstart P1 P2 P3 (P5) P4 Pendの順に並べたポルタメント。エンベロープが2点の場合空配列
        v: list of int
            ノート頭からms順に並べたポルタメントの音量値。エンベロープが2点の場合空配列
        '''

        if len(p) == 0: #休符等の例外
            self._apply_data = np.zeros_like(self._range_data)
            return

        self._apply_data = self._range_data * np.interp(np.arange(self._range_data.shape[0]), p, v[:len(p)]) / 100

    def _getEnvelopes(self, length: float) -> Tuple[list, list]:
        '''
        | エンベロープをノート頭からのms順に並べ、pとvのリストを返します。
        | エンベロープがパターンにマッチすることを事前に確認するのが条件です。
        Parameters
        ----------
        length : float
            datに追加する長さ(ms)

        Returns
        -------
        p :list of int
            Pstart P1 P2 P3 (P5) P4 Pendの順に並べたポルタメント。エンベロープが2点の場合空配列
        v: list of int
            ノート頭からms順に並べたポルタメントの音量値。エンベロープが2点の場合空配列
        '''

        if len(self._envelope) == 2: #休符等の例外
            return [], []

        p :list = [0] #Pstart
        v :list = [0] #Vstart
        frame_per_ms: float = self._header.framerate / 1000
        p.append(int(float(self._envelope[0]) * frame_per_ms)) #p1
        p.append(int((float(self._envelope[0]) + float(self._envelope[1])) * frame_per_ms)) #p2
        v.append(int(self._envelope[3])) #v1
        v.append(int(self._envelope[4])) #v2
        if len(self._envelope) >= 11:
            p.append(int((float(self._envelope[0]) + float(self._envelope[1]) + float(self._envelope[9])) * frame_per_ms)) #p5
            v.append(int(self._envelope[10])) #v5
        v.append(int(self._envelope[5])) #v3
        v.append(int(self._envelope[6])) #v4
        if len(self._envelope) >= 9:
            p.append(int((length - float(self._envelope[8]) - float(self._envelope[2])) * frame_per_ms))  #p3はp4からのms
            p.append(int((length - float(self._envelope[8])) * frame_per_ms)) #p4は後ろからのms
        else:
            p.append(int((length - float(self._envelope[2])) * frame_per_ms)) #p3はp4からのms

        p.append(length * frame_per_ms) # Pend
        v.append(0) #Vend

        return p,v

    def write(self):
        self._header.write(self._output)
        #self._dat.write(self._output + ".dat", self._header.samplewidth)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description='This module emulate UTAU\'s wavtool')
    parser.add_argument('output', help='output wav path', type=str)
    parser.add_argument('input', help='input wav path', type=str)
    parser.add_argument('stp', help='start offset of wav', type=float)
    parser.add_argument('length', help='append length(ms)')
    parser.add_argument('envelope', nargs='*', type=float,
                        help='envelope patern ' +
                        '\'p1 p2\' or \'p1 p2 p3 v1 v2 v3 v4 ove\'' +
                        ' or \'p1 p2 p3 v1 v2 v3 v4' +
                        ' or \'p1 p2 p3 v1 v2 v3 v4 ove p4' +
                        ' or \'p1 p2 p3 v1 v2 v3 v4 ove p4 p5 v5\'')
    args = parser.parse_args()
    if (len(args.envelope) not in ARROW_ENVELOPE_VALUES):
        print("value error:envelope patern is not matching.")
    length: float
    wavtool = WavTool(args.output)
    wavtool.inputCheck(args.input)
    wavtool.setEnvelope(args.envelope)
    if type(args.length) == float:
        length = args.length
    else:
        length = length_string.str2float(args.length)
    wavtool.applyData(args.stp, length)
    wavtool.write()


import unittest
from unittest.util import safe_repr
import numpy as np
import time
from util import r_squared, get_housing_prices_data, grad_check
from util import add_feature_ones, randomize_in_place
from rl_functions import standardize, compute_cost, compute_wgrad
from rl_functions import batch_gradient_descent, stochastic_gradient_descent
from rl_functions import linear_regression_prediction

def run_test(testClass):
    """
    Function to run all the tests from a class of tests.
    :param testClass: class for testing
    :type testClass: unittest.TesCase
    """
    suite = unittest.TestLoader().loadTestsFromTestCase(testClass)
    unittest.TextTestRunner(verbosity=2).run(suite)

class TestEP2(unittest.TestCase):
    """
    Class that test the functions from rl_functions module
    """
    @classmethod
    def setUpClass(cls):
        X, y = get_housing_prices_data(N=350)
        randomize_in_place(X, y)
        cls.train_X = X[0:250]
        cls.train_y = y[0:250]
        cls.valid_X = X[250:300]
        cls.valid_y = y[250:300]
        cls.test_X = X[300:]
        cls.test_y = y[300:]
        cls.total_score = 0

    def assertTrue(self, expr, msg=None, score=0):
        """Check that the expression is true."""
        if not expr:
            msg = self._formatMessage(msg, "%s is not true" % safe_repr(expr)) # noqa
            raise self.failureException(msg)
        else:
            TestEP2.total_score += score

    def test_exercise_1(self):
        try:
            toy_X = np.array([[1100.3, 2.4, 34.34],
                              [2300.3, 1.4, 442.23]])
            toy_y = np.array([[1000.2], [2000.5]])
            toy_X_norm = standardize(toy_X)
            toy_y_norm = standardize(toy_y)
            xmean, xstd = np.mean(toy_X_norm), np.std(toy_X_norm)
            ymean, ystd = np.mean(toy_y_norm), np.std(toy_y_norm)
            self.assertTrue(-1 <= xmean < 0, score=1 / 4)
            self.assertTrue(0 <= ymean < 1, score=1 / 4)
            self.assertTrue(0.9 <= xstd <= 1, score=1 / 4)
            self.assertTrue(0.9 <= ystd <= 1, score=1 / 4)
        except NotImplementedError:
            self.fail('Exercício 1) Falta fazer!')

    def test_exercise_2(self):
        try:
            toy_w = np.array([[1], [1], [2]])
            toy_X = np.array([[2, 3, 1],
                              [5, 1, 2]])
            toy_y = np.array([[1], [1]])
            self.assertTrue(compute_cost(toy_X, toy_y, toy_w) == 58.5,
                            score=1)
        except NotImplementedError:
            self.fail('Exercício 2) Falta fazer!')

    def test_exercise_3(self):
        try:
            toy_w1 = np.array([[1.], [2.], [1.], [2.]])
            toy_X1 = np.array([[2., 3., 1., 2.],
                              [5., 1., 1., 2.]])
            toy_y1 = np.array([[1.], [-1.]])
            toy_w2 = np.array([[-100.22], [20002.1], [102.5]])
            toy_X2 = np.array([[2111.3, -2223., 404.0],
                              [5222., -22221., 3.3]])
            toy_y2 = np.array([[122.], [221.]])
            toy_w3 = np.array([[-10.22], [-3.1]])
            toy_X3 = np.array([[1.3, -1.2],
                              [2.2, -2.1],
                              [-2.3, -5.5],
                              [3.2, 8.1],
                              [3.3, -1.1],
                              [-3.4, -2.22],
                              [2.23, -4.4],
                              [5.2, -2.3]])
            toy_y3 = np.array([[10.3],
                               [23.3],
                               [10.1],
                               [-20.2],
                               [-10.2],
                               [20.2],
                               [-14.4],
                               [-30.3]])
            self.assertTrue(grad_check(toy_X1,
                                       toy_y1,
                                       toy_w1,
                                       compute_cost,
                                       compute_wgrad), score=1 / 3)
            self.assertTrue(grad_check(toy_X2,
                                       toy_y2,
                                       toy_w2,
                                       compute_cost,
                                       compute_wgrad), score=1 / 3)
            self.assertTrue(grad_check(toy_X3,
                                       toy_y3,
                                       toy_w3,
                                       compute_cost,
                                       compute_wgrad), score=1 / 3)
        except NotImplementedError:
            self.fail('Exercício 3) Falta fazer!')

    def test_exercise_4(self):
        try:
            initial_w = np.array([[15], [-35.3]])
            train_X_norm = standardize(self.train_X)
            train_y_norm = standardize(self.train_y)
            train_X_1 = add_feature_ones(train_X_norm)
            learning_rate = 0.8
            iterations = 20000
            init = time.time()
            w, weights_history, cost_history = batch_gradient_descent(train_X_1, # noqa
                                                                      train_y_norm, # noqa
                                                                      initial_w, # noqa
                                                                      learning_rate, # noqa
                                                                      iterations) # noqa
            init = time.time() - init
            self.assertTrue(cost_history[-1] < cost_history[0], score=1 / 5)
            self.assertTrue(type(w) == np.ndarray, score=1 / 5)
            self.assertTrue(len(weights_history) == len(cost_history), score=1 / 5) # noqa
            self.assertTrue(init < 2.5, score=1 / 5)
            test_X_norm = standardize(self.test_X)
            test_X_1 = add_feature_ones(test_X_norm)
            prediction = linear_regression_prediction(test_X_1, w)
            prediction = (prediction * np.std(self.train_y)) + np.mean(self.train_y) # noqa
            r_2 = r_squared(self.test_y, prediction)
            self.assertTrue(0.3 < r_2, score=1 / 5)
        except NotImplementedError:
            self.fail('Exercício 4) Falta fazer!')

    def test_exercise_5(self):
        try:
            initial_w = np.array([[15], [-35.3]])
            train_X_norm = standardize(self.train_X)
            train_y_norm = standardize(self.train_y)
            train_X_1 = add_feature_ones(train_X_norm)
            learning_rate = 0.001
            iterations = 2000
            batch_size = 36
            init = time.time()
            w, weights_history, cost_history = stochastic_gradient_descent(train_X_1, # noqa
                                                                           train_y_norm, # noqa
                                                                           initial_w, # noqa
                                                                           learning_rate, # noqa
                                                                           iterations, # noqa
                                                                           batch_size) # noqa
            init = time.time() - init
            self.assertTrue(cost_history[-1] < cost_history[0], score=1 / 5)
            self.assertTrue(type(w) == np.ndarray, score=1 / 5)
            self.assertTrue(len(weights_history) == len(cost_history), score=1 / 5) # noqa
            self.assertTrue(init < 2.5, score=1 / 5)
            test_X_norm = standardize(self.test_X)
            test_X_1 = add_feature_ones(test_X_norm)
            prediction = linear_regression_prediction(test_X_1, w)
            prediction = (prediction * np.std(self.train_y)) + np.mean(self.train_y) # noqa
            r_2 = r_squared(self.test_y, prediction)
            self.assertTrue(0.3 < r_2, score=1 / 5)
        except NotImplementedError:
            self.fail('Exercício 5) Falta fazer!')

if __name__ == '__main__':
    run_test(TestEP2)
    time.sleep(0.1)
    total_score = (TestEP2.total_score / 5) * 10
    print("\nEP2 total_score = ({:.1f} / 10.0)".format(total_score))