# -*- coding: utf-8 -*-
#
# @Author   : Yuxiang WU
# @Email    : elephantameler@gmail.com

import os
import sys
import time
import torch
import random
import argparse
import numpy as np
from torch import nn
from typing import Dict
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from sklearn.model_selection import train_test_split

sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__))))
from common_utils.metric.det import calc_det_score_wlabel
from common_utils.metric.merge import calc_merge_score_wlabel
from common_utils.dataset_building import split_multi_individuals_datasets
from common_utils.prints import print_log_message, print_info_message, get_checkpoint_timestamp
from preprocessing.pp_infer import auto_preprocess
from preprocessing.training.utils import extract_annos
from detection.inference.dataset import InferringNeuronDataset
from detection.det_infer import nms_multiprocess, det_infer_main
from detection.training.det_train_utils import TrainingNeuronDataset
from detection.inference.bbox_regression_utils import calc_predicted_bbox
from detection.inference.local_peak import parse_volume_peaks_multiprocess
from detection.inference.network import MFDetectNetworkModule41, wing_loss
from merge.xgraph_alignment import xneuronalign_multiprocess

def main(num_epochs: int,
         checkpoint_timestamp: str,
         model_save_path: str,
         tb_writer: SummaryWriter,
         training_iou_threshold: float,
         val_f1score_threshold: float,
         nms_threshold: float,
         ):
    """ Detection training function
        0.5x Faster, but GPU memory is the same
        Mixed precision training: https://pytorch.org/blog/accelerating-training-on-nvidia-gpus-with-pytorch-automatic-mixed-precision/
                                  https://pytorch.org/docs/stable/notes/amp_examples.html#typical-mixed-precision-training
    """
    scaler = torch.cuda.amp.GradScaler()

    best_f1_score, best_epoch = 0.0, 0
    for epoch in range(num_epochs):
        train_dataset = TrainingNeuronDataset(vol_peak = {name: vol_peaks[name] for name in dataset_names[0]},
                                              input_size = args.det_input_size,
                                              anchors_size = args.det_anchors_size,
                                              patches_size = args.det_patches_size,
                                              labels = {name: labels[name] for name in dataset_names[0]},
                                              ls = {name: results[name][2][3][:2] for name in dataset_names[0]},
                                              iou_thresh = args.det_label_iou_threshold)
        train_dataloader = DataLoader(train_dataset, batch_size = args.det_batch_size, drop_last = True,
                                      shuffle = True, pin_memory = False, num_workers = args.det_num_workers)

        # =========  train  =========
        network.train()
        score_loss_sum, regression_loss_sum = 0.0, 0.0
        for iter_idx, (mrfs, scores, deltas) in enumerate(train_dataloader):
            scheduler.step(epoch + iter_idx / len(train_dataloader))
            optimizer.zero_grad()
            with torch.cuda.amp.autocast():
                pred_score, pred_delta = network(mrfs.cuda())
                score_loss = criterion(pred_score, scores.cuda())
                regression_loss = wing_loss(pred_delta, deltas.cuda(), w = 0.3)
                loss = 0.6 * score_loss + 0.4 * regression_loss
            scaler.scale(loss).backward()
            scaler.step(optimizer)
            scaler.update()
            score_loss_sum, regression_loss_sum = score_loss_sum + score_loss.item(), regression_loss_sum + regression_loss.item()
        tb_writer.add_scalars("train_loss", {"score": score_loss_sum / len(train_dataloader), "regression": regression_loss_sum / len(train_dataloader)}, global_step = epoch)
        # print_log_message(f'TRAIN Epoch: {epoch}/{num_epochs} Score Loss: {(score_loss_sum / (iter_idx + 1)):.2f}, Wing Loss: {(regression_loss_sum / (iter_idx + 1)):.2f},')

        # =========  verification  =========
        network.eval()
        with torch.no_grad():
            cn, cs, cb = list(), list(), list()
            for names, peaks, shifts, mrfs in val_dataloader:
                pred_score, pred_delta = network(mrfs.cuda())
                pred_score, pred_delta = torch.sigmoid(pred_score.cpu()), pred_delta.cpu()
                pos_idxes = torch.nonzero(pred_score > training_iou_threshold)[:, 0]
                # store
                cn.extend(names[p] for p in pos_idxes)
                cs.extend(float(s) for s in pred_score[pos_idxes])
                cb.extend(calc_predicted_bbox(p.tolist(), s.tolist(), d.tolist()) for p, s, d in zip(peaks[pos_idxes], shifts[pos_idxes], pred_delta[pos_idxes]))
        candidate_scores, candidate_bboxes = dict(), dict()
        [candidate_scores.update({n: candidate_scores.get(n, []) + [s]}) for n, s in zip(cn, cs)]
        [candidate_bboxes.update({n: candidate_bboxes.get(n, []) + [b]}) for n, b in zip(cn, cb)]

        if len(candidate_bboxes) > 0:
            f1_score = calc_det_score_wlabel(nms_multiprocess(candidate_scores, candidate_bboxes, nms_threshold, args.det_number_threshold), val_labels, val_ls, threshold = val_f1score_threshold)[2]
        else:
            f1_score = 0.0
        tb_writer.add_scalar("validation_f1_score", f1_score, global_step = epoch)
        # print_log_message(f'EVALUATE Epoch: {epoch}/{num_epochs}, f1 score: {f1_score}')

        if best_f1_score <= f1_score:
            best_epoch, best_f1_score = epoch, f1_score
            torch.save({'network': network.state_dict()}, model_save_path)
            # print_log_message(f'{model_save_path} done!')

    print_info_message(f"------------------------------------------- >>>>>>>>>>>>>>>>> The best val epoch of {checkpoint_timestamp} model is {best_epoch} \t  F1 score: {(best_f1_score * 100.0):.2f} % \t ")

    return best_f1_score, best_epoch, model_save_path

if __name__ == '__main__':
    # ----------- Parameters -----------
    parser = argparse.ArgumentParser(description = "Detection training phase")
    # ----- Preprocessing --------------------
    parser.add_argument('--checkpoint-timestamp', type = str, default = get_checkpoint_timestamp())
    parser.add_argument('--det-tensorboard-root', type = str, default = "")
    parser.add_argument('--zrange', type = tuple, default = (0, 18))
    parser.add_argument('--process-stack-root', type = str, default = '')
    parser.add_argument('--load-preprocess-result-root', type = str, default = '')
    parser.add_argument('--save-preprocess-result-root', type = str, default = '')
    parser.add_argument('--name-reg', type = str, default = r"[iI]ma?ge?_?[sS]t(?:ac)?k_?\d+_dk?\d+.*[wW]\d+_?Dt\d{6}")
    parser.add_argument('--preprocessing-pixel-threshold', type = float, default = 1.8)  # the mean + 3 * std of a volume
    parser.add_argument('--preprocessing-mode', type = int, default = 3)
    parser.add_argument('--random-seed', type = int, default = 1212)
    parser.add_argument('--label-root', type = str, default = "")

    # ----- Det parameters --------------------
    parser.add_argument('--det-fp16', action = "store_true")
    parser.add_argument('--det-input-size', type = int, default = 41)
    parser.add_argument('--det-anchors-size', type = int, nargs = "+", default = [9])
    parser.add_argument('--det-patches-size', type = int, nargs = "+", default = [15, 31, 41, 81])
    # ----- Training parameters --------------------
    parser.add_argument('--det-label-iou-threshold', type = float, default = 0.20)
    parser.add_argument('--det-training-score-iou-threshold', type = float, default = 0.40)
    parser.add_argument('--det-f1score-threshold', type = float, default = 0.30)
    parser.add_argument('--det-nms-iou-threshold', type = float, default = 0.20)
    parser.add_argument('--det-number-threshold', type = float, default = 100000)
    parser.add_argument('--det-model-load-path', type = str, default = "")
    parser.add_argument('--det-model-save-path', type = str, default = f'')
    parser.add_argument('--num-epochs', type = int, default = 205)
    parser.add_argument('--lr', type = float, default = 0.05)
    parser.add_argument('--momentum', default = 0.9, type = float)
    parser.add_argument('--weight-decay', default = 5e-2, type = float)
    parser.add_argument('--gamma', default = 0.1, type = float)
    parser.add_argument('--det-num-workers', default = 4, type = int)
    parser.add_argument('--det-batch-size', default = 256, type = int)
    parser.add_argument('--epoch-interval', default = 15, type = int)
    # ----- Merge parameters --------------------
    parser.add_argument('--merge-f1score-threshold', type = float, default = 0.30)

    args = parser.parse_args()
    print_info_message(args)

    random.seed(args.random_seed)
    np.random.seed(args.random_seed)
    torch.manual_seed(args.random_seed)

    # ----- Data --------------------
    w2_labels = extract_annos(os.path.join(args.label_root, "PR_v051_ImgStk002_dk001_{w2_Dt210513220213Rebuild}_{red_from1516to2515}Dt210824.mat"), list(range(1, 31, 1)), args.name_reg)
    w4_labels = extract_annos(os.path.join(args.label_root, "PR_v045_ImgStk001_dk001_{w4_Dt210513}_{Dt210824_new_PR_onDt211222}.mat"), list(range(30)), args.name_reg)
    w6_labels = extract_annos(os.path.join(args.label_root, "PR_v150_ImgStk001_dk001_{w6_Dt210514001057rebuild}_{red_from2966to3965}_Dt211119.mat"), list(range(50)), args.name_reg)
    w6_labels_p2 = extract_annos(os.path.join(args.label_root, "PR_v006_ImgStk001_dk002_{w6_Dt210514001057rebuild}_{red_from4966to5965}}_Dt211123modified.mat"), list(range(10)), args.name_reg)
    mat_paths = [
        os.path.join(args.process_stack_root, "ImgStk002_dk001_{w2_Dt210513220213Rebuild}_{red_from1516to2515}.mat"),
        os.path.join(args.process_stack_root, "ImgStk001_dk001_{w4_Dt210513230327rebuild}_{red_from566to1565}.mat"),
        os.path.join(args.process_stack_root, "ImgStk001_dk001_{w6_Dt210514001057rebuild}_{red_from2966to3965}.mat"),
        os.path.join(args.process_stack_root, "ImgStk001_dk002_{w6_Dt210514001057rebuild}_{red_from4966to5965}.mat")
    ]
    labels, dataset_names = split_multi_individuals_datasets(labels = [{**w6_labels, **w6_labels_p2}, w4_labels, w2_labels],
                                                             indexes = [[(0, 40), (40, 50), (50, 60)], [(0, 20), (20, 25), (25, 30)], [(0, 20), (20, 25), (25, 30)]],
                                                             shuffle_type = 0)
    print_info_message(f"\n \t training vols: {dataset_names[0]} \n \t validation vols: {dataset_names[1]} \n \t test vols: {dataset_names[2]} ")

    results = auto_preprocess(mode = args.preprocessing_mode, paths = mat_paths, args = args)
    results = {k: results[k] for k in list(labels.keys())}
    vol_peaks: Dict = parse_volume_peaks_multiprocess(results, args.preprocessing_pixel_threshold)

    val_dataset = InferringNeuronDataset(vol_peak = {name: vol_peaks[name] for name in dataset_names[1]},
                                         input_size = args.det_input_size,
                                         anchors_size = args.det_anchors_size,
                                         patches_size = args.det_patches_size,
                                         num_workers = args.det_num_workers)
    val_dataloader = DataLoader(val_dataset, batch_size = args.det_batch_size, drop_last = False, shuffle = True, pin_memory = False, num_workers = args.det_num_workers)
    val_labels = {name: labels[name] for name in dataset_names[1]}
    val_ls = {name: results[name][2][3][:2] for name in dataset_names[1]}

    # ----------- Network & Loss Function-----------
    network = MFDetectNetworkModule41(num_channels = len(args.det_patches_size) + 1).cuda()
    if os.path.isfile(args.det_model_load_path):
        network.load_state_dict(torch.load(args.det_model_load_path, map_location = 'cuda:0')['network'])

    criterion = nn.BCEWithLogitsLoss().cuda()

    # ----------- Optimizer & Learning Schedule -----------
    # optimizer = torch.optim.Adam(network.parameters(), lr = args.lr)
    optimizer = torch.optim.SGD(network.parameters(), lr = args.lr)
    scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, 5, 100)

    # ----------- Main -----------
    best_f1_score, best_epoch, model_save_path = main(num_epochs = args.num_epochs,
                                                      checkpoint_timestamp = args.checkpoint_timestamp,
                                                      model_save_path = os.path.join(args.det_model_save_path, f"det_{args.checkpoint_timestamp}.ckpt"),
                                                      tb_writer = SummaryWriter(os.path.join(args.det_tensorboard_root, args.checkpoint_timestamp)),
                                                      training_iou_threshold = args.det_training_score_iou_threshold,
                                                      val_f1score_threshold = args.det_f1score_threshold,
                                                      nms_threshold = args.det_nms_iou_threshold, )

    # ----------- Test -----------
    args.det_model_load_path = model_save_path
    test_det_results = det_infer_main(args, {name: vol_peaks[name] for name in dataset_names[2]})
    test_f1_score = calc_det_score_wlabel(test_det_results, labels = {name: labels[name] for name in dataset_names[2]},
                                          sp = {name: results[name][2][3][:2] for name in dataset_names[2]}, threshold = args.det_f1score_threshold)
    print_info_message(f"Test Detection scores (precision, recall, f1 score): {test_f1_score[0:3]}")

    # Change test_det_results to labels to test merge step F1 score.
    test_merge_results = xneuronalign_multiprocess(0.05, 1, vol_peaks.copy(), test_det_results)
    test_merge_f1_score = calc_merge_score_wlabel(test_merge_results, {name: labels[name] for name in dataset_names[2]}.copy(), {name: results[name][2][3][:2] for name in dataset_names[2]},
                                                  threshold = args.merge_f1score_threshold, method = "F1SCORE")
    print_info_message(f"Test Merge scores (precision, recall, f1 score): {test_merge_f1_score[0:3]}")

    # hyperparameters tuning
    # args.det_f1score_threshold = 0.30
    # # args.det_nms_iou_threshold = 0.20
    # # args.det_label_iou_threshold = 0.50
    # test_f1_scores = dict()
    # for j in np.arange(0.20, 0.70, 0.05):
    #     args.det_label_iou_threshold = j
    #     test_results = det_infer_main(args, {name: vol_peaks[name] for name in dataset_names[2]})
    #     test_f1_scores[j] = calc_det_score_wlabel(test_results.copy(), labels = {name: labels[name] for name in dataset_names[2]},
    #                                               sp = {name: results[name][2][3][:2] for name in dataset_names[2]}, threshold = args.det_f1score_threshold)
    #     print_info_message(f"args.det_label_iou_threshold: {j}, Test scores (precision, recall, f1 score): {test_f1_score[0:3]}")


import pandas as pd
import numpy as np
import click
import sys

from .runner import PyProphetLearner, PyProphetWeightApplier
from .ipf import infer_peptidoforms
from .levels_contexts import infer_peptides, infer_proteins, infer_genes, subsample_osw, reduce_osw, merge_osw, backpropagate_oswr
from .export import export_tsv, export_score_plots
from .export_compound import export_compound_tsv
from .filter import filter_sqmass
from .data_handling import (transform_pi0_lambda, transform_threads, transform_subsample_ratio, check_sqlite_table)
from functools import update_wrapper
import sqlite3
from tabulate import tabulate

from hyperopt import hp

try:
    profile
except NameError:
    def profile(fun):
        return fun

@click.group(chain=True)
@click.version_option()
def cli():
    """
    PyProphet: Semi-supervised learning and scoring of OpenSWATH results.

    Visit http://openswath.org for usage instructions and help.
    """

# PyProphet semi-supervised learning and scoring
@cli.command()
# # File handling
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='PyProphet input file.')
@click.option('--out', 'outfile', type=click.Path(exists=False), help='PyProphet output file.')
# Semi-supervised learning
@click.option('--classifier', default='LDA', show_default=True, type=click.Choice(['LDA', 'XGBoost']), help='Either a "LDA" or "XGBoost" classifier is used for semi-supervised learning.')
@click.option('--xgb_autotune/--no-xgb_autotune', default=False, show_default=True, help='XGBoost: Autotune hyperparameters.')

@click.option('--apply_weights', type=click.Path(exists=True), help='Apply PyProphet score weights file instead of semi-supervised learning.')
@click.option('--xeval_fraction', default=0.5, show_default=True, type=float, help='Data fraction used for cross-validation of semi-supervised learning step.')
@click.option('--xeval_num_iter', default=10, show_default=True, type=int, help='Number of iterations for cross-validation of semi-supervised learning step.')
@click.option('--ss_initial_fdr', default=0.15, show_default=True, type=float, help='Initial FDR cutoff for best scoring targets.')
@click.option('--ss_iteration_fdr', default=0.05, show_default=True, type=float, help='Iteration FDR cutoff for best scoring targets.')
@click.option('--ss_num_iter', default=10, show_default=True, type=int, help='Number of iterations for semi-supervised learning step.')
@click.option('--ss_main_score', default="var_xcorr_shape", show_default=True, type=str, help='Main score to start semi-supervised-learning.')
@click.option('--ss_score_filter', default='', help='Specify scores which should used for scoring. In addition specific predefined profiles can be used. For example for metabolomis data use "metabolomics".  Please specify any additional input as follows: "var_ms1_xcorr_coelution,var_library_corr,var_xcorr_coelution,etc."')
# Statistics
@click.option('--group_id', default="group_id", show_default=True, type=str, help='Group identifier for calculation of statistics.')
@click.option('--parametric/--no-parametric', default=False, show_default=True, help='Do parametric estimation of p-values.')
@click.option('--pfdr/--no-pfdr', default=False, show_default=True, help='Compute positive false discovery rate (pFDR) instead of FDR.')
@click.option('--pi0_lambda', default=[0.1,0.5,0.05], show_default=True, type=(float, float, float), help='Use non-parametric estimation of p-values. Either use <START END STEPS>, e.g. 0.1, 1.0, 0.1 or set to fixed value, e.g. 0.4, 0, 0.', callback=transform_pi0_lambda)
@click.option('--pi0_method', default='bootstrap', show_default=True, type=click.Choice(['smoother', 'bootstrap']), help='Either "smoother" or "bootstrap"; the method for automatically choosing tuning parameter in the estimation of pi_0, the proportion of true null hypotheses.')
@click.option('--pi0_smooth_df', default=3, show_default=True, type=int, help='Number of degrees-of-freedom to use when estimating pi_0 with a smoother.')
@click.option('--pi0_smooth_log_pi0/--no-pi0_smooth_log_pi0', default=False, show_default=True, help='If True and pi0_method = "smoother", pi0 will be estimated by applying a smoother to a scatterplot of log(pi0) estimates against the tuning parameter lambda.')
@click.option('--lfdr_truncate/--no-lfdr_truncate', show_default=True, default=True, help='If True, local FDR values >1 are set to 1.')
@click.option('--lfdr_monotone/--no-lfdr_monotone', show_default=True, default=True, help='If True, local FDR values are non-decreasing with increasing p-values.')
@click.option('--lfdr_transformation', default='probit', show_default=True, type=click.Choice(['probit', 'logit']), help='Either a "probit" or "logit" transformation is applied to the p-values so that a local FDR estimate can be formed that does not involve edge effects of the [0,1] interval in which the p-values lie.')
@click.option('--lfdr_adj', default=1.5, show_default=True, type=float, help='Numeric value that is applied as a multiple of the smoothing bandwidth used in the density estimation.')
@click.option('--lfdr_eps', default=np.power(10.0,-8), show_default=True, type=float, help='Numeric value that is threshold for the tails of the empirical p-value distribution.')
# OpenSWATH options
@click.option('--level', default='ms2', show_default=True, type=click.Choice(['ms1', 'ms2', 'ms1ms2', 'transition']), help='Either "ms1", "ms2", "ms1ms2" or "transition"; the data level selected for scoring. "ms1ms2 integrates both MS1- and MS2-level scores and can be used instead of "ms2"-level results."')
# IPF options
@click.option('--ipf_max_peakgroup_rank', default=1, show_default=True, type=int, help='Assess transitions only for candidate peak groups until maximum peak group rank.')
@click.option('--ipf_max_peakgroup_pep', default=0.7, show_default=True, type=float, help='Assess transitions only for candidate peak groups until maximum posterior error probability.')
@click.option('--ipf_max_transition_isotope_overlap', default=0.5, show_default=True, type=float, help='Maximum isotope overlap to consider transitions in IPF.')
@click.option('--ipf_min_transition_sn', default=0, show_default=True, type=float, help='Minimum log signal-to-noise level to consider transitions in IPF. Set -1 to disable this filter.')
# TRIC
@click.option('--tric_chromprob/--no-tric_chromprob', default=False, show_default=True, help='Whether chromatogram probabilities for TRIC should be computed.')
# Processing
@click.option('--threads', default=1, show_default=True, type=int, help='Number of threads used for semi-supervised learning. -1 means all available CPUs.', callback=transform_threads)
@click.option('--test/--no-test', default=False, show_default=True, help='Run in test mode with fixed seed.')
def score(infile, outfile, classifier, xgb_autotune, apply_weights, xeval_fraction, xeval_num_iter, ss_initial_fdr, ss_iteration_fdr, ss_num_iter, ss_main_score, group_id, parametric, pfdr, pi0_lambda, pi0_method, pi0_smooth_df, pi0_smooth_log_pi0, lfdr_truncate, lfdr_monotone, lfdr_transformation, lfdr_adj, lfdr_eps, level, ipf_max_peakgroup_rank, ipf_max_peakgroup_pep, ipf_max_transition_isotope_overlap, ipf_min_transition_sn, tric_chromprob, threads, test, ss_score_filter):
    """
    Conduct semi-supervised learning and error-rate estimation for MS1, MS2 and transition-level data.
    """

    if outfile is None:
        outfile = infile
    else:
        outfile = outfile

    # Prepare XGBoost-specific parameters
    xgb_hyperparams = {'autotune': xgb_autotune, 'autotune_num_rounds': 10, 'num_boost_round': 100, 'early_stopping_rounds': 10, 'test_size': 0.33}

    xgb_params = {'eta': 0.3, 'gamma': 0, 'max_depth': 6, 'min_child_weight': 1, 'subsample': 1, 'colsample_bytree': 1, 'colsample_bylevel': 1, 'colsample_bynode': 1, 'lambda': 1, 'alpha': 0, 'scale_pos_weight': 1, 'silent': 1, 'objective': 'binary:logitraw', 'nthread': 1, 'eval_metric': 'auc'}

    xgb_params_space = {'eta': hp.uniform('eta', 0.0, 0.3), 'gamma': hp.uniform('gamma', 0.0, 0.5), 'max_depth': hp.quniform('max_depth', 2, 8, 1), 'min_child_weight': hp.quniform('min_child_weight', 1, 5, 1), 'subsample': 1, 'colsample_bytree': 1, 'colsample_bylevel': 1, 'colsample_bynode': 1, 'lambda': hp.uniform('lambda', 0.0, 1.0), 'alpha': hp.uniform('alpha', 0.0, 1.0), 'scale_pos_weight': 1.0, 'silent': 1, 'objective': 'binary:logitraw', 'nthread': 1, 'eval_metric': 'auc'}

    if not apply_weights:
        PyProphetLearner(infile, outfile, classifier, xgb_hyperparams, xgb_params, xgb_params_space, xeval_fraction, xeval_num_iter, ss_initial_fdr, ss_iteration_fdr, ss_num_iter, ss_main_score, group_id, parametric, pfdr, pi0_lambda, pi0_method, pi0_smooth_df, pi0_smooth_log_pi0, lfdr_truncate, lfdr_monotone, lfdr_transformation, lfdr_adj, lfdr_eps, level, ipf_max_peakgroup_rank, ipf_max_peakgroup_pep, ipf_max_transition_isotope_overlap, ipf_min_transition_sn, tric_chromprob, threads, test, ss_score_filter).run()
    else:
        PyProphetWeightApplier(infile, outfile, classifier, xgb_hyperparams, xgb_params, xgb_params_space, xeval_fraction, xeval_num_iter, ss_initial_fdr, ss_iteration_fdr, ss_num_iter, ss_main_score, group_id, parametric, pfdr, pi0_lambda, pi0_method, pi0_smooth_df, pi0_smooth_log_pi0, lfdr_truncate, lfdr_monotone, lfdr_transformation, lfdr_adj, lfdr_eps, level, ipf_max_peakgroup_rank, ipf_max_peakgroup_pep, ipf_max_transition_isotope_overlap, ipf_min_transition_sn, tric_chromprob, threads, test, apply_weights, ss_score_filter).run()

# IPF
@cli.command()
# File handling
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='PyProphet input file.')
@click.option('--out', 'outfile', type=click.Path(exists=False), help='PyProphet output file.')
# IPF parameters
@click.option('--ipf_ms1_scoring/--no-ipf_ms1_scoring', default=True, show_default=True, help='Use MS1 precursor data for IPF.')
@click.option('--ipf_ms2_scoring/--no-ipf_ms2_scoring', default=True, show_default=True, help='Use MS2 precursor data for IPF.')
@click.option('--ipf_h0/--no-ipf_h0', default=True, show_default=True, help='Include possibility that peak groups are not covered by peptidoform space.')
@click.option('--ipf_grouped_fdr/--no-ipf_grouped_fdr', default=False, show_default=True, help='[Experimental] Compute grouped FDR instead of pooled FDR to better support data where peak groups are evaluated to originate from very heterogeneous numbers of peptidoforms.')
@click.option('--ipf_max_precursor_pep', default=0.7, show_default=True, type=float, help='Maximum PEP to consider scored precursors in IPF.')
@click.option('--ipf_max_peakgroup_pep', default=0.7, show_default=True, type=float, help='Maximum PEP to consider scored peak groups in IPF.')
@click.option('--ipf_max_precursor_peakgroup_pep', default=0.4, show_default=True, type=float, help='Maximum BHM layer 1 integrated precursor peakgroup PEP to consider in IPF.')
@click.option('--ipf_max_transition_pep', default=0.6, show_default=True, type=float, help='Maximum PEP to consider scored transitions in IPF.')
def ipf(infile, outfile, ipf_ms1_scoring, ipf_ms2_scoring, ipf_h0, ipf_grouped_fdr, ipf_max_precursor_pep, ipf_max_peakgroup_pep, ipf_max_precursor_peakgroup_pep, ipf_max_transition_pep):
    """
    Infer peptidoforms after scoring of MS1, MS2 and transition-level data.
    """

    if outfile is None:
        outfile = infile
    else:
        outfile = outfile

    infer_peptidoforms(infile, outfile, ipf_ms1_scoring, ipf_ms2_scoring, ipf_h0, ipf_grouped_fdr, ipf_max_precursor_pep, ipf_max_peakgroup_pep, ipf_max_precursor_peakgroup_pep, ipf_max_transition_pep)

# Peptide-level inference
@cli.command()
# File handling
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='PyProphet input file.')
@click.option('--out', 'outfile', type=click.Path(exists=False), help='PyProphet output file.')
# Context
@click.option('--context', default='run-specific', show_default=True, type=click.Choice(['run-specific', 'experiment-wide', 'global']), help='Context to estimate protein-level FDR control.')
# Statistics
@click.option('--parametric/--no-parametric', default=False, show_default=True, help='Do parametric estimation of p-values.')
@click.option('--pfdr/--no-pfdr', default=False, show_default=True, help='Compute positive false discovery rate (pFDR) instead of FDR.')
@click.option('--pi0_lambda', default=[0.1,0.5,0.05], show_default=True, type=(float, float, float), help='Use non-parametric estimation of p-values. Either use <START END STEPS>, e.g. 0.1, 1.0, 0.1 or set to fixed value, e.g. 0.4, 0, 0.', callback=transform_pi0_lambda)
@click.option('--pi0_method', default='bootstrap', show_default=True, type=click.Choice(['smoother', 'bootstrap']), help='Either "smoother" or "bootstrap"; the method for automatically choosing tuning parameter in the estimation of pi_0, the proportion of true null hypotheses.')
@click.option('--pi0_smooth_df', default=3, show_default=True, type=int, help='Number of degrees-of-freedom to use when estimating pi_0 with a smoother.')
@click.option('--pi0_smooth_log_pi0/--no-pi0_smooth_log_pi0', default=False, show_default=True, help='If True and pi0_method = "smoother", pi0 will be estimated by applying a smoother to a scatterplot of log(pi0) estimates against the tuning parameter lambda.')
@click.option('--lfdr_truncate/--no-lfdr_truncate', show_default=True, default=True, help='If True, local FDR values >1 are set to 1.')
@click.option('--lfdr_monotone/--no-lfdr_monotone', show_default=True, default=True, help='If True, local FDR values are non-decreasing with increasing p-values.')
@click.option('--lfdr_transformation', default='probit', show_default=True, type=click.Choice(['probit', 'logit']), help='Either a "probit" or "logit" transformation is applied to the p-values so that a local FDR estimate can be formed that does not involve edge effects of the [0,1] interval in which the p-values lie.')
@click.option('--lfdr_adj', default=1.5, show_default=True, type=float, help='Numeric value that is applied as a multiple of the smoothing bandwidth used in the density estimation.')
@click.option('--lfdr_eps', default=np.power(10.0,-8), show_default=True, type=float, help='Numeric value that is threshold for the tails of the empirical p-value distribution.')
def peptide(infile, outfile, context, parametric, pfdr, pi0_lambda, pi0_method, pi0_smooth_df, pi0_smooth_log_pi0, lfdr_truncate, lfdr_monotone, lfdr_transformation, lfdr_adj, lfdr_eps):
    """
    Infer peptides and conduct error-rate estimation in different contexts.
    """

    if outfile is None:
        outfile = infile
    else:
        outfile = outfile

    infer_peptides(infile, outfile, context, parametric, pfdr, pi0_lambda, pi0_method, pi0_smooth_df, pi0_smooth_log_pi0, lfdr_truncate, lfdr_monotone, lfdr_transformation, lfdr_adj, lfdr_eps)

# Gene-level inference
@cli.command()
# File handling
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='PyProphet input file.')
@click.option('--out', 'outfile', type=click.Path(exists=False), help='PyProphet output file.')
# Context
@click.option('--context', default='run-specific', show_default=True, type=click.Choice(['run-specific', 'experiment-wide', 'global']), help='Context to estimate gene-level FDR control.')
# Statistics
@click.option('--parametric/--no-parametric', default=False, show_default=True, help='Do parametric estimation of p-values.')
@click.option('--pfdr/--no-pfdr', default=False, show_default=True, help='Compute positive false discovery rate (pFDR) instead of FDR.')
@click.option('--pi0_lambda', default=[0.1,0.5,0.05], show_default=True, type=(float, float, float), help='Use non-parametric estimation of p-values. Either use <START END STEPS>, e.g. 0.1, 1.0, 0.1 or set to fixed value, e.g. 0.4, 0, 0.', callback=transform_pi0_lambda)
@click.option('--pi0_method', default='bootstrap', show_default=True, type=click.Choice(['smoother', 'bootstrap']), help='Either "smoother" or "bootstrap"; the method for automatically choosing tuning parameter in the estimation of pi_0, the proportion of true null hypotheses.')
@click.option('--pi0_smooth_df', default=3, show_default=True, type=int, help='Number of degrees-of-freedom to use when estimating pi_0 with a smoother.')
@click.option('--pi0_smooth_log_pi0/--no-pi0_smooth_log_pi0', default=False, show_default=True, help='If True and pi0_method = "smoother", pi0 will be estimated by applying a smoother to a scatterplot of log(pi0) estimates against the tuning parameter lambda.')
@click.option('--lfdr_truncate/--no-lfdr_truncate', show_default=True, default=True, help='If True, local FDR values >1 are set to 1.')
@click.option('--lfdr_monotone/--no-lfdr_monotone', show_default=True, default=True, help='If True, local FDR values are non-decreasing with increasing p-values.')
@click.option('--lfdr_transformation', default='probit', show_default=True, type=click.Choice(['probit', 'logit']), help='Either a "probit" or "logit" transformation is applied to the p-values so that a local FDR estimate can be formed that does not involve edge effects of the [0,1] interval in which the p-values lie.')
@click.option('--lfdr_adj', default=1.5, show_default=True, type=float, help='Numeric value that is applied as a multiple of the smoothing bandwidth used in the density estimation.')
@click.option('--lfdr_eps', default=np.power(10.0,-8), show_default=True, type=float, help='Numeric value that is threshold for the tails of the empirical p-value distribution.')
def gene(infile, outfile, context, parametric, pfdr, pi0_lambda, pi0_method, pi0_smooth_df, pi0_smooth_log_pi0, lfdr_truncate, lfdr_monotone, lfdr_transformation, lfdr_adj, lfdr_eps):
    """
    Infer genes and conduct error-rate estimation in different contexts.
    """

    if outfile is None:
        outfile = infile
    else:
        outfile = outfile

    infer_genes(infile, outfile, context, parametric, pfdr, pi0_lambda, pi0_method, pi0_smooth_df, pi0_smooth_log_pi0, lfdr_truncate, lfdr_monotone, lfdr_transformation, lfdr_adj, lfdr_eps)

# Protein-level inference
@cli.command()
# File handling
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='PyProphet input file.')
@click.option('--out', 'outfile', type=click.Path(exists=False), help='PyProphet output file.')
# Context
@click.option('--context', default='run-specific', show_default=True, type=click.Choice(['run-specific', 'experiment-wide', 'global']), help='Context to estimate protein-level FDR control.')
# Statistics
@click.option('--parametric/--no-parametric', default=False, show_default=True, help='Do parametric estimation of p-values.')
@click.option('--pfdr/--no-pfdr', default=False, show_default=True, help='Compute positive false discovery rate (pFDR) instead of FDR.')
@click.option('--pi0_lambda', default=[0.1,0.5,0.05], show_default=True, type=(float, float, float), help='Use non-parametric estimation of p-values. Either use <START END STEPS>, e.g. 0.1, 1.0, 0.1 or set to fixed value, e.g. 0.4, 0, 0.', callback=transform_pi0_lambda)
@click.option('--pi0_method', default='bootstrap', show_default=True, type=click.Choice(['smoother', 'bootstrap']), help='Either "smoother" or "bootstrap"; the method for automatically choosing tuning parameter in the estimation of pi_0, the proportion of true null hypotheses.')
@click.option('--pi0_smooth_df', default=3, show_default=True, type=int, help='Number of degrees-of-freedom to use when estimating pi_0 with a smoother.')
@click.option('--pi0_smooth_log_pi0/--no-pi0_smooth_log_pi0', default=False, show_default=True, help='If True and pi0_method = "smoother", pi0 will be estimated by applying a smoother to a scatterplot of log(pi0) estimates against the tuning parameter lambda.')
@click.option('--lfdr_truncate/--no-lfdr_truncate', show_default=True, default=True, help='If True, local FDR values >1 are set to 1.')
@click.option('--lfdr_monotone/--no-lfdr_monotone', show_default=True, default=True, help='If True, local FDR values are non-decreasing with increasing p-values.')
@click.option('--lfdr_transformation', default='probit', show_default=True, type=click.Choice(['probit', 'logit']), help='Either a "probit" or "logit" transformation is applied to the p-values so that a local FDR estimate can be formed that does not involve edge effects of the [0,1] interval in which the p-values lie.')
@click.option('--lfdr_adj', default=1.5, show_default=True, type=float, help='Numeric value that is applied as a multiple of the smoothing bandwidth used in the density estimation.')
@click.option('--lfdr_eps', default=np.power(10.0,-8), show_default=True, type=float, help='Numeric value that is threshold for the tails of the empirical p-value distribution.')
def protein(infile, outfile, context, parametric, pfdr, pi0_lambda, pi0_method, pi0_smooth_df, pi0_smooth_log_pi0, lfdr_truncate, lfdr_monotone, lfdr_transformation, lfdr_adj, lfdr_eps):
    """
    Infer proteins and conduct error-rate estimation in different contexts.
    """

    if outfile is None:
        outfile = infile
    else:
        outfile = outfile

    infer_proteins(infile, outfile, context, parametric, pfdr, pi0_lambda, pi0_method, pi0_smooth_df, pi0_smooth_log_pi0, lfdr_truncate, lfdr_monotone, lfdr_transformation, lfdr_adj, lfdr_eps)

# Subsample OpenSWATH file to minimum for integrated scoring
@cli.command()
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='OpenSWATH input file.')
@click.option('--out','outfile', type=click.Path(exists=False), help='Subsampled OSWS output file.')
@click.option('--subsample_ratio', default=1, show_default=True, type=float, help='Subsample ratio used per input file.', callback=transform_subsample_ratio)
@click.option('--test/--no-test', default=False, show_default=True, help='Run in test mode with fixed seed.')
def subsample(infile, outfile, subsample_ratio, test):
    """
    Subsample OpenSWATH file to minimum for integrated scoring
    """

    if outfile is None:
        outfile = infile
    else:
        outfile = outfile

    subsample_osw(infile, outfile, subsample_ratio, test)

# Reduce scored PyProphet file to minimum for global scoring
@cli.command()
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='Scored PyProphet input file.')
@click.option('--out','outfile', type=click.Path(exists=False), help='Reduced OSWR output file.')
def reduce(infile, outfile):
    """
    Reduce scored PyProphet file to minimum for global scoring
    """

    if outfile is None:
        outfile = infile
    else:
        outfile = outfile

    reduce_osw(infile, outfile)

# Merging of multiple runs
@cli.command()
@click.argument('infiles', nargs=-1, type=click.Path(exists=True))
@click.option('--out','outfile', required=True, type=click.Path(exists=False), help='Merged OSW output file.')
@click.option('--same_run/--no-same_run', default=False, help='Assume input files are from same run (deletes run information).')
@click.option('--template','templatefile', required=True, type=click.Path(exists=False), help='Template OSW file.')
@click.option('--merged_post_scored_runs', is_flag=True, help='Merge OSW output files that have already been scored.')
def merge(infiles, outfile, same_run, templatefile, merged_post_scored_runs):
    """
    Merge multiple OSW files and (for large experiments, it is recommended to subsample first).
    """

    if len(infiles) < 1:
        raise click.ClickException("At least one PyProphet input file needs to be provided.")

    merge_osw(infiles, outfile, templatefile, same_run, merged_post_scored_runs)

# Backpropagate multi-run peptide and protein scores to single files
@cli.command()
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='Single run PyProphet input file.')
@click.option('--out','outfile', type=click.Path(exists=False), help='Single run (with multi-run scores) PyProphet output file.')
@click.option('--apply_scores', required=True, type=click.Path(exists=True), help='PyProphet multi-run scores file to apply.')
def backpropagate(infile, outfile, apply_scores):
    """
    Backpropagate multi-run peptide and protein scores to single files
    """

    if outfile is None:
        outfile = infile
    else:
        outfile = outfile

    backpropagate_oswr(infile, outfile, apply_scores)

# Export TSV
@cli.command()
# File handling
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='PyProphet input file.')
@click.option('--out', 'outfile', type=click.Path(exists=False), help='Output TSV/CSV (matrix, legacy_split, legacy_merged) file.')
@click.option('--format', default='legacy_split', show_default=True, type=click.Choice(['matrix', 'legacy_split', 'legacy_merged','score_plots']), help='Export format, either matrix, legacy_split/legacy_merged (mProphet/PyProphet) or score_plots format.')
@click.option('--csv/--no-csv', 'outcsv', default=False, show_default=True, help='Export CSV instead of TSV file.')
# Context
@click.option('--transition_quantification/--no-transition_quantification', default=True, show_default=True, help='[format: legacy] Report aggregated transition-level quantification.')
@click.option('--max_transition_pep', default=0.7, show_default=True, type=float, help='[format: legacy] Maximum PEP to retain scored transitions for quantification (requires transition-level scoring).')
@click.option('--ipf', default='peptidoform', show_default=True, type=click.Choice(['peptidoform','augmented','disable']), help='[format: matrix/legacy] Should IPF results be reported if present? "peptidoform": Report results on peptidoform-level, "augmented": Augment OpenSWATH results with IPF scores, "disable": Ignore IPF results')
@click.option('--ipf_max_peptidoform_pep', default=0.4, show_default=True, type=float, help='[format: matrix/legacy] IPF: Filter results to maximum run-specific peptidoform-level PEP.')
@click.option('--max_rs_peakgroup_qvalue', default=0.05, show_default=True, type=float, help='[format: matrix/legacy] Filter results to maximum run-specific peak group-level q-value.')
@click.option('--peptide/--no-peptide', default=True, show_default=True, help='Append peptide-level error-rate estimates if available.')
@click.option('--max_global_peptide_qvalue', default=0.01, show_default=True, type=float, help='[format: matrix/legacy] Filter results to maximum global peptide-level q-value.')
@click.option('--protein/--no-protein', default=True, show_default=True, help='Append protein-level error-rate estimates if available.')
@click.option('--max_global_protein_qvalue', default=0.01, show_default=True, type=float, help='[format: matrix/legacy] Filter results to maximum global protein-level q-value.')
def export(infile, outfile, format, outcsv, transition_quantification, max_transition_pep, ipf, ipf_max_peptidoform_pep, max_rs_peakgroup_qvalue, peptide, max_global_peptide_qvalue, protein, max_global_protein_qvalue):
    """
    Export TSV/CSV tables
    """
    if format == "score_plots":
        export_score_plots(infile)
    else:
        if outfile is None:
            if outcsv:
                outfile = infile.split(".osw")[0] + ".csv"
            else:
                outfile = infile.split(".osw")[0] + ".tsv"
        else:
            outfile = outfile

        export_tsv(infile, outfile, format, outcsv, transition_quantification, max_transition_pep, ipf, ipf_max_peptidoform_pep, max_rs_peakgroup_qvalue, peptide, max_global_peptide_qvalue, protein, max_global_protein_qvalue)

# Export Compound TSV
@cli.command()
#File handling
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='PyProphet input file.')
@click.option('--out', 'outfile', type=click.Path(exists=False), help='Output TSV/CSV (matrix, legacy_merged) file.')
@click.option('--format', default='legacy_merged', show_default=True, type=click.Choice(['matrix', 'legacy_merged','score_plots']), help='Export format, either matrix, legacy_merged (PyProphet) or score_plots format.')
@click.option('--csv/--no-csv', 'outcsv', default=False, show_default=True, help='Export CSV instead of TSV file.')
# Context
@click.option('--max_rs_peakgroup_qvalue', default=0.05, show_default=True, type=float, help='[format: matrix/legacy] Filter results to maximum run-specific peak group-level q-value.')
def export_compound(infile, outfile, format, outcsv, max_rs_peakgroup_qvalue):
    """
    Export Compound TSV/CSV tables
    """
    if format == "score_plots":
        export_score_plots(infile)
    else:
        if outfile is None:
            if outcsv:
                outfile = infile.split(".osw")[0] + ".csv"
            else:
                outfile = infile.split(".osw")[0] + ".tsv"
        else:
            outfile = outfile

        export_compound_tsv(infile, outfile, format, outcsv, max_rs_peakgroup_qvalue)

# Filter sqMass files
@cli.command()
# File handling
@click.argument('sqmassfiles', nargs=-1, type=click.Path(exists=True))
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='PyProphet input file.')
@click.option('--max_precursor_pep', default=0.7, show_default=True, type=float, help='Maximum PEP to retain scored precursors in sqMass.')
@click.option('--max_peakgroup_pep', default=0.7, show_default=True, type=float, help='Maximum PEP to retain scored peak groups in sqMass.')
@click.option('--max_transition_pep', default=0.7, show_default=True, type=float, help='Maximum PEP to retain scored transitions in sqMass.')
def filter(sqmassfiles, infile, max_precursor_pep, max_peakgroup_pep, max_transition_pep):
    """
    Filter sqMass files
    """

    filter_sqmass(sqmassfiles, infile, max_precursor_pep, max_peakgroup_pep, max_transition_pep)

# Print statistics
@cli.command()
@click.option('--in', 'infile', required=True, type=click.Path(exists=True), help='PyProphet input file.')
def statistics(infile):
    """
    Print PyProphet statistics
    """

    con = sqlite3.connect(infile)

    qts = [0.01, 0.05, 0.10]

    for qt in qts:
        if check_sqlite_table(con, 'SCORE_MS2'):
            peakgroups = pd.read_sql('SELECT * FROM SCORE_MS2 INNER JOIN FEATURE ON SCORE_MS2.feature_id = FEATURE.id INNER JOIN PRECURSOR ON FEATURE.precursor_id = PRECURSOR.id INNER JOIN RUN ON FEATURE.RUN_ID = RUN.ID WHERE RANK==1 AND DECOY==0;' , con)

            click.echo("Total peakgroups (q-value<%s): %s" % (qt, len(peakgroups[peakgroups['QVALUE']<qt][['FEATURE_ID']].drop_duplicates())))
            click.echo("Total peakgroups per run (q-value<%s):" % qt)
            click.echo(tabulate(peakgroups[peakgroups['QVALUE']<qt].groupby(['FILENAME'])['FEATURE_ID'].nunique().reset_index(), showindex=False))
            click.echo(10*"=")

        if check_sqlite_table(con, 'SCORE_PEPTIDE'):
            peptides_global = pd.read_sql('SELECT * FROM SCORE_PEPTIDE INNER JOIN PEPTIDE ON SCORE_PEPTIDE.peptide_id = PEPTIDE.id WHERE CONTEXT=="global" AND DECOY==0;' , con)
            peptides = pd.read_sql('SELECT * FROM SCORE_PEPTIDE INNER JOIN PEPTIDE ON SCORE_PEPTIDE.peptide_id = PEPTIDE.id INNER JOIN RUN ON SCORE_PEPTIDE.RUN_ID = RUN.ID WHERE DECOY==0;' , con)

            click.echo("Total peptides (global context) (q-value<%s): %s" % (qt, len(peptides_global[peptides_global['QVALUE']<qt][['PEPTIDE_ID']].drop_duplicates())))
            click.echo(tabulate(peptides[peptides['QVALUE']<qt].groupby(['FILENAME'])['PEPTIDE_ID'].nunique().reset_index(), showindex=False))
            click.echo(10*"=")

        if check_sqlite_table(con, 'SCORE_PROTEIN'):
            proteins_global = pd.read_sql('SELECT * FROM SCORE_PROTEIN INNER JOIN PROTEIN ON SCORE_PROTEIN.protein_id = PROTEIN.id WHERE CONTEXT=="global" AND DECOY==0;' , con)
            proteins = pd.read_sql('SELECT * FROM SCORE_PROTEIN INNER JOIN PROTEIN ON SCORE_PROTEIN.protein_id = PROTEIN.id INNER JOIN RUN ON SCORE_PROTEIN.RUN_ID = RUN.ID WHERE DECOY==0;' , con)

            click.echo("Total proteins (global context) (q-value<%s): %s" % (qt, len(proteins_global[proteins_global['QVALUE']<qt][['PROTEIN_ID']].drop_duplicates())))
            click.echo(tabulate(proteins[proteins['QVALUE']<qt].groupby(['FILENAME'])['PROTEIN_ID'].nunique().reset_index(), showindex=False))
            click.echo(10*"=")


# -*- coding: utf-8 -*-

import numpy as np
import os
import random as rd
import pygame as pg
import sys

import utils
from utils import *
import time

class Map:
    def __init__(self, tx, ty):
        self.map = np.full((ty, tx), " ", dtype='U1') #ne pas inverser
        self.__folder__ = 'resx/maps/'
        self.__encoding__ = 'UTF-8'
        self.__delimiter__ = ' '
        self.__comment__ = '§'
        self.superposes = ['v', 'a', 'd', 'm', '+', '€']

        folder_img = 'resx/imgs/'
        self.textures = {'-': pg.image.load(get_path(folder_img + 'wall.png')),
                         '|': pg.image.load(get_path(folder_img + 'wall.png')),
                         'v': pg.image.load(get_path(folder_img + 'lifepotion.png')),
                         'a': pg.image.load(get_path(folder_img + 'strengthpotion.png')),
                         'd': pg.image.load(get_path(folder_img + 'defpotion.png')),
                         'm': pg.image.load(get_path(folder_img + 'manapotion.png')),
                         '+' : pg.image.load(get_path(folder_img + 'door.png')),
                         '#' : pg.image.load(get_path(folder_img + 'corridor.png')),
                         '.' : pg.image.load(get_path(folder_img + 'sol.png')),
                         '€' : pg.image.load(get_path(folder_img + 'money.png')),
                         '¤' : pg.image.load(utils.get_path(folder_img + 'void.png'))}

    def get_tile(self, i, j):
        if self.map[i,j] in self.textures:
            t = self.textures[self.map[i,j]]
            if self.map[i,j] in self.superposes:
                return [self.textures['.'], t]
            else:
                return [t]
        elif self.map[i,j] in ['@', '&', 'C']:
            return [self.textures['.']]
        else:
            return [self.textures['¤']]

    def load(self, name):
        self.map = np.genfromtxt(utils.get_path(self.__folder__ + name), delimiter=self.__delimiter__, dtype='U1', comments=self.__comment__, encoding=self.__encoding__)
        print(f"Map {name} loaded (shape {self.map.shape})")

    def save(self, name):
        np.savetxt(utils.get_path(self.__folder__ + name), self.map, fmt='%s', delimiter=self.__delimiter__, encoding=self.__encoding__)
        print(f"Map {name} saved")

    def generate(self, seed=None):
        if seed != None:
            rd.seed(seed)

        ty, tx = self.map.shape
        self.map = np.full((ty, tx), "¤", dtype='U1')
        cases = []

        def valid_pos():
            j = rd.randrange(1, tx-2) # pas sur le bord
            i = rd.randrange(1, ty-2) # en bas case pas vide
            taille = min(rd.randint(3,7),ty-i, tx-j)

            recouvrement = False
            for ib in range(i, i+taille):
                for jb in range(j ,j+taille):
                    if (self.map[ib, jb] != '¤'):
                        recouvrement = True

            if (self.map[i, j] == '¤') and not recouvrement:
                return (i, j, taille)
            else:
                return valid_pos()

        nb_rooms = rd.randint(15,30)
        col = []
        for _ in range(nb_rooms):
            i,j, taille = valid_pos()
            borders = []
            for ib in range(i, i+taille):
                for jb in range(j ,j+taille):
                    if ((ib!=i) and (jb!=j) and (ib!=(i+taille-1)) and (jb!=(j+taille-1))):
                        if (self.map[ib,jb] == '¤'):
                            self.map[ib,jb] = '.'
                        cases.append((ib,jb))
                    else:
                        if (self.map[ib,jb] == '¤'):
                            self.map[ib,jb] = '-'
                        if (not ((ib == i) and (jb == j)) # pour la porte, quelles frontières bien
                        and not ((ib==i) and (jb==(j+taille-1)))
                        and not ((ib==(i+taille-1)) and (jb==j))
                        and not ((ib==(i+taille-1)) and (jb==(j+taille-1)))):
                            if ((0 < ib < (ty-1)) and (0 < (jb) < (tx-1))):
                                borders.append((ib,jb))
            col.append(borders)

        # portes
        locs = []
        for e in col:
            tmp = rd.randrange(0, len(e))
            vu = set()
            while len(list(get_neighbors(self.map, e[tmp]))) == 0 and len(vu) < len(e):
                tmp = rd.randrange(0, len(e))
                vu.add(e[tmp])
            locs.append(e[tmp])
            self.map[locs[-1]] = '+'

        # couloirs
        k = len(locs)
        for i in range(k):
            for j in range(i, k):
                if i != j:
                    try: #éviter de trop relier
                        find_path(self.map, locs[i], locs[j], True)
                    except:
                        try:
                            p = find_path(self.map, locs[i], locs[j])
                            for e in p[1:-1]:
                                self.map[e] = '#'
                        except:
                            print('err')

        # personnages, potions, gobelins
        def ajouter_elem(elem, min_i=1, max_i=6):
            nb_potions = rd.randint(min_i, max_i)
            for _ in range(nb_potions):
                ind = rd.randrange(0, len(cases))
                self.map[cases[ind]] = elem
                del cases[ind]
                if (len(cases) == 0):
                    break

        ajouter_elem('@', 1, 1)
        ajouter_elem('C', 1, 1)
        ajouter_elem('v',1,3)
        ajouter_elem('a',1,3)
        ajouter_elem('d',1,3)
        ajouter_elem('m',1,3)
        ajouter_elem('€',1,3)
        ajouter_elem('&', 2, 10)

        print("Map generated")


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

# COPYRIGHT (C) 2014-2020 Mitsuo KONDOU.
# This software is released under the MIT License.
# https://github.com/konsan1101
# Thank you for keeping the rules.

import os
import time

import queue
import threading
import subprocess

import pyautogui
import numpy as np
import cv2

class qFFmpeg_class:

    def __init__(self, ):
        self.face_procWidth  = 640
        self.face_procHeight = 480
        self.face_casName    = '_xml/_vision_opencv_face.xml'
        self.face_cascade    = cv2.CascadeClassifier(self.face_casName)
        self.face_haar_scale    = 1.1
        self.face_min_neighbors = 10
        self.face_min_size      = ( 15, 15)

    def detect_face(self, inp_image=None, ):
        output_img  = inp_image
        output_face = []

        try:

                image_img   = inp_image.copy()
                image_height, image_width = image_img.shape[:2]

                output_img  = image_img.copy()

                proc_width  = image_width
                proc_height = image_height
                if (proc_width  > self.face_procWidth):
                    proc_width  = self.face_procWidth
                    proc_height = int(proc_width * image_height / image_width)
                if (proc_width  != image_width ) \
                or (proc_height != image_height):
                    proc_img = cv2.resize(image_img, (proc_width, proc_height))
                else:
                    proc_img = image_img.copy()
                    proc_height, proc_width = proc_img.shape[:2]

                gray1 = cv2.cvtColor(proc_img, cv2.COLOR_BGR2GRAY)
                gray2 = cv2.equalizeHist(gray1)

                hit_count = 0
                hit_img   = None

                rects = self.face_cascade.detectMultiScale(gray2,
                        scaleFactor=self.face_haar_scale,
                        minNeighbors=self.face_min_neighbors,
                        minSize=self.face_min_size, )
                if (not rects is None):
                    for (hit_x, hit_y, hit_w, hit_h) in rects:
                        hit_count += 1
                        x  = int(hit_x * image_width  / proc_width )
                        y  = int(hit_y * image_height / proc_height)
                        w  = int(hit_w * image_width  / proc_width )
                        h  = int(hit_h * image_height / proc_height)
                        if (x > 10):
                            x -= 10
                            w += 20
                        if (y > 10):
                            y -= 10
                            h += 20
                        if (x < 0):
                            x = 0
                        if (y < 0):
                            y = 0
                        if ((x + w) > image_width):
                                w = image_width - x
                        if ((y + h) > image_height):
                                h = image_height - y
                        cv2.rectangle(output_img, (x,y), (x+w,y+h), (0,0,255), 2)

                        hit_img = cv2.resize(image_img[y:y+h, x:x+w],(h,w))

                        # 結果出力
                        output_face.append(hit_img)

        except:
            pass

        return output_img, output_face

    def dshow_dev_list(self, ):
        cam = []
        mic = []

        if (os.name == 'nt'):

            ffmpeg = subprocess.Popen(['ffmpeg',
	            '-threads', '2',
	            '-f', 'dshow',
	            '-list_devices', 'true',
	            '-i', 'nul',
	            ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

            flag = ''
            checkTime = time.time()
            while ((time.time() - checkTime) < 2):
                # バッファから1行読み込む.
                line = ffmpeg.stderr.readline()
                # バッファが空 + プロセス終了.
                if (not line) and (not ffmpeg.poll() is None):
                    break
                # テキスト
                txt = line.decode('utf-8')
                if   (txt.find('DirectShow video devices') >=0):
                    flag = 'cam'
                elif (txt.find('DirectShow audio devices') >=0):
                    flag = 'mic'
                elif (flag == 'cam') and (txt.find(']  "') >=0):
                    st = txt.find(']  "') + 4
                    en = txt[st:].find('"')
                    cam.append(txt[st:st+en])
                    #print('cam:', txt[st:st+en])
                elif (flag == 'mic') and (txt.find(']  "') >=0):
                    st = txt.find(']  "') + 4
                    en = txt[st:].find('"')
                    mic.append(txt[st:st+en])
                    #print('mic:', txt[st:st+en])

            ffmpeg.terminate()
            ffmpeg = None

        return cam, mic

    def capture(self, dev='desktop', full=True,
                work_path='temp/_work/desktop', retry_max=1, retry_wait=2.00, ):

        retry_max   = 3
        retry_count = 0
        check       = False
        while (check == False) and (retry_count <= retry_max):
            # キャプチャー
            image = self.capture_sub(dev, full, work_path)
            if (not image is None):
                check = True
            # リトライ
            if (check == False):
                time.sleep(retry_wait)

        if (check == True):
            return image
        else:
            return None

    def capture_sub(self, dev='desktop', full=True,
                    work_path='temp/_work/desktop', ):
        capture = None

        # ファイル削除
        for i in range(1, 9999):
            fn = work_path + '.' + '{:04}'.format(i) + '.jpg'
            if os.path.isfile(fn):
                os.remove(fn)
            else:
                break

        # pyautogui キャプチャ
        if (capture is None):
            if (dev == 'desktop') and (full == False):

                pil_image = pyautogui.screenshot()
                capture = np.array(pil_image, dtype=np.uint8)
                if (capture.ndim == 2):  # モノクロ
                    pass
                elif (capture.shape[2] == 3):  # カラー
                    capture = cv2.cvtColor(capture, cv2.COLOR_RGB2BGR)
                elif (capture.shape[2] == 4):  # 透過
                    capture = cv2.cvtColor(capture, cv2.COLOR_RGBA2BGRA)

                # イメージ保存
                #try:
                #    cv2.imwrite(workpath + '.0001.jpg', capture)
                #except:
                #    capture = None

        # ffmpag キャプチャ
        if (capture is None):

            ffmpeg = None

            # デスクトップ
            if (dev == 'desktop'):
                if (os.name == 'nt'):

                    ffmpeg = subprocess.Popen(['ffmpeg',
                        '-threads', '2',
                        '-f', 'gdigrab', '-i', 'desktop',
                        '-ss','0','-t','0.2','-r','10',
                        '-q','1',
                        work_path + '.' + '%04d.jpg',
                        '-loglevel', 'warning',
                        #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                        ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

                else:

                    ffmpeg = subprocess.Popen(['ffmpeg',
                        '-threads', '2',
                        '-f', 'avfoundation', '-i', '1:2',
                        '-ss','0','-t','0.2','-r','10',
                        '-q','1',
                        work_path + '.' + '%04d.jpg',
                        '-loglevel', 'warning',
                        #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                        ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

            # カメラ
            if (dev != 'desktop'):
                if (os.name == 'nt'):

                    ffmpeg = subprocess.Popen(['ffmpeg',
                        '-threads', '2',
                        '-rtbufsize', '1024M',
                        '-f', 'dshow', '-i', 'video=' + dev,
                        '-ss','0','-t','0.2','-r','10',
                        '-q','1',
                        work_path + '.' + '%04d.jpg',
                        '-loglevel', 'warning',
                        #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                        ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

            # 時限待機・画像取得
            if (not ffmpeg is None):
                checkTime = time.time()
                while ((time.time() - checkTime) < 2):
                    line = ffmpeg.stderr.readline()
                    #print(line)
                    if (not line) and (not ffmpeg.poll() is None):
                        break
                ffmpeg.terminate()
                ffmpeg = None

            # イメージ取得
            try:
                capture = cv2.imread(work_path + '.0001.jpg')
            except:
                capture = None

        return capture

    def rec_start(self, dev='desktop', rate=10,
                    out_filev='temp/_work/recorder.mp4', out_filea='temp/_work/recorder.wav',
                    retry_max=3, retry_wait=5.00, ):

        #retry_max   = 3
        retry_count = 0
        check       = False
        while (check == False) and (retry_count <= retry_max):
            # 録画
            res_ffmpeg, res_sox, res_filev, res_filea = self.rec_start_sub(
                dev=dev, rate=rate, out_filev=out_filev, out_filea=out_filea, try_count=retry_count, )
            # チェック
            if (not res_ffmpeg is None) or (not res_sox is None):
                check = True
            # リトライ
            if (check == False) and (retry_count < retry_max):
                #print('retry')
                time.sleep(retry_wait)
                retry_count += 1

        if (check == True):
            return res_ffmpeg, res_sox, res_filev, res_filea
        else:
            return None, None, '', ''

    def rec_start_sub(self, dev='desktop', rate=10,
                        out_filev='temp/_work/recorder.mp4', out_filea='temp/_work/recorder.wav',
                        try_count=0, ):
        res_ffmpeg = None
        res_sox    = None
        res_filev  = out_filev
        res_filea  = out_filea

        # 録画　開始
        if (res_filev != ''):

            # デスクトップ Linux
            if (res_ffmpeg == None) and (dev == 'desktop'):
                if (os.name != 'nt'):

                        if (res_filev[-4:] != '.flv'):
                            res_filev = res_filev[:-4] + '.flv'

                        if (os.path.isfile(res_filev)):
                            os.remove(res_filev)

                        # ffmpeg -f avfoundation -i 1:2 -vcodec flv1 -q:v 0 -r 10 desktop.flv
                        res_ffmpeg = subprocess.Popen(['ffmpeg',
                            '-threads', '2',
                            '-f', 'avfoundation',
                            '-i', '1:2',
                            '-vf', 'scale=1920:-2',
                            '-vcodec', 'flv1',
                            '-q:v', '0',
                            '-r', str(rate),
                            res_filev,
                            '-loglevel', 'warning',
                            #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                            ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

            # デスクトップ Windows ※初回
            if (res_ffmpeg == None) and (dev == 'desktop'):
                if (os.name == 'nt'):
                    if (try_count == 0) and (res_filev[-4:] == '.mp4'):

                        if (os.path.isfile(res_filev)):
                            os.remove(res_filev)

                        # GPU encoder, intel GPU 6th enable !
                        # ffmpeg -init_hw_device qsv:hw -f gdigrab -i desktop -vcodec hevc_qsv -r 10 desktop.mp4
                        res_ffmpeg = subprocess.Popen(['ffmpeg',
                            '-threads', '2',
                            '-init_hw_device', 'qsv:hw',
                            '-f', 'gdigrab', '-i', 'desktop',
                            '-vf', 'scale=1920:-2',
                            '-vcodec', 'hevc_qsv',
                            '-r', str(rate),
                            res_filev,
                            '-loglevel', 'warning',
                            #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                            ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

            # デスクトップ Windows ※２回目
            if (res_ffmpeg == None) and (dev == 'desktop'):
                if (os.name == 'nt'):
                    if (try_count == 1) and (res_filev[-4:] == '.mp4'):

                        if (os.path.isfile(res_filev)):
                            os.remove(res_filev)

                        # GPU encoder, intel GPU enable !
                        # ffmpeg -init_hw_device qsv:hw -f gdigrab -i desktop -vcodec h264_qsv -r 10 desktop.mp4
                        res_ffmpeg = subprocess.Popen(['ffmpeg',
                            '-threads', '2',
                            '-init_hw_device', 'qsv:hw',
                            '-f', 'gdigrab', '-i', 'desktop',
                            '-vf', 'scale=1920:-2',
                            '-vcodec', 'h264_qsv',
                            '-r', str(rate),
                            res_filev,
                            '-loglevel', 'warning',
                            #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                            ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

            # デスクトップ Windows ※３回目以降
            if (res_ffmpeg == None) and (dev == 'desktop'):
                if (os.name == 'nt'):
                    if (try_count >= 2):

                        if (res_filev[-4:] != '.flv'):
                            res_filev = res_filev[:-4] + '.flv'

                        if (os.path.isfile(res_filev)):
                            os.remove(res_filev)

                        # ffmpeg -f gdigrab -i desktop -vcodec flv1 -q:v 0 -r 10 desktop.flv
                        res_ffmpeg = subprocess.Popen(['ffmpeg',
                            '-threads', '2',
                            '-f', 'gdigrab', '-i', 'desktop',
                            '-vf', 'scale=1920:-2',
                            '-vcodec', 'flv1',
                            '-q:v', '0',
                            '-r', str(rate),
                            res_filev,
                            '-loglevel', 'warning',
                            #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                            ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

            # カメラ Windows ※初回
            if (res_ffmpeg == None) and (dev != 'desktop'):
                if (os.name == 'nt'):
                    if (try_count == 0) and (res_filev[-4:] == '.mp4'):

                        if (os.path.isfile(res_filev)):
                            os.remove(res_filev)

                        # GPU encoder, intel 6th GPU enable !
                        # ffmpeg -init_hw_device qsv:hw -rtbufsize 1024M -f dshow -i "video=Microsoft Camera Front" -vcodec hevc_qsv -r 10 camera.mp4
                        res_ffmpeg = subprocess.Popen(['ffmpeg',
                            '-threads', '2',
                            '-init_hw_device', 'qsv:hw',
                            '-rtbufsize', '1024M',
                            '-f', 'dshow', '-i', 'video=' + dev,
                            '-vf', 'scale=1920:-2',
                            '-vcodec', 'hevc_qsv',
                            '-r', str(rate),
                            res_filev,
                            '-loglevel', 'warning',
                            #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                            ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

            # カメラ Windows ※２回目
            if (res_ffmpeg == None) and (dev != 'desktop'):
                if (os.name == 'nt'):
                    if (try_count == 1) and (res_filev[-4:] == '.mp4'):

                        if (os.path.isfile(res_filev)):
                            os.remove(res_filev)

                        # GPU encoder, intel GPU enable !
                        # ffmpeg -init_hw_device qsv:hw -rtbufsize 1024M -f dshow -i "video=Microsoft Camera Front" -vcodec h264_qsv -r 10 camera.mp4
                        res_ffmpeg = subprocess.Popen(['ffmpeg',
                            '-threads', '2',
                            '-init_hw_device', 'qsv:hw',
                            '-rtbufsize', '1024M',
                            '-f', 'dshow', '-i', 'video=' + dev,
                            '-vf', 'scale=1920:-2',
                            '-vcodec', 'h264_qsv',
                            '-r', str(rate),
                            res_filev,
                            '-loglevel', 'warning',
                            #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                            ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

            # カメラ Windows ※３回目以降
            if (res_ffmpeg == None) and (dev != 'desktop'):
                if (os.name == 'nt'):
                    if (try_count >= 2):

                        if (res_filev[-4:] != '.flv'):
                            res_filev = res_filev[:-4] + '.flv'

                        if (os.path.isfile(res_filev)):
                            os.remove(res_filev)

                        # ffmpeg -rtbufsize 1024M -f dshow -i "video=Microsoft Camera Front" -vcodec flv1 -q:v 0 -r 10 camera.flv
                        res_ffmpeg = subprocess.Popen(['ffmpeg',
                            '-threads', '2',
                            '-rtbufsize', '1024M',
                            '-f', 'dshow', '-i', 'video=' + dev,
                            '-vf', 'scale=1920:-2',
                            '-vcodec', 'flv1',
                            '-q:v', '0',
                            '-r', str(rate),
                            res_filev,
                            '-loglevel', 'warning',
                            #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                            ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

        # 録音　開始
        if (res_filea != ''):

            if (os.path.isfile(res_filea)):
                os.remove(res_filea)

            res_sox = subprocess.Popen(['sox',
                '-q', '-d', '-r', '16000', '-b', '16', '-c', '1',
                res_filea,
                #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

        # 起動確認
        time.sleep(5.00)

        check = True
        if (res_filev != ''):
            if (not os.path.isfile(res_filev)):
                check = False
            else:
                if (os.path.getsize(res_filev) == 0):
                    check = False
        #if (res_filea != ''):
        #    if (not os.path.isfile(res_filea)):
        #        check = False
        #    else:
        #        if (os.path.getsize(res_filea) == 0):
        #            check = False

        # 起動エラー
        if (check == False):
            if (res_filev != ''):
                if (not res_ffmpeg is None):
                    res_ffmpeg.terminate()
                    res_ffmpeg = None
                    res_filev  = ''
            if (res_filea != ''):
                if (not res_sox is None):
                    res_sox.terminate()
                    res_sox    = None
                    res_filea  = ''

        return res_ffmpeg, res_sox, res_filev, res_filea

    def rec_stop(self, ffmpeg, sox, ):

        # 録画　停止
        if (not ffmpeg is None):
            ffmpeg.stdin.write(b'q\n')
            try:
                ffmpeg.stdin.flush()
            except Exception as e:
                pass

        # 録音　停止・終了
        if (not sox is None):
            #if (os.name != 'nt'):
            #    sox.send_signal(signal.SIGINT)
            #else:
            #    sox.send_signal(signal.CTRL_C_EVENT)
            #time.sleep(2.00)
            sox.terminate()
            sox = None

        # 録画　時限待機・終了
        if (not ffmpeg is None):
            checkTime = time.time()
            while ((time.time() - checkTime) < 5):
                line = ffmpeg.stderr.readline()
                if (not line) and (not ffmpeg.poll() is None):
                    break

            #logb, errb = ffmpeg.communicate()
            ffmpeg.wait()
            ffmpeg.terminate()
            ffmpeg = None

        return True

    def encodemp4mp3(self, inp_filev='temp/_work/recorder.mp4', inp_filea='temp/_work/recorder.wav', rate=10,
                    out_filev='temp/_work/recorder_h265.mp4', out_filea='temp/_work/recorder_mp3.mp3', ):

        mp4ok = False
        mp3ok = False

        # 音声確認
        if (inp_filea != ''):
            if (not os.path.isfile(inp_filea)):
                inp_filea = ''
                out_filea = ''
            else:
                if (os.path.getsize(inp_filea) <= 44):
                    inp_filea = ''
                    out_filea = ''

        # 動画＋音声変換

        # GPU encoder 変換
        if (mp4ok == False) and (os.name == 'nt'):
            if (inp_filev != '') and (inp_filea != '') and (out_filev != ''):

                if (os.path.isfile(out_filev)):
                    os.remove(out_filev)

                # GPU encoder, intel 6th GPU enable !
                ffmpeg = subprocess.Popen(['ffmpeg',
                    '-threads', '2',
                    '-init_hw_device', 'qsv:hw',
                    '-i', inp_filev, '-i', inp_filea,
                    '-vf', 'scale=1920:-1',
                    '-vcodec', 'hevc_qsv', '-r', str(rate),
                    '-acodec', 'libmp3lame', '-strict', 'unofficial', '-ab', '96k', '-ac', '1', '-ar', '16000',
                    #'-acodec', 'aac', '-ab', '96k', '-ac', '1', '-ar', '16000',
                    out_filev,
                    '-loglevel', 'warning',
                    #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                    ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

                #logb, errb = ffmpeg.communicate()
                ffmpeg.wait()
                ffmpeg.terminate()
                ffmpeg = None
                if (os.path.isfile(out_filev)):
                    if (os.path.getsize(out_filev) > 0):
                        mp4ok = True

        # GPU encoder 変換
        if (mp4ok == False) and (os.name == 'nt'):
            if (inp_filev != '') and (inp_filea != '') and (out_filev != ''):

                if (os.path.isfile(out_filev)):
                    os.remove(out_filev)

                # GPU encoder, intel GPU enable !
                ffmpeg = subprocess.Popen(['ffmpeg',
                    '-threads', '2',
                    '-init_hw_device', 'qsv:hw',
                    '-i', inp_filev, '-i', inp_filea,
                    '-vf', 'scale=1920:-1',
                    '-vcodec', 'h264_qsv', '-r', str(rate),
                    '-acodec', 'libmp3lame', '-strict', 'unofficial', '-ab', '96k', '-ac', '1', '-ar', '16000',
                    #'-acodec', 'aac', '-ab', '96k', '-ac', '1', '-ar', '16000',
                    out_filev,
                    '-loglevel', 'warning',
                    #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                    ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

                #logb, errb = ffmpeg.communicate()
                ffmpeg.wait()
                ffmpeg.terminate()
                ffmpeg = None
                if (os.path.isfile(out_filev)):
                    if (os.path.getsize(out_filev) > 0):
                        mp4ok = True

        # software encoder 変換
        if (mp4ok == False):
            if (inp_filev != '') and (inp_filea != '') and (out_filev != ''):

                if (os.path.isfile(out_filev)):
                    os.remove(out_filev)

                # software encoder,
                ffmpeg = subprocess.Popen(['ffmpeg',
                    '-threads', '2',
                    '-i', inp_filev, '-i', inp_filea,
                    '-vcodec', 'libx265', '-r', str(rate),
                    '-acodec', 'libmp3lame', '-strict', 'unofficial', '-ab', '96k', '-ac', '1', '-ar', '16000',
                    #'-acodec', 'aac', '-ab', '96k', '-ac', '1', '-ar', '16000',
                    out_filev,
                    '-loglevel', 'warning',
                    #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                    ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

                #logb, errb = ffmpeg.communicate()
                ffmpeg.wait()
                ffmpeg.terminate()
                ffmpeg = None
                if (os.path.isfile(out_filev)):
                    if (os.path.getsize(out_filev) > 0):
                        mp4ok = True

        # 動画変換

        # GPU encoder 変換
        if (mp4ok == False) and (os.name == 'nt'):
            if (inp_filev != '') and (out_filev != ''):

                if (os.path.isfile(out_filev)):
                    os.remove(out_filev)

                # GPU encoder, intel 6th GPU enable !
                ffmpeg = subprocess.Popen(['ffmpeg',
                    '-threads', '2',
                    '-init_hw_device', 'qsv:hw',
                    '-i', inp_filev,
                    '-vf', 'scale=1920:-1',
                    '-vcodec', 'hevc_qsv', '-r', str(rate),
                    out_filev,
                    '-loglevel', 'warning',
                    #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                    ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

                #logb, errb = ffmpeg.communicate()
                ffmpeg.wait()
                ffmpeg.terminate()
                ffmpeg = None
                if (os.path.isfile(out_filev)):
                    if (os.path.getsize(out_filev) > 0):
                        mp4ok = True

        # GPU encoder 変換
        if (mp4ok == False) and (os.name == 'nt'):
            if (inp_filev != '') and (out_filev != ''):

                if (os.path.isfile(out_filev)):
                    os.remove(out_filev)

                # GPU encoder, intel GPU enable !
                ffmpeg = subprocess.Popen(['ffmpeg',
                    '-threads', '2',
                    '-init_hw_device', 'qsv:hw',
                    '-i', inp_filev,
                    '-vf', 'scale=1920:-1',
                    '-vcodec', 'h264_qsv', '-r', str(rate),
                    out_filev,
                    '-loglevel', 'warning',
                    #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                    ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

                #logb, errb = ffmpeg.communicate()
                ffmpeg.wait()
                ffmpeg.terminate()
                ffmpeg = None
                if (os.path.isfile(out_filev)):
                    if (os.path.getsize(out_filev) > 0):
                        mp4ok = True

        # software encoder 変換
        if (mp4ok == False):
            if (inp_filev != '') and (out_filev != ''):

                if (os.path.isfile(out_filev)):
                    os.remove(out_filev)

                # software encoder,
                ffmpeg = subprocess.Popen(['ffmpeg',
                    '-threads', '2',
                    '-i', inp_filev,
                    '-vcodec', 'libx265', '-r', str(rate),
                    out_filev,
                    '-loglevel', 'warning',
                    #], stdin=subprocess.PIPE, stderr=subprocess.PIPE,)
                    ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )

                #logb, errb = ffmpeg.communicate()
                ffmpeg.wait()
                ffmpeg.terminate()
                ffmpeg = None
                if (os.path.isfile(out_filev)):
                    if (os.path.getsize(out_filev) > 0):
                        mp4ok = True

        # 音声処理
        mp3ok = False
        if (inp_filea != '') and (out_filea != ''):
            sox = subprocess.Popen(['sox', '-q', inp_filea, out_filea,
                ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, )
            sox.wait()
            sox.terminate()
            sox = None
            if (os.path.isfile(out_filea)):
                if (os.path.getsize(out_filea) > 0):
                    mp3ok = True

        # 戻り値
        if   (mp4ok == True) and (mp3ok == True):
            return [out_filev, out_filea]
        elif (mp4ok == True) and (mp3ok == False):
            return [out_filev]
        elif (mp4ok == False) and (mp3ok == True):
            return [out_filea]
        else:
            return False

if __name__ == '__main__':

    qPath_work = 'temp/_work'
    if (not os.path.isdir('temp')):
        os.mkdir('temp')
    if (not os.path.isdir('temp/_work')):
        os.mkdir('temp/_work')

    # ffmpeg 操作
    qFFmpeg = qFFmpeg_class()

    # デバイス名取得
    cam, mic = qFFmpeg.dshow_dev_list()

    print('snapshot test')

    # デスクトップ
    print('desktop')

    work_path = qPath_work + 'capture'
    img       = qFFmpeg.capture(dev='desktop', full=True, work_path=work_path, )
    if (not img is None):
        proc_img = cv2.resize(img, (320, 240))
        cv2.imshow('', proc_img)
        cv2.waitKey(1)
        time.sleep(2.00)

    # カメラ
    print('camera')

    count = 0
    for cam_dev in cam:
        count += 1
        work_path = qPath_work + 'capture' + str(count)
        img       = qFFmpeg.capture(dev=cam_dev, full=True, work_path=work_path, )
        if (not img is None):
            out_image, _ = qFFmpeg.detect_face(inp_image=img, )
            if (not out_image is None):
                proc_img = cv2.resize(out_image, (320, 240))
                cv2.imshow('', proc_img)
                cv2.waitKey(1)
                time.sleep(2.00)

    time.sleep(5.00)
    cv2.destroyAllWindows()

    print('recorder test')

    # デスクトップ
    print('start')

    rec_filev = 'temp/_work/recorder.mp4'
    rec_filea = 'temp/_work/recorder.wav'
    ffmpeg, sox, rec_filev, rec_filea = qFFmpeg.rec_start(dev='desktop', rate=10,
                                                            out_filev=rec_filev, out_filea=rec_filea,
                                                            retry_max=3, retry_wait=5.00,)
    time.sleep(5.00)

    print('stop')

    qFFmpeg.rec_stop(ffmpeg, sox,)
    ffmpeg = None
    sox    = None

    # 動画変換
    print('convert')

    mp4_filev = 'temp/_work/recorder_mp4.mp4'
    mp3_filea = 'temp/_work/recorder_mp3.mp3'
    res = qFFmpeg.encodemp4mp3(inp_filev=rec_filev, inp_filea=rec_filea, rate=10,
                                out_filev=mp4_filev, out_filea=mp3_filea,)


import numpy as np
import torch
import torch.utils.data as data
import cv2
import os
import h5py
import random

import sys
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../"))
sys.path.insert(0, ROOT_DIR)

from utils import train_utils,evaluation_utils

torch.multiprocessing.set_sharing_strategy('file_system')

class Offline_Dataset(data.Dataset):
    def __init__(self,config,mode):
        assert mode=='train' or mode=='valid'

        self.config = config
        self.mode = mode
        metadir=os.path.join(config.dataset_path,'valid') if mode=='valid' else os.path.join(config.dataset_path,'train')

        pair_num_list=np.loadtxt(os.path.join(metadir,'pair_num.txt'),dtype=str)
        self.total_pairs=int(pair_num_list[0,1])
        self.pair_seq_list,self.accu_pair_num=train_utils.parse_pair_seq(pair_num_list)

    def collate_fn(self, batch):
        batch_size, num_pts = len(batch), batch[0]['x1'].shape[0]

        data = {}
        dtype=['x1','x2','kpt1','kpt2','desc1','desc2','num_corr','num_incorr1','num_incorr2','e_gt','pscore1','pscore2','img_path1','img_path2']
        for key in dtype:
            data[key]=[]
        for sample in batch:
            for key in dtype:
                data[key].append(sample[key])

        for key in ['x1', 'x2','kpt1','kpt2', 'desc1', 'desc2','e_gt','pscore1','pscore2']:
            data[key] = torch.from_numpy(np.stack(data[key])).float()
        for key in ['num_corr', 'num_incorr1', 'num_incorr2']:
            data[key] = torch.from_numpy(np.stack(data[key])).int()

        # kpt augmentation with random homography
        if (self.mode == 'train' and self.config.data_aug):
            homo_mat = torch.from_numpy(train_utils.get_rnd_homography(batch_size)).unsqueeze(1)
            aug_seed=random.random()
            if aug_seed<0.5:
                x1_homo = torch.cat([data['x1'], torch.ones([batch_size, num_pts, 1])], dim=-1).unsqueeze(-1)
                x1_homo = torch.matmul(homo_mat.float(), x1_homo.float()).squeeze(-1)
                data['aug_x1'] = x1_homo[:, :, :2] / x1_homo[:, :, 2].unsqueeze(-1)
                data['aug_x2']=data['x2']
            else:
                x2_homo = torch.cat([data['x2'], torch.ones([batch_size, num_pts, 1])], dim=-1).unsqueeze(-1)
                x2_homo = torch.matmul(homo_mat.float(), x2_homo.float()).squeeze(-1)
                data['aug_x2'] = x2_homo[:, :, :2] / x2_homo[:, :, 2].unsqueeze(-1)
                data['aug_x1']=data['x1']
        else:
            data['aug_x1'],data['aug_x2']=data['x1'],data['x2']
        return data

    def __getitem__(self, index):
        seq=self.pair_seq_list[index]
        index_within_seq=index-self.accu_pair_num[seq]

        with h5py.File(os.path.join(self.config.dataset_path,seq,'info.h5py'),'r') as data:
            R,t = data['dR'][str(index_within_seq)][()], data['dt'][str(index_within_seq)][()]
            egt = np.reshape(np.matmul(np.reshape(evaluation_utils.np_skew_symmetric(t.astype('float64').reshape(1, 3)), (3, 3)),np.reshape(R.astype('float64'), (3, 3))), (3, 3))
            egt = egt / np.linalg.norm(egt)
            K1, K2 = data['K1'][str(index_within_seq)][()],data['K2'][str(index_within_seq)][()]
            size1,size2=data['size1'][str(index_within_seq)][()],data['size2'][str(index_within_seq)][()]

            img_path1,img_path2=data['img_path1'][str(index_within_seq)][()][0].decode(),data['img_path2'][str(index_within_seq)][()][0].decode()
            img_name1,img_name2=img_path1.split('/')[-1],img_path2.split('/')[-1]
            img_path1,img_path2=os.path.join(self.config.rawdata_path,img_path1),os.path.join(self.config.rawdata_path,img_path2)
            fea_path1,fea_path2=os.path.join(self.config.desc_path,seq,img_name1+self.config.desc_suffix),\
                                os.path.join(self.config.desc_path,seq,img_name2+self.config.desc_suffix)
            with h5py.File(fea_path1,'r') as fea1, h5py.File(fea_path2,'r') as fea2:
                desc1,kpt1,pscore1=fea1['descriptors'][()],fea1['keypoints'][()][:,:2],fea1['keypoints'][()][:,2]
                desc2,kpt2,pscore2=fea2['descriptors'][()],fea2['keypoints'][()][:,:2],fea2['keypoints'][()][:,2]
                kpt1,kpt2,desc1,desc2=kpt1[:self.config.num_kpt],kpt2[:self.config.num_kpt],desc1[:self.config.num_kpt],desc2[:self.config.num_kpt]

            # normalize kpt
            if self.config.input_normalize=='intrinsic':
                x1, x2 = np.concatenate([kpt1, np.ones([kpt1.shape[0], 1])], axis=-1), np.concatenate(
                    [kpt2, np.ones([kpt2.shape[0], 1])], axis=-1)
                x1, x2 = np.matmul(np.linalg.inv(K1), x1.T).T[:, :2], np.matmul(np.linalg.inv(K2), x2.T).T[:, :2]
            elif self.config.input_normalize=='img' :
                x1,x2=(kpt1-size1/2)/size1,(kpt2-size2/2)/size2
                S1_inv,S2_inv=np.asarray([[size1[0],0,0.5*size1[0]],[0,size1[1],0.5*size1[1]],[0,0,1]]),\
                            np.asarray([[size2[0],0,0.5*size2[0]],[0,size2[1],0.5*size2[1]],[0,0,1]])
                M1,M2=np.matmul(np.linalg.inv(K1),S1_inv),np.matmul(np.linalg.inv(K2),S2_inv)
                egt=np.matmul(np.matmul(M2.transpose(),egt),M1)
                egt = egt / np.linalg.norm(egt)
            else:
                raise NotImplementedError

            corr=data['corr'][str(index_within_seq)][()]
            incorr1,incorr2=data['incorr1'][str(index_within_seq)][()],data['incorr2'][str(index_within_seq)][()]

        #permute kpt
        valid_corr=corr[corr.max(axis=-1)<self.config.num_kpt]
        valid_incorr1,valid_incorr2=incorr1[incorr1<self.config.num_kpt],incorr2[incorr2<self.config.num_kpt]
        num_corr, num_incorr1, num_incorr2 = len(valid_corr), len(valid_incorr1), len(valid_incorr2)
        mask1_invlaid, mask2_invalid = np.ones(x1.shape[0]).astype(bool), np.ones(x2.shape[0]).astype(bool)
        mask1_invlaid[valid_corr[:, 0]] = False
        mask2_invalid[valid_corr[:, 1]] = False
        mask1_invlaid[valid_incorr1] = False
        mask2_invalid[valid_incorr2] = False
        invalid_index1,invalid_index2=np.nonzero(mask1_invlaid)[0],np.nonzero(mask2_invalid)[0]

        #random sample from point w/o valid annotation
        cur_kpt1 = self.config.num_kpt - num_corr - num_incorr1
        cur_kpt2 = self.config.num_kpt - num_corr - num_incorr2

        if (invalid_index1.shape[0] < cur_kpt1):
            sub_idx1 = np.concatenate([np.arange(len(invalid_index1)),np.random.randint(len(invalid_index1),size=cur_kpt1-len(invalid_index1))])
        if (invalid_index1.shape[0] >= cur_kpt1):
            sub_idx1 =np.random.choice(len(invalid_index1), cur_kpt1,replace=False)
        if (invalid_index2.shape[0] < cur_kpt2):
            sub_idx2 = np.concatenate([np.arange(len(invalid_index2)),np.random.randint(len(invalid_index2),size=cur_kpt2-len(invalid_index2))])
        if (invalid_index2.shape[0] >= cur_kpt2):
            sub_idx2 = np.random.choice(len(invalid_index2), cur_kpt2,replace=False)

        per_idx1,per_idx2=np.concatenate([valid_corr[:,0],valid_incorr1,invalid_index1[sub_idx1]]),\
                          np.concatenate([valid_corr[:,1],valid_incorr2,invalid_index2[sub_idx2]])

        pscore1,pscore2=pscore1[per_idx1][:,np.newaxis],pscore2[per_idx2][:,np.newaxis]
        x1,x2=x1[per_idx1][:,:2],x2[per_idx2][:,:2]
        desc1,desc2=desc1[per_idx1],desc2[per_idx2]
        kpt1,kpt2=kpt1[per_idx1],kpt2[per_idx2]

        return {'x1': x1, 'x2': x2, 'kpt1':kpt1,'kpt2':kpt2,'desc1': desc1, 'desc2': desc2, 'num_corr': num_corr, 'num_incorr1': num_incorr1,'num_incorr2': num_incorr2,'e_gt':egt,\
                'pscore1':pscore1,'pscore2':pscore2,'img_path1':img_path1,'img_path2':img_path2}

    def __len__(self):
        return self.total_pairs


import win32con
import win32gui
import win32ui
import win32print
import numpy as np
import cv2

class GetImg(object):

    def __init__(self):
        self.win_title = 'Dota 2'  # 窗口标题
        self.win_class = 'SDL_app'  # 窗口类
        self.dpi = 1  # 初始化缩放大小参数
        self.refresh_dpi()  # 获得当前系统缩放比例
        self.screen = None
        self.rst1 = None
        self.rst2 = None

    def get_rst1(self):
        """获取小技能图区"""
        if self.rst1 is None:
            print("请先运行get_screen()和get_ability_area1()")
        return self.rst1

    def get_rst2(self):
        """获取大招图区"""
        if self.rst2 is None:
            print("请先运行get_screen()和get_ability_area2()")
        return self.rst2

    def refresh_dpi(self):
        """获得当前系统缩放比例"""
        hdc = win32gui.GetDC(0)
        dpi1 = win32print.GetDeviceCaps(hdc, win32con.DESKTOPHORZRES) / win32print.GetDeviceCaps(hdc, win32con.HORZRES)
        dpi2 = win32print.GetDeviceCaps(hdc, win32con.LOGPIXELSX) / 0.96 / 100
        if dpi1 == 1:
            self.dpi = dpi2
        elif dpi2 == 1:
            self.dpi = dpi1
        elif dpi1 == dpi2:
            self.dpi = dpi1
        else:
            self.dpi = -1
            print("请调整系统缩放比例！")
            exit(1)

    def get_dpi(self):
        return self.dpi

    def get_screen(self):
        hwnd = win32gui.FindWindow(self.win_class, self.win_title)  # 获取窗口句柄
        left, top, right, bot = win32gui.GetWindowRect(hwnd)  # 获取句柄窗口的大小信息

        # right = int(right * self.dpi)
        # bot = int(bot * self.dpi)
        width = right - left
        height = bot - top

        hwnd_dc = win32gui.GetWindowDC(hwnd)  # 返回句柄窗口的设备环境，覆盖整个窗口，包括非客户区，标题栏，菜单，边框
        mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)  # 创建设备描述表
        save_dc = mfc_dc.CreateCompatibleDC()  # 创建内存设备描述表
        bit_map = win32ui.CreateBitmap()  # 创建位图对象准备保存图片
        bit_map.CreateCompatibleBitmap(mfc_dc, width, height)  # 为bitmap开辟存储空间
        save_dc.SelectObject(bit_map)  # 将截图保存到bit_map中
        save_dc.BitBlt((0, 0), (width, height), mfc_dc, (0, 0), win32con.SRCCOPY)  # 保存bit_map到内存设备描述表

        signed_ints_array = bit_map.GetBitmapBits(True)
        img = np.fromstring(signed_ints_array, dtype='uint8')
        img.shape = (height, width, 4)
        cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)
        img = img[:, :, :-1]
        img = cv2.resize(img, (1920, 1080), interpolation=cv2.INTER_LINEAR)  # 原图归一化
        self.screen = img
        cv2.cvtColor(img, cv2.COLOR_BGRA2RGB)

        win32gui.DeleteObject(bit_map.GetHandle())
        save_dc.DeleteDC()
        mfc_dc.DeleteDC()
        win32gui.ReleaseDC(hwnd, hwnd_dc)
        return 0

    def save_img(self, path="./cache/screen.jpg", img=None):
        if img is None:
            img = self.screen
        cv2.imwrite(path, img)  # 保存
        return 0

    def show_img(self, img=None):
        if img is None:
            img = self.screen
        cv2.namedWindow('img', cv2.WINDOW_NORMAL)  # 命名窗口
        cv2.imshow("img", img)  # 显示
        cv2.waitKey(0)
        cv2.destroyAllWindows()

    def get_ability_area1(self):
        img = self.screen
        pa = np.array([[722, 341], [1197, 341], [669, 835], [1250, 835]], dtype="float32")  # 小技能框
        pb = np.array([[0, 0], [500, 0], [0, 500], [500, 500]], dtype="float32")  # 参考矩形
        mat = cv2.getPerspectiveTransform(pa, pb)  # 小技能区域变换矩阵
        self.rst1 = cv2.warpPerspective(img, mat, (500, 500))  # 校正后小技能框

    def get_ability_area2(self):
        img = self.screen
        pa = np.array([[672, 142], [1247, 142], [686, 339], [1233, 339]], dtype="float32")  # 大招框
        pb = np.array([[0, 0], [575, 0], [0, 200], [575, 200]], dtype="float32")  # 参考矩形
        mat = cv2.getPerspectiveTransform(pa, pb)  # 大招区域变换矩阵
        self.rst2 = cv2.warpPerspective(img, mat, (575, 200))  # 校正后大招框

        # pa = np.array([[675, 377], [1244, 377], [613, 930], [1305, 930]], dtype="float32")  # 小技能框
        # pa = np.array([[618, 156], [1302, 156], [634, 377], [1286, 377]], dtype="float32")  # 大招框
        # img = cv2.resize(img, (1920, 1200), interpolation=cv2.INTER_LINEAR)  # 原图归一化