function layer = sequenceUnfoldingLayer( varargin )
% sequenceUnfoldingLayer   Sequence unfolding layer
%
%   layer = sequenceUnfoldingLayer creates a layer that restores the
%   observation and sequence dimensions of its input.
%
%   layer = sequenceUnfoldingLayer('Name',name) creates a sequence
%   unfolding layer and specifies the layer name:
%
%       'Name'                    - A name for the layer. The default is
%                                   ''.
%
%   A sequence unfolding layer restores the observation dimension (N) and
%   the sequence dimension (S) of the input. The layer does not affect
%   inputs with no sequence dimension or sequence dimension 1. For example:
%
%   Description         | Size                        | Unfolded size
%   ----------------------------------------------------------------------
%   Vector sequences    | C x (N*S)                   | C x N x S
%   Image sequences     | H x W x C x (N*S)           | H x W x C x N x S
%   Images              | H x W x C x N               | H x W x C x N      (No effect)
%   3-D images          | H x W x D x C x N           | H x W x D x C x N  (No effect)
%
%   A sequence unfolding layer has two inputs. The inputs can be connected
%   to other layers using connectLayers.
%
%   sequenceUnfoldingLayer inputs:
%       'in'            - Input feature map.
%       'miniBatchSize' - Size of the mini-batch passed from the previous
%                         sequence folding layer. This input must be
%                         connected to the 'miniBatchSize' output of a
%                         sequence folding layer. 'miniBatchSize'
%                         corresponds to (N) in the description table
%                         above.
%
%   Example:
%       % Apply 2D convolution to sequences of images.
%
%       % Create layer array
%       layers = [ ...
%           sequenceInputLayer([28 28 1], 'Name', 'seqinput')
%           sequenceFoldingLayer('Name', 'seqfold')
%           convolution2dLayer(5, 20, 'Name', 'conv')
%           reluLayer('Name', 'relu')
%           maxPooling2dLayer(2, 'Stride', 2, 'Name', 'maxpool')
%           sequenceUnfoldingLayer('Name', 'sequnfold')
%           flattenLayer('Name', 'flatten')
%           lstmLayer(128, 'OutputMode', 'last', 'Name', 'lstm')
%           fullyConnectedLayer(2, 'Name', 'fc')
%           softmaxLayer('Name', 'softmax')
%           classificationLayer('Name', 'classout') ];
%
%       % Convert layers to a layer graph. 
%       lgraph = layerGraph(layers)
% 
%       % Connect sequence folding and unfolding layers.
%       lgraph = connectLayers(lgraph, 'seqfold/miniBatchSize', 'sequnfold/miniBatchSize');
%
%   See also nnet.cnn.layer.SequenceUnfoldingLayer, sequenceFoldingLayer.
%
%   <a href="matlab:helpview('deeplearning','list_of_layers')">List of Deep Learning Layers</a>

%   Copyright 2018 The MathWorks, Inc.

% Parse the input arguments.
inputArguments = iParseInputArguments(varargin{:});

% Create an internal representation of an unfolding layer.
internalLayer = nnet.internal.cnn.layer.SequenceUnfolding( ...
    inputArguments.Name, [] );

% Pass the internal layer to a function to construct a user visible layer.
layer = nnet.cnn.layer.SequenceUnfoldingLayer(internalLayer);
end

function inputArguments = iParseInputArguments(varargin)
parser = iCreateParser();
parser.parse(varargin{:});
userInput = parser.Results;

inputArguments = iConvertToCanonicalForm(userInput);
end

function p = iCreateParser()
p = inputParser;
defaultName = '';

addParameter(p, 'Name', defaultName, @iAssertValidLayerName);
end

function inputArguments = iConvertToCanonicalForm(params)
inputArguments = struct;
% Convert strings to char-vectors
inputArguments.Name = convertStringsToChars(params.Name);
end

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