# Copyright or © or Copr. IETR/INSA Rennes (2019)
#
# Contributors :
# Eduardo Fernandes-Montesuma eduardo.fernandes-montesuma@insa-rennes.fr (2019)
# Florian Lemarchand florian.lemarchand@insa-rennes.fr (2019)
#
#
# OpenDenoising is a computer program whose purpose is to benchmark image
# restoration algorithms.
#
# This software is governed by the CeCILL-C license under French law and
# abiding by the rules of distribution of free software. You can use,
# modify and/ or redistribute the software under the terms of the CeCILL-C
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL-C license and that you accept its terms.
import os
import numpy as np
from OpenDenoising.model import module_logger
from OpenDenoising.model import AbstractDeepLearningModel
try:
import matlab.engine
from matlab.engine import EngineError
from matlab.engine import MatlabExecutionError
MATLAB_IMPORTED = True
except ImportError as err:
module_logger.warning("Matlab engine was not installed correctly. Take a look on the documentation's tutorial for \
its installation.")
MATLAB_IMPORTED = False
def kwargs_to_string(**kwargs):
string = ""
for i, argname in enumerate(kwargs):
if i < len(kwargs) - 1:
string += "'{}', {},".format(argname, kwargs[argname])
else:
string += "'{}', {}".format(argname, kwargs[argname])
return string
[docs]class MatlabModel(AbstractDeepLearningModel):
"""Matlab Deep Learning toolbox wrapper class.
Notes
-----
To use this class you need a Matlab license with access to the Deep Learning toolbox.
Attributes
----------
logdir : str
Path to the directory where training logs will be saved.
See Also
--------
:class:`model.AbstractDenoiser` : for the basic functionalities of Image Denoisers.
:class:`model.AbstractDeepLearningModel` : for the basic functionalities of Deep Learning based Denoisers.
"""
[docs] def __init__(self, model_name="MatlabModel", logdir="./logs/Matlab", return_diff=False):
global MATLAB_IMPORTED
assert MATLAB_IMPORTED, "Got expcetion {} while importing matlab.engine. Check Matlab's Engine installation.".format(err)
try:
self.engine = matlab.engine.start_matlab()
except EngineError as err:
module_logger.exception("Matlab license error. Make sure you have a valid Matlab license.")
raise err
assert self.engine.license('test', 'neural_network_toolbox'), "Expected Neural Network Toolbox to be installed."
super().__init__(model_name, framework="Matlab", return_diff=return_diff)
self.logdir = logdir
[docs] def charge_model(self, model_function=None, model_path=None, **kwargs):
"""MatlabModel model charging functions
This function works by using Matlab's Python engine to make Matlab internal calls. You can either specify the
path to a Matlab function that will build the model, or to a .mat file holding the pretrained model.
Notes
-----
You may specify optional arguments to the model building function through keyword arguments.
Parameters
----------
model_function : str
String containing the path to the matlab .m function that will build the model. Note that this function
should return a list of layers.
model_path : str
String containing the path to the .mat file holding the trained network object.
"""
assert (model_function is not None or model_path is not None), "You should provide at least a model_function\
or a model_path to build your neural network\
model"
if model_function is not None:
model_function = model_function.split("/")
model_function_path = "/".join(model_function[:-1])
function_filename = model_function[-1]
function_name, _ = os.path.splitext(function_filename)
self.engine.addpath(os.path.abspath(model_function_path))
if self.engine.which(function_filename) == '':
raise ValueError("Function {} is not in PATH".format(model_function))
self.engine.evalc("layers = {}({});".format(function_name, kwargs_to_string(**kwargs)))
elif model_path is not None:
self.engine.evalc("load('{}');".format(model_path))
[docs] def train(self, train_generator, valid_generator=None, n_epochs=250, n_stages=500, learning_rate=1e-3,
optimizer_name="adam", valid_steps=10):
"""Trains a Matlab denoiser model.
Notes
-----
Instead of using Clean/Full/Blind dataset Python classes, you should use the class MatlabDatasetWrapper,
which exports a dataset to matlab workspace.
Parameters
----------
train_generator : str
Name of the matlab imageDatastore variable (for instance, if you have train_imds in your workspace you
should pass 'train_imds' for train_generator).
valid_generator : str
Name of the matlab imageDatastore variable (for instance, if you have train_imds in your workspace you
should pass 'valid_imds' for valid_generator).
n_epochs : int
Number of training epochs.
n_stages : int
Number of image batches are drawn during a training epoch.
learning_rate : float
Initial value for learning rate value for optimization.
optimizer_name : str
One among {'sgdm', 'adam', 'rmsprop'}
See Also
--------
:class:`data.MatlabDatasetWrapper` : for the class providing data to train such kinds of models.
"""
assert optimizer_name in ['sgdm', 'adam', 'rmsprop'], "Expected optimizer_name to be in {}, but got {}".format(
['sgdm', 'adam', 'rmsprop'], optimizer_name
)
if optimizer_name == None:
optimizer_name = "adam"
try:
self.engine.workspace.__getitem__("{}".format(train_generator))
except MatlabExecutionError as err:
raise MatlabExecutionError("Variable {} is not in Matlab's workspace."\
"Workspace:\n".format(train_generator, self.engine.workspace))
if valid_generator:
print("opts = trainingOptions('{}', {});".format(optimizer_name, kwargs_to_string(
Plots="'training-progress'", Verbose="true", VerboseFrequency=n_stages,
MaxEpochs=n_epochs, Shuffle="'every-epoch'", ValidationData="valid_imds",
CheckpointPath="'{}'".format(self.logdir)
)))
# Adds datasets to workspace
self.engine.evalc("opts = trainingOptions('{}', {});".format(optimizer_name, kwargs_to_string(
Plots="'training-progress'", Verbose="true", VerboseFrequency=n_stages,
MaxEpochs=n_epochs, Shuffle="'every-epoch'", ValidationData=valid_generator,
CheckpointPath="'{}'".format(self.logdir)
)))
else:
self.engine.evalc("opts = trainingOptions('{}', {});".format(optimizer_name, kwargs_to_string(
Plots="'training-progress'", Verbose="true", VerboseFrequency=n_stages,
MaxEpochs=n_epochs, Shuffle="'every-epoch'", CheckpointPath="'{}'".format(self.logdir)
)))
self.engine.evalc("net = trainNetwork({}, layers, opts)".format(train_generator))
self.engine.evalc("save('{}.mat', 'net')".format(self))
[docs] def __call__(self, image):
"""Denoises a batch of images.
Notes
-----
To perform inference on MatlabModels, you need to have a variable on Matlab's workspace called 'net'. This
variable is the output of a training session, or the result of a load('obj').
Parameters
----------
image: :class:`numpy.ndarray`
4D batch of noised images. It has shape: (batch_size, height, width, channels)
Returns
-------
:class:`numpy.ndarray`:
Restored batch of images, with same shape as the input.
"""
if image.shape[-1] in [1, 3]:
# Matlab's convention: height x width x channels x batch_size
image = np.transpose(image, [1, 2, 3, 0])
elif image.shape[1] in [1, 3]:
# Matlab's convention: height x width x channels x batch_size
image = np.transpose(image, [2, 3, 1, 0])
# Copy data into Matlab's workspace
self.engine.workspace['Xbatch'] = matlab.double(image.tolist())
# Denoises data using net object.
self.engine.evalc("predicted_image = denoiseImage(Xbatch, net);")
if not self.return_diff:
# Returns difference between predicted image and input image.
self.engine.evalc("predicted_image = Xbatch - predicted_image;")
return np.transpose(np.array(self.engine.workspace['predicted_image']), [3, 0, 1, 2])
[docs] def __len__(self):
"""Counts the number of parameters in the networks.
Returns
-------
nparams : int
Number of parameters in the network.
"""
n_params = 0
for i in range(1, eng.eval("length(net)") + 1):
if 'Convolution2DLayer' in eng.eval("class(net.Layers({}))".format(i)):
n_params += eng.eval("prod(size(net.Layers({}).Weights)) + prod(size(net.Layers({}).Bias))".format(i,
i))
if 'BatchNormalizationLayer' in eng.eval("class(net.Layers({}))".format(i)):
n_params += eng.eval("prod(size(net.Layers({}).Offset)) + prod(size(net.Layers({}).Scale))".format(i,
i))