"""
Programme   : TP-images-partie3-correction-exo2.py
Langage     : Python 3.7.3
Modules     : Pillow 7.1.2
Auteur      : Mathieu Pons
Description : mise en oeuvre du filtre de Sobel pour la détection de contours et du filtre réducteur de bruit
Pillow      : https://pillow.readthedocs.io/en/stable/

Exercice à réaliser :
	1) Ecrire une fonction qui applique le filtre de Sobel à une image.

	2) Ecrire une fonction qui applique le filtre de réduction de bruit à une image.
	Remarques : l'image originale n'étant pas bruitée, le résultat obtenu n'est guère convaincant
	et n'a pas grand intérêt puisqu'il dégrade l'image originale. Pour comprendre l'intérêt du filtre
	réducteur de bruit on peut :
		a) soit l'appliquer à une image bruitée comme par exemple une vieille photo numérisée ;
		b) soit introduire volontairement du bruit dans notre image originale en changeant de manière
		aléatoire la valeur de quelques pixels de l'image ;

	3) Ecrire une fonction qui introduit aléatoirement du bruit dans l'image originale sous forme
	de pixel dont le niveau de gris est lui-même aléatoire puis appliquer le filtre réducteur de bruit.
"""

# IMPORT =========================================================================================
from PIL import Image
import math, random

# FONCTIONS =======================================================================================
def voisinage(pimg, px, py):
	"""
	Description : 	construit la matrice carrée 3x3 au voisinage du pixel (px, py)
	Paramètres 	: 	type(pimg) => PIL.Image ; type(px, py) => (int, int)
	Retour 		: 	liste de listes 3x3
	"""
	return [[pimg.getpixel((px - 1, py - 1)), pimg.getpixel((px, py - 1)), pimg.getpixel((px + 1, py - 1))],
			[pimg.getpixel((px - 1, py    )), pimg.getpixel((px, py    )), pimg.getpixel((px + 1,     py))],
			[pimg.getpixel((px - 1, py + 1)), pimg.getpixel((px, py + 1)), pimg.getpixel((px + 1, py + 1))]]

def convolution(K, V):
	"""
	Description : 	effectue le produit de convolution de K par V
	Paramètres 	: 	type(K, V) => liste de listes 3x3
	Retour 		: 	int
	"""
	return  K[0][0] * V[0][0] + K[1][0] * V[1][0] + K[2][0] * V[2][0] + \
			K[0][1] * V[0][1] + K[1][1] * V[1][1] + K[2][1] * V[2][1] + \
			K[0][2] * V[0][2] + K[1][2] * V[1][2] + K[2][2] * V[2][2]

def sobel(Sx, Sy, pname):
	"""
	Description : 	applique le filtre de Sobel sur le fichier image pname
	Paramètres 	: 	type(Sx, Sy) => liste de listes 3x3 ; type(pname) => str
	Retour 		: 	Image.PIL ou None si le chargement a échoué
	"""
	try:
		img = Image.open(pname)
	except:
		print("Impossible d'ouvrir le fichier " + pname)
		return

	img.show()
	larg = img.width
	haut = img.height
	img_retour = Image.new("L", (larg, haut))
	for y in range(1, haut - 1): # on ignore la première et dernière colonne de l'image
		for x in range(1, larg - 1): # on ignore la première et dernière ligne de l'image
			V = voisinage(img, x, y) # on charge les voisins du pixel de coordonnées (x, y) dans une matrice
			gx = int(convolution(Sx, V) / 4)
			gy = int(convolution(Sy, V) / 4)
			gris = int(math.sqrt(gx * gx + gy * gy))
			img_retour.putpixel((x, y), gris)
	return img_retour

def reduire_bruit(pname):
	"""
	Description : 	applique le filtre de réduction de bruit par détermination de la médiane
	Paramètres 	: 	type(pname) => str
	Retour 		: 	Image.PIL ou None si le chargement a échoué
	"""
	try:
		img = Image.open(pname)
	except:
		print("Impossible d'ouvrir le fichier " + pname)
		return

	img.show()
	larg = img.width
	haut = img.height
	img_retour = Image.new("L", (larg, haut))
	for y in range(1, haut - 1):
		for x in range(1, larg - 1):
			V = voisinage(img, x, y) # on récupère une liste de 3 sous-listes qu'il faut transformer en une liste unique pour pouvoir la trier
			V_liste = []
			for sous_liste in V: # pour chaque sous-listes de la liste V (ou pour chaque ligne de la matrice V)
				for val in sous_liste: # pour chaque valeur de la sous-liste considérée
				 V_liste.append(val) # on ajoute cette valeur à la liste unique
			V_liste.sort() # on peut alors trier V_liste avec une méthode de liste de Python
			gris = V_liste[4] # il y a 9 éléments dans la liste donc la médiane est le 4ième (attention, on part de 0)
			img_retour.putpixel((x,y), gris)
	return img_retour

def introduire_bruit(pname):
	"""
	Description : 	introduit du bruit sous forme de pixel dont la valeur de gris est aléatoire
	Paramètres 	: 	type(pname) => str
	Retour 		: 	Image.PIL ou None si le chargement a échoué
	"""
	try:
		img = Image.open(pname)
	except:
		print("Impossible d'ouvrir le fichier " + pname)
		return

	img.show()
	larg = img.width
	haut = img.height
	img_retour = Image.new("L", (larg, haut))
	for y in range(1, haut - 1):
		for x in range(1, larg - 1):
			if random.randint(0, 100) > 95: # on introduit environ 5% de bruit numérique
				gris = random.randint(0, 255) # on génère un niveau de bruit dans la plage des niveaux de gris possibles
			else:
				gris = img.getpixel((x, y))
			img_retour.putpixel((x,y), gris)
	return img_retour

# PROGRAMME PRINCIPAL =============================================================================
# Question 1
# Noyau de convolution pour le calcul du gradient horizontal
Sx = 	[[-1, -2, -1],
		 [ 0,  0,  0],
		 [ 1,  2,  1]]
# Noyau de convolution pour le calcul du gradient vertical
Sy = 	[[-1, 0, 1],
		 [-2, 0, 2],
		 [-1, 0, 1]]
img = sobel(Sx, Sy, "gris-paf.png")
if img == None:
	print("Echec dans l'application du filtre")
else:
	img.show()

# Question 2
img = introduire_bruit("gris-paf.png")
if img == None:
	print("Echec dans l'application du filtre")
else:
	img.show()

img = reduire_bruit("gris-paf.png")
if img == None:
	print("Echec dans l'application du filtre")
else:
	img.show()
