#Código para controlar via puerto serie al Monocromador desarrollado en la cátedra de
# física experimental II de la facultad de ciencias exactas y naturales - UNCuyo por:
#            M. J. V. Ventura,
#            M. Galarza Martinez,
#            L. A. Merenda,
#            G. Portillo,
#            M. Chiarpotti,
#            F. Parizek,
#            D. Illesca,
#            N. Bizzotto.

########################################################################################################################
#Importamos la librerías necesarias y algunas funciones a utilizar.

import serial   #para comunicación serial con arduino
import os       #para control visual desde la consola
import time

def mover(pos,steps_taken):
    #Esta función toma la posición a la que debe moverse y calcula cuantos pasos tiene
    #que hacer el motor en función de la posición actual. Luego escribe en el puerto serie
    #la cantidad de pasos que tiene que hacer el motor y devuelve la posición actual del motor.
    os.system('clear')
    n_step_to_take = str(pos - steps_taken)  # pasos a dar desde la posición actual del motor
    arduino_port.write(n_step_to_take.encode()) #enviamos al puerto serie los pasos a dar
    print(f"Pasos a dar: {n_step_to_take}")
    time.sleep(0.5)
    print(f"Arduino confirma que dio {arduino_port.readline()} pasos")
    steps_taken += int(n_step_to_take)  # Actualizamos la posición actual de la red
    return steps_taken

def wait():
    #Esta función espera a que el usuario del monocromador indique
    #que quiere cambiar de posición para evitar salir al menú y cambiar
    #accidentalmente de posición
    os.system('clear')
    standby = input("Moviendo red a la posición elegida...\n...\nSi desea cambiar de color, ir a modo manual  o salir ingrese \"cam\":  ")

    while standby != "cam":
        standby = input("Si desea cambiar de color, ir a modo manual  o salir ingrese \"cam\":  ")

    print("Volviendo al menú principal... \n...\n")
    time.sleep(0.2)

def retorno(steps_taken):
    #Esta función solo retorna el motor a su posición inicial.

    print("Retornando la red a su posición inicial")
    steps_taken = str(-steps_taken)
    arduino_port.write(steps_taken.encode())
    print(f"Pasos a dar: {steps_taken}")
    time.sleep(0.5)
    print(f"Arduino confirma que dio {arduino_port.read()} pasos")
################################################################################################################

#Iniciamos el programa.
print("""
######################################################################################
###                           Monocromador FCEN-UNCuyo v1.0                        ###
######################################################################################
""")

#Abrimos el puerto para comunicarnos con el arduino,

puerto = "/dev/ttyUSB0"         #Por defecto en GNU/linux, en Windows ("COM3")
velocidad = 115200              #Puede ser: 50, 75, 110, 134, 150, 200, 300, 600,
                                #1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200.
arduino_port = serial.Serial(puerto,velocidad,timeout=1)  #Abrimos el puerto finalmente

#Asegurarse que la velocidad es la misma que la configurada en el arduino
#La configuración default para bit de data, bit de paridad y bits de stop
#es la misma (8N1) por defecto tanto en la libreria serial de python como
#en el serial.begin() de arduino por lo que no es necesaria configurarlo.

#Definimos algunas variables constantes a utilizar

angle_per_step = 5.625 / 64 #ángulo por paso del motor paso a paso 28BYJ-48
n_step_taken = 0  # Iniciamos la variable para llevar cuenta de cuantos pasos se han dado desde la posición inicial.
selection = 0 #iniciamos variable auxiliar de seleccion del menu.

while selection != "Sa":
    selection = input("""\n
Seleccionar un opción del menú:\n
    1)Rojo [Ro]
    2)Naranja [Na]
    3)Amarillo [Am]
    4)Verde [Ve]
    5)Turquesa [Tu]
    6)Azul [Az1]
    7)Azul [Az2]
    8)Violeta [Vi]
    9)Control manual [Cm]
    10)Salir [Sa]
    \nOpción seleccionada:   """)

    #A cada color le va a corresponder una posición en cantidad de pasos desde la posición inicial
    if selection == "Ro":
        position = 0 #valor de prueba hay que calibrar!

        #Movemos el motor y una actualizamos la posición del motor
        n_step_taken = mover(position,n_step_taken)

        #Esperamos a que el usuario quiera otro color, modo manual o salir
        wait()

    elif selection == "Na":
        position = +15  # valor de prueba hay que calibrar!

        # Movemos el motor y una actualizamos la posición del motor
        n_step_taken = mover(position, n_step_taken)

        # Esperamos a que el usuario quiera otro color, modo manual o salir
        wait()

    elif selection == "Am":
        position = +23  # valor de prueba hay que calibrar!

        # Movemos el motor y una actualizamos la posición del motor
        n_step_taken = mover(position, n_step_taken)

        # Esperamos a que el usuario quiera otro color, modo manual o salir
        wait()

    elif selection == "Ve":
        position = +33  # valor de prueba hay que calibrar!

        # Movemos el motor y una actualizamos la posición del motor
        n_step_taken = mover(position, n_step_taken)

        # Esperamos a que el usuario quiera otro color, modo manual o salir
        wait()

    elif selection == "Tu":
        position = +43  # valor de prueba hay que calibrar!

        # Movemos el motor y una actualizamos la posición del motor
        n_step_taken = mover(position, n_step_taken)

        # Esperamos a que el usuario quiera otro color, modo manual o salir
        wait()

    elif selection == "Az1":
        position = +53  # valor de prueba hay que calibrar!

        # Movemos el motor y una actualizamos la posición del motor
        n_step_taken = mover(position, n_step_taken)

        # Esperamos a que el usuario quiera otro color, modo manual o salir
        wait()

    elif selection == "Az1":
        position = +63  # valor de prueba hay que calibrar!

        # Movemos el motor y una actualizamos la posición del motor
        n_step_taken = mover(position, n_step_taken)

        # Esperamos a que el usuario quiera otro color, modo manual o salir
        wait()

    elif selection == "Vi":
        position = +69  # valor de prueba hay que calibrar!

        # Movemos el motor y una actualizamos la posición del motor
        n_step_taken = mover(position, n_step_taken)

        # Esperamos a que el usuario quiera otro color, modo manual o salir
        wait()

    elif selection == "Cm":
        #Para el modo manual volvemos el motor a su posición inicial
        retorno(n_step_taken)
        #Y empezamos nuevamente.
        n_step_taken = 0 #la volvemos a cero

        cm = True   #variable auxiliar para control manual
        while cm != False:
            #iniciamos modo manual e ingresamos la cantidad de pasos a dar
            angle_to_take = input("\nModo Manual:\nInserte Giro Horario[+] o AntiHorario[-], y ángulo a mover (Ejemplo: \"+15\") \n ó ingrese\"[s]\" para volver al menu anterior:")
            if angle_to_take != "s":
                #Traducimos de ángulo a pasos del motor
                #Redondeamos a un numero entero de pasos porque no existen pasos intermedios
                position = round(float(angle_to_take) / angle_per_step) + n_step_taken #Angles are in degrees

                #Movemos el motor y actualizamos la posición del motor
                n_step_taken = mover(position,n_step_taken)

                #Falta stand by
                # Esperamos a que el usuario quiera otro color, modo manual o salir
                wait()

            else:
                cm = False

    elif selection == "Sa":
        #retornamos la red a su posición inicial.
        retorno(n_step_taken)
        print("\nGracias por utilizar Monocromador v1.0")
        time.sleep(1.5)

    else:
        print("Opción incorrecta o no renocida")

    #Limpiamos la pantalla cada vez que mostramos el menú
    os.system('clear')


import os
import numpy as np
import h5py
from tqdm import tqdm
from scipy.ndimage import imread
from random import shuffle
from keras.utils import np_utils

def patch_sampling(image, patch_size=40, stride=20, b_image=None, b_thr=1, m_image=None, beta=1):
    (rows, cols, channels) = image.shape
    bias_rows = int(round((rows % patch_size) / 2))
    bias_cols = int(round((cols % patch_size) / 2))
    auth_patch_list = []
    tamp_patch_list = []

    # put m_image from 255/0 to 1/0
    if not(m_image == None):
        nonzeros = np.argwhere(m_image > 0)
        m_image[nonzeros[:, 0], nonzeros[:, 1]] = 1

    for r in range(bias_rows, rows - bias_rows - patch_size, stride):
        for c in range(bias_cols, cols - bias_cols - patch_size, stride):
            isTampered = False
            # divide tampered from authentic patches
            if not(m_image == None):
                m_patch = m_image[r:r + patch_size, c:c + patch_size]
                nb_whites = m_patch.sum()
                if nb_whites > float(patch_size)*beta and nb_whites < (patch_size**2) - float(patch_size)*beta:
                    # is tampered
                    isTampered = True
                elif nb_whites >= (patch_size**2) - float(patch_size)*beta:
                    # not considering the patch for training if it is in the tampered area
                    continue
            # check if the border image has a border inside
            if not(b_image == None):
                b_patch = b_image[r:r + patch_size, c:c + patch_size]
                b_patch[b_patch <= 128] = 0
                if len(b_patch[b_patch == 0]) > float(b_thr) * float(patch_size):
                    patch = image[r:r + patch_size, c:c + patch_size, :]
                    if isTampered:
                        tamp_patch_list.append(patch)
                    else:
                        auth_patch_list.append(patch)
            # don't care about borders in this case, get all the patches
            else:
                patch = image[r:r + patch_size, c:c + patch_size, :]
                if isTampered:
                    tamp_patch_list.append(patch)
                else:
                    auth_patch_list.append(patch)
    # now list containing image as (rows, cols, channels)
    # need to be exported as a ndarray (n_samples, n_channels, n_rows, n_cols)
    nb_patches_auth = len(auth_patch_list)
    parray_auth = np.zeros((nb_patches_auth, 3, patch_size, patch_size), dtype=np.float32)
    for i in range(nb_patches_auth):
        img = auth_patch_list[i]
        new_img = np.rollaxis(img, 2, 0)
        parray_auth[i, :, :, :] = new_img
    nb_patches_tamp = len(tamp_patch_list)
    parray_tamp = np.zeros((nb_patches_tamp, 3, patch_size, patch_size), dtype=np.float32)
    for i in range(nb_patches_tamp):
        img = tamp_patch_list[i]
        new_img = np.rollaxis(img, 2, 0)
        parray_tamp[i, :, :, :] = new_img
    return parray_auth, parray_tamp

def border_patch_sampling(image, patch_size=40, stride=20, b_image=None, b_thr=1):
    (rows, cols, channels) = image.shape
    bias_rows = int(round((rows % patch_size) / 2))
    bias_cols = int(round((cols % patch_size) / 2))
    patch_list = []
    for r in range(bias_rows, rows - bias_rows - patch_size, stride):
        for c in range(bias_cols, cols - bias_cols - patch_size, stride):
            # check if the border image has a border inside
            if not(b_image == None):
                b_patch = b_image[r:r + patch_size, c:c + patch_size]
                b_patch[b_patch <= 128] = 0
                if len(b_patch[b_patch == 0]) > float(b_thr) * float(patch_size):
                    patch = image[r:r + patch_size, c:c + patch_size, :]
                    patch_list.append(patch)
            # don't care about borders in this case, get all the patches
            else:
                patch = image[r:r + patch_size, c:c + patch_size, :]
                patch_list.append(patch)
    # now list containing image as (rows, cols, channels)
    # need to be exported as a ndarray (n_samples, n_channels, n_rows, n_cols)
    nb_patches = len(patch_list)
    parray = np.zeros((nb_patches, 3, patch_size, patch_size), dtype=np.float32)
    for i in range(nb_patches):
        img = patch_list[i]
        new_img = np.rollaxis(img, 2, 0)
        parray[i, :, :, :] = new_img
    return parray

def random_patch_sampling(image, patch_size=40, stride=20, howmany=10, isTampered=False):
    (rows, cols, channels) = image.shape
    bias_rows = int(round((rows % patch_size) / 2))
    bias_cols = int(round((cols % patch_size) / 2))
    patch_list = []
    for r in range(bias_rows, rows - bias_rows - patch_size, stride):
        for c in range(bias_cols, cols - bias_cols - patch_size, stride):
            patch = image[r:r + patch_size, c:c + patch_size, :]
            patch_list.append(patch)
    # now list containing image as (rows, cols, channels)
    # need to be exported as a ndarray (n_samples, n_channels, n_rows, n_cols)
    nb_patches = len(patch_list)
    shuffle(patch_list)
    patch_list = patch_list[0:howmany]
    nb_patches = len(patch_list)
    parray = np.zeros((nb_patches, 3, patch_size, patch_size), dtype=np.float32)
    for i in range(nb_patches):
        img = patch_list[i]
        new_img = np.rollaxis(img, 2, 0)
        parray[i, :, :, :] = new_img
    if isTampered:
        larray = np.ones((len(parray), 1), dtype=np.float32)
    else:
        larray = np.zeros((len(parray), 1), dtype=np.float32)
    return parray, larray

# mode 0 for exhaustive, 1 for border and 2 for random
def get_patch_array(myimages, description, p_size, p_stride, mode=0, doLocalization=False, doBalance=False, howmany=50):
    # check preventivo
    if doLocalization and mode == 2:
        raise('Cannot do localization with random patch sampling')
    x_tmp = []
    y_tmp = []
    nb_auth_patches = 0
    nb_tamp_patches = 0
    for i in tqdm(range(len(myimages)), desc=description):
        ## Load an color image in BGR
        img = imread(myimages[i].image_path, mode='RGB')
        if doLocalization:
            m_img = imread(myimages[i].mask_image, flatten=True)
        else:
            m_img = None
        if mode == 1:
            img_b = imread(myimages[i].border_image, flatten=True)
            auth_plist, tamp_plist = patch_sampling(img, patch_size=p_size, stride=p_stride, b_image=img_b, m_image=m_img, beta=1)
            #tmp_plist = border_patch_sampling(img, patch_size=p_size, stride=p_stride, b_image=img_b)
        elif mode == 2:
            tmp_plist, tmp_labels =random_patch_sampling(img, patch_size=p_size, stride=p_stride, howmany=howmany, isTampered=bool(myimages[i].label))
            if myimages[i].label == 0:
                nb_auth_patches += howmany
            else:
                nb_tamp_patches += howmany
            x_tmp.append(tmp_plist)
            y_tmp.append(tmp_labels)
            continue
        else:
            auth_plist, tamp_plist = patch_sampling(img, patch_size=p_size, stride=p_stride, m_image=m_img, beta=1)
            #tmp_plist = border_patch_sampling(img, patch_size=p_size, stride=p_stride)
        nb_auth_patches += len(auth_plist)
        nb_tamp_patches += len(tamp_plist)
        nb_samples = int(auth_plist.shape[0]) + int(tamp_plist.shape[0])
        tmp_plist = np.concatenate((auth_plist, tamp_plist))
        if myimages[i].label == 0: ## in this case there is no need to search for tampered
            tmp_labels = np.zeros((nb_samples, 1), dtype=np.float32)
        elif myimages[i].label == 1:
            tmp_labels = np.ones((nb_samples, 1), dtype=np.float32)
            if doLocalization:
                ## have to change all the authentic patches to 0
                tmp_labels[0:len(auth_plist)] = 0
        else:
            raise ("A label different fro 0 and 1 is not possible.")
        ## Creation of the training set
        x_tmp.append(tmp_plist)
        y_tmp.append(tmp_labels)
    # generate the final array
    p_shape = x_tmp[0].shape
    nb_over_patches = nb_auth_patches + nb_tamp_patches
    x_arr = np.zeros((nb_over_patches, p_shape[1], p_shape[2], p_shape[3]), dtype=np.float32)
    y_arr = np.zeros((nb_over_patches, 1), dtype=np.float32)
    c = 0
    for i in tqdm(range(len(myimages)), desc='Conversion to array'):
        single_image_patches = x_tmp[i]
        single_image_targets = y_tmp[i]
        pack_size = len(single_image_patches)
        x_arr[c:c+pack_size, :, :, :] = single_image_patches
        y_arr[c:c+pack_size, :] = single_image_targets
        c += pack_size
    # print('DEBUG: size of x_arr/y_arr here 1: {}/{}'.format(x_arr.shape, y_arr.shape))
    if doLocalization and doBalance:
        # need to balance the training set
        print('Balancing the Training set.')
        ref = nb_auth_patches
        if nb_auth_patches > nb_tamp_patches:
            ref = nb_tamp_patches
        # print('DEBUG: ref: {}; auth: {}; tamp: {}\nShuffling'.format(ref, nb_auth_patches, nb_tamp_patches))
        idxA = np.argwhere(y_arr[:, 0] == 0)
        idxT = np.argwhere(y_arr[:, 0] == 1)
        order = range(nb_auth_patches)
        shuffle(order)
        order = order[0:ref]
        idxA = idxA[order,0]
        order = range(nb_tamp_patches)
        shuffle(order)
        order = order[0:ref]
        idxT = idxT[order, 0]
        # print('DEBUG: A = {}; T = {}'.format(idxA.shape, idxT.shape))
        idxALL = np.concatenate((idxA, idxT))
        x_arr = x_arr[idxALL, :, :, :]
        y_arr = y_arr[idxALL, :]
        # print('DEBUG: nb auth = {}; nb tamp = {}'.format(len(np.argwhere(y_arr == 0)), len(np.argwhere(y_arr == 1))))
        # print('DEBUG: size of x_arr here 2: {}'.format(x_arr.shape))
    return x_arr, y_arr

## Creates the h5 dataset for training and test (test might be used for validation only since it is patch based)
def create_database(training_images, test_images, prename='tmp', patch_size=40, patch_stride=20, working_dir='.', useBorders=False, doLocalization=False, doRandom=False, howmany=50):
    # Creating model
    tmp_filename_train = '{}_training_p{}_s{}.h5'.format(prename, patch_size, patch_stride)
    tmp_filename_train = os.path.join(working_dir, tmp_filename_train)
    if not(os.path.exists(tmp_filename_train)):
        print('{} not found! Need to fetch data.'.format(tmp_filename_train))
        if doRandom:
            train_x, train_y = get_patch_array(training_images, 'Creation of training set', patch_size,
                                               patch_stride, mode=2, doLocalization=doLocalization, doBalance=True, howmany=50)
        elif useBorders:
            train_x, train_y = get_patch_array(training_images, 'Creation of training set', patch_size,
                                               patch_stride, mode=1, doLocalization=doLocalization, doBalance=True)
        else:
            train_x, train_y = get_patch_array(training_images, 'Creation of training set', patch_size,
                                               patch_stride, mode=0, doLocalization=doLocalization, doBalance=True)
        print('Normalization of the training set.')
        train_x = train_x / 255
        print('Shuffling data')
        order = range(len(train_x))
        shuffle(order)
        train_x = train_x[order, :, :, :]
        train_y = train_y[order, :]
        # labels to categorical
        train_y = np_utils.to_categorical(train_y, 2)
        print('Saving Training set: {}'.format(tmp_filename_train))
        with h5py.File(tmp_filename_train, 'w') as f:
            f.create_dataset('data', data=train_x, dtype='float32')
            f.create_dataset('label', data=train_y, dtype='float32')
            f.flush()
    else:
        print('Loading DB training: {}'.format(tmp_filename_train))

    # extract patches for test
    tmp_filename_test = '{}_test_p{}_s{}.h5'.format(prename, patch_size, patch_stride)
    tmp_filename_test = os.path.join(working_dir, tmp_filename_test)
    if not(os.path.exists(tmp_filename_test)):
        print('{} not found! Need to fetch data.'.format(tmp_filename_test))
        if doRandom:
            test_x, test_y = get_patch_array(training_images, 'Creation of test set', patch_size,
                                               patch_stride, mode=2, doLocalization=doLocalization, doBalance=True,
                                               howmany=50)
        elif useBorders:
            test_x, test_y= get_patch_array(training_images, 'Creation of test set', patch_size,
                                               patch_stride, mode=1, doLocalization=doLocalization, doBalance=True)
        else:
            test_x, test_y = get_patch_array(training_images, 'Creation of test set', patch_size,
                                               patch_stride, mode=0, doLocalization=doLocalization, doBalance=True) # Non necessario se non per validation

        print('Normalization of the test set.')
        test_x = test_x / 255
        # labels to categorical
        test_y = np_utils.to_categorical(test_y, 2)
        print('Saving Test set: {}'.format(tmp_filename_test))
        with h5py.File(tmp_filename_test, 'w') as f:
            f.create_dataset('data', data=test_x, dtype='float32')
            f.create_dataset('label', data=test_y, dtype='float32')
            f.flush()
    else:
        print('Loading DB test: {}'.format(tmp_filename_test))
    return tmp_filename_train, tmp_filename_test


"""
The GUI component of the Sorting Algorithm Visualizer.
This visualizes all functionalities and the drawing processes using kivy.
"""
import drawing_flow
from algorithms import *

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.metrics import dp
from kivy.graphics import Line, Color, Rectangle
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.uix.screenmanager import ScreenManager, Screen
from random import randint

class AlgorithmDatabase(Screen):
    pass

class WindowManager(ScreenManager):
    pass

class MainScreen(Screen):

    shellsort_description = """Optimized insertion sort that allows the exchange of items that are far apart.
                             Sorts the list in place by iteratively swapping elements, first very far apart and
                             eventually only 1 space apart, making it a normal insertion sort at the end."""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # booleans
        self.algorithm_chosen = False
        self.selected_algorithm = None
        self.button_pressed = False
        self.is_heap_sort = False

        # lists
        self.items = []
        self.rectangles = []
        self.labels = []
        self.indices = []  # (current_ind, old_ind, new_ind) for every change
        self.heap_history = []

        # initialize default values that will be changed according to slider values
        self.slider_value = 22.5
        self.item_counter = int(self.slider_value * 3)
        self.data_width = int(500 / self.slider_value)
        self.data_spacing = int(50 / self.slider_value)
        self.font_size_items = int(9 + (9 - (self.slider_value ** 0.6))) if self.slider_value <= 25 else 0
        self.standard_units = dp(100)
        self.start_pos_y = int(8.5 * self.standard_units)
        self.speed = .5

        self.offset_x = 0
        self.i = 0

    def run_algorithm(self):
        """
        When the 'Run Algorithm' Button is pressed, this method is executed.
        It initialises the algorithm.
        :return: None
        """
        self.button_pressed = True
        self.call_algorithm()

    def call_algorithm(self):
        """
        Calls the appropriate algorithm and the drawing procedure
        :return: None
        """
        self.indices = []
        self.i = 0

        if self.selected_algorithm == "Selection Sort":
            self.indices = selection_sort.selection_sort(self)
        elif self.selected_algorithm == "Insertion Sort":
            self.indices = insertion_sort.insertion_sort(self)
        elif self.selected_algorithm == "Shellsort":
            self.indices = shellsort.shell_sort(self)
        elif self.selected_algorithm == "Merge Sort":
            self.indices, self.items = merge_sort.call_merge_sort(self.items)
            print(self.items)
        elif self.selected_algorithm == "Heapsort":
            self.indices, self.heap_history = heapsort.heapsort(self)
        elif self.selected_algorithm == "Quicksort":
            self.indices = quicksort.quick_sort(self.items)

        drawing_flow.start_drawing(self)

    def slider_moved(self):
        """
        Whenever the slider is moved, this method is executed.
        It updates slider-related values, resets lists and draws the items on the GUI.
        :return: None
        """
        self.update_values()
        self.reset_lists()
        self.initialize_items()

    def drop_down_clicked(self, value):
        """
        This method is called whenever the drop-down menu is clicked.
        It sets the selected algorithm to the clicked algorithm and updates the button text.
        :param value: String (Button value)
        :return: None
        """
        self.ids.run_algorithm_button.disabled = False
        self.ids.run_algorithm_button.text = f'Run {value}'
        self.selected_algorithm = value

    def update_values(self):
        """
        Updates slider-related values.
        :return: None
        """
        # update slider information
        self.slider_value = int(self.ids.my_slider.value)
        self.item_counter = int(self.slider_value * 3)
        self.data_width = int(500 / self.slider_value)
        self.data_spacing = int(50 / self.slider_value)
        self.font_size_items = int(9 + (9 - (self.slider_value ** 0.6))) if self.slider_value <= 25 else 0
        self.offset_x = self.width / 10
        # set speed at an 'appropriate' level
        self.speed = ((1 / self.slider_value) ** 2) * 5

    def reset_lists(self):
        """
        Resets the object's lists and populates the items with random integers.
        :return: None
        """
        # reset and populate list
        self.items = [randint(1, 750) for i in range(self.item_counter)]
        self.rectangles = []
        self.labels = []

    def initialize_items(self):
        """
        Draws the rectangles on the board.
        :return: None
        """
        # redraw black screen
        with self.canvas:
            Color(0, 0, 0)
            Rectangle(pos=(0, self.start_pos_y), size=(self.width, - 9 * self.standard_units))

        # draw rectangles and labels
        offset = self.offset_x - self.width/2
        with self.canvas:
            for data_height in self.items:
                pos_x = int(self.width/2 - self.data_width/2 + offset)
                rectangle_size = (self.data_width, -dp(data_height))
                text_y = 8.4 * self.standard_units - dp(data_height)
                Color(1, 1, 1)
                self.rectangles.append(Rectangle(pos=(pos_x, self.start_pos_y), size=rectangle_size))
                self.labels.append(Label(pos=(pos_x, text_y), width=self.data_width,
                                         height=dp(-5), text=str(data_height), font_size=self.font_size_items,
                                         bold=True))
                offset += (self.data_width + self.data_spacing)

    def draw_change(self, dt=0):
        """
        Visualizes the exchange of two items.
        :param dt: float (time, required for Clock._ methods)
        :return: None
        """
        old_ind, new_ind, old_val, new_val = self.indices[self.i][1:]

        offset = (self.data_width + self.data_spacing)
        start_pos_x = int((self.width / 2 - self.data_width / 2) - self.width/2 + self.offset_x)

        old_pos = (start_pos_x + old_ind * offset, self.start_pos_y)
        old_size = (self.data_width, -dp(old_val))
        new_pos = (start_pos_x + new_ind * offset, self.start_pos_y)
        new_size = (self.data_width, -dp(new_val))
        black_box_size = (self.data_width, -int(9 * self.standard_units))

        text_y_old = 8.4 * self.standard_units - dp(old_val)
        text_y_new = 8.4 * self.standard_units - dp(new_val)

        # draw black where the items are
        with self.canvas:
            Color(0, 0, 0)
            self.rectangles[old_ind] = Rectangle(pos=old_pos, size=black_box_size)
            self.rectangles[new_ind] = Rectangle(pos=new_pos, size=black_box_size)

        # draw new items in place
        with self.canvas:
            Color(0, 102/255, 0)  # dark green
            self.rectangles[old_ind] = Rectangle(pos=old_pos, size=new_size)
            self.rectangles[new_ind] = Rectangle(pos=new_pos, size=old_size)
            self.labels.append(Label(pos=(new_pos[0], text_y_old), width=self.data_width, height=dp(-5),
                                     text=str(old_val), font_size=self.font_size_items, bold=True))
            self.labels.append(Label(pos=(old_pos[0], text_y_new), width=self.data_width, height=dp(-5),
                                     text=f'{new_val}', font_size=self.font_size_items, bold=True))

        self.i += 1

    def draw_done(self):
        """
        Changes the rectangles color.
        Called when sorting is completed.
        :return: None
        """
        # draw black
        with self.canvas:
            Color(0, 0, 0)
            Rectangle(pos=(0, 0), size=(self.width, self.start_pos_y + 30))

        # draw rectangles and labels
        offset = self.offset_x - self.width / 2
        with self.canvas:
            for data_height in self.items:
                pos_x = int(self.width / 2 - self.data_width / 2 + offset)
                rectangle_size = (self.data_width, -dp(data_height))
                text_y = 8.4 * self.standard_units - dp(data_height)
                Color(1, 0, 0)
                self.rectangles.append(Rectangle(pos=(pos_x, self.start_pos_y), size=rectangle_size))
                self.labels.append(Label(pos=(pos_x, text_y), width=self.data_width,
                                         height=dp(-5), text=str(data_height), font_size=self.font_size_items,
                                         bold=True))
                offset += (self.data_width + self.data_spacing)

    def indicate_current_rect(self):
        """
        Draws a violet box above the currently indexed rectangle.
        :return: None
        """
        y_pos = self.start_pos_y + 10
        y_size = 20

        offset = (self.data_width + self.data_spacing)
        start_pos_x = int((self.width / 2 - self.data_width / 2) - self.width/2 + self.offset_x)
        current_ind = self.indices[self.i][0]

        indicator_pos = (start_pos_x + current_ind * offset, y_pos)
        indicator_size = (self.data_width, y_size)
        blackbox_pos = (0, y_pos)
        blackbox_size = (self.width, y_size)

        with self.canvas:
            Color(0, 0, 0)
            Rectangle(pos=blackbox_pos, size=blackbox_size)

        with self.canvas:
            Color(204/255, 0, 204/255)  # violet
            Rectangle(pos=indicator_pos, size=indicator_size)

    def draw_black_above_rectangles(self):
        """
        Visually removes the indicator of the current index.
        :return: None
        """
        y_pos = self.start_pos_y + 10
        y_size = 20

        blackbox_pos = (0, y_pos)
        blackbox_size = (self.width, y_size)

        with self.canvas:
            Color(0, 0, 0)
            Rectangle(pos=blackbox_pos, size=blackbox_size)

    def redraw_items(self, dt):
        """
        Redraws the items on the screen, in a different color.
        :param dt: float (time, required for Clock._ methods)
        :return: None
        """
        n = len(self.heap_history[self.i])
        offset = self.offset_x - self.width / 2
        pos_x = int(self.width / 2 - self.data_width / 2 + offset)
        blackbox_size_x = int(n * (self.data_width + self.data_spacing))

        # redraw black screen
        with self.canvas:
            Color(0, 0, 0)
            Rectangle(pos=(pos_x, self.start_pos_y), size=(blackbox_size_x, - 9 * self.standard_units))

        # draw rectangles and labels
        with self.canvas:
            for data_height in self.heap_history[self.i]:
                pos_x = int(self.width / 2 - self.data_width / 2 + offset)
                rectangle_size = (self.data_width, -dp(data_height))
                text_y = 8.4 * self.standard_units - dp(data_height)
                Color(0, 0, 1)
                self.rectangles.append(Rectangle(pos=(pos_x, self.start_pos_y), size=rectangle_size))
                self.labels.append(Label(pos=(pos_x, text_y), width=self.data_width,
                                         height=dp(-5), text=str(data_height), font_size=self.font_size_items,
                                         bold=True))
                offset += (self.data_width + self.data_spacing)

    def color_all(self):
        """
        Draws the rectangles on the board.
        :return: None
        """
        self.rectangles = []
        self.labels = []
        # redraw black screen
        with self.canvas:
            Color(0, 0, 0)
            Rectangle(pos=(0, self.start_pos_y), size=(self.width, - 9 * self.standard_units))

        # draw rectangles and labels
        offset = self.offset_x - self.width / 2
        with self.canvas:
            for data_height in self.items:
                pos_x = int(self.width / 2 - self.data_width / 2 + offset)
                rectangle_size = (self.data_width, -dp(data_height))
                text_y = 8.4 * self.standard_units - dp(data_height)
                Color(0, 0, 1)
                self.rectangles.append(Rectangle(pos=(pos_x, self.start_pos_y), size=rectangle_size))
                self.labels.append(Label(pos=(pos_x, text_y), width=self.data_width,
                                         height=dp(-5), text=str(data_height), font_size=self.font_size_items,
                                         bold=True))
                offset += (self.data_width + self.data_spacing)

    def indicate_redrawing(self, dt):
        """
        Changes color of the rectangles that are about to be exchanged.
        :param dt: float (time interval that the function is called in)
        :return: None
        """
        if self.i == len(self.indices):
            drawing_flow.unschedule_drawing()

        else:
            self.indicate_current_rect()

            old_ind, new_ind, old_val, new_val = self.indices[self.i][1:]

            offset = (self.data_width + self.data_spacing)
            start_pos_x = int((self.width / 2 - self.data_width / 2) - self.width / 2 + self.offset_x)

            old_pos = (start_pos_x + old_ind * offset, self.start_pos_y)
            old_size = (self.data_width, -dp(old_val))
            new_pos = (start_pos_x + new_ind * offset, self.start_pos_y)
            new_size = (self.data_width, -dp(new_val))

            with self.canvas:
                Color(0, 1, 1)
                self.rectangles[old_ind] = Rectangle(pos=old_pos, size=old_size)
                self.rectangles[new_ind] = Rectangle(pos=new_pos, size=new_size)

    def switch_items(self, old_ind, new_ind):
        """
        Switches items' indices.
        :param old_ind: int (old index)
        :param new_ind: int (new index)
        :return: None
        """
        store = self.items[old_ind]
        self.items[old_ind] = self.items[new_ind]
        self.items[new_ind] = store

class GuiApp(App):
    pass


from django.conf.urls import include, url
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.auth.decorators import login_required
from glims import views
from django.contrib import admin
from plugins.utils import PluginManager
from bioshare import urls as bioshare_urls
admin.autodiscover()
from permissions import urls as permission_urls
from attachments import urls as attachment_urls
from proteomics import urls as proteomics_urls
from extensible import urls as extensible_urls
from django_compute import urls as compute_urls
from django_formly import urls as formly_urls
from notifications import urls as notification_urls
import api
from utils.jsutils import jsurls
from django.contrib.auth import views as auth_views
# if USE_CAS:
#     admin.site.login = login_required(admin.site.login)
#     urlpatterns += patterns('',
#         url(r'^login/$', 'cas.views.login, name='login'),
#         url(r'^logout/$', 'cas.views.logout, name='logout'),
#         url(r'^admin/logout/$', 'cas.views.logout'),
#     )

from rest_framework import routers
from glims.api.viewsets import ProjectViewSet, SampleViewSet, LabViewSet, ModelTypeSerializerViewSet, PoolViewSet, JobViewset, UserViewSet, GroupViewSet,\
    StatusSerializerViewSet, AdapterViewSet, LibraryViewSet, ExtendedNoteViewSet

router = routers.DefaultRouter()
router.register(r'users', UserViewSet,'User')
router.register(r'groups', GroupViewSet,'Group')
router.register(r'model_types', ModelTypeSerializerViewSet,'ModelType')
router.register(r'projects', ProjectViewSet,'Project')
router.register(r'samples', SampleViewSet,'Sample')
router.register(r'adapters', AdapterViewSet,'Adapter')
router.register(r'libraries', LibraryViewSet,'Library')
router.register(r'pools', PoolViewSet,'Pool')
router.register(r'jobs', JobViewset,'Job')
# router.register(r'submissions', JobSubmissionViewset)
router.register(r'labs', LabViewSet,'Lab')
router.register(r'statuses', StatusSerializerViewSet,'Status')
router.register(r'notes', ExtendedNoteViewSet,'Note')

urlpatterns = [
    # Examples:
#     url(r'^$', views.home, name='home'),
    # url(r'^blog/', include('blog.urls')),

    url(r'^admin/', include(admin.site.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    url(r'^$', views.home, name='home'),
    url(r'^projects/(?P<pk>[\-\w]+)/$', views.project, name='project'),
#     url(r'^samples/(?P<pk>[\-\w]+)/$', views.sample, name='sample'),
#     url(r'^samples/(?P<pk>[\-\w]+)/update/$', login_required(glims_views.SampleUpdate.as_view()), name='update_sample'),
#     url(r'^file/(?P<pk>\d+)/get/$', views.get_file, name='get_file'),
#     url(r'^files/(?P<model>\w+)/(?P<pk>[\-\w]+)/attach/$', views.attach_file, name='attach_file'),
    url(r'^labs/$', views.labs, name='labs'),
    url(r'^labs/(?P<pk>\d+)/modify/$', views.modify_lab, name='modify_lab'),
    url(r'^labs/create$', views.create_lab, name='create_lab'),
    url(r'^labs/(?P<pk>\d+)/$', views.lab, name='lab'),
    url(r'^projects/$', views.projects, name='projects'),
#     url(r'^projects/(?P<pk>[\-\w]+)/data/$', views.project_files, name='project_files'),
    url(r'^samples/$', views.samples, name='samples'),
    url(r'^pools/$', views.pools, name='pools'),
#     url(r'^pools/create$', views.create_pool, name='create_pool'),
    url(r'^pools/(?P<pk>[\-\w]+)/$', views.pool, name='pool'),
    url(r'^pools/(?P<pk>[\-\w]+)/delete/$', views.delete_pool, name='delete_pool'),
    url(r'^cart/$', views.cart, name='cart'),
#     url(r'^workflow/$', views.workflow, name='workflow'),

    url(r'^jobs/$', views.jobs, name='jobs'),
#     url(r'^job_submissions/(?P<id>[\d_A-Za-z]+)/$', views.job_submission, name='job_submission'),
    url(r'^jobs/(?P<id>[A-Z0-9]{10})/$', views.job, name='job'),
    url(r'^jobs/(?P<id>[A-Z0-9]{10})/files/$', views.job_files, name='job_files'),
    url(r'^accounts/login/$', auth_views.login, {'template_name': 'registration/login.html','redirect_authenticated_user': True}, name='login'),
    url(r'^accounts/logout/$', auth_views.logout, {'template_name': 'registration/logout.html','next_page':'home'}, name='logout'),
    url(r'^admin/model_types/$', views.model_types, name='model_types'),
    url(r'^admin/model_types/(?P<pk>\d+)/$', views.model_type, name='model_type'),
    url(r'^permissions/', include(permission_urls.urlpatterns)),
    url(r'^attachments/', include(attachment_urls.urlpatterns)),
    url(r'^proteomics/', include(proteomics_urls.urlpatterns)),
#     url(r'^bioinformatics/', include(bioinformatics_urls.urlpatterns)),
    url(r'^sample/(?P<pk>[\-\w]+)/$', views.sample, name='sample'),
    url(r'^api/add_samples_to_cart/$', api.views.add_samples_to_cart, name='add_samples_to_cart'),
    url(r'^api/remove_samples_from_cart/$', api.views.remove_samples_from_cart, name='remove_samples_from_cart'),
    url(r'^api/', include(router.urls)),
    url(r'^jsurls.js$', jsurls, {}, 'jsurls'),
#    url(r'^api/job/(?P<job_id>\d+\.?\d*)/update/$', api.views.update_job, name='update_job'),
    url(r'^api/pool/(?P<pk>\d+)/update/$', api.views.update_pool, name='update_pool'),
    url(r'^api/pool/(?P<pool_id>\d+)/sample/(?P<sample_id>\d+)/update/$', api.views.update_pool_sample, name='update_pool_sample'),
    url(r'^api/pool/(?P<pk>\d+)/remove_libraries/$', api.views.remove_pool_libraries, name='remove_pool_libraries'),
    url(r'^api/pool/(?P<pk>\d+)/add_libraries/$', api.views.add_pool_libraries, name='add_pool_libraries'),
    url(r'^api/projects/(?P<project_id>[\-\w]+)/data/(?:(?P<path>.*/))?$', api.views.project_files, name='get_project_files'),
    url(r'^samples/', include('glims.samples.urls')),
    url(r'^plugins/', include('plugins.urls')),
    url(r'^tasks/', include('tasks.urls')),
    url(r'^extensible/', include(extensible_urls.urlpatterns)),
    url(r'^compute/', include(compute_urls.urlpatterns)),
    url(r'^formly_forms/', include(formly_urls.urlpatterns)),
    url(r'^notifications/', include(notification_urls.urlpatterns)),
    url(r'^bioshare/', include(bioshare_urls.urlpatterns)),
    url(r'^sequencing/', include('sequencing.urls')),
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

plugin_manager = PluginManager()
urlpatterns += plugin_manager.get_urls()


from openpyxl import load_workbook
import requests
import json

wb = load_workbook(filename="saved.xlsx")
ws3 = wb["holders"]
qua = 0
ws3.cell(row=1, column=4).value = "USDC"
usd_user = requests.get(
    "https://proton.cryptolions.io/v2/state/get_tokens?limit=1000&account=pimlrp"
).text
usd_user = json.loads(usd_user)
for x in usd_user["tokens"]:
    if x["symbol"] == "XUSDC":
        USDC = x["amount"]
print(USDC)
upper5 = 0.035 * USDC
num6_20 = 0.025 * USDC
num21_40 = 0.0125 * USDC

for r in range(2, ws3.max_row):
    if r > 40 and int(ws3.cell(row=r, column=3).value) >= 100:
        qua += 1
others = USDC * (0.1 / qua)
print(qua)
for r in range(2, ws3.max_row):
    if r < 7:
        ws3.cell(row=r, column=4).value = upper5
    if r < 21 and r > 6:
        ws3.cell(row=r, column=4).value = num6_20
    if r > 20 and r < 42:
        ws3.cell(row=r, column=4).value = num21_40
    if r > 41 and r <= 40 + qua:
        ws3.cell(row=r, column=4).value = others
wb.save("temp.xlsx")


from django.urls import path, include
from .views import Home, CreatToDo, MyToDo, MyToDoDetail, MyToDoUpdate, MyToDoDelete

urlpatterns = [
    path("", Home.as_view(), name="home"),
    path("my-todos", MyToDo.as_view(), name="my_todos"),
    path("my-todos/details/<int:pk>", MyToDoDetail.as_view(), name="my_todo_details"),
    path("my-todos/update/<int:pk>", MyToDoUpdate.as_view(), name="my_todo_update"),
    path("my-todos/delete/<int:pk>", MyToDoDelete.as_view(), name="my_todo_delete"),
    path("create-todo", CreatToDo.as_view(), name="create_todo"),
]


from django.db.models import Q
from django.conf import settings
from django.http import JsonResponse
from django.contrib.postgres.aggregates import ArrayAgg
from django.views.generic.list import BaseListView
from django.views.generic.detail import BaseDetailView

from movies.models import FilmWork, PersonFilmWork

class MoviesApiMixin:
    model = FilmWork
    http_method_names = ["get"]
    movies_simple_fields_in_result = [
        "id",
        "title",
        "description",
        "creation_date",
        "rating",
        "type",
    ]
    movies_roles_in_result = {
        "actors": PersonFilmWork.Roles.ACTOR,
        "directors": PersonFilmWork.Roles.DIRECTOR,
        "writes": PersonFilmWork.Roles.WRITER,
    }

    def get_queryset(self):
        queryset = super().get_queryset().values(*self.movies_simple_fields_in_result)
        return queryset.annotate(genres=ArrayAgg("genres__name", distinct=True), **{
            field_name: ArrayAgg("persons__full_name", distinct=True, filter=Q(personfilmwork__role=role))
            for field_name, role in self.movies_roles_in_result.items()
        })

    def render_to_response(self, context, **response_kwargs):
        return JsonResponse(context)

class MoviesListApi(MoviesApiMixin, BaseListView):
    paginate_by = settings.MOVIES_PAGE_SIZE

    def get_context_data(self, *, object_list=None, **kwargs):
        paginator, page, queryset, is_paginated = self.paginate_queryset(self.get_queryset(), self.paginate_by)

        context = {
            "count": paginator.count,
            "total_pages": paginator.num_pages,
            "prev": page.previous_page_number() if page.has_previous() else None,
            "next": page.next_page_number() if page.has_next() else None,
            "results": list(queryset),
        }
        return context

class MoviesDetailApi(MoviesApiMixin, BaseDetailView):
    def get_context_data(self, **kwargs):
        return self.object


from typing import Any

from bleak.backends.scanner import AdvertisementData

ADVERTISEMENT_DATA_DEFAULTS = {
    "manufacturer_data": {},
    "service_data": {},
    "service_uuids": [],
    "rssi": -127,
    "platform_data": ((),),
    "tx_power": -127,
}

def generate_advertisement_data(**kwargs: Any) -> AdvertisementData:
    """Generate advertisement data with defaults."""
    new = kwargs.copy()
    for key, value in ADVERTISEMENT_DATA_DEFAULTS.items():
        new.setdefault(key, value)
    return AdvertisementData(**new)


from typing import Union

from pytorch_lightning.loggers.base import DummyLogger
from pytorch_lightning.utilities import rank_zero_info, rank_zero_warn
from pytorch_lightning.utilities.exceptions import MisconfigurationException

class DebuggingConnector:

    def __init__(self, trainer):
        self.trainer = trainer

    def on_init_start(
            self,
            limit_train_batches,
            limit_val_batches,
            limit_test_batches,
            val_check_interval,
            overfit_batches,
            fast_dev_run
    ):
        if not isinstance(fast_dev_run, (bool, int)):
            raise MisconfigurationException(
                f'fast_dev_run={fast_dev_run} is not a valid configuration.'
                ' It should be either a bool or an int >= 0'
            )

        if isinstance(fast_dev_run, int) and (fast_dev_run < 0):
            raise MisconfigurationException(
                f'fast_dev_run={fast_dev_run} is not a'
                ' valid configuration. It should be >= 0.'
            )

        self.trainer.fast_dev_run = fast_dev_run
        fast_dev_run = int(fast_dev_run)

        # set fast_dev_run=True when it is 1, used while logging
        if fast_dev_run == 1:
            self.trainer.fast_dev_run = True

        if fast_dev_run:
            limit_train_batches = fast_dev_run
            limit_val_batches = fast_dev_run
            limit_test_batches = fast_dev_run
            self.trainer.max_steps = fast_dev_run
            self.trainer.num_sanity_val_steps = 0
            self.trainer.max_epochs = 1
            self.trainer.val_check_interval = 1.0
            self.trainer.check_val_every_n_epoch = 1
            self.trainer.logger = DummyLogger()

            rank_zero_info(
                'Running in fast_dev_run mode: will run a full train,'
                f' val and test loop using {fast_dev_run} batch(es).'
            )

        self.trainer.limit_train_batches = _determine_batch_limits(limit_train_batches, 'limit_train_batches')
        self.trainer.limit_val_batches = _determine_batch_limits(limit_val_batches, 'limit_val_batches')
        self.trainer.limit_test_batches = _determine_batch_limits(limit_test_batches, 'limit_test_batches')
        self.trainer.val_check_interval = _determine_batch_limits(val_check_interval, 'val_check_interval')
        self.trainer.overfit_batches = _determine_batch_limits(overfit_batches, 'overfit_batches')
        self.determine_data_use_amount(self.trainer.overfit_batches)

    def determine_data_use_amount(self, overfit_batches: float) -> None:
        """Use less data for debugging purposes"""
        if overfit_batches > 0:
            self.trainer.limit_train_batches = overfit_batches
            self.trainer.limit_val_batches = overfit_batches
            self.trainer.limit_test_batches = overfit_batches

def _determine_batch_limits(batches: Union[int, float], name: str) -> Union[int, float]:
    if 0 <= batches <= 1:
        return batches
    elif batches > 1 and batches % 1.0 == 0:
        return int(batches)
    else:
        raise MisconfigurationException(
            f'You have passed invalid value {batches} for {name}, it has to be in [0.0, 1.0] or an int.'
        )


#!/usr/bin/python3
# encoding: utf-8

import threading
from tile.sqllitedb import TileSqlLiteDB
from random import *
from tile.Info import TileInfo
import hashlib
import shutil
import os
from PIL import Image

Image.MAX_IMAGE_PIXELS = None

OpenSeaMapMerged = 'OpenSeaMapMerged'

def _MergePictures(background_file, overlay_file, result_file):

    background = Image.open(background_file)
    overlay = Image.open(overlay_file)

    background = background.convert("RGBA")
    overlay = overlay.convert("RGBA")

    new_img = Image.alpha_composite(background, overlay)
    new_img.save(result_file, "PNG")

class MergeThread(threading.Thread):

    def __init__(self, tileman, lock, DBDIR):
        threading.Thread.__init__(self)
        self.tileman = tileman
        self.DBDIR = DBDIR
        self.stop = False
        self.lock = lock
        self._WorkingDirMerge = self.tileman._WorkingDirectory + "{}".format(randint(1, 0xffffffff))

    def SetTileSrv(self, TsOpenSeaMap, TSOpenStreetMap):
        self.TSOpenStreetMap = TSOpenStreetMap
        self.TsOpenSeaMap = TsOpenSeaMap

    def CleanupWorkingDir(self):
        if os.path.exists(self._WorkingDirMerge):
            shutil.rmtree(self._WorkingDirMerge)

    def run(self):
        # log = getlog()
        self.db = TileSqlLiteDB(self.DBDIR)
        cnt = 0

        while(self.stop is False):
            if len(self.tileman.joblist) is 0:
                self.stop = True
                break
            job = self.tileman.joblist[0]
            self.tileman.joblist.pop(0)
            # print("{} is working on job {} {} {} {}".format(self.name, job[0], job[1], job[2], job[3]))

            x = job[1]
            y = job[2]
            z = job[3]

            self.lock.acquire()
            tile_osm_street = self.db.GetTile(self.TSOpenStreetMap.name, z, x, y)
            tile_osm_sea = self.db.GetTile(self.TsOpenSeaMap.name, z, x, y)
            tile_osm3 = self.db.GetTile(OpenSeaMapMerged, z, x, y)
            self.lock.release()

            # stop processing if tile is not available
            if tile_osm_street is None or tile_osm_sea is None:
                assert(False)
                break

            if (tile_osm_street.updated is not 0) or (tile_osm_sea.updated is not 0) or (tile_osm3 is None):
                tile_merged = self.MergeTile(tile_osm_street, tile_osm_sea)
                tile_osm_street.updated = False
                tile_osm_sea.updated = False

                self.lock.acquire()
                self.db.StoreTile(OpenSeaMapMerged, tile_merged, z, x, y)
                self.db.StoreTile(self.TSOpenStreetMap.name, tile_osm_street, z, x, y)
                self.db.StoreTile(self.TsOpenSeaMap.name, tile_osm_sea, z, x, y)
                self.lock.release()

                self.tileman.tilemerged += 1
            else:
                self.tileman.tilemergedskipped += 1
            cnt += 1

        self.db.CloseDB()

    def MergeTile(self, tile_osm_street, tile_osm_sea):
        # store tile  in file
        filename_in1 = self._WorkingDirMerge + "/" + 'file_openstreetmap.png'
        filename_in2 = self._WorkingDirMerge + "/" + 'file_openseamap.png'
        filename_result1 = self._WorkingDirMerge + "/" + 'file_merged.png'

        # print(hashlib.md5(tile_osm_sea.data).hexdigest())

        # check if tile is blank (or in other words empty)
        if "df9310416043ee37c3b3d4896549d823" == hashlib.md5(tile_osm_sea.data).hexdigest():
            filename_result1 = filename_in1
            tile_osm_street.StoreFile(filename_result1)
        else:
            tile_osm_street.StoreFile(filename_in1)
            tile_osm_sea.StoreFile(filename_in2)

            background = Image.open(filename_in1)
            overlay = Image.open(filename_in2)

            background = background.convert("RGBA")
            overlay = overlay.convert("RGBA")

            new_img = Image.alpha_composite(background, overlay)
            new_img.save(filename_result1, "PNG")

        ret = TileInfo()
        ret.SetData(filename_result1)
        return ret


import requests
import base64
import pandas as pd
from core.banner import banner
from core.log import logger
from core.cmdline import args
from core.conf import email,key
from  core.colors import red
from core.lib import ip2cidr,saveContext,readFile
import json
logger=logger()
num = 0
class FofaAPI(object):
    def __init__(self, email, key):
        self.email = email
        self.key = key
        self.base_url = 'https://fofa.so'
        self.search_api_url = '/api/v1/search/all'
        self.login_api_url = '/api/v1/info/my'
        self.get_userinfo()

    def get_userinfo(self):
        try:
            url = '{url}{api}'.format(url=self.base_url, api=self.login_api_url)
            data = {"email": self.email, 'key': self.key}
            req = requests.get(url, params=data)
            return req.json()
        except requests.exceptions.ConnectionError:
            error_msg = {"error": True, "errmsg": "Connect error"}
            return error_msg

    def get_data(self, query_str,size=1000, fields='host,ip,title'):
        try:
            url = '{url}{api}'.format(url=self.base_url, api=self.search_api_url)
            query_str =query_str.encode(encoding="utf-8")
            base64_str=base64.b64encode(query_str)
            data = {'qbase64': base64_str, 'email': self.email, 'key': self.key, 'size': size,
                    'fields': fields}
            req = requests.get(url, params=data, timeout=10)
            return req.json()
        except requests.exceptions.ConnectionError:
            error_msg = {"error": True, "errmsg": "Connect error"}
            return error_msg

if __name__ == '__main__':
    banner()
    ips=[]
    fofa = FofaAPI(email,key)
    if args.cert:
        f = readFile(args.cert)
        domains = f.readlines()
        for domain in domains:
            domain = domain.strip()
            if args.status_code:
                query = 'cert="{}" && type="subdomain" && country="CN" && status_code="{}"'.format(domain,args.status_code)
            else:
                query='cert="{}" && type="subdomain" && country="CN"'.format(domain)
            logger.INFO(red+"Finding.....................................[{}]!".format(domain))
            try:
                result = fofa.get_data(query,args.size, "host,ip,title")
            except:
                logger.INFO("search error!!")
            try:
                for host, ip, title in result['results']:
                    if args.cidr:
                        ips.append(ip2cidr(ip))
                    logger.INFO("Host: {0} IP: {1} Title: [{2}]".format(host, ip, title) + "  is FOUND!")
                    num = num + 1

                if args.cidr:
                    cips = saveContext(ips, domain+"_cidr"+".csv")
                    logger.INFO("C段为: {}".format(cips[1]))
                    logger.INFO("一共查询出{}个C段 ".format(cips[0]) + ",保存为{}......".format(domain+"_cidr"+".csv"))
                logger.INFO("一共导出{0}条数据，保存为{1}........................".format(num,"{}_{}.csv".format(args.status_code,domain) ))
                logger.INFO("保存中.....")
                num=0
                df = pd.DataFrame(columns=['host', 'ip', 'title'], data=result['results'])
                df.to_csv("./{}".format("{}_{}.csv".format(args.status_code,domain)), encoding='utf-8', index=False)
                logger.INFO("保存完毕......")
            except:
                logger.INFO("search [{}] error!! please check your query words!!".format(domain))
    else:
        try:
            result=fofa.get_data(args.query,args.size,"host,ip,title")
        except:
            logger.INFO("search error!!")
        try:
            for host,ip,title in result['results']:
                if args.cidr:
                    ips.append(ip2cidr(ip))
                logger.INFO("Host: {0} IP: {1} Title: [{2}]".format(host,ip,title)+"  is FOUND!")
                num=num+1

            if args.cidr:
                cips=saveContext(ips,args.cidr)
                logger.INFO("C段为: {}".format(cips[1]))
                logger.INFO("一共查询出{}个C段 ".format(cips[0])+",保存为{}......".format(args.cidr))
            logger.INFO("一共导出{0}条数据，保存为{1}.........".format(num,args.report_name))
            logger.INFO("保存中.....")
            df = pd.DataFrame(columns=['host', 'ip','title'], data=result['results'])
            df.to_csv("./{}".format(args.report_name), encoding='utf-8', index=False)
            logger.INFO("保存完毕......")
        except:
            logger.INFO("search [{}] error!! please check your query words!!".format(args.query))


import argparse
import gym
import numpy as np
import os
import tensorflow as tf
import tempfile
import time
import json
import random

import rlattack.common.tf_util as U

from rlattack import logger
from rlattack import deepq
from rlattack.deepq.replay_buffer import ReplayBuffer, PrioritizedReplayBuffer
from rlattack.common.misc_util import (
    boolean_flag,
    pickle_load,
    pretty_eta,
    relatively_safe_pickle_dump,
    set_global_seeds,
    RunningAvg,
    SimpleMonitor,
)
from rlattack.common.schedules import LinearSchedule, PiecewiseSchedule

# when updating this to non-deprecated ones, it is important to
# copy over LazyFrames
from rlattack.common.atari_wrappers_deprecated import wrap_dqn
from rlattack.common.azure_utils import Container
from model import model, dueling_model
from statistics import statistics

def parse_args():
    parser = argparse.ArgumentParser("DQN experiments for Atari games")
    # Environment
    parser.add_argument("--env", type=str, default="Pong", help="name of the game")
    parser.add_argument("--seed", type=int, default=42, help="which seed to use")
    # Core DQN parameters
    parser.add_argument(
        "--replay-buffer-size", type=int, default=int(1e6), help="replay buffer size"
    )
    parser.add_argument(
        "--lr", type=float, default=1e-4, help="learning rate for Adam optimizer"
    )
    parser.add_argument(
        "--num-steps",
        type=int,
        default=int(2e8),
        help="total number of steps to \
                        run the environment for",
    )
    parser.add_argument(
        "--batch-size",
        type=int,
        default=32,
        help="number of transitions to optimize \
                        at the same time",
    )
    parser.add_argument(
        "--learning-freq",
        type=int,
        default=4,
        help="number of iterations between \
                        every optimization step",
    )
    parser.add_argument(
        "--target-update-freq",
        type=int,
        default=40000,
        help="number of iterations between \
                        every target network update",
    )
    # Bells and whistles
    boolean_flag(parser, "noisy", default=False, help="whether or not to NoisyNetwork")
    boolean_flag(
        parser, "double-q", default=True, help="whether or not to use double q learning"
    )
    boolean_flag(
        parser, "dueling", default=False, help="whether or not to use dueling model"
    )
    boolean_flag(
        parser,
        "prioritized",
        default=False,
        help="whether or not to use prioritized replay buffer",
    )
    parser.add_argument(
        "--prioritized-alpha",
        type=float,
        default=0.6,
        help="alpha parameter for prioritized replay buffer",
    )
    parser.add_argument(
        "--prioritized-beta0",
        type=float,
        default=0.4,
        help="initial value of beta \
                        parameters for prioritized replay",
    )
    parser.add_argument(
        "--prioritized-eps",
        type=float,
        default=1e-6,
        help="eps parameter for prioritized replay buffer",
    )
    # Checkpointing
    parser.add_argument(
        "--save-dir",
        type=str,
        default=None,
        required=True,
        help="directory in which \
                        training state and model should be saved.",
    )
    parser.add_argument(
        "--save-azure-container",
        type=str,
        default=None,
        help="It present data will saved/loaded from Azure. \
                        Should be in format ACCOUNT_NAME:ACCOUNT_KEY:\
                        CONTAINER",
    )
    parser.add_argument(
        "--save-freq",
        type=int,
        default=1e6,
        help="save model once every time this many \
                        iterations are completed",
    )
    boolean_flag(
        parser,
        "load-on-start",
        default=True,
        help="if true and model was previously saved then training \
                 will be resumed",
    )

    # V: Attack Arguments #
    parser.add_argument(
        "--attack", type=str, default=None, help="Method to attack the model."
    )
    parser.add_argument(
        "--attack-init", type=int, default=0, help="Iteration no. to begin attacks"
    )
    parser.add_argument(
        "--attack-prob",
        type=float,
        default=0.0,
        help="Probability of attack at each step, \
                        float in range 0 - 1.0",
    )
    return parser.parse_args()

def make_env(game_name):
    env = gym.make(game_name + "NoFrameskip-v4")
    monitored_env = SimpleMonitor(env)
    env = wrap_dqn(monitored_env)
    return env, monitored_env

def maybe_save_model(savedir, container, state):
    if savedir is None:
        return
    start_time = time.time()
    model_dir = "model-{}".format(state["num_iters"])
    U.save_state(os.path.join(savedir, model_dir, "saved"))
    if container is not None:
        container.put(os.path.join(savedir, model_dir), model_dir)
    relatively_safe_pickle_dump(
        state, os.path.join(savedir, "training_state.pkl.zip"), compression=True
    )
    if container is not None:
        container.put(
            os.path.join(savedir, "training_state.pkl.zip"), "training_state.pkl.zip"
        )
    relatively_safe_pickle_dump(
        state["monitor_state"], os.path.join(savedir, "monitor_state.pkl")
    )
    if container is not None:
        container.put(os.path.join(savedir, "monitor_state.pkl"), "monitor_state.pkl")
    logger.log("Saved model in {} seconds\n".format(time.time() - start_time))

def maybe_load_model(savedir, container):
    """Load model if present at the specified path."""
    if savedir is None:
        return

    state_path = os.path.join(os.path.join(savedir, "training_state.pkl.zip"))
    if container is not None:
        logger.log("Attempting to download model from Azure")
        found_model = container.get(savedir, "training_state.pkl.zip")
    else:
        found_model = os.path.exists(state_path)
    if found_model:
        state = pickle_load(state_path, compression=True)
        model_dir = "model-{}".format(state["num_iters"])
        if container is not None:
            container.get(savedir, model_dir)
        U.load_state(os.path.join(savedir, model_dir, "saved"))
        logger.log(
            "Loaded models checkpoint at {} iterations".format(state["num_iters"])
        )
        return state

if __name__ == "__main__":
    args = parse_args()
    # Parse savedir and azure container.
    savedir = args.save_dir
    if args.save_azure_container is not None:
        account_name, account_key, container_name = args.save_azure_container.split(":")
        container = Container(
            account_name=account_name,
            account_key=account_key,
            container_name=container_name,
            maybe_create=True,
        )
        if savedir is None:
            # Careful! This will not get cleaned up.
            savedir = tempfile.TemporaryDirectory().name
    else:
        container = None
    # Create and seed the env.
    env, monitored_env = make_env(args.env)
    if args.seed > 0:
        set_global_seeds(args.seed)
        env.unwrapped.seed(args.seed)

    # V: Save arguments, configure log dump path to savedir #
    if savedir:
        with open(os.path.join(savedir, "args.json"), "w") as f:
            json.dump(vars(args), f)
        logger.configure(dir=savedir)  # log to savedir

    with U.make_session(4) as sess:
        # Create training graph and replay buffer
        act, train, update_target, debug, craft_adv = deepq.build_train(
            make_obs_ph=lambda name: U.Uint8Input(
                env.observation_space.shape, name=name
            ),
            q_func=dueling_model if args.dueling else model,
            num_actions=env.action_space.n,
            optimizer=tf.train.AdamOptimizer(learning_rate=args.lr, epsilon=1e-4),
            gamma=0.99,
            grad_norm_clipping=10,
            double_q=args.double_q,
            noisy=args.noisy,
            attack=args.attack,
        )
        approximate_num_iters = args.num_steps / 4
        exploration = PiecewiseSchedule(
            [
                (0, 1.0),
                (approximate_num_iters / 50, 0.1),
                (approximate_num_iters / 5, 0.01),
            ],
            outside_value=0.01,
        )

        if args.prioritized:
            replay_buffer = PrioritizedReplayBuffer(
                args.replay_buffer_size, args.prioritized_alpha
            )
            beta_schedule = LinearSchedule(
                approximate_num_iters, initial_p=args.prioritized_beta0, final_p=1.0
            )
        else:
            replay_buffer = ReplayBuffer(args.replay_buffer_size)

        U.initialize()
        update_target()
        num_iters = 0

        # Load the model
        state = maybe_load_model(savedir, container)
        if state is not None:
            num_iters, replay_buffer = (
                state["num_iters"],
                state["replay_buffer"],
            )
            monitored_env.set_state(state["monitor_state"])

        start_time, start_steps = None, None
        steps_per_iter = RunningAvg(0.999)
        iteration_time_est = RunningAvg(0.999)
        obs = env.reset()
        # Record the mean of the \sigma
        sigma_name_list = []
        sigma_list = []
        for param in tf.trainable_variables():
            # only record the \sigma in the action network
            if "sigma" in param.name and "deepq/q_func/action_value" in param.name:
                summary_name = (
                    param.name.replace("deepq/q_func/action_value/", "")
                    .replace("/", ".")
                    .split(":")[0]
                )
                sigma_name_list.append(summary_name)
                sigma_list.append(tf.reduce_mean(tf.abs(param)))
        f_mean_sigma = U.function(inputs=[], outputs=sigma_list)
        # Statistics
        writer = tf.summary.FileWriter(savedir, sess.graph)
        im_stats = statistics(
            scalar_keys=["action", "im_reward", "td_errors", "huber_loss"]
            + sigma_name_list
        )
        ep_stats = statistics(scalar_keys=["ep_reward", "ep_length"])
        # Main trianing loop
        ep_length = 0
        while True:
            num_iters += 1
            ep_length += 1

            # V: Perturb observation if we are past the init stage
            # and at a designated attack step
            # if craft_adv != None and (num_iters >= args.attack_init)
            # and ((num_iters - args.attack_init) % args.attack_freq == 0) :
            if (
                craft_adv is not None
                and (num_iters >= args.attack_init)
                and (random.random() <= args.attack_prob)
            ):
                obs = craft_adv(np.array(obs)[None])[0]

            # Take action and store transition in the replay buffer.
            if args.noisy:
                # greedily choose
                action = act(np.array(obs)[None], stochastic=False)[0]
            else:
                # epsilon greedy
                action = act(
                    np.array(obs)[None], update_eps=exploration.value(num_iters)
                )[0]
            new_obs, rew, done, info = env.step(action)
            replay_buffer.add(obs, action, rew, new_obs, float(done))
            obs = new_obs
            if done:
                obs = env.reset()

            if (
                num_iters > max(5 * args.batch_size, args.replay_buffer_size // 20)
                and num_iters % args.learning_freq == 0
            ):
                # Sample a bunch of transitions from replay buffer
                if args.prioritized:
                    experience = replay_buffer.sample(
                        args.batch_size, beta=beta_schedule.value(num_iters)
                    )
                    (
                        obses_t,
                        actions,
                        rewards,
                        obses_tp1,
                        dones,
                        weights,
                        batch_idxes,
                    ) = experience
                else:
                    obses_t, actions, rewards, obses_tp1, dones = replay_buffer.sample(
                        args.batch_size
                    )
                    weights = np.ones_like(rewards)
                # Minimize the error in Bellman's and compute TD-error
                td_errors, huber_loss = train(
                    obses_t, actions, rewards, obses_tp1, dones, weights
                )
                # Update the priorities in the replay buffer
                if args.prioritized:
                    new_priorities = np.abs(td_errors) + args.prioritized_eps
                    replay_buffer.update_priorities(batch_idxes, new_priorities)
                # Write summary
                mean_sigma = f_mean_sigma()
                im_stats.add_all_summary(
                    writer,
                    [action, rew, np.mean(td_errors), np.mean(huber_loss)] + mean_sigma,
                    num_iters,
                )

            # Update target network.
            if num_iters % args.target_update_freq == 0:
                update_target()

            if start_time is not None:
                steps_per_iter.update(info["steps"] - start_steps)
                iteration_time_est.update(time.time() - start_time)
            start_time, start_steps = time.time(), info["steps"]

            # Save the model and training state.
            if num_iters > 0 and (
                num_iters % args.save_freq == 0 or info["steps"] > args.num_steps
            ):
                maybe_save_model(
                    savedir,
                    container,
                    {
                        "replay_buffer": replay_buffer,
                        "num_iters": num_iters,
                        "monitor_state": monitored_env.get_state(),
                    },
                )

            if info["steps"] > args.num_steps:
                break

            if done:
                steps_left = args.num_steps - info["steps"]
                completion = np.round(info["steps"] / args.num_steps, 1)
                mean_ep_reward = np.mean(info["rewards"][-100:])
                logger.record_tabular("% completion", completion)
                logger.record_tabular("steps", info["steps"])
                logger.record_tabular("iters", num_iters)
                logger.record_tabular("episodes", len(info["rewards"]))
                logger.record_tabular(
                    "reward (100 epi mean)", np.mean(info["rewards"][-100:])
                )
                if not args.noisy:
                    logger.record_tabular("exploration", exploration.value(num_iters))
                if args.prioritized:
                    logger.record_tabular("max priority", replay_buffer._max_priority)
                fps_estimate = (
                    float(steps_per_iter) / (float(iteration_time_est) + 1e-6)
                    if steps_per_iter._value is not None
                    else "calculating:"
                )
                logger.dump_tabular()
                logger.log()
                logger.log("ETA: " + pretty_eta(int(steps_left / fps_estimate)))
                logger.log()
                # add summary for one episode
                ep_stats.add_all_summary(writer, [mean_ep_reward, ep_length], num_iters)
                ep_length = 0


from collections.abc import Sequence
from typing import Union, cast

import awkward as ak
import numpy as np
from scanpy import logging

from scirpy import get
from scirpy.util import DataHandler, _is_na

@DataHandler.inject_param_docs()
def chain_qc(
    adata: DataHandler.TYPE,
    *,
    airr_mod="airr",
    airr_key="airr",
    chain_idx_key="chain_indices",
    inplace: bool = True,
    key_added: Sequence[str] = ("receptor_type", "receptor_subtype", "chain_pairing"),
) -> Union[None, tuple[np.ndarray, np.ndarray, np.ndarray]]:
    """\
    Perform quality control based on the receptor-chain pairing configuration.

    Categorizes cells into their receptor types and according to their chain pairing
    status. The function adds three columns to `adata.obs`, two containing a coarse
    and fine annotation of receptor types, a third classifying cells according
    to the number of matched receptor types.

    `receptor_type` can be one of the following
        * `TCR` (all cells that contain any combination of TRA/TRB/TRG/TRD chains,
          but no IGH/IGK/IGL chains)
        * `BCR` (all cells that contain any combination of IGH/IGK/IGL chains,
          but no TCR chains)
        * `ambiguous` (all cells that contain both BCR and TCR chains)
        * `multichain` (all cells with more than two VJ or more than two VDJ chains)
        * `no IR` (all cells without any detected immune receptor)

    `receptor_subtype` can be one of the following
        * `TRA+TRB` (all cells that have only TRA and/or TRB chains)
        * `TRG+TRD` (all cells that have only TRG and/or TRD chains)
        * `IGH` (all cells that have only IGH chains, but no IGL or IGK)
        * `IGH+IGL` (all cells that have only IGH and IGL chains)
        * `IGH+IGK` (all cells that have only IGH and IGK chains)
        * `multichain` (all cells with more than two VJ or more than two VDJ chains)
        * `ambiguous` (all cells that are none of the above, e.g. TRA+TRD, TRA+IGH or,
          IGH+IGK as the primary and IGH+IGL as the secondary receptor)
        * `no IR` (all cells without any detected immune receptor)

    `chain_pairing` can be one of the following
        * `single pair` (all cells that have exactely one matched VJ and VDJ chain)
        * `orphan VJ` (all cells that have only one VJ chain)
        * `orphan VDJ` (all cells that have only one VDJ chain)
        * `extra VJ` (all cells that have a matched pair of VJ and VDJ chains plus an
          additional VJ-chain)
        * `extra VDJ` (analogous)
        * `two full chains` (all cells that have two matched pairs of VJ and VDJ chains)
        * `ambiguous` (all cells that have unmatched chains, i.e. that have been
          classified as an `ambiguous` receptor_subtype)
        * `multichain` (all cells with more than two VJ or more than two VDJ chains)
        * `no IR` (all chains with not immune receptor chains)

    Parameters
    ----------
    {adata}
    {inplace}
    {key_added}
    {airr_mod}
    {airr_key}
    {chain_idx_key}

    Returns
    -------
    Depending on the value of `inplace` either adds three columns to
    `adata.obs` or returns a tuple with three numpy arrays containing
    the annotations.
    """
    params = DataHandler(adata, airr_mod, airr_key, chain_idx_key)

    # initalize result arrays
    string_length = len("multichain")
    res_receptor_type = np.empty(dtype=f"<U{string_length}", shape=(params.adata.shape[0],))
    res_receptor_subtype = np.empty(dtype=f"<U{string_length}", shape=(params.adata.shape[0],))

    mask_has_ir = get._has_ir(params)
    mask_multichain = mask_has_ir & ak.to_numpy(params.chain_indices["multichain"])

    vj_loci = cast(np.ndarray, get.airr(params, "locus", ["VJ_1", "VJ_2"]).values)
    vdj_loci = cast(np.ndarray, get.airr(params, "locus", ["VDJ_1", "VDJ_2"]).values)

    # Build masks for receptor chains
    has_tra = (vj_loci == "TRA").any(axis=1)
    has_trg = (vj_loci == "TRG").any(axis=1)
    has_igk = (vj_loci == "IGK").any(axis=1)
    has_igl = (vj_loci == "IGL").any(axis=1)

    has_trb = (vdj_loci == "TRB").any(axis=1)
    has_trd = (vdj_loci == "TRD").any(axis=1)
    has_igh = (vdj_loci == "IGH").any(axis=1)

    has_tr = has_tra | has_trg | has_trb | has_trd
    has_ig = has_igk | has_igl | has_igh

    # Combine masks into receptor types and subtypes
    type_is_t = has_tr & ~has_ig
    type_is_b = ~has_tr & has_ig

    subtype_is_tab = (has_tra | has_trb) & ~(has_trg | has_trd | has_ig)
    subtype_is_tgd = (has_trg | has_trd) & ~(has_tra | has_trb | has_ig)
    subtype_is_ighk = (has_igk) & ~(has_tr | has_igl)
    subtype_is_ighl = (has_igl) & ~(has_tr | has_igk)
    # orphan IGH
    subtype_is_igh = (has_igh) & ~(has_igk | has_igl | has_tr)

    # Apply masks for receptor type
    res_receptor_type[:] = "ambiguous"
    res_receptor_type[~mask_has_ir] = "no IR"
    res_receptor_type[type_is_t] = "TCR"
    res_receptor_type[type_is_b] = "BCR"
    res_receptor_type[mask_multichain] = "multichain"

    # Apply masks for receptor subtypes
    res_receptor_subtype[:] = "ambiguous"
    res_receptor_subtype[~mask_has_ir] = "no IR"
    res_receptor_subtype[subtype_is_tab] = "TRA+TRB"
    res_receptor_subtype[subtype_is_tgd] = "TRG+TRD"
    res_receptor_subtype[subtype_is_igh] = "IGH"
    res_receptor_subtype[subtype_is_ighl] = "IGH+IGL"
    res_receptor_subtype[subtype_is_ighk] = "IGH+IGK"
    res_receptor_subtype[mask_multichain] = "multichain"

    res_chain_pairing = _chain_pairing(params, res_receptor_subtype == "ambiguous", mask_has_ir, mask_multichain)

    if inplace:
        col_receptor_type, col_receptor_subtype, col_chain_pairing = key_added
        params.set_obs(col_receptor_type, res_receptor_type)
        params.set_obs(col_receptor_subtype, res_receptor_subtype)
        params.set_obs(col_chain_pairing, res_chain_pairing)
    else:
        return (res_receptor_type, res_receptor_subtype, res_chain_pairing)

def _chain_pairing(
    params: DataHandler,
    mask_ambiguous: np.ndarray,
    mask_has_ir: np.ndarray,
    mask_multichain: np.ndarray,
) -> np.ndarray:
    """Annotate chain pairing categories.

    Parameters
    ----------
    mask_ambiguous
        boolean array of the same length as `adata.obs`, marking
        which cells have an ambiguous receptor configuration.
    """
    string_length = len("two full chains")
    results = np.empty(dtype=f"<U{string_length}", shape=(params.adata.shape[0],))

    logging.debug("Done initalizing")

    mask_has_vj1 = ~_is_na(get.airr(params, "junction_aa", "VJ_1").values)
    mask_has_vdj1 = ~_is_na(get.airr(params, "junction_aa", "VDJ_1").values)
    mask_has_vj2 = ~_is_na(get.airr(params, "junction_aa", "VJ_2").values)
    mask_has_vdj2 = ~_is_na(get.airr(params, "junction_aa", "VDJ_2").values)

    logging.debug("Done with masks")

    for m in [mask_has_vj1, mask_has_vdj1, mask_has_vj2, mask_has_vdj2]:
        # no cell can have a junction_aa sequence but no TCR
        assert np.setdiff1d(np.where(m)[0], np.where(mask_has_ir)[0]).size == 0

    results[~mask_has_ir] = "no IR"
    results[mask_has_vj1] = "orphan VJ"
    results[mask_has_vdj1] = "orphan VDJ"
    results[mask_has_vj1 & mask_has_vdj1] = "single pair"
    results[mask_has_vj1 & mask_has_vdj1 & mask_has_vj2] = "extra VJ"
    results[mask_has_vj1 & mask_has_vdj1 & mask_has_vdj2] = "extra VDJ"
    results[mask_has_vj1 & mask_has_vdj1 & mask_has_vj2 & mask_has_vdj2] = "two full chains"
    results[mask_ambiguous] = "ambiguous"
    results[mask_multichain] = "multichain"

    assert "" not in results, "One or more chains are not characterized"

    return results