Publishing R3
[platform/upstream/dldt.git] / model-optimizer / mo / ops / pad.py
1 """
2  Copyright (c) 2018 Intel Corporation
3
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
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
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.
15 """
16
17 import networkx as nx
18 import numpy as np
19
20 from mo.front.common.partial_infer.transpose import transpose_infer
21 from mo.front.extractor import attr_getter
22 from mo.ops.op import Op
23
24
25 class Pad(Op):
26     ''' Pad operation that explicitly extends an input tensor at edges.
27         
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
33         padding.
34
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:
39
40             [[pad_begin_dim1, pad_end_dim1],
41              [pad_begin_dim2, pad_end_dim2],
42              ...
43              [pad_begin_dimN, pad_end_dimN]]
44
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
47         format.
48     '''
49
50     op = 'Pad'
51     enabled = True
52
53     def __init__(self, graph: nx.MultiDiGraph, attrs: dict):
54         super().__init__(graph, {
55             # no 'type' as this operation is not directly supported by IE
56             'op': __class__.op,
57             'infer': __class__.infer,
58             'mode': 'constant',
59             'fill_value': float(0),
60             'pads': None
61         }, attrs)
62
63     def supported_attrs(self):
64         return ['mode', 'fill_value', 'pads']
65
66     def backend_attrs(self):
67         # it shouldn't be translated to IE layer
68         return []
69
70     @staticmethod
71     def infer(node):
72         if node.has_valid('pads'):
73             assert len(node.in_nodes()) == 1, "Pad operation has pads attribute and unexpected additional input argument for node {}.".format(node.name)
74             padding = node.pads
75         else:
76             assert len(node.in_nodes()) == 2, "Missing required second input argument for node {} and pads attribute is missing.".format(node.name)
77             padding = node.in_node(1).value
78
79         input_shape = node.in_node(0).shape
80         if padding is None or input_shape is None:
81             return None
82
83         # paddings can be defined, partially defined or undefined
84         # TODO for now we only handle fully defined paddings
85         # That means that intermediate tensor that delivers padding
86         # should have defined value and size Nx2
87         # TODO possible broadcasts are not supported
88         assert (padding.ndim == 2 and padding.shape[1] == 2)
89
90         # make sure that input has the same number of dimensions as the number of padding dimensions
91         assert (padding.shape[0] == len(input_shape)), \
92             "Input tensor shape {} and pads values {} do not match for Pad node {}".format(
93                 input_shape, padding.shape, node.name
94             )
95
96         # sum low and high padding values to calculate the shape modification vector
97         shape_change = np.add.reduce(padding, 1)
98         assert (shape_change.shape == input_shape.shape)
99
100         # preserve non-positive values in the input shape, because it has a special meaning
101         shape = np.array(
102             [shape_change[i] + input_shape[i] if input_shape[i] > 0 else input_shape[i] for i in range(len(input_shape))])
103
104         assert len(node.out_nodes()) == 1
105
106         node.out_node().shape = shape