From f465cd0242bcddcfc347fcba4f5480c391e362da Mon Sep 17 00:00:00 2001 From: vddong <15234076020@163.com> Date: Fri, 19 Nov 2021 13:36:24 +0800 Subject: [PATCH] [feat][assistant][I3T96L]add new dataset operator YesNo --- .../ccsrc/minddata/dataset/api/datasets.cc | 22 ++ .../engine/ir/datasetops/source/bindings.cc | 11 + .../engine/datasetops/source/CMakeLists.txt | 43 ++-- .../engine/datasetops/source/yes_no_op.cc | 148 +++++++++++++ .../engine/datasetops/source/yes_no_op.h | 92 ++++++++ .../engine/ir/datasetops/dataset_node.h | 1 + .../ir/datasetops/source/CMakeLists.txt | 1 + .../ir/datasetops/source/yes_no_node.cc | 115 ++++++++++ .../engine/ir/datasetops/source/yes_no_node.h | 92 ++++++++ .../dataset/include/dataset/datasets.h | 66 ++++++ .../dataset/include/dataset/samplers.h | 1 + mindspore/dataset/engine/datasets.py | 116 ++++++++++- mindspore/dataset/engine/validators.py | 26 +++ tests/ut/cpp/dataset/CMakeLists.txt | 1 + .../cpp/dataset/c_api_dataset_yes_no_test.cc | 196 ++++++++++++++++++ .../dataset/testYesNoData/1_2_3_4_5_6_7_1.wav | Bin 0 -> 32044 bytes .../dataset/testYesNoData/5_1_1_1_1_1_1_1.wav | Bin 0 -> 32044 bytes .../dataset/testYesNoData/8_1_1_1_1_1_1_1.wav | Bin 0 -> 32044 bytes .../ut/python/dataset/test_datasets_yes_no.py | 185 +++++++++++++++++ 19 files changed, 1094 insertions(+), 22 deletions(-) create mode 100644 mindspore/ccsrc/minddata/dataset/engine/datasetops/source/yes_no_op.cc create mode 100644 mindspore/ccsrc/minddata/dataset/engine/datasetops/source/yes_no_op.h create mode 100644 mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/yes_no_node.cc create mode 100644 mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/yes_no_node.h create mode 100644 tests/ut/cpp/dataset/c_api_dataset_yes_no_test.cc create mode 100644 tests/ut/data/dataset/testYesNoData/1_2_3_4_5_6_7_1.wav create mode 100644 tests/ut/data/dataset/testYesNoData/5_1_1_1_1_1_1_1.wav create mode 100644 tests/ut/data/dataset/testYesNoData/8_1_1_1_1_1_1_1.wav create mode 100644 tests/ut/python/dataset/test_datasets_yes_no.py diff --git a/mindspore/ccsrc/minddata/dataset/api/datasets.cc b/mindspore/ccsrc/minddata/dataset/api/datasets.cc index 74c47b0e60b..91cfbf031d3 100644 --- a/mindspore/ccsrc/minddata/dataset/api/datasets.cc +++ b/mindspore/ccsrc/minddata/dataset/api/datasets.cc @@ -115,6 +115,7 @@ #include "minddata/dataset/engine/ir/datasetops/source/tf_record_node.h" #include "minddata/dataset/engine/ir/datasetops/source/usps_node.h" #include "minddata/dataset/engine/ir/datasetops/source/voc_node.h" +#include "minddata/dataset/engine/ir/datasetops/source/yes_no_node.h" #endif namespace mindspore { @@ -1543,6 +1544,27 @@ TFRecordDataset::TFRecordDataset(const std::vector> &dataset_f ir_node_ = std::static_pointer_cast(ds); } +YesNoDataset::YesNoDataset(const std::vector &dataset_dir, const std::shared_ptr &sampler, + const std::shared_ptr &cache) { + auto sampler_obj = sampler ? sampler->Parse() : nullptr; + auto ds = std::make_shared(CharToString(dataset_dir), sampler_obj, cache); + ir_node_ = std::static_pointer_cast(ds); +} + +YesNoDataset::YesNoDataset(const std::vector &dataset_dir, const Sampler *sampler, + const std::shared_ptr &cache) { + auto sampler_obj = sampler ? sampler->Parse() : nullptr; + auto ds = std::make_shared(CharToString(dataset_dir), sampler_obj, cache); + ir_node_ = std::static_pointer_cast(ds); +} + +YesNoDataset::YesNoDataset(const std::vector &dataset_dir, const std::reference_wrapper sampler, + const std::shared_ptr &cache) { + auto sampler_obj = sampler.get().Parse(); + auto ds = std::make_shared(CharToString(dataset_dir), sampler_obj, cache); + ir_node_ = std::static_pointer_cast(ds); +} + #endif } // namespace dataset } // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/engine/ir/datasetops/source/bindings.cc b/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/engine/ir/datasetops/source/bindings.cc index 8d51dc4af13..582b8f36658 100644 --- a/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/engine/ir/datasetops/source/bindings.cc +++ b/mindspore/ccsrc/minddata/dataset/api/python/bindings/dataset/engine/ir/datasetops/source/bindings.cc @@ -44,6 +44,7 @@ #include "minddata/dataset/engine/ir/datasetops/source/mnist_node.h" #include "minddata/dataset/engine/ir/datasetops/source/random_node.h" #include "minddata/dataset/engine/ir/datasetops/source/text_file_node.h" +#include "minddata/dataset/engine/ir/datasetops/source/yes_no_node.h" // IR leaf nodes disabled for android #ifndef ENABLE_ANDROID @@ -448,5 +449,15 @@ PYBIND_REGISTER(VOCNode, 2, ([](const py::module *m) { })); })); +PYBIND_REGISTER(YesNoNode, 2, ([](const py::module *m) { + (void)py::class_>(*m, "YesNoNode", + "to create a YesNoNode") + .def(py::init([](std::string dataset_dir, py::handle sampler) { + auto yes_no = std::make_shared(dataset_dir, toSamplerObj(sampler), nullptr); + THROW_IF_ERROR(yes_no->ValidateParams()); + return yes_no; + })); + })); + } // namespace dataset } // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/CMakeLists.txt b/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/CMakeLists.txt index ab6ff288d76..94c41590968 100644 --- a/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/CMakeLists.txt +++ b/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/CMakeLists.txt @@ -3,33 +3,34 @@ file(GLOB_RECURSE _CURRENT_SRC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cc" set_property(SOURCE ${_CURRENT_SRC_FILES} PROPERTY COMPILE_DEFINITIONS SUBMODULE_ID=mindspore::SubModuleId::SM_MD) set(DATASET_ENGINE_DATASETOPS_SOURCE_SRC_FILES - io_block.cc - image_folder_op.cc - mnist_op.cc - coco_op.cc - cifar_op.cc - random_data_op.cc - celeba_op.cc - sbu_op.cc - text_file_op.cc - clue_op.cc - csv_op.cc + ag_news_op.cc album_op.cc - usps_op.cc - mappable_leaf_op.cc - nonmappable_leaf_op.cc + celeba_op.cc + cifar_op.cc cityscapes_op.cc + clue_op.cc + coco_op.cc + csv_op.cc + dbpedia_op.cc div2k_op.cc - flickr_op.cc - qmnist_op.cc emnist_op.cc fake_image_op.cc - lj_speech_op.cc - places365_op.cc - photo_tour_op.cc fashion_mnist_op.cc - ag_news_op.cc - dbpedia_op.cc + flickr_op.cc + image_folder_op.cc + io_block.cc + lj_speech_op.cc + mappable_leaf_op.cc + mnist_op.cc + nonmappable_leaf_op.cc + photo_tour_op.cc + places365_op.cc + qmnist_op.cc + random_data_op.cc + sbu_op.cc + text_file_op.cc + usps_op.cc + yes_no_op.cc ) set(DATASET_ENGINE_DATASETOPS_SOURCE_SRC_FILES diff --git a/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/yes_no_op.cc b/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/yes_no_op.cc new file mode 100644 index 00000000000..ae71876f2da --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/yes_no_op.cc @@ -0,0 +1,148 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "minddata/dataset/engine/datasetops/source/yes_no_op.h" + +#include +#include +#include +#include +#include + +#include "minddata/dataset/audio/kernels/audio_utils.h" +#include "minddata/dataset/core/config_manager.h" +#include "minddata/dataset/core/tensor_shape.h" +#include "minddata/dataset/engine/datasetops/source/sampler/sequential_sampler.h" +#include "minddata/dataset/engine/execution_tree.h" +#include "utils/file_utils.h" +#include "utils/ms_utils.h" + +namespace mindspore { +namespace dataset { +constexpr float kMaxShortVal = 32767.0; +constexpr char kExtension[] = ".wav"; +constexpr int kStrLen = 15; // the length of name. +#ifndef _WIN32 +constexpr char kSplitSymbol[] = "/"; +#else +constexpr char kSplitSymbol[] = "\\"; +#endif + +YesNoOp::YesNoOp(const std::string &file_dir, int32_t num_workers, int32_t queue_size, + std::unique_ptr data_schema, std::shared_ptr sampler) + : MappableLeafOp(num_workers, queue_size, std::move(sampler)), + dataset_dir_(file_dir), + data_schema_(std::move(data_schema)) {} + +Status YesNoOp::PrepareData() { + auto realpath = FileUtils::GetRealPath(dataset_dir_.data()); + if (!realpath.has_value()) { + MS_LOG(ERROR) << "Get real path failed, path=" << dataset_dir_; + RETURN_STATUS_UNEXPECTED("Get real path failed, path=" + dataset_dir_); + } + Path dir(realpath.value()); + if (dir.Exists() == false || dir.IsDirectory() == false) { + RETURN_STATUS_UNEXPECTED("Invalid parameter, failed to open speech commands: " + dataset_dir_); + } + std::shared_ptr dir_itr = Path::DirIterator::OpenDirectory(&dir); + RETURN_UNEXPECTED_IF_NULL(dir_itr); + while (dir_itr->HasNext()) { + Path file = dir_itr->Next(); + if (file.Extension() == kExtension) { + all_wave_files_.emplace_back(file.ToString()); + } + } + CHECK_FAIL_RETURN_UNEXPECTED(!all_wave_files_.empty(), "Invalid file, no .wav files found under " + dataset_dir_); + num_rows_ = all_wave_files_.size(); + return Status::OK(); +} + +void YesNoOp::Print(std::ostream &out, bool show_all) const { + if (!show_all) { + ParallelOp::Print(out, show_all); + out << "\n"; + } else { + ParallelOp::Print(out, show_all); + out << "\nNumber of rows: " << num_rows_ << "\nYesNo directory: " << dataset_dir_ << "\n\n"; + } +} + +Status YesNoOp::Split(const std::string &line, std::vector *split_num) { + RETURN_UNEXPECTED_IF_NULL(split_num); + std::string str = line; + int dot_pos = str.find_last_of(kSplitSymbol); + std::string sub_line = line.substr(dot_pos + 1, kStrLen); // (dot_pos + 1) because the index start from 0. + std::string::size_type pos; + std::vector split; + sub_line += "_"; // append to sub_line indicating the end of the string. + uint32_t size = sub_line.size(); + for (uint32_t index = 0; index < size;) { + pos = sub_line.find("_", index); + if (pos != index) { + std::string s = sub_line.substr(index, pos - index); + split.emplace_back(s); + } + index = pos + 1; + } + try { + for (int i = 0; i < split.size(); i++) { + split_num->emplace_back(stoi(split[i])); + } + } catch (const std::exception &e) { + MS_LOG(ERROR) << "Converting char to int confront with an error in function stoi()."; + RETURN_STATUS_UNEXPECTED("Converting char to int confront with an error in function stoi()."); + } + return Status::OK(); +} + +Status YesNoOp::LoadTensorRow(row_id_type index, TensorRow *trow) { + RETURN_UNEXPECTED_IF_NULL(trow); + std::shared_ptr waveform, sample_rate_scalar, label_scalar; + int32_t sample_rate; + std::string file_name = all_wave_files_[index]; + std::vector label; + std::vector waveform_vec; + RETURN_IF_NOT_OK(Split(file_name, &label)); + RETURN_IF_NOT_OK(ReadWaveFile(file_name, &waveform_vec, &sample_rate)); + RETURN_IF_NOT_OK(Tensor::CreateFromVector(waveform_vec, &waveform)); + RETURN_IF_NOT_OK(waveform->ExpandDim(0)); + RETURN_IF_NOT_OK(Tensor::CreateScalar(sample_rate, &sample_rate_scalar)); + RETURN_IF_NOT_OK(Tensor::CreateFromVector(label, &label_scalar)); + (*trow) = TensorRow(index, {waveform, sample_rate_scalar, label_scalar}); + trow->setPath({file_name, file_name, file_name}); + return Status::OK(); +} + +Status YesNoOp::CountTotalRows(int64_t *count) { + RETURN_UNEXPECTED_IF_NULL(count); + if (all_wave_files_.size() == 0) { + RETURN_IF_NOT_OK(PrepareData()); + } + *count = static_cast(all_wave_files_.size()); + return Status::OK(); +} + +Status YesNoOp::ComputeColMap() { + if (column_name_id_map_.empty()) { + for (int32_t i = 0; i < data_schema_->NumColumns(); ++i) { + column_name_id_map_[data_schema_->Column(i).Name()] = i; + } + } else { + MS_LOG(WARNING) << "Column name map is already set!"; + } + return Status::OK(); +} +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/yes_no_op.h b/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/yes_no_op.h new file mode 100644 index 00000000000..9c6bd442c05 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/engine/datasetops/source/yes_no_op.h @@ -0,0 +1,92 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MINDSPORE_CCSRC_MINDDATA_DATASET_ENGINE_DATASETOPS_SOURCE_YES_NO_OP_H_ +#define MINDSPORE_CCSRC_MINDDATA_DATASET_ENGINE_DATASETOPS_SOURCE_YES_NO_OP_H_ + +#include +#include +#include +#include +#include + +#include "minddata/dataset/core/tensor.h" +#include "minddata/dataset/engine/data_schema.h" +#include "minddata/dataset/engine/datasetops/parallel_op.h" +#include "minddata/dataset/engine/datasetops/source/mappable_leaf_op.h" +#include "minddata/dataset/engine/datasetops/source/sampler/sampler.h" +#include "minddata/dataset/util/path.h" +#include "minddata/dataset/util/queue.h" +#include "minddata/dataset/util/services.h" +#include "minddata/dataset/util/status.h" +#include "minddata/dataset/util/wait_post.h" + +namespace mindspore { +namespace dataset { +class YesNoOp : public MappableLeafOp { + public: + /// Constructor. + /// @param std::string file_dir - dir directory of YesNo. + /// @param int32_t num_workers - number of workers reading images in parallel. + /// @param int32_t queue_size - connector queue size. + /// @param std::unique_ptr data_schema - the schema of the YesNo dataset. + /// @param std::shared_ptr sampler - sampler tells YesNoOp what to read. + YesNoOp(const std::string &file_dir, int32_t num_workers, int32_t queue_size, std::unique_ptr data_schema, + std::shared_ptr sampler); + + /// Destructor. + ~YesNoOp() = default; + + /// A print method typically used for debugging. + /// @param std::ostream &out - out stream. + /// @param bool show_all - whether to show all information. + void Print(std::ostream &out, bool show_all) const override; + + /// Op name getter. + /// @return Name of the current Op. + std::string Name() const override { return "YesNoOp"; } + + /// @param int64_t *count - output rows number of YesNoDataset. + /// @return Status - The status code returned. + Status CountTotalRows(int64_t *count); + + private: + /// Load a tensor row according to wave id. + /// @param row_id_type row_id - id for this tensor row. + /// @param TensorRow trow - wave & target read into this tensor row. + /// @return Status - The status code returned. + Status LoadTensorRow(row_id_type row_id, TensorRow *trow) override; + + /// Get file infos by file name. + /// @param string line - file name. + /// @param vector split_num - vector of annotation. + /// @return Status - The status code returned. + Status Split(const std::string &line, std::vector *split_num); + + /// Initialize YesNoDataset related var, calls the function to walk all files. + /// @return Status - The status code returned. + Status PrepareData(); + + /// Private function for computing the assignment of the column name map. + /// @return Status - The status code returned. + Status ComputeColMap() override; + + std::vector all_wave_files_; + std::string dataset_dir_; + std::unique_ptr data_schema_; +}; +} // namespace dataset +} // namespace mindspore +#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_ENGINE_DATASETOPS_SOURCE_YES_NO_OP_H diff --git a/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/dataset_node.h b/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/dataset_node.h index 1d42ed1deb7..0494427a705 100644 --- a/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/dataset_node.h +++ b/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/dataset_node.h @@ -104,6 +104,7 @@ constexpr char kTextFileNode[] = "TextFileDataset"; constexpr char kTFRecordNode[] = "TFRecordDataset"; constexpr char kUSPSNode[] = "USPSDataset"; constexpr char kVOCNode[] = "VOCDataset"; +constexpr char kYesNoNode[] = "YesNoDataset"; Status AddShuffleOp(int64_t num_files, int64_t num_devices, int64_t num_rows, int64_t total_rows, int32_t connector_que_size, std::shared_ptr *shuffle_op); diff --git a/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/CMakeLists.txt b/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/CMakeLists.txt index e9bd4ba6b8e..1b43e86f8e1 100644 --- a/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/CMakeLists.txt +++ b/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/CMakeLists.txt @@ -32,6 +32,7 @@ set(DATASET_ENGINE_IR_DATASETOPS_SOURCE_SRC_FILES tf_record_node.cc usps_node.cc voc_node.cc + yes_no_node.cc ) if(ENABLE_PYTHON) diff --git a/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/yes_no_node.cc b/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/yes_no_node.cc new file mode 100644 index 00000000000..bee2291bd45 --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/yes_no_node.cc @@ -0,0 +1,115 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "minddata/dataset/engine/ir/datasetops/source/yes_no_node.h" + +#include + +#include "minddata/dataset/engine/datasetops/source/yes_no_op.h" +#include "minddata/dataset/util/status.h" + +namespace mindspore { +namespace dataset { +// Constructor for YesNoNode. +YesNoNode::YesNoNode(const std::string &dataset_dir, std::shared_ptr sampler, + std::shared_ptr cache) + : MappableSourceNode(std::move(cache)), dataset_dir_(dataset_dir), sampler_(sampler) {} + +std::shared_ptr YesNoNode::Copy() { + std::shared_ptr sampler = (sampler_ == nullptr) ? nullptr : sampler_->SamplerCopy(); + auto node = std::make_shared(dataset_dir_, sampler, cache_); + return node; +} + +void YesNoNode::Print(std::ostream &out) const { + out << (Name() + "(cache: " + ((cache_ != nullptr) ? "true" : "false") + ")"); +} + +Status YesNoNode::ValidateParams() { + RETURN_IF_NOT_OK(DatasetNode::ValidateParams()); + RETURN_IF_NOT_OK(ValidateDatasetDirParam("YesNoNode", dataset_dir_)); + RETURN_IF_NOT_OK(ValidateDatasetSampler("YesNoNode", sampler_)); + + return Status::OK(); +} + +Status YesNoNode::Build(std::vector> *const node_ops) { + // Do internal Schema generation. + auto schema = std::make_unique(); + RETURN_IF_NOT_OK( + schema->AddColumn(ColDescriptor("waveform", DataType(DataType::DE_FLOAT32), TensorImpl::kFlexible, 1))); + TensorShape sample_rate_scalar = TensorShape::CreateScalar(); + TensorShape lable_scalar = TensorShape::CreateScalar(); + RETURN_IF_NOT_OK(schema->AddColumn( + ColDescriptor("sample_rate", DataType(DataType::DE_INT32), TensorImpl::kFlexible, 0, &sample_rate_scalar))); + RETURN_IF_NOT_OK( + schema->AddColumn(ColDescriptor("label", DataType(DataType::DE_INT32), TensorImpl::kFlexible, 0, &lable_scalar))); + std::shared_ptr sampler_rt = nullptr; + RETURN_IF_NOT_OK(sampler_->SamplerBuild(&sampler_rt)); + + auto op = std::make_shared(dataset_dir_, num_workers_, connector_que_size_, std::move(schema), + std::move(sampler_rt)); + op->SetTotalRepeats(GetTotalRepeats()); + op->SetNumRepeatsPerEpoch(GetNumRepeatsPerEpoch()); + node_ops->push_back(op); + + return Status::OK(); +} + +Status YesNoNode::GetShardId(int32_t *shard_id) { + *shard_id = sampler_->ShardId(); + return Status::OK(); +} + +Status YesNoNode::GetDatasetSize(const std::shared_ptr &size_getter, bool estimate, + int64_t *dataset_size) { + if (dataset_size_ > 0) { + *dataset_size = dataset_size_; + return Status::OK(); + } + + int64_t num_rows, sample_size; + std::vector> ops; + RETURN_IF_NOT_OK(Build(&ops)); + CHECK_FAIL_RETURN_UNEXPECTED(!ops.empty(), "Unable to build YesNoOp."); + auto op = std::dynamic_pointer_cast(ops.front()); + RETURN_IF_NOT_OK(op->CountTotalRows(&num_rows)); + std::shared_ptr sampler_rt = nullptr; + RETURN_IF_NOT_OK(sampler_->SamplerBuild(&sampler_rt)); + sample_size = sampler_rt->CalculateNumSamples(num_rows); + if (sample_size == -1) { + RETURN_IF_NOT_OK(size_getter->DryRun(shared_from_this(), &sample_size)); + } + *dataset_size = sample_size; + dataset_size_ = *dataset_size; + return Status::OK(); +} + +Status YesNoNode::to_json(nlohmann::json *out_json) { + nlohmann::json args, sampler_args; + RETURN_IF_NOT_OK(sampler_->to_json(&sampler_args)); + args["sampler"] = sampler_args; + args["num_parallel_workers"] = num_workers_; + args["dataset_dir"] = dataset_dir_; + if (cache_ != nullptr) { + nlohmann::json cache_args; + RETURN_IF_NOT_OK(cache_->to_json(&cache_args)); + args["cache"] = cache_args; + } + *out_json = args; + return Status::OK(); +} +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/yes_no_node.h b/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/yes_no_node.h new file mode 100644 index 00000000000..32b5053fc2b --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/engine/ir/datasetops/source/yes_no_node.h @@ -0,0 +1,92 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MINDSPORE_CCSRC_MINDDATA_DATASET_ENGINE_IR_DATASETOPS_SOURCE_YES_NO_NODE_H +#define MINDSPORE_CCSRC_MINDDATA_DATASET_ENGINE_IR_DATASETOPS_SOURCE_YES_NO_NODE_H + +#include +#include +#include + +#include "minddata/dataset/engine/ir/datasetops/dataset_node.h" + +namespace mindspore { +namespace dataset { +class YesNoNode : public MappableSourceNode { + public: + /// \brief Constructor. + YesNoNode(const std::string &dataset_dir, std::shared_ptr sampler, std::shared_ptr cache); + + /// \brief Destructor. + ~YesNoNode() = default; + + /// \brief Node name getter. + /// \return Name of the current node. + std::string Name() const override { return "YesNoNode"; } + + /// \brief Print the description. + /// \param out - The output stream to write output to. + void Print(std::ostream &out) const override; + + /// \brief Copy the node to a new object. + /// \return A shared pointer to the new copy. + std::shared_ptr Copy() override; + + /// \brief a base class override function to create the required runtime dataset op objects for this class. + /// \param node_ops - A vector containing shared pointer to the Dataset Ops that this object will create. + /// \return Status Status::OK() if build successfully. + Status Build(std::vector> *const node_ops) override; + + /// \brief Parameters validation. + /// \return Status Status::OK() if all the parameters are valid. + Status ValidateParams() override; + + /// \brief Get the shard id of node. + /// \param[in] shard_id Shard id. + /// \return Status Status::OK() if get shard id successfully. + Status GetShardId(int32_t *shard_id) override; + + /// \brief Base-class override for GetDatasetSize. + /// \param[in] size_getter Shared pointer to DatasetSizeGetter. + /// \param[in] estimate This is only supported by some of the ops and it's used to speed up the process of getting + /// dataset size at the expense of accuracy. + /// \param[out] dataset_size the size of the dataset. + /// \return Status of the function. + Status GetDatasetSize(const std::shared_ptr &size_getter, bool estimate, + int64_t *dataset_size) override; + + /// \brief Getter functions. + const std::string &DatasetDir() const { return dataset_dir_; } + + /// \brief Get the arguments of node. + /// \param[out] out_json JSON string of all attributes. + /// \return Status of the function. + Status to_json(nlohmann::json *out_json) override; + + /// \brief Sampler getter. + /// \return SamplerObj of the current node. + std::shared_ptr Sampler() override { return sampler_; } + + /// \brief Sampler setter. + /// \param[in] sampler Sampler object used to choose samples from the dataset. + void SetSampler(std::shared_ptr sampler) override { sampler_ = sampler; } + + private: + std::string dataset_dir_; + std::shared_ptr sampler_; +}; +} // namespace dataset +} // namespace mindspore +#endif // MINDSPORE_CCSRC_MINDDATA_DATASET_ENGINE_IR_DATASETOPS_SOURCE_YES_NO_NODE_H diff --git a/mindspore/ccsrc/minddata/dataset/include/dataset/datasets.h b/mindspore/ccsrc/minddata/dataset/include/dataset/datasets.h index 5b81aa03788..e4416699a15 100644 --- a/mindspore/ccsrc/minddata/dataset/include/dataset/datasets.h +++ b/mindspore/ccsrc/minddata/dataset/include/dataset/datasets.h @@ -3919,6 +3919,72 @@ inline std::shared_ptr VOC(const std::string &dataset_dir, const std MapStringToChar(class_indexing), decode, sampler, cache, extra_metadata); } +/// \class YesNoDataset. +/// \brief A source dataset for reading and parsing YesNo dataset. +class YesNoDataset : public Dataset { + public: + /// \brief Constructor of YesNoDataset. + /// \param[in] dataset_dir Path to the root directory that contains the dataset. + /// \param[in] sampler Shared pointer to a sampler object used to choose samples from the dataset. If sampler is not + /// given, a `RandomSampler` will be used to randomly iterate the entire dataset. + /// \param[in] cache Tensor cache to use. + YesNoDataset(const std::vector &dataset_dir, const std::shared_ptr &sampler, + const std::shared_ptr &cache); + + /// \brief Constructor of YesNoDataset. + /// \param[in] dataset_dir Path to the root directory that contains the dataset. + /// \param[in] sampler Raw pointer to a sampler object used to choose samples from the dataset. + /// \param[in] cache Tensor cache to use. + YesNoDataset(const std::vector &dataset_dir, const Sampler *sampler, + const std::shared_ptr &cache); + + /// \brief Constructor of YesNoDataset. + /// \param[in] dataset_dir Path to the root directory that contains the dataset. + /// \param[in] sampler Sampler object used to choose samples from the dataset. + /// \param[in] cache Tensor cache to use. + YesNoDataset(const std::vector &dataset_dir, const std::reference_wrapper sampler, + const std::shared_ptr &cache); + + /// Destructor of YesNoDataset. + ~YesNoDataset() = default; +}; + +/// \brief Function to create a YesNo Dataset. +/// \note The generated dataset has three columns ["waveform", "sample_rate", "label"]. +/// \param[in] dataset_dir Path to the root directory that contains the dataset. +/// \param[in] sampler Shared pointer to a sampler object used to choose samples from the dataset. If sampler is not +/// given, a `RandomSampler` will be used to randomly iterate the entire dataset (default = RandomSampler()). +/// \param[in] cache Tensor cache to use (default=nullptr, which means no cache is used). +/// \return Shared pointer to the current Dataset. +inline std::shared_ptr YesNo(const std::string &dataset_dir, + const std::shared_ptr &sampler = std::make_shared(), + const std::shared_ptr &cache = nullptr) { + return std::make_shared(StringToChar(dataset_dir), sampler, cache); +} + +/// \brief Function to create a YesNo Dataset. +/// \note The generated dataset has three columns ["waveform", "sample_rate", "label"]. +/// \param[in] dataset_dir Path to the root directory that contains the dataset. +/// \param[in] sampler Raw pointer to a sampler object used to choose samples from the dataset. +/// \param[in] cache Tensor cache to use (default=nullptr, which means no cache is used). +/// \return Shared pointer to the current Dataset. +inline std::shared_ptr YesNo(const std::string &dataset_dir, Sampler *sampler, + const std::shared_ptr &cache = nullptr) { + return std::make_shared(StringToChar(dataset_dir), sampler, cache); +} + +/// \brief Function to create a YesNo Dataset. +/// \note The generated dataset has three columns ["waveform", "sample_rate", "label"]. +/// \param[in] dataset_dir Path to the root directory that contains the dataset. +/// \param[in] sampler Sampler object used to choose samples from the dataset. +/// \param[in] cache Tensor cache to use (default=nullptr, which means no cache is used). +/// \return Shared pointer to the current Dataset. +inline std::shared_ptr YesNo(const std::string &dataset_dir, + const std::reference_wrapper sampler, + const std::shared_ptr &cache = nullptr) { + return std::make_shared(StringToChar(dataset_dir), sampler, cache); +} + /// \brief Function to create a cache to be attached to a dataset. /// \note The reason for providing this API is that std::string will be constrained by the /// compiler option '_GLIBCXX_USE_CXX11_ABI' while char is free of this restriction. diff --git a/mindspore/ccsrc/minddata/dataset/include/dataset/samplers.h b/mindspore/ccsrc/minddata/dataset/include/dataset/samplers.h index 1a6e6779273..9a13e30375b 100644 --- a/mindspore/ccsrc/minddata/dataset/include/dataset/samplers.h +++ b/mindspore/ccsrc/minddata/dataset/include/dataset/samplers.h @@ -57,6 +57,7 @@ class Sampler : std::enable_shared_from_this { friend class TFRecordDataset; friend class USPSDataset; friend class VOCDataset; + friend class YesNoDataset; friend std::shared_ptr SelectSampler(int64_t, bool, int32_t, int32_t); public: diff --git a/mindspore/dataset/engine/datasets.py b/mindspore/dataset/engine/datasets.py index 8ea6f3d1743..d4060573681 100644 --- a/mindspore/dataset/engine/datasets.py +++ b/mindspore/dataset/engine/datasets.py @@ -68,7 +68,8 @@ from .validators import check_batch, check_shuffle, check_map, check_filter, che check_tuple_iterator, check_dict_iterator, check_schema, check_to_device_send, check_flickr_dataset, \ check_sb_dataset, check_flowers102dataset, check_cityscapes_dataset, check_usps_dataset, check_div2k_dataset, \ check_sbu_dataset, check_qmnist_dataset, check_emnist_dataset, check_fake_image_dataset, check_places365_dataset, \ - check_photo_tour_dataset, check_ag_news_dataset, check_dbpedia_dataset, check_lj_speech_dataset + check_photo_tour_dataset, check_ag_news_dataset, check_dbpedia_dataset, check_lj_speech_dataset, \ + check_yes_no_dataset from ..core.config import get_callback_timeout, _init_device_info, get_enable_shared_mem, get_num_parallel_workers, \ get_prefetch_size from ..core.datatypes import mstype_to_detype, mstypelist_to_detypelist @@ -8290,3 +8291,116 @@ class DIV2KDataset(MappableDataset): def parse(self, children=None): return cde.DIV2KNode(self.dataset_dir, self.usage, self.downgrade, self.scale, self.decode, self.sampler) + + +class YesNoDataset(MappableDataset): + """ + A source dataset for reading and parsing the YesNo dataset. + + The generated dataset has three columns :py:obj:`[waveform, sample_rate, labels]`. + The tensor of column :py:obj:`waveform` is a vector of the float32 type. + The tensor of column :py:obj:`sample_rate` is a scalar of the int32 type. + The tensor of column :py:obj:`labels` is a scalar of the int32 type. + + Args: + dataset_dir (str): Path to the root directory that contains the dataset. + num_samples (int, optional): The number of images to be included in the dataset + (default=None, will read all images). + num_parallel_workers (int, optional): Number of workers to read the data + (default=None, will use value set in the config). + shuffle (bool, optional): Whether or not to perform shuffle on the dataset + (default=None, expected order behavior shown in the table). + sampler (Sampler, optional): Object used to choose samples from the + dataset (default=None, expected order behavior shown in the table). + num_shards (int, optional): Number of shards that the dataset will be divided into (default=None). + When this argument is specified, `num_samples` reflects the maximum sample number of per shard. + shard_id (int, optional): The shard ID within `num_shards` (default=None). This argument can only + be specified when `num_shards` is also specified. + cache (DatasetCache, optional): Use tensor caching service to speed up dataset processing + (default=None, which means no cache is used). + + Raises: + RuntimeError: If dataset_dir does not contain data files. + RuntimeError: If num_parallel_workers exceeds the max thread numbers. + RuntimeError: If sampler and shuffle are specified at the same time. + RuntimeError: If sampler and sharding are specified at the same time. + RuntimeError: If num_shards is specified but shard_id is None. + RuntimeError: If shard_id is specified but num_shards is None. + ValueError: If shard_id is invalid (< 0 or >= num_shards). + + Note: + - This dataset can take in a `sampler`. `sampler` and `shuffle` are mutually exclusive. + The table below shows what input arguments are allowed and their expected behavior. + + .. list-table:: Expected Order Behavior of Using `sampler` and `shuffle` + :widths: 25 25 50 + :header-rows: 1 + + * - Parameter `sampler` + - Parameter `shuffle` + - Expected Order Behavior + * - None + - None + - random order + * - None + - True + - random order + * - None + - False + - sequential order + * - Sampler object + - None + - order defined by sampler + * - Sampler object + - True + - not allowed + * - Sampler object + - False + - not allowed + + Examples: + >>> yes_no_dataset_dir = "/path/to/yes_no_dataset_directory" + >>> + >>> # Read 3 samples from YesNo dataset + >>> dataset = ds.YesNoDataset(dataset_dir=yes_no_dataset_dir, num_samples=3) + >>> + >>> # Note: In YesNo dataset, each dictionary has keys "waveform", "sample_rate", "label" + + About YesNo dataset: + + Yesno is an audio dataset consisting of 60 recordings of one individual saying yes or no in Hebrew; each + recording is eight words long. It was created for the Kaldi audio project by an author who wishes to + remain anonymous. + + Here is the original YesNo dataset structure. + You can unzip the dataset files into this directory structure and read by MindSpore's API. + + .. code-block:: + + . + └── yes_no_dataset_dir + ├── 1_1_0_0_1_1_0_0.wav + ├── 1_0_0_0_1_1_0_0.wav + ├── 1_1_0_0_1_1_0_0.wav + └──.... + + Citation: + + .. code-block:: + + @NetworkResource{Kaldi_audio_project, + author = {anonymous}, + url = "http://wwww.openslr.org/1/" + } + """ + + @check_yes_no_dataset + def __init__(self, dataset_dir, num_samples=None, num_parallel_workers=None, shuffle=None, + sampler=None, num_shards=None, shard_id=None, cache=None): + super().__init__(num_parallel_workers=num_parallel_workers, sampler=sampler, num_samples=num_samples, + shuffle=shuffle, num_shards=num_shards, shard_id=shard_id, cache=cache) + + self.dataset_dir = dataset_dir + + def parse(self, children=None): + return cde.YesNoNode(self.dataset_dir, self.sampler) diff --git a/mindspore/dataset/engine/validators.py b/mindspore/dataset/engine/validators.py index 17d28795c84..da9a61b8e3c 100644 --- a/mindspore/dataset/engine/validators.py +++ b/mindspore/dataset/engine/validators.py @@ -1806,3 +1806,29 @@ def check_dbpedia_dataset(method): return method(self, *args, **kwargs) return new_method + + +def check_yes_no_dataset(method): + """A wrapper that wraps a parameter checker around the original Dataset(YesNoDataset).""" + + @wraps(method) + def new_method(self, *args, **kwargs): + _, param_dict = parse_user_args(method, *args, **kwargs) + + nreq_param_int = ['num_samples', 'num_parallel_workers', 'num_shards', 'shard_id'] + nreq_param_bool = ['shuffle'] + + dataset_dir = param_dict.get('dataset_dir') + check_dir(dataset_dir) + + validate_dataset_param_value(nreq_param_int, param_dict, int) + validate_dataset_param_value(nreq_param_bool, param_dict, bool) + + check_sampler_shuffle_shard_options(param_dict) + + cache = param_dict.get('cache') + check_cache_option(cache) + + return method(self, *args, **kwargs) + + return new_method diff --git a/tests/ut/cpp/dataset/CMakeLists.txt b/tests/ut/cpp/dataset/CMakeLists.txt index 65004fe0177..7692fcf00d8 100644 --- a/tests/ut/cpp/dataset/CMakeLists.txt +++ b/tests/ut/cpp/dataset/CMakeLists.txt @@ -43,6 +43,7 @@ SET(DE_UT_SRCS c_api_dataset_tfrecord_test.cc c_api_dataset_usps_test.cc c_api_dataset_voc_test.cc + c_api_dataset_yes_no_test.cc c_api_datasets_test.cc c_api_epoch_ctrl_test.cc c_api_pull_based_test.cc diff --git a/tests/ut/cpp/dataset/c_api_dataset_yes_no_test.cc b/tests/ut/cpp/dataset/c_api_dataset_yes_no_test.cc new file mode 100644 index 00000000000..666c34dc652 --- /dev/null +++ b/tests/ut/cpp/dataset/c_api_dataset_yes_no_test.cc @@ -0,0 +1,196 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "common/common.h" +#include "minddata/dataset/include/dataset/datasets.h" + +using namespace mindspore::dataset; +using mindspore::dataset::DataType; +using mindspore::dataset::Tensor; +using mindspore::dataset::TensorShape; + +class MindDataTestPipeline : public UT::DatasetOpTesting { + protected: +}; + +/// Feature: Test YesNo dataset. +/// Description: read data from a single file. +/// Expectation: the data is processed successfully. +TEST_F(MindDataTestPipeline, TestYesNoDataset) { + MS_LOG(INFO) << "Doing MindDataTestPipeline-TestYesNoDataset."; + // Create a YesNoDataset + std::string folder_path = datasets_root_path_ + "/testYesNoData/"; + std::shared_ptr ds = YesNo(folder_path, std::make_shared(false, 2)); + EXPECT_NE(ds, nullptr); + // Create an iterator over the result of the above dataset. + // This will trigger the creation of the Execution Tree and launch it. + std::shared_ptr iter = ds->CreateIterator(); + EXPECT_NE(iter, nullptr); + + // Iterate the dataset and get each row + std::unordered_map row; + ASSERT_OK(iter->GetNextRow(&row)); + MS_LOG(INFO) << "iter->GetNextRow(&row) OK"; + + EXPECT_NE(row.find("waveform"), row.end()); + EXPECT_NE(row.find("sample_rate"), row.end()); + EXPECT_NE(row.find("label"), row.end()); + + uint64_t i = 0; + while (row.size() != 0) { + i++; + auto waveform = row["waveform"]; + MS_LOG(INFO) << "Tensor waveform shape: " << waveform.Shape(); + ASSERT_OK(iter->GetNextRow(&row)); + } + + EXPECT_EQ(i, 2); + + // Manually terminate the pipeline + iter->Stop(); +} + +/// Feature: Test YesNo dataset. +/// Description: test YesNo dataset with pipeline. +/// Expectation: the data is processed successfully. +TEST_F(MindDataTestPipeline, YesNoDatasetWithPipeline) { + MS_LOG(INFO) << "Doing MindDataTestPipeline-YesNoDatasetWithPipeline."; + + std::string folder_path = datasets_root_path_ + "/testYesNoData/"; + std::shared_ptr ds1 = YesNo(folder_path, std::make_shared(false, 1)); + std::shared_ptr ds2 = YesNo(folder_path, std::make_shared(false, 2)); + EXPECT_NE(ds1, nullptr); + EXPECT_NE(ds2, nullptr); + + // Create two Repeat operation on ds + int32_t repeat_num = 1; + ds1 = ds1->Repeat(repeat_num); + EXPECT_NE(ds1, nullptr); + repeat_num = 2; + ds2 = ds2->Repeat(repeat_num); + EXPECT_NE(ds2, nullptr); + + // Create two Project operation on ds + std::vector column_project = {"waveform", "sample_rate", "label"}; + ds1 = ds1->Project(column_project); + EXPECT_NE(ds1, nullptr); + ds2 = ds2->Project(column_project); + EXPECT_NE(ds2, nullptr); + + // Create a Concat operation on the ds + ds1 = ds1->Concat({ds2}); + EXPECT_NE(ds1, nullptr); + + std::shared_ptr iter = ds1->CreateIterator(); + EXPECT_NE(iter, nullptr); + + // Iterate the dataset and get each row + std::unordered_map row; + ASSERT_OK(iter->GetNextRow(&row)); + + EXPECT_NE(row.find("waveform"), row.end()); + EXPECT_NE(row.find("sample_rate"), row.end()); + EXPECT_NE(row.find("label"), row.end()); + + uint64_t i = 0; + while (row.size() != 0) { + i++; + auto waveform = row["waveform"]; + MS_LOG(INFO) << "Tensor waveform shape: " << waveform.Shape(); + ASSERT_OK(iter->GetNextRow(&row)); + } + + EXPECT_EQ(i, 5); + iter->Stop(); +} + +/// Feature: Test YesNo dataset. +/// Description: get the size of YesNo dataset. +/// Expectation: the data is processed successfully. +TEST_F(MindDataTestPipeline, TestYesNoGetDatasetSize) { + MS_LOG(INFO) << "Doing MindDataTestPipeline-TestYesNoGetDatasetSize."; + + // Create a YesNo Dataset + std::string folder_path = datasets_root_path_ + "/testYesNoData/"; + std::shared_ptr ds = YesNo(folder_path); + EXPECT_NE(ds, nullptr); + + EXPECT_EQ(ds->GetDatasetSize(), 3); +} + +/// Feature: Test YesNo dataset. +/// Description: getter functions. +/// Expectation: the data is processed successfully. +TEST_F(MindDataTestPipeline, TestYesNoGetters) { + MS_LOG(INFO) << "Doing MindDataTestPipeline-TestYesNoMixGetter."; + // Create a YesNo Dataset + std::string folder_path = datasets_root_path_ + "/testYesNoData/"; + std::shared_ptr ds = YesNo(folder_path); + EXPECT_NE(ds, nullptr); + + EXPECT_EQ(ds->GetDatasetSize(), 3); + std::vector types = ToDETypes(ds->GetOutputTypes()); + std::vector shapes = ToTensorShapeVec(ds->GetOutputShapes()); + std::vector column_names = {"waveform", "sample_rate", "label"}; + EXPECT_EQ(types.size(), 3); + EXPECT_EQ(types[0].ToString(), "float32"); + EXPECT_EQ(types[1].ToString(), "int32"); + EXPECT_EQ(types[2].ToString(), "int32"); + EXPECT_EQ(shapes.size(), 3); + EXPECT_EQ(shapes[1].ToString(), "<>"); + EXPECT_EQ(shapes[2].ToString(), "<8>"); + EXPECT_EQ(ds->GetBatchSize(), 1); + EXPECT_EQ(ds->GetRepeatCount(), 1); + + EXPECT_EQ(ds->GetDatasetSize(), 3); + EXPECT_EQ(ToDETypes(ds->GetOutputTypes()), types); + EXPECT_EQ(ToTensorShapeVec(ds->GetOutputShapes()), shapes); + + EXPECT_EQ(ds->GetColumnNames(), column_names); +} + +/// Feature: Test YesNo dataset. +/// Description: DatasetFail tests. +/// Expectation: throw error messages when certain errors occur. +TEST_F(MindDataTestPipeline, TestYesNoDatasetFail) { + MS_LOG(INFO) << "Doing MindDataTestPipeline-TestYesNoDatasetFail."; + + // Create a YesNo Dataset + std::shared_ptr ds = YesNo("", std::make_shared(false, 1)); + EXPECT_NE(ds, nullptr); + + // Create an iterator over the result of the above dataset + std::shared_ptr iter = ds->CreateIterator(); + // Expect failure: Invalid YesNo directory + EXPECT_EQ(iter, nullptr); +} + +/// Feature: Test YesNo dataset. +/// Description: NullSamplerFail tests. +/// Expectation: Throw error messages when certain errors occur. +TEST_F(MindDataTestPipeline, TestYesNoDatasetWithNullSamplerFail) { + MS_LOG(INFO) << "Doing MindDataTestPipeline-TestYesNo10DatasetWithNullSamplerFail."; + + // Create a YesNo Dataset + std::string folder_path = datasets_root_path_ + "/testYesNoData/"; + std::shared_ptr ds = YesNo(folder_path, nullptr); + // Expect failure: Null Sampler + EXPECT_NE(ds, nullptr); + + // Create an iterator over the result of the above dataset + std::shared_ptr iter = ds->CreateIterator(); + // Expect failure: Null Sampler + EXPECT_EQ(iter, nullptr); +} diff --git a/tests/ut/data/dataset/testYesNoData/1_2_3_4_5_6_7_1.wav b/tests/ut/data/dataset/testYesNoData/1_2_3_4_5_6_7_1.wav new file mode 100644 index 0000000000000000000000000000000000000000..f6f27667dbd0e3070fbf6d56be763742ddafd7d9 GIT binary patch literal 32044 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 literal 0 HcmV?d00001 diff --git a/tests/ut/python/dataset/test_datasets_yes_no.py b/tests/ut/python/dataset/test_datasets_yes_no.py new file mode 100644 index 00000000000..dbdf0fd973e --- /dev/null +++ b/tests/ut/python/dataset/test_datasets_yes_no.py @@ -0,0 +1,185 @@ +# Copyright 2021 Huawei Technologies Co., Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +import numpy as np +import pytest + +import mindspore.dataset as ds +import mindspore.dataset.audio.transforms as audio +from mindspore import log as logger + +DATA_DIR = "../data/dataset/testYesNoData/" + + +def test_yes_no_basic(): + """ + Feature: YesNo Dataset + Description: Read all files + Expectation: Output the amount of file + """ + logger.info("Test YesNoDataset Op") + + data = ds.YesNoDataset(DATA_DIR) + num_iter = 0 + for _ in data.create_dict_iterator(num_epochs=1): + num_iter += 1 + assert num_iter == 3 + + +def test_yes_no_num_samples(): + """ + Feature: YesNo Dataset + Description: Test num_samples + Expectation: Get certain number of samples + """ + data = ds.YesNoDataset(DATA_DIR, num_samples=2) + num_iter = 0 + for _ in data.create_dict_iterator(num_epochs=1): + num_iter += 1 + assert num_iter == 2 + + +def test_yes_no_repeat(): + """ + Feature: YesNo Dataset + Description: Test repeat + Expectation: Output the amount of file + """ + data = ds.YesNoDataset(DATA_DIR, num_samples=2) + data = data.repeat(5) + num_iter = 0 + for _ in data.create_dict_iterator(num_epochs=1): + num_iter += 1 + assert num_iter == 10 + + +def test_yes_no_dataset_size(): + """ + Feature: YesNo Dataset + Description: Test dataset_size + Expectation: Output the size of dataset + """ + data = ds.YesNoDataset(DATA_DIR, shuffle=False) + assert data.get_dataset_size() == 3 + + +def test_yes_no_sequential_sampler(): + """ + Feature: YesNo Dataset + Description: Use SequentialSampler to sample data. + Expectation: The number of samplers returned by dict_iterator is equal to the requested number of samples. + """ + logger.info("Test YesNoDataset Op with SequentialSampler") + num_samples = 2 + sampler = ds.SequentialSampler(num_samples=num_samples) + data1 = ds.YesNoDataset(DATA_DIR, sampler=sampler) + data2 = ds.YesNoDataset(DATA_DIR, shuffle=False, num_samples=num_samples) + sample_rate_list1, sample_rate_list2 = [], [] + num_iter = 0 + for item1, item2 in zip(data1.create_dict_iterator(num_epochs=1), + data2.create_dict_iterator(num_epochs=1)): + sample_rate_list1.append(item1["sample_rate"]) + sample_rate_list2.append(item2["sample_rate"]) + num_iter += 1 + np.testing.assert_array_equal(sample_rate_list1, sample_rate_list2) + assert num_iter == num_samples + + +def test_yes_no_exception(): + """ + Feature: Error tests + Description: Throw error messages when certain errors occur + Expectation: Output error message + """ + logger.info("Test error cases for YesNoDataset") + error_msg_1 = "sampler and shuffle cannot be specified at the same time" + with pytest.raises(RuntimeError, match=error_msg_1): + ds.YesNoDataset(DATA_DIR, shuffle=False, sampler=ds.PKSampler(3)) + + error_msg_2 = "sampler and sharding cannot be specified at the same time" + with pytest.raises(RuntimeError, match=error_msg_2): + ds.YesNoDataset(DATA_DIR, sampler=ds.PKSampler(3), + num_shards=2, shard_id=0) + + error_msg_3 = "num_shards is specified and currently requires shard_id as well" + with pytest.raises(RuntimeError, match=error_msg_3): + ds.YesNoDataset(DATA_DIR, num_shards=10) + + error_msg_4 = "shard_id is specified but num_shards is not" + with pytest.raises(RuntimeError, match=error_msg_4): + ds.YesNoDataset(DATA_DIR, shard_id=0) + + error_msg_5 = "Input shard_id is not within the required interval" + with pytest.raises(ValueError, match=error_msg_5): + ds.YesNoDataset(DATA_DIR, num_shards=5, shard_id=-1) + with pytest.raises(ValueError, match=error_msg_5): + ds.YesNoDataset(DATA_DIR, num_shards=5, shard_id=5) + with pytest.raises(ValueError, match=error_msg_5): + ds.YesNoDataset(DATA_DIR, num_shards=2, shard_id=5) + + error_msg_6 = "num_parallel_workers exceeds" + with pytest.raises(ValueError, match=error_msg_6): + ds.YesNoDataset(DATA_DIR, shuffle=False, num_parallel_workers=0) + with pytest.raises(ValueError, match=error_msg_6): + ds.YesNoDataset(DATA_DIR, shuffle=False, num_parallel_workers=256) + with pytest.raises(ValueError, match=error_msg_6): + ds.YesNoDataset(DATA_DIR, shuffle=False, num_parallel_workers=-2) + + error_msg_7 = "Argument shard_id" + with pytest.raises(TypeError, match=error_msg_7): + ds.YesNoDataset(DATA_DIR, num_shards=2, shard_id="0") + + def exception_func(item): + raise Exception("Error occur!") + + error_msg_8 = "The corresponding data files" + with pytest.raises(RuntimeError, match=error_msg_8): + data = ds.YesNoDataset(DATA_DIR) + data = data.map(operations=exception_func, input_columns=[ + "waveform"], num_parallel_workers=1) + for _ in data.__iter__(): + pass + with pytest.raises(RuntimeError, match=error_msg_8): + data = ds.YesNoDataset(DATA_DIR) + data = data.map(operations=exception_func, input_columns=[ + "sample_rate"], num_parallel_workers=1) + for _ in data.__iter__(): + pass + + +def test_yes_no_pipeline(): + """ + Feature: Pipeline test + Description: Read a sample + Expectation: The amount of each function are equal + """ + # Original waveform + dataset = ds.YesNoDataset(DATA_DIR, num_samples=1) + band_biquad_op = audio.BandBiquad(8000, 200.0) + # Filtered waveform by bandbiquad + dataset = dataset.map(input_columns=["waveform"], operations=band_biquad_op, num_parallel_workers=2) + num_iter = 0 + for _ in dataset.create_dict_iterator(num_epochs=1, output_numpy=True): + num_iter += 1 + assert num_iter == 1 + + +if __name__ == '__main__': + test_yes_no_basic() + test_yes_no_num_samples() + test_yes_no_repeat() + test_yes_no_dataset_size() + test_yes_no_sequential_sampler() + test_yes_no_exception() + test_yes_no_pipeline()