Caffe2 - C++ API
A deep learning, cross platform ML framework
collect_and_distribute_fpn_rpn_proposals_op.cc
1 #include "caffe2/operators/collect_and_distribute_fpn_rpn_proposals_op.h"
2 
3 #ifdef CAFFE2_USE_MKL
4 #include "caffe2/mkl/operators/operator_fallback_mkl.h"
5 #endif // CAFFE2_USE_MKL
6 
7 namespace caffe2 {
8 
9 namespace utils {
10 
11 // Compute the area of an array of boxes.
12 ERArrXXf BoxesArea(const ERArrXXf& boxes) {
13  // equivalent to python code
14  // w = (boxes[:, 2] - boxes[:, 0] + 1)
15  // h = (boxes[:, 3] - boxes[:, 1] + 1)
16  // areas = w * h
17  // assert np.all(areas >= 0), 'Negative areas founds'
18  const auto w = boxes.col(2) - boxes.col(0) + 1;
19  const auto h = boxes.col(3) - boxes.col(1) + 1;
20  const ERArrXXf areas = w * h;
21  CAFFE_ENFORCE((areas >= 0).all(), "Negative areas founds: ", boxes);
22  return areas;
23 }
24 
25 // Determine which FPN level each RoI in a set of RoIs should map to based
26 // on the heuristic in the FPN paper.
27 ERArrXXf MapRoIsToFpnLevels(Eigen::Ref<const ERArrXXf> rois,
28  const float k_min, const float k_max,
29  const float s0, const float lvl0) {
30  // Compute level ids
31  ERArrXXf s = BoxesArea(rois).sqrt();
32  // s0 = cfg.FPN.ROI_CANONICAL_SCALE # default: 224
33  // lvl0 = cfg.FPN.ROI_CANONICAL_LEVEL # default: 4
34 
35  // Eqn.(1) in FPN paper
36  // equivalent to python code
37  // target_lvls = np.floor(lvl0 + np.log2(s / s0 + 1e-6))
38  // target_lvls = np.clip(target_lvls, k_min, k_max)
39  auto target_lvls = (lvl0 + (s / s0 + 1e-6).log() / log(2)).floor();
40  auto target_lvls_clipped = target_lvls.min(k_max).max(k_min);
41  return target_lvls_clipped;
42 }
43 
44 // Sort RoIs from highest to lowest individual RoI score based on
45 // values from scores array and limit to n results
46 void SortAndLimitRoIsByScores(Eigen::Ref<const EArrXf> scores, int n,
47  ERArrXXf& rois) {
48  CAFFE_ENFORCE(rois.rows() == scores.size(), "RoIs and scores count mismatch");
49  // Create index array with 0, 1, ... N
50  std::vector<int> idxs(rois.rows());
51  std::iota(idxs.begin(), idxs.end(), 0);
52  // Reuse a comparator based on scores and store a copy of RoIs that
53  // will be truncated and manipulated below
54  auto comp = [&scores](int lhs, int rhs) {
55  return scores(lhs) > scores(rhs);
56  };
57  ERArrXXf rois_copy = rois;
58  // Note that people have found nth_element + sort to be much faster
59  // than partial_sort so we use it here
60  if (n > 0 && n < rois.rows()) {
61  std::nth_element(idxs.begin(), idxs.begin() + n, idxs.end(), comp);
62  rois.resize(n, rois.cols());
63  } else {
64  n = rois.rows();
65  }
66  std::sort(idxs.begin(), idxs.begin() + n, comp);
67  // Update RoIs based on new order
68  for (int i = 0; i < n; i++) {
69  rois.row(i) = rois_copy.row(idxs[i]);
70  }
71 }
72 
73 // Updates arr to be indices that would sort the array. Implementation of
74 // https://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html
75 void ArgSort(EArrXi& arr) {
76  // Create index array with 0, 1, ... N and sort based on array values
77  std::vector<int> idxs(arr.size());
78  std::iota(std::begin(idxs), std::end(idxs), 0);
79  std::sort(idxs.begin(), idxs.end(), [&arr](int lhs, int rhs) {
80  return arr(lhs) < arr(rhs);
81  });
82  // Update array to match new order
83  for (int i = 0; i < arr.size(); i++) {
84  arr(i) = idxs[i];
85  }
86 }
87 
88 // Update out_filtered and out_indices with rows from rois where lvl matches
89 // value in lvls passed in.
90 void RowsWhereRoILevelEquals(Eigen::Ref<const ERArrXXf> rois,
91  const ERArrXXf& lvls, const int lvl,
92  ERArrXXf* out_filtered, EArrXi* out_indices) {
93  CAFFE_ENFORCE(out_filtered != nullptr, "Output filtered required");
94  CAFFE_ENFORCE(out_indices != nullptr, "Output indices required");
95  CAFFE_ENFORCE(rois.rows() == lvls.rows(), "RoIs and lvls count mismatch");
96  // Calculate how many rows we need
97  int filtered_size = (lvls == lvl).rowwise().any().count();
98  // Fill in the rows and indices
99  out_filtered->resize(filtered_size, rois.cols());
100  out_indices->resize(filtered_size);
101  for (int i = 0, filtered_idx = 0; i < rois.rows(); i++) {
102  auto lvl_row = lvls.row(i);
103  if ((lvl_row == lvl).any()) {
104  out_filtered->row(filtered_idx) = rois.row(i);
105  (*out_indices)(filtered_idx) = i;
106  filtered_idx++;
107  }
108  }
109 }
110 
111 } // namespace utils
112 
113 template <>
114 bool CollectAndDistributeFpnRpnProposalsOp<CPUContext>::RunOnDevice() {
115  int num_rpn_lvls = rpn_max_level_ - rpn_min_level_ + 1;
116  CAFFE_ENFORCE_EQ(InputSize(), 2 * num_rpn_lvls);
117 
118  // roi_in: (N, 5)
119  const auto& first_roi_in = Input(0);
120  const auto N = first_roi_in.dim(0);
121  CAFFE_ENFORCE_EQ(first_roi_in.dims(), (vector<TIndex>{N, 5}));
122 
123  // score_in: (N)
124  const auto& first_score_in = Input(num_rpn_lvls);
125  CAFFE_ENFORCE_EQ(first_score_in.dims(), (vector<TIndex>{N}));
126 
127  int num_roi_lvls = roi_max_level_ - roi_min_level_ + 1;
128  CAFFE_ENFORCE_EQ(OutputSize(), num_roi_lvls + 2);
129 
130  // Collect rois and scores in Eigen
131  // rois are in [[batch_idx, x0, y0, x1, y2], ...] format
132  // Combine predictions across all levels and retain the top scoring
133  //
134  // TODO: This makes the assumption that roi size at each level is the same.
135  //
136  // equivalent to python code
137  // roi_inputs = inputs[:num_rpn_lvls]
138  // score_inputs = inputs[num_rpn_lvls:]
139  // rois = np.concatenate([blob.data for blob in roi_inputs])
140  // scores = np.concatenate([blob.data for blob in score_inputs]).squeeze()
141  ERArrXXf rois(N * num_rpn_lvls, 5);
142  EArrXf scores(N * num_rpn_lvls);
143  for (int i = 0; i < num_rpn_lvls; i++) {
144  // roi_in: (N, 5)
145  const auto& roi_in = Input(i);
146  CAFFE_ENFORCE_EQ(roi_in.dims(), (vector<TIndex>{N, 5}));
147 
148  Eigen::Map<const ERArrXXf> roi(roi_in.data<float>(), N, 5);
149  rois.block(i * N, 0, N, 5) = roi;
150 
151  // score_in: (N)
152  const auto& score_in = Input(num_rpn_lvls + i);
153  CAFFE_ENFORCE_EQ(score_in.dims(), (vector<TIndex>{N}));
154 
155  // No need to squeeze, since we are reshaping when converting to Eigen
156  // https://docs.scipy.org/doc/numpy/reference/generated/numpy.squeeze.html
157  Eigen::Map<const EArrXf> score(score_in.data<float>(), N);
158  scores.segment(i * N, N) = score;
159  }
160 
161  // Grab only top rpn_post_nms_topN rois
162  // equivalent to python code
163  // inds = np.argsort(-scores)[:rpn_post_nms_topN]
164  // rois = rois[inds, :]
165  utils::SortAndLimitRoIsByScores(scores, rpn_post_nms_topN_, rois);
166 
167  // Distribute
168  // equivalent to python code
169  // lvl_min = cfg.FPN.ROI_MIN_LEVEL
170  // lvl_max = cfg.FPN.ROI_MAX_LEVEL
171  // lvls = fpn.map_rois_to_fpn_levels(rois[:, 1:5], lvl_min, lvl_max)
172  const int lvl_min = roi_min_level_;
173  const int lvl_max = roi_max_level_;
174  const int canon_scale = roi_canonical_scale_;
175  const int canon_level = roi_canonical_level_;
176  auto rois_block = rois.block(0, 1, rois.rows(), 4);
177  auto lvls = utils::MapRoIsToFpnLevels(rois_block,
178  lvl_min, lvl_max,
179  canon_scale, canon_level);
180 
181  // equivalent to python code
182  // outputs[0].reshape(rois.shape)
183  // outputs[0].data[...] = rois
184  auto* rois_out = Output(0);
185  rois_out->Resize(rois.rows(), rois.cols());
186  Eigen::Map<ERArrXXf> rois_out_mat(rois_out->mutable_data<float>(),
187  rois.rows(), rois.cols());
188  rois_out_mat = rois;
189 
190  // Create new roi blobs for each FPN level
191  // (See: modeling.FPN.add_multilevel_roi_blobs which is similar but annoying
192  // to generalize to support this particular case.)
193  //
194  // equivalent to python code
195  // rois_idx_order = np.empty((0, ))
196  // for (output_idx, lvl in enumerate(range(lvl_min, lvl_max + 1)))
197  // idx_lvl = np.where(lvls == lvl)[0]
198  // blob_roi_level = rois[idx_lvl, :]
199  // outputs[output_idx + 1].reshape(blob_roi_level.shape)
200  // outputs[output_idx + 1].data[...] = blob_roi_level
201  // rois_idx_order = np.concatenate((rois_idx_order, idx_lvl))
202  // rois_idx_restore = np.argsort(rois_idx_order)
203  // blob_utils.py_op_copy_blob(rois_idx_restore.astype(np.int32), outputs[-1])
204  EArrXi rois_idx_restore;
205  for (int i = 0, lvl = lvl_min; i < num_roi_lvls; i++, lvl++) {
206  ERArrXXf blob_roi_level;
207  EArrXi idx_lvl;
208  utils::RowsWhereRoILevelEquals(rois, lvls, lvl, &blob_roi_level, &idx_lvl);
209 
210  // Output blob_roi_level
211  auto* roi_out = Output(i + 1);
212  roi_out->Resize(blob_roi_level.rows(), blob_roi_level.cols());
213  Eigen::Map<ERArrXXf> roi_out_mat(roi_out->mutable_data<float>(),
214  blob_roi_level.rows(),
215  blob_roi_level.cols());
216  roi_out_mat = blob_roi_level;
217 
218  // Append indices from idx_lvl to rois_idx_restore
219  rois_idx_restore.conservativeResize(rois_idx_restore.size() + idx_lvl.size());
220  rois_idx_restore.tail(idx_lvl.size()) = idx_lvl;
221  }
222  utils::ArgSort(rois_idx_restore);
223  auto* rois_idx_restore_out = Output(OutputSize() - 1);
224  rois_idx_restore_out->Resize(rois_idx_restore.size());
225  Eigen::Map<EArrXi> rois_idx_restore_out_mat(rois_idx_restore_out->mutable_data<int>(),
226  rois_idx_restore.size());
227  rois_idx_restore_out_mat = rois_idx_restore;
228 
229  return true;
230 }
231 
232 namespace {
233 
234 REGISTER_CPU_OPERATOR(CollectAndDistributeFpnRpnProposals, CollectAndDistributeFpnRpnProposalsOp<CPUContext>);
235 
236 #ifdef CAFFE2_HAS_MKL_DNN
237 REGISTER_MKL_OPERATOR(
238  CollectAndDistributeFpnRpnProposals,
239  mkl::MKLFallbackOp<CollectAndDistributeFpnRpnProposalsOp<CPUContext>>);
240 #endif // CAFFE2_HAS_MKL_DNN
241 
242 OPERATOR_SCHEMA(CollectAndDistributeFpnRpnProposals)
243  .NumInputs(2, INT_MAX)
244  .NumOutputs(3, INT_MAX)
245  .SetDoc(R"DOC(
246 Merge RPN proposals generated at multiple FPN levels and then
247 distribute those proposals to their appropriate FPN levels for Faster RCNN.
248 An anchor at one FPN level may predict an RoI that will map to another level,
249 hence the need to redistribute the proposals.
250 
251 Only inference is supported. To train, please use the original Python
252 operator in Detectron.
253 
254 Inputs and outputs are examples only; if min/max levels change,
255 the number of inputs and outputs, as well as their level numbering,
256 will change.
257 )DOC")
258  .Arg("roi_canonical_scale", "(int) ROI_CANONICAL_SCALE")
259  .Arg("roi_canonical_level", "(int) ROI_CANONICAL_LEVEL")
260  .Arg("roi_max_level", "(int) ROI_MAX_LEVEL")
261  .Arg("roi_min_level", "(int) ROI_MIN_LEVEL")
262  .Arg("rpn_max_level", "(int) RPN_MAX_LEVEL")
263  .Arg("rpn_min_level", "(int) RPN_MIN_LEVEL")
264  .Arg("rpn_post_nms_topN", "(int) RPN_POST_NMS_TOP_N")
265  .Input(
266  0,
267  "rpn_rois_fpn2",
268  "RPN proposals for FPN level 2, size (n x 5), "
269  "format (image_index, x1, y1, x2, y2). See rpn_rois "
270  "documentation from GenerateProposals.")
271  .Input(
272  1,
273  "rpn_rois_fpn3",
274  "RPN proposals for FPN level 3, size (n x 5), "
275  "format (image_index, x1, y1, x2, y2). See rpn_rois "
276  "documentation from GenerateProposals.")
277  .Input(
278  2,
279  "rpn_rois_fpn4",
280  "RPN proposals for FPN level 4, size (n x 5), "
281  "format (image_index, x1, y1, x2, y2). See rpn_rois "
282  "documentation from GenerateProposals.")
283  .Input(
284  3,
285  "rpn_rois_fpn5",
286  "RPN proposals for FPN level 5, size (n x 5), "
287  "format (image_index, x1, y1, x2, y2). See rpn_rois "
288  "documentation from GenerateProposals.")
289  .Input(
290  4,
291  "rpn_rois_fpn6",
292  "RPN proposals for FPN level 6, size (n x 5), "
293  "format (image_index, x1, y1, x2, y2). See rpn_rois "
294  "documentation from GenerateProposals.")
295  .Input(
296  5,
297  "rpn_roi_probs_fpn2",
298  "RPN objectness probabilities for FPN level 2, size (n). "
299  "See rpn_roi_probs documentation from GenerateProposals.")
300  .Input(
301  6,
302  "rpn_roi_probs_fpn3",
303  "RPN objectness probabilities for FPN level 3, size (n). "
304  "See rpn_roi_probs documentation from GenerateProposals.")
305  .Input(
306  7,
307  "rpn_roi_probs_fpn4",
308  "RPN objectness probabilities for FPN level 4, size (n). "
309  "See rpn_roi_probs documentation from GenerateProposals.")
310  .Input(
311  8,
312  "rpn_roi_probs_fpn5",
313  "RPN objectness probabilities for FPN level 5, size (n). "
314  "See rpn_roi_probs documentation from GenerateProposals.")
315  .Input(
316  9,
317  "rpn_roi_probs_fpn6",
318  "RPN objectness probabilities for FPN level 6, size (n). "
319  "See rpn_roi_probs documentation from GenerateProposals.")
320  .Output(
321  0,
322  "rois",
323  "Top proposals limited to rpn_post_nms_topN total, "
324  "size (n x 5), format (image_index, x1, y1, x2, y2)")
325  .Output(
326  1,
327  "rois_fpn2",
328  "RPN proposals for ROI level 2, size (n x 5), "
329  "format (image_index, x1, y1, x2, y2)")
330  .Output(
331  2,
332  "rois_fpn3",
333  "RPN proposals for ROI level 3, size (n x 5), "
334  "format (image_index, x1, y1, x2, y2)")
335  .Output(
336  3,
337  "rois_fpn4",
338  "RPN proposals for ROI level 4, size (n x 5), "
339  "format (image_index, x1, y1, x2, y2)")
340  .Output(
341  4,
342  "rois_fpn5",
343  "RPN proposals for ROI level 5, size (n x 5), "
344  "format (image_index, x1, y1, x2, y2)")
345  .Output(
346  5,
347  "rois_idx_restore",
348  "Permutation on the concatenation of all "
349  "rois_fpni, i=min...max, such that when applied the RPN RoIs are "
350  "restored to their original order in the input blobs.");
351 
352 SHOULD_NOT_DO_GRADIENT(CollectAndDistributeFpnRpnProposals);
353 
354 } // namespace
355 } // namespace caffe2
A global dictionary that holds information about what Caffe2 modules have been loaded in the current ...