function layer = crop3dLayer(varargin)
%crop3dLayer 3-D crop layer
%   layer = crop3dLayer() creates a layer for center cropping an input
%   feature map. The size, [H W D], of the cropped region is automatically
%   determined using the size of a second input feature map connected to
%   this layer. After creating this layer, use connectLayers to connect a
%   second input.
%
%   layer = crop3dLayer([X Y Z]) creates a layer for cropping an input
%   feature map. [X Y Z] specifies the upper-left-front corner location of
%   a cropping window positioned within the first input feature map. X is
%   the location in the horizontal direction, Y is the location in the
%   vertical direction and Z is the location in the depth dimension. The
%   size, [H W D], of the cropping window is automatically determined by
%   the size of the second input feature map connected to this layer. Use
%   connectLayers to connect a second input to this layer.
%
%   layer = crop3dLayer(..., Name, Value) specifies optional parameter
%   name-value pairs for creating the layer:
%
%      'Name'      - A name for the layer. The default is ''.
%
%   A 3-D crop layer has the following inputs:
%      'in'        - Input feature map to crop.
%      'ref'       - Reference feature map whose first three dimensions,
%                    [H W D], are used to determine the corresponding first
%                    three dimensions of the cropped output.
%
% Example:
%    % Create a 3-D crop layer and connect both of its inputs using a
%    % layerGraph object.
%
%    layers = [
%        image3dInputLayer([32 32 32 3], 'Name', 'image')
%        convolution3dLayer(3,16,'Padding','same','Name','conv')
%        crop3dLayer('Name', 'crop')
%        concatenationLayer(4,2,'Name','concat')
%        ]
%
%    % Create a layerGraph. The first input of crop3dLayer is automatically
%    % connected to the output of the convolution3dLayer.
%    lgraph = layerGraph(layers)
%
%    % Add a maxPooling layer to the layer Graph
%    maxPool = maxPooling3dLayer(2,'stride',2,'Name','pool');
%    lgraph = addLayers(lgraph,maxPool);
%    lgraph = connectLayers(lgraph, 'image', 'pool')
%
%    % Connect the second input to the maxPooling layer output.
%    lgraph = connectLayers(lgraph, 'pool', 'crop/ref')
%
%    % Concatenate crop layer and maxpooling layer output
%    lgraph = connectLayers(lgraph, 'pool', 'concat/in2')
%
% See also nnet.cnn.layer.Crop3DLayer, pixelClassificationLayer,
%          layerGraph, trainNetwork.

% Copyright 2019-2023 The MathWorks, Inc.

args = iParseArguments(varargin{:});

internalLayer = nnet.internal.cnn.layer.Crop3D(args.Name, args.CropLocation);

layer = nnet.cnn.layer.Crop3DLayer(internalLayer);
end

%--------------------------------------------------------------------------
function args = iParseArguments(varargin)
p = inputParser();
p.addOptional('CropLocation','centercrop',@iIsValidLocation);
p.addParameter('Name', '', @nnet.internal.cnn.layer.paramvalidation.validateLayerName);
p.parse(varargin{:});
userInput = p.Results;

if ischar(userInput.CropLocation) || isstring(userInput.CropLocation)
    args.CropLocation = 'centercrop';
else
    userInput.CropLocation = iConvertToDouble(userInput.CropLocation);
    if isscalar(userInput.CropLocation)
        args.CropLocation = repelem(userInput.CropLocation,1,3);
    else
        args.CropLocation = userInput.CropLocation;
    end
    
end
args.Name = char(userInput.Name);
end

%--------------------------------------------------------------------------
function flag = iIsValidLocation(sz)
validateattributes(sz, {'numeric','char','string'}, {}, mfilename, 'CropLocation');
if isstring(sz) || ischar(sz)
    validatestring(sz, {'centercrop'}, 1);
else
    validateattributes(sz, {'numeric'}, ...
        {'real', 'finite', 'nonsparse','integer','positive'},...
        mfilename, 'CropLocation');
    
    % must be scalar or 3-element vector
    if ~(isscalar(sz) || iIsThreeElementVector(sz))
        error(message('nnet_cnn:layer:Crop3DLayer:crop3dInvalidLocation'));
    end
end
flag = true;
end

%--------------------------------------------------------------------------
function tf = iIsThreeElementVector(sz)
tf = isvector(sz) && numel(sz) == 3;
end

function value = iConvertToDouble(value)
value = nnet.internal.cnn.layer.util.convertToDouble(value);
end
