function layer = instanceNormalizationLayer(NameValueArgs)
% instanceNormalizationLayer   Instance normalization layer
%
%   layer = instanceNormalizationLayer creates an instance normalization layer.
%   This type of instance normalizes the input data across each channel and
%   applies an affine transformation with learnable parameters.
%
%   layer = instanceNormalizationLayer('PARAM1', VAL1, ...)
%   specifies optional parameter name/value pairs for creating the layer:
%
%       'Name'                    - A name for the layer. Default is ''.
%
%       'Offset'                  - Initial value for learnable parameter
%                                   Offset (also called beta). This can be:
%                                     - []
%                                     - a 1-by-1-by-C array (for 2-D images)
%                                     - a 1-by-1-by-1-by-C array (for 3-D
%                                       images)
%                                     - a C-by-1 array (for sequence and
%                                       feature vectors)
%                                   where C is the number of channels.
%
%                                   The default is [].
%
%       'Scale'                   - Initial value for learnable parameter
%                                   Scale (also called gamma). This can be:
%                                     - []
%                                     - a 1-by-1-by-C array (for 2-D images)
%                                     - a 1-by-1-by-1-by-C array (for 3-D
%                                       images)
%                                     - a C-by-1 array (for sequence and
%                                       feature vectors)
%                                   where C is the number of channels.
%                                   
%                                   The default is [].
%
%       'Epsilon'                 - Offset for the variance to avoid
%                                   divide-by-zero errors, specified as a
%                                   positive scalar.
%
%                                   The default is 1e-5.
%
%       'OffsetInitializer'       - Function to initialize the Offset,
%                                   specified as 'narrow-normal', 'zeros',
%                                   'ones' or a function handle. The
%                                   default is 'zeros'.
%
%       'ScaleInitializer'        - Function to initialize the Scale,
%                                   specified as 'narrow-normal', 'ones' or
%                                   a function handle. The default is
%                                   'ones'.
%
%       'OffsetLearnRateFactor'   - Multiplier for the learning rate of
%                                   Offset. Default is 1.
%
%       'ScaleLearnRateFactor'    - Multiplier for the learning rate of
%                                   Scale. Default is 1.
%
%       'OffsetL2Factor'          - Multiplier for the L2 weight regulariser
%                                   for Offset. Default is 1.
%
%       'ScaleL2Factor'           - Multiplier for the L2 weight regulariser
%                                   for Scale. Default is 1.
%
%   Example:
%     % Create an instance normalization layer.
%
%     layer = instanceNormalizationLayer;
%
%   See also nnet.cnn.layer.instanceNormalizationLayer.
%
%   <a href="matlab:helpview('deeplearning','list_of_layers')">List of Deep Learning Layers</a>

%   Copyright 2020-2022 The MathWorks, Inc.

arguments
    NameValueArgs.Name                   {iAssertValidLayerName} = ""
    NameValueArgs.Epsilon                (1,1) = 1e-5
    NameValueArgs.Offset                 = []
    NameValueArgs.Scale                  = []
    NameValueArgs.OffsetLearnRateFactor  (1,1) {iAssertValidFactor} = 1
    NameValueArgs.ScaleLearnRateFactor   (1,1) {iAssertValidFactor} = 1
    NameValueArgs.OffsetL2Factor         (1,1) {iAssertValidFactor} = 1
    NameValueArgs.ScaleL2Factor          (1,1) {iAssertValidFactor} = 1
    NameValueArgs.OffsetInitializer      = 'zeros'
    NameValueArgs.ScaleInitializer       = 'ones'
end

NameValueArgs.Epsilon = iValidateAndConvertEpsilon(NameValueArgs.Epsilon);
NameValueArgs.Name = convertStringsToChars(NameValueArgs.Name);

NameValueArgs = nnet.internal.cnn.layer.util.gatherParametersToCPU(NameValueArgs);

% Create an internal representation of the layer.
internalLayer = nnet.internal.cnn.layer.InstanceNormalization(NameValueArgs.Name, [], NameValueArgs.Epsilon);

internalLayer.Offset.L2Factor = NameValueArgs.OffsetL2Factor;
internalLayer.Offset.LearnRateFactor = NameValueArgs.OffsetLearnRateFactor;

internalLayer.Scale.L2Factor = NameValueArgs.ScaleL2Factor;
internalLayer.Scale.LearnRateFactor = NameValueArgs.ScaleLearnRateFactor;

% Use the internal layer to construct a user visible layer.
layer = nnet.cnn.layer.InstanceNormalizationLayer(internalLayer);

% Set these properties (if supplied) using the visible layer so that the
% number of channels is correctly checked.
layer = iSetPropertyIfNonEmpty(layer, NameValueArgs, ...
    {'Offset', 'Scale' ...
    'OffsetInitializer', 'ScaleInitializer'});
end


function iAssertValidLayerName(name)
nnet.internal.cnn.layer.paramvalidation.validateLayerName(name)
end

function epsilon = iValidateAndConvertEpsilon(value)
epsilon = nnet.internal.cnn.layer.paramvalidation...
    .validateAndConvertEpsilon(value);
end

function iAssertValidFactor(value)
nnet.internal.cnn.layer.paramvalidation.validateLearnFactor(value);
end

function layer = iSetPropertyIfNonEmpty(layer, args, propertyNames)
for i=1:numel(propertyNames)
    name = propertyNames{i};
    if ~isempty(args.(name))
        layer.(name) = args.(name);
    end
end
end