"""
  AO PREENCHER ESSE CABEÇALHO COM O MEU NOME E O MEU NÚMERO USP,
  DECLARO QUE SOU O ÚNICO AUTOR E RESPONSÁVEL POR ESSE PROGRAMA.
  TODAS AS PARTES ORIGINAIS DESSE EXERCÍCIO PROGRAMA (EP) FORAM
  DESENVOLVIDAS E IMPLEMENTADAS POR MIM SEGUINDO AS INSTRUÇÕES
  DESSE EP E QUE PORTANTO NÃO CONSTITUEM DESONESTIDADE ACADÊMICA
  OU PLÁGIO.
  DECLARO TAMBÉM QUE SOU RESPONSÁVEL POR TODAS AS CÓPIAS
  DESSE PROGRAMA E QUE EU NÃO DISTRIBUI OU FACILITEI A
  SUA DISTRIBUIÇÃO. ESTOU CIENTE QUE OS CASOS DE PLÁGIO E
  DESONESTIDADE ACADÊMICA SERÃO TRATADOS SEGUNDO OS CRITÉRIOS
  DIVULGADOS NA PÁGINA DA DISCIPLINA.
  ENTENDO QUE EPS SEM ASSINATURA NÃO SERÃO CORRIGIDOS E,
  AINDA ASSIM, PODERÃO SER PUNIDOS POR DESONESTIDADE ACADÊMICA.

  Nome :
  NUSP :
  Curso: indique MAE, MAP, MAT, IF, Poli, ECA, etc.
  Prof.:

  Referências: Com exceção das rotinas fornecidas no enunciado
  e em sala de aula, caso você tenha utilizado alguma referência,
  liste-as abaixo para que o seu programa não seja considerado
  plágio ou irregular.

  Exemplo:
  - O algoritmo Quicksort foi baseado em
  http://www.ime.usp.br/~pf/algoritmos/aulas/quick.html

  Não altere as assinaturas/protótipos/cabeçalhos das funções.
"""

############################################################
##
## IMPORTS
##

## misc
## vamos usar misc.imread e misc.imsave para carregar e salvar imagens .png
from scipy import misc

## plt
## vamos usar plt.imshow para mostrar imagens na tela
import matplotlib.pyplot as plt

##
import numpy as np

############################################################
##
## CONSTANTES que você pode usar em seu programa se quiser
##
# canais de uma imagem colorida
RED   = 0
GREEN = 1
BLUE  = 2

# algumas cores básicas
VERMELHO = (255, 0, 0)
VERDE    = (0, 255, 0)
AZUL     = (0, 0, 255)
PRETO    = (0, 0, 0)
CINZA    = (127, 127, 127)
BRANCO   = (255, 255, 255)
AMARELO  = (255, 255, 0)
MAGENTA  = (255, 0, 255)
CIANO    = (0, 255, 255)

# alguns filtros de convolução
FILTRO_BORRAMENTO = np.array(
    [
        [0, 1, 2, 1, 0],
        [1, 2, 4, 2, 1],
        [2, 4, 8, 4, 2],
        [1, 2, 4, 2, 1],
        [0, 1, 2, 1, 0]
    ]
)

# outro detector de borda
OUTRO_FILTRO = np.array(
    [
        [ 0, -4,  0],
        [-4, 16, -4],
        [ 0, -4,  0]
    ]
)

############################################################
##
## Programa Principal
##
def main():
    # leitura dos parâmetros
    nome_entrada =input("Digite o nome do arquivo de entrada [.PNG] >>> ")
    nome_saida   =input("Digite o nome do arquivo de saída   [.PNG] >>> ")
    limiar  = int(input("Digite o limiar desejado (int)             >>> "))

    # carrega imagem de entrada
    entrada = Imagem()
    entrada.carregue(nome_entrada)
    print("Imagem de entrada: ", nome_entrada)
    print("Feche a janela da imagem para continuar....")
    entrada.mostre()    

    # pre-processamento. Prepara para segmentar bordas
    cinza = entrada.para_cinza()
    # normaliza os pesos do filtro
    borramento = FILTRO_BORRAMENTO / FILTRO_BORRAMENTO.sum()
    # borramento elimina as bordas "mais fracas"
    borrada = cinza.filtre(borramento)

    bordas = borrada.segmente_bordas(limiar)
    print("Imagem das bordas segmentadas: ")
    print("Feche a janela da imagem para continuar....")
    bordas.mostre() 

    # realce das bordas na imagem original
    pintada = entrada.pinte( VERDE, bordas )
    print("Imagem com bordas realçadas ")
    print("Feche a janela da imagem para continuar....")
    pintada.mostre()

    # salva o resultado
    pintada.salve(nome_saida)
    print("Fim. A imagem realçada foi salva em ", nome_saida)

############################################################
##
##  CLASSE IMAGEM
##
class Imagem:

    def __init__(self, array = None ):
        ''' (self, array)
        Construtor de um objeto Imagem, que recebe um array com
        o conteúdo da imagem.
        '''
        if array is not None:
            self.data = np.copy(array)
        else:
            self.data = None

    # --------------------------------------------------
    def getpixel(self, lin, col):
        ''' (self, int, int) -> pixel
        retorna o valor do pixel na posição (lin, col)
        '''
        return self.data[lin, col]

    # --------------------------------------------------
    def setpixel(self, lin, col, valor):
        ''' (self, int, int) -> pixel
        atribui valor ao pixel na posição (lin, col)
        '''
        self.data[lin, col] = valor

    # --------------------------------------------------
    def carregue(self, arquivo):
        ''' (string) -> None
        Para esse exercício, vamos adotar que todo arquivo
        deve possuir a extensão ".png"
        '''
        self.data = misc.imread(arquivo)

    # --------------------------------------------------
    def salve(self, arquivo):
        ''' (string) -> None
        Para esse exercício, vamos adotar que todo arquivo
        deve possuir a extensão ".png"
        '''
        misc.imsave(arquivo, self.data)

    # --------------------------------------------------
    def mostre(self):
        ''' (self) -> Nome
        exibe a imagem em uma janela. 
        '''
        s = self.data.shape
        if len(s)==2:
            # níveis de cinza
            plt.imshow(self.data, cmap=plt.cm.gray)
        else:
            # imagem colorida
            plt.imshow(self.data)
        plt.show()

    # --------------------------------------------------
    ####################################################
    # --------------------------------------------------
    # ESCREVA OS MÉTODOS A SEGUIR
    # para_cinza
    # binarize
    # filtre
    # pinte
    # segmente_bordas
    # --------------------------------------------------
    ####################################################
    # --------------------------------------------------
    def para_cinza(self):
        ''' (self) -> Imagem
        retorna uma Imagem convertida para cinza.
        Assuma que a imagem colorida foi previamente carregada
        de um arquivo PNG, ou seja, os 3 canais existem.
        0.2126 R + 0.7152 G + 0.0722 B.
        Exemplo numérico (apenas ilustra o conteúdo das imagens em um formato de lista):
        para uma imagem 5x5 com:
        [[ [  0, 250,  0], [  0, 250, 100], [  0, 250, 150], [  0, 250, 200], [  0, 250, 250]],
         [ [100, 200,  0], [100, 200, 100], [100, 200, 150], [100, 200, 200], [100, 200, 250]],
         [ [150, 150,  0], [150, 150, 100], [150, 150, 150], [150, 150, 200], [150, 150, 250]],
         [ [200, 100,  0], [200, 100, 100], [200, 100, 150], [200, 100, 200], [200, 100, 250]],
         [ [250,   0,  0], [250,   0, 100], [250,   0, 150], [250,   0, 200], [250,   0, 250]]]

        A imagem de saída deve ser:
        [[ 178,  186,  189,  193,  196],
         [ 164,  171,  175,  178,  182],
         [ 139,  146,  150,  153,  157],
         [ 114,  121,  124,  128,  132],
         [  53,   60,   63,   67,   71]]
        '''
        print("Vixe! Ainda não fiz para_cinza()")
        return Imagem(np.zeros(self.data.shape))

    # --------------------------------------------------
    def binarize(self, limiar):
        ''' (self, int) -> Imagem
        retorna uma Imagem convertida para binária.
        Assuma que a imagem self é com níveis de cinza com um byte,
        ou seja, seus valores são inteiros de 0 a 255.
        A imagem binarizada deve conter True nos pixels de self 
        maiores que o limiar, e False caso contrário.
        Exemplo numérico (apenas ilustra o conteúdo das imagens em um formato de lista):
        Para uma imagem 5x5 com:
        [[ 178,  186,  189,  193,  196],
         [ 164,  171,  175,  178,  182],
         [ 139,  146,  150,  153,  157],
         [ 114,  121,  124,  128,  132],
         [  53,   60,   63,   67,   71]]

        A imagem de saída com limiar 150 deve ser:
        [[ True,  True,  True,  True,  True],
         [ True,  True,  True,  True,  True],
         [False, False, False,  True,  True],
         [False, False, False, False, False],
         [False, False, False, False, False]]
        '''
        print("Vixe! Ainda não fiz binarize()")
        return Imagem(np.zeros(self.data.shape))
        
    # --------------------------------------------------
    def filtre(self, filtro):
        ''' (self, ndarray) -> Imagem cinza
        Assuma que self é uma imagem com níveis de cinza.
        O método retorna uma imagem resultado da convolução de self 
        com o filtro.
        Como os valores do filtro são reais, os valores da imagem
        resultado também serão reais.

        Exemplo numérico (apenas ilustra o conteúdo das imagens em um formato de lista):
        para uma imagem:
        [[ 250,  250,  250,  100,  150],
         [ 250,  100,  100,  100,  150],
         [ 250,  100,   50,  100,  250],
         [ 250,  100,  100,  100,  250],
         [ 250,  250,  250,  250,  250]]
         e o filtro:
         [[-1, 0, 1], 
          [-2, 0, 2], 
          [-1, 0, 1]]
         o resultado é:
         [[   0.,    0.,    0.,    0.,    0.],
          [   0., -500., -150.,  200.,    0.],
          [   0., -700.,    0.,  600.,    0.],
          [   0., -500.,    0.,  500.,    0.],
          [   0.,    0.,    0.,    0.,    0.]]
        '''
        print("Vixe! Ainda não fiz filtre()")
        return Imagem(np.zeros(self.data.shape))


    # --------------------------------------------------
    def pinte(self, cor, mascara):
        ''' (self, cor, Imagem) -> Imagem
        Recebe uma imagem binária mascara e pinta os pixels de self
        correspondentes aos pixels True da mascara com a cor. Observe
        que a cor deve ter o mesmo número de bits da imagem em self.
        Exemplo numérico (apenas ilustra o conteúdo das imagens):
        para uma imagem 5x5 com:
        [[ [  0, 250,  0], [  0, 250, 100], [  0, 250, 150], [  0, 250, 200], [  0, 250, 250]],
         [ [100, 200,  0], [100, 200, 100], [100, 200, 150], [100, 200, 200], [100, 200, 250]],
         [ [150, 150,  0], [150, 150, 100], [150, 150, 150], [150, 150, 200], [150, 150, 250]],
         [ [200, 100,  0], [200, 100, 100], [200, 100, 150], [200, 100, 200], [200, 100, 250]],
         [ [250,   0,  0], [250,   0, 100], [250,   0, 150], [250,   0, 200], [250,   0, 250]]]
        cor [0, 0, 0] e mascara:
        [[ True  True  True  True  True]
         [ True  True  True  True  True]
         [False False False  True  True]
         [False False False False False]
         [False False False False False]]
        A imagem pintada deverá possuir:
        [[ [  0,   0,  0], [  0,   0,   0], [  0,   0,   0], [  0,   0,   0], [  0,   0,   0]],
         [ [  0,   0,  0], [  0,   0,   0], [  0,   0,   0], [  0,   0,   0], [  0,   0,   0]],
         [ [150, 150,  0], [150, 150, 100], [150, 150, 150], [  0,   0,   0], [  0,   0,   0]],
         [ [200, 100,  0], [200, 100, 100], [200, 100, 150], [200, 100, 200], [200, 100, 250]],
         [ [250,   0,  0], [250,   0, 100], [250,   0, 150], [250,   0, 200], [250,   0, 250]]]
        '''
        print("Vixe! Ainda não fiz pinte()")
        return Imagem(np.zeros(self.data.shape))
        

    # --------------------------------------------------
    def segmente_bordas(self, limiar):
        ''' (self, int) -> Imagem
        Assuma que self é uma imagem com níveis de cinza. 
        O método calcula as matrizes gradiente gH e gV
        utilizando os filtros Sh e Sv de Sobel, como descritos no
        enunciado do EP, e retorna uma imagem 
        binária com as mesmas dimensões da imagem cinza self, 
        onde os valores True satisfazem:
        sqrt(gH*gH + gV*gV) > limiar.
        Pixels que não satisfazem a condição devem receber False.

        Importante: como o limiar é um número entre 0 e 255,
        antes de aplicar o limiar (comparar), a imagem correspondente ao 
        módulo do gradiente (sqrt(gH*gH + gV*gV)) deve ser normalizada para
        o intervalo 0 a 255.

        Exemplo numérico (apenas ilustra o conteúdo das imagens):
        [   [ 150,  150,  150,  100,  150],
            [ 150,  100,  100,  100,  150],
            [ 150,  100,   75,  100,  250],
            [ 150,  100,  100,  100,  250],
            [ 150,  150,  250,  250,  250]]
        e limiar = 100,
        o resultado seria:
        [[False False False False False]
         [False False False  True False]
         [False False False  True False]
         [False  True  True  True False]
         [False False False False False]]
        '''
        print("Vixe! Ainda não fiz segmente_bordas()")
        return Imagem(np.zeros(self.data.shape))


############################################################

if __name__ == '__main__':
    main()
