Publishing 2019 R1 content
[platform/upstream/dldt.git] / model-optimizer / mo / ops / pad.py
1 """
2  Copyright (c) 2018-2019 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 logging as log
18
19 import numpy as np
20
21 from mo.graph.graph import Graph
22 from mo.ops.op import Op, PermuteAttrs
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: Graph, attrs: dict):
54         super().__init__(graph, {
55             'op': __class__.op,
56             'type': __class__.op,
57             'infer': __class__.infer,
58             'in_ports_count': 2,
59             'out_ports_count': 1,
60             'mode': 'constant',
61             'fill_value': float(0),
62             'pads': None
63         }, attrs)
64
65     def supported_attrs(self):
66         return ['mode', 'fill_value', 'pads']
67
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]))),
73                 ]
74
75     @staticmethod
76     def infer(node):
77         PermuteAttrs.create_permute_attrs(node, attrs=[('pads', 'input:0')])
78
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)
82         else:
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
88         padding = node.pads
89
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')))
93             return
94
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)
101
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
106             )
107
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)
111
112         # preserve non-positive values in the input shape, because it has a special meaning
113         shape = np.array(
114             [shape_change[i] + input_shape[i] if input_shape[i] > 0 else input_shape[i] for i in
115              range(len(input_shape))])
116
117         assert len(node.out_nodes()) == 1
118
119         node.out_node().shape = shape