# import the necessary packages
from scipy.spatial import distance as dist
from imutils import face_utils
from operator import itemgetter
from numpy import nan

import numpy as np
import argparse
import imutils
import time
import dlib
import cv2
import sys,os
import importlib

from data_funcs import *

def eye_aspect_ratio(eye):
    # compute the euclidean distances between the two sets of
    # vertical eye landmarks (x, y)-coordinates
    A = dist.euclidean(eye[1], eye[5])
    B = dist.euclidean(eye[2], eye[4])

    # compute the euclidean distance between the horizontal
    # eye landmark (x, y)-coordinates
    C = dist.euclidean(eye[0], eye[3])

    # compute the eye aspect ratio
    ear = (A + B) / (2.0 * C)

    # return the eye aspect ratio
    return ear

# returns:
# rect = dlib.rectangle or None if no faces found
# detectType to say which detector the rectangle represents
#
# if detector does not find a face than detector2 will try
def detectFaces(gray,FACE_DOWNSAMPLE_RATIO):
    detectType = 1

    # rescale frame for face detection
    gray_rescale = cv2.resize(gray, (0,0),fx=1.0/FACE_DOWNSAMPLE_RATIO,fy=1.0/FACE_DOWNSAMPLE_RATIO)

    # detect faces in the grayscale frame
    rects = detector(gray_rescale, 0)

    # dets = detector3(gray_rescale,0)

    # rects = dlib.rectangles()
    # rects.extend([d.rect for d in dets])

    # note rect is a dlib rectangle
    if(len(rects) == 0):
        # if no face found, try second detector
        faces = detector2.detectMultiScale(gray_rescale,1.3,5)
        if(len(faces) == 0):
            return (None,None)

        faces = sorted(faces,reverse = True,key=lambda x: x[2] * x[3])
        # use largest face found
        face = faces[0]
        (x,y,w,h) = face
        rect = dlib.rectangle(left=(x+w).item(), bottom=(y+h).item(), right=x.item(), top=y.item())
        detectType = 2
    else:

        # use largest face found
        # use first face found
        rects = sorted(rects,reverse=True,key=lambda x: x.area())
        rect = rects[0]

    return rect, detectType

# flip frame
def horizontal_flip(src):
    return cv2.flip(src,-1)

# tool built by Steven to label the video data
# p to pause, b to label, k to go back, any other button goes forward
# video is file name
def label_video(video,starting_frame = 0,FLIP = False):
    vs = cv2.VideoCapture(vidPath + video)

    num_frames = vs.get(cv2.CAP_PROP_FRAME_COUNT)
    FPS = vs.get(cv2.CAP_PROP_FPS)

    video_label = np.zeros(int(num_frames))
    current_frame = 0
    video_frame = []

    print("num frames = " + str(num_frames))

    if(starting_frame > num_frames):
        return "Starting_frame > num_frames"

    # go to starting frame
    while current_frame != starting_frame:
        cv2.waitKey(40)
        flag,frame = vs.read()
        if flag:
            if(current_frame % int(num_frames/100) == 0):
                print("loading: " + str(current_frame))
            current_frame += 1
        else:
            print("did not grab frame")
            time.sleep(0.5)

    first = True
    while True:
        #print("current frame: " + str(current_frame))
        flag, frame = vs.read()
        if flag:
            frame = cv2.resize(frame,(1280,720))
            if(FLIP):
                frame = horizontal_flip(frame)
            video_frame.append(frame)
            if(len(video_frame) >= 1*int(FPS)):
                video_frame.pop(0)
            cv2.putText(frame, "Frame: {}".format(current_frame), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
            cv2.imshow("Frame", frame)
            key = cv2.waitKey(40)

            # pause
            if first or key == ord('p'):
                first = False
                frame_counter = len(video_frame) - 1
                frame_pause = video_frame[int(frame_counter)]
                while True:
                    key2 = cv2.waitKey(0)
                    # label
                    if key2 == ord('b'):
                        video_label[current_frame - (len(video_frame) - frame_counter)] = 1
                        cv2.putText(frame_pause, "Paused", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
                        cv2.putText(frame_pause, "Labeled: {}".format(current_frame - (len(video_frame) - frame_counter-1)), (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
                        cv2.imshow("Frame", frame_pause)
                    # go back one frame
                    elif key2 == ord('k'):
                        if frame_counter > 0:
                            frame_counter -= 1
                        frame_pause = video_frame[int(frame_counter)]
                        cv2.putText(frame_pause, "Paused", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
                        cv2.imshow("Frame", frame_pause)
                    elif key2 == ord('p'):
                        break
                    else:
                        # any other goes forward a frame
                        if frame_counter < len(video_frame) - 1:
                            frame_counter += 1
                        frame_pause = video_frame[int(frame_counter)]
                        cv2.putText(frame_pause, "Paused", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
                        cv2.imshow("Frame", frame_pause)
            elif key == ord('q'):
                break

            current_frame += 1
        else:
            # vs.set(cv2.CAP_PROP_POS_FRAMES, current_frame-1)
            print("frame is not ready")
            key = cv2.waitKey(40)
            if(key == ord('q')):
                break
            # It is better to wait for a while for the next frame to be ready
            continue

        if current_frame == num_frames or current_frame == num_frames - 1:
            # If the number of captured frames is equal to the total number of frames,
            # we stop
            key2 = cv2.waitKey(0)
            if(key == ord('q')):
                break

    cv2.destroyAllWindows()
    vs.release()
    return video_label

# Generates a csv string of the ears of each frame (nothing if no face detected)
def gen_ears(filename, SHOW_FRAME = True, FLIP = False):
    print("Generating EARS for " + vidPath + filename)

    # How much to downsample face for detection
    FACE_DOWNSAMPLE_RATIO = 4

    # output matrix
    out = []

    # grab the indexes of the facial landmarks for the left and
    # right eye, respectively
    (lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
    (rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]

    # start the video stream thread
    vs = cv2.VideoCapture(vidPath + filename)

    time.sleep(5.0)
    FRAME_NUM = 0
    print("Applying classifer to " + vidPath + filename)

    print("Video properties:")
    FPS = vs.get(cv2.CAP_PROP_FPS)
    FC = vs.get(cv2.CAP_PROP_FRAME_COUNT)
    F = vs.get(cv2.CAP_PROP_FORMAT)
    W = vs.get(cv2.CAP_PROP_FRAME_WIDTH)
    H = vs.get(cv2.CAP_PROP_FRAME_HEIGHT)
    print("\tFPS = " + str(FPS))
    print("\tFrame count = " + str(FC))
    print("\tFormat = " + str(F))
    print("\t(width,height) = (" + str(W) + "," + str(H) + ")")

    # "dropped" frames
    NOT_GRABBED = 0

    # loop over frames from the video stream
    # stop when we haven't grabbed END_VIDEO_LIMIT frames
    while FRAME_NUM < FC:

        # Try to grab frame
        (grabbed,frame) = vs.read()

        if(grabbed):
            FRAME_NUM += 1
            NOT_GRABBED = 0
        else:
            print("not grabbed = " + str(NOT_GRABBED))
            NOT_GRABBED += 1
            continue

        if(FLIP):
            frame = horizontal_flip(frame)

        # output timestamp and frame
        #timestamp = 1000.0 * float(FRAME_NUM)/FPS
        timestamp = FRAME_NUM

        # Apply transformations (one of them being grayscale)
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        rect, detectType = detectFaces(gray, FACE_DOWNSAMPLE_RATIO)

        if(rect is not None):
            # Resize obtained rectangle for full resolution image
            rect_resize = dlib.rectangle(
                left=rect.left() * FACE_DOWNSAMPLE_RATIO,
                top=rect.top() * FACE_DOWNSAMPLE_RATIO,
                right=rect.right() * FACE_DOWNSAMPLE_RATIO,
                bottom=rect.bottom() * FACE_DOWNSAMPLE_RATIO
            )

            shape = predictor(gray,rect_resize)
            shape = face_utils.shape_to_np(shape)
            # extract the left and right eye coordinates, then use the
            # coordinates to compute the eye aspect ratio for both eyes
            leftEye = shape[lStart:lEnd]
            rightEye = shape[rStart:rEnd]

            leftEAR = eye_aspect_ratio(leftEye)
            rightEAR = eye_aspect_ratio(rightEye)

            # average the eye aspect ratio together for both eyes
            ear = (leftEAR + rightEAR) / 2.0

            # compute the convex hull for the left and right eye, then
            # visualize each of the eyes
            leftEyeHull = cv2.convexHull(leftEye)
            rightEyeHull = cv2.convexHull(rightEye)

            cv2.rectangle(frame,(rect_resize.left(),rect_resize.top()),(rect_resize.right(),rect_resize.bottom()),cv2.COLOR_BGR2HSV,10)
            cv2.drawContours(frame, [leftEyeHull], -1, (0, 255, 0), 2)
            cv2.drawContours(frame, [rightEyeHull], -1, (0, 255, 0), 2)

        if(SHOW_FRAME):
            display_frame = cv2.resize(frame,(1280,720))
            # draw the total number of blinks on the frame along with
            # the computed eye aspect ratio for the frame
            # Reset counter if we did not find any faces
            if(rect is None):
                cv2.putText(display_frame, "EAR: N\\A", (300, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
                cv2.putText(display_frame, "NO FACE DETECTED", (300, 60),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
            else:
                cv2.putText(display_frame, "EAR: {:.2f}".format(ear), (300, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

            cv2.putText(display_frame, "Frame: {}".format(FRAME_NUM), (10, 60),
                cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)

            # show the frame
            cv2.imshow("Frame", display_frame)

        # output different csv things if face was detected
        if(rect is not None):
            out.append([int(timestamp),ear])
        else:
            out.append([int(timestamp),nan])

        if cv2.waitKey(1) & 0xFF == ord('q'):
            cv2.destroyAllWindows()
            return out
            break

    # do a bit of cleanup
    cv2.destroyAllWindows()

    return np.asarray(out)

# ---------------------------------------------------------------------------

WRITE_TO_CSV = True

print("[INFO] loading facial classifiers...")
# dlibs HOG based classifier
detector = dlib.get_frontal_face_detector()
# openCVs HAAR cascade classifier
detector2 = cv2.CascadeClassifier(detector2Path)
# dlibs CNN based classifier
# currently too slow even with rescaling
detector3 = dlib.cnn_face_detection_model_v1(detector3Path)
# dlibs facial landmark detector (68 points)
predictor = dlib.shape_predictor(shapePredPath)

# get files
files = []
for filename in os.listdir(vidPath):
    print(filename)
    if(filename.endswith(".MP4") or filename.endswith(".mp4")):
        files.append(filename)

# =============================================================================
# # gen ears
# file = files[0]
# out = gen_ears(file)
# out = np.asarray(out)
# print("saving ears")
# vidName, ext = os.path.splitext(os.path.basename(file))
# np.savetxt(csvPath + vidName + "_ears.csv", out, delimiter=",")
# =============================================================================

# # gen ears
# for file in files:
    # vidName, ext = os.path.splitext(os.path.basename(file))
    # out = gen_ears(file,SHOW_FRAME = True)
    # if(WRITE_TO_CSV):
        # print("Writing " + str(vidName) + "_ears.csv")
        # #np.savetxt(csvPath + vidName + "_ears.csv", out, delimiter=",")

    # print("-----")

# Label video
file = files[0]
out = label_video(file,starting_frame=70000)
print(out.shape)
vidName, ext = os.path.splitext(os.path.basename(file))
print("saving label file")
np.savetxt(csvPath + vidName + "_labels.csv", out, delimiter=",")

# Label video
# # for file in files:
    # # vidName, ext = os.path.splitext(os.path.basename(filename))
    # # print("labeling " + vidPath + filename)
    # # video_label = label_video(file)
    # # if(WRITE_TO_CSV):
        # # np.savetxt(csvPath + vidName + "_labels.csv", video_label, delimiter=",")

print("Done!")


import boto3
from boto3.dynamodb.conditions import Key, Attr
import argparse, random
from uuid import uuid4

import boto3
ddb = boto3.resource('dynamodb')

def batch_write(table_name, num):
    table = ddb.Table(table_name)
    with table.batch_writer() as batch:
        for i in range(num):
            batch.put_item(
                Item={
                    'item_id': str(uuid4()),
                    'first_name': 'John',
                    'last_name': 'Doe',
                    'age': random.randint(1,50),
                }
            )
    print(f"Finished writing {num} items to the table")

def clear_database(table_name):
    table = ddb.Table(table_name)
    scan = None

    with table.batch_writer() as batch:
        count = 0
        while scan is None or 'LastEvaluatedKey' in scan:
            if scan is not None and 'LastEvaluatedKey' in scan:
                scan = table.scan(
                    ExclusiveStartKey=scan['LastEvaluatedKey'],
                )
            else:
                scan = table.scan()

            for item in scan['Items']:
                if count % 5000 == 0:
                    print(count)
                batch.delete_item(Key={'item_id': item['item_id']})
                count = count + 1
    print("Deleted all elements in the database.")

def search_under_age(table_name, age):
    table = ddb.Table(table_name)
    response = table.scan(
        FilterExpression=Attr('age').lt(age)
    )
    print(response.get("Items"))

if __name__ == "__main__":
    # parse arguments
    parser = argparse.ArgumentParser()
    parser.add_argument("table_name", type=str)
    subparsers = parser.add_subparsers(dest="command")

    sp1 = subparsers.add_parser("write")
    sp1.add_argument("num", type=int)

    sp2 = subparsers.add_parser("clear")

    sp3 = subparsers.add_parser("search_under_age")
    sp3.add_argument("age", type=int)

    args = parser.parse_args()

    if args.command == "write":
        batch_write(args.table_name, args.num)
    elif args.command == "clear":
        clear_database(args.table_name)
    elif args.command == "search_under_age":
        search_under_age(args.table_name, args.age)


import requests
from bs4 import BeautifulSoup
import execjs

url = 'https://match.yuanrenxue.com/match/1'

session = requests.session()

# headers = {
#     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36"
# }

headers = {
    "User-Agent": "yuanrenxue.project"
}

h = session.get(url,headers=headers,verify=False)
print(h)
print(session.cookies.get_dict())
session.cookies.set('sessionid','zt5jid1j4lkip4tz4cotm6fycn628je4')

with open('111.js', 'r+',errors='ignore')as f:
    result = f.read()

ctx = execjs.compile(result, cwd=r'C:\Users\Administrator\AppData\Roaming\npm\node_modules')

nums = 0
a = 0
for i in range(1,6):
    times = str(execjs.eval("Date['\x70\x61\x72\x73\x65'](new Date()) + (16798545 + -72936737 + 156138192)"))
    sion = ctx.call('oo0O0',str(times))
    print(sion)
    print(sion + '丨'+str(times)[:-3])
    url = 'https://match.yuanrenxue.com/api/match/1?page={}&m={}'.format(i,sion + '丨'+str(times)[:-3])
    h = session.get(url,headers=headers,verify=False)
    print(h.json())
    for resu in h.json()['data']:
        nums += list(resu.values())[0]
        a += 1
    print(nums)
print(nums/a)


import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision.models as models
from project.models import sew_resnet
from spikingjelly.clock_driven import neuron, functional, surrogate, layer
from einops import rearrange

def get_encoder(in_channels: int) -> nn.Module:
    resnet18 = models.resnet18(progress=True)

    resnet18.fc = nn.Identity()

    if in_channels != 3:
        resnet18.conv1 = nn.Conv2d(
            in_channels,
            64,
            kernel_size=(7, 7),
            stride=(2, 2),
            padding=(3, 3),
            bias=False,
        )

    return resnet18

def get_encoder_3d(in_channels: int) -> nn.Module:
    # resnet18 = models.video.r3d_18()
    resnet18 = models.video.mc3_18()
    resnet18.fc = nn.Identity()
    resnet18.stem[0] = nn.Conv3d(
        in_channels=in_channels,
        out_channels=64,
        kernel_size=(3, 7, 7),
        stride=(1, 2, 2),
        padding=(1, 3, 3),
        bias=False,
    )
    # resnet18 = models.video.r2plus1d_18()
    # resnet18.fc = nn.Identity()
    # resnet18.stem[0] = nn.Conv3d(in_channels=in_channels, out_channels=45, kernel_size=(1,7,7), stride=(1,2,2), padding=(0,3,3), bias=False)
    return resnet18

def get_projector(in_channels: int = 512):
    projector = nn.Sequential(
        nn.Linear(in_channels, 3 * in_channels),
        nn.BatchNorm1d(3 * in_channels),
        nn.ReLU(),
        nn.Linear(3 * in_channels, 3 * in_channels),
        nn.BatchNorm1d(3 * in_channels),
        nn.ReLU(),
        nn.Linear(3 * in_channels, 3 * in_channels),
    )

    return projector

def get_encoder_snn(in_channels: int, T: int):
    resnet18 = sew_resnet.MultiStepSEWResNet(
        block=sew_resnet.MultiStepBasicBlock,
        layers=[2, 2, 2, 2],
        zero_init_residual=True,
        T=T,
        cnf="ADD",
        multi_step_neuron=neuron.MultiStepIFNode,
        detach_reset=True,
        surrogate_function=surrogate.ATan(),
    )

    if in_channels != 3:
        resnet18.conv1 = nn.Conv2d(
            in_channels,
            64,
            kernel_size=(7, 7),
            stride=(2, 2),
            padding=(3, 3),
            bias=False,
        )

    return resnet18

class ConvEnc(nn.Module):
    def __init__(self, in_channels: int, timesteps: int, mode="snn"):
        super(ConvEnc, self).__init__()
        if mode not in ("3dcnn", "snn", "cnn"):
            raise ValueError(
                f"Mode must be one of these values: ('3dcnn', 'snn', 'cnn'). Got:{mode}"
            )

        if mode == "3dcnn":
            self.encoder = get_encoder_3d(in_channels=in_channels)
        elif mode == "snn":
            self.encoder = get_encoder_snn(in_channels=in_channels, T=timesteps)
        else:  # cnn
            self.encoder = get_encoder(in_channels=in_channels * timesteps)

        self.mode = mode
        self.timesteps = timesteps

    def forward(self, x):
        # x = B,T,C,H,W

        if self.mode == "snn":
            functional.reset_net(self.encoder)
            x = x.permute(1, 0, 2, 3, 4)  # from (B,T,C,H,W) to (T, B, C, H, W)
        elif self.mode == "3dcnn":
            x = x.permute(0, 2, 1, 3, 4)  # from (T,B,C,H,W) to (B,C,T,H,W)
        elif self.mode == "cnn" and len(x.shape) == 5:
            x = rearrange(
                x,
                "batch time channel height width -> batch (time channel) height width",
            )

        x = self.encoder(x)
        return x


import os
import sys
from logging.handlers import SysLogHandler, RotatingFileHandler
import logging

# Logging Levels
# https://docs.python.org/3/library/logging.html#logging-levels
# CRITICAL  50
# ERROR 40
# WARNING   30
# INFO  20
# DEBUG 10
# NOTSET    0

loggers = {}

def set_up_logging(name):
    global loggers

    if loggers.get(name):
        return loggers.get(name)
    else:
        if not 'logs' in os.getcwd():
            log_path = os.getcwd() + '/logs/falcon_pedidos.log'
        else:
            log_path = os.getcwd() + '/falcon_pedidos.log'

        with open(log_path, 'a+'):
            pass

        logger = logging.getLogger(name)
        logger.setLevel(logging.INFO)
        handler = RotatingFileHandler(log_path,maxBytes=5242880,backupCount=5)
        #handler = logging.StreamHandler()
        handler.setLevel(logging.INFO)
        formatter = logging.Formatter('[%(asctime)s] [%(name)s] [%(process)d]:[%(thread)d] [%(levelname)s]: %(message)s]')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers[name] = logger

        return logger


import torch
import os
import json
import math
from tqdm import tqdm
from torch.utils.data import DataLoader
from dataloader.Distribution import MNIST, MnistGenerator
from torch.utils import tensorboard
from architectures.simple_fc import SimpleFC
from utils import metrics, utilities, spline_utils
import matplotlib.pyplot as plt

class TrainerWasserstein:
    """
    """
    def __init__(self, config, device):
        self.config = config
        self.device = device

        # Prepare dataset classes
        train_dataset = MNIST(config['training_options']['train_dataset_file'])
        self.mnist_generator = MnistGenerator(device=self.device)

        print('Preparing the dataloaders')
        # Prepare dataloaders
        self.train_dataloader = DataLoader(train_dataset, batch_size=config["training_options"]["batch_size"], shuffle=True, num_workers=config["training_options"]["num_workers"], drop_last=True)
        self.batch_size = config["training_options"]["batch_size"]
        self.val_dataset, _ = torch.load(config['training_options']['val_dataset_file'])
        self.val_dataset = self.val_dataset.unsqueeze(1)

        print('Building the model')
        # Build the model

        self.model = SimpleFC(config['net_params'], **config['activation_fn_params'])
        self.model = self.model.to(device)

        print("Number of parameters in the model: ", self.model.get_num_params())

        print(self.model)

        # Set up the optimizer
        self.set_optimization()
        self.batch_size = config['training_options']['batch_size']
        self.epochs = config["training_options"]['epochs']

        # CHECKPOINTS & TENSOBOARD
        run_name = config['exp_name']
        self.checkpoint_dir = os.path.join(config['log_dir'], config["exp_name"], 'checkpoints')
        if not os.path.exists(self.checkpoint_dir):
            os.makedirs(self.checkpoint_dir)
        config_save_path = os.path.join(config['log_dir'], config["exp_name"], 'config.json')
        with open(config_save_path, 'w') as handle:
            json.dump(self.config, handle, indent=4, sort_keys=True)

        writer_dir = os.path.join(config['log_dir'], config["exp_name"], 'tensorboard_logs')
        self.writer = tensorboard.SummaryWriter(writer_dir)

        self.total_training_step = 0

    def set_optimization(self):
        """Initialize the optmizer"""

        params_list = [{'params': spline_utils.get_no_spline_coefficients(self.model), 'lr': self.config["optimizer"]["lr_weights"]}]
        if self.model.using_splines:
            params_list.append({'params': spline_utils.get_spline_coefficients(self.model), \
                                'lr': self.config["optimizer"]["lr_spline_coeffs"]})

            params_list.append({'params': spline_utils.get_spline_scaling_coeffs(self.model), \
                                'lr': self.config["optimizer"]["lr_spline_scaling_coeffs"]})

        self.optimizer = torch.optim.Adam(params_list)

    def train(self):

        best_val = 0.0
        self.model.train()
        for epoch in range(self.epochs+1):
            self.train_epoch(epoch)
            if epoch % 5 == 0:
                val_result = self.valid_epoch(epoch)

            # SAVE CHECKPOINT
            if val_result > best_val and epoch % 5 == 0:
                best_val = val_result
                self.save_checkpoint(epoch)

        self.writer.flush()
        self.writer.close()

    def train_epoch(self, epoch):
        """
        """
        self.model.train()

        log = {}
        tbar = tqdm(self.train_dataloader, ncols=135, position=0, leave=True)
        for batch_idx, data in enumerate(tbar):
            data_p1 = data[0].to(self.device).view(-1, 784) / 255
            data_p2 = self.mnist_generator(self.batch_size).view(-1, 784)
            self.optimizer.zero_grad()

            wassserstein_loss = -1 * (torch.mean(self.model(data_p1)) - torch.mean(self.model(data_p2)))

            # regularization
            regularization = torch.zeros_like(wassserstein_loss)
            if self.model.using_splines and self.config['activation_fn_params']['lmbda'] > 0:
                regularization = self.config['activation_fn_params']['lmbda'] * self.model.TV2()

            total_loss = wassserstein_loss + regularization
            total_loss.backward()
            self.optimizer.step()

            log['train_loss'] = total_loss.detach().cpu().item()

            if self.model.using_splines:
                spline_scaling_coeffs = torch.nn.utils.parameters_to_vector(spline_utils.get_spline_scaling_coeffs(self.model))
                log['spline_scaling_coeff_mean'] = torch.mean(spline_scaling_coeffs).cpu().item()
                log['spline_scaling_coeff_std'] = torch.std(spline_scaling_coeffs).cpu().item()

                # the step actually represents the amount of data that has been in the model
            self.wrt_step = self.total_training_step * self.batch_size
            self.write_scalars_tb(log)

            tbar.set_description('T ({}) | TotalLoss {:.5f} |'.format(epoch, log['train_loss']))
            self.total_training_step += 1

        return log

    def valid_epoch(self, epoch):

        self.model.eval()

        with torch.no_grad():
            data_p1 = self.val_dataset.to(self.device).view(-1, 784) / 255
            data_p2 = self.mnist_generator(6000).view(-1, 784)

            mean_wasserstein_distance = (torch.mean(self.model(data_p1)) - torch.mean(self.model(data_p2)))

            # METRICS TO TENSORBOARD
            self.wrt_mode = 'val'
            self.writer.add_scalar(f'{self.wrt_mode}/Mean Wasserstein Distance', mean_wasserstein_distance, epoch)

            # Log parameters
            if epoch == self.epochs and self.model.using_splines:
                j = 1
                for module in self.model.modules_linearspline:
                    x = module.grid_tensor
                    y = module.lipschitz_coefficients
                    figures_list = []
                    for kk in range(x.shape[0]):
                        if kk % 16 == 0:
                            fig, ax = plt.subplots()
                            ax.grid()
                        ax.plot(x[kk,:].cpu().numpy(), y[kk,:].cpu().numpy())
                        if kk % 16 == 0:
                            figures_list.append(fig)
                            plt.close()
                    self.writer.add_figure(' Activation functions layer ' + str(j), figures_list, global_step=epoch)
                    j += 1

        return mean_wasserstein_distance

    def write_scalars_tb(self, logs):
        for k, v in logs.items():
            self.writer.add_scalar(f'train/{k}', v, self.wrt_step)

    def save_checkpoint(self, epoch):
        state = {
            'epoch': epoch,
            'state_dict': self.model.state_dict(),
            'optimizer': self.optimizer.state_dict(),
            'config': self.config
        }

        print('Saving a checkpoint:')
        filename = self.checkpoint_dir + '/checkpoint_best_epoch.pth'
        torch.save(state, filename)


import logging
import os
import subprocess
from subprocess import PIPE
from time import sleep

import telegram
from telegram.error import NetworkError, Unauthorized

from bot.long_tasks import full_submission_cycle_test

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

logger = logging.getLogger()
logger.setLevel(logging.INFO)

nick_chat_id = 138018444

authorized_chats = [138018444, 126848884, -1001163348450]

BOT_AUTH_TOKEN = os.getenv('BOT_AUTH_TOKEN')
if not BOT_AUTH_TOKEN:
    raise ValueError('No valid BOT_AUTH_TOKEN supplied')

def main():
    """Run the bot."""
    global update_id
    # Telegram Bot Authorization Token
    bot = telegram.Bot(BOT_AUTH_TOKEN)

    # get the first pending update_id, this is so we can skip over it in case
    # we get an "Unauthorized" exception.
    try:
        update_id = bot.get_updates()[0].update_id
    except IndexError:
        update_id = None

    logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

    while True:
        try:
            echo(bot)
        except NetworkError:
            sleep(1)
        except Unauthorized:
            # The user has removed or blocked the bot.
            update_id += 1

unauthorized_actions = ['/submit_queue', '/is_all_ok']

def get_submit_queue(*_, **__) -> str:
    cp = subprocess.run(['bash', '-c', 'redis-cli -n 1 --eval redis-submits-queue-count.lua 0'],
                        stdout=PIPE, stderr=PIPE)
    return cp.stdout.decode('utf-8')

def full_submission_cycle(_, chat_id):
    full_submission_cycle_test.delay(chat_id)
    return 'Начинаем тестирование...'

actions_map = {
    '/submit_queue': get_submit_queue,
    '/start_full_submission_cycle': full_submission_cycle
}

def make_action(text: str, chat_id: int) -> str:
    try:
        action = actions_map[text]
    except KeyError:
        return 'Я вас не понял'
    try:
        return action(text, chat_id)
    except Exception as e:
        logger.exception(e)
        if chat_id == nick_chat_id:
            return f'Exception\n{str(e)}'
        return 'Oops! An error wa happened. We already trying to solve the problem!'

def handle_command(msg):
    text = msg.text
    chat_id = msg.chat['id']
    text = text.split('@')[0]
    if text in unauthorized_actions:
        resp = make_action(text, chat_id)
        msg.reply_text(resp)
    else:
        if chat_id in authorized_chats:
            resp = make_action(text, chat_id)
            msg.reply_text(resp)
        else:
            msg.reply_text('Я вас не понял')

def echo(bot):
    """Echo the message the user sent."""
    global update_id
    # Request updates after the last update_id
    for update in bot.get_updates(offset=update_id, timeout=10):
        update_id = update.update_id + 1

        if update.message:
            print(update.message.chat)
            handle_command(update.message)

if __name__ == '__main__':
    main()


import numpy as np
from transformers import AutoModel, AutoTokenizer, XLMRobertaTokenizer
import torch

def to_cuda(inputs, is_tensor=True):
    for e in inputs:
        if not is_tensor:
            inputs[e] = torch.tensor(inputs[e])
        inputs[e] = inputs[e].cuda()
    return inputs

class wrapper():
    def __init__(self, args):
        self.model = AutoModel.from_pretrained(args.model_name_or_path)
        self.model = self.model.cuda()
        if 'roberta' in args.model_name_or_path:
            self.tokenizer = XLMRobertaTokenizer.from_pretrained(args.model_name_or_path)
        else:
            self.tokenizer = AutoTokenizer.from_pretrained(args.model_name_or_path)
        self.args = args

    def _encode_batch(self, batch):
        args = self.args
        with torch.no_grad():
            outputs = self.model(**batch, output_hidden_states=True, return_dict=True)
            last_hidden = outputs.last_hidden_state
            pooler_output = outputs.pooler_output
            hidden_states = outputs.hidden_states
        if args.pooler == 'cls':
            # There is a linear+activation layer after CLS representation
            return pooler_output.cpu()
        elif args.pooler == 'cls_before_pooler':
            return last_hidden[:, 0].cpu()
        elif args.pooler == "avg":
            return ((last_hidden * batch['attention_mask'].unsqueeze(-1)).sum(1) / batch['attention_mask'].sum(-1).unsqueeze(-1)).cpu()
        elif args.pooler == "avg_first_last":
            first_hidden = hidden_states[0]
            last_hidden = hidden_states[-1]
            pooled_result = ((first_hidden + last_hidden) / 2.0 * batch['attention_mask'].unsqueeze(-1)).sum(1) / batch['attention_mask'].sum(-1).unsqueeze(-1)
            return pooled_result.cpu()
        elif args.pooler == "avg_top2":
            second_last_hidden = hidden_states[-2]
            last_hidden = hidden_states[-1]
            pooled_result = ((last_hidden + second_last_hidden) / 2.0 * batch['attention_mask'].unsqueeze(-1)).sum(1) / batch['attention_mask'].sum(-1).unsqueeze(-1)
            return pooled_result.cpu()

    def encode_texts(self, texts, batch_size=128, max_length=32):
        self.model.eval()
        with torch.no_grad():
            embeddings = []
            text_ids = []
            for i in range(len(texts)//batch_size + 1):
                s = i*batch_size
                e = (i+1)*batch_size
                if s >= len(texts):
                    break
                inputs = self.tokenizer(texts[s:e], padding=True, truncation=True, return_tensors="pt", max_length=max_length)
                inputs = to_cuda(inputs)
                outputs = self._encode_batch(inputs)
                for emb in outputs:
                    embeddings.append(emb)
                for one_id in inputs['input_ids']:
                    text_ids.append(one_id)
            return torch.stack(embeddings,dim=0)


from mtcnn import MTCNN
import cv2

def main():
    img = cv2.imread(".\\images\\war.jpg")
    #img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    detector = MTCNN()
    bound_boxes = detector.detect_faces(img)
    for box in bound_boxes:
        img = bound_box_draw(box, img)
    cv2.imshow("Image", img)
    cv2.waitKey(0)

def bound_box_draw(bb, img):
    #face
    red = (0, 0, 255)
    green = (0, 255, 0)
    blue = (255, 0, 0)
    x, y, w, h = bb['box']

    #dynamic thickness
    thickness = int(w/30)
    if(thickness<=0):
        thickness = 1

    img = cv2.rectangle(img, (x, y+h), (x+w, y), red, thickness=thickness)
    img = cv2.putText(img, "face", (x,y), cv2.FONT_HERSHEY_PLAIN, thickness, green, thickness=thickness)
    #other face parts
    attrs = bb['keypoints']
    img = cv2.circle(img, attrs['left_eye'], thickness, blue, thickness=-1)
    img = cv2.circle(img, attrs['right_eye'], thickness, blue, thickness=-1)
    img = cv2.circle(img, attrs['nose'], thickness, blue, thickness=-1)
    img = cv2.circle(img, attrs['mouth_left'], thickness, blue, thickness=-1)
    img = cv2.circle(img, attrs['mouth_right'], thickness, blue, thickness=-1)
    return img

if __name__ == "__main__":
    main()


# -*- coding: utf-8 -*-
"""Module defines a fixture for :class:`~ms_graph_exporter.ms_graph.api.MsGraph`."""
from os import environ

import pytest

from ms_graph_exporter.ms_graph.api import MsGraph

@pytest.fixture(scope="session")
def graph_real(logger):
    """Provide MsGraph instance with access to actual MS Graph API."""
    try:
        _graph = MsGraph(
            client_id=environ.get("GRAPH_CLIENT_ID", "undefined"),
            client_secret=environ.get("GRAPH_CLIENT_SECRET", "undefined"),
            tenant=environ.get("GRAPH_TENANT", "undefined"),
        )

    except Exception:
        logger.exception("[MsGraph]: Fixture not properly instantiated")
        yield None

    else:
        logger.info("%s: Provide 'graph_real' fixture", _graph)
        yield _graph

    finally:
        logger.info("%s: Destroy 'graph_real' fixture", _graph)


import json
import os
import tqdm

lengths_data_path = "/home/alanes/wimbd/lengths_data/"

for filename in os.listdir(lengths_data_path):
    toks_data = dict()
    chars_data = dict()
    dataset = filename.split(".")[0]

    print(f'analyzing {dataset}')

    with open(os.path.join(lengths_data_path, filename)) as infile:
        for line in tqdm.tqdm(infile.readlines()):
            data = json.loads(line)
            key = data['key']
            count = int(data['count'])

            if key == 'text is None':
                continue

            count_type, value = key.split(' ')
            length = int(value)
            if count_type.lower() == 'tokens':
                chars_data[length] = count
            elif count_type.lower() == 'chars':
                toks_data[length] = count

    count_sum = sum(toks_data.values())
    dist = {length: count / count_sum for length, count in toks_data.items()}
    print(f'Found {count_sum} total token datapoints; values sum to {sum(dist.values())}')
    with open(os.path.join("/home/alanes/wimbd/lengths_tok_summary/", f'toks_{dataset}.json'), 'w') as ofile:
        json.dump(dist, ofile)

    count_sum = sum(chars_data.values())
    dist = {length: count / count_sum for length, count in chars_data.items()}
    print(f'Found {count_sum} total char datapoints; values sum to {sum(dist.values())}')
    with open(os.path.join("/home/alanes/wimbd/lengths_char_summary/", f'chars_{dataset}.json'), 'w') as ofile:
        json.dump(dist, ofile)


# https://github.com/RsaCtfTool/RsaCtfTool
# ./RsaCtfTool.py --createpub -n n -e e > key.pub
# ./RsaCtfTool.py --publickey key.pub --private --dump

import gmpy2

def small_m_large_n(n, e, c):
    gmpy2.get_context().precision = gmpy2.bit_length(c)
    m = int(gmpy2.root(c, e))
    assert pow(m, e, n) == c
    return m

if __name__ == '__main__':
    from Crypto.Util.number import getPrime, getRandomNBitInteger

    _BITS = 256

    _n = getPrime(_BITS) * getPrime(_BITS)
    _m = getRandomNBitInteger(8)
    _e = 3
    _c = pow(_m, _e, _n)
    assert _m == small_m_large_n(_n, _e, _c)


import threading
import time
import uuid
from typing import Any, Callable

from danswer.configs.constants import DocumentSource
from danswer.connectors.factory import instantiate_connector
from danswer.connectors.interfaces import LoadConnector, PollConnector, EventConnector
from danswer.connectors.models import InputType, Document
from tinyfish.imp.models import ConnectorTaskTfCallbackType, ConnectorTaskTfCredentialsCallback, \
    ConnectorTaskTfDocumentCallback, ConnectorTaskTfFinishedCallback

def _merge_split_docs(doc: Document) -> str:
    s = ''
    for section in doc.sections:
        s += section.text
    return s

class TinyFishSyncThread(threading.Thread):
    def __init__(self,
                 source: str,
                 input_type: str,
                 credentials: dict[str, Any],
                 config: dict[str, Any],
                 last_run_time: float,
                 event_callback: Callable[[ConnectorTaskTfCallbackType, Any], None]):
        super().__init__()

        self.source = source
        self.input_type = input_type
        self.credentials = credentials
        self.config = config
        self.last_run_time = last_run_time
        self.event_callback = event_callback
        self.run_time = time.time()
        self.sync_id = str(uuid.uuid4())
        self.cancelled = False

    def stop(self):
        self.cancelled = True

    def run(self):
        err = None
        try:
            self.work()
        except Exception as e:
            print(e)
            err = str(e)

        if self.cancelled:
            err = "Cancelled"

        self.on_sync_done(err)

    def work(self):
        connector, new_credentials = instantiate_connector(
            source=DocumentSource(self.source),
            input_type=InputType(self.input_type),
            connector_specific_config=self.config,
            credentials=self.credentials)
        new_credentials = connector.load_credentials(new_credentials or self.credentials)
        if new_credentials is not None and new_credentials != self.credentials:
            self.on_credential_changed(new_credentials)

        if self.input_type == InputType.LOAD_STATE:
            assert isinstance(connector, LoadConnector)
            generator = connector.load_from_state()
        elif self.input_type == InputType.POLL:
            assert isinstance(connector, PollConnector)
            generator = connector.poll_source(0, 0)
        elif self.input_type == InputType.EVENT:
            assert isinstance(connector, EventConnector)
            generator = connector.handle_event(0)
        else:
            raise ValueError(f"Invalid input_type={self.input_type}")

        for batch_docs in generator:
            if self.cancelled:
                break

            for doc in batch_docs:
                if self.cancelled:
                    break
                self.on_doc_fetched(doc)

            if self.cancelled:
                break

    def on_credential_changed(self, new_credentials: dict[str, Any]):
        event_type = ConnectorTaskTfCallbackType.CREDENTIAL_CHANGED
        data = ConnectorTaskTfCredentialsCallback(
            sync_id=self.sync_id,
            credentials=new_credentials,
        )
        self.event_callback(event_type, data)

    def on_doc_fetched(self, doc: Document):
        event_type = ConnectorTaskTfCallbackType.DOCUMENT
        data = ConnectorTaskTfDocumentCallback(
            sync_id=self.sync_id,
            meta=doc.metadata,
            data=_merge_split_docs(doc),
        )
        self.event_callback(event_type, data)

    def on_sync_done(self, error: str | None):
        event_type = ConnectorTaskTfCallbackType.FINISHED
        data = ConnectorTaskTfFinishedCallback(
            sync_id=self.sync_id,
            success=error is None,
            error=error,
            run_time=self.run_time,
        )

        print("on_sync_done", data)
        self.event_callback(event_type, data)


#!/usr/bin/env python
import argparse
import glob
import itertools
import logging
import multiprocessing as mp
import os
import sys
from gzip import open as gzopen
from typing import Dict, List, Optional, Tuple

import pandas as pd
from Bio.SeqIO.QualityIO import FastqGeneralIterator

config: Dict[str, int] = {
    "LEFT_ADAPTER": 3,
    "RIGHT_ADAPTER": 2,
    "POOL_BARCODE": 4,
    "CELL_BARCODE": 7,
    "UMI": 8,
    "DEFAULT_SAMPLE_NAME": "GGTTTACT",  # arbitrary sequence
}

def check_folder(folder: str, mandatory: Optional[bool] = False):
    """Checks if folder exists, otherwise create it.

    Args:
        folder (str): Output folder
    """
    if not os.path.exists(folder):
        if mandatory:
            logging.error(f"Folder {folder} not found!")
            sys.exit(-1)
        else:
            logging.warning(f"Missing folder {folder}, creating ...")
            os.mkdir(folder)

def trim_cdna(r1: List[str]) -> Tuple[str, str]:
    """Trim cdna by removing adapters and Pool barcode.

    Args:
        r1 (List[str]): full cdna sequence

    Returns:
        Tuple[str, str]: trimmed cdna and quality
    """
    global config

    # [0]: header, [1]: seq, [2]: quality
    _, seq, qa = r1
    cdna: str = seq[(config["LEFT_ADAPTER"] + config["POOL_BARCODE"]) : -config["RIGHT_ADAPTER"]]
    cdna_quality: str = qa[(config["LEFT_ADAPTER"] + config["POOL_BARCODE"]) : -config["RIGHT_ADAPTER"]]

    return cdna, cdna_quality

def create_r2(r1: FastqGeneralIterator, r2: FastqGeneralIterator) -> Tuple[str, str]:
    """Create R2 consisting of
        - pool barcode (4bp)
        - cell barcode (7bp)
        - UMI          (8bp)

    Args:
        r1 ([FastqGeneralIterator]): R1 read
        r2 ([FastqGeneralIterator]): R2 read

    Returns:
        Tuple[str, str]: New barcode sequence and quality
    """
    global config

    _, r1_seq, r1_qa = r1
    _, r2_seq, r2_qa = r2

    # get pool barcode
    pb_seq: str = r1_seq[config["LEFT_ADAPTER"] : config["LEFT_ADAPTER"] + config["POOL_BARCODE"]]
    pb_qa: str = r1_qa[config["LEFT_ADAPTER"] : config["LEFT_ADAPTER"] + config["POOL_BARCODE"]]

    barcode_len: int = config["POOL_BARCODE"] + config["CELL_BARCODE"] + config["UMI"]
    barcode_seq: str = f"{pb_seq}{r2_seq}"[:barcode_len]
    barcode_qa: str = f"{pb_qa}{r2_qa}"[:barcode_len]

    return barcode_seq, barcode_qa

def convert_to_10x(params: Tuple[str, str, str, str], chunk_size: int = 100) -> None:
    """Main function processing the reads.

    Args:
        params (Tuple[str, str, str, str]): R1, R2, input, output folder
    """
    fastq_r1, fastq_r2, input_folder, output_folder = params

    with gzopen(fastq_r1, "rt") as fq_r1, gzopen(fastq_r2, "rt") as fq_r2, gzopen(
        f"{output_folder}/{os.path.basename(fastq_r1)}", "wt"
    ) as fastq_r1_out, gzopen(f"{output_folder}/{os.path.basename(fastq_r2)}", "wt") as fastq_r2_out:
        fastq_r1 = FastqGeneralIterator(fq_r1)
        fastq_r2 = FastqGeneralIterator(fq_r2)

        counter: int = 0
        fastq_r1_content, fastq_r2_content = "", ""
        for r1, r2 in zip(fastq_r1, fastq_r2):
            # [0]: header, [1]: seq, [2]: quality

            cdna, cdna_quality = trim_cdna(r1)
            barcode, barcode_quality = create_r2(r1, r2)

            fastq_r1_content += f'@{r1[0]}:{config["DEFAULT_SAMPLE_NAME"]}\n{barcode}\n+\n{barcode_quality}\n'
            fastq_r2_content += f'@{r2[0]}:{config["DEFAULT_SAMPLE_NAME"]}\n{cdna}\n+\n{cdna_quality}\n'

            if counter % chunk_size == 0:
                fastq_r1_out.write(fastq_r1_content)
                fastq_r2_out.write(fastq_r2_content)
                counter, fastq_r1_content, fastq_r2_content = 0, "", ""

        # write the rest of the file
        if counter != 0:
            fastq_r1_out.write(fastq_r1_content)
            fastq_r2_out.write(fastq_r2_content)

def convert(fastqs_folder: str, output_folder: str, threads: int):
    """Main function for converting MARS-seq raw files to 10X format.

    Args:
        input_folder (str): Input folder
        output_folder (str): Output folder
    """

    check_folder(fastqs_folder, mandatory=True)
    check_folder(output_folder)

    r1_files = sorted(glob.glob(f"{fastqs_folder}/*R1*.fastq.gz"))
    r2_files = sorted(glob.glob(f"{fastqs_folder}/*R2*.fastq.gz"))

    if len(r1_files) != len(r2_files):
        print(f"Something is off, please check you have the same amount of paired-end files!")
        sys.exit(-1)

    data = zip(
        r1_files,
        r2_files,
        itertools.repeat(fastqs_folder, len(r1_files)),
        itertools.repeat(output_folder, len(r1_files)),
    )
    with mp.Pool(threads) as pool:
        _ = pool.map(convert_to_10x, data)

def whitelist(batch: str, amp_batches: str, well_cells: str):
    """Generate whitelist for StarSolo.
    The file should contain cell tags.

    Args:
        batch (str): batch name
        amp_batches (str): Amplification batches [xls]
        well_cells (str): Well cells [xlsx]
    """
    batches = pd.read_excel(amp_batches)
    wells = pd.read_excel(well_cells)

    # preprocess and clean-up
    batches = batches[batches.Seq_batch_ID == batch].astype("category")
    wells = wells[wells.Amp_batch_ID.isin(batches.Amp_batch_ID)]
    wells = wells[["Amp_batch_ID", "Cell_barcode"]]
    wells["Amp_batch_ID"] = wells["Amp_batch_ID"].str.strip().astype("category")
    wells["Cell_barcode"] = wells["Cell_barcode"].str.strip().astype("category")

    # merge and concat barcodes
    wells["Amp_batch_ID"] = wells["Amp_batch_ID"].cat.rename_categories(batches.Pool_barcode.unique())
    wells["whitelist"] = wells[["Amp_batch_ID", "Cell_barcode"]].agg("".join, axis=1)

    # save
    wells["whitelist"].to_csv(f"whitelist.txt", header=None, index=None)

if __name__ == "__main__":
    logging.getLogger().setLevel(logging.INFO)
    formatter = logging.Formatter("%(asctime)s  %(message)s", "%d-%m-%Y %H:%M:%S")

    arg_parser = argparse.ArgumentParser(description="Plugin for converting and running velocity on MARS-seq v2.")

    arg_parser.add_argument("--version", "-v", action="version", version=f"velocity 0.1")
    command_parser = arg_parser.add_subparsers(dest="command")

    # convert
    convert_parser = command_parser.add_parser("convert", help="Convert reads to 10X format")
    convert_parser.add_argument("--input", type=str, help="Input folder with fastq files", required=True)
    convert_parser.add_argument("--output", type=str, help="Output folder", required=True)
    convert_parser.add_argument("--threads", type=int, help="Number of threads", required=True, default=4)

    # whitelist
    whitelist_parser = command_parser.add_parser("whitelist", help="Create whitelist for StarSolo.")
    whitelist_parser.add_argument("--batch", type=str, help="Batch name", required=True)
    whitelist_parser.add_argument("--amp_batches", type=str, help="Amplification batches [xls]", required=True)
    whitelist_parser.add_argument("--well_cells", type=str, help="Well cells [xls]", required=True)

    args = arg_parser.parse_args()

    if args.command == "convert":
        convert(args.input, args.output, args.threads)
    elif args.command == "whitelist":
        whitelist(args.batch, args.amp_batches, args.well_cells)
    else:
        logging.error(f"Command {args.command} not recognized!")


import numpy as np
import pandas as pd
import rnnInput as inp
import tensorflow as tf
import time as t
from tensorflow.contrib import learn
from rpy2.robjects.packages import importr
import rpy2.robjects as ro
import pandas.rpy.common as com

#length=2271
#maxSec=300
#TIMESTEPS=40

def poissonDiv(size=10):
    #gera uma distribuicao poison e devolve o vetor e o fator lambda que a gerou
    #faixa 60-120 para compatibilidade com DNAcopy
    val1=np.random.uniform(60,120)
    val2=np.random.uniform(60,120)
    a= np.random.poisson(lam=val1, size=size)
    b= np.random.poisson(lam=val2, size=size)
    lambRate=(val1/val2)
    c=np.log2(a/b)
    return (a/120,b/120,lambRate)

def makeCNV(tamVec,maxSector=80,minSector=30):
    #o processo de criacao do cnv ocorre da seguinte maneira
    #sorteio um tamanho para o setor, gero uma matriz com a resposta da funcao poisson
    #normalizo a matriz e preencho um vetor com o valor e a resposta (lambda/lambda)
    #repito o mesmo processo ate gerar um vetor de tamVec pra retornar

    #gera o vetor e cria o vetor resposta
    arr = np.ndarray(tamVec, np.float32)
    tY = np.ndarray(tamVec, np.float32)
    tY[:]=0
    #primeira iteracao
    tamSec=np.random.randint(minSector,high=maxSector)
    rawmatriz0,rawmatrizB,yy = poissonDiv(size=tamSec)
    matriz0=np.ma.compressed(np.ma.fix_invalid(rawmatriz0))
    matrizb=np.ma.compressed(np.ma.fix_invalid(rawmatrizB))
    #Nmatriz0=matriz0/matriz0.max()
    Nmatriz0=matriz0.compress(matriz0!=0)
    NmatrizB=matrizb.compress(matrizb!=0)
    #testeY=0

    #yy=testeY
    #preenche o vetor y
    for i in range (Nmatriz0.size): arr[i] = yy
    out = Nmatriz0
    outB = NmatrizB
    tempI=0

    #enquanto o vetor nao estiver todo preenchido, ele continua rodando
    if (Nmatriz0.size < tamVec):
        sizeZ=Nmatriz0.size
        while(sizeZ<tamVec):

            sizeZ+=tempI
            #testeY=sizeZ
            tamSec=np.random.randint(minSector,high=maxSector)
            rawMatrizT,rawMatrizB,yt = poissonDiv(size=tamSec)
            #yt = testeY
            if (sizeZ<tamVec):
                tY[sizeZ]=2**yt
            matrizT=np.ma.compressed(np.ma.fix_invalid(rawMatrizT))
            matrizB=np.ma.compressed(np.ma.fix_invalid(rawMatrizB))
            #NmatrizT=matrizT/matrizT.max()
            NmatrizT=matrizT.compress(matrizT!=0)
            NmatrizB=matrizB.compress(matrizB!=0)
            for i in range (sizeZ,NmatrizT.size+sizeZ):
                if (i<tamVec):
                    arr[i] = yt

            tempI=NmatrizT.size
            out = np.append(out,NmatrizT)
            outB = np.append(outB,NmatrizB)
        out = out[:tamVec]
        outB = outB[:tamVec]
        out=pd.DataFrame(out)
        outB=pd.DataFrame(outB)
        arr=pd.DataFrame(arr)
    #devolve o vetor escrito e o vetor gerado
    return (out,outB,arr,tY)

def getDataRND(maxLength,maxSector):
    #funcao que gera o cnv (rnd)
    #devolve o cnv e as "leituras"
    arrY=np.ndarray(shape=(maxLength))
    arrX=np.ndarray(shape=(maxLength,1))
    posAtual=0;
    setorM=np.random.randint(0,high=maxSector)
    valorSetor=np.random.uniform(0.1,0.9)
    randomFaixa = 0.18
    for i in range(maxLength):
        arrY[i] = valorSetor
        arrX[i] = np.random.uniform(valorSetor-randomFaixa,valorSetor+randomFaixa)
        if (i>setorM):
            setorM=np.random.randint(setorM,high=setorM+maxSector)
            valorSetor=np.random.uniform(0.1,0.9)
    arrY=pd.DataFrame(arrY)
    arrX=pd.DataFrame(arrX)
    return arrX,arrY

def getDataInverse(maxLength,timeSteps,maxSector):
    #devolve o inverso da funcao anterior
    #devolve leituras e na f(x) = cnv
    arrY=np.ndarray(shape=(maxLength))
    arrX=np.ndarray(shape=(maxLength,1))
    posAtual=0;
    randomFaixa = 0.08
    setorM=np.random.randint(0,high=maxSector)
    valorSetor=np.random.uniform(0.1,0.9)
    for i in range(maxLength):
        arrX[i] = valorSetor
        arrY[i] = np.random.uniform(np.absolute(valorSetor)-randomFaixa,np.absolute(valorSetor)+randomFaixa)
        if (i>setorM):
            setorM=np.random.randint(setorM,high=setorM+maxSector)
            valorSetor=np.random.uniform(0.1,0.9)
    arrY=pd.DataFrame(arrY)
    arrX=pd.DataFrame(arrX)
    return arrX,arrY

def getFullDataCNV(maxLength,maxSector):
    #apenas para simplificar
    x,y = makeCNV(maxLength,maxSector)
    return getPair(x,y)

def getFullDataOriginaCNV(maxLength,maxSector):
    #para retornar a gerador (multiplas previsoes)
    x,y = makeCNV(maxLength,maxSector=maxSector)
    gx,gy = getPair(x,y)
    return (x,y),(gx,gy)

def getFullDataRND(maxLength,maxSector):
    #apenas para simplificar
    x,y = getDataRND(maxLength,maxSector)
    return getPair(x,y)

def getFullDataOriginaRND(maxLength,maxSector):
    #para retornar a gerador (multiplas previsoes)
    x,y = getDataRND(maxLength,maxSector)
    gx,gy = getPair(x,y)
    return (x,y),(gx,gy)

def dataFitRND(length,maxSec):
    #apenas para simplificar o treinamento
    data_train2, y_train2 = getFullDataRND(length,maxSec)
    data_val2, y_val2 = getFullDataRND(int(length),maxSec)
    with tf.device('/gpu:0'):
        validation_monitor2 = learn.monitors.ValidationMonitor(data_val2, y_val2,
                                                     every_n_steps=100)
    with tf.device('/gpu:0'):
        regressor.fit(data_train2, y_train2,
              monitors=[validation_monitor2],
              batch_size=BATCH_SIZE,
              steps=TRAINING_STEPS)
    return (data_train2, y_train2)

def dataFitCNV(length,maxSec,TIMESTEPS,regressor,BATCH_SIZE,TRAINING_STEPS):
    #apenas para simplificar o treinamento
    (train_x,train_y),(orig_tes_x,origtest_y),(valid_x,valid_y) = retCNV2(length,maxSec,train=5)
    TX=inp.rnn_data(train_x,TIMESTEPS)
    TY=inp.rnn_data(train_y,TIMESTEPS,labels=True)
    VX=inp.rnn_data(valid_x,TIMESTEPS)
    VY=inp.rnn_data(valid_y,TIMESTEPS,labels=True)
    with tf.device('/gpu:0'):
    #o monitor de validacao que é responsavel por "reforcar" o aprendizado, ele usa o data_val para
        validation_monitor2 = learn.monitors.ValidationMonitor(VX, VY,
                                                     every_n_steps=1000,
                                                     early_stopping_rounds=1000)
    with tf.device('/gpu:0'):
        regressor.fit(TX, TY,
              monitors=[validation_monitor2],
              batch_size=BATCH_SIZE,
              steps=TRAINING_STEPS)
    return (TX, TY)

def retCNV2(length,maxSec,train=1):
    orig_trainxB,orig_trainxA,orig_trainy,orig_trainp = makeCNV(length*train,maxSec)
    orig_validxB,orig_validxA,orig_validy,orig_validp = makeCNV(length,maxSec)
    orig_testxB,orig_testxA,orig_testy,orig_testp = makeCNV(length,maxSec)

    orig_train_y=pd.DataFrame(orig_trainy,dtype=np.float32)
    t_p=pd.DataFrame(orig_trainy,dtype=np.float32)
    orig_train_y = orig_train_y.join(t_p,lsuffix='L',rsuffix='R',sort=False)
    orig_test_y=pd.DataFrame(orig_testy,dtype=np.float32)
    t_p=pd.DataFrame(orig_testy,dtype=np.float32)
    orig_test_y = orig_test_y.join(t_p,lsuffix='L',rsuffix='R',sort=False)
    orig_valid_y=pd.DataFrame(orig_validy,dtype=np.float32)
    t_p=pd.DataFrame(orig_validy,dtype=np.float32)
    orig_valid_y = orig_valid_y.join(t_p,lsuffix='L',rsuffix='R',sort=False)

    orig_train_x=pd.DataFrame(orig_trainxA,dtype=np.float32)
    t_p=pd.DataFrame(orig_trainxB,dtype=np.float32)
    orig_train_x = orig_train_x.join(t_p,lsuffix='L',rsuffix='R',sort=False)
    orig_test_x=pd.DataFrame(orig_testxA,dtype=np.float32)
    t_p=pd.DataFrame(orig_testxB,dtype=np.float32)
    orig_test_x = orig_test_x.join(t_p,lsuffix='L',rsuffix='R',sort=False)
    orig_valid_x=pd.DataFrame(orig_validxA,dtype=np.float32)
    t_p=pd.DataFrame(orig_validxB,dtype=np.float32)
    orig_valid_x = orig_valid_x.join(t_p,lsuffix='L',rsuffix='R',sort=False)

    return (orig_train_x,orig_train_y),(orig_test_x,orig_test_y),(orig_valid_x,orig_valid_y)

#funções para segmentação
def mediaS(vec,s,tam):
    somal=0
    for i in reversed(range(s,tam)):
        somal=somal+(vec[i])
    return(somal/(tam-s))

def segmenta(vetor,menorSeg,treshold):
    pts= np.ndarray(vetor.size)
    pts2= np.ndarray(vetor.size)
    ptsf= np.ndarray(vetor.size)
    mean= np.ndarray(vetor.size)
    pts[:]=0
    pts2[:]=0
    ptsf[:]=0
    mean[:]=0
    dadoS=(vetor)
    dadosP=np.log2(dadoS)
    lastp=0
    m=menorSeg
    tresh=treshold
    for i in range (dadoS.size):
        if (i==m):
            pts[i]=mediaS(dadosP,i-(m-1),i)
        if (i>m):
            pts[i]=mediaS(dadosP,i-m,i)
            pts2[i]=(pts[i] - pts[i-1])
            if (np.absolute(pts2[i])>tresh):
                #nao gerar setores menores q o a media
                if (i>lastp+m):
                    ptsf[i]=1
                    lastp=i

    lastpoint=0
    for i in range(0,ptsf.size):
        if (ptsf[i]==1):
            mean[i] = mediaS(dadoS,lastpoint,i)
            lastpoint=i
    i=ptsf.size
    mean[i-1] = mediaS(dadoS,lastpoint,i)
    for i in reversed(range(0,ptsf.size)):
        if (mean[i]!=0):
            at=mean[i]
        else:
            mean[i]=at
    return mean

def trechos(entra,tresh=10):
    #entra=arrayDeslocaM
    vecB=np.ndarray(entra.size)
    vecB[:]=0
    val0=entra[0]
    #tresh = 10
    for i in range(vecB.size):
        if (entra[i]!=val0):
            for j in reversed(range(i-tresh,i+tresh)):
                if (j>0 and j<vecB.size):
                    vecB[j]=1
            vecB[i]=1
        val0=entra[i]
    return vecB

def DNAcopy(entraDNAcopy):
    ro.r('library(DNAcopy)')
    d_base = com.load_data('coriell')
    _=pd.DataFrame(d_base.index,dtype=np.float32)
    _=_-1
    _.loc[_.size] = _.size
    d_base['Position'] = _
    d_base['Chromosome'] = 1
    d_base=d_base.fillna(0)
    d_base['Coriell.05296'] = entraDNAcopy
    data_r= com.convert_to_r_dataframe(d_base)
    ro.globalenv['teste'] = data_r
    ro.r('CNA.object <- CNA(cbind(teste$Coriell.05296),  teste$Chromosome,teste$Position,  data.type="logratio",sampleid="c05296")')
    ro.r('smoothed.CNA.object <- smooth.CNA(CNA.object)')
    segmentadoR=ro.r('segment.smoothed.CNA.object <- segment(smoothed.CNA.object, verbose=1)')
    return segmentadoR

def segmentaDNAcopy(fimsetores,medias):
    valorMedia=medias
    mediaatual=0
    entrada=fimsetores
    saida=np.ndarray(entrada[entrada.size-1])
    saidaB=np.ndarray(entrada[entrada.size-1])
    saida[:]=0
    saidaB[:]=0
    valatual=entrada[mediaatual]
    for i in (range(0,saida.size)):
        valatual=valorMedia[mediaatual]
        if (i>entrada[mediaatual]):
            saidaB[i]=1
            mediaatual = mediaatual + 1
            valatual=valorMedia[mediaatual]
        saida[i]=valatual
    return (2**saida),saidaB

def qtdErros(vetEntrada,vetVerifica):
    entra=vetEntrada
    verifica=vetVerifica
    sobe=False
    ponto=False
    pontos=0
    acertos=0
    for i in range(entra.size):
        if (entra[i]==1):
            sobe=True
        if (sobe and verifica[i]):
            ponto=True
        if (entra[i]==0 and sobe):
            sobe=False
            if (ponto):
                acertos=acertos + 1
                ponto=False
            else: pontos = pontos + 1
    return acertos,pontos

def pontosDNAcopy(segmentadoR,arrayOrig):
    #entraDNAcopy=np.log2(orig_test_x['A1X']/orig_test_x['A0X'])
    #segmentadoR=bioinp.DNAcopy(entraDNAcopy)
    fimsetores = np.array(list(segmentadoR[2][1]))
    medias = np.array(list(segmentadoR[1][5]))

    med,loc=segmentaDNAcopy(fimsetores,medias)
    #med = med[loc.size-arrayOrig.size:]
    loc = loc[loc.size-arrayOrig.size:]
    orig=trechos(arrayOrig,tresh=5)
    c=trechos(loc,tresh=5)

    acertos,false_negative = qtdErros (orig,c)
    _,false_positive = qtdErros (c,orig)
    #print('acertos {} fn {} ,fp {}'.format(acertos,false_positive,false_negative))
    return (acertos,false_positive,false_negative)

def dataTestCNV(length,maxSec,TIMESTEPS,regressor,i=0):
    #batch de teste
    #retorna o erro e acerto de um teste utilizando o tcnv e dnacopy
    (train_x,train_y),(orig_test_x,orig_test_y),(valid_x,valid_y) = retCNV2(length,maxSec,train=1)

    #salvar dados
    inp.setPairCtoCSV(orig_test_x,orig_test_y,"test_valid"+str(i)+".csv")

    testX = inp.rnn_data(orig_test_x,TIMESTEPS)
    with tf.device('/gpu:0'):
        predicted = regressor.predict(testX)
    predictedArr = np.array(list(predicted))
    predictedDataFrame=pd.DataFrame(predictedArr)

    segmentado=segmenta(predictedDataFrame[0],10,0.025)
    origArr = np.array(list(orig_test_y['0L']))
    origArr.shape = -1,1
    origArr=origArr[TIMESTEPS:]
    arrayDeslocado=np.array(list(origArr))

    orig=trechos(arrayDeslocado,tresh=5)
    teste=trechos(segmentado,tresh=5)
    acertosT,false_negativeT = qtdErros (orig,teste)
    _,false_positiveT = qtdErros (teste,orig)

    entraDNAcopy=np.log2(orig_test_x['0L']/orig_test_x['0R'])
    segmentadoR=DNAcopy(entraDNAcopy)
    acertos,false_positive,false_negative = pontosDNAcopy(segmentadoR,arrayDeslocado)

    #erro divisao por 0
    if(acertosT==0): acertosT=1
    if(acertos==0): acertos=1
    return (acertosT,false_positiveT,false_negativeT),(acertos,false_positive,false_negative)

def comparaSeg(length,maxSec,TIMESTEPS,regressor,qtd=1):
    scorTCNV=pd.DataFrame(index=range(qtd), columns=['tp', 'fp', 'fn', 'ppv', 'sn','score'])
    scorDNAC=pd.DataFrame(index=range(qtd), columns=['tp', 'fp', 'fn', 'ppv', 'sn','score'])
    for i in range(qtd):
        (tpt,fpt,fnt),(tpd,fpd,fnd) = dataTestCNV(length,maxSec,TIMESTEPS,regressor,i=i)

        ppvt=tpt/(tpt+fpt)
        snt=tpt/(tpt+fnt)
        ppvd=tpd/(tpd+fpd)
        snd=tpd/(tpd+fnd)
        scorTCNV['tp'][i]=tpt
        scorTCNV['fp'][i]=fpt
        scorTCNV['fn'][i]=fnt
        scorTCNV['ppv'][i]=ppvt
        scorTCNV['sn'][i]=snt
        scorTCNV['score'][i]=2*((ppvt*snt)/(ppvt+snt))

        scorDNAC['tp'][i]=tpd
        scorDNAC['fp'][i]=fpd
        scorDNAC['fn'][i]=fnd
        scorDNAC['ppv'][i]=ppvd
        scorDNAC['sn'][i]=snd
        scorDNAC['score'][i]=2*((ppvd*snd)/(ppvd+snd))

    return (scorTCNV,scorDNAC)


from .opt import flag, param, arg, walk, sub
from .usage import Usage, Help

class Base(Help):
    "The based class fror your command line app"

    def ready(self, *args, **kwargs):
        "Called before walk options. Subclass should call super().ready(*args, **kwargs)"

    def start(self, *args, **kwargs):
        "Start point of app."
        " Called after walk options."
        " .main(...) --> ready(...) --> start(...)."
        return self

    def _o_walk_sub(self, which, **kwargs):
        klass = kwargs["cmd_map"][which]
        sub = klass()
        sub._o_parent = self
        # print(list(kwargs["opt"].argv))
        return sub.main(kwargs["opt"].argv, skip_first=False)

    def options(self, opt, *args, **kwargs):
        try:
            f = super().options
        except AttributeError:
            return opt
        else:
            return f(opt)

    def main(self, argv=None, skip_first=None, **kwargs):
        "Entry point of app"
        if argv is None:
            from sys import argv
        self.ready(**kwargs)
        opt = Opt(self)
        self.options(opt)
        opt.walk(argv, skip_first=skip_first)
        # NOTE: if .start did not do anything may be it has 'yield' statement
        return self.start(**kwargs)

class Main(Usage):
    "The based class fror your command line app"

    def ready(self, *args, **kwargs):
        "Called before walk options. Subclass should call super().ready(*args, **kwargs)"

    def main(self, argv=None, **kwargs):
        "Entry point of app"
        if argv is None:
            from sys import argv
        self.ready(**kwargs)
        walk(self, argv)
        # NOTE: if .start did not do anything may be it has 'yield' statement
        return self.start(**kwargs)

    def start(self, *args, **kwargs):
        "Start point of app."
        " Called after walk options."
        " .main(...) --> ready(...) --> start(...)."

    def _o_walk_sub(self, value, **kwargs):
        klass = self._o_sub[value]
        sub = klass()
        sub._o_parent = self
        sub.ready(**kwargs)
        walk(sub, self._o_argv, skip_first=False)
        return sub.start(**kwargs)

    # def __getattr__(self, name):
    #     # TODO: Document
    #     m = "__let_" not in name
    #     if m:
    #         m = getattr(self, "__let_" + name, None)
    #     if m:
    #         setattr(self, name, None)
    #         x = m()
    #         setattr(self, name, x)
    #         return x
    #     #
    #     try:
    #         m = super().__getattr__
    #     except AttributeError:
    #         raise AttributeError(name, self)
    #     else:
    #         return m(name)

__all__ = ("flag", "param", "arg", "sub", "Main", "Base")

class Opt:
    __slots__ = ("cli", "o_params", "o_args", "inc", "argv")

    def __init__(self, cli):
        self.cli = cli
        self.inc = 0
        from collections import OrderedDict

        self.o_params = OrderedDict()
        self.o_args = OrderedDict()

    def flag(self, *args, **kwargs):
        o = self.o_params
        if not kwargs.get("dest"):
            for v in args:
                if v:
                    kwargs["dest"] = v
                    break
        for x in args:
            if x not in o:
                o[x] = kwargs
        return self

    def param(self, *args, **kwargs):
        if not kwargs.get("type"):
            kwargs["type"] = str
        return self.flag(*args, **kwargs)

    def arg(self, *args, **kwargs):
        o = self.o_args
        if True in o:
            return self
        for v in args:
            if v.isidentifier():
                # if v.isalnum():
                kwargs["dest"] = v
            else:  # v in ('+', '*', True)
                kwargs["required"] = v
        if "requires" in kwargs:
            kwargs["required"] = kwargs["requires"]

        dest = kwargs.get("dest")
        append = kwargs.get("append")
        call = kwargs.get("call")
        required = kwargs.get("required")
        if dest is None and append and append is not True:
            kwargs["dest"] = dest = append
        if dest is None and call:
            kwargs["dest"] = "arg:call:{!r}".format(call)
        k = True if (append or required in ("+", "*")) else (dest or id(kwargs))
        assert "append" not in kwargs or isinstance(
            kwargs["append"], (bool, str)
        ), ".arg append must string or True"
        # assert "dest" in kwargs, ".arg needs 'dest'"

        o[k] = kwargs
        return self

    def sub(self, cmd_map, *args, **kwargs):
        if "choices" not in kwargs:
            kwargs["choices"] = cmd_map.keys()
        if "call" not in kwargs:
            kwargs["call"] = "_o_walk_sub"
            kwargs["dest"] = "command"  # OTDO: use metavar like
        kwargs["kwargs"] = dict(cmd_map=cmd_map, opt=self)
        return self.arg(**kwargs)

    def walk(self, argv, skip_first=None):
        cli = self.cli
        _params = self.o_params
        _args = list(self.o_args.items())
        _ctx = {}
        # seen = set()
        # seen_a = {}
        # seen_p = {}
        # import pprint

        def next_arg(argv, before):
            try:
                return next(argv)
            except StopIteration:
                raise RuntimeError("Expected argument after {!r}".format(before))

        def find_param(name, arg, flag=None):
            for k, a in _params.items():
                if k == name:
                    if len(k) == 1 if flag else len(k) > 1:
                        if "seen" in a:
                            a["seen"] += 1
                        else:
                            a["seen"] = 1
                        return a
            if flag:
                raise RuntimeError("Unknown option {!r} in {!r}".format(name, arg))
            raise RuntimeError("Unknown option {!r}".format(arg))

        def find_arg(arg):
            if _args:
                k, v = _args.pop(0)
                # print("find_arg",  k, v, arg)
                if k is True:
                    _ctx["end"] = v
                return v
            elif "end" in _ctx:
                return _ctx["end"]
            raise RuntimeError("Unexpected argument {!r}".format(arg))

        # def try_call(call, v):
        #     (getattr(cli, call) if isinstance(call, str) else call)(v)

        def try_call(cur, v):
            call = cur.get("call")
            if call:
                try:
                    fn = getattr(cli, call) if isinstance(call, str) else call
                except Exception:
                    raise RuntimeError(
                        "from argument {!r} get {!r} failed".format(arg, call)
                    )
                try:
                    fn(v, **cur.get("kwargs", {}))
                except SystemExit:
                    raise
                except Exception:
                    raise RuntimeError(
                        "from argument {!r} call {!r} failed".format(arg, call)
                    )
                return True

        def push(cur, val):
            if "choices" in cur:
                val = cur.get("select", select)(val, cur["choices"])
            kind = cur.get("type")
            if kind:
                try:
                    val = kind(val)
                except Exception as exc:
                    raise RuntimeError(
                        "parse {!r} failed. from argument {!r}".format(arg, val)
                    ) from exc
            # - call
            if try_call(cur, val):
                return
            dest = cur.get("dest")
            # - append
            if "append" in cur:
                x = getattr(cli, dest, None)
                if x is None:
                    setattr(cli, dest, [val])
                else:
                    x.append(val)
                return
            setattr(cli, dest, val)

        def push_flag(cur, state):
            # - call
            if try_call(cur, state):
                return
            dest = cur["dest"]
            # - append
            append = cur.get("append")
            if append:
                if dest:
                    pass
                elif append and append is not True:
                    dest = append
                val = cur.get("const")
                x = getattr(cli, dest, None)
                if x is None:
                    if state is False:
                        pass
                    else:
                        setattr(cli, dest, [val])
                else:
                    if state is False:
                        try:
                            x.remove(val)
                        except ValueError:
                            pass
                    else:
                        x.append(val)
                return
            # - count
            count = cur.get("count")
            if count:
                if dest:
                    pass
                elif count and count is not True:
                    dest = count
                x = getattr(cli, dest, None)
                if x is None:
                    val = cur.get("const", 0)
                    if state is False:
                        setattr(cli, dest, val - 1)
                    else:
                        setattr(cli, dest, val + 1)
                else:
                    if state is False:
                        setattr(cli, dest, x - 1)
                    else:
                        setattr(cli, dest, x + 1)
                return
            # - set
            if "const" in cur:
                return setattr(cli, dest, cur["const"])
            else:
                # print("state", dest, state, state is not False)
                return setattr(cli, dest, state is not False)

        def plain(cur, arg):
            push(cur, arg)

        def short(cur, chrs, index, argv):
            index += 1
            if "type" in cur:  # params
                # print(cur['type'], chrs, index, index < len(chrs))
                push(cur, chrs[index:] if index < len(chrs) else next_arg(argv, chrs))
            else:  # flag
                # print(cur['type'], chrs, index, index < len(chrs))
                push_flag(cur, True)
                if index < len(chrs):
                    short(find_param(chrs[index], chrs, index), chrs, index, argv)

        def long(arg, cur, value=None, argv=None):
            if "type" in cur:  # params
                if value is False:
                    raise RuntimeError(
                        "{!r} takes an argument and not negatable".format(arg)
                    )
                elif value is not None:
                    push(cur, value)  # --name=VALUE
                elif argv is not None:
                    push(cur, next_arg(argv, arg))  # --name VALUE
                else:
                    raise RuntimeError("{!r} takes an argument".format(arg))
            elif value:
                raise RuntimeError("{!r} does not takes an argument".format(arg))
            else:  # flag
                assert value in (None, False)
                push_flag(cur, value)

        dd = None
        self.argv = argv = iter(argv)
        # skip prog name
        skip_first is False or next(argv, None)
        # get first argument
        arg = next(argv, None)
        while arg is not None:
            # print('ARG', arg, cli)
            if dd or ("-" == arg):
                plain(find_arg(arg), arg)
            elif "--" == arg:
                dd = True
            elif arg.startswith("--"):
                if "=" in arg:  # --name=value
                    t = arg.partition("=")
                    long(arg, find_param(t[0][2:].replace("-", "_"), arg), t[2], argv)
                elif arg.startswith("--no-"):  # --no-name
                    long(arg, find_param(arg[5:].replace("-", "_"), arg), False)
                elif arg.startswith("--no"):  # --noname
                    long(arg, find_param(arg[4:].replace("-", "_"), arg), False)
                else:  # --name
                    long(arg, find_param(arg[2:].replace("-", "_"), arg), None, argv)
            elif arg.startswith("-"):  # -qhoFILE == -q -h -o FILE
                short(find_param(arg[1], arg, 1), arg, 1, argv)
            else:
                plain(find_arg(arg), arg)
            # get next argument
            arg = next(argv, None)
        for v in _params.values():
            # print("enum_params", v)
            if "seen" in v:  # provided
                pass
            # elif hasattr(cli, v["dest"]):
            elif v["dest"] in cli.__dict__:
                pass
            elif "required" in v:
                raise RuntimeError("{} is required".format(v["dest"]))
            elif "default" in v:
                # print("default", n, v)
                setattr(cli, v["dest"], v["default"])
            elif "append" in v:
                setattr(cli, v["dest"], [])

        for k, a in _args:
            # print("enum_args", a)
            required = a.get("required")
            if required is None:
                # if hasattr(cli, a["dest"]):
                if a["dest"] in cli.__dict__:
                    pass
                elif "default" in a:
                    if try_call(a, a["default"]):
                        continue
                    setattr(cli, a["dest"], a["default"])
            elif required == "+":
                raise RuntimeError("{!r} needs atleast one argument".format(a["dest"]))
            elif required == "*":
                if "call" not in a:
                    setattr(cli, a["dest"], [])
            else:
                assert required is True, "Unexpected required value"
                raise RuntimeError("{!r} needed".format(a["dest"]))

def select(val, choices):
    chosen = None
    for n in choices:
        if not n or not n.startswith(val):
            pass
        elif chosen:
            raise RuntimeError("{!r} matches {!r} and {!r}".format(val, n, chosen))
        else:
            chosen = n
    # print(klass)
    if chosen is None:
        raise RuntimeError("Invalid choice {!r} choose from {!r} ".format(val, choices))
    return chosen


import math

import numpy as np
from PIL import Image, ImageChops

from linora.image._image_rgb import _fill_color

__all__ = ['flip_up_left', 'flip_up_right', 'flip_left_right', 'flip_up_down',
           'rotate', 'translate', 'offset', 'pad', 'channel_shuffle',
           'perspective', 'affine', 'shear', 'rescale', 'jigsaw'
          ]

def flip_up_left(image, p=1):
    """Randomly flip an image (up to left).

    Args:
        image: a PIL instance.
        p: probability that the image does this. Default value is 1.
    Returns:
        A PIL instance.
    """
    if np.random.uniform()>p:
        return image
    return image.transpose(Image.Transpose.TRANSPOSE)

def flip_up_right(image, p=1):
    """Randomly flip an image (up to right).

    Args:
        image: a PIL instance.
        p: probability that the image does this. Default value is 1.
    Returns:
        A PIL instance.
    """
    if np.random.uniform()>p:
        return image
    return image.transpose(Image.Transpose.TRANSVERSE)

def flip_left_right(image, p=1):
    """Randomly flip an image (left to right).

    Args:
        image: a PIL instance.
        p: probability that the image does this. Default value is 1.
    Returns:
        A PIL instance.
    """
    if np.random.uniform()>p:
        return image
    return image.transpose(Image.Transpose.FLIP_LEFT_RIGHT)

def flip_up_down(image, p=1):
    """Randomly flip an image (up to down).

    Args:
        image: a PIL instance.
        p: probability that the image does this. Default value is 1.
    Returns:
        A PIL instance.
    """
    if np.random.uniform()>p:
        return image
    return image.transpose(Image.Transpose.FLIP_TOP_BOTTOM)

def rotate(image, angle, expand=False, center=(0.5,0.5), translate=(0,0), fill_color=None, p=1):
    """Returns a rotated copy of this image.

    This method returns a copy of this image, rotated the given number of degrees counter clockwise around its centre.

    Args:
        image: a PIL instance.
        angle: In degrees counter clockwise, angle in [-180, 180].
               if int or float, rotation angle.
               if list or tuple, randomly picked in the interval `[angle[0], angle[1])` value.
        expand: if true, expands the output image to make it large enough to hold the entire rotated image.
                if false, make the output image the same size as the input image.
                Note that the expand flag assumes rotation around the center and no translation.
                if value is None, then the function is random.
        center: center of rotation, xaxis and yaxis in [0,1], default is the center of the image.
                if int or float, xaxis=yaxis,
                if 2-tuple, (xaxis, yaxis),
                if 4-tuple, xaxis in (center[0], center[1]) and yaxis in (center[2], center[3]).
        translate: post-rotate translation, xoffset and yoffset in [-1,1], see la.image.translate method.
                   if int or float, xoffset=yoffset,
                   if 2-tuple, (xoffset, yoffset),
                   if 4-tuple, xoffset in (translate[0], translate[1]) and  yoffset in (translate[2], translate[3]).
        fill_color: color for area outside, int or str or tuple or la.image.RGBMode, rgb color.
        p: probability that the image does this. Default value is 1.
    Returns:
        A PIL instance.
    """
    if np.random.uniform()>p:
        return image
    if isinstance(angle, (list, tuple)):
        angle = np.random.uniform(angle[0], angle[1])
    if expand is None:
        expand = np.random.choice([True, False])

    if isinstance(center, (int, float)):
        center = (int(center*image.size[0]), int(center*image.size[1]))
    elif isinstance(center, (list, tuple)):
        if len(center)==2:
            center = (int(center[0]*image.size[0]), int(center[1]*image.size[1]))
        elif len(center)==4:
            center = (int(np.random.uniform(center[0], center[1])*image.size[0]),
                      int(np.random.uniform(center[2], center[3])*image.size[1]))
        else:
            raise ValueError('`center` value format error.')
    else:
        raise ValueError('`center` value format error.')

    if isinstance(translate, (int, float)):
        translate = (int(translate*image.size[0]), int(translate*image.size[1]))
    elif isinstance(translate, (list, tuple)):
        if len(translate)==2:
            translate = (int(translate[0]*image.size[0]), int(translate[1]*image.size[1]))
        elif len(translate)==4:
            translate = (int(np.random.uniform(translate[0], translate[1])*image.size[0]),
                         int(np.random.uniform(translate[2], translate[3])*image.size[1]))
        else:
            raise ValueError('`translate` value format error.')
    else:
        raise ValueError('`translate` value format error.')

    fill_color = _fill_color(image, fill_color)
    return image.rotate(angle, resample=Image.Resampling.NEAREST, expand=expand,
                        center=center, translate=translate, fillcolor=fill_color)

def translate(image, xoffset=(-0.5,0.5), yoffset=None, fill_color=None, p=1):
    """Returns a translate copy of this image.

    Args:
        image: a PIL instance.
        xoffset: [-1, 1], int or float, width offset.
                 if list or tuple, randomly picked in the interval `[xoffset[0], xoffset[1])`.
        yoffset: [-1, 1], int or float, height offset.
                 if list or tuple, randomly picked in the interval `[yoffset[0], yoffset[1])`.
        fill_color: int or str or tuple or la.image.RGBMode, rgb color.
        p: probability that the image does this. Default value is 1.
    Returns:
        A PIL instance.
    """
    if np.random.uniform()>p:
        return image
    if isinstance(xoffset, (list, tuple)):
        xoffset = np.random.uniform(xoffset[0], xoffset[1])
    if yoffset is None:
        yoffset = xoffset
    elif isinstance(yoffset, (list, tuple)):
        yoffset = np.random.uniform(yoffset[0], yoffset[1])
    xoffset = int(image.size[0]*xoffset)
    yoffset = int(image.size[1]*yoffset)
    fill_color = _fill_color(image, fill_color)
    return rotate(image, angle=0, expand=0, center=None, translate=(xoffset, yoffset), fillcolor=fill_color)

def offset(image, xoffset, yoffset=None, p=1):
    """Returns a copy of the image where data has been offset by the given distances.

    Data wraps around the edges. If ``yoffset`` is omitted, it is assumed to be equal to ``xoffset``.

    Args:
        image: a PIL instance.
        xoffset: int or list ot tuple, The horizontal distance.
                 if tuple or list, randomly picked in the interval `[xoffset[0], xoffset[1])`.
        yoffset: int or list ot tuple, The vertical distance.
                 If omitted, both distances are set to the same value.
                 if tuple or list, randomly picked in the interval `[yoffset[0], yoffset[1])`.
        p: probability that the image does this. Default value is 1.
    Return:
        a PIL instance.
    """
    if np.random.uniform()>p:
        return image
    if isinstance(xoffset, (list, tuple)):
        xoffset = np.random.randint(xoffset[0], xoffset[1])
    if yoffset is None:
        yoffset = xoffset
    elif isinstance(yoffset, (list, tuple)):
        yoffset = np.random.randint(yoffset[0], yoffset[1])
    return ImageChops.offset(image, xoffset, yoffset)

def pad(image, pad_value, pad_color=None, p=1):
    """Add border to the image

    Args:
        image: a PIL instance.
        pad_value: int or list or tuple, if int, pad same value with border,
                   if list or tuple, len(pad_value)==2, left, top = right, bottom = pad_value
                   if list or tuple, len(pad_value)==4, left, top, right, bottom = pad_value
        pad_color: str or tuple or list or la.image.RGBMode, fill RGB color value,
                   if str, hexadecimal color;
                   if len(pad_color) == 2, left_color, top_color = right_color, bottom_color = pad_color
                   if len(pad_color) == 3, left_color = top_color = right_color = bottom_color = pad_color
                   if len(pad_color) == 4, left_color, top_color, right_color, bottom_color = pad_color
        p: probability that the image does this. Default value is 1.
    Returns:
        A PIL instance.
    """
    if np.random.uniform()>p:
        return image
    if isinstance(pad_value, (tuple, list)):
        if len(pad_value) == 2:
            left, top = right, bottom = pad_value
        elif len(pad_value) == 4:
            left, top, right, bottom = pad_value
        else:
            raise ValueError('`pad_value` value error.')
    else:
        left = top = right = bottom = pad_value

    if pad_color is None:
        left_color = (np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255))
        top_color = (np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255))
        right_color = (np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255))
        bottom_color = (np.random.randint(0, 255), np.random.randint(0, 255), np.random.randint(0, 255))
    elif isinstance(pad_color, dict):
        left_color = top_color = right_color = bottom_color = pad_color['rgb']
    elif isinstance(pad_color, (tuple, list)):
        pad_color = tuple([i['rgb'] if isinstance(i, dict) else i for i in pad_color])
        if len(pad_color) == 2:
            left_color, top_color = right_color, bottom_color = pad_color
        elif len(pad_color) == 3:
            left_color = top_color = right_color = bottom_color = pad_color
        elif len(pad_color) == 4:
            left_color, top_color, right_color, bottom_color = pad_color
        else:
            raise ValueError('`pad_value` value error.')
    else:
        left_color = top_color = right_color = bottom_color = pad_color

    width = left + image.size[0] + right
    height = top + image.size[1] + bottom

    out = Image.new(image.mode, (width, height), left_color)
    out.paste(image, (left, top))
    out.paste(Image.new(image.mode, (image.size[0] + right, top), top_color), (left, 0))
    out.paste(Image.new(image.mode, (right, top + image.size[1] + bottom), right_color), (left + image.size[0], top))
    out.paste(Image.new(image.mode, (left + image.size[0], bottom), bottom_color), (0, top + image.size[1]))
    return out

def channel_shuffle(image, p=1):
    """Random shuffle image channel.

    Args:
        image: a PIL instance.
        p: probability that the image does this. Default value is 1.
    returns:
        a PIL instance.
    """
    if np.random.uniform()>p:
        return image
    t = image.split()
    return Image.merge(image.mode, [t[i] for i in np.random.choice(list(range(len(t))), len(t), replace=False)])

def perspective(image, distortion_scale, fill_color=None, p=1):
    """Performs a random perspective transformation of the given image with a given probability.

    Args:
        image: a PIL instance.
        distortion_scale: float, argument to control the degree of distortion and ranges from 0 to 1.
        fill_color: int or str or tuple or la.image.RGBMode, rgb color.
        p: probability that the image does this. Default value is 1.
    Returns:
        a PIL instance.
    """
    if np.random.uniform()>p:
        return image
    (width, height) = image.size
    half_height = height // 2
    half_width = width // 2
    topleft = [np.random.randint(0, distortion_scale*half_width+1),
               np.random.randint(0, distortion_scale*half_height+1)]
    topright = [np.random.randint(width - distortion_scale * half_width- 1, width),
                np.random.randint(0, distortion_scale * half_height + 1)]
    botright = [np.random.randint(width - distortion_scale * half_width - 1, width),
                np.random.randint(height - distortion_scale * half_height - 1, height)]
    botleft = [np.random.randint(0, distortion_scale * half_width + 1),
               np.random.randint(height - distortion_scale * half_height - 1, height)]

    startpoints = [[0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]]
    endpoints = [topleft, topright, botright, botleft]

    a_matrix = np.zeros([2 * len(startpoints), 8])
    for i, (p1, p2) in enumerate(zip(endpoints, startpoints)):
        a_matrix[2 * i, :] = np.array([p1[0], p1[1], 1, 0, 0, 0, -p2[0] * p1[0], -p2[0] * p1[1]])
        a_matrix[2 * i + 1, :] = np.array([0, 0, 0, p1[0], p1[1], 1, -p2[1] * p1[0], -p2[1] * p1[1]])

    b_matrix = np.array(startpoints, dtype=np.float32).flatten()
    coeffs = np.linalg.lstsq(a_matrix, b_matrix, rcond=None)[0].tolist()
    fill_color = _fill_color(image, fill_color)
    return image.transform(image.size, Image.Transform.PERSPECTIVE, coeffs,
                           Image.Resampling.NEAREST, fillcolor=fill_color)

def affine(image, angle=(-180, 180), center=(0.5,0.5), translate=(0, 0), scale=1., shear=(0,0), fill_color=None, p=1):
    """Apply affine transformation on the image.

    Args:
        image: a PIL instance.
        angle: int or float, rotation angle in degrees between -180 and 180. Set to 0 to deactivate rotations.
               if list or tuple, randomly picked in the interval `[angle[0], angle[1])`.
        center: center of rotation, xaxis and yaxis in [0,1], default is the center of the image.
                if int or float, xaxis=yaxis,
                if 2-tuple, (xaxis, yaxis),
                if 4-tuple, xaxis in (center[0], center[1]) and yaxis in (center[2], center[3]).
        translate: post-rotate translation, xoffset and yoffset in [-1,1], see la.image.translate method.
                   if int or float, xoffset=yoffset,
                   if 2-tuple, (xoffset, yoffset),
                   if 4-tuple, xoffset in (translate[0], translate[1]) and  yoffset in (translate[2], translate[3]).
        scale: float, scaling factor interval, should be positive.
               if list or tuple, randomly picked in the interval `[scale[0], scale[1])`.
        shear: Range of degrees to select from, xoffset and yoffset in [-360,360].
               if int or float, xoffset=yoffset,
               if 2-tuple, (xoffset, yoffset),
               if 4-tuple, xoffset in (shear[0], shear[1]) and  yoffset in (shear[2], shear[3]).
        fill_color: int or str or tuple or la.image.RGBMode, rgb color.
        p: probability that the image does this. Default value is 1.
    Returns:
        a PIL instance.
    """
    if np.random.uniform()>p:
        return image
    if isinstance(angle, (list, tuple)):
        angle = np.random.uniform(angle[0], angle[1])

    if isinstance(center, (int, float)):
        cx, cy = (int(center*image.size[0]), int(center*image.size[1]))
    elif isinstance(center, (list, tuple)):
        if len(center)==2:
            cx, cy = (int(center[0]*image.size[0]), int(center[1]*image.size[1]))
        elif len(center)==4:
            cx = int(np.random.uniform(center[0], center[1])*image.size[0])
            cy = int(np.random.uniform(center[2], center[3])*image.size[1])
        else:
            raise ValueError('`center` value format error.')
    else:
        raise ValueError('`center` value format error.')

    if isinstance(translate, (int, float)):
        tx, ty = int(translate*image.size[0]), int(translate*image.size[1])
    elif isinstance(translate, (list, tuple)):
        if len(translate)==2:
            tx, ty = int(translate[0]*image.size[0]), int(translate[1]*image.size[1])
        elif len(translate)==4:
            tx = int(np.random.uniform(translate[0], translate[1])*image.size[0])
            ty = int(np.random.uniform(translate[2], translate[3])*image.size[1])
        else:
            raise ValueError('`translate` value format error.')
    else:
        raise ValueError('`translate` value format error.')

    if isinstance(scale, (list, tuple)):
        scale = np.random.uniform(scale[0], scale[1])
    if scale <= 0.0:
        raise ValueError("Argument scale should be positive")

    if isinstance(shear, (int, float)):
        shear = (shear, shear)
    elif isinstance(shear, (list, tuple)):
        if len(shear)==4:
            shear = (np.random.uniform(shear[0], shear[1]), np.random.uniform(shear[2], shear[3]))
        elif len(shear)!=2:
            raise ValueError('`translate` value format error.')
    else:
        raise ValueError('`translate` value format error.')

    rot = math.radians(angle)
    sx = math.radians(shear[0])
    sy = math.radians(shear[1])

    a = math.cos(rot - sy) / math.cos(sy)
    b = -math.cos(rot - sy) * math.tan(sx) / math.cos(sy) - math.sin(rot)
    c = math.sin(rot - sy) / math.cos(sy)
    d = -math.sin(rot - sy) * math.tan(sx) / math.cos(sy) + math.cos(rot)

    matrix = [d, -b, 0.0, -c, a, 0.0]
    matrix = [x / scale for x in matrix]
    matrix[2] += matrix[0] * (-cx - tx) + matrix[1] * (-cy - ty)
    matrix[5] += matrix[3] * (-cx - tx) + matrix[4] * (-cy - ty)
    matrix[2] += cx
    matrix[5] += cy
    fill_color = _fill_color(image, fill_color)
    return image.transform(image.size, Image.Transform.AFFINE, matrix,
                           Image.Resampling.NEAREST, fillcolor=fill_color)

def shear(image, xoffset=(-90, 90), yoffset=None, fill_color=None, p=1):
    """Apply affine shear on the image.

    Args:
        image: a PIL instance.
        xoffset: int or list ot tuple, The horizontal degrees, xoffset in [-360,360].
                 if tuple or list, randomly picked in the interval `[xoffset[0], xoffset[1])`
        yoffset: int or list ot tuple, The vertical degrees, yoffset in [-360,360].
                 If omitted, both distances are set to the same value.
                 if tuple or list, randomly picked in the interval `[yoffset[0], yoffset[1])`
        fill_color: int or str or tuple or la.image.RGBMode, rgb color.
        p: probability that the image does this. Default value is 1.
    Returns:
        a PIL instance.
    """
    if np.random.uniform()>p:
        return image
    if isinstance(xoffset, (list, tuple)):
        xoffset = np.random.uniform(xoffset[0], xoffset[1])
    if yoffset is None:
        yoffset = xoffset
    elif isinstance(yoffset, (list, tuple)):
        yoffset = np.random.uniform(yoffset[0], yoffset[1])
    fill_color = _fill_color(image, fill_color)
    return affine(image, angle=(0,0), center=(0.5,0.5), translate=(0, 0), scale=1.,
                  shear=(xoffset, yoffset), fill_color=fill_color, p=1)

def rescale(image, xscale=(0.5,1.5), yscale=(0.5,1.5), fill_color=None, p=1):
    """Apply scaling on the x or y axis to input data.

    Args:
        image: a PIL instance.
        xscale: if int or float, width expansion and contraction, xscale should be positive.
                if tuple or list, randomly picked in the interval `[xscale[0], xscale[1])`.
        yscale: if int or float, height expansion and contraction, yscale should be positive.
                if tuple or list, randomly picked in the interval `[yscale[0], yscale[1])`.
        fill_color: int or str or tuple or la.image.RGBMode, rgb color.
        p: probability that the image does this. Default value is 1.
    Return:
        a PIL instance.
    """
    if np.random.uniform()>p:
        return image
    if isinstance(xscale, (list, tuple)):
        xscale = np.random.uniform(xscale[0], xscale[1])
    if isinstance(yscale, (list, tuple)):
        yscale = np.random.uniform(yscale[0], yscale[1])
    fill_color = _fill_color(image, fill_color)
    size = [int(image.size[0]*xscale), int(image.size[1]*yscale)]
    size1 = [min(image.size[0], size[0]), min(image.size[1], size[1])]
    box1 = [int(size[0]/2-size1[0]/2), int(size[1]/2-size1[1]/2)]
    box1 = box1 + [box1[0]+size1[0], box1[1]+size1[1]]
    box2 = [int(image.size[0]/2-size1[0]/2), int(image.size[1]/2-size1[1]/2)]
    box2 = box2 + [box2[0]+size1[0], box2[1]+size1[1]]
    image2 = Image.new(image.mode, image.size, fill_color)
    image2.paste(image.resize(size).crop(box1), box2)
    return image2

def jigsaw(image, size=(10,10), prob=0.1, p=1):
    """Move cells within images similar to jigsaw patterns.

    Args:
        image: a PIL instance.
        size: if int or float, xsize=ysize, numbers of jigsaw.
              if 2-tuple, (xsize, ysize),
              if 4-tuple, xsize in (size[0], size[1]) and  ysize in (size[2], size[3]).
        prob: probability of every jigsaw being changed.
        p: probability that the image does this. Default value is 1.
    Return:
        a PIL instance.
    """
    if isinstance(size, (int, float)):
        size = (size, size)
    elif isinstance(size, (list, tuple)):
        if len(size)==4:
            size = (np.random.uniform(size[0], size[1]), np.random.uniform(size[2], size[3]))
        elif len(size)!=2:
            raise ValueError('`size` value format error.')
    else:
        raise ValueError('`size` value format error.')
    size = (max(2, int(size[0])), max(2, int(size[1])))

    w = image.size[0]//size[0]
    h = image.size[1]//size[1]
    axis = [[i*w, j*h, i*w+w, j*h+h] for i in range(size[0]) for j in range(size[1])]
    image2 = image.copy()
    s = np.random.choice(range(size[0]*size[1]), max(int(size[0]*size[1]*prob), 2), replace=False)
    s_index = np.random.choice(s, len(s), replace=False)
    for i,j in zip(s, s_index):
        image2.paste(image.crop(axis[i]), axis[j])
    return image2