Skip to content

Commit

Permalink
Added common package, splitted common functions, updatd picture showi…
Browse files Browse the repository at this point in the history
…ng, updated inpainting
  • Loading branch information
NicolasBizzozzero committed May 22, 2018
1 parent b530531 commit d1a6e85
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 71 deletions.
8 changes: 4 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from src.picture_tools.examples import CAMERAMAN, HOUSE, JETPLANE, LAKE, LENA_COLOR_256, LENA_COLOR_512, LENA_GRAY_256, \
LENA_GRAY_512, LIVINGROOM, MANDRIL_COLOR, MANDRIL_GRAY, PEPPERS_COLOR, PEPPERS_GRAY, PIRATE, WALKBRIDGE, \
WOMAN_BLONDE, WOMAN_DARKHAIR
WOMAN_BLONDE, WOMAN_DARKHAIR, CASTLE, OUTDOOR
from src.picture_tools.codage import Codage
from src.picture_tools.picture import Picture, show_patch
from src.usps_tools import test_all_usps_1_vs_all, test_all_usps
Expand All @@ -20,13 +20,13 @@ def main():
picture.show()

# Ajout du bruit
# picture.add_rectangle(3, 3, 10, 10)
picture.add_noise(0.01)
picture.add_rectangle(250, 190, 40, 50)
# picture.add_noise(0.01)
picture.show()

# On inpaint l'image !
inpainting = InPainting(PATCH_SIZE)
inpainting.inpaint(picture)
picture = inpainting.inpaint(picture)
picture.show()


Expand Down
6 changes: 6 additions & 0 deletions rapport/rapport.tex
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ \subsubsection{Vecteur de poids}

\newpage
\section{LASSO et Inpainting}
\subsection{Introduction}
\subsubsection{Déroulement}


\subsubsection{Applications}
Marche bien pour recouvrir de larges zones. Permet de retirer des objets dans un but artistique (touristes sur une photo, défaut sur un visage).



Expand Down
Empty file added src/common/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions src/common/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
def time_this(function: callable) -> callable:
""" Print the execution time of the wrapped function. """
def wrapper(*args, **kwargs):
from time import time
time_begin = time()
result = function(*args, **kwargs)
time_end = time()
time_total = time_end - time_begin
second_or_seconds = "second" if (time_total < 1) else "seconds"
print("Execution time for \"{}\": {} {}".format(
function.__name__, time_total, second_or_seconds))
return result
return wrapper


if __name__ == "__main__":
pass
22 changes: 1 addition & 21 deletions src/common.py → src/common/math.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
# -*- coding: utf-8 -*-
""" Ce module contient toutes les méthodes et fonctions n'ayant pas pu être rangées dans un module précis. Il est
souvent réutilisé dans plusieurs modules différents, et doit donc être en bout de chaîne pour éviter les dépendances
cycliques.
"""
from typing import Union

import numpy as np
from typing import Union

Number = Union[int, float]

Expand Down Expand Up @@ -62,20 +57,5 @@ def make_grid(data=None, xmin=-5, xmax=5, ymin=-5, ymax=5, step=20):
return grid, x, y


def time_this(function: callable) -> callable:
""" Print the execution time of the wrapped function. """
def wrapper(*args, **kwargs):
from time import time
time_begin = time()
result = function(*args, **kwargs)
time_end = time()
time_total = time_end - time_begin
second_or_seconds = "second" if (time_total < 1) else "seconds"
print("Execution time for \"{}\": {} {}".format(
function.__name__, time_total, second_or_seconds))
return result
return wrapper


if __name__ == "__main__":
pass
36 changes: 36 additions & 0 deletions src/common/matplotlib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from typing import List

import numpy as np
from matplotlib import pyplot as plt


def show_pictures(pictures: iter, titles: List[str] = None, columns: int = 1):
""" Display a list of images in a single figure with matplotlib.
:param pictures: List of np.arrays compatible with plt.imshow
:param titles: List of titles corresponding to each image. Must have the same length as titles.
:param columns: Number of columns in figure (number of rows is set to np.ceil(n_images / float(cols)))
Source :
https://gist.github.com/soply/f3eec2e79c165e39c9d540e916142ae1
"""
assert ((titles is None) or (len(pictures) == len(titles)))

# Set default title names for each picture
if titles is None:
titles = ['Image (%d)' % i for i in range(1, len(pictures) + 1)]

fig = plt.figure()
for index_picture, (picture, title) in enumerate(zip(pictures, titles)):
sub_figure = fig.add_subplot(columns, np.ceil(len(pictures) / float(columns)), index_picture + 1)
if picture.ndim == 2:
plt.gray()
plt.imshow(picture)
sub_figure.set_title(title)
sub_figure.axis("off")
fig.set_size_inches(np.array(fig.get_size_inches()) * len(pictures))
plt.show()


if __name__ == "__main__":
pass
59 changes: 23 additions & 36 deletions src/inpainting.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,68 +3,58 @@

import numpy as np
from sklearn.linear_model import Lasso
from progressbar import ProgressBar, Percentage, Counter, Timer
from progressbar import ProgressBar, Percentage, Counter, Timer, ETA

from src.common import time_this
from src.picture_tools.picture import Picture, VALUE_MISSING_PIXEL, get_center, flatten, unflatten, show_patch
from src.linear.cost_function import *
from src.linear.gradient_descent import DescenteDeGradient
from src.linear.linear_regression import Initialisation, LinearRegression


PB_WIDGETS = ["Inpainting: processed ", Counter(), " pixels [", Percentage(), "], ",
Timer()]
PB_WIDGETS = ["Inpainting: processed ", Counter(), " pixels [", Percentage(), "], ", Timer(), ", ", ETA()]


class InPainting:
def __init__(self, patch_size: int, step: int = None, max_missing_pixel: int = 0,
value_missing_pixel: int = VALUE_MISSING_PIXEL, loss: callable = l1, loss_g: callable = l1_g,
max_iter: int = 10000, eps: float = 0.01, biais: bool = True,
type_descente: DescenteDeGradient = DescenteDeGradient.BATCH, taille_batch: int = 50,
initialisation: Initialisation = Initialisation.RANDOM, alpha: float = 0.001):
def __init__(self, patch_size: int, step: int = None, value_missing_pixel: int = VALUE_MISSING_PIXEL,
alpha: float = 1.0, max_iterations: int = 1000, tolerance: float = 0.0001):
self.patch_size = patch_size
self.step = patch_size if step is None else step
self.max_missing_pixel = max_missing_pixel
self.loss = loss
self.loss_g = loss_g
self.max_iter = max_iter
self.eps = eps
self.biais = biais
self.type_descente = type_descente
self.taille_batch = taille_batch
self.initialisation = initialisation
self.alpha = alpha
self.value_missing_pixel = value_missing_pixel

self.classifier = LinearRegression(loss=loss, loss_g=loss_g, max_iter=max_iter, eps=eps, biais=biais,
type_descente=type_descente, taille_batch=taille_batch,
initialisation=initialisation, alpha=alpha)
classifiers_kwaargs = {"alpha": alpha, "copy_X": True, "fit_intercept": True, "max_iter": max_iterations,
"normalize": False, "positive": False, "precompute": False, "random_state": None,
"selection": 'cyclic', "tol": tolerance, "warm_start": False}
self._classifier_hue = Lasso(**classifiers_kwaargs)
self._classifier_saturation = Lasso(**classifiers_kwaargs)
self._classifier_value = Lasso(**classifiers_kwaargs)

def inpaint(self, picture: Picture) -> Picture:
picture = picture.copy()

def inpaint(self, picture: Picture):
# Initialisation de la barre de progression
progress_bar = ProgressBar(widgets=PB_WIDGETS,
maxval=len(picture.pixels[picture.pixels == self.value_missing_pixel]) // 3,
minval=0)
progress_bar.start()

# On récupère le dictionnaire
dictionary = picture.get_dictionnaire(self.patch_size, self.step, self.max_missing_pixel)
dictionary = picture.get_dictionnaire(self.patch_size, self.step, max_missing_pixel=0)

while self.value_missing_pixel in picture.pixels:
# On récupère le patch centré sur le prochain pixel à traiter
next_pixel = self._get_next_pixel(picture.pixels)

# On reconstruit le pixel choisit
show_patch(picture.get_patch(*next_pixel, self.patch_size))
# On reconstruit le pixel choisi
next_pixel_value = self._get_next_pixel_value(picture, dictionary, *next_pixel)
picture.pixels[next_pixel] = next_pixel_value

# On met à jour la barre de progression
progress_bar.update(progress_bar.value + 1)

progress_bar.finish()
return picture

def _get_next_pixel(self, pixels: np.ndarray) -> Tuple[int, int]:
def _get_next_pixel(self, pixels: np.ndarray, strategy) -> Tuple[int, int]:
""" Return the next pixel to be painted.
This pixel is found by assigning a priority value to all remaining pixels in the picture, then by returning the
one with the maximal priority.
Expand Down Expand Up @@ -99,18 +89,15 @@ def _get_next_pixel_value(self, picture: Picture, dictionary, next_pixel_x, next
datay_value.append(patch[x, y, 2])

# Apprentissage
classifier_hue = Lasso()
classifier_saturation = Lasso()
classifier_value = Lasso()
classifier_hue.fit(datax_hue, datay_hue)
classifier_saturation.fit(datax_saturation, datay_saturation)
classifier_value.fit(datax_value, datay_value)
self._classifier_hue.fit(datax_hue, datay_hue)
self._classifier_saturation.fit(datax_saturation, datay_saturation)
self._classifier_value.fit(datax_value, datay_value)

# Prédiction
x, y = self.patch_size // 2, self.patch_size // 2
hue = classifier_hue.predict(dictionary[:, x, y, 0].reshape(1, -1))
saturation = classifier_saturation.predict(dictionary[:, x, y, 1].reshape(1, -1))
value = classifier_value.predict(dictionary[:, x, y, 2].reshape(1, -1))
hue = self._classifier_hue.predict(dictionary[:, x, y, 0].reshape(1, -1))
saturation = self._classifier_saturation.predict(dictionary[:, x, y, 1].reshape(1, -1))
value = self._classifier_value.predict(dictionary[:, x, y, 2].reshape(1, -1))
return np.hstack((hue, saturation, value))


Expand Down
5 changes: 5 additions & 0 deletions src/picture_tools/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

_PATH_DIR_PICTURES = join(dirname(__file__), "../../res/pictures")

# Famous pictures
CAMERAMAN = join(_PATH_DIR_PICTURES, "cameraman.tif")
HOUSE = join(_PATH_DIR_PICTURES, "house.tif")
JETPLANE = join(_PATH_DIR_PICTURES, "jetplane.tif")
Expand All @@ -24,6 +25,10 @@
WOMAN_BLONDE = join(_PATH_DIR_PICTURES, "woman_blonde.tif")
WOMAN_DARKHAIR = join(_PATH_DIR_PICTURES, "woman_darkhair.tif")

# Personal pictures
CASTLE = join(_PATH_DIR_PICTURES, "castle.jpg")
OUTDOOR = join(_PATH_DIR_PICTURES, "outdoor.jpg")


if __name__ == "__main__":
pass
42 changes: 32 additions & 10 deletions src/picture_tools/picture.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
from numpy import uint8

from src.picture_tools.codage import Codage, change_codage
from src.common import normalize, time_this
from src.common.math import normalize
from src.common.decorators import time_this


VALUE_MISSING_PIXEL = np.ones((3,)) * -100
VALUE_SHOWING_MISSING_PIXEL = np.random.uniform(low=-1, high=1)
VALUE_SHOWING_MISSING_PIXEL = np.array([-1]) # np.random.uniform(low=-1, high=1)


class Picture:
Expand All @@ -42,12 +44,8 @@ def show(self, show: bool = True) -> None:
""" Plot l'image sur matplotlib et l'affiche sur demande.
:param: show, waut `True` si on affiche l'image après l'avoir plottée.
"""
# Remove missing values
picture = np.copy(self.pixels)
picture[picture == VALUE_MISSING_PIXEL] = VALUE_SHOWING_MISSING_PIXEL

picture = change_codage(picture, self.codage, Codage.RGB)
picture = normalize(picture, 0, 255, -1, 1).astype(uint8)
picture = self._get_showable_picture()
plt.axis("off")
plt.imshow(picture)
if show:
plt.show()
Expand All @@ -63,14 +61,21 @@ def save(self, picture_path: str = None) -> None:

# Remove missing values
picture = np.copy(self.pixels)
picture[picture == VALUE_MISSING_PIXEL] = np.random.uniform(
low=-1, high=1)
picture[picture == VALUE_MISSING_PIXEL] = VALUE_SHOWING_MISSING_PIXEL

picture = change_codage(self.pixels, self.codage, Codage.RGB)
picture = normalize(picture, 0, 255, -1, 1).astype(uint8)
plt.imshow(picture)
plt.savefig(picture_path)

def copy(self):
new_picture = Picture.__new__(Picture)
new_picture.picture_path = self.picture_path
new_picture.codage = self.codage
new_picture.hauteur, new_picture.largeur = self.hauteur, self.largeur
new_picture.pixels = np.copy(self.pixels)
return new_picture

def add_noise(self, threshold: float = 0.05):
""" Ajoute aléatoirement du bruit dans l'image.
:param: threshold, seuil en dessous duquel on bruite le pixel.
Expand Down Expand Up @@ -180,6 +185,23 @@ def out_of_bounds_patch(self, x: int, y: int, size: int) -> bool:
(y - (size // 2) <= 0) or \
(y + (size // 2) + 1 < self.pixels.shape[1])

def _get_showable_picture(self) -> np.ndarray:
""" Return the picture in a showable format (as in a format which can be plotted by invocating `plt.imshow`on
it.
"""
pixels = np.copy(self.pixels)

# Fill the missing pixels with an interpretable value
pixels[pixels == VALUE_MISSING_PIXEL] = VALUE_SHOWING_MISSING_PIXEL

# Change the format of the picture to the RGB format (for better visibility)
pixels = change_codage(pixels, self.codage, Codage.RGB)

# Normalise the values of the pixel from [-1, 1] to [0, 255]
pixels = normalize(pixels, 0, 255, -1, 1).astype(uint8)

return pixels


def get_center(pixels: np.ndarray) -> np.ndarray:
""" Return the pixel at the center of the pixels. """
Expand Down

0 comments on commit d1a6e85

Please sign in to comment.