diff --git a/examples/images/cat_1.jpg b/examples/images/cat_1.jpg new file mode 100644 index 00000000000..b4efc6c98b7 Binary files /dev/null and b/examples/images/cat_1.jpg differ diff --git a/examples/images/cat_2.jpg b/examples/images/cat_2.jpg new file mode 100644 index 00000000000..63ca49c9a78 Binary files /dev/null and b/examples/images/cat_2.jpg differ diff --git a/examples/images/cat_3.jpg b/examples/images/cat_3.jpg new file mode 100644 index 00000000000..bd641d393ed Binary files /dev/null and b/examples/images/cat_3.jpg differ diff --git a/include/caffe/data_layers.hpp b/include/caffe/data_layers.hpp index 3958cb7ecb0..36e5cffc6c3 100644 --- a/include/caffe/data_layers.hpp +++ b/include/caffe/data_layers.hpp @@ -78,6 +78,41 @@ class BasePrefetchingDataLayer : Blob transformed_data_; }; +/** + * @brief Provides pre-fetching base for data layers that feed multiple blobs + * to the Net. + * + * TODO(dox): thorough documentation for Forward and proto params. + */ +template +class BasePrefetchingMultiDataLayer : + public BaseDataLayer, public InternalThread { + public: + explicit BasePrefetchingMultiDataLayer(const LayerParameter& param) + : BaseDataLayer(param) {} + // LayerSetUp: implements common data layer setup functionality, and calls + // DataLayerSetUp to do special data layer setup for individual layer types. + // This method may not be overridden. + void LayerSetUp(const vector*>& bottom, + const vector*>& top); + + virtual void Forward_cpu(const vector*>& bottom, + const vector*>& top); + virtual void Forward_gpu(const vector*>& bottom, + const vector*>& top); + + virtual void CreatePrefetchThread(); + virtual void JoinPrefetchThread(); + // The thread's function + virtual void InternalThreadEntry() {} + + protected: + int input_data_size_; + std::vector*> prefetch_data_; + Blob prefetch_label_; + Blob transformed_data_; +}; + template class DataLayer : public BasePrefetchingDataLayer { public: @@ -217,27 +252,41 @@ class HDF5OutputLayer : public Layer { /** * @brief Provides data to the Net from image files. * + * The file format is of the form: + * [file_path] { ... [file_path]} [label] + * The delimiter can be ' ', ',' or '\t' (checked in this order) + * To enable more than one file input, just add a 'top' in the layer parameters + * while keeping the label last. + * * TODO(dox): thorough documentation for Forward and proto params. */ template -class ImageDataLayer : public BasePrefetchingDataLayer { +class ImageDataLayer : public BasePrefetchingMultiDataLayer { public: explicit ImageDataLayer(const LayerParameter& param) - : BasePrefetchingDataLayer(param) {} + : BasePrefetchingMultiDataLayer(param) {} virtual ~ImageDataLayer(); virtual void DataLayerSetUp(const vector*>& bottom, const vector*>& top); virtual inline const char* type() const { return "ImageData"; } virtual inline int ExactNumBottomBlobs() const { return 0; } - virtual inline int ExactNumTopBlobs() const { return 2; } + virtual inline int ExactNumTopBlobs() const { + return this->layer_param_.top_size(); + } + // would this work? return this->input_data_size_ + 1; protected: shared_ptr prefetch_rng_; virtual void ShuffleImages(); virtual void InternalThreadEntry(); + int findNumOccurrences(char delim, std::string text); + void split( + char delim, + const std::string &line, + std::vector *parts); - vector > lines_; + vector, int> > lines_; int lines_id_; }; diff --git a/src/caffe/layers/base_data_layer.cpp b/src/caffe/layers/base_data_layer.cpp index 26a1118282f..494d55c31eb 100644 --- a/src/caffe/layers/base_data_layer.cpp +++ b/src/caffe/layers/base_data_layer.cpp @@ -83,7 +83,76 @@ void BasePrefetchingDataLayer::Forward_cpu( STUB_GPU_FORWARD(BasePrefetchingDataLayer, Forward); #endif + + +template +void BasePrefetchingMultiDataLayer::LayerSetUp( + const vector*>& bottom, const vector*>& top) { + // the data size is of the size the top - 1 as top = data + label + input_data_size_ = top.size() - 1; + for (int i = 0; i < input_data_size_; i++) + prefetch_data_.push_back(new Blob()); + BaseDataLayer::LayerSetUp(bottom, top); + // Now, start the prefetch thread. Before calling prefetch, we make two + // cpu_data calls so that the prefetch thread does not accidentally make + // simultaneous cudaMalloc calls when the main thread is running. In some + // GPUs this seems to cause failures if we do not so. + for (int data_id = 0; data_id < input_data_size_; data_id++) { + this->prefetch_data_[data_id]->mutable_cpu_data(); + } + if (this->output_labels_) { + this->prefetch_label_.mutable_cpu_data(); + } + DLOG(INFO) << "Initializing prefetch"; + this->CreatePrefetchThread(); + DLOG(INFO) << "Prefetch initialized."; +} + +template +void BasePrefetchingMultiDataLayer::CreatePrefetchThread() { + this->data_transformer_->InitRand(); + CHECK(StartInternalThread()) << "Thread execution failed"; +} + +template +void BasePrefetchingMultiDataLayer::JoinPrefetchThread() { + CHECK(WaitForInternalThreadToExit()) << "Thread joining failed"; +} + +template +void BasePrefetchingMultiDataLayer::Forward_cpu( + const vector*>& bottom, const vector*>& top) { + // First, join the thread + JoinPrefetchThread(); + DLOG(INFO) << "Thread joined"; + // Reshape to loaded data. + for (int data_id = 0; data_id < input_data_size_; data_id++) { + top[data_id]->ReshapeLike(*prefetch_data_[data_id]); + // Copy the data + caffe_copy( + prefetch_data_[data_id]->count(), + prefetch_data_[data_id]->cpu_data(), + top[data_id]->mutable_cpu_data()); + DLOG(INFO) << "Prefetch copied"; + } + if (this->output_labels_) { + // Reshape to loaded labels. + top[input_data_size_]->ReshapeLike(prefetch_label_); + // Copy the labels. + caffe_copy(prefetch_label_.count(), prefetch_label_.cpu_data(), + top[input_data_size_]->mutable_cpu_data()); + } + // Start a new prefetch thread + DLOG(INFO) << "CreatePrefetchThread"; + CreatePrefetchThread(); +} + +#ifdef CPU_ONLY +STUB_GPU_FORWARD(BasePrefetchingMultiDataLayer, Forward); +#endif + INSTANTIATE_CLASS(BaseDataLayer); INSTANTIATE_CLASS(BasePrefetchingDataLayer); +INSTANTIATE_CLASS(BasePrefetchingMultiDataLayer); } // namespace caffe diff --git a/src/caffe/layers/base_data_layer.cu b/src/caffe/layers/base_data_layer.cu index 9335a5bc9a9..8f64444edb8 100644 --- a/src/caffe/layers/base_data_layer.cu +++ b/src/caffe/layers/base_data_layer.cu @@ -27,4 +27,33 @@ void BasePrefetchingDataLayer::Forward_gpu( INSTANTIATE_LAYER_GPU_FORWARD(BasePrefetchingDataLayer); +template +void BasePrefetchingMultiDataLayer::Forward_gpu( + const vector*>& bottom, const vector*>& top) { + // First, join the thread + JoinPrefetchThread(); + // Reshape to loaded data. + for (int data_id = 0; data_id < input_data_size_; data_id++) { + top[data_id]->ReshapeLike(*prefetch_data_[data_id]); + // Copy the data + caffe_copy( + prefetch_data_[data_id]->count(), + prefetch_data_[data_id]->cpu_data(), + top[data_id]->mutable_gpu_data()); + DLOG(INFO) << "Prefetch copied"; + } + if (this->output_labels_) { + // Reshape to loaded labels. + top[input_data_size_]->ReshapeLike(prefetch_label_); + // Copy the labels. + caffe_copy(prefetch_label_.count(), prefetch_label_.cpu_data(), + top[input_data_size_]->mutable_gpu_data()); + } + // Start a new prefetch thread + DLOG(INFO) << "CreatePrefetchThread"; + CreatePrefetchThread(); +} + +INSTANTIATE_LAYER_GPU_FORWARD(BasePrefetchingMultiDataLayer); + } // namespace caffe diff --git a/src/caffe/layers/image_data_layer.cpp b/src/caffe/layers/image_data_layer.cpp index 18c035cba9d..82befe20338 100644 --- a/src/caffe/layers/image_data_layer.cpp +++ b/src/caffe/layers/image_data_layer.cpp @@ -15,6 +15,33 @@ namespace caffe { + +template +int ImageDataLayer::findNumOccurrences(char delim, std::string text) { + int count = 0; + int pos = text.find(delim, 0); + while (pos != text.npos) { + pos = text.find(delim, pos+1); + count++; + } + return count; +} + +template +void ImageDataLayer::split( + char delim, + const std::string &line, + std::vector *parts) { + int pos = line.find(delim, 0); + int last_pos = 0; + while (pos != line.npos) { + parts->push_back(line.substr(last_pos, pos - last_pos)); + last_pos = pos+1; + pos = line.find(delim, pos+1); + } + parts->push_back(line.substr(last_pos, line.size()-1)); +} + template ImageDataLayer::~ImageDataLayer() { this->JoinPrefetchThread(); @@ -26,20 +53,51 @@ void ImageDataLayer::DataLayerSetUp(const vector*>& bottom, const int new_height = this->layer_param_.image_data_param().new_height(); const int new_width = this->layer_param_.image_data_param().new_width(); const bool is_color = this->layer_param_.image_data_param().is_color(); - string root_folder = this->layer_param_.image_data_param().root_folder(); + const int batch_size = this->layer_param_.image_data_param().batch_size(); + const string root_folder = this->layer_param_.image_data_param().root_folder(); + const string source = this->layer_param_.image_data_param().source(); CHECK((new_height == 0 && new_width == 0) || (new_height > 0 && new_width > 0)) << "Current implementation requires " "new_height and new_width to be set at the same time."; // Read the file with filenames and labels - const string& source = this->layer_param_.image_data_param().source(); - LOG(INFO) << "Opening file " << source; + LOG(INFO) << "Opening file '" << source << "'"; std::ifstream infile(source.c_str()); - string filename; + std::string line; + char delimiter = 0; + std::vector filenames; int label; - while (infile >> filename >> label) { - lines_.push_back(std::make_pair(filename, label)); + + // read one line and find the delimiter + CHECK(std::getline(infile, line)) + << "Failed to read from input file: '" + << source.c_str() << "'"; + // find delimiter occurrences + // 2 occurrences means it can be slit in 2 files and a label + LOG(INFO) << "Input set size: " << this->input_data_size_; + if (findNumOccurrences(' ', line) == this->input_data_size_) { + delimiter = ' '; + } else if (findNumOccurrences(',', line) == this->input_data_size_) { + delimiter = ','; + } else if (findNumOccurrences('\t', line) == this->input_data_size_) { + delimiter = '\t'; + } else { + LOG(INFO) + << "delimiter outputs:\n" + << "' ' " << findNumOccurrences(' ', line) << "\n" + << "',' " << findNumOccurrences(',', line) << "\n" + << "'\t' " << findNumOccurrences('\t', line) << "\n"; } + CHECK_NE((int)delimiter, 0) + << "Input size for data layer doesn't match the text file."; + + do { + std::vector filenames; + split(delimiter, line, &filenames); + label = std::atoi(filenames.back().c_str()); + filenames.pop_back(); + lines_.push_back(std::make_pair(filenames, label)); + } while (std::getline(infile, line)); if (this->layer_param_.image_data_param().shuffle()) { // randomly shuffle data @@ -59,24 +117,26 @@ void ImageDataLayer::DataLayerSetUp(const vector*>& bottom, CHECK_GT(lines_.size(), skip) << "Not enough points to skip"; lines_id_ = skip; } - // Read an image, and use it to initialize the top blob. - cv::Mat cv_img = ReadImageToCVMat(root_folder + lines_[lines_id_].first, - new_height, new_width, is_color); - // Use data_transformer to infer the expected blob shape from a cv_image. - vector top_shape = this->data_transformer_->InferBlobShape(cv_img); - this->transformed_data_.Reshape(top_shape); - // Reshape prefetch_data and top[0] according to the batch_size. - const int batch_size = this->layer_param_.image_data_param().batch_size(); - top_shape[0] = batch_size; - this->prefetch_data_.Reshape(top_shape); - top[0]->ReshapeLike(this->prefetch_data_); - - LOG(INFO) << "output data size: " << top[0]->num() << "," - << top[0]->channels() << "," << top[0]->height() << "," - << top[0]->width(); + for (int img_c = 0; img_c < this->input_data_size_; img_c++) { + // Read an image, and use it to initialize the top blob. + cv::Mat cv_img = ReadImageToCVMat( + root_folder + lines_[lines_id_].first.at(img_c), + new_height, new_width, is_color); + // Use data_transformer to infer the expected blob shape from a cv_image. + vector top_shape = this->data_transformer_->InferBlobShape(cv_img); + this->transformed_data_.Reshape(top_shape); + // Reshape prefetch_data and top[0] according to the batch_size. + top_shape[0] = batch_size; + this->prefetch_data_[img_c]->Reshape(top_shape); + top[img_c]->ReshapeLike(*(this->prefetch_data_[img_c])); + + LOG(INFO) << "output data size: " << top[img_c]->num() << "," + << top[img_c]->channels() << "," << top[img_c]->height() << "," + << top[img_c]->width(); + } // label vector label_shape(1, batch_size); - top[1]->Reshape(label_shape); + top.back()->Reshape(label_shape); this->prefetch_label_.Reshape(label_shape); } @@ -95,7 +155,7 @@ void ImageDataLayer::InternalThreadEntry() { double read_time = 0; double trans_time = 0; CPUTimer timer; - CHECK(this->prefetch_data_.count()); + CHECK(this->prefetch_data_[0]->count()); CHECK(this->transformed_data_.count()); ImageDataParameter image_data_param = this->layer_param_.image_data_param(); const int batch_size = image_data_param.batch_size(); @@ -106,36 +166,44 @@ void ImageDataLayer::InternalThreadEntry() { // Reshape according to the first image of each batch // on single input batches allows for inputs of varying dimension. - cv::Mat cv_img = ReadImageToCVMat(root_folder + lines_[lines_id_].first, + cv::Mat cv_img = ReadImageToCVMat( + root_folder + lines_[lines_id_].first.at(0), new_height, new_width, is_color); // Use data_transformer to infer the expected blob shape from a cv_img. vector top_shape = this->data_transformer_->InferBlobShape(cv_img); this->transformed_data_.Reshape(top_shape); // Reshape prefetch_data according to the batch_size. top_shape[0] = batch_size; - this->prefetch_data_.Reshape(top_shape); - - Dtype* prefetch_data = this->prefetch_data_.mutable_cpu_data(); + std::vector prefetch_data; Dtype* prefetch_label = this->prefetch_label_.mutable_cpu_data(); - + for (int img_c = 0; img_c < this->input_data_size_; img_c++) { + this->prefetch_data_[img_c]->Reshape(top_shape); + prefetch_data.push_back(this->prefetch_data_[img_c]->mutable_cpu_data()); + } // datum scales const int lines_size = lines_.size(); for (int item_id = 0; item_id < batch_size; ++item_id) { - // get a blob - timer.Start(); - CHECK_GT(lines_size, lines_id_); - cv::Mat cv_img = ReadImageToCVMat(root_folder + lines_[lines_id_].first, - new_height, new_width, is_color); - CHECK(cv_img.data) << "Could not load " << lines_[lines_id_].first; - read_time += timer.MicroSeconds(); - timer.Start(); - // Apply transformations (mirror, crop...) to the image - int offset = this->prefetch_data_.offset(item_id); - this->transformed_data_.set_cpu_data(prefetch_data + offset); - this->data_transformer_->Transform(cv_img, &(this->transformed_data_)); - trans_time += timer.MicroSeconds(); + for (int img_c = 0; img_c < this->input_data_size_; img_c++) { + // get a blob + timer.Start(); + CHECK_GT(lines_size, lines_id_); + cv::Mat cv_img = ReadImageToCVMat( + root_folder + lines_[lines_id_].first.at(img_c), + new_height, new_width, is_color); + CHECK(cv_img.data) + << "Could not load " << lines_[lines_id_].first.at(img_c); + read_time += timer.MicroSeconds(); + timer.Start(); + // Apply transformations (mirror, crop...) to the image + int offset = this->prefetch_data_[img_c]->offset(item_id); + this->transformed_data_.set_cpu_data(prefetch_data[img_c] + offset); + this->data_transformer_->Transform(cv_img, &(this->transformed_data_)); + trans_time += timer.MicroSeconds(); + } + // set the label prefetch_label[item_id] = lines_[lines_id_].second; + // go to the next iter lines_id_++; if (lines_id_ >= lines_size) { @@ -148,6 +216,7 @@ void ImageDataLayer::InternalThreadEntry() { } } batch_timer.Stop(); + DLOG(INFO) << "Prefetch batch: " << batch_timer.MilliSeconds() << " ms."; DLOG(INFO) << " Read time: " << read_time / 1000 << " ms."; DLOG(INFO) << "Transform time: " << trans_time / 1000 << " ms."; diff --git a/src/caffe/test/test_image_data_layer.cpp b/src/caffe/test/test_image_data_layer.cpp index 931a5ebf137..22539f526e9 100644 --- a/src/caffe/test/test_image_data_layer.cpp +++ b/src/caffe/test/test_image_data_layer.cpp @@ -23,6 +23,8 @@ class ImageDataLayerTest : public MultiDeviceTest { ImageDataLayerTest() : seed_(1701), blob_top_data_(new Blob()), + blob_top_data_2_(new Blob()), + blob_top_data_3_(new Blob()), blob_top_label_(new Blob()) {} virtual void SetUp() { blob_top_vec_.push_back(blob_top_data_); @@ -33,27 +35,73 @@ class ImageDataLayerTest : public MultiDeviceTest { std::ofstream outfile(filename_.c_str(), std::ofstream::out); LOG(INFO) << "Using temporary file " << filename_; for (int i = 0; i < 5; ++i) { - outfile << EXAMPLES_SOURCE_DIR "images/cat.jpg " << i; + outfile << EXAMPLES_SOURCE_DIR "images/cat.jpg " << i << "\n"; } outfile.close(); // Create test input file for images of distinct sizes. MakeTempFilename(&filename_reshape_); std::ofstream reshapefile(filename_reshape_.c_str(), std::ofstream::out); LOG(INFO) << "Using temporary file " << filename_reshape_; - reshapefile << EXAMPLES_SOURCE_DIR "images/cat.jpg " << 0; - reshapefile << EXAMPLES_SOURCE_DIR "images/fish-bike.jpg " << 1; + reshapefile << EXAMPLES_SOURCE_DIR "images/cat.jpg " << 0 << "\n"; + reshapefile << EXAMPLES_SOURCE_DIR "images/fish-bike.jpg " << 1 << "\n"; reshapefile.close(); + + // Create test input files for multiple images as an input (siamese etc.). + MakeTempFilename(&filename_siamese_); + std::ofstream siamesefile(filename_siamese_.c_str(), std::ofstream::out); + LOG(INFO) << "Using temporary file " << filename_siamese_; + siamesefile << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << 1 << "\n"; + siamesefile << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_2.jpg " + << 0 << "\n"; + siamesefile << EXAMPLES_SOURCE_DIR "images/cat_2.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_2.jpg " + << 1 << "\n"; + siamesefile.close(); + + MakeTempFilename(&filename_tripple_); + std::ofstream tripplefile(filename_tripple_.c_str(), std::ofstream::out); + LOG(INFO) << "Using temporary file " << filename_tripple_; + tripplefile << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_2.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_3.jpg " + << 0 << "\n"; + tripplefile << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_2.jpg " + << 1 << "\n"; + tripplefile << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_2.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << 2 << "\n"; + tripplefile << EXAMPLES_SOURCE_DIR "images/cat_2.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << 3 << "\n"; + tripplefile << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << EXAMPLES_SOURCE_DIR "images/cat_1.jpg " + << 4 << "\n"; + tripplefile.close(); } virtual ~ImageDataLayerTest() { delete blob_top_data_; + delete blob_top_data_2_; + delete blob_top_data_3_; delete blob_top_label_; } int seed_; string filename_; string filename_reshape_; + string filename_siamese_; + string filename_tripple_; Blob* const blob_top_data_; + Blob* const blob_top_data_2_; + Blob* const blob_top_data_3_; Blob* const blob_top_label_; vector*> blob_bottom_vec_; vector*> blob_top_vec_; @@ -65,6 +113,8 @@ TYPED_TEST(ImageDataLayerTest, TestRead) { typedef typename TypeParam::Dtype Dtype; LayerParameter param; ImageDataParameter* image_data_param = param.mutable_image_data_param(); + param.add_top("img"); + param.add_top("label"); image_data_param->set_batch_size(5); image_data_param->set_source(this->filename_.c_str()); image_data_param->set_shuffle(false); @@ -91,6 +141,8 @@ TYPED_TEST(ImageDataLayerTest, TestResize) { typedef typename TypeParam::Dtype Dtype; LayerParameter param; ImageDataParameter* image_data_param = param.mutable_image_data_param(); + param.add_top("img"); + param.add_top("label"); image_data_param->set_batch_size(5); image_data_param->set_source(this->filename_.c_str()); image_data_param->set_new_height(256); @@ -119,6 +171,8 @@ TYPED_TEST(ImageDataLayerTest, TestReshape) { typedef typename TypeParam::Dtype Dtype; LayerParameter param; ImageDataParameter* image_data_param = param.mutable_image_data_param(); + param.add_top("img"); + param.add_top("label"); image_data_param->set_batch_size(1); image_data_param->set_source(this->filename_reshape_.c_str()); image_data_param->set_shuffle(false); @@ -146,6 +200,8 @@ TYPED_TEST(ImageDataLayerTest, TestShuffle) { typedef typename TypeParam::Dtype Dtype; LayerParameter param; ImageDataParameter* image_data_param = param.mutable_image_data_param(); + param.add_top("img"); + param.add_top("label"); image_data_param->set_batch_size(5); image_data_param->set_source(this->filename_.c_str()); image_data_param->set_shuffle(true); @@ -176,4 +232,122 @@ TYPED_TEST(ImageDataLayerTest, TestShuffle) { } } +TYPED_TEST(ImageDataLayerTest, TestSiamese) { + typedef typename TypeParam::Dtype Dtype; + this->blob_top_vec_.clear(); + this->blob_top_vec_.push_back(this->blob_top_data_); + this->blob_top_vec_.push_back(this->blob_top_data_2_); + this->blob_top_vec_.push_back(this->blob_top_label_); + LayerParameter param; + ImageDataParameter* image_data_param = param.mutable_image_data_param(); + param.add_top("img1"); + param.add_top("img2"); + param.add_top("label"); + image_data_param->set_batch_size(3); + image_data_param->set_source(this->filename_siamese_.c_str()); + image_data_param->set_shuffle(false); + ImageDataLayer layer(param); + layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_); + EXPECT_EQ(this->blob_top_vec_[0]->num(), 3); + EXPECT_EQ(this->blob_top_vec_[0]->channels(), 3); + EXPECT_EQ(this->blob_top_vec_[0]->height(), 360); + EXPECT_EQ(this->blob_top_vec_[0]->width(), 480); + EXPECT_EQ(this->blob_top_vec_[1]->num(), 3); + EXPECT_EQ(this->blob_top_vec_[1]->channels(), 3); + EXPECT_EQ(this->blob_top_vec_[1]->height(), 360); + EXPECT_EQ(this->blob_top_vec_[1]->width(), 480); + EXPECT_EQ(this->blob_top_label_->num(), 3); + EXPECT_EQ(this->blob_top_label_->channels(), 1); + EXPECT_EQ(this->blob_top_label_->height(), 1); + EXPECT_EQ(this->blob_top_label_->width(), 1); + // Go through the data twice + for (int iter = 0; iter < 2; ++iter) { + layer.Forward(this->blob_bottom_vec_, this->blob_top_vec_); + EXPECT_EQ(1, this->blob_top_label_->cpu_data()[0]); + EXPECT_EQ(0, this->blob_top_label_->cpu_data()[1]); + EXPECT_EQ(1, this->blob_top_label_->cpu_data()[2]); + int n = 0; + for (int c = 0; c < 3; c++) { + for (int h = 0; h < 360; h++) { + for (int w = 0; w < 480; w++) { + EXPECT_EQ(this->blob_top_data_->data_at(n, c, h, w), + this->blob_top_data_2_->data_at(n, c, h, w)); + } + } + } + n = 2; + for (int c = 0; c < 3; c++) { + for (int h = 0; h < 360; h++) { + for (int w = 0; w < 480; w++) { + EXPECT_EQ(this->blob_top_data_->data_at(n, c, h, w), + this->blob_top_data_2_->data_at(n, c, h, w)); + } + } + } + } +} + + +TYPED_TEST(ImageDataLayerTest, TestTripple) { + typedef typename TypeParam::Dtype Dtype; + this->blob_top_vec_.clear(); + this->blob_top_vec_.push_back(this->blob_top_data_); + this->blob_top_vec_.push_back(this->blob_top_data_2_); + this->blob_top_vec_.push_back(this->blob_top_data_3_); + this->blob_top_vec_.push_back(this->blob_top_label_); + LayerParameter param; + ImageDataParameter* image_data_param = param.mutable_image_data_param(); + param.add_top("img1"); + param.add_top("img2"); + param.add_top("img3"); + param.add_top("label"); + image_data_param->set_batch_size(5); + image_data_param->set_source(this->filename_tripple_.c_str()); + image_data_param->set_shuffle(false); + ImageDataLayer layer(param); + layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_); + EXPECT_EQ(this->blob_top_vec_[0]->num(), 5); + EXPECT_EQ(this->blob_top_vec_[0]->channels(), 3); + EXPECT_EQ(this->blob_top_vec_[0]->height(), 360); + EXPECT_EQ(this->blob_top_vec_[0]->width(), 480); + EXPECT_EQ(this->blob_top_vec_[1]->num(), 5); + EXPECT_EQ(this->blob_top_vec_[1]->channels(), 3); + EXPECT_EQ(this->blob_top_vec_[1]->height(), 360); + EXPECT_EQ(this->blob_top_vec_[1]->width(), 480); + EXPECT_EQ(this->blob_top_vec_[2]->num(), 5); + EXPECT_EQ(this->blob_top_vec_[2]->channels(), 3); + EXPECT_EQ(this->blob_top_vec_[2]->height(), 360); + EXPECT_EQ(this->blob_top_vec_[2]->width(), 480); + EXPECT_EQ(this->blob_top_label_->num(), 5); + EXPECT_EQ(this->blob_top_label_->channels(), 1); + EXPECT_EQ(this->blob_top_label_->height(), 1); + EXPECT_EQ(this->blob_top_label_->width(), 1); + // Go through the data twice + for (int iter = 0; iter < 2; ++iter) { + layer.Forward(this->blob_bottom_vec_, this->blob_top_vec_); + for (int i = 0; i < 5; ++i) { + EXPECT_EQ(i, this->blob_top_label_->cpu_data()[i]); + } + for (int n = 0; n < 5; n++) { + for (int c = 0; c < 3; c++) { + for (int h = 0; h < 360; h++) { + for (int w = 0; w < 480; w++) { + if (n == 1 || n == 4) { + EXPECT_EQ(this->blob_top_data_->data_at(n, c, h, w), + this->blob_top_data_2_->data_at(n, c, h, w)); + } + if (n == 2 || n == 4) { + EXPECT_EQ(this->blob_top_data_->data_at(n, c, h, w), + this->blob_top_data_3_->data_at(n, c, h, w)); + } + if (n == 3 || n == 4) { + EXPECT_EQ(this->blob_top_data_3_->data_at(n, c, h, w), + this->blob_top_data_2_->data_at(n, c, h, w)); + } + } + } + } + } + } +} } // namespace caffe