add dtype validation for RandomCropWithBbox

This commit is contained in:
Xiao Tianci 2022-07-22 14:33:03 +08:00 committed by Xiao
parent 67c6d9733e
commit f180e1f9d2
8 changed files with 53 additions and 48 deletions

View File

@ -92,19 +92,22 @@ Tensor &Tensor::operator=(Tensor &&other) noexcept {
}
Status Tensor::CreateEmpty(const TensorShape &shape, const DataType &type, TensorPtr *out) {
CHECK_FAIL_RETURN_UNEXPECTED(shape.known(), "Invalid shape.");
CHECK_FAIL_RETURN_UNEXPECTED(type != DataType::DE_UNKNOWN, "Invalid data type.");
CHECK_FAIL_RETURN_UNEXPECTED(shape.known(), "Failed to create empty tensor, tensor shape is unknown.");
CHECK_FAIL_RETURN_UNEXPECTED(type != DataType::DE_UNKNOWN, "Failed to create empty tensor, data type is unknown.");
RETURN_UNEXPECTED_IF_NULL(out);
const TensorAlloc *alloc = GlobalContext::Instance()->tensor_allocator();
*out = std::allocate_shared<Tensor>(*alloc, shape, type);
CHECK_FAIL_RETURN_UNEXPECTED(out != nullptr, "Allocate memory failed.");
CHECK_FAIL_RETURN_UNEXPECTED(out != nullptr, "Failed to create empty tensor, allocate memory failed.");
// if it's a string tensor and it has no elements, Just initialize the shape and type.
if (!type.IsNumeric() && shape.NumOfElements() == 0) {
return Status::OK();
if (!type.IsNumeric()) {
if (shape.NumOfElements() == 0) {
return Status::OK();
} else {
RETURN_STATUS_UNEXPECTED(
"Failed to create empty tensor, number of elements should be 0 when data type is string.");
}
}
CHECK_FAIL_RETURN_UNEXPECTED(type.IsNumeric(), "Number of elements is not 0. The type should be numeric.");
int64_t byte_size = (*out)->SizeInBytes();
// Don't allocate if we have a tensor with no elements.
@ -197,7 +200,11 @@ Status Tensor::CreateFromNpString(py::array arr, std::shared_ptr<Tensor> *out) {
Status Tensor::CreateFromNpArray(const py::array &arr, std::shared_ptr<Tensor> *out) {
RETURN_UNEXPECTED_IF_NULL(out);
if (DataType::FromNpArray(arr) == DataType::DE_STRING) {
DataType type = DataType::FromNpArray(arr);
CHECK_FAIL_RETURN_UNEXPECTED(type != DataType::DE_UNKNOWN,
"Failed to create tensor from numpy array, data type is unknown.");
if (type == DataType::DE_STRING) {
return CreateFromNpString(arr, out);
}
@ -221,10 +228,10 @@ Status Tensor::CreateFromNpArray(const py::array &arr, std::shared_ptr<Tensor> *
unsigned char *data = static_cast<unsigned char *>(arr.request().ptr);
if (is_strided) {
RETURN_IF_NOT_OK(Tensor::CreateEmpty(TensorShape(shape), DataType::FromNpArray(arr), out));
RETURN_IF_NOT_OK(Tensor::CreateEmpty(TensorShape(shape), type, out));
RETURN_IF_NOT_OK(CopyStridedArray((*out)->data_, data, shape, strides, (*out)->type_.SizeInBytes()));
} else {
RETURN_IF_NOT_OK(Tensor::CreateFromMemory(TensorShape(shape), DataType::FromNpArray(arr), data, out));
RETURN_IF_NOT_OK(Tensor::CreateFromMemory(TensorShape(shape), type, data, out));
}
return Status::OK();
}

View File

@ -142,7 +142,17 @@ Status ValidateImage(const std::shared_ptr<Tensor> &image, const std::string &op
if (valid_rank.find(rank) == valid_rank.end()) {
std::string err_msg = op_name + ": the dimension of image tensor does not match the requirement of operator.";
err_msg += " Expecting tensor in dimension of " + NumberSetToString(valid_rank);
if (valid_rank == std::set<dsize_t>({kMinImageRank, kDefaultImageRank})) {
err_msg += ", in shape of <H, W> or <H, W, C>";
} else if (valid_rank == std::set<dsize_t>({kMinImageRank})) {
err_msg += ", in shape of <H, W>";
} else if (valid_rank == std::set<dsize_t>({kDefaultImageRank})) {
err_msg += ", in shape of <H, W, C>";
}
err_msg += ". But got dimension " + std::to_string(rank) + ".";
if (rank == 1) {
err_msg += " You may need to perform Decode first.";
}
RETURN_STATUS_UNEXPECTED(err_msg);
}
}
@ -164,8 +174,8 @@ Status ValidateImageDtype(const std::string &op_name, DataType dtype) {
uint8_t type = dtype.AsCVType();
if (type == kCVInvalidType) {
std::string type_name = "unknown";
if (type < DataType::NUM_OF_TYPES) {
type_name = std::string(DataType::kTypeInfo[type].name_);
if (dtype.value() < DataType::NUM_OF_TYPES) {
type_name = std::string(DataType::kTypeInfo[dtype.value()].name_);
}
std::string err_msg = op_name + ": Cannot convert [" + type_name + "] to OpenCV type." +
" Currently unsupported data type: [uint32, int64, uint64, string]";
@ -1532,6 +1542,8 @@ Status Pad(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output
const int32_t &pad_bottom, const int32_t &pad_left, const int32_t &pad_right, const BorderType &border_types,
uint8_t fill_r, uint8_t fill_g, uint8_t fill_b) {
try {
RETURN_IF_NOT_OK(ValidateImage(input, "Pad", {1, 2, 3, 4, 5, 6, 10, 11, 12}, {2, 3}, {1, 3}));
// input image
std::shared_ptr<CVTensor> input_cv = CVTensor::AsCVTensor(input);
@ -1539,16 +1551,6 @@ Status Pad(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output
RETURN_STATUS_UNEXPECTED("[Internal ERROR] Pad: load image failed.");
}
// validate rank and number channels
RETURN_IF_NOT_OK(ValidateImageRank("Pad", input_cv->Rank()));
if (input_cv->Rank() == kDefaultImageRank) {
if (input_cv->shape()[kChannelIndexHWC] != kDefaultImageChannel &&
input_cv->shape()[kChannelIndexHWC] != kMinImageChannel) {
RETURN_STATUS_UNEXPECTED("Pad: number of channels for input tensor can only be 1 or 3, got channel: " +
std::to_string(input_cv->shape()[kChannelIndexHWC]));
}
}
// get the border type in openCV
auto b_type = GetCVBorderType(border_types);
// output image

View File

@ -38,8 +38,7 @@ std::string SizeToString(const std::vector<T> &size) {
Status PadToSizeOp::Compute(const std::shared_ptr<Tensor> &input, std::shared_ptr<Tensor> *output) {
IO_CHECK(input, output);
RETURN_IF_NOT_OK(ValidateImageDtype("PadToSize", input->type()));
RETURN_IF_NOT_OK(ValidateImageRank("PadToSize", input->Rank()));
RETURN_IF_NOT_OK(ValidateImage(input, "PadToSize", {1, 2, 3, 4, 5, 6, 10, 11, 12}, {2, 3}, {1, 3}));
std::vector<dsize_t> image_size;
RETURN_IF_NOT_OK(ImageSize(input, &image_size));
CHECK_FAIL_RETURN_SYNTAX_ERROR(

View File

@ -62,7 +62,7 @@ Status RandomCropOp::ImagePadding(const std::shared_ptr<Tensor> &input, std::sha
CHECK_FAIL_RETURN_UNEXPECTED(
pad_top_ < input->shape()[0] * max_ratio && pad_bottom_ < input->shape()[0] * max_ratio &&
pad_left_ < input->shape()[1] * max_ratio && pad_right_ < input->shape()[1] * max_ratio,
"Pad: padding size is three times bigger than the image size, padding top: " + std::to_string(pad_top_) +
"RandomCrop: padding size is three times bigger than the image size, padding top: " + std::to_string(pad_top_) +
", padding bottom: " + std::to_string(pad_bottom_) + ", padding pad_left_: " + std::to_string(pad_left_) +
", padding padding right:" + std::to_string(pad_right_) + ", image shape: " + std::to_string(input->shape()[0]) +
", " + std::to_string(input->shape()[1]));
@ -125,12 +125,12 @@ Status RandomCropOp::Compute(const TensorRow &input, TensorRow *output) {
for (size_t i = 0; i < input.size() - 1; i++) {
if (input[i]->Rank() != 2 && input[i]->Rank() != 3) {
std::string err_msg =
"RandomCropOp: image shape is not <H,W,C> or <H, W>, but got rank:" + std::to_string(input[i]->Rank());
"RandomCrop: image shape is not <H,W,C> or <H, W>, but got rank:" + std::to_string(input[i]->Rank());
RETURN_STATUS_UNEXPECTED(err_msg);
}
if (input[i]->shape()[0] != input[i + 1]->shape()[0] || input[i]->shape()[1] != input[i + 1]->shape()[1]) {
RETURN_STATUS_UNEXPECTED(
"RandomCropOp: Input images in different column must have the same shape, check the output shape in "
"RandomCrop: Input images in different column must have the same shape, check the output shape in "
"specified 'input_columns' before call this operation.");
}
}

View File

@ -26,6 +26,16 @@
namespace mindspore {
namespace dataset {
Status ConvertNumpyToTensor(const py::object &py_obj, TensorRow *output) {
std::shared_ptr<Tensor> out;
// Python object like bool, int, float, list or tuple can also be converted
// to a NumPy array by the following cast, but the data type will be unknown
// if it is not a valid NumPy object
RETURN_IF_NOT_OK(Tensor::CreateFromNpArray(py_obj.cast<py::array>(), &out));
output->push_back(out);
return Status::OK();
}
Status PyFuncOp::Compute(const TensorRow &input, TensorRow *output) {
IO_CHECK_VECTOR(input, output);
Status ret = Status(StatusCode::kSuccess, "PyFunc Call Succeed");
@ -57,7 +67,7 @@ Status PyFuncOp::Compute(const TensorRow &input, TensorRow *output) {
} else {
if (py::isinstance<py::tuple>(ret_py_obj)) {
// In case of a n-m mapping, the return value will be a tuple of numpy arrays
py::tuple ret_py_tuple = ret_py_obj.cast<py::tuple>();
auto ret_py_tuple = ret_py_obj.cast<py::tuple>();
// Iterate over two containers simultaneously for memory copy
for (size_t i = 0; i < ret_py_tuple.size(); i++) {
py::object ret_py_ele = ret_py_tuple[i];
@ -67,16 +77,11 @@ Status PyFuncOp::Compute(const TensorRow &input, TensorRow *output) {
"True, PyFunc may execute time out.";
goto TimeoutError;
}
std::shared_ptr<Tensor> out;
RETURN_IF_NOT_OK(Tensor::CreateFromNpArray(ret_py_ele.cast<py::array>(), &out));
output->push_back(out);
RETURN_IF_NOT_OK(ConvertNumpyToTensor(ret_py_ele, output));
}
} else {
// In case of a n-1 mapping, the return value will be a numpy array
std::shared_ptr<Tensor> out;
RETURN_IF_NOT_OK(Tensor::CreateFromNpArray(ret_py_obj.cast<py::array>(), &out));
output->push_back(out);
RETURN_IF_NOT_OK(ConvertNumpyToTensor(ret_py_obj, output));
}
}
} catch (const py::error_already_set &e) {

View File

@ -2746,14 +2746,6 @@ class _PythonCallable:
if result is None:
# Invoke original Python callable in master process in case the pool is gone.
result = self.py_callable(*args)
if isinstance(result, tuple):
result_tmp = []
for r in result:
r = np.array(r) if not isinstance(r, np.ndarray) else r
result_tmp.append(r)
result = tuple(result_tmp)
else:
result = np.array(result) if not isinstance(result, np.ndarray) else result
return result
def to_json(self):
@ -2828,7 +2820,6 @@ def _worker_loop(operations, pipe):
"""
signal.signal(signal.SIGINT, signal.SIG_IGN)
while not _main_process_already_exit():
_ignore_sigint()

View File

@ -174,11 +174,12 @@ def test_pad_to_size_check():
test_invalid_input(RuntimeError, "target size to pad should be no less than the original image size", size=(5, 5))
test_invalid_input(RuntimeError, "sum of offset and original image size should be no more than the target size",
(30, 30), (5, 5))
test_invalid_input(RuntimeError, "number of channels for input tensor can only be 1 or 3",
test_invalid_input(RuntimeError, "Expecting tensor in channel of (1, 3)",
data=np.random.random((28, 28, 4)))
test_invalid_input(RuntimeError, "input tensor is not in shape of <H,W> or <H,W,C>",
test_invalid_input(RuntimeError, "Expecting tensor in dimension of (2, 3)",
data=np.random.random(28))
test_invalid_input(RuntimeError, "Currently unsupported data type: [uint32, int64, uint64, string]",
test_invalid_input(RuntimeError, "Expecting tensor in type of "
"(bool, int8, uint8, int16, uint16, int32, float16, float32, float64)",
data=np.random.random((28, 28, 3)).astype(np.str))

View File

@ -546,7 +546,7 @@ def test_random_crop_09():
with pytest.raises(RuntimeError) as error_info:
for _ in data.create_dict_iterator(num_epochs=1, output_numpy=True):
pass
error_msg = "number of channels for input tensor can only be 1 or 3"
error_msg = "Expecting tensor in channel of (1, 3)"
assert error_msg in str(error_info.value)