function X = crosschannelnorm(X, channelWindowSize, nvArgs)
%CROSSCHANNELNORM   Cross channel square-normalize using local responses
%
%   Y = CROSSCHANNELNORM(X,CHANNELWINDOWSIZE) normalizes each channel of
%   the input data X by the sum of the squared values of the elements in
%   the channel window defined by CHANNELWINDOWSIZE. Input X must be a
%   formatted dlarray with up to two spatial dimensions. CHANNELWINDOWSIZE
%   must be a scalar. Output Y is a formatted dlarray with the same
%   dimension labels as X.
%
%   Y = CROSSCHANNELNORM(X,CHANNELWINDOWSIZE,'DataFormat',FMT) computes the
%   cross-channel normalization of an unformatted dlarray X. FMT specifies
%   the dimension labels of the input data X. The output Y is an
%   unformatted dlarray with the same dimension order as X.
%
%   Y = CROSSCHANNELNORM(__,'PARAM',VAL) specifies optional
%   parameter name/value pairs:
%
%       'Alpha'           - Normalization constant that multiplies the sum
%                           of the squared values, specified as a scalar.
%                           The default value is 1e-4.
%       'Beta'            - Contrast constant used as exponent of the
%                           normalizing value, specified as a scalar
%                           greater than or equal to 0.01. The default
%                           value is 0.75.
%       'K'               - Normalization hyperparameter used for avoiding
%                           singularities in the normalization, specified as
%                           a scalar greater than or equal to 1e-5. The
%                           default value is 2.
%       'DataFormat'      - Dimension labels of the input data X, specified
%                           as a char array or string. Required if X is an
%                           unformatted dlarray.
%
%   Example
%       % Create a formatted dlarray with two spatial, one channel, and one
%       % observation dimensions
%       width = 12;
%       height = 12;
%       numChannels = 3;
%       numObservations = 16;
%       X = dlarray(randn(width,height,numChannels,numObservations),'SSCB');
%
%       % Compute cross-channel normalization over a channel window of size
%       % 3.
%       Z = crosschannelnorm(X,3);
%
%   See also DLARRAY, RELU, AVGPOOL, BATCHNORM

%   Copyright 2019-2021 The MathWorks, Inc.

arguments
    X {mustBeNumeric, mustBeNonsparse, mustBeReal}
    channelWindowSize (1,1) {deep.internal.dlarray.mustBeNonDlarray, ...
        mustBeNumeric, mustBeFinite, mustBeInteger, mustBePositive}
    nvArgs.Alpha (1,1) {deep.internal.dlarray.mustBeNonDlarray, ...
        mustBeNumeric, mustBeReal} = 0.0001
    nvArgs.Beta (1,1) {deep.internal.dlarray.mustBeNonDlarray, ...
        mustBeNumeric, mustBeGreaterThanOrEqual(nvArgs.Beta,0.01)} = 0.75
    nvArgs.K (1,1) {deep.internal.dlarray.mustBeNonDlarray, ...
        mustBeNumeric, mustBeGreaterThanOrEqual(nvArgs.K,1e-5)} = 2
    nvArgs.DataFormat {deep.internal.dlarray.mustBeDataFormatType} = ''
end

% Only data defines execution environment, not parameters
channelWindowSize = gather(channelWindowSize);
nvArgs.Alpha = gather(nvArgs.Alpha);
nvArgs.Beta = gather(nvArgs.Beta);
nvArgs.K = gather(nvArgs.K);

% Handle input format
[X,perm] = deep.internal.dlarray.validateDataFormatArg(X, nvArgs.DataFormat);

% Find and validate the number of spatial dimensions
minSDims = 0;
maxSDims = 2;
[numSpatialDims,validLabels] = deep.internal.dlarray.validateSpatialDimensions(dims(X),minSDims,maxSDims);
if ~validLabels
    error(message('deep:dlarray:UnsupportedNumberOfSpatialDimensionsCrossChannelNorm',minSDims,maxSDims));
end

internalArgs = {channelWindowSize, nvArgs.Alpha, nvArgs.Beta, nvArgs.K};
X = deep.internal.dlarray.normalizeData(X, numSpatialDims, maxSDims, @internal_crosschannelnorm, internalArgs);

% Remove dimensions from the dlarray if it was passed as unformatted.
if ~isempty(nvArgs.DataFormat)
    X = stripdims(X);
    X = ipermute(X, perm); % Permutes back in the same order as DataFormat
end
end
