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.
19 from extensions.ops.DetectionOutput import DetectionOutput
20 from extensions.ops.splitv import SplitV
21 from mo.front.subgraph_matcher import SubgraphMatch
22 from mo.front.tf.replacement import FrontReplacementFromConfigFileSubGraph
23 from mo.graph.graph import Node, Graph
24 from mo.ops.concat import Concat
25 from mo.ops.const import Const
26 from mo.ops.eltwise import Eltwise
27 from mo.ops.power import Power
28 from mo.ops.reshape import Reshape
31 class RetinaNetFilteredDetectionsReplacement(FrontReplacementFromConfigFileSubGraph):
33 The class replaces the sub-graph that performs boxes post-processing and NMS with the DetectionOutput layer.
35 The post-processing in the RetinaNet topology is performed differently from the DetectionOutput layer implementation
36 in the Inference Engine. The first one calculates (d_x1, d_y1, d_x2, d_y2) which are a factor of the prior box width
37 and height. The DetectionOuput with "code_type" equal to "caffe.PriorBoxParameter.CORNER" just adds predicted deltas
38 to the prior box coordinates. This replacer add nodes which calculate prior box widths and heights, apply variances
39 to the predicated box coordinates and multiply them. With this approach the DetectionOutput layer with "code_type"
40 equal to "caffe.PriorBoxParameter.CORNER" produces the same result as the post-processing in the original topology.
42 replacement_id = 'RetinaNetFilteredDetectionsReplacement'
45 def _create_sub(graph: Graph, input_1: Node, port_1: int, input_2: Node, port_2: int):
46 negate = Power(graph, dict(scale=-1, name=input_2.name + '/negate_'))
47 add = Eltwise(graph, dict(operation='sum', name=input_1.name + '/add_'))
48 out_node = add.create_node([(input_1, port_1), negate.create_node([(input_2, port_2)])])
51 def output_edges_match(self, graph: Graph, match: SubgraphMatch, new_sub_graph: dict):
52 return {match.output_node(0)[0].id: new_sub_graph['detection_output_node'].id}
54 def nodes_to_remove(self, graph: Graph, match: SubgraphMatch):
55 new_nodes_to_remove = match.matched_nodes_names()
56 new_nodes_to_remove.remove(match.single_input_node(0)[0].id)
57 new_nodes_to_remove.remove(match.single_input_node(1)[0].id)
58 new_nodes_to_remove.remove(match.single_input_node(2)[0].id)
59 return new_nodes_to_remove
61 def generate_sub_graph(self, graph: Graph, match: SubgraphMatch):
62 reshape_classes_op = Reshape(graph, {'dim': np.array([0, -1])})
63 reshape_classes_node = reshape_classes_op.create_node([match.single_input_node(1)[0]],
64 dict(name='do_reshape_classes'))
66 priors_node = match.single_input_node(2)[0]
68 placeholder = [Node(graph, node_id) for node_id in graph.nodes() if Node(graph, node_id).op == 'Placeholder'][0]
69 im_height = placeholder.shape[1]
70 im_width = placeholder.shape[2]
72 # scale prior boxes to the [0, 1] interval
73 priors_scale_const_node = Const(graph, {'value': np.array([1 / im_width,
76 1 / im_height])}).create_node([])
77 priors_scale_node = Eltwise(graph, {'name': 'scale_priors', 'operation': 'mul'}).create_node(
78 [priors_node, priors_scale_const_node])
80 # calculate prior boxes widths and heights
81 split_node = SplitV(graph, {'axis': 2, 'size_splits': [1, 1, 1, 1], 'out_ports_count': 4}).create_node([priors_scale_node])
82 priors_width_node = __class__._create_sub(graph, split_node, 2, split_node, 0)
83 priors_height_node = __class__._create_sub(graph, split_node, 3, split_node, 1)
85 # concat weights and heights into a single tensor and multiple with the box coordinates regression values
86 concat_width_height_node = Concat(graph, {'name': 'concat_priors_width_height', 'axis': -1, 'in_ports_count': 4}).create_node(
87 [priors_width_node, priors_height_node, priors_width_node, priors_height_node])
88 applied_width_height_regressions_node = Eltwise(graph, {'name': 'final_regressions', 'operation': 'mul'}). \
89 create_node([concat_width_height_node, match.single_input_node(0)[0]])
91 # reshape to 2D tensor as Inference Engine Detection Output layer expects
92 reshape_regression_op = Reshape(graph, {'dim': np.array([0, -1])})
93 reshape_regression_node = reshape_regression_op.create_node([applied_width_height_regressions_node],
94 {'name': 'reshape_regression'})
96 detection_output_op = DetectionOutput(graph, match.custom_replacement_desc.custom_attributes)
97 detection_output_op.attrs['old_infer'] = detection_output_op.attrs['infer']
98 detection_output_op.attrs['infer'] = __class__.do_infer
99 detection_output_node = detection_output_op.create_node(
100 [reshape_regression_node, reshape_classes_node, priors_scale_node],
101 dict(name=detection_output_op.attrs['type'], clip=1, normalized=1, variance_encoded_in_target=0))
103 return {'detection_output_node': detection_output_node}
107 # append variances to the tensor with boxes regressions
108 prior_boxes = node.in_node(2).value
109 assert prior_boxes is not None, "The prior boxes are not constants"
110 if prior_boxes is not None:
111 variances = np.tile(node.variance, [prior_boxes.shape[-2], 1])
112 prior_boxes = prior_boxes.reshape([-1, 4])
113 prior_boxes = np.concatenate((prior_boxes, variances), 0)
114 # adding another dimensions, as the prior-boxes are expected as 3d tensor
115 prior_boxes = prior_boxes.reshape((1, 2, -1))
116 node.in_node(2).shape = np.array(prior_boxes.shape, dtype=np.int64)
117 node.in_node(2).value = prior_boxes