Caffe2 - C++ API
A deep learning, cross platform ML framework
image_input_op.h
1 
18 #ifndef CAFFE2_IMAGE_IMAGE_INPUT_OP_H_
19 #define CAFFE2_IMAGE_IMAGE_INPUT_OP_H_
20 
21 #include <opencv2/opencv.hpp>
22 
23 #include <iostream>
24 #include <algorithm>
25 
26 #include "caffe/proto/caffe.pb.h"
27 #include "caffe2/core/db.h"
28 #include "caffe2/utils/cast.h"
29 #include "caffe2/utils/math.h"
30 #include "caffe2/utils/thread_pool.h"
31 #include "caffe2/operators/prefetch_op.h"
32 #include "caffe2/image/transform_gpu.h"
33 
34 namespace caffe2 {
35 
36 class CUDAContext;
37 
38 template <class Context>
39 class ImageInputOp final
40  : public PrefetchOperator<Context> {
41  // SINGLE_LABEL: single integer label for multi-class classification
42  // MULTI_LABEL_SPARSE: sparse active label indices for multi-label classification
43  // MULTI_LABEL_DENSE: dense label embedding vector for label embedding regression
44  // MULTI_LABEL_WEIGHTED_SPARSE: sparse active label indices with per-label weights
45  // for multi-label classification
46  // SINGLE_LABEL_WEIGHTED: single integer label for multi-class classification with weighted sampling
47  enum LABEL_TYPE {
48  SINGLE_LABEL = 0,
49  MULTI_LABEL_SPARSE = 1,
50  MULTI_LABEL_DENSE = 2,
51  MULTI_LABEL_WEIGHTED_SPARSE = 3,
52  SINGLE_LABEL_WEIGHTED = 4
53  };
54 
55  // INCEPTION_STYLE: Random crop with size 8% - 100% image area and aspect
56  // ratio in [3/4, 4/3]. Reference: GoogleNet paper
57  enum SCALE_JITTER_TYPE {
58  NO_SCALE_JITTER = 0,
59  INCEPTION_STYLE = 1
60  // TODO(zyan3): ResNet-style random scale jitter
61  };
62 
63  public:
64  using OperatorBase::OutputSize;
67  explicit ImageInputOp(const OperatorDef& operator_def,
68  Workspace* ws);
69  ~ImageInputOp() {
71  }
72 
73  bool Prefetch() override;
74  bool CopyPrefetched() override;
75 
76  private:
77  using BoundingBox = struct {
78  bool valid;
79  int ymin;
80  int xmin;
81  int height;
82  int width;
83  };
84 
85  // Structure to store per-image information
86  // This can be modified by the DecodeAnd* so needs
87  // to be privatized per launch.
88  using PerImageArg = struct {
89  BoundingBox bounding_params;
90  };
91 
92  bool GetImageAndLabelAndInfoFromDBValue(
93  const string& value, cv::Mat* img, PerImageArg& info, int item_id,
94  std::mt19937* randgen);
95  void DecodeAndTransform(
96  const std::string& value, float *image_data, int item_id,
97  const int channels, std::size_t thread_index);
98  void DecodeAndTransposeOnly(
99  const std::string& value, uint8_t *image_data, int item_id,
100  const int channels, std::size_t thread_index);
101 
102  unique_ptr<db::DBReader> owned_reader_;
103  const db::DBReader* reader_;
104  CPUContext cpu_context_;
105  TensorCPU prefetched_image_;
106  TensorCPU prefetched_label_;
107  vector<TensorCPU> prefetched_additional_outputs_;
108  Tensor<Context> prefetched_image_on_device_;
109  Tensor<Context> prefetched_label_on_device_;
110  vector<Tensor<Context>> prefetched_additional_outputs_on_device_;
111  // Default parameters for images
112  PerImageArg default_arg_;
113  int batch_size_;
114  LABEL_TYPE label_type_;
115  int num_labels_;
116 
117  bool color_;
118  bool color_jitter_;
119  float img_saturation_;
120  float img_brightness_;
121  float img_contrast_;
122  bool color_lighting_;
123  float color_lighting_std_;
124  std::vector<std::vector<float>> color_lighting_eigvecs_;
125  std::vector<float> color_lighting_eigvals_;
126  SCALE_JITTER_TYPE scale_jitter_type_;
127  int scale_;
128  // Minsize is similar to scale except that it will only
129  // force the image to scale up if it is too small. In other words,
130  // it ensures that both dimensions of the image are at least minsize_
131  int minsize_;
132  bool warp_;
133  int crop_;
134  std::vector<float> mean_;
135  std::vector<float> std_;
136  Tensor<Context> mean_gpu_;
137  Tensor<Context> std_gpu_;
138  bool mirror_;
139  bool is_test_;
140  bool use_caffe_datum_;
141  bool gpu_transform_;
142  bool mean_std_copied_ = false;
143 
144  // thread pool for parse + decode
145  int num_decode_threads_;
146  int additional_inputs_offset_;
147  int additional_inputs_count_;
148  std::shared_ptr<TaskThreadPool> thread_pool_;
149 
150  // Output type for GPU transform path
151  TensorProto_DataType output_type_;
152 
153  // random minsize
154  vector<int> random_scale_;
155  bool random_scaling_;
156 
157 
158  // Working variables
159  std::vector<std::mt19937> randgen_per_thread_;
160 };
161 
162 template <class Context>
164  const OperatorDef& operator_def,
165  Workspace* ws)
166  : PrefetchOperator<Context>(operator_def, ws),
167  reader_(nullptr),
168  prefetched_additional_outputs_(OutputSize() - 2),
169  prefetched_additional_outputs_on_device_(OutputSize() - 2),
170  batch_size_(
171  OperatorBase::template GetSingleArgument<int>("batch_size", 0)),
172  label_type_(static_cast<LABEL_TYPE>(
173  OperatorBase::template GetSingleArgument<int>("label_type", 0))),
174  num_labels_(
175  OperatorBase::template GetSingleArgument<int>("num_labels", 0)),
176  color_(OperatorBase::template GetSingleArgument<int>("color", 1)),
177  color_jitter_(
178  OperatorBase::template GetSingleArgument<int>("color_jitter", 0)),
179  img_saturation_(OperatorBase::template GetSingleArgument<float>(
180  "img_saturation",
181  0.4)),
182  img_brightness_(OperatorBase::template GetSingleArgument<float>(
183  "img_brightness",
184  0.4)),
185  img_contrast_(
186  OperatorBase::template GetSingleArgument<float>("img_contrast", 0.4)),
187  color_lighting_(
188  OperatorBase::template GetSingleArgument<int>("color_lighting", 0)),
189  color_lighting_std_(OperatorBase::template GetSingleArgument<float>(
190  "color_lighting_std",
191  0.1)),
192  scale_jitter_type_(static_cast<SCALE_JITTER_TYPE>(
193  OperatorBase::template GetSingleArgument<int>(
194  "scale_jitter_type",
195  0))),
196  scale_(OperatorBase::template GetSingleArgument<int>("scale", -1)),
197  minsize_(OperatorBase::template GetSingleArgument<int>("minsize", -1)),
198  warp_(OperatorBase::template GetSingleArgument<int>("warp", 0)),
199  crop_(OperatorBase::template GetSingleArgument<int>("crop", -1)),
200  mirror_(OperatorBase::template GetSingleArgument<int>("mirror", 0)),
201  is_test_(OperatorBase::template GetSingleArgument<int>(
202  OpSchema::Arg_IsTest,
203  0)),
204  use_caffe_datum_(
205  OperatorBase::template GetSingleArgument<int>("use_caffe_datum", 0)),
206  gpu_transform_(OperatorBase::template GetSingleArgument<int>(
207  "use_gpu_transform",
208  0)),
209  num_decode_threads_(
210  OperatorBase::template GetSingleArgument<int>("decode_threads", 4)),
211  thread_pool_(std::make_shared<TaskThreadPool>(num_decode_threads_)),
212  // output type only supported with CUDA and use_gpu_transform for now
213  output_type_(
214  cast::GetCastDataType(ArgumentHelper(operator_def), "output_type")),
215  random_scale_(
216  OperatorBase::template GetRepeatedArgument<int>("random_scale", {-1,-1})) {
217  if ((random_scale_[0] == -1) || (random_scale_[1] == -1)) {
218  random_scaling_ = false;
219  } else {
220  random_scaling_ = true;
221  minsize_ = random_scale_[0];
222  }
223 
224  mean_ = OperatorBase::template GetRepeatedArgument<float>(
225  "mean_per_channel",
226  {OperatorBase::template GetSingleArgument<float>("mean", 0.)});
227 
228  std_ = OperatorBase::template GetRepeatedArgument<float>(
229  "std_per_channel",
230  {OperatorBase::template GetSingleArgument<float>("std", 1.)});
231 
232  vector<int> additional_output_sizes =
233  OperatorBase::template GetRepeatedArgument<int>(
234  "output_sizes", vector<int>(OutputSize() - 2, 1));
235  additional_inputs_count_ = OutputSize() - 2;
236 
237  default_arg_.bounding_params = {
238  false,
239  OperatorBase::template GetSingleArgument<int>("bounding_ymin", -1),
240  OperatorBase::template GetSingleArgument<int>("bounding_xmin", -1),
241  OperatorBase::template GetSingleArgument<int>("bounding_height", -1),
242  OperatorBase::template GetSingleArgument<int>("bounding_width", -1),
243  };
244 
245  if (operator_def.input_size() == 0) {
246  LOG(ERROR) << "You are using an old ImageInputOp format that creates "
247  "a local db reader. Consider moving to the new style "
248  "that takes in a DBReader blob instead.";
249  string db_name =
250  OperatorBase::template GetSingleArgument<string>("db", "");
251  CAFFE_ENFORCE_GT(db_name.size(), 0, "Must specify a db name.");
252  owned_reader_.reset(new db::DBReader(
253  OperatorBase::template GetSingleArgument<string>(
254  "db_type", "leveldb"),
255  db_name));
256  reader_ = owned_reader_.get();
257  }
258 
259  // hard-coded PCA eigenvectors and eigenvalues, based on RBG channel order
260  color_lighting_eigvecs_.push_back(
261  std::vector<float>{-144.7125, 183.396, 102.2295});
262  color_lighting_eigvecs_.push_back(
263  std::vector<float>{-148.104, -1.1475, -207.57});
264  color_lighting_eigvecs_.push_back(
265  std::vector<float>{-148.818, -177.174, 107.1765});
266 
267  color_lighting_eigvals_ = std::vector<float>{0.2175, 0.0188, 0.0045};
268 
269  CAFFE_ENFORCE_GT(batch_size_, 0, "Batch size should be nonnegative.");
270  if (use_caffe_datum_) {
271  CAFFE_ENFORCE(label_type_ == SINGLE_LABEL || label_type_ == SINGLE_LABEL_WEIGHTED,
272  "Caffe datum only supports single integer label");
273  }
274  if (label_type_ != SINGLE_LABEL && label_type_ != SINGLE_LABEL_WEIGHTED) {
275  CAFFE_ENFORCE_GT(num_labels_, 0,
276  "Number of labels must be set for using either sparse label indices or dense label embedding.");
277  }
278  if (label_type_ == MULTI_LABEL_WEIGHTED_SPARSE ||
279  label_type_ == SINGLE_LABEL_WEIGHTED) {
280  additional_inputs_offset_ = 3;
281  } else {
282  additional_inputs_offset_ = 2;
283  }
284  CAFFE_ENFORCE((scale_ > 0) != (minsize_ > 0),
285  "Must provide one and only one of scaling or minsize");
286  CAFFE_ENFORCE_GT(crop_, 0, "Must provide the cropping value.");
287  CAFFE_ENFORCE_GE(
288  scale_ > 0 ? scale_ : minsize_,
289  crop_, "The scale/minsize value must be no smaller than the crop value.");
290 
291  CAFFE_ENFORCE_EQ(
292  mean_.size(),
293  std_.size(),
294  "The mean and std. dev vectors must be of the same size.");
295  CAFFE_ENFORCE(mean_.size() == 1 || mean_.size() == 3,
296  "The mean and std. dev vectors must be of size 1 or 3");
297  CAFFE_ENFORCE(
298  !use_caffe_datum_ || OutputSize() == 2,
299  "There can only be 2 outputs if the Caffe datum format is used");
300  CAFFE_ENFORCE(
301  additional_output_sizes.size() == OutputSize() - 2,
302  "If the output sizes are specified, they must be specified for all "
303  "additional outputs");
304 
305  CAFFE_ENFORCE(random_scale_.size() == 2,
306  "Must provide [scale_min, scale_max]");
307  CAFFE_ENFORCE_GE(random_scale_[1], random_scale_[0],
308  "random scale must provide a range [min, max]");
309 
310  if (default_arg_.bounding_params.ymin < 0
311  || default_arg_.bounding_params.xmin < 0
312  || default_arg_.bounding_params.height < 0
313  || default_arg_.bounding_params.width < 0) {
314  default_arg_.bounding_params.valid = false;
315  } else {
316  default_arg_.bounding_params.valid = true;
317  }
318 
319  if (mean_.size() == 1) {
320  // We are going to extend to 3 using the first value
321  mean_.resize(3, mean_[0]);
322  std_.resize(3, std_[0]);
323  }
324 
325  LOG(INFO) << "Creating an image input op with the following setting: ";
326  LOG(INFO) << " Using " << num_decode_threads_ << " CPU threads;";
327  if (gpu_transform_) {
328  LOG(INFO) << " Performing transformation on GPU";
329  }
330  LOG(INFO) << " Outputting in batches of " << batch_size_ << " images;";
331  LOG(INFO) << " Treating input image as "
332  << (color_ ? "color " : "grayscale ") << "image;";
333  if (default_arg_.bounding_params.valid) {
334  LOG(INFO) << " Applying a default bounding box of Y ["
335  << default_arg_.bounding_params.ymin << "; "
336  << default_arg_.bounding_params.ymin +
337  default_arg_.bounding_params.height
338  << ") x X ["
339  << default_arg_.bounding_params.xmin << "; "
340  << default_arg_.bounding_params.xmin +
341  default_arg_.bounding_params.width
342  << ")";
343  }
344  if (scale_ > 0 && !random_scaling_) {
345  LOG(INFO) << " Scaling image to " << scale_
346  << (warp_ ? " with " : " without ") << "warping;";
347  } else {
348  if (random_scaling_) {
349  // randomly set min_size_ for each image
350  LOG(INFO) << " Randomly scaling shortest side between "
351  << random_scale_[0] << " and "
352  << random_scale_[1];
353  } else {
354  // Here, minsize_ > 0
355  LOG(INFO) << " Ensuring minimum image size of " << minsize_
356  << (warp_ ? " with " : " without ") << "warping;";
357  }
358  }
359  LOG(INFO) << " " << (is_test_ ? "Central" : "Random")
360  << " cropping image to " << crop_
361  << (mirror_ ? " with " : " without ") << "random mirroring;";
362  LOG(INFO) << "Label Type: " << label_type_;
363  LOG(INFO) << "Num Labels: " << num_labels_;
364 
365  auto mit = mean_.begin();
366  auto sit = std_.begin();
367 
368  for (int i = 0;
369  mit != mean_.end() && sit != std_.end();
370  ++mit, ++sit, ++i) {
371  LOG(INFO) << " Default [Channel " << i << "] Subtract mean " << *mit
372  << " and divide by std " << *sit << ".";
373  // We actually will use the inverse of std, so inverse it here
374  *sit = 1.f / *sit;
375  }
376  LOG(INFO) << " Outputting images as "
377  << OperatorBase::template GetSingleArgument<string>("output_type", "unknown") << ".";
378 
379  std::mt19937 meta_randgen(time(nullptr));
380  for (int i = 0; i < num_decode_threads_; ++i) {
381  randgen_per_thread_.emplace_back(meta_randgen());
382  }
383  prefetched_image_.Resize(
384  TIndex(batch_size_),
385  TIndex(crop_),
386  TIndex(crop_),
387  TIndex(color_ ? 3 : 1));
388  if (label_type_ != SINGLE_LABEL && label_type_ != SINGLE_LABEL_WEIGHTED) {
389  prefetched_label_.Resize(TIndex(batch_size_), TIndex(num_labels_));
390  } else {
391  prefetched_label_.Resize(vector<TIndex>(1, batch_size_));
392  }
393 
394  for (int i = 0; i < additional_output_sizes.size(); ++i) {
395  prefetched_additional_outputs_[i].Resize(
396  TIndex(batch_size_), TIndex(additional_output_sizes[i]));
397  }
398 }
399 
400 // Inception-stype scale jittering
401 template <class Context>
402 bool RandomSizedCropping(
403  cv::Mat* img,
404  const int crop,
405  std::mt19937* randgen
406 ) {
407  cv::Mat scaled_img;
408  bool inception_scale_jitter = false;
409  int im_height = img->rows, im_width = img->cols;
410  int area = im_height * im_width;
411  std::uniform_real_distribution<> area_dis(0.08, 1.0);
412  std::uniform_real_distribution<> aspect_ratio_dis(3.0 / 4.0, 4.0 / 3.0);
413 
414  cv::Mat cropping;
415  for (int i = 0; i < 10; ++i) {
416  int target_area = int(ceil(area_dis(*randgen) * area));
417  float aspect_ratio = aspect_ratio_dis(*randgen);
418  int nh = floor(std::sqrt(((float)target_area / aspect_ratio)));
419  int nw = floor(std::sqrt(((float)target_area * aspect_ratio)));
420  if (nh >= 1 && nh <= im_height && nw >=1 && nw <= im_width) {
421  int height_offset = std::uniform_int_distribution<>(
422  0, im_height - nh)(*randgen);
423  int width_offset = std::uniform_int_distribution<>(
424  0,im_width - nw)(*randgen);
425  cv::Rect ROI(width_offset, height_offset, nw, nh);
426  cropping = (*img)(ROI);
427  cv::resize(
428  cropping,
429  scaled_img,
430  cv::Size(crop, crop),
431  0,
432  0,
433  cv::INTER_AREA);
434  *img = scaled_img;
435  inception_scale_jitter = true;
436  break;
437  }
438  }
439  return inception_scale_jitter;
440 }
441 
442 template <class Context>
444  const string& value,
445  cv::Mat* img,
446  PerImageArg& info,
447  int item_id,
448  std::mt19937* randgen) {
449  //
450  // recommend using --caffe2_use_fatal_for_enforce=1 when using ImageInputOp
451  // as this function runs on a worker thread and the exceptions from
452  // CAFFE_ENFORCE are silently dropped by the thread worker functions
453  //
454  cv::Mat src;
455 
456  // Use the default information for images
457  info = default_arg_;
458  if (use_caffe_datum_) {
459  // The input is a caffe datum format.
460  caffe::Datum datum;
461  CAFFE_ENFORCE(datum.ParseFromString(value));
462 
463  prefetched_label_.mutable_data<int>()[item_id] = datum.label();
464  if (datum.encoded()) {
465  // encoded image in datum.
466  src = cv::imdecode(
467  cv::Mat(
468  1,
469  datum.data().size(),
470  CV_8UC1,
471  const_cast<char*>(datum.data().data())),
472  color_ ? CV_LOAD_IMAGE_COLOR : CV_LOAD_IMAGE_GRAYSCALE);
473  } else {
474  // Raw image in datum.
475  CAFFE_ENFORCE(datum.channels() == 3 || datum.channels() == 1);
476 
477  int src_c = datum.channels();
478  src.create(
479  datum.height(), datum.width(), (src_c == 3) ? CV_8UC3 : CV_8UC1);
480 
481  if (src_c == 1) {
482  memcpy(src.ptr<uchar>(0), datum.data().data(), datum.data().size());
483  } else {
484  // Datum stores things in CHW order, let's do HWC for images to make
485  // things more consistent with conventional image storage.
486  for (int c = 0; c < 3; ++c) {
487  const char* datum_buffer =
488  datum.data().data() + datum.height() * datum.width() * c;
489  uchar* ptr = src.ptr<uchar>(0) + c;
490  for (int h = 0; h < datum.height(); ++h) {
491  for (int w = 0; w < datum.width(); ++w) {
492  *ptr = *(datum_buffer++);
493  ptr += 3;
494  }
495  }
496  }
497  }
498  }
499  } else {
500  // The input is a caffe2 format.
501  TensorProtos protos;
502  CAFFE_ENFORCE(protos.ParseFromString(value));
503  const TensorProto& image_proto = protos.protos(0);
504  const TensorProto& label_proto = protos.protos(1);
505  vector<TensorProto> additional_output_protos;
506  int start = additional_inputs_offset_;
507  int end = start + additional_inputs_count_;
508  for (int i = start; i < end; ++i) {
509  additional_output_protos.push_back(protos.protos(i));
510  }
511 
512  if (protos.protos_size() == end + 1) {
513  // We have bounding box information
514  const TensorProto& bounding_proto = protos.protos(end);
515  DCHECK_EQ(bounding_proto.data_type(), TensorProto::INT32);
516  DCHECK_EQ(bounding_proto.int32_data_size(), 4);
517  info.bounding_params.valid = true;
518  info.bounding_params.ymin = bounding_proto.int32_data(0);
519  info.bounding_params.xmin = bounding_proto.int32_data(1);
520  info.bounding_params.height = bounding_proto.int32_data(2);
521  info.bounding_params.width = bounding_proto.int32_data(3);
522  }
523 
524  if (image_proto.data_type() == TensorProto::STRING) {
525  // encoded image string.
526  DCHECK_EQ(image_proto.string_data_size(), 1);
527  const string& encoded_image_str = image_proto.string_data(0);
528  int encoded_size = encoded_image_str.size();
529  // We use a cv::Mat to wrap the encoded str so we do not need a copy.
530  src = cv::imdecode(
531  cv::Mat(
532  1,
533  &encoded_size,
534  CV_8UC1,
535  const_cast<char*>(encoded_image_str.data())),
536  color_ ? CV_LOAD_IMAGE_COLOR : CV_LOAD_IMAGE_GRAYSCALE);
537  } else if (image_proto.data_type() == TensorProto::BYTE) {
538  // raw image content.
539  int src_c = (image_proto.dims_size() == 3) ? image_proto.dims(2) : 1;
540  CAFFE_ENFORCE(src_c == 3 || src_c == 1);
541 
542  src.create(
543  image_proto.dims(0),
544  image_proto.dims(1),
545  (src_c == 3) ? CV_8UC3 : CV_8UC1);
546  memcpy(
547  src.ptr<uchar>(0),
548  image_proto.byte_data().data(),
549  image_proto.byte_data().size());
550  } else {
551  LOG(FATAL) << "Unknown image data type.";
552  }
553 
554  if (label_proto.data_type() == TensorProto::FLOAT) {
555  if (label_type_ == SINGLE_LABEL || label_type_ == SINGLE_LABEL_WEIGHTED) {
556  DCHECK_EQ(label_proto.float_data_size(), 1);
557  prefetched_label_.mutable_data<float>()[item_id] =
558  label_proto.float_data(0);
559  } else if (label_type_ == MULTI_LABEL_SPARSE) {
560  float* label_data = prefetched_label_.mutable_data<float>() +
561  item_id * num_labels_;
562  memset(label_data, 0, sizeof(float) * num_labels_);
563  for (int i = 0; i < label_proto.float_data_size(); ++i) {
564  label_data[(int)label_proto.float_data(i)] = 1.0;
565  }
566  } else if (label_type_ == MULTI_LABEL_WEIGHTED_SPARSE) {
567  const TensorProto& weight_proto = protos.protos(2);
568  float* label_data =
569  prefetched_label_.mutable_data<float>() + item_id * num_labels_;
570  memset(label_data, 0, sizeof(float) * num_labels_);
571  for (int i = 0; i < label_proto.float_data_size(); ++i) {
572  label_data[(int)label_proto.float_data(i)] =
573  weight_proto.float_data(i);
574  }
575  } else if (label_type_ == MULTI_LABEL_DENSE) {
576  CAFFE_ENFORCE(label_proto.float_data_size() == num_labels_);
577  float* label_data = prefetched_label_.mutable_data<float>() +
578  item_id * num_labels_;
579  for (int i = 0; i < label_proto.float_data_size(); ++i) {
580  label_data[i] = label_proto.float_data(i);
581  }
582  } else {
583  LOG(ERROR) << "Unknown label type:" << label_type_;
584  }
585  } else if (label_proto.data_type() == TensorProto::INT32) {
586  if (label_type_ == SINGLE_LABEL || label_type_ == SINGLE_LABEL_WEIGHTED) {
587  DCHECK_EQ(label_proto.int32_data_size(), 1);
588  prefetched_label_.mutable_data<int>()[item_id] =
589  label_proto.int32_data(0);
590  } else if (label_type_ == MULTI_LABEL_SPARSE) {
591  int* label_data = prefetched_label_.mutable_data<int>() +
592  item_id * num_labels_;
593  memset(label_data, 0, sizeof(int) * num_labels_);
594  for (int i = 0; i < label_proto.int32_data_size(); ++i) {
595  label_data[label_proto.int32_data(i)] = 1;
596  }
597  } else if (label_type_ == MULTI_LABEL_WEIGHTED_SPARSE) {
598  const TensorProto& weight_proto = protos.protos(2);
599  float* label_data =
600  prefetched_label_.mutable_data<float>() + item_id * num_labels_;
601  memset(label_data, 0, sizeof(float) * num_labels_);
602  for (int i = 0; i < label_proto.int32_data_size(); ++i) {
603  label_data[label_proto.int32_data(i)] = weight_proto.float_data(i);
604  }
605  } else if (label_type_ == MULTI_LABEL_DENSE) {
606  CAFFE_ENFORCE(label_proto.int32_data_size() == num_labels_);
607  int* label_data = prefetched_label_.mutable_data<int>() +
608  item_id * num_labels_;
609  for (int i = 0; i < label_proto.int32_data_size(); ++i) {
610  label_data[i] = label_proto.int32_data(i);
611  }
612  } else {
613  LOG(ERROR) << "Unknown label type:" << label_type_;
614  }
615  } else {
616  LOG(FATAL) << "Unsupported label data type.";
617  }
618 
619  for (int i = 0; i < additional_output_protos.size(); ++i) {
620  auto additional_output_proto = additional_output_protos[i];
621 
622  if (additional_output_proto.data_type() == TensorProto::FLOAT) {
623  float* additional_output =
624  prefetched_additional_outputs_[i].template mutable_data<float>() +
625  item_id * additional_output_proto.float_data_size();
626 
627  for (int j = 0; j < additional_output_proto.float_data_size(); ++j) {
628  additional_output[j] = additional_output_proto.float_data(j);
629  }
630  } else if (additional_output_proto.data_type() == TensorProto::INT32) {
631  int* additional_output =
632  prefetched_additional_outputs_[i].template mutable_data<int>() +
633  item_id * additional_output_proto.int32_data_size();
634 
635  for (int j = 0; j < additional_output_proto.int32_data_size(); ++j) {
636  additional_output[j] = additional_output_proto.int32_data(j);
637  }
638  } else if (additional_output_proto.data_type() == TensorProto::INT64) {
639  int64_t* additional_output =
640  prefetched_additional_outputs_[i].template mutable_data<int64_t>() +
641  item_id * additional_output_proto.int64_data_size();
642 
643  for (int j = 0; j < additional_output_proto.int64_data_size(); ++j) {
644  additional_output[j] = additional_output_proto.int64_data(j);
645  }
646  }
647  else {
648  LOG(FATAL) << "Unsupported output type.";
649  }
650  }
651  }
652 
653  //
654  // convert source to the color format requested from Op
655  //
656  int out_c = color_ ? 3 : 1;
657  if (out_c == src.channels()) {
658  *img = src;
659  } else {
660  cv::cvtColor(src, *img, (out_c == 1) ? CV_BGR2GRAY : CV_GRAY2BGR);
661  }
662 
663  // Note(Yangqing): I believe that the mat should be created continuous.
664  CAFFE_ENFORCE(img->isContinuous());
665 
666  // Sanity check now that we decoded everything
667 
668  // Ensure that the bounding box is legit
669  if (info.bounding_params.valid
670  && (src.rows < info.bounding_params.ymin + info.bounding_params.height
671  || src.cols < info.bounding_params.xmin + info.bounding_params.width
672  )) {
673  info.bounding_params.valid = false;
674  }
675 
676  // Apply the bounding box if requested
677  if (info.bounding_params.valid) {
678  // If we reach here, we know the parameters are sane
679  cv::Rect bounding_box(info.bounding_params.xmin, info.bounding_params.ymin,
680  info.bounding_params.width, info.bounding_params.height);
681  *img = (*img)(bounding_box);
682 
683  /*
684  LOG(INFO) << "Did bounding with ymin:"
685  << info.bounding_params.ymin << " xmin:" << info.bounding_params.xmin
686  << " height:" << info.bounding_params.height
687  << " width:" << info.bounding_params.width << "\n";
688  LOG(INFO) << "Bounded matrix: " << img;
689  */
690  } else {
691  // LOG(INFO) << "No bounding\n";
692  }
693 
694  cv::Mat scaled_img;
695  bool inception_scale_jitter = false;
696  if (scale_jitter_type_ == INCEPTION_STYLE) {
697  if (!is_test_) {
698  // Inception-stype scale jittering is only used for training
699  inception_scale_jitter = RandomSizedCropping<Context>(img, crop_, randgen);
700  // if a random crop is still not found, do simple random cropping later
701  }
702  }
703 
704  if ((scale_jitter_type_ == NO_SCALE_JITTER) ||
705  (scale_jitter_type_ == INCEPTION_STYLE && !inception_scale_jitter)) {
706  int scaled_width, scaled_height;
707  int scale_to_use = scale_ > 0 ? scale_ : minsize_;
708 
709  // set the random minsize
710  if (random_scaling_) {
711  scale_to_use = std::uniform_int_distribution<>(random_scale_[0],
712  random_scale_[1])(*randgen);
713  }
714 
715  if (warp_) {
716  scaled_width = scale_to_use;
717  scaled_height = scale_to_use;
718  } else if (img->rows > img->cols) {
719  scaled_width = scale_to_use;
720  scaled_height =
721  static_cast<float>(img->rows) * scale_to_use / img->cols;
722  } else {
723  scaled_height = scale_to_use;
724  scaled_width =
725  static_cast<float>(img->cols) * scale_to_use / img->rows;
726  }
727  if ((scale_ > 0 &&
728  (scaled_height != img->rows || scaled_width != img->cols))
729  || (scaled_height > img->rows || scaled_width > img->cols)) {
730  // We rescale in all cases if we are using scale_
731  // but only to make the image bigger if using minsize_
732  /*
733  LOG(INFO) << "Scaling to " << scaled_width << " x " << scaled_height
734  << " From " << img->cols << " x " << img->rows;
735  */
736  cv::resize(
737  *img,
738  scaled_img,
739  cv::Size(scaled_width, scaled_height),
740  0,
741  0,
742  cv::INTER_AREA);
743  *img = scaled_img;
744  }
745  }
746  // TODO(Yangqing): return false if any error happens.
747  return true;
748 }
749 
750 // assume HWC order and color channels BGR
751 template <class Context>
752 void Saturation(
753  float* img,
754  const int img_size,
755  const float alpha_rand,
756  std::mt19937* randgen
757 ) {
758  float alpha = 1.0f +
759  std::uniform_real_distribution<float>(-alpha_rand, alpha_rand)(*randgen);
760  // BGR to Gray scale image: R -> 0.299, G -> 0.587, B -> 0.114
761  int p = 0;
762  for (int h = 0; h < img_size; ++h) {
763  for (int w = 0; w < img_size; ++w) {
764  float gray_color = img[3 * p] * 0.114f + img[3 * p + 1] * 0.587f +
765  img[3 * p + 2] * 0.299f;
766  for (int c = 0; c < 3; ++c) {
767  img[3 * p + c] = img[3 * p + c] * alpha + gray_color * (1.0f - alpha);
768  }
769  p++;
770  }
771  }
772 }
773 
774 // assume HWC order and color channels BGR
775 template <class Context>
776 void Brightness(
777  float* img,
778  const int img_size,
779  const float alpha_rand,
780  std::mt19937* randgen
781 ) {
782  float alpha = 1.0f +
783  std::uniform_real_distribution<float>(-alpha_rand, alpha_rand)(*randgen);
784  int p = 0;
785  for (int h = 0; h < img_size; ++h) {
786  for (int w = 0; w < img_size; ++w) {
787  for (int c = 0; c < 3; ++c) {
788  img[p++] *= alpha;
789  }
790  }
791  }
792 }
793 
794 // assume HWC order and color channels BGR
795 template <class Context>
796 void Contrast(
797  float* img,
798  const int img_size,
799  const float alpha_rand,
800  std::mt19937* randgen
801 ){
802  float gray_mean = 0;
803  int p = 0;
804  for (int h = 0; h < img_size; ++h) {
805  for (int w = 0; w < img_size; ++w) {
806  // BGR to Gray scale image: R -> 0.299, G -> 0.587, B -> 0.114
807  gray_mean += img[3 * p] * 0.114f + img[3 * p + 1] * 0.587f +
808  img[3 * p + 2] * 0.299f;
809  p++;
810  }
811  }
812  gray_mean /= (img_size * img_size);
813 
814  float alpha = 1.0f +
815  std::uniform_real_distribution<float>(-alpha_rand, alpha_rand)(*randgen);
816  p = 0;
817  for (int h = 0; h < img_size; ++h) {
818  for (int w = 0; w < img_size; ++w) {
819  for (int c = 0; c < 3; ++c) {
820  img[p] = img[p] * alpha + gray_mean * (1.0f - alpha);
821  p++;
822  }
823  }
824  }
825 }
826 
827 // assume HWC order and color channels BGR
828 template <class Context>
829 void ColorJitter(
830  float* img,
831  const int img_size,
832  const float saturation,
833  const float brightness,
834  const float contrast,
835  std::mt19937* randgen
836 ) {
837  std::srand (unsigned(std::time(0)));
838  std::vector<int> jitter_order{0, 1, 2};
839  // obtain a time-based seed:
840  unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
841  std::shuffle(jitter_order.begin(), jitter_order.end(),
842  std::default_random_engine(seed));
843 
844  for (int i = 0; i < 3; ++i) {
845  if (jitter_order[i] == 0) {
846  Saturation<Context>(img, img_size, saturation, randgen);
847  } else if (jitter_order[i] == 1) {
848  Brightness<Context>(img, img_size, brightness, randgen);
849  } else {
850  Contrast<Context>(img, img_size, contrast, randgen);
851  }
852  }
853 }
854 
855 // assume HWC order and color channels BGR
856 template <class Context>
857 void ColorLighting(
858  float* img,
859  const int img_size,
860  const float alpha_std,
861  const std::vector<std::vector<float>>& eigvecs,
862  const std::vector<float>& eigvals,
863  std::mt19937* randgen
864 ) {
865  std::normal_distribution<float> d(0, alpha_std);
866  std::vector<float> alphas(3);
867  for (int i = 0; i < 3; ++i) {
868  alphas[i] = d(*randgen);
869  }
870 
871  std::vector<float> delta_rgb(3, 0.0);
872  for (int i = 0; i < 3; ++i) {
873  for (int j = 0; j < 3; ++j) {
874  delta_rgb[i] += eigvecs[i][j] * eigvals[j] * alphas[j];
875  }
876  }
877 
878  int p = 0;
879  for (int h = 0; h < img_size; ++h) {
880  for (int w = 0; w < img_size; ++w) {
881  for (int c = 0; c < 3; ++c) {
882  img[p++] += delta_rgb[2 - c];
883  }
884  }
885  }
886 
887 }
888 
889 // assume HWC order and color channels BGR
890 // mean subtraction and scaling.
891 template <class Context>
892 void ColorNormalization(
893  float* img,
894  const int img_size,
895  const int channels,
896  const std::vector<float>& mean,
897  const std::vector<float>& std
898 ) {
899  int p = 0;
900  for (int h = 0; h < img_size; ++h) {
901  for (int w = 0; w < img_size; ++w) {
902  for (int c = 0; c < channels; ++c) {
903  img[p] = (img[p] - mean[c]) * std[c];
904  p++;
905  }
906  }
907  }
908 }
909 
910 // Factored out image transformation
911 template <class Context>
912 void TransformImage(
913  const cv::Mat& scaled_img,
914  const int channels,
915  float* image_data,
916  const bool color_jitter,
917  const float saturation,
918  const float brightness,
919  const float contrast,
920  const bool color_lighting,
921  const float color_lighting_std,
922  const std::vector<std::vector<float>>& color_lighting_eigvecs,
923  const std::vector<float>& color_lighting_eigvals,
924  const int crop,
925  const bool mirror,
926  const std::vector<float>& mean,
927  const std::vector<float>& std,
928  std::mt19937* randgen,
929  std::bernoulli_distribution* mirror_this_image,
930  bool is_test = false) {
931  CAFFE_ENFORCE_GE(
932  scaled_img.rows, crop, "Image height must be bigger than crop.");
933  CAFFE_ENFORCE_GE(
934  scaled_img.cols, crop, "Image width must be bigger than crop.");
935 
936  // find the cropped region, and copy it to the destination matrix
937  int width_offset, height_offset;
938  if (is_test) {
939  width_offset = (scaled_img.cols - crop) / 2;
940  height_offset = (scaled_img.rows - crop) / 2;
941  } else {
942  width_offset =
943  std::uniform_int_distribution<>(0, scaled_img.cols - crop)(*randgen);
944  height_offset =
945  std::uniform_int_distribution<>(0, scaled_img.rows - crop)(*randgen);
946  }
947 
948  float* image_data_ptr = image_data;
949  if (!is_test && mirror && (*mirror_this_image)(*randgen)) {
950  // Copy mirrored image.
951  for (int h = height_offset; h < height_offset + crop; ++h) {
952  for (int w = width_offset + crop - 1; w >= width_offset; --w) {
953  const uint8_t* cv_data = scaled_img.ptr(h) + w * channels;
954  for (int c = 0; c < channels; ++c) {
955  *(image_data_ptr++) = static_cast<float>(cv_data[c]);
956  }
957  }
958  }
959  } else {
960  // Copy normally.
961  for (int h = height_offset; h < height_offset + crop; ++h) {
962  for (int w = width_offset; w < width_offset + crop; ++w) {
963  const uint8_t* cv_data = scaled_img.ptr(h) + w * channels;
964  for (int c = 0; c < channels; ++c) {
965  *(image_data_ptr++) = static_cast<float>(cv_data[c]);
966  }
967  }
968  }
969  }
970 
971  if (color_jitter && channels == 3 && !is_test) {
972  ColorJitter<Context>(image_data, crop, saturation, brightness, contrast,
973  randgen);
974  }
975  if (color_lighting && channels == 3 && !is_test) {
976  ColorLighting<Context>(image_data, crop, color_lighting_std,
977  color_lighting_eigvecs, color_lighting_eigvals, randgen);
978  }
979 
980  // Color normalization
981  // Mean subtraction and scaling.
982  ColorNormalization<Context>(image_data, crop, channels, mean, std);
983 }
984 
985 // Only crop / transose the image
986 // leave in uint8_t dataType
987 template <class Context>
988 void CropTransposeImage(const cv::Mat& scaled_img, const int channels,
989  uint8_t *cropped_data, const int crop,
990  const bool mirror, std::mt19937 *randgen,
991  std::bernoulli_distribution *mirror_this_image,
992  bool is_test = false) {
993  CAFFE_ENFORCE_GE(
994  scaled_img.rows, crop, "Image height must be bigger than crop.");
995  CAFFE_ENFORCE_GE(
996  scaled_img.cols, crop, "Image width must be bigger than crop.");
997 
998  // find the cropped region, and copy it to the destination matrix
999  int width_offset, height_offset;
1000  if (is_test) {
1001  width_offset = (scaled_img.cols - crop) / 2;
1002  height_offset = (scaled_img.rows - crop) / 2;
1003  } else {
1004  width_offset =
1005  std::uniform_int_distribution<>(0, scaled_img.cols - crop)(*randgen);
1006  height_offset =
1007  std::uniform_int_distribution<>(0, scaled_img.rows - crop)(*randgen);
1008  }
1009 
1010  if (mirror && (*mirror_this_image)(*randgen)) {
1011  // Copy mirrored image.
1012  for (int h = height_offset; h < height_offset + crop; ++h) {
1013  for (int w = width_offset + crop - 1; w >= width_offset; --w) {
1014  const uint8_t* cv_data = scaled_img.ptr(h) + w*channels;
1015  for (int c = 0; c < channels; ++c) {
1016  *(cropped_data++) = cv_data[c];
1017  }
1018  }
1019  }
1020  } else {
1021  // Copy normally.
1022  for (int h = height_offset; h < height_offset + crop; ++h) {
1023  for (int w = width_offset; w < width_offset + crop; ++w) {
1024  const uint8_t* cv_data = scaled_img.ptr(h) + w*channels;
1025  for (int c = 0; c < channels; ++c) {
1026  *(cropped_data++) = cv_data[c];
1027  }
1028  }
1029  }
1030  }
1031 }
1032 
1033 // Parse datum, decode image, perform transform
1034 // Intended as entry point for binding to thread pool
1035 template <class Context>
1037  const std::string& value, float *image_data, int item_id,
1038  const int channels, std::size_t thread_index) {
1039 
1040  CAFFE_ENFORCE((int)thread_index < num_decode_threads_);
1041 
1042  std::bernoulli_distribution mirror_this_image(0.5f);
1043  std::mt19937* randgen = &(randgen_per_thread_[thread_index]);
1044 
1045  cv::Mat img;
1046  // Decode the image
1047  PerImageArg info;
1048  CHECK(GetImageAndLabelAndInfoFromDBValue(value, &img, info, item_id,
1049  randgen));
1050 
1051  // Factor out the image transformation
1052  TransformImage<Context>(img, channels, image_data,
1053  color_jitter_, img_saturation_, img_brightness_, img_contrast_,
1054  color_lighting_, color_lighting_std_, color_lighting_eigvecs_,
1055  color_lighting_eigvals_, crop_, mirror_, mean_, std_,
1056  randgen, &mirror_this_image, is_test_);
1057 }
1058 
1059 template <class Context>
1061  const std::string& value, uint8_t *image_data, int item_id,
1062  const int channels, std::size_t thread_index) {
1063 
1064  CAFFE_ENFORCE((int)thread_index < num_decode_threads_);
1065 
1066  std::bernoulli_distribution mirror_this_image(0.5f);
1067  std::mt19937* randgen = &(randgen_per_thread_[thread_index]);
1068 
1069  cv::Mat img;
1070  // Decode the image
1071  PerImageArg info;
1072  CHECK(GetImageAndLabelAndInfoFromDBValue(value, &img, info, item_id,
1073  randgen));
1074 
1075  // Factor out the image transformation
1076  CropTransposeImage<Context>(img, channels, image_data, crop_, mirror_,
1077  randgen, &mirror_this_image, is_test_);
1078 }
1079 
1080 
1081 template <class Context>
1083  if (!owned_reader_.get()) {
1084  // if we are not owning the reader, we will get the reader pointer from
1085  // input. Otherwise the constructor should have already set the reader
1086  // pointer.
1087  reader_ = &OperatorBase::Input<db::DBReader>(0);
1088  }
1089  const int channels = color_ ? 3 : 1;
1090  // Call mutable_data() once to allocate the underlying memory.
1091  if (gpu_transform_) {
1092  // we'll transfer up in int8, then convert later
1093  prefetched_image_.mutable_data<uint8_t>();
1094  } else {
1095  prefetched_image_.mutable_data<float>();
1096  }
1097 
1098  prefetched_label_.mutable_data<int>();
1099  // Prefetching handled with a thread pool of "decode_threads" threads.
1100 
1101  for (int item_id = 0; item_id < batch_size_; ++item_id) {
1102  std::string key, value;
1103  cv::Mat img;
1104 
1105  // read data
1106  reader_->Read(&key, &value);
1107 
1108  // determine label type based on first item
1109  if( item_id == 0 ) {
1110  if( use_caffe_datum_ ) {
1111  prefetched_label_.mutable_data<int>();
1112  } else {
1113  TensorProtos protos;
1114  CAFFE_ENFORCE(protos.ParseFromString(value));
1115  TensorProto_DataType labeldt = protos.protos(1).data_type();
1116  if( labeldt == TensorProto::INT32 ) {
1117  prefetched_label_.mutable_data<int>();
1118  } else if ( labeldt == TensorProto::FLOAT) {
1119  prefetched_label_.mutable_data<float>();
1120  } else {
1121  LOG(FATAL) << "Unsupported label type.";
1122  }
1123 
1124  for (int i = 0; i < additional_inputs_count_; ++i) {
1125  int index = additional_inputs_offset_ + i;
1126  TensorProto additional_output_proto = protos.protos(index);
1127 
1128  if (additional_output_proto.data_type() == TensorProto::FLOAT) {
1129  prefetched_additional_outputs_[i].template mutable_data<float>();
1130  } else if (
1131  additional_output_proto.data_type() == TensorProto::INT32) {
1132  prefetched_additional_outputs_[i].template mutable_data<int>();
1133  } else if (
1134  additional_output_proto.data_type() == TensorProto::INT64) {
1135  prefetched_additional_outputs_[i].template mutable_data<int64_t>();
1136  } else {
1137  LOG(FATAL) << "Unsupported output type.";
1138  }
1139  }
1140  }
1141  }
1142 
1143  // launch into thread pool for processing
1144  // TODO: support color jitter and color lighting in gpu_transform
1145  if (gpu_transform_) {
1146  // output of decode will still be int8
1147  uint8_t* image_data = prefetched_image_.mutable_data<uint8_t>() +
1148  crop_ * crop_ * channels * item_id;
1149  thread_pool_->runTaskWithID(std::bind(
1151  this,
1152  std::string(value),
1153  image_data,
1154  item_id,
1155  channels,
1156  std::placeholders::_1));
1157  } else {
1158  float* image_data = prefetched_image_.mutable_data<float>() +
1159  crop_ * crop_ * channels * item_id;
1160  thread_pool_->runTaskWithID(std::bind(
1162  this,
1163  std::string(value),
1164  image_data,
1165  item_id,
1166  channels,
1167  std::placeholders::_1));
1168  }
1169  }
1170  thread_pool_->waitWorkComplete();
1171 
1172  // If the context is not CPUContext, we will need to do a copy in the
1173  // prefetch function as well.
1174  if (!std::is_same<Context, CPUContext>::value) {
1175  prefetched_image_on_device_.CopyFrom(prefetched_image_, &context_);
1176  prefetched_label_on_device_.CopyFrom(prefetched_label_, &context_);
1177 
1178  for (int i = 0; i < prefetched_additional_outputs_on_device_.size(); ++i) {
1179  prefetched_additional_outputs_on_device_[i].CopyFrom(
1180  prefetched_additional_outputs_[i], &context_);
1181  }
1182  }
1183  return true;
1184 }
1185 
1186 template <class Context>
1188  auto* image_output = OperatorBase::Output<Tensor<Context> >(0);
1189  auto* label_output = OperatorBase::Output<Tensor<Context> >(1);
1190  vector<Tensor<Context>*> additional_outputs_output;
1191 
1192  for (int i = 2; i < OutputSize(); ++i) {
1193  additional_outputs_output.push_back(
1194  OperatorBase::Output<Tensor<Context>>(i));
1195  }
1196 
1197  // Note(jiayq): The if statement below should be optimized away by the
1198  // compiler since std::is_same is a constexpr.
1199  if (std::is_same<Context, CPUContext>::value) {
1200  image_output->CopyFrom(prefetched_image_, &context_);
1201  label_output->CopyFrom(prefetched_label_, &context_);
1202 
1203  for (int i = 0; i < additional_outputs_output.size(); ++i) {
1204  additional_outputs_output[i]->CopyFrom(
1205  prefetched_additional_outputs_[i], &context_);
1206  }
1207  } else {
1208  // TODO: support color jitter and color lighting in gpu_transform
1209  if (gpu_transform_) {
1210  if (!mean_std_copied_) {
1211  mean_gpu_.Resize(mean_.size());
1212  std_gpu_.Resize(std_.size());
1213 
1214  context_.template Copy<float, CPUContext, Context>(
1215  mean_.size(), mean_.data(), mean_gpu_.template mutable_data<float>());
1216  context_.template Copy<float, CPUContext, Context>(
1217  std_.size(), std_.data(), std_gpu_.template mutable_data<float>());
1218  mean_std_copied_ = true;
1219  }
1220  // GPU transform kernel allows explicitly setting output type
1221  if (output_type_ == TensorProto_DataType_FLOAT) {
1222  TransformOnGPU<uint8_t,float,Context>(prefetched_image_on_device_,
1223  image_output, mean_gpu_,
1224  std_gpu_, &context_);
1225  } else if (output_type_ == TensorProto_DataType_FLOAT16) {
1226  TransformOnGPU<uint8_t,float16,Context>(prefetched_image_on_device_,
1227  image_output, mean_gpu_,
1228  std_gpu_, &context_);
1229  } else {
1230  return false;
1231  }
1232  } else {
1233  image_output->CopyFrom(prefetched_image_on_device_, &context_);
1234  }
1235  label_output->CopyFrom(prefetched_label_on_device_, &context_);
1236 
1237  for (int i = 0; i < additional_outputs_output.size(); ++i) {
1238  additional_outputs_output[i]->CopyFrom(
1239  prefetched_additional_outputs_on_device_[i], &context_);
1240  }
1241  }
1242  return true;
1243 }
1244 } // namespace caffe2
1245 
1246 #endif // CAFFE2_IMAGE_IMAGE_INPUT_OP_H_
void Read(string *key, string *value) const
Read a set of key and value from the db and move to next.
Definition: db.h:238
A reader wrapper for DB that also allows us to serialize it.
Definition: db.h:160
Definition: types.h:88
The CPU Context, representing the bare minimum of what a Context class in Caffe2 should implement...
Definition: context.h:82
A helper class to index into arguments.
Definition: proto_utils.h:198
T * mutable_data()
Returns a typed pointer of the underlying storage.
Definition: tensor.h:594
Workspace is a class that holds all the related objects created during runtime: (1) all blobs...
Definition: workspace.h:63
void Resize(Ts...dim_source)
Resizes a tensor.
Definition: tensor.h:304
Copyright (c) 2016-present, Facebook, Inc.