2 Copyright (c) 2018-2019 Intel Corporation
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
21 from mo.graph.graph import Graph
22 from mo.ops.op import Op, PermuteAttrs
26 """ Pad operation that explicitly extends an input tensor at edges.
28 This operation frequently appears in TF and rarely in ONNX models
29 followed by some windowed operation like convolution or pooling.
30 The operation extends each (not only spatial) dimensions of input
31 tensors by new elements increasing output shape. The filling values
32 is defined by 'mode' and 'fill_value' attributes, but usually it is zero
35 The operation has two forms: with one or two input arguments.
36 The first aruments is an input tensor to be padded. The second
37 argument is an optional padding values of shape Nx2, where N is
38 a number of dimensions in an input tensor:
40 [[pad_begin_dim1, pad_end_dim1],
41 [pad_begin_dim2, pad_end_dim2],
43 [pad_begin_dimN, pad_end_dimN]]
45 where pad_begin_dim1 etc. are padding margins in elements. If the second
46 input argument is omitted, then it is in 'pads' attribute in the same
53 def __init__(self, graph: Graph, attrs: dict):
54 super().__init__(graph, {
57 'infer': __class__.infer,
61 'fill_value': float(0),
65 def supported_attrs(self):
66 return ['mode', 'fill_value', 'pads']
68 def backend_attrs(self):
69 return [('pad_mode', 'mode'),
70 ('pad_value', 'fill_value'),
71 ('pads_begin', lambda node: ','.join(map(str, node.pads[:, 0]))),
72 ('pads_end', lambda node: ','.join(map(str, node.pads[:, 1]))),
77 PermuteAttrs.create_permute_attrs(node, attrs=[('pads', 'input:0')])
79 if node.has_valid('pads'):
80 assert len(node.in_nodes()) == 1, "Pad operation has pads attribute and unexpected additional input " \
81 "argument for node {}.".format(node.name)
83 assert len(node.in_nodes()) >= 2, "Missing required second input argument for node {} and pads attribute " \
84 "is missing.".format(node.name)
85 node.pads = node.in_node(1).value
86 if len(node.in_nodes()) == 3: # the third input contains the fill value
87 node.fill_value = node.in_node(2).value
90 input_shape = node.in_node(0).shape
91 if padding is None or input_shape is None:
92 log.error('The paddings are not defined for node "{}"'.format(node.soft_get('name')))
95 # paddings can be defined, partially defined or undefined
96 # TODO for now we only handle fully defined paddings
97 # That means that intermediate tensor that delivers padding
98 # should have defined value and size Nx2
99 # TODO possible broadcasts are not supported
100 assert (padding.ndim == 2 and padding.shape[1] == 2)
102 # make sure that input has the same number of dimensions as the number of padding dimensions
103 assert (padding.shape[0] == len(input_shape)), \
104 "Input tensor shape {} and pads values {} do not match for Pad node {}".format(
105 input_shape, padding.shape, node.name
108 # sum low and high padding values to calculate the shape modification vector
109 shape_change = np.add.reduce(padding, 1)
110 assert (shape_change.shape == input_shape.shape)
112 # preserve non-positive values in the input shape, because it has a special meaning
114 [shape_change[i] + input_shape[i] if input_shape[i] > 0 else input_shape[i] for i in
115 range(len(input_shape))])
117 assert len(node.out_nodes()) == 1
119 node.out_node().shape = shape