# %load ./FunctionsDB.py

import copy
from MainFunctions import writepickle, initray
from rdkit import Chem  # Importing RDKit
from FindFunctionalGroups import identify_functional_groups as IFG
import os
import dask.dataframe as dd
import dask.bag as db
import pandas as pd
from typing import List, Dict, Union, Tuple
from AnalgCompds import getCarrierFrags0
import modin.pandas as mpd

#%% Building basic substance database

def info(molfile: str) -> Union[Tuple[Chem.rdchem.Mol, str], str]:
    """
    Processes a molfile address of a species and returns either a tuple of the SMILES and the mol object, or the address if the mol object is not valid.

    Args:
        molfile (str): molfile address of the species.

    Returns:
        Union[Tuple[Chem.rdchem.Mol, str], str]: Either a tuple of the SMILES and the mol object, or the address if the mol object is not valid.
    """
    mol = Chem.MolFromMolFile(molfile)
    if mol:
        Chem.SanitizeMol(mol)
        mol.UpdatePropertyCache(strict=False)
        smiles = Chem.MolToSmiles(mol)
        return mol, smiles
    else:
        return molfile

def basic(ID: str, folder: str) -> Dict:
    """
    Based on the species ID and folder location, returns a dictionary of species information by processing the molfile.
    The molfile needs to be located in folder+os.sep+ID address, and will be processed by RDKit.
    Args:
        ID (str): Reaxys ID of the species.
        folder (str): Folder address of the species mol file on the server

    Returns:
        Dict: Dictionary of species information with SubstanceID, MolFileAddress, and Error if present
    """

    if str.isdecimal(ID):
        molfileaddress = folder + os.sep + ID
        try:
            res = info(molfileaddress)
        except Exception as e:
            error = e
            compaddrs = {
                "SubstanceID": int(ID),
                "MolFileAddress": molfileaddress,
                "Error": error,
            }
        else:
            if type(res) == tuple:
                smiles = res[1]
                compaddrs = {
                    "SubstanceID": int(ID),
                    "MolFileAddress": molfileaddress,
                    "Smiles": smiles,
                }
            else:
                error = "Valence error"
                compaddrs = {
                    "SubstanceID": int(ID),
                    "MolFileAddress": molfileaddress,
                    "Error": error,
                }
    else:
        compaddrs = {}
    return compaddrs

def basicgroup(molfilelist: List[str], folder: str) -> List[Dict]:
    """
    Invokes basic for each ID in molfilelist and returns a list of dictionaries of species information.

    Args:
        molfilelist (List[str]): List of Reaxys IDs to be processed
        folder (str): Folder address

    Returns:
        List[Dict]: List of dictionaries of species information
    """
    return [basic(ID, folder) for ID in molfilelist]

def substancedblist(folderName: str, partitions: int) -> List[Dict]:
    """
    Invoke basicgroup for folder and returns a list of dictionaries of species information.

    Args:
        folderName (str): Folder address
        partitions (int): Number of partitions to split folder contents into.

    Returns:
        List[Dict]: List of dictionaries of species information
    """
    dem = os.listdir(folderName)
    b = db.from_sequence(dem, npartitions=partitions)
    dflist = b.map_partitions(basicgroup, folderName).compute()
    return dflist

#%% Adding fragment smarts column accounting for mixtures
def getfrags(
    series: pd.Series, expand: int = 1, resFormat: str = "smiles"
) -> Union[List[str], str]:  # natoms changed to expand
    """
    Returns carrier fragments of a species given a dataframe row or pandas series and a number of atoms to expand from identified functional groups.
    Accordingly processes mixtures (eg. salts)

    Args:
        series (pd.Series): pandas series of a row of the dataframe
        expand (int, optional): Number of neighboring atoms to expand functional group to. Defaults to 1.
        resFormat (str, optional): Format of the returned results. Defaults to "smiles".

    Returns:
        Union[List[str], str]: If no errors in smiles, returns a tuple of the list of fragment smiles/smarts present in species. Otherwise,
        returns "Error"
    """
    smiles = series["Smiles"]
    if smiles == "Error":
        return "Error"
    if (
        series[">1 Compound"] == True
    ):  # This compound is a mixture. Need to split and apply getcarrierfrags to each smiles
        fraginfo = getmixturefrags(smiles, expand=expand, resFormat=resFormat)
    else:
        try:
            fraginfo = getCarrierFrags0(smiles, expand=expand, resFormat=resFormat)
        except Exception:
            return "Error"
    if type(fraginfo) != list:
        fraginfo = [fraginfo]
    return fraginfo

def createfragdb(
    substancedb: Union[str, pd.DataFrame],
    expand: int = 1,
    ncpus: int = 16,
    restart: bool = True,
    resFormat: str = "smiles",
    explode: bool = True,
    fragdb: pd.DataFrame = None,
) -> pd.DataFrame:
    """
    Generates fragment dataframe from a substance dataframe by applying the getfrags function to each row.

    Args:
        substancedb (Union[str, pd.DataFrame]): Either a path to a pickle file of a pandas dataframe of the substance database, or a pandas dataframe of the substance database.
        expand (int, optional): Number of neighboring atoms to expand functional group to. Defaults to 1.
        ncpus (int, optional): Number of CPUs to use. Defaults to 16.
        restart (bool, optional): Controls if ray cluster already exists, will restart. Defaults to True.
        resFormat (str, optional): Format of the returned results. Defaults to "smiles".
        explode (bool, optional): Controls if the returned dataframe is exploded (on FragmentSmiles or FragmentSmarts). Defaults to True.
        fragdb (pd.DataFrame, optional): If provided, will not run the fragment function but instead will process/cleanup the dataframe. Defaults to None.

    Returns:
        pd.DataFrame: Fragment dataframe with the new column FragmentSmiles or FragmentSmarts
    """
    if resFormat == "smiles":
        columns = "FragmentSmiles"
    elif resFormat == "smarts":
        columns = "FragmentSmarts"
    if fragdb is None:
        if isinstance(substancedb, str):
            substancedb = pd.read_pickle(substancedb)
        if ncpus > 1:
            initray(restart=restart, num_cpus=ncpus)
            substancedbdis = mpd.DataFrame(substancedb)
        else:
            substancedbdis = substancedb
        fragdat = substancedbdis.apply(
            getfrags, axis=1, expand=expand, resFormat=resFormat, result_type="reduce"
        )
        fragdat = pd.Series(data=fragdat.values, index=fragdat.index)
        fragdb = pd.DataFrame(data=fragdat, index=fragdat.index, columns=[columns])
    if fragdb.index.name != "SubstanceID" or fragdb.index.names:
        if fragdb.index.name or fragdb.index.names:
            fragdb.reset_index(inplace=True)
        fragdb.set_index("SubstanceID", inplace=True)
    if substancedb.index.name != "SubstanceID" or substancedb.index.names:
        if substancedb.index.name or substancedb.index.names:
            substancedb.reset_index(inplace=True)
        substancedb.set_index("SubstanceID", inplace=True)
    fragdb["Smiles"] = substancedb.loc[substancedb.index.isin(fragdb.index)]["Smiles"]
    fragdb[">1 Compound"] = substancedb[substancedb.index.isin(fragdb.index)][
        ">1 Compound"
    ]
    # fragdb=fragdb.loc[fragdb[columns]!='Error']
    if explode:
        fragdb = fragdb.explode(columns)
    if "count" not in fragdb.columns:
        fragdb["count"] = fragdb.groupby([fragdb.index, columns])[columns].transform(
            "count"
        )
    fragdb.reset_index(inplace=True)
    fragdb.drop_duplicates(inplace=True)
    fragdb.set_index([columns, "SubstanceID"], inplace=True)
    return fragdb

def createfragfreq(fragdb: Union[str, pd.DataFrame]) -> pd.DataFrame:
    """
    Returns a fragment frequency dataframe

    Args:
        fragdb (Union[str,pd.DataFrame]): Either a path to a pickle file of a pandas dataframe of the fragment database, or a pandas dataframe of the fragment database.

    Returns:
        pd.DataFrame: Fragment frequency dataframe
    """
    if isinstance(fragdb, str):
        fragdb = pd.read_pickle(fragdb)
    if fragdb.index.names or fragdb.index.name:
        fragdb.reset_index(inplace=True)
    freq = (
        fragdb[["SubstanceID", "FragmentSmiles"]]
        .groupby(["FragmentSmiles"])["SubstanceID"]
        .transform("count")
    )
    freqtable = fragdb[["FragmentSmiles"]]
    freqtable["Frequency"] = freq
    freqtable.drop_duplicates(inplace=True)
    freqtable["Popularity"] = (
        freqtable["Frequency"]
        .div(freqtable["Frequency"].sum(axis=0), axis=0)
        .multiply(100)
        .round()
    )
    return freqtable

def getfragpartition(partition, natoms):
    return partition.apply(getfrags, natoms=natoms, axis=1)

#%% Adding fragment smiles column
def getfragsmiles(fragsmarts: Union[str, List[str]]) -> Union[str, List[str]]:
    """
    Converts fragment smarts to smiles

    Args:
        fragsmarts (Union[str,List[str]]): Fragment smarts either string or list of strings

    Returns:
        Union[str,List[str]]: Returns fragment smiles either as a string , list of strings or "Error" if input is "Error"
    """
    if type(fragsmarts) == list:
        fragsmiles = [
            Chem.MolToSmiles(Chem.MolFromSmarts(fragsmart)) for fragsmart in fragsmarts
        ]
    elif fragsmarts == "Error":
        return "Error"
    else:
        fragsmiles = Chem.MolToSmiles(Chem.MolFromSmarts(fragsmarts))
    return fragsmiles

def getsmiles(series: pd.Series) -> Union[str, List[str]]:
    """
    Row version of getfragsmiles

    Args:
        series (pd.Series): Row containing fragment smarts

    Returns:
        Union[str, List[str]]: Returns fragment smiles either as a string , list of strings or "Error" if input is "Error"
    """
    fragsmarts = series["FragmentSmarts"]
    return getfragsmiles(fragsmarts)

def getsmilespartition(partition):
    return partition.apply(getsmiles, axis=1)

#%% Creating mixture column (True if mixture, False if not mixture, Error if smiles not present)

def mixtures(smiles: str) -> Union[bool, str]:
    """
    Returns if a species is a mixture.

    Args:
        smiles (str): SMILES string of the species.

    Returns:
        Union[bool,str]: True if the species is a mixture, False if not, and Error if the species has no SMILES.
    """
    if smiles == "Error":
        return "Error"
    elif len(smiles.split(".")) > 1:
        return True
    else:
        return False

def findMixtures(series: pd.Series) -> Union[bool, str]:
    """
    Takes in a pandas series/row corresponding to a species, and returns if the species is a mixture.

    Args:
        series (pd.Series): Pandas series or row corresponding to a species.

    Returns:
        Union[bool,str]: True if the species is a mixture, False if not, and Error if the species has no SMILES.
    """
    smiles = series["Smiles"]
    return mixtures(smiles)

def findMixturespartition(partition):
    return partition.apply(findMixtures, axis=1)

#%% Changing fragment entries in mixture rows

def getmixturefrags(
    mixsmiles: str, expand: int = 1, resFormat: str = "smarts", addHs: bool = True
) -> Union[str, List[str]]:
    """
    Returns the carrier fragments of a mixture (SMILES has '.' in it) by splitting it into constituent species

    Args:
        mixsmiles (str): Mixture SMILES string
        expand (int, optional): Number of neighboring atoms to expand functional group to. Defaults to 1.
        resFormat (str, optional): Result type, either 'smarts' or 'smiles. Defaults to 'smarts'.
        addHs (bool, optional): If true, hydrogens will be considered in generation of fragment, recommended as true
        otherwise terminal atoms of a molecule are not differentiated with other atoms. Defaults to True.

    Returns:
        Union[str,List[str]]: List of strings of SMARTS or SMILES representing carrier fragments of the mixture. If workflow fails, returns 'Error'.
    """
    try:
        res = []
        for smiles in mixsmiles.split("."):
            reslist = getCarrierFrags0(
                smiles, expand=expand, resFormat=resFormat, addHs=addHs
            )
            if type(reslist) != list:
                res += [reslist]
            else:
                res += reslist
    except Exception:
        return "Error"
    else:
        return res

def getMixturefrags(series: pd.Series, expand: bool = 1) -> Union[str, List[str]]:
    """
    Row version of getmixturefrags

    Args:
        series (pd.Series): Row containing mixture SMILES
        expand (bool, optional): Number of neighboring atoms to expand functional group to. Defaults to 1.

    Returns:
        Union[str,List[str]]: List of strings of SMARTS or SMILES representing carrier fragments of the mixture. If workflow fails, returns 'Error'.
    """
    mixsmiles = series[
        "Smiles"
    ]  # add .values[0] if column is a multiindex, otherwise droplevel = 1 to remove list
    return getmixturefrags(mixsmiles, expand=expand)

def getMixturefragspartition(partition, expand=1):
    return partition.apply(getMixturefrags, expand=expand, axis=1)

#%% Joining columns to a dataframe

def joindf(
    seriesdf: pd.DataFrame, DB: pd.DataFrame, explodeDB: Union[str, Tuple] = None
) -> pd.DataFrame:
    """
    Joins seriesdf (dataframe to add) to DB (dataframe to be added to). Optionally, can explode DB into separate rows for each entry in DB based on
    columns specified in explodeDB.

    Args:
        seriesdf (pd.DataFrame): Dataframe to add.
        DB (pd.DataFrame): Dataframe to be added to.
        explodeDB (Union[str,Tuple], optional): Either a string column or tuple of multiple columns. Defaults to None.

    Returns:
        pd.DataFrame: Dataframe with seriesdf added to DB.
    """
    if seriesdf.index.name != DB.index.name or seriesdf.index.names != DB.index.names:
        if DB.index.name or DB.index.names:
            DB.reset_index(inplace=True)
        if seriesdf.index.name or seriesdf.index.names:
            seriesdf.reset_index(inplace=True)
    DB = DB.join(seriesdf)
    if explodeDB:
        DB = DB.explode(explodeDB)
    return DB

def createspecfreqdb(
    substancedb: Union[str, pd.DataFrame],
) -> pd.DataFrame:
    """
    Creates a dataframe with species and frequency of occurrence in a database.

    Args:
        substancedb (Union[str,pd.DataFrame]): Database to be analyzed. Can be either a filename or a dataframe.

    Returns:
        pd.DataFrame: Dataframe with species and frequency of occurrence in the database.
    """
    if isinstance(substancedb, str):
        substancedb = pd.read_pickle(substancedb)
    species = substancedb.groupby("Smiles").size().reset_index(name="Frequency")
    species.sort_values(by="Frequency", ascending=False, inplace=True)
    species.reset_index(drop=True, inplace=True)
    return species

#%% Deprecated..see DB_Reaxys.ipynb
def buildfragdb(
    sdb=None,
    sdbd=None,
    writesdbd=False,
    sdbdc=None,
    fragseries=None,
    natoms=None,
    writefragseries=False,
    fdbm=None,
    fdb=None,
    dfdb=None,
    fragsmiles=False,
    mixtures=False,
    mixturefrags=False,
    index=None,
    writefdb=False,
    writedfdb=False,
):

    # Note: cluster and client must be initiated for this function to work. Substance database, either dask or pandas
    # should also be loaded. It is advised to avoid loading dask dataframes due to high memory usage. It is recommended to
    # persist and create dask dataframes outside this function, otherwise overheads may be added. Never reset dask dataframe
    # index or else it will reindex all partitions...always start with pandas, reindex and then convert to dask when large computations
    # need to be done.

    # sdb = substance database, sdbd = substance database dask, sdbdc = substance database dask cleaned
    # (remove smile errors), # fragseries = series containing fragment information, natoms =  size of fragment,
    # writefragseries = True if write to file false otherwise, fdbm = fragment database master, pandas version of sdbdc,
    # fdb = final fragment database exploded and unindexed pfdb = final pandas fragment dataframe, multiindexed,
    # dfdb = final dask fragment dataframe

    # Step 1: Create dask dataframe from pandas substance database (output: sdbd)

    if sdb and not type(sdbd) == dd.core.DataFrame:
        sdbd = dd.from_pandas(sdb, npartitions=16)
        sdbd = client.persist(sdbd)
        print("Dask substance dataframe created and persisted")
        if writesdbd:
            sdbd.to_parquet(writesdbd)
            print("Dask substance dataframe written to file: " + writesdbd)
        if not natoms and not fragseries:
            print("Please specify size of fragments that should be retrieved")
            return sdbd

    # Step 2: Clean dask data frame and select only substance ID and smiles column, removing errors (output: sdbdc).

    if sdbd and not type(sdbdc) == dd.core.DataFrame:
        sdbd = client.persist(sdbd)
        sdbdc = sdbd.reset_index()[["SubstanceID", "Smiles", ">1 Compound"]]
        sdbdc = sdbdc[sdbdc.Smiles != "Error"]
        sdbdc = client.persist(sdbdc)
        print("Cleaned dask substance dataframe created and persisted")
        if not natoms and not type(fragseries) == pd.core.series.Series:
            print("Please specify size of fragments that should be retrieved")
            return sdbdc

    # Step 3: Scrape 16 million compounds, and extract series of active fragments for each (output: fragseries)

    if (
        not type(fragseries) == pd.core.series.Series
        and not type(fdb) == pd.core.frame.DataFrame
    ):
        if not type(sdbdc) == dd.core.DataFrame:
            return "Please include a cleaned dask substance dataframe for fragment retrieval"
        if not natoms:
            return "Please specify size of fragments retrieved"
        sdbdc = client.persist(sdbdc)
        #         if natoms==0:
        #             name='ActiveFragmentSmarts'
        #         else:
        #            filename='CarrierFragments'+'(n='+str(natoms)+')'
        name = "FragmentSmarts"
        fragseries = sdbdc.map_partitions(
            getfragpartition, natoms=natoms, meta=(name, "O")
        ).compute()
        print("fragseries retrieved")

    if writefragseries:
        if not type(fragseries) == pd.core.series.Series:
            return "Supply fragment series to write to file"
        writepickle(fragseries, writefragseries)
        print("fragseries writted to file: " + writefragseries)

    # Step 4: Prepare cleaned pandas dataframe for fragment series attachment (output: fdbm)

    if (
        not type(fdbm) == pd.core.frame.DataFrame
        and not type(fdb) == pd.core.frame.DataFrame
    ):
        if not type(sdbdc) == dd.core.DataFrame:
            return "Please include a cleaned dask substance dataframe to which fragment information can be attached"
        fdbm = sdbdc.compute()
    #         fdbm.reset_index(inplace=True)
    #         fdbm.drop('index',axis=1,inplace=True)

    # Step 5: Attaching fragment series, generating an exploded fragment database (output: fdb)

    if type(fdbm) == pd.core.frame.DataFrame:
        if not type(fragseries) == pd.core.series.Series:
            return "Please include the fragment series that should be attached"
        fragdf = pd.DataFrame(fragseries)
        fdb = joindf(fragdf, fdbm, explodeDB=fragdf.columns[0])
        print("Unindexed fragment database completed")

    # Step 6: Adding additional columns, formatting, indexing and writing fragment database to file

    if fragsmiles:
        if not type(fdb) == pd.core.frame.DataFrame:
            return "Please supply fragment database to analyze"
        if not type(dfdb) == dd.core.DataFrame:
            if fdb.index.name or fdb.index.names:
                fdb.reset_index(inplace=True)
            dfdb = dd.from_pandas(fdb, npartitions=181)
            dfdb = client.persist(dfdb)
        fragseries = dfdb.map_partitions(
            getsmilespartition, meta=("FragmentSmiles", "O")
        ).compute()
        if writefragseries:
            if not type(fragseries) == pd.core.series.Series:
                return "Supply fragment series to write to file"
            writepickle(fragseries, writefragseries)
            print("fragseries writted to file: " + writefragseries)
        smilesdf = pd.DataFrame(fragseries)
        fdb = joindf(smilesdf, fdb)

    if mixtures:
        if not type(fdb) == pd.core.frame.DataFrame:
            return "Please supply fragment database to analyze"
        if not type(dfdb) == dd.core.DataFrame:
            if fdb.index.name or fdb.index.names:
                fdb.reset_index(inplace=True)
            dfdb = dd.from_pandas(fdb, npartitions=181)
            dfdb = client.persist(dfdb)
        fragseries = dfdb.map_partitions(
            findMixturespartition, meta=(">1 Compound", "boolean")
        )
        if writefragseries:
            if not type(fragseries) == pd.core.series.Series:
                return "Supply fragment series to write to file"
            writepickle(fragseries, writefragseries)
            print("fragseries writted to file: " + writefragseries)
        mixturedf = pd.DataFrame(fragseries)
        fdb = joindf(mixturedf, fdb)

    if mixturefrags:
        # NOTE: If exploded, fragment database should be aggregated by Smiles/Smarts
        if (
            not type(fdb) == pd.core.frame.DataFrame
            or "> 1 Compound" not in fdb.columns
        ):
            return "Please supply fragment database to analyze, with mixture indication (specify mixtures=True)"
        if not natoms:
            return "Please specify size of fragments retrieved"
        if not type(dfdb) == dd.core.DataFrame:
            dfdb = dd.from_pandas(fdb, npartitions=16)
            dfdb = client.persist(dfdb)
        fragseries = dfdb.map_partitions(
            getMixturefragspartition, natoms=natoms, meta=("FragmentSmarts", "O")
        )
        if writefragseries:
            if not type(fragseries) == pd.core.series.Series:
                return "Supply fragment series to write to file"
            writepickle(fragseries, writefragseries)
            print("fragseries writted to file: " + writefragseries)
        mixturesmarts = pd.DataFrame(fragseries)
        fdb = joindf(mixturesmarts, fdb)
    #%% Indexing dataframe

    if index:
        if not type(fdb) == pd.core.frame.DataFrame:
            return "Please supply fragment database to index"
        if fdb.index.name == index or fdb.index.names == index:
            print("Database is already indexed.")
        elif fdb.index.name or fdb.index.names:
            fdb.reset_index(inplace=True)
            fdb.set_index([index], inplace=True)
        else:
            fdb.set_index([index], inplace=True)
        print("Pandas fragment database indexed")
    if writefdb:
        writepickle(fdb, writefdb)
        print("Pandas fragment database written to file: " + writefdb)

    return fdb


from django import forms
from .models import Profile
import datetime

class DatePicker(forms.DateInput):
    """
    Allows users to select their date of birth with a datepicker.
    """
    input_type = 'date'

class ProfileForm(forms.ModelForm):
    """
    Form for editing the profile.
    """
    class Meta:
        model = Profile
        fields = ('date_of_birth', 'country', 'biography', 'avatar')
        widgets = {'date_of_birth': DatePicker}

    # Source for forbidding dates in future: StackOverFlow.
    # See README file under 'Sources'.
    def clean_date_of_birth(self):
        """
        This makes sure only dates in the past are accepted as date of birth.
        It gives an error message when a user enters a date in the future/
        present.
        """
        date_of_birth = self.cleaned_data['date_of_birth']
        if date_of_birth:
            if date_of_birth >= datetime.date.today():
                raise forms.ValidationError("Select a date in the past")
            return date_of_birth


import serial
import time
import string
import pynmea2
import socket

# $ sudo apt-get install python-rpi.gpio python3-rpi.gpio
import RPi.GPIO as GPIO    # Import Raspberry Pi GPIO library
from time import sleep
# Import the sleep function from the time module
GPIO.setwarnings(False)    # Ignore warning for now
GPIO.setmode(GPIO.BOARD)   # Use physical pin numbering
GPIO.setup(3, GPIO.OUT, initial=GPIO.LOW) # pin used 3-VCC 9-GND

UDP_IP = "127.0.0.1"
UDP_PORT = 5005
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

while True:
    port="/dev/ttyAMA0"
    ser=serial.Serial(port, baudrate=9600, timeout=0.5)
    dataout = pynmea2.NMEAStreamReader()
    newdata=ser.readline()
#     print(newdata[0:6].decode("utf-8") )
    try:
        if newdata[0:6].decode("utf-8") == "$GPRMC":
            newmsg=pynmea2.parse(newdata.decode("utf-8"))
            lat=newmsg.latitude
            lng=newmsg.longitude
            print(lat,lng)
            if(lat == 0 or lng == 0):
                GPIO.output(3, GPIO.HIGH)
            else:
                GPIO.output(3, GPIO.LOW)

            message = str(lat)+","+str(lng)
            sock.sendto(message.encode(), (UDP_IP, UDP_PORT))
    #         gps = "Latitude=" + str(lat) + "and Longitude=" + str(lng)
    #         print(gps)
    except:
        sock.sendto("-1,-1".encode(), (UDP_IP, UDP_PORT))


# Histograma mediante librería PIL
#Elaborado por César Alejandro Villegas Espíndola

# Se importo desde PIL imagen
from PIL import Image
from matplotlib import pyplot as plt #De igual manera se puede importar como from matplotlib.pyplot as plt
import numpy as np
import matplotlib.image as mpimg
from sklearn.metrics import homogeneity_completeness_v_measure

img = Image.open('/Users/cvillegas/workspace/Py/DIP/Ejercicio 4/Cat4.jpg')
img_gris = img.convert('L')
hist=img_gris.histogram()
plt.plot(hist, color='gray')

plt.xlabel('Intensiddad')
plt.ylabel('Cantidad de pixeles')
plt.show()

#img = Image.open('/Users/cvillegas/workspace/Py/DIP/Ejercicio 4/Cat4.jpg')
#histograma=np.zeros(256)
#print(histograma)

#Dimensiones de la imagen 2048 × 2048
img = mpimg.imread('/Users/cvillegas/workspace/Py/DIP/Ejercicio 4/Cat4.jpg')
img_gris = 0.2989*img[:,:,0] + 0.5870*img[:,:,1] + 0.1140*img[:,:,2]

# Histograma en rojo
histograma1=np.zeros(256)
#print() # Prueba de acceso al arreglo
for i in range(2048):
    for j in range(2048):
        histograma1[img[i,j,0]] = histograma1[img[i,j,0]] + 1
print(histograma1)
plt.plot(histograma1, color='red')
plt.xlabel('Intensidad')
plt.ylabel('Cantidad de pixeles')
#plt.show()

#Histograma en verde
histograma2=np.zeros(256)
#print() # Prueba de acceso al arreglo
for i in range(2048):
    for j in range(2048):
        histograma2[img[i,j,1]] = histograma2[img[i,j,1]] + 1
print(histograma2)
plt.plot(histograma2, color='green')
plt.xlabel('Intensidad')
plt.ylabel('Cantidad de pixeles')
#plt.show()

# Histograma en azul
histograma3=np.zeros(256)
#print() # Prueba de acceso al arreglo
for i in range(2048):
    for j in range(2048):
        histograma3[img[i,j,2]] = histograma3[img[i,j,2]] + 1
print(histograma3)
plt.plot(histograma3, color='blue')
plt.xlabel('Intensidad')
plt.ylabel('Cantidad de pixeles')

plt.show()


# ##### BEGIN GPL LICENSE BLOCK #####
#
# (C) 2017 MKB
#
#  This program is free software; you can redistribute it and / or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
#

# LOAD MODUL #
import bpy
from bpy import *

# LOAD UI #
from toolplus_transform.ntransform_menu      import (VIEW3D_TP_Transform_Menu)

# KEY REGISTRY #
addon_keymaps = []
keymaps_list = [
    {
        'name_view': "3D View",
        'space_type': "VIEW_3D",
        'prop_name': "VIEW3D_TP_Transform_Menu"
    },
    {
        'name_view': "Image",
        'space_type': "IMAGE_EDITOR",
        'prop_name': "VIEW3D_TP_Transform_Menu"
    },
    {
        'name_view': "Graph Editor",
        'space_type': "GRAPH_EDITOR",
        'prop_name': "VIEW3D_TP_Transform_Menu"
    },
    {
        'name_view': "Node Editor",
        'space_type': "NODE_EDITOR",
        'prop_name': "VIEW3D_TP_Transform_Menu"
    }
]

def update_menu_ntransform(self, context):
    try:
        bpy.utils.unregister_class(VIEW3D_TP_Transform_Menu)

        # Keymapping
        # remove keymaps when add-on is deactivated
        wm = bpy.context.window_manager
        if wm.keyconfigs.addon:
            for km in addon_keymaps:
                for kmi in km.keymap_items:
                    km.keymap_items.remove(kmi)
        addon_keymaps.clear()

    except:
        pass

    if context.user_preferences.addons[__package__].preferences.tab_menu_ntransform == 'menu':

        bpy.utils.register_class(VIEW3D_TP_Transform_Menu)

        # Keymapping
        kc = bpy.context.window_manager.keyconfigs.addon
        if kc:
            for keym in keymaps_list:
                km = kc.keymaps.new(name=keym['name_view'], space_type=keym['space_type'])
                kmi = km.keymap_items.new('wm.call_menu', 'X', 'PRESS', ctrl=True, shift=True)
                kmi.properties.name = keym['prop_name']
                addon_keymaps.append(km)

    if context.user_preferences.addons[__package__].preferences.tab_menu_ntransform == 'off':
        pass


from pathlib import Path
import PySimpleGUI as pg

from progature.engine.core.game.loader import GameLoader
from progature.engine.core.game.manager import GameManager
from progature.utils.loader import load_all_games
from progature.engine.structures import Pot
from progature.engine.components import Game

def main_window() -> pg.Window:
    layout = []
    game_pot = load_all_games()
    games = game_pot.items
    layout.append(
        [
            pg.Listbox(
                games,
                size=(100, 10),
                font=("Arial Bold", 14),
                expand_y=True,
                expand_x=True,
                enable_events=True,
                key="_GAMES_",
            )
        ]
    )

    layout.append(
        [
            pg.Button("Close", enable_events=True, key="_CLOSE_"),
            pg.Button("Open Game", enable_events=True, key="_GAME_OPEN_"),
        ]
    )

    window = pg.Window(
        "Progature", layout=layout, size=(500, 500), resizable=True, finalize=True
    )

    return window

def game_window(manager: GameManager) -> pg.Window:
    layout = []

    layout.append([pg.Text(f"Game name: {manager.game_name()}")])

    layout.append([pg.Text(f"Game skill: {manager.game_skill()}")])

    layout.append([pg.Text(f"Game Completion: {manager.game.is_complete}")])

    layout.append(
        [
            pg.Button("Chapters", enable_events=True, key="_CHAPTER_LIST_"),
            pg.Button("Complete", enable_events=True, key="_GAME_COMPLETE_"),
        ]
    )

    layout.append([pg.Button("Close", enable_events=True, key="_CLOSE_")])

    window = pg.Window(
        "Game", layout=layout, size=(500, 500), resizable=True, finalize=True
    )

    while True:
        event, values = window.read(timeout=10000)

        if event == pg.WIN_CLOSED or event == "_CLOSE_":
            window.close()

            break

        if event == "_GAME_COMPLETE_":
            manager.game_complete()

            continue

        if event == "_CHAPTER_LIST_":
            chapter_window(manager)

            continue

def chapter_window(manager: GameManager) -> pg.Window:
    layout = []
    chapters = manager.chapters().items
    layout.append(
        [
            pg.Listbox(
                chapters,
                size=(100, 10),
                font=("Arial Bold", 14),
                expand_y=True,
                expand_x=True,
                enable_events=True,
                key="_CHAPTERS_",
            )
        ]
    )

    layout.append(
        [
            pg.Button("Close", enable_events=True, key="_CLOSE_"),
            pg.Button("Levels", enable_events=True, key="_LEVELS_LIST_"),
            pg.Button("Complete", enable_events=True, key="_CHAPTER_COMPLETE_"),
        ]
    )

    window = pg.Window(
        "Chpater", layout=layout, size=(500, 500), resizable=True, finalize=True
    )
    # return window
    while True:
        event, values = window.read(timeout=10000)

        if event == "_CLOSE_" or event == pg.WIN_CLOSED:
            window.close()
            break

        if event == "_LEVELS_LIST_":
            if values["_CHAPTERS_"]:
                chapter = values["_CHAPTERS_"][0]
                if chapter:
                    level_window(manager, chapter.index)
            else:
                pg.popup_error("Please select a chapter")

            continue

        if event == "_CHAPTER_COMPLETE_":
            if values["_CHAPTERS_"]:
                chapter = values["_CHAPTERS_"][0]
                if chapter:
                    manager.chapter_complete(chapter.index)
                    chapters = manager.chapters().items
                    window["_CHAPTERS_"].update(chapters)
            else:
                pg.popup_error("Please select a chapter")

            continue

def level_window(manager: GameManager, chapter_index) -> pg.Window:
    layout = []
    levels = manager.chapters()[chapter_index].levels.items
    layout.append(
        [
            pg.Listbox(
                levels,
                size=(100, 10),
                font=("Arial Bold", 14),
                expand_y=True,
                expand_x=True,
                enable_events=True,
                key="_LEVELS_",
            )
        ]
    )

    layout.append(
        [
            pg.Button("Close", enable_events=True, key="_CLOSE_"),
            pg.Button("Quests", enable_events=True, key="_QUESTS_LIST_"),
            pg.Button("Complete", enable_events=True, key="_LEVEL_COMPLETE_"),
        ]
    )

    window = pg.Window(
        "Level", layout=layout, size=(500, 500), resizable=True, finalize=True
    )
    # return window
    while True:
        event, values = window.read(timeout=10000)

        if event == "_CLOSE_" or event == pg.WIN_CLOSED:
            window.close()
            break

        if event == "_QUESTS_LIST_":
            if values["_LEVELS_"]:
                level = values["_LEVELS_"][0]
                if level:
                    quest_window(manager, chapter_index, level.index)
            else:
                pg.popup_error("Please select a level")

            continue

        if event == "_LEVEL_COMPLETE_":
            if values["_LEVELS_"]:
                level = values["_LEVELS_"][0]
                if level:
                    manager.level_complete(chapter_index, level.index)
                    levels = manager.chapters()[chapter_index].levels.items
                    window["_LEVELS_"].update(levels)
            else:
                pg.popup_error("Please select a level")

            continue

def quest_window(manager: GameManager, chapter_index, level_index) -> pg.Window:
    layout = []
    quests = manager.chapters()[chapter_index].levels[level_index].quests.items
    layout.append(
        [
            pg.Listbox(
                quests,
                size=(100, 10),
                font=("Arial Bold", 14),
                expand_y=True,
                expand_x=True,
                enable_events=True,
                key="_QUESTS_",
            )
        ]
    )

    layout.append(
        [
            pg.Button("Close", enable_events=True, key="_CLOSE_"),
            pg.Button("Complete", enable_events=True, key="_QUEST_COMPLETE_"),
        ]
    )

    window = pg.Window(
        "Quest", layout=layout, size=(500, 500), resizable=True, finalize=True
    )
    # return window
    while True:
        event, values = window.read(timeout=10000)

        if event == "_CLOSE_" or event == pg.WIN_CLOSED:
            window.close()
            break

        if event == "_QUEST_COMPLETE_":
            if values["_QUESTS_"]:
                quest = values["_QUESTS_"][0]
                if quest:
                    manager.quest_complete(chapter_index, level_index, quest.index)
                    quests = (
                        manager.chapters()[chapter_index].levels[level_index].quests.items
                    )
                    window["_QUESTS_"].update(quests)
            else:
                pg.popup_error("Please select a quest")

            continue


from itertools import combinations

def main():
    N = int(input())
    names = list(combinations([input() for _ in range(N)], 2))
    connectable = 0
    for S, T in names:
        for k in range(min(len(S), len(T))):
            if S[-k-1:] == T[:k+1] or T[-k-1:] == S[:k+1]:
                connectable += 1
                break
    print(connectable)

if __name__ == "__main__":
    main()

BOJ_Contest/2023 서강대학교 청정수컵 Open Contest/D-이민희진.py


from flask import Flask
from api.routes.routes import bp

def create_app():
    app = Flask(__name__)
    app.register_blueprint(bp)
    return app

app = create_app()

@app.route('/')
def index():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)


from libser_engine.engine import accumulate, Extract
import random
import os
import  requests
import pdfplumber
import urllib3

def download_pdf_from_link(url):
    urllib3.disable_warnings()
    try:
        output_path = 'pdf/' + str(random.randint(100000, 999999))+'.pdf'
        response = requests.get(url, timeout=5, verify=False, allow_redirects=False)

        if response.status_code == 200:
            with open(output_path, 'wb') as file:
                file.write(response.content)
            return output_path

        return False
    except: return False

def extract_text_from_pdf(file_path):
    with pdfplumber.open(file_path) as pdf:
        text = ""
        for page in pdf.pages:
            text += page.extract_text()

    return text

def chunk_string(text):
    chunk_size = 350

    return [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)]

def get_pdf(subjects):

    extracter = accumulate(query=subjects, filetype='pdf')
    book_link = extracter.all()

    if book_link != []:

        download_book = download_pdf_from_link(book_link[1])
        if download_book is not False:
            doc_text = extract_text_from_pdf(download_book)
            chunked_text = chunk_string(doc_text)
            return chunked_text


"""Test for the multiple_output_pardo example."""

import logging
import re
import tempfile
import unittest

from apache_beam.examples.cookbook import multiple_output_pardo

class MultipleOutputParDo(unittest.TestCase):

  SAMPLE_TEXT = 'A whole new world\nA new fantastic point of view'
  EXPECTED_SHORT_WORDS = [('A', 2), ('new', 2), ('of', 1)]
  EXPECTED_WORDS = [
      ('whole', 1), ('world', 1), ('fantastic', 1), ('point', 1), ('view', 1)]

  def create_temp_file(self, contents):
    with tempfile.NamedTemporaryFile(delete=False) as f:
      f.write(contents)
      return f.name

  def get_wordcount_results(self, temp_path):
    results = []
    with open(temp_path) as result_file:
      for line in result_file:
        match = re.search(r'([A-Za-z]+): ([0-9]+)', line)
        if match is not None:
          results.append((match.group(1), int(match.group(2))))
    return results

  def test_multiple_output_pardo(self):
    temp_path = self.create_temp_file(self.SAMPLE_TEXT)
    result_prefix = temp_path + '.result'

    multiple_output_pardo.run([
        '--input=%s*' % temp_path,
        '--output=%s' % result_prefix]).wait_until_finish()

    expected_char_count = len(''.join(self.SAMPLE_TEXT.split('\n')))
    with open(result_prefix + '-chars-00000-of-00001') as f:
      contents = f.read()
      self.assertEqual(expected_char_count, int(contents))

    short_words = self.get_wordcount_results(
        result_prefix + '-short-words-00000-of-00001')
    self.assertEqual(sorted(short_words), sorted(self.EXPECTED_SHORT_WORDS))

    words = self.get_wordcount_results(result_prefix + '-words-00000-of-00001')
    self.assertEqual(sorted(words), sorted(self.EXPECTED_WORDS))

if __name__ == '__main__':
  logging.getLogger().setLevel(logging.INFO)
  unittest.main()


'''
Created on Jun 13, 2018

@author: cef

hp functions for the scipy and sklearn modulew
'''
#===============================================================================
# # IMPORTS --------------------------------------------------------------------
#===============================================================================
import logging, os, sys, imp, time, math, re, copy

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.stats

from collections import OrderedDict
from weakref import proxy

#===============================================================================
# import other helpers
#===============================================================================
import hp.plot2
import model.sofda.hp.basic as hp_basic
import model.sofda.hp.np as hp_np
import model.sofda.hp.oop as hp_oop
import model.sofda.hp.data as hp_data

mod_logger = logging.getLogger(__name__)

#class Fit_func(hp_data.Data_o): #thin wrapper for regressions

class Data_func(hp_data.Data_wrapper,
                hp.plot2.Plotr,
                hp_oop.Child): #for analysis by data type

    #===========================================================================
    # regressions
    #===========================================================================
    dfunc   = None #placeholder for callable function that takes a set of indepdent values and returns depdendent
    fits_od = OrderedDict() #dictionary of regression fit children

    #===========================================================================
    # fit plotting formatters
    #===========================================================================
    fit_color = 'red'
    fit_alpha = 0.6
    fit_lw = 3
    fit_linestyle = 'solid'
    fit_markersize = 0

    units = 'none'

    #===========================================================================
    # object handling overrides
    #===========================================================================

    def __init__(self, parent = None, session = None, name = 'datafunc'):

        self.name = name
        self.parent = parent
        self.session = session

        #initilzie teh baseclass
        self.label = self.name + '(%s)'%self.units

        if not parent is None:
            self.inherit_logr(parent)
        else:
            self.logger = mod_logger

    def clean_data(self, raw_data):
        'placeholder'
        return raw_data

    def calc_stats(self): #update teh stats from teh data

        data = self.data

        self.min = data.min()
        self.max = data.max()
        self.mean = data.mean()
        self.var = data.var()

        self.stat_str = 'min: %.2f, max = %.2f, mean = %.2f, var = %.2f'\
            %(self.min, self.max, self.mean, self.var)

        self.logger.debug(self.stat_str)

    def spawn_fit(self, kid_class=None, childname = None, **kwargs):

        #=======================================================================
        # defautls
        #=======================================================================
        if kid_class is None: kid_class = hp.plot.Plot_o
        if childname is None: childname = '%s %s fit'%(self.name, self.fit_name)

        #spawn the child

        child = self.spawn_child(childname = childname,
                         kid_class = kid_class, **kwargs)

        #give it the datat function
        child.dfunc = self.dfunc

        #pass down the correct attributes
        child.units =           self.units

        #give it the formatters
        child.color  =          self.fit_color
        child.alpha =           self.fit_alpha
        child.lineweight =      self.fit_lw
        child.linestype =       self.fit_linestyle
        child.markersize =      self.fit_markersize

        self.fits_od[child.name] = child

        return child

class Continuous_1D(Data_func): #set of 1d discrete data
    rv = None #scipy random variable placeholder
    fit_name = None #placeholder for the type of fit applied

    def clean_data(self, ar_raw): #clean the data
        logger = self.logger.getChild('clean_data')

        if not hp_np.isar(ar_raw):
            try:
                ar1 = ar_raw.values
                if not hp_np.isar(ar1): raise ValueError
            except:
                self.logger.error('failed to convert to array')
                raise IOError

        else: ar1 = copy.deepcopy(ar_raw) #just get a copy

        #dimensional check
        ar2 = hp_np.make_1D(ar1, logger = self.logger)

        ar3 = hp_np.dropna(ar2, logger = self.logger)

        ar_clean = ar3

        logger.debug('cleaned %s to %s'%(str(ar_raw.shape), str(ar_clean.shape)))

        return ar_clean

    def fit_norm(self): #fit and freeze a normal distribution to this
        logger = self.logger.getChild('fit_norm')
        self.fit_name = 'norm'

        logger.debug('fitting a normal disribution to data')

        #get the noral dist paramters for this data
        pars = scipy.stats.norm.fit(self.data)

        #=======================================================================
        # check the parameters
        #=======================================================================
        if np.isnan(pars[0]): raise IOError
        if not len(pars) == 2: raise IOError #normal distribution should only return 2 pars

        #freeze a distribution with these paramters
        self.rv = scipy.stats.norm(loc = pars[0], scale = pars[1])

        logger.info('froze dist with pars: %s '%str(pars))

        self.pars = pars

        return

    def fit_lognorm(self):

        logger = self.logger.getChild('fit_norm')

    def plot_pdf(self, ax=None, title = None,wtf=None, annot_f=False,
                 color = 'red', alpha = 0.6, lw = 3, label = None,
                 outpath = None): #cretate a plot of the pdf
        """
        Ive createdd a separate plot frunctio n(from hp.plot) as this is a curve fit to the data... not the data
        """
        #=======================================================================
        # defautls
        #=======================================================================
        if self.rv is None: raise IOError
        if wtf is None: wtf = self.session._write_figs
        if label is None: label = self.fit_name + ' pdf'

        rv = self.rv

        logger = self.logger.getChild('plot_pdf')
        logger.debug('plotting with ax = \'%s\''%ax)
        #=======================================================================
        # setup plot
        #=======================================================================
        if ax is None:
            plt.close()
            fig = plt.figure(1)
            fig.set_size_inches(self.figsize)
            ax = fig.add_subplot(111)

            if title is None: title = self.name + ' '+ self.fit_name + ' pdf plot'

            ax.set_title(title)
            ax.set_ylabel('likelihood')
            ax.set_xlabel(self.label)

        else:
            fig = ax.figure
            xmin, xmax = ax.get_xlim()

        #=======================================================================
        # data setup
        #=======================================================================
        x = np.linspace(rv.ppf(0.001), rv.ppf(0.999), 200) #dummy x values for plotting

        #=======================================================================
        # plot
        #=======================================================================
        pline = ax.plot(x, rv.pdf(x),
                        lw = lw, alpha = alpha, label = label, color=color)

        if annot_f:
            max1ind = np.argmax(rv.pdf(x)) #indicies of first occurance of the max value
            max_x = x[max1ind]
            """
            boolmax = max(rv.pdf(x)) == rv.pdf(x)
            boolmax = x[np.argmax(rv.pdf(x))]
            try:
                max_x = float(x[boolmax])
            except:
                max_x = 0.00"""

            annot = '%s dist \n'%self.rv.dist.name +\
                    r'$\mu=%.2f,\ \sigma=%.2f$, max=%.2f'%(self.rv.kwds['loc'], self.rv.kwds['scale'], max_x)

            #add the shape parameter for 3 par functions
            if len(self.rv.args) > 0:
                annot = annot + '\n shape = %.2f'%self.rv.args[0]

            #=======================================================================
            # Add text string 'annot' to lower left of plot
            #=======================================================================
            xmin, xmax = ax.get_xlim()
            ymin, ymax = ax.get_ylim()

            x_text = xmin + (xmax - xmin)*.5 # 1/10 to the right of the left axis
            y_text = ymin + (ymax - ymin)*.5 #1/10 above the bottom axis
            anno_obj = ax.text(x_text, y_text, annot)

        logger.debug('finished')

        if wtf:
            try:
                self.save_fig(fig, outpath=outpath)
            except:
                logger.warning('failed to safe figure')

        """
        plt.show()
        """

        return ax

    def plot_fit(self, bins = None, #plot the fit curve adn the data
                 ax = None, title=None, wtf=None,
                 **kwargs):

        #=======================================================================
        # defautls
        #=======================================================================
        if self.rv is None: raise IOError
        if wtf is None: wtf = self.session._write_figs
        rv = self.rv

        logger = self.logger.getChild('plot_fit')

        #=======================================================================
        # setup plot
        #=======================================================================
        if ax is None:
            plt.close()
            fig = plt.figure(1)
            fig.set_size_inches(self._figsize)
            ax = fig.add_subplot(111)

            if title is None: title = self.name + ' '+ self.fit_name + ' fit plot'

            ax.set_title(title)
            ax.set_ylabel('likelihood')
            ax.set_xlabel(self.label)

        else:
            fig = ax.figure
            xmin, xmax = ax.get_xlim()

        #=======================================================================
        # setup annotation
        #=======================================================================
        annot =  r'n = %i, $\mu=%.2f,\ \sigma=%.2f$'%(len(self.data), self.mean, self.var)

        #=======================================================================
        # plot the data
        #=======================================================================
        ax = self.plot_data_hist(normed=True, bins = bins,
                                 ax=ax, title=title, wtf=False, annot = annot,
                                 **kwargs)

        ax = self.plot_pdf(ax=ax, title = title, wtf=False)

        #=======================================================================
        # post formatting
        #=======================================================================

        if wtf:
            flag = hp.plot.save_fig(self, fig, dpi = self.dpi, legon = True)
            if not flag: raise IOError

        logger.info('finished')

class Boolean_1D(Data_func): #set of 1d discrete data
    reg = None #LogisticRegression from sklearn
    fit_name = None #placeholder for the type of fit applied

    data2_o = None #partenr data to compare against

    data_int = None #boolean data converted to integers

    logit_solvers = ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga']

    def data_setup(self, data2_o): #basic cleaning adn data setup

        if data2_o is None: data2_o = self.data2_o
        if data2_o is None: raise IOError

        self.data2_o = data2_o

        if self.data_int is None: self.bool_to_num()

        #=======================================================================
        # combine data into frame
        #=======================================================================
        df1 = pd.DataFrame(index = self.data_int.index)

        df1['bool'] = self.data
        df1['int'] = self.data_int.values
        df1['data2'] = data2_o.data

        self.df_raw = df1 #attach this

        #=======================================================================
        # clean
        #=======================================================================
        df2 = df1.dropna(axis='index', how='any')

        self.df_clean = df2
        #=======================================================================
        # data setup
        #=======================================================================

        self.dep_ar = df2.loc[:,'int'].astype(np.int).values
        self.ind_ar = df2.loc[:,'data2'].astype(np.int).values

        return

    def bool_to_num(self): #convert the boolean data to numeric

        if not hp_pd.isser(self.data): raise IOError

        self.data = self.data.astype(np.bool) #convert the original data to boolean

        self.data_int = self.data.astype(np.int)

    def fit_LogisticRegression(self, data2_o=None, target = 'int',
                   solver = 'newton-cg', verbose=2): #fit the data to a logit model
        """
        #=======================================================================
        # INPUTS
        #=======================================================================
        target: what header in the clean_df to use as the target array
        """

        #=======================================================================
        # set defaults
        #=======================================================================
        logger = self.logger.getChild('fit_LogisticRegression')

        self.data_setup(data2_o)
        #if not dep_ar.shape == ind_ar.shape: raise IOError
        df = self.df_clean

        #=======================================================================
        # build the model
        #=======================================================================
        import sklearn.linear_model

        #get teh train/target data from the clean frame
        train_ar = df['data2'].values.reshape(-1,1)
        target_ar = df[target].values.reshape(-1,1)

        #initilze teh model
        reg = sklearn.linear_model.LogisticRegression(solver = solver , verbose=verbose)

        #fit to the data
        reg = reg.fit(train_ar, target_ar)

        self.reg = reg
        self.fit_name = 'LogisticRegression'
        #=======================================================================
        # get equilvanet pars
        #=======================================================================
        'doin gthis here for plotting annot'
        self.loc = self.data2_o.data.min() #this looks good. closest yet
        self.scale = 1.0/float(self.reg.coef_) #looks pretty good

        #=======================================================================
        # create a new child for this
        #=======================================================================
        child = self.spawn_fit()

        child.reg   = self.reg #give it the regression

        child.data2_o = self.data2_o #give it the indepdendnet dato

        #=======================================================================
        # wrap up and report
        #=======================================================================
        logger.info('finished with coef_  = %.2f,  intercept_  = %.2f, n_iter_  = %.2f'
                    %(reg.coef_ , reg.intercept_ , reg.n_iter_ ))

        return child

    def try_all_logit_solvers(self): #plot results for all solver methods
        logger = self.logger.getChild('plot_asnum')

        success = []

        for solver in self.logit_solvers:
            try:
                reg = self.fit_LogisticRegression(solver = solver, verbose=0)

                title = self.name + ' logit on \'%s\' with \'%s\''%(self.data2_o.name, solver)

                ax = self.plot_fit(title = title) #make the plot

                success.append(solver)

            except:
                logger.error('failed on \'%s\''%solver)

        logger.info('finished with %i (of %i) successful solvers: %s'%(len(success), len(self.logit_solvers), success))

    def dfunc(self, x):

        y = self.reg.predict_proba(x.reshape(-1,1))[:,1]

        if not x.shape == y.shape:
            raise IOError

        return y

    def build_scipy_equil(self, type = 'cdf'): #build an equilvalent scipy logistic (or Sech-squared) continuous random variable.
        """
        because we need a more sophisiticated LinearMOdel to fit to the boolean data
            we use Sklearn to train the model

        However, there doesn't seem to be a good way to incorporate a simply parametersized Sklearn model into ABMRI

        SOLUTION:
            use the coefficents from teh Sklearn training to paramterize a simple scipy logistc curve

        """
        logger = self.logger.getChild('plot_asnum')

        if self.reg is None: raise IOError

        #=======================================================================
        # get teh equilvanet parameters
        #=======================================================================
        #=======================================================================
        # loc = self.data2_o.data.min() #this looks good. closest yet
        # scale = 1.0/float(self.reg.coef_) #looks pretty good
        #=======================================================================

        'pull from teh fit_LogisticRegression'
        loc, scale = self.loc, self.scale #attach these

        #get a frozen func
        self.rv = scipy.stats.logistic(loc = loc, scale = scale)

        logger.info('parameterized a scipy.stats.logistic with loc = %.2f and scale = %.2f'%(loc, scale))

        #=======================================================================
        # create a new child for this
        #=======================================================================
        childname = '%s %s fit'%(self.name, 'scipy.stats.logistic')
        child = self.spawn_fit(childname = childname)

        #attach the attributes
        child.data2_o = self.data2_o #give it the indepdendnet dato
        child.rv = self.rv #attach teh frozen curve
        #=======================================================================
        # child.loc   = self.loc
        # child.scale = self.scale
        #=======================================================================

        #=======================================================================
        # attach the function
        #=======================================================================

        def cdf(x):
            'no need to pass loc and scale as the curve is frozen'
            return self.rv.cdf(x)

        def pdf(x):
            return self.rv.pdf(x)

        if type == 'cdf': child.dfunc = cdf
        elif type == 'pdf': child.dfunc = pdf

        #=======================================================================
        # attach the formatter overrides
        #=======================================================================
        'most the formatters are applied during spawn_fit'
        child.linestyle = 'dashed'

        return child

    def plot_asnum(self, data2_o=None,
                   title = None,
                   ax=None, wtf=None, **kwargs): #plot the raw data converting bools to integers

        logger = self.logger.getChild('plot_asnum')

        if data2_o is None: data2_o = self.data2_o
        if data2_o is None: raise IOError

        #=======================================================================
        # data setup
        #=======================================================================
        if self.data_int is None: self.bool_to_num()
        dep_ar = self.data_int

        #=======================================================================
        # formatting
        #=======================================================================
        if title is None: title ='%s vs %s plot'%(self.name, data2_o.name)

        #=======================================================================
        # send for plotting
        #=======================================================================

        ax = self.parent.plot(self, indp_dato = data2_o, dep_ar = dep_ar,
                         linewidth = 0,
                         title = title, ax = ax, wtf=wtf, **kwargs)

        """
        data2_o.name
        plt.show()
        """

        return ax

    def plot_probs(self, data2_o=None, fit_o = None,
                    title = None, ax=None, wtf=None, **kwargs): #plot the raw data converting bools to integers

        logger = self.logger.getChild('plot_probs')

        if data2_o is None: data2_o = self.data2_o
        if data2_o is None: raise IOError

        #get the fitter child
        if fit_o is None:
            if not len(self.fits_od) == 1:
                logger.warning('found more than one fit. taking first')
            fit_o = list(self.fits_od.values())[0]

        #=======================================================================
        # data setup
        #=======================================================================
        x = np.linspace(data2_o.data.min(), data2_o.data.max(), 100) #dummy x values for plotting

        #=======================================================================
        # formatting
        #=======================================================================
        if title is None: title ='%s %s prob plot'%(self.name, self.fit_name)

        logger.debug('%s with x: %s'%(title, x.shape))

        ax = self.plot(fit_o, indp_dato = data2_o, indp_ar = x,
                        title = title,
                        ax = ax, wtf=wtf, **kwargs)

        return ax

    def plot_fit(self, title = None, wtf=None,ax = None):

        #=======================================================================
        # defauilts
        #=======================================================================
        logger = self.logger.getChild('fit_LogisticRegression')
        if wtf is None: wtf = self.session._write_figs

        #=======================================================================
        # formatters
        #=======================================================================
        if title is None: title = 'plot %s fit of \'%s\'  to \'%s\''%(self.fit_name, self.name,self.data2_o.name)

        annot = 'LogisticRegression coefs: \nn_iter_ = %i, coef_ = %.2e, intercept_ = %.2f, xmin = %.2f \n' \
            %(self.reg.n_iter_, self.reg.coef_, self.reg.intercept_, self.data2_o.data.min()) \
            + 'loc = %.2f, scale = %.2f'%(self.loc, self.scale)

        #=======================================================================
        # plot the raw data
        #=======================================================================
        ax = self.plot_asnum(ax = None, wtf = False, title = title, annot = annot)
        ax = self.plot_probs(ax = ax, wtf = False)

        #=======================================================================
        # setup the synthetic data
        #=======================================================================
        plt.legend() #turn teh legend on

        """
        plt.show()

        reg = self.reg
        from scipy.stats import logistic
        x = np.linspace(self.data2_o.data.min(), self.data2_o.data.max(), 100) #dummy x values for plotting

        import math.exp

        for value in [reg.n_iter_, reg.coef_, reg.intercept_]:
            value = float(value)
            #print value
            print 1.0/value

            print math.exp(value)
            print 1.0/math.exp(value)

            print math.exp(-value)
            print 1.0/math.exp(-value)

            print math.exp(1.0/value)
            print math.exp(-1.0/value)

            try:
                1/print math.log(value)
                1/print math.log(1/value)

            except:
                print ('failed on %.2f'%value)
            pass

        int()
        1.0/int(reg.n_iter_)

        loc =
        scale = float(self.reg.coef_) #way too low

        reg.get_params() #nothing useful

        scale = -float(self.reg.intercept_) #bad

        float(self.reg.intercept_) #bad

        ax.plot(x,ylogistic ,'b-', lw=1, alpha=0.6, label='logistic pdf')

        self.reg.coef_
        self.reg.intercept_

                logger.info('finished with coef_  = %.2f,  intercept_  = %.2f, n_iter_  = %.2f'
                    %(reg ,  , reg.n_iter_ ))

        """

        if wtf:
            fig = ax.figure
            flag = hp.plot.save_fig(self, fig, dpi = self.dpi)
            if not flag: raise IOError

        return ax


import pymongo
from flask import jsonify
import datetime

# Initializing Date
date = datetime.datetime.today()

# Database
client = pymongo.MongoClient(
    "localhost", port=27017)

#Rules Database
rulesDB = client.list_database_names()
rulesDB = client["Rules"]

# Server Database
dataDB = client["ServerData"]
Datacollections = [collection
                   for collection in dataDB.list_collection_names()]

# Class Defination for Rule CRUD Operation
class Rules:

    def __init__(self, group=None, ruleName=None, field=None, criteria=None, value=None):
        self.Group = group
        self.Rule = ruleName
        self.Field = field
        self.Criteria = criteria
        self.Value = value

    def createGroup(self, group):
        try:
            rulesDB['Groups'].insert_one({'_id':group})
            return {'Response': True}
        except Exception as e:
            return {'Response': False, 'Reasons': str(e)}

    def getGroups(self):
        try:
            resultSet = rulesDB['Groups'].find()
            groups = {group['_id']: group['_id'] for group in resultSet}
            return {'Groups':groups}
        except Exception as e:
            return {'Response': False, 'Reasons': str(e)}

    def editGroups(self, groupName, newName):
        if groupName in self.getGroups()['Groups'].values():
            try:
                self.collection = rulesDB["Groups"]
                self.collection.delete_one({"_id": groupName})
                self.createGroup(newName)
                self.rules= rulesDB["Rules"]
                self.rules.update_many({"Group":groupName},{'$set':{'Group':newName}})
                return {"Response":True}
            except Exception:
                return {"Response":False}

    def deleteGroup(self, groupName):
        if groupName in self.getGroups()['Groups'].values():
            try:
                self.collection = rulesDB["Groups"]
                self.collection.delete_one({"_id": groupName})
                return {"Response":True}
            except Exception as e:
                return {'Response': False, 'Reasons': str(e)}
        else:
            return {"Response":False}

    def get(self):
        try:
            collection = rulesDB["Rules"]
            groups = set(document["Group"] for document in collection.find())
            data = {group: {doc['_id']: doc for doc in collection.find(
                {"Group": group})} for group in groups}
            return data
        except Exception:
            return False

    def post(self):
        self.collection = rulesDB["Rules"]
        try:
            self.collection.insert_one({
                "_id": self.Rule,
                "Group": self.Group,
                "Field": self.Field,
                "Criteria": self.Criteria,
                "Value": self.Value
            })
            return True
        except Exception:
            return False

    def update(self):
        try:
            self.collection = rulesDB["Rules"]
            self.collection.update_one({"_id": self.Rule}, {
                "$set": {"Group": self.Group, "Field": self.Field,
                         "Criteria": self.Criteria, "Value": self.Value}})
            return True
        except Exception:
            return False

    def delete(self):
        try:
            self.collection = rulesDB["Rules"]
            result = self.collection.delete_one({"_id": self.Rule})
            return result.deleted_count
        except Exception:
            return False

    def apply(self, days=1):
        previousDates = []
        today = date
        for day in range(days):
            previousDates.append((str(today).split(' ')[0]))
            today = today - datetime.timedelta(days=1)
        rules = self.get()
        groups = list(rules.keys())
        dataWithDate = {}
        for day in previousDates:
            data = {}
            for grp in groups:
                for rule in list(rules[grp].keys()):
                    targetField = rules[grp][rule]["Field"].split(".")
                    Value = rules[grp][rule]["Value"]
                    Criteria = rules[grp][rule]["Criteria"]
                    target = targetField[0]
                    if target in Datacollections:
                        try:
                            db = dataDB[target]
                            if Criteria.lower() == "gt":
                                matched = db.find(
                                    {targetField[1]: {'$gt': Value}})
                                Rule = rule + \
                                    f"-> {rules[grp][rule]['Field']} > {Value}"
                            elif Criteria.lower() == "gte":
                                matched = db.find(
                                    {targetField[1]: {'$gte': Value}})
                                Rule = rule + \
                                    f"-> {rules[grp][rule]['Field']} >= {Value}"
                            elif Criteria.lower() == "lt":
                                matched = db.find(
                                    {targetField[1]: {'$lt': Value}})
                                Rule = rule + \
                                    f"-> {rules[grp][rule]['Field']} < {Value}"
                            elif Criteria.lower() == "lte":
                                matched = db.find(
                                    {targetField[1]: {'$lte': Value}})
                                Rule = rule + \
                                    f"-> {rules[grp][rule]['Field']} <= {Value}"
                            elif Criteria.lower() == "eq":
                                matched = db.find(
                                    {targetField[1]: {'$eq': Value}})
                                Rule = rule + \
                                    f"-> {rules[grp][rule]['Field']} == {Value}"
                            elif Criteria.lower() == "neq":
                                matched = db.find(
                                    {targetField[1]: {'$not':{'$eq': Value}}})
                                Rule = rule + \
                                    f"-> {rules[grp][rule]['Field']} != {Value}"
                            else:
                                return False
                            passedData = {}
                            for d in matched:
                                check = d['Date'].split(' ')[0]
                                if day == check:
                                    passedData[d['ApplicationName']] = {d['TierName']: {
                                        "Date": d['Date'], targetField[1]: d[targetField[1]]}}
                            data[Rule] = passedData

                        except Exception:
                            return False
            dataWithDate[day] = data
        return dataWithDate


#!/usr/bin/env python
#coding:utf-8
import cv2
import numpy as np

def readImg(path):
	return cv2.imread(path)

def grayImg(img):
	return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 图片宽度对应数组列数，高度对应数组行数
def getImgSize(img):
	return (img.shape[1], img.shape[0])

def swap(a, b):
	return b, a

# 根据宽高创建纯白图，宽高对应数组的列数和行数
def createWhiteImg(size):
	return np.uint8(np.ones((size[1], size[0])) * 255)

def createBlackImg(size):
	return np.uint8(np.zeros((size[1], size[0])))

def findContours(grayImg, mode = cv2.RETR_EXTERNAL, method = cv2.CHAIN_APPROX_SIMPLE):
	_, contours, hierarchy = cv2.findContours(grayImg, mode, method)
	return contours, hierarchy

def drawContours(img, contours, color = (0, 0, 0), thickness = -1):
	cv2.drawContours(img, contours, -1, color, thickness)
	return img

def binaryInv(grayImg):
	thresh, bimg = cv2.threshold(grayImg, 0, 255, cv2.THRESH_OTSU)
	return bimg

def binaryThresh(grayImg, thresh1 = 0, thresh2 = 255):
	thresh, bimg = cv2.threshold(grayImg, thresh1, thresh2, cv2.THRESH_BINARY)
	return bimg

def getKernel(size):
	kernel = np.uint8(np.zeros(size))
	kw, kh = kernel.shape
	for x in range(kh):
		kernel[x, kw / 2] = 1
	for x in range(kw):
		kernel[kh / 2, x] = 1
	return kernel

# 将矩形定点按top-left, top-right, bottom-right, bottom-left顺序排列
def order_points(pts):
	rect = np.zeros((4, 2), dtype = "float32")
	# the top-left point will have the smallest sum, whereas
	# the bottom-right point will have the largest sum
	s = pts.sum(axis = 1)
	rect[0] = pts[np.argmin(s)]
	rect[2] = pts[np.argmax(s)]
	# top-right point will have the smallest difference,
	# whereas the bottom-left will have the largest difference
	diff = np.diff(pts, axis = 1)
	rect[1] = pts[np.argmin(diff)]
	rect[3] = pts[np.argmax(diff)]
	return rect

def erosion(grayImg, kernel = getKernel((4, 4)), iterations = 1):
	return cv2.erode(grayImg, kernel, iterations = iterations)

def dilation(grayImg, kernel = getKernel((4, 4)), iterations = 1):
	return cv2.dilate(grayImg, kernel, iterations = iterations)

def sobel(grayImg):
	gradX = cv2.Sobel(grayImg, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1)
	gradY = cv2.Sobel(grayImg, ddepth=cv2.CV_32F, dx=0, dy=1, ksize=-1)
	gradient = cv2.subtract(gradX, gradY)
	gradient = cv2.convertScaleAbs(gradient)
	return gradient

def showImg(*imgs):
	index = 0
	for img in imgs:
		cv2.imshow("img" + str(index), img)
		index += 1
	cv2.waitKey(10)
	cv2.destroyAllWindows()

def showImgSingle(img, wname = "img"):
	cv2.imshow(wname, img)
	cv2.waitKey(0)
	cv2.destroyAllWindows()

# 获取轮廓的包围矩形
def getBoundingRect(contours):
	boundingRect = []
	for contour in contours:
		boundingRect.append(cv2.boundingRect(contour))
	return boundingRect

# lines[ line[ subline[]], ...]
def drawLines(img, lines, color = (0, 0, 0), thickness = 1):
	for line in lines:
		for subline in line:
			cv2.line(img, (subline[0], subline[1]), (subline[2], subline[3]), color, thickness)

# boundingBox = [x, y, w, h] -> [x1, y1, x2, y2]
def convertBoundingBoxToBox(boundingBox):
	realBox = []
	for box in boundingBox:
		realBox.append([box[0], box[1], box[0] + box[2], box[1] + box[3]])
	return realBox

def drawBox(img, boxes, color = (0, 0, 0), thickness = 1):
	for box in boxes:
		cv2.rectangle(img, (box[0], box[1]), (box[2], box[3]), color, thickness)

def drawRectP4(img, points, color = (0, 0, 0), thickness = 1):
	lines = [[points[0], points[1]], [points[1], points[2]], [points[2], points[3]], [points[3], points[0]]]
	for line in lines:
		cv2.line(img, (line[0][0], line[0][1]), (line[1][0], line[1][1]), color, thickness)

# 从极坐标获取线段端点
def getLinesFromPolarCoord(polarLines, thresh = 4000):
	lines = []
	for rhoThetaPack in polarLines:
		subline = []
		for rho,theta in rhoThetaPack:
		    a = np.cos(theta)
		    b = np.sin(theta)
		    x0 = a * rho
		    y0 = b * rho
		    x1 = int(x0 + thresh * (-b))
		    y1 = int(y0 + thresh * (a))
		    x2 = int(x0 - thresh * (-b))
		    y2 = int(y0 - thresh * (a))
		    subline.append([x1, y1, x2, y2])
		lines.append(subline)
	return lines

# 找出面积大于一定阈值的矩形
def findMainBox(boundingBox, size, thresh = 2):
	boxes = []
	S = size[0] * size[1]
	for box in boundingBox:
		for x1, y1, x2, y2 in box:
			w = np.abs(x2 - x1)
			h = np.abs(y2 - y1)
			s = w * h
			if s > S / thresh:
				boxes.append(box)
	return boxes

# 将短线段加长, d == 0(垂直)
def expandLine(lines, thresh):
	newLines = []
	if lines is None:
		return newLines
	for sublines in lines:
		newSublines = []
		for x1, y1, x2, y2 in sublines:
			if x1 > x2:
				x1, x2 = swap(x1, x2)
				y1, y2 = swap(y1, y2)
			newLine = [0] * 4
			d1 = float(x2 - x1)
			d2 = float(y2 - y1)
			# 垂直
			if d1 >= -5 and d1 <= 5:
				newLine = [x1, y1, x2, y2]
				f = newLine[1] - newLine[3]
				f = int(float(f) / np.abs(f))
				newLine[1] = newLine[1] + f * thresh
				newLine[3] = newLine[3] - f * thresh
			else:
				deltaY1 = d2 / d1 * x1
				deltaY2 = d2 / d1 * thresh
				newLine[0] = 0
				newLine[1] = int(y1 - deltaY1)
				newLine[2] = int(x2 + thresh)
				newLine[3] = int(y2 + deltaY2)
			newSublines.append(newLine)
		newLines.append(newSublines)
	return newLines

# 保留一定角度的直线, 自动取角度绝对值
def remainLine(lines, angles = [[0, 10], [80, 90]]):
	if lines is None:
		return lines
	PI_DEGREE = 180.0 / np.pi
	newLines = []
	for sublines in lines:
		newSublines = []
		for x1, y1, x2, y2 in sublines:
			newLine = [x1, y1, x2, y2]
			if x1 - x2 == 0:
				theta = 90
			else:
				theta = np.arctan(np.abs(float(y1 - y2) / (x1 - x2))) * PI_DEGREE
			for angle in angles:
				if theta >= angle[0] and theta <= angle[1]:
					newSublines.append(newLine)
					break
		if len(newSublines) != 0:
			newLines.append(newSublines)
	return newLines

# 计算两直线夹角
def computeLineAngle(line1, line2):
	dx1 = line1[2] - line1[0]
	dx2 = line2[2] - line2[0]
	dy1 = line1[3] - line1[1]
	dy2 = line2[3] - line2[1]
	dx1 /= 100
	dx2 /= 100
	dy1 /= 100
	dy2 /= 100
	theta =  np.arccos((dx1 * dx2 + dy1 * dy2) / np.sqrt((dx1 * dx1 + dy1 * dy1) * (dx2 * dx2 + dy2 * dy2)))
	theta = theta * 180.0 / np.pi
	return theta

# 两条直线交点
# segment==True, 求线段交点, 允许thresh大小的误差
def computeIntersect(a, b, segment = False, thresh = 100):
	x1 = a[0]; y1 = a[1]; x2 = a[2]; y2 = a[3]; x3 = b[0]; y3 = b[1]; x4 = b[2]; y4 = b[3]
	d = float((x1 - x2) * (y3 - y4)) - (y1 - y2) * (x3 - x4)
	if d != 0:
		pt = [0, 0]
		pt[0] = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d
		pt[1] = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d
		if segment:
			x = [x1, x2, x3, x4]
			y = [y1, y2, y3, y4]
			x.remove(min(x))
			x.remove(max(x))
			y.remove(min(y))
			y.remove(max(y))
			x.sort()
			y.sort()
			x[0] -= thresh
			x[1] += thresh
			y[0] -= thresh
			y[1] += thresh
			if (pt[0] < x[0] or pt[0] > x[1]) or (pt[1] < y[0] or pt[1] > y[1]):
				pt = [-1, -1]
		return pt
	else:
		return [-1, -1]

# 线段交点
# thresh1可以调整交点超出边界的程度
def getIntersectPoints(lines, filterSize = None, thresh1 = 0):
	length = len(lines)
	points = []
	for i in range(length):
		for n in range(i, length):
			# 防止近似平行线相交
			angle = computeLineAngle(lines[i][0], lines[n][0])
			thresh0 = 3
			if angle <= thresh0 or angle >= 180 - thresh0:
				continue
			point = computeIntersect(lines[i][0], lines[n][0], True)
			if point[0] != -1:
				if filterSize:
					if point[0] >= 0 - thresh1 and point[0] <= filterSize[0] + thresh1 and point[1] >= 0 - thresh1 and point[1] <= filterSize[1] + thresh1:
						points.append(point)
				else:
					points.append(point)
	return points

# 边角点
def getBoundingCornerPoints(points, size):
	points = np.array(points)
	centerP = np.array([size[0] / 2.0, size[1] / 2.0])
	topLeftPoint = [[0, 0], 0]
	topRightPoint = [[size[0], 0], 0]
	bottomRightPoint = [[size[0], size[1]], 0]
	bottomLeftPoint = [[0, size[1]], 0]
	for point in points:
		length = np.sum((point - centerP) * (point - centerP))
		# topLeft
		if point[0] <= centerP[0] and point[1] <= centerP[1]:
			if length > topLeftPoint[1]:
				topLeftPoint[0] = point
				topLeftPoint[1] = length
		# topRight
		elif point[0] >= centerP[0] and point[1] <= centerP[1]:
			if length > topRightPoint[1]:
				topRightPoint[0] = point
				topRightPoint[1] = length
		# bottomRight
		elif point[0] >= centerP[0] and point[1] >= centerP[1]:
			if length > bottomRightPoint[1]:
				bottomRightPoint[0] = point
				bottomRightPoint[1] = length
		# bottomLeft
		else:
			if length > bottomLeftPoint[1]:
				bottomLeftPoint[0] = point
				bottomLeftPoint[1] = length

	return np.array([topLeftPoint[0], topRightPoint[0], bottomRightPoint[0], bottomLeftPoint[0]], dtype = np.float32)

# 求众数, 允许+-thresh误差, 若merge != 0, 则分别把+-前merge个连续符号的统计结果合并, 并且只返回合并后的+-两个结果
# [(elem, [count, [indexes]]), ...]
# merge: [([elem1, elem2, ...], [count, [indexes]]), ...]
def findMode(data, thresh = 1, merge = 2):
	# {[count, [indexes]]}
	elemMap = {}
	for index, elem in enumerate(data):
		finded = False
		if elem in elemMap:
			elemMap[elem][0] += 1
			elemMap[elem][1].append(index)
			finded = True
		elif thresh != 0:
			if elem + thresh in elemMap:
				elemMap[elem + thresh][0] += 1
				elemMap[elem + thresh][1].append(index)
				finded = True
			if elem - thresh in elemMap:
				elemMap[elem - thresh][0] += 1
				elemMap[elem - thresh][1].append(index)
				finded = True
		if not finded:
			elemMap[elem] = [1, [index]]
	# [(elem, [count, [indexes]])]
	elemMap = sorted(elemMap.items(), key=lambda e:e[1][0], reverse=True)
	if merge != 0:
		if merge < 0:
			merge = max(0, len(elemMap) + merge)
		newElemMap = [([], [0, []]), ([], [0, []])]
		for index, modeR in enumerate(elemMap):
			if modeR[0] >= 0:
				newElemMap[0][0].append(modeR[0])
				newElemMap[0][1][0] += modeR[1][0]
				newElemMap[0][1][1].extend(modeR[1][1])
			else:
				newElemMap[1][0].append(modeR[0])
				newElemMap[1][1][0] += modeR[1][0]
				newElemMap[1][1][1].extend(modeR[1][1])
			if index >= merge:
				break
		elemMap = newElemMap
	return elemMap

# 直线倾角
def findAngle(lines):
	angles = []
	if lines is None:
		return angles
	for sublines in lines:
		for x1, y1, x2, y2 in sublines:
			length = np.sqrt((y1 - y2) * (y1 - y2) + (x1 - x2) * (x1 - x2))
			angle = np.arcsin((y1 - y2) / length) * 180 / np.pi
			angles.append(np.round(angle))
	return angles

# main function
def houghRectify(img):
	img = cv2.resize(img, (600, 700))
	originalImg = img
	# 灰度图
	img = grayImg(img)
	imgSize = getImgSize(img)
	minW = min(img.shape)
	maxW = max(img.shape)
	img = cv2.blur(img, (4, 4))
	# 画出轮廓
	edges = cv2.Canny(img, 10, 50, apertureSize = 3)
	edges0 = dilation(edges, iterations = 2)
	edges = cv2.Canny(edges0, 10, 50, apertureSize = 3)
	lines = np.array(cv2.HoughLinesP(edges, 1, np.pi / 360, 50, minLineLength = 100, maxLineGap = 50))
	# 求倾角众数
	mode = findMode(findAngle(lines), 7, 9)
	mode[0][1][1].extend(mode[1][1][1])
	lines = lines[mode[0][1][1]]
	# 计算交点
	intersectPoints = getIntersectPoints(lines, imgSize)
	cornerPoints = getBoundingCornerPoints(intersectPoints, imgSize)
	# 微调, 收缩边框
	thresh = 190.0
	threshZero = 1.0
	cornerPoints[0] += minW / thresh * threshZero
	cornerPoints[1][0] -= minW / thresh * threshZero
	cornerPoints[1][1] += minW / thresh * threshZero
	cornerPoints[2] -= minW / thresh * threshZero
	cornerPoints[3][0] += minW / thresh * threshZero
	cornerPoints[3][1] -= minW / thresh * threshZero
	transPs = np.array([[0, 0], [imgSize[0], 0], [imgSize[0], imgSize[1]], [0, imgSize[1]]], dtype = np.float32)
	transform = cv2.getPerspectiveTransform(cornerPoints, transPs)
	# 画出前景
	wimg3 = cv2.warpPerspective(src = originalImg, M = transform, dsize =  imgSize)
	return wimg3

# 从填涂区域包围框中选出正确的填涂框, 并计算中心坐标
# hierarchy结构：(前一个框序号，后一个框序号，第一个子框序号，父框序号)
# 序号为-1表示无
# 返回所有填涂区域的中心坐标和区域框
def findAnswerBoxCenter(rectangles, hierarchy):
	hierarchy = hierarchy[0]
	ansBoxCenter = []
	topBoundingBox = []
	for index, item in enumerate(hierarchy):
		if item[3] != -1:
			x1, y1, w, h = rectangles[index]
			# centerX， centerY，S
			ansBoxCenter.append(((x1 + x1 + w) / 2, (y1 + y1 + h) / 2, w * h, w, h))
		elif rectangles[index][2] != -1:
			topBoundingBox = rectangles[index]
	return ansBoxCenter, topBoundingBox

# 通过填涂区域坐标确定填涂的答案
# ansBoxCenter为填涂区域中心坐标
# questionCount为竖排的题目数
# answerCount为每个题目备选答案数目
# W,H为作答区域的宽高
# 返回答题结果，questionCount * answerCount的二位数组，置1位代表填涂，置0位代表未填涂
def determineAnswer(ansBoxCenter, questionCount, answerCount, W, H, restrictArea = True, restrictAreaThresh = 0.2):
	baseX = baseY = 0
	stepX = int(float(W) / questionCount)
	stepY = int(float(H) / answerCount)
	standardS = stepX * stepY
	answerMap = np.zeros((questionCount, answerCount))
	for anSenter in ansBoxCenter:
		ansX, ansY, S = anSenter
		# 填涂面积至少为判定方格面积的20%
		if restrictArea and S <= restrictAreaThresh * standardS:
			continue
		ansIndex = int((ansY - baseY) / stepY)
		quesIndex = int((ansX - baseX) / stepX)
		# 位于边线上，按最后一题或选项算
		if quesIndex < questionCount and ansIndex < answerCount:
			answerMap[quesIndex][ansIndex] = 1
		elif ansIndex == answerCount:
			answerMap[quesIndex][answerCount - 1] = 1
		elif quesIndex == questionCount:
			answerMap[questionCount - 1][ansIndex] = 1
	return answerMap

# 横跨整个答题卡的长条形作答区域，包含多个题组
# 通过填涂区域坐标确定填涂的答案
# ansBoxCenter为填涂区域中心坐标
# questionCount为[!每个!]题组的竖排的题目数
# answerCount为每个[!每个!]题组的题目备选答案数目
# groupCount为题组个数
# W,H为作答区域的宽高
# spaceStep为每个题组的间隔长度，默认一个填涂区域大小
# 返回答题结果，questionCount * answerCount的二位数组，置1位代表填涂，置0位代表未填涂
def determineAnswerBar(ansBoxCenter, questionCount, answerCount, groupCount, W, H, spaceStep = 1, restrictArea = True, restrictAreaThresh = 0.2, baseYBias = 0):
	baseX = baseY = 0
	baseY += baseYBias
	stepX = int(float(W) / (questionCount * groupCount + groupCount - 1))
	stepY = int(np.ceil(float(H) / answerCount))
	standardS = stepX * stepY
	answerMap = np.zeros((questionCount * groupCount, answerCount))
	for anSenter in ansBoxCenter:
		# 顶部会出现细长方块
		ansX, ansY, S, sw, sh = anSenter
		# 填涂面积至少为判定方格面积的20%
		if restrictArea and (S <= restrictAreaThresh * standardS or float(sw) / sh > 5):
			# print anSenter, standardS * restrictAreaThresh
			continue
		ansIndex = int((ansY - baseY) / stepY)
		quesIndePre = int((ansX - baseX) / stepX)
		# gapIndex 填涂区域所属组序号， 从1编号
		# determine spacegap index
		# quesIndex -> [q * (i - 1) + 1, (q + 1) * i - 1], where q:questionCount, i:gapIndex
		# q * (i - 1) + 1 <= x and (q + 1) * i - 1 >= x, where x: quesIndex
		# resolve this euqation
		# i -> [(x + 1) / (q + 1), (x + q) / (q + 1)]
		gapIndex = np.ceil(float((quesIndePre + 1)) / (questionCount + 1)) # or np.floor(float((quesIndePre + questionCount)) / (questionCount + 1))
		quesIndex = int(quesIndePre - gapIndex + 1)
		# 位于边线上，按最后一题或选项算
		if quesIndex < questionCount and ansIndex < answerCount:
			answerMap[quesIndex][ansIndex] = 1
		elif ansIndex == answerCount:
			answerMap[quesIndex][answerCount - 1] = 1
		elif quesIndex == questionCount:
			answerMap[questionCount - 1][ansIndex] = 1
		# print S, standardS * restrictAreaThresh, quesIndex, ansIndex
	return answerMap

# main function
def readCard(img, details = [], mode = "noise", baseYBias = 0, showImgs = False):
	if "area" not in details or not details["area"]:
		area = None
	else:
		area = details["area"]
	groupCount = details["groupCount"]
	questionCount = details["questionCount"]
	answerCount = details["answerCount"]
	# 灰度图
	img = grayImg(img)
	h, w = img.shape
	# otsu二值化
	img = binaryInv(img)
	# 低通滤波
	if mode == "noise":
		# 手机
		if baseYBias != 0:
			img = cv2.blur(img, (3, 3))
		else:
			img = cv2.blur(img, (4, 4))
	# print baseYBias
	# 腐蚀, 实际效果为涂抹填涂区域
	# img = erosion(img)
	# 膨胀， 实际效果为缩小填涂区域
	if mode == "noise":
		img = dilation(img, iterations = 1)
	if mode == "noise":
		img = erosion(img, iterations = 1)
	# 裁剪
	if area:
		rectImg01 = img[area[0]: area[1], area[2]: area[3]]
	else:
		rectImg01 = img
	# 白边框，防止贴近边缘的填涂区域被并入外围边框中
	row, col = rectImg01.shape
	rectImg01[0] = 255
	rectImg01[row - 1] = 255
	rectImg01[:, col - 1] = 255
	rectImg01[:, 0] = 255
	# 找出轮廓
	contours, hierarchy = findContours(rectImg01, cv2.RETR_TREE)
	if showImgs:
		# 调试:寻找并在白色底图上画出轮廓
		whiteImg = createWhiteImg((col, row))
		whiteImgH, whiteImgW = whiteImg.shape
		lineStep = int(np.ceil(whiteImgH / float(answerCount)))
		# 调试:画出轮廓
		drawContours(whiteImg, contours, (0, 0, 0), 2)
		# 画线
		for i in range(answerCount):
			whiteImg = cv2.line(whiteImg, (0, lineStep * i), (whiteImgW, lineStep * i), (0, 0, 0), 1)
	# 得到填涂答案
	boundingBox = getBoundingRect(contours)
	# print boundingBox
	ansBoxCenter, topBoundingBox = findAnswerBoxCenter(boundingBox, hierarchy)
	# 单个题组
	# ansMap = determineAnswer(ansBoxCenter, 5, 4, topBoundingBox[2] - topBoundingBox[0], topBoundingBox[3] - topBoundingBox[1])
	# 四个题组
	if baseYBias != 0:
		ansMap = determineAnswerBar(ansBoxCenter, questionCount, answerCount, groupCount, topBoundingBox[2] - topBoundingBox[0], topBoundingBox[3] - topBoundingBox[1]
			, restrictArea = True, restrictAreaThresh = 0.20, baseYBias = baseYBias)
	else:
		ansMap = determineAnswerBar(ansBoxCenter, questionCount, answerCount, groupCount, topBoundingBox[2] - topBoundingBox[0], topBoundingBox[3] - topBoundingBox[1]
		, restrictArea = True, restrictAreaThresh = 0.20, baseYBias = baseYBias)
	# ansMap = determineAnswerBar(ansBoxCenter, questionCount, answerCount, groupCount, w, h
	# 	, restrictArea = True, restrictAreaThresh = 0.18)
	if showImgs:
		# 调试:画出轮廓
		showImg(rectImg01, whiteImg)
	return ansMap

# if __name__ == "__main__":
# 	img = readImg("/Volumes/SD/ML/scantron/pics/card01.jpg")
# 	#houghTest(img)
# 	readCard(img)


from cx_Freeze import setup, Executable

executables = [
    Executable(
    	script="cuota_datos.py",
    )
]

bdist_msi_options = {
    'add_to_path': True,
    'initial_target_dir': r'[ProgramFilesFolder]\%s\%s' % ("Cuota", "cuota_nauta"),
}

setup(
        name='cuota_datos',
        author='Josué Carballo',
        author_email='josuecb@yandex.com',
        version='0.2',
        description='Para conocer y comprar datos móviles de Etecsa.',
        executables=executables,
        options={
            "bdist_msi": bdist_msi_options,
        },
    )