<repo_name>prise-3d/Thesis-Denoising-autoencoder<file_sep>/image_denoising.py
# main imports
import os
import json
import pandas as pd
import numpy as np
import argparse

# model imports
from keras.layers import Input, Conv3D, MaxPooling3D, UpSampling3D
from keras.models import Model
from keras import backend as K
from keras.callbacks import TensorBoard
from sklearn.utils import shuffle

# image processing imports
import cv2

# modules imports
import custom_config as cfg

def generate_model(input_shape):

    input_img = Input(shape=input_shape)  # adapt this if using `channels_first` image data format

    x = Conv3D(32, (1, 3, 3), activation='relu', padding='same')(input_img)
    x = MaxPooling3D((1, 2, 2), padding='same')(x)
    x = Conv3D(32, (1, 3, 3), activation='relu', padding='same')(x)
    x = MaxPooling3D((1, 2, 2), padding='same')(x)
    x = Conv3D(32, (1, 3, 3), activation='relu', padding='same')(x)
    encoded = MaxPooling3D((1, 2, 2), padding='same')(x)

    print(encoded)

    x = Conv3D(32, (1, 3, 3), activation='relu', padding='same')(encoded)
    x = UpSampling3D((1, 2, 2))(x)
    x = Conv3D(32, (1, 3, 3), activation='relu', padding='same')(x)
    x = UpSampling3D((1, 2, 2))(x)
    x = Conv3D(32, (1, 3, 3), activation='relu', padding='same')(x)
    x = UpSampling3D((1, 2, 2))(x)
    decoded = Conv3D(3, (1, 3, 3), activation='sigmoid', padding='same')(x)

    autoencoder = Model(input_img, decoded)

    autoencoder.compile(optimizer='adadelta', loss='mse')

    return autoencoder

def main():

    # load params
    parser = argparse.ArgumentParser(description="Train Keras model and save it into .json file")

    parser.add_argument('--data', type=str, help='dataset filename prefix (without .train and .test)', required=True)
    parser.add_argument('--output', type=str, help='output file name desired for model (without .json extension)', required=True)
    parser.add_argument('--batch_size', type=int, help='batch size used as model input', default=cfg.keras_batch)
    parser.add_argument('--epochs', type=int, help='number of epochs used for training model', default=cfg.keras_epochs)
   
    args = parser.parse_args()

    p_data_file  = args.data
    p_output     = args.output
    p_batch_size = args.batch_size
    p_epochs     = args.epochs

    # load data from `p_data_file`
    ########################
    # 1. Get and prepare data
    ########################
    print("Preparing data...")
    dataset_train = pd.read_csv(p_data_file + '.train', header=None, sep=";")
    dataset_test = pd.read_csv(p_data_file + '.test', header=None, sep=";")

    print("Train set size : ", len(dataset_train))
    print("Test set size : ", len(dataset_test))

    # default first shuffle of data
    dataset_train = shuffle(dataset_train)
    dataset_test = shuffle(dataset_test)

    print("Reading all images data...")

    # getting number of chanel
    n_channels = len(dataset_train[1][1].split('::'))
    print("Number of channels : ", n_channels)

    img_width, img_height = cfg.keras_img_size

    # specify the number of dimensions
    if K.image_data_format() == 'channels_first':
        if n_channels > 1:
            input_shape = (1, n_channels, img_width, img_height)
        else:
            input_shape = (n_channels, img_width, img_height)

    else:
        if n_channels > 1:
            input_shape = (1, img_width, img_height, n_channels)
        else:
            input_shape = (img_width, img_height, n_channels)

    # `:` is the separator used for getting each img path
    if n_channels > 1:
        dataset_train[1] = dataset_train[1].apply(lambda x: [cv2.imread(path, cv2.IMREAD_GRAYSCALE) for path in x.split('::')])
        dataset_test[1] = dataset_test[1].apply(lambda x: [cv2.imread(path, cv2.IMREAD_GRAYSCALE) for path in x.split('::')])
    else:
        dataset_train[1] = dataset_train[1].apply(lambda x: cv2.imread(x, cv2.IMREAD_GRAYSCALE))
        dataset_test[1] = dataset_test[1].apply(lambda x: cv2.imread(x, cv2.IMREAD_GRAYSCALE))

    x_dataset_train = dataset_train[1].apply(lambda x: np.array(x).reshape(input_shape))
    x_dataset_test = dataset_test[1].apply(lambda x: np.array(x).reshape(input_shape))
    
    y_dataset_train = dataset_train[0].apply(lambda x: cv2.imread(x).reshape(input_shape))
    y_dataset_test = dataset_test[0].apply(lambda x: cv2.imread(x).reshape(input_shape))

    # format data correctly
    x_data_train = np.array([item[0].reshape(input_shape) for item in x_dataset_train.values])
    x_data_test = np.array([item[0].reshape(input_shape) for item in x_dataset_test.values])

    y_data_train = np.array([item[0].reshape(input_shape) for item in y_dataset_train.values])
    y_data_test = np.array([item[0].reshape(input_shape) for item in y_dataset_test.values])

    # load model
    autoencoder = generate_model(input_shape)

    # tensorboard --logdir=/tmp/autoencoder
    autoencoder.fit(x_data_train, y_data_train,
                    epochs=100,
                    batch_size=32,
                    shuffle=True,
                    validation_data=(x_data_test, y_data_test),
                    callbacks=[TensorBoard(log_dir='/tmp/autoencoder', histogram_freq=0, write_graph=False)])

    ##############
    # save model #
    ##############
    if not os.path.exists(cfg.saved_models_folder):
        os.makedirs(cfg.saved_models_folder)

    # save the model into HDF5 file
    model_output_path = os.path.join(cfg.saved_models_folder, p_output + '.json')
    json_model_content = autoencoder.to_json()

    with open(model_output_path, 'w') as f:
        print("Model saved into ", model_output_path)
        json.dump(json_model_content, f, indent=4)

    autoencoder.save_weights(model_output_path.replace('.json', '.h5'))
    
if __name__ == "__main__":
    main()<file_sep>/generate/generate_dataset.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Jun 19 11:47:42 2019

@author: jbuisine
"""

# main imports
import sys, os, argparse
import numpy as np
import random

# images processing imports
from PIL import Image
from ipfml.processing.segmentation import divide_in_blocks

# modules imports
sys.path.insert(0, '') # trick to enable import of main folder module

import custom_config  as cfg
from modules.utils import data as dt
from modules.classes.Transformation import Transformation

# getting configuration information
config_filename         = cfg.config_filename
zone_folder             = cfg.zone_folder
learned_folder          = cfg.learned_zones_folder
min_max_filename        = cfg.min_max_filename_extension

# define all scenes values
scenes_list             = cfg.scenes_names
scenes_indexes          = cfg.scenes_indices
dataset_path            = cfg.dataset_path
zones                   = cfg.zones_indices
seuil_expe_filename     = cfg.seuil_expe_filename

features_choices        = cfg.features_choices_labels
output_data_folder      = cfg.output_data_folder

generic_output_file_svd = '_random.csv'

def generate_data_model(_scenes_list, _filename, _transformations, _scenes, _nb_zones = 4, _random=0, _only_noisy=0):

    output_train_filename = _filename + ".train"
    output_test_filename = _filename + ".test"

    if not '/' in output_train_filename:
        raise Exception("Please select filename with directory path to save data. Example : data/dataset")

    # create path if not exists
    if not os.path.exists(output_data_folder):
        os.makedirs(output_data_folder)

    train_file_data = []
    test_file_data  = []

    scenes = os.listdir(dataset_path)
    # remove min max file from scenes folder
    scenes = [s for s in scenes if min_max_filename not in s]

    # go ahead each scenes
    for id_scene, folder_scene in enumerate(_scenes_list):

        scene_path = os.path.join(dataset_path, folder_scene)

        config_file_path = os.path.join(scene_path, config_filename)

        # only get last image path
        with open(config_file_path, "r") as config_file:
            last_image_name = config_file.readline().strip()

        ref_image_path = os.path.join(scene_path, last_image_name)

        zones_indices = zones

        # shuffle list of zones (=> randomly choose zones)
        # only in random mode
        if _random:
            random.shuffle(zones_indices)

         # store zones learned
        learned_zones_indices = zones_indices[:_nb_zones]

        # write into file
        folder_learned_path = os.path.join(learned_folder, _filename.split('/')[1])

        if not os.path.exists(folder_learned_path):
            os.makedirs(folder_learned_path)

        file_learned_path = os.path.join(folder_learned_path, folder_scene + '.csv')

        with open(file_learned_path, 'w') as f:
            for i in learned_zones_indices:
                f.write(str(i) + ';')

        ref_image_blocks = divide_in_blocks(Image.open(ref_image_path), cfg.keras_img_size)

        for id_zone, index_folder in enumerate(zones_indices):

            index_str = str(index_folder)
            if len(index_str) < 2:
                index_str = "0" + index_str
            
            current_zone_folder = "zone" + index_str
            zone_path = os.path.join(scene_path, current_zone_folder)

            # path of zone of reference image
            # ref_image_block_path = os.path.join(zone_path, last_image_name)

            # compute augmented images for ref image
            current_ref_zone_image = ref_image_blocks[id_zone]

            ref_image_name_prefix = last_image_name.replace('.png', '')
            dt.augmented_data_image(current_ref_zone_image, zone_path, ref_image_name_prefix)

            # get list of all augmented ref images
            ref_augmented_images = [os.path.join(zone_path, f) for f in os.listdir(zone_path) if ref_image_name_prefix in f]

            # custom path for interval of reconstruction and features
            features_path = []

            for transformation in _transformations:
                
                # check if it's a static content and create augmented images if necessary
                if transformation.getName() == 'static':
                    
                    # {sceneName}/zoneXX/static
                    static_features_path = os.path.join(zone_path, transformation.getName())

                    # img.png
                    image_name = transformation.getParam().split('/')[-1]

                    # {sceneName}/zoneXX/static/img
                    image_prefix_name = image_name.replace('.png', '')
                    image_folder_path = os.path.join(static_features_path, image_prefix_name)
                    
                    if not os.path.exists(image_folder_path):
                        os.makedirs(image_folder_path)

                    features_path.append(image_folder_path)

                    # get image path to manage
                    # {sceneName}/static/img.png
                    transform_image_path = os.path.join(scene_path, transformation.getName(), image_name) 
                    static_transform_image = Image.open(transform_image_path)

                    static_transform_image_block = divide_in_blocks(static_transform_image, cfg.keras_img_size)[id_zone]

                    # generate augmented data
                    dt.augmented_data_image(static_transform_image_block, image_folder_path, image_prefix_name)

                else:
                    features_interval_path = os.path.join(zone_path, transformation.getTransformationPath())
                    features_path.append(features_interval_path)

            # as labels are same for each features
            for label in os.listdir(features_path[0]):

                if (label == cfg.not_noisy_folder and _only_noisy == 0) or label == cfg.noisy_folder:
                    
                    label_features_path = []

                    for path in features_path:
                        label_path = os.path.join(path, label)
                        label_features_path.append(label_path)

                    # getting images list for each features
                    features_images_list = []
                        
                    for index_features, label_path in enumerate(label_features_path):

                        if _transformations[index_features].getName() == 'static':
                            # by default append nothing..
                            features_images_list.append([])
                        else:
                            images = sorted(os.listdir(label_path))
                            features_images_list.append(images)

                    # construct each line using all images path of each
                    for index_image in range(0, len(features_images_list[0])):
                        
                        images_path = []

                        # get information about rotation and flip from first transformation (need to be a not static transformation)
                        current_post_fix =  features_images_list[0][index_image].split(cfg.post_image_name_separator)[-1]

                        # getting images with same index and hence name for each features (transformation)
                        for index_features in range(0, len(features_path)):

                            # custom behavior for static transformation (need to check specific image)
                            if _transformations[index_features].getName() == 'static':
                                # add static path with selecting correct data augmented image
                                image_name = _transformations[index_features].getParam().split('/')[-1].replace('.png', '')
                                img_path = os.path.join(features_path[index_features], image_name + cfg.post_image_name_separator + current_post_fix)
                                images_path.append(img_path)
                            else:
                                img_path = features_images_list[index_features][index_image]
                                images_path.append(os.path.join(label_features_path[index_features], img_path))

                        # get information about rotation and flip
                        current_post_fix = images_path[0].split(cfg.post_image_name_separator)[-1]

                        # get ref block which matchs we same information about rotation and flip
                        augmented_ref_image_block_path = next(img for img in ref_augmented_images 
                                                              if img.split(cfg.post_image_name_separator)[-1] == current_post_fix)

                        line = augmented_ref_image_block_path + ';'

                        # compute line information with all images paths
                        for id_path, img_path in enumerate(images_path):
                            if id_path < len(images_path) - 1:
                                line = line + img_path + '::'
                            else:
                                line = line + img_path
                        
                        line = line + '\n'

                        if id_zone < _nb_zones and folder_scene in _scenes:
                            train_file_data.append(line)
                        else:
                            test_file_data.append(line)

    train_file = open(output_train_filename, 'w')
    test_file = open(output_test_filename, 'w')

    random.shuffle(train_file_data)
    random.shuffle(test_file_data)

    for line in train_file_data:
        train_file.write(line)

    for line in test_file_data:
        test_file.write(line)

    train_file.close()
    test_file.close()

def main():

    parser = argparse.ArgumentParser(description="Compute specific dataset for model using of features")

    parser.add_argument('--output', type=str, help='output file name desired (.train and .test)')
    parser.add_argument('--features', type=str, 
                                     help="list of features choice in order to compute data",
                                     default='svd_reconstruction, ipca_reconstruction',
                                     required=True)
    parser.add_argument('--params', type=str, 
                                    help="list of specific param for each features choice (See README.md for further information in 3D mode)", 
                                    default='100, 200 :: 50, 25',
                                    required=True)
    parser.add_argument('--scenes', type=str, help='List of scenes to use for training data')
    parser.add_argument('--nb_zones', type=int, help='Number of zones to use for training data set', choices=list(range(1, 17)))
    parser.add_argument('--renderer', type=str, help='Renderer choice in order to limit scenes used', choices=cfg.renderer_choices, default='all')
    parser.add_argument('--random', type=int, help='Data will be randomly filled or not', choices=[0, 1])
    parser.add_argument('--only_noisy', type=int, help='Only noisy will be used', choices=[0, 1])

    args = parser.parse_args()

    p_filename   = args.output
    p_features   = list(map(str.strip, args.features.split(',')))
    p_params     = list(map(str.strip, args.params.split('::')))
    p_scenes     = args.scenes.split(',')
    p_nb_zones   = args.nb_zones
    p_renderer   = args.renderer
    p_random     = args.random
    p_only_noisy = args.only_noisy

    # create list of Transformation
    transformations = []

    for id, features in enumerate(p_features):

        if features not in features_choices:
            raise ValueError("Unknown features, please select a correct features : ", features_choices)

        transformations.append(Transformation(features, p_params[id]))

    # list all possibles choices of renderer
    scenes_list = dt.get_renderer_scenes_names(p_renderer)
    scenes_indices = dt.get_renderer_scenes_indices(p_renderer)

    # getting scenes from indexes user selection
    scenes_selected = []

    for scene_id in p_scenes:
        index = scenes_indices.index(scene_id.strip())
        scenes_selected.append(scenes_list[index])

    # create database using img folder (generate first time only)
    generate_data_model(scenes_list, p_filename, transformations, scenes_selected, p_nb_zones, p_random, p_only_noisy)

if __name__== "__main__":
    main()
<file_sep>/generate/generate_reconstructed_data.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Jun 19 11:47:42 2019

@author: jbuisine
"""

# main imports
import sys, os, argparse
import numpy as np

# images processing imports
from PIL import Image
from ipfml.processing.segmentation import divide_in_blocks

# modules imports
sys.path.insert(0, '') # trick to enable import of main folder module

import custom_config as cfg
from modules.utils.data import get_scene_image_quality
from modules.classes.Transformation import Transformation

# getting configuration information
config_filename         = cfg.config_filename
zone_folder             = cfg.zone_folder
min_max_filename        = cfg.min_max_filename_extension

# define all scenes values
scenes_list             = cfg.scenes_names
scenes_indexes          = cfg.scenes_indices
path                    = cfg.dataset_path
zones                   = cfg.zones_indices
seuil_expe_filename     = cfg.seuil_expe_filename

features_choices        = cfg.features_choices_labels
output_data_folder      = cfg.output_data_folder

generic_output_file_svd = '_random.csv'

def generate_data(transformation):
    """
    @brief Method which generates all .csv files from scenes
    @return nothing
    """

    scenes = os.listdir(path)
    # remove min max file from scenes folder
    scenes = [s for s in scenes if min_max_filename not in s]

    # go ahead each scenes
    for id_scene, folder_scene in enumerate(scenes):

        print(folder_scene)
        scene_path = os.path.join(path, folder_scene)

        config_file_path = os.path.join(scene_path, config_filename)

        with open(config_file_path, "r") as config_file:
            last_image_name = config_file.readline().strip()
            prefix_image_name = config_file.readline().strip()
            start_index_image = config_file.readline().strip()
            end_index_image = config_file.readline().strip()
            step_counter = int(config_file.readline().strip())

        # construct each zones folder name
        zones_folder = []
        features_folder = []
        zones_threshold = []

        # get zones list info
        for index in zones:
            index_str = str(index)
            if len(index_str) < 2:
                index_str = "0" + index_str

            current_zone = "zone"+index_str
            zones_folder.append(current_zone)
            zone_path = os.path.join(scene_path, current_zone)

            with open(os.path.join(zone_path, cfg.seuil_expe_filename)) as f:
                zones_threshold.append(int(f.readline()))

            # custom path for feature
            feature_path = os.path.join(zone_path, transformation.getName())

            if not os.path.exists(feature_path):
                os.makedirs(feature_path)

            # custom path for interval of reconstruction and feature
            feature_interval_path = os.path.join(zone_path, transformation.getTransformationPath())
            features_folder.append(feature_interval_path)

            if not os.path.exists(feature_interval_path):
                os.makedirs(feature_interval_path)

            # create for each zone the labels folder
            labels = [cfg.not_noisy_folder, cfg.noisy_folder]

            for label in labels:
                label_folder = os.path.join(feature_interval_path, label)

                if not os.path.exists(label_folder):
                    os.makedirs(label_folder)


        # get all images of folder
        scene_images = sorted([os.path.join(scene_path, img) for img in os.listdir(scene_path) if cfg.scene_image_extension in img])
        number_scene_image = len(scene_images)

        # for each images
        for id_img, img_path in enumerate(scene_images):

            current_img = Image.open(img_path)
            img_blocks = divide_in_blocks(current_img, cfg.keras_img_size)

            current_quality_index = int(get_scene_image_quality(img_path))

            for id_block, block in enumerate(img_blocks):

                ##########################
                # Image computation part #
                ##########################
                
                # pass block to grey level


                output_block = transformation.getTransformedImage(block)
                output_block = np.array(output_block, 'uint8')
                
                # current output image
                output_block_img = Image.fromarray(output_block)

                label_path = features_folder[id_block]

                # get label folder for block
                if current_quality_index > zones_threshold[id_block]:
                    label_path = os.path.join(label_path, cfg.not_noisy_folder)
                else:
                    label_path = os.path.join(label_path, cfg.noisy_folder)

                # Data augmentation!
                rotations = [0, 90, 180, 270]
                img_flip_labels = ['original', 'horizontal', 'vertical', 'both']

                horizontal_img = output_block_img.transpose(Image.FLIP_LEFT_RIGHT)
                vertical_img = output_block_img.transpose(Image.FLIP_TOP_BOTTOM)
                both_img = output_block_img.transpose(Image.TRANSPOSE)

                flip_images = [output_block_img, horizontal_img, vertical_img, both_img]

                # rotate and flip image to increase dataset size
                for id, flip in enumerate(flip_images):
                    for rotation in rotations:
                        rotated_output_img = flip.rotate(rotation)

                        output_reconstructed_filename = img_path.split('/')[-1].replace('.png', '') + '_' + zones_folder[id_block] + cfg.post_image_name_separator
                        output_reconstructed_filename = output_reconstructed_filename + img_flip_labels[id] + '_' + str(rotation) + '.png'
                        output_reconstructed_path = os.path.join(label_path, output_reconstructed_filename)

                        rotated_output_img.save(output_reconstructed_path)

            print(transformation.getName() + "_" + folder_scene + " - " + "{0:.2f}".format(((id_img + 1) / number_scene_image)* 100.) + "%")
            sys.stdout.write("\033[F")

        print('\n')

    print("%s_%s : end of data generation\n" % (transformation.getName(), transformation.getParam()))


def main():

    parser = argparse.ArgumentParser(description="Compute and prepare data of feature of all scenes using specific interval if necessary")

    parser.add_argument('--features', type=str, 
                                     help="list of features choice in order to compute data",
                                     default='svd_reconstruction, ipca_reconstruction',
                                     required=True)
    parser.add_argument('--params', type=str, 
                                    help="list of specific param for each feature choice (See README.md for further information in 3D mode)", 
                                    default='100, 200 :: 50, 25',
                                    required=True)

    args = parser.parse_args()

    p_features = list(map(str.strip, args.features.split(',')))
    p_params   = list(map(str.strip, args.params.split('::')))

    transformations = []

    for id, feature in enumerate(p_features):

        if feature not in features_choices:
            raise ValueError("Unknown feature, please select a correct feature : ", features_choices)

        transformations.append(Transformation(feature, p_params[id]))

    # generate all or specific feature data
    for transformation in transformations:
        generate_data(transformation)

if __name__== "__main__":
    main()
<file_sep>/README.md
# Denoising with autoencoder

## Description

Utilisation d'un autoencoder pour apprendre statistiquement comment il est possible de générer une image de synthèse.

Input : 
- Noisy image
- Z-buffer
- Normal card

or other information...

Output :
- Reference image

## Requirements

```bash
git clone --recursive https://github.com/prise-3d/Thesis-Denoising-autoencoder.git XXXXX
```

```bash
pip install -r requirements.txt
```

## How to use ?

[Autoencoder keras documentation](https://blog.keras.io/building-autoencoders-in-keras.html)

Generate reconstructed data from specific method of reconstruction (run only once time or clean data folder before):
```
python generate/generate_reconstructed_data.py -h
```

Generate custom dataset from one reconstructed method or multiples (implemented later)
```
python generate/generate_dataset.py -h
```

### Reconstruction parameter (--params)

List of expected parameter by reconstruction method:
- **svd_reconstruction:** Singular Values Decomposition
  - Param definition: *interval data used for reconstruction (begin, end)*
  - Example: *"100, 200"*
- **ipca_reconstruction:** Iterative Principal Component Analysis
  - Param definition: *number of components used for compression and batch size*
  - Example: *"30, 35"*
- **fast_ica_reconstruction:**  Fast Iterative Component Analysis
  - Param definition: *number of components used for compression*
  - Example: *"50"*
- **static** Use static file to manage (such as z-buffer, normals card...)
  - Param definition: *Name of image of scene need to be in {sceneName}/static/xxxx.png*
  - Example: *"img.png"*

**__Example:__**
```bash
python generate/generate_dataset.py --output data/output_data_filename --metrics "svd_reconstruction, ipca_reconstruction, fast_ica_reconstruction" --renderer "maxwell" --scenes "A, D, G, H" --params "100, 200 :: 50, 10 :: 50" --nb_zones 10 --random 1 --only_noisy 1
```

Then, run the model:
```bash
python image_denoising --data data/my_dataset --output output_model_name
```

## License

[The MIT license](https://github.com/prise-3d/Thesis-NoiseDetection-metrics/blob/master/LICENSE)
<file_sep>/requirements.txt
Pillow
keras
tensorflow
sklearn
matplotlib
path.py
ipfml
cv2<file_sep>/custom_config.py
from modules.config.cnn_config import *

# store all variables from cnn config
context_vars = vars()

# Custom config used for redefined config variables if necessary

# folders

## noisy_folder                    = 'noisy'
## not_noisy_folder                = 'notNoisy'

# file or extensions

## post_image_name_separator       = '___'

# variables

## features_choices_labels         = ['static', 'svd_reconstruction', 'fast_ica_reconstruction', 'ipca_reconstruction']

# parameters

keras_epochs                    = 200
## keras_batch                     = 32
## val_dataset_size                = 0.2

## keras_img_size                  = (200, 200)