1# Copyright 2019-2022 Huawei Technologies Co., Ltd 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================== 15""" 16The module transforms provides common operations, including Compose, OneHot and TypeCast. 17""" 18import json 19from abc import ABC 20import os 21import threading 22 23import sys 24from enum import IntEnum 25import numpy as np 26 27import mindspore._c_dataengine as cde 28from mindspore._c_expression import typing 29from mindspore.common import dtype as mstype 30import mindspore.dataset.transforms.c_transforms as c_transforms 31import mindspore.dataset.transforms.py_transforms as py_transforms 32import mindspore.dataset.vision.c_transforms as c_vision 33from . import py_transforms_util as util 34from .py_transforms_util import Implementation, FuncWrapper 35from .validators import check_fill_value, check_slice_option, check_slice_op, check_one_hot_op, check_compose_call, \ 36 check_mask_op_new, check_pad_end, check_concat_type, check_random_transform_ops, check_plugin, check_type_cast 37from ..core.datatypes import mstype_to_detype, nptype_to_detype 38from ..vision.py_transforms_util import is_pil 39 40 41# hold all the executor objects when in training procedure 42# key : pid_tid which distinguishes multiple executors by process_id + thread_id 43# value : executor object which lifecycle will always exist during training 44EXECUTORS_LIST = dict() 45 46 47# the follow case process / thread exit need call the function 48# 1. user defined dataset with process mode 49# 2. user defined dataset with thread mode 50# 3. user defined transform in map op with process mode 51# 4. user defined transform in map op with thread mode 52# 5. batch op with per_batch_map operation in process mode 53# 6. batch op with per_batch_map operation in thread mode 54def clean_unused_executors(): 55 """ 56 clean the unused executor object in UDF or map with PyFunc process / thread mode 57 """ 58 key = str(os.getpid()) + "_" + str(threading.currentThread().ident) 59 if key in EXECUTORS_LIST: 60 EXECUTORS_LIST.pop(key) 61 62 63class TensorOperation: 64 """ 65 Base class Tensor Ops 66 """ 67 68 def __init__(self): 69 super().__init__() 70 self.implementation = None 71 self.device_target = "CPU" 72 73 def __call__(self, *input_tensor_list): 74 """ 75 Call method. 76 """ 77 # Check PIL Image with device_target 78 if (len(input_tensor_list) == 1 and is_pil(input_tensor_list[0])) and self.device_target == "Ascend": 79 raise TypeError("The input PIL Image cannot be executed on Ascend, " 80 "you can convert the input to the numpy ndarray type.") 81 82 # Check if Python implementation of op, or PIL input 83 if (self.implementation == Implementation.PY) or \ 84 (len(input_tensor_list) == 1 and is_pil(input_tensor_list[0]) and getattr(self, '_execute_py', None)): 85 return self._execute_py(*input_tensor_list) 86 87 tensor_row = [] 88 for tensor in input_tensor_list: 89 try: 90 tensor_row.append(cde.Tensor(np.asarray(tensor))) 91 except (RuntimeError, TypeError): 92 raise TypeError("Invalid user input. Got {}: {}, cannot be converted into tensor." \ 93 .format(type(tensor), tensor)) 94 95 # get or create the executor from EXECUTORS_LIST 96 executor = None 97 key = str(os.getpid()) + "_" + str(threading.currentThread().ident) 98 if key in EXECUTORS_LIST: 99 # get the executor by process id and thread id 100 executor = EXECUTORS_LIST[key] 101 # remove the old transform which in executor and update the new transform 102 executor.UpdateOperation(self.parse()) 103 else: 104 # create a new executor by process id and thread_id 105 executor = cde.Execute(self.parse()) 106 # add the executor the global EXECUTORS_LIST 107 EXECUTORS_LIST[key] = executor 108 109 output_tensor_list = executor(tensor_row) 110 output_numpy_list = [x.as_array() for x in output_tensor_list] 111 return output_numpy_list[0] if len(output_numpy_list) == 1 else tuple(output_numpy_list) 112 113 @staticmethod 114 def parse(): 115 """parse function - not yet implemented""" 116 raise NotImplementedError("TensorOperation has to implement parse() method.") 117 118 119class PyTensorOperation: 120 """ 121 Base Python Tensor Operations class 122 """ 123 124 def __init__(self): 125 self.transforms = [] 126 self.output_type = None 127 128 def __call__(self, img): 129 """ 130 Call method. 131 132 Args: 133 img (PIL Image): Image to be augmented. 134 135 Returns: 136 PIL Image, augmented image. 137 """ 138 return self._execute_py(img) 139 140 @classmethod 141 def from_json(cls, json_string): 142 """ 143 Base from_json for Python tensor operations class 144 """ 145 json_obj = json.loads(json_string) 146 new_op = cls.__new__(cls) 147 new_op.__dict__ = json_obj 148 if "transforms" in json_obj.keys(): 149 # operations which have transforms as input, need to call _from_json() for each transform to deseriallize 150 transforms = [] 151 for json_op in json_obj["transforms"]: 152 transforms.append(getattr( 153 sys.modules.get(json_op.get("python_module")), 154 json_op["tensor_op_name"]).from_json(json.dumps(json_op["tensor_op_params"]))) 155 new_op.transforms = transforms 156 if "output_type" in json_obj.keys(): 157 output_type = np.dtype(json_obj["output_type"]) 158 new_op.output_type = output_type 159 return new_op 160 161 def to_json(self): 162 """ 163 Base to_json for Python tensor operations class 164 """ 165 json_obj = {} 166 json_trans = {} 167 if "transforms" in self.__dict__.keys(): 168 # operations which have transforms as input, need to call _to_json() for each transform to serialize 169 json_list = [] 170 for transform in self.transforms: 171 json_list.append(json.loads(transform.to_json())) 172 json_trans["transforms"] = json_list 173 self.__dict__.pop("transforms") 174 if "output_type" in self.__dict__.keys(): 175 json_trans["output_type"] = np.dtype( 176 self.__dict__["output_type"]).name 177 self.__dict__.pop("output_type") 178 json_obj["tensor_op_params"] = self.__dict__ 179 # append transforms to the tensor_op_params of the operation 180 json_obj.get("tensor_op_params").update(json_trans) 181 json_obj["tensor_op_name"] = self.__class__.__name__ 182 json_obj["python_module"] = self.__class__.__module__ 183 return json.dumps(json_obj) 184 185 186class CompoundOperation(TensorOperation, PyTensorOperation, ABC): 187 """ 188 Compound Tensor Operations class 189 """ 190 191 def __init__(self, transforms): 192 super(CompoundOperation, self).__init__() 193 self.transforms = [] 194 trans_with_imple = [] 195 for op in transforms: 196 if callable(op) and not hasattr(op, "implementation") and \ 197 not isinstance(op, c_transforms.TensorOperation) and \ 198 not isinstance(op, py_transforms.PyTensorOperation) and \ 199 not isinstance(op, c_vision.ImageTensorOperation): 200 op = util.FuncWrapper(op) 201 if hasattr(op, "implementation"): 202 if op.implementation is not None: 203 trans_with_imple.append(op) 204 else: 205 raise RuntimeError("Mixing old legacy c/py_transforms and new unified transforms is not allowed.") 206 self.transforms.append(op) 207 208 if all([t.implementation == Implementation.PY for t in self.transforms]): 209 self.implementation = Implementation.PY 210 elif all([t.implementation is not None for t in self.transforms]): 211 self.implementation = Implementation.C 212 elif not trans_with_imple: 213 self.implementation = None 214 elif all([t.implementation == Implementation.PY for t in trans_with_imple]): 215 self.implementation = Implementation.PY 216 elif all([t.implementation == Implementation.C for t in trans_with_imple]): 217 self.implementation = Implementation.C 218 219 @staticmethod 220 def parse(): 221 """parse function - not yet implemented""" 222 raise NotImplementedError("CompoundOperation has to implement parse() method.") 223 224 def parse_transforms(self): 225 operations = [] 226 for op in self.transforms: 227 if op and getattr(op, 'parse', None): 228 operations.append(op.parse()) 229 else: 230 operations.append(op) 231 return operations 232 233 234def not_random(function): 235 """ 236 Specify the function as "not random", i.e., it produces deterministic result. 237 A Python function can only be cached after it is specified as "not random". 238 """ 239 function.random = False 240 return function 241 242 243class Compose(CompoundOperation): 244 """ 245 Compose a list of transforms into a single transform. 246 247 .. Note:: 248 Compose takes a list of transformations in `mindspore.dataset.transforms` / `mindspore.dataset.vision` 249 and user-defined Python callable objects to combine as single data augmentation. 250 For user-defined Python callable objects, the return value is required to be type numpy.ndarray. 251 252 Args: 253 transforms (list): List of transformations to be applied. 254 255 Raises: 256 TypeError: If `transforms` is not of type list. 257 ValueError: If `transforms` is empty. 258 TypeError: If elements of `transforms` are neither Python callable objects nor data 259 processing operations in transforms.py. 260 261 Supported Platforms: 262 ``CPU`` 263 264 Examples: 265 >>> import numpy as np 266 >>> import mindspore.dataset as ds 267 >>> import mindspore.dataset.transforms as transforms 268 >>> import mindspore.dataset.vision as vision 269 >>> from mindspore.dataset.transforms import Relational 270 >>> 271 >>> # Use the transform in dataset pipeline mode 272 >>> # create a dataset that reads all files in dataset_dir with 8 threads 273 >>> data = np.random.randint(0, 255, size=(1, 100, 100, 3)).astype(np.uint8) 274 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["image"]) 275 >>> 276 >>> # create a list of transformations to be applied to the image data 277 >>> transform = transforms.Compose([ 278 ... vision.RandomHorizontalFlip(0.5), 279 ... vision.ToTensor(), 280 ... vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262), is_hwc=False), 281 ... vision.RandomErasing()]) 282 >>> # apply the transform to the dataset through dataset.map function 283 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transform, input_columns=["image"]) 284 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 285 ... print(item["image"].shape, item["image"].dtype) 286 ... break 287 (3, 100, 100) float32 288 >>> 289 >>> # Compose is also be invoked implicitly, by just passing in a list of ops 290 >>> # the above example then becomes: 291 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["image"]) 292 >>> transforms_list = [vision.RandomHorizontalFlip(0.5), 293 ... vision.ToTensor(), 294 ... vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262), is_hwc=False), 295 ... vision.RandomErasing()] 296 >>> 297 >>> # apply the transform to the dataset through dataset.map() 298 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms_list, input_columns=["image"]) 299 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 300 ... print(item["image"].shape, item["image"].dtype) 301 ... break 302 (3, 100, 100) float32 303 >>> 304 >>> # Certain C++ and Python ops can be combined, but not all of them 305 >>> # An example of combined operations 306 >>> arr = [0, 1] 307 >>> numpy_slices_dataset = ds.NumpySlicesDataset(arr, column_names=["cols"], shuffle=False) 308 >>> transformed_list = [transforms.OneHot(2), 309 ... transforms.Mask(transforms.Relational.EQ, 1)] 310 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transformed_list, input_columns=["cols"]) 311 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 312 ... print(item["cols"].shape, item["cols"].dtype) 313 ... break 314 (2,) bool 315 >>> 316 >>> # Here is an example of mixing vision ops 317 >>> op_list=[vision.Resize((224, 244)), 318 ... vision.ToPIL(), 319 ... np.array, # need to convert PIL image to a NumPy array to pass it to C++ operation 320 ... vision.Resize((24, 24))] 321 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["image"]) 322 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=op_list, input_columns=["image"]) 323 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 324 ... print(item["image"].shape, item["image"].dtype) 325 ... break 326 (24, 24, 3) uint8 327 >>> 328 >>> # Use the transform in eager mode 329 >>> data = np.array([1, 2, 3]) 330 >>> output = transforms.Compose([transforms.Fill(10), transforms.Mask(Relational.EQ, 100)])(data) 331 >>> print(output.shape, output.dtype) 332 (3,) bool 333 """ 334 335 @check_random_transform_ops 336 def __init__(self, transforms): 337 super().__init__(transforms) 338 self.transforms = Compose.decompose(self.transforms) 339 if all(hasattr(transform, "random") and not transform.random for transform in self.transforms): 340 self.random = False 341 342 # pylint: disable=missing-docstring 343 @staticmethod 344 def decompose(operations): 345 # Remove all compose operation from the given list of operations. 346 # 347 # Args: 348 # operations (list): list of transforms. 349 # 350 # Returns: 351 # list of operations without compose operations. 352 new_operations = [] 353 for op in operations: 354 if isinstance(op, Compose): 355 new_operations.extend(Compose.decompose(op.transforms)) 356 else: 357 new_operations.append(op) 358 return new_operations 359 360 # pylint: disable=missing-docstring 361 @staticmethod 362 def reduce(operations): 363 # Wraps adjacent Python operations in a Compose to allow mixing of Python and C++ operations. 364 # 365 # Args: 366 # operations (list): list of tensor operations. 367 # 368 # Returns: 369 # list, the reduced list of operations. 370 new_ops, start_ind, end_ind = [], 0, 0 371 for i, op in enumerate(operations): 372 if op.implementation == Implementation.C and not isinstance(op, FuncWrapper): 373 # reset counts 374 if start_ind != end_ind: 375 if end_ind == start_ind + 1: 376 composed_op = operations[start_ind] 377 else: 378 composed_op = Compose(operations[start_ind:end_ind]) 379 composed_op.implementation = Implementation.PY 380 new_ops.append(composed_op) 381 new_ops.append(op) 382 start_ind, end_ind = i + 1, i + 1 383 else: 384 end_ind += 1 385 # do additional check in case the last operation is a Python operation 386 if start_ind != end_ind: 387 if end_ind == start_ind + 1: 388 composed_op = operations[start_ind] 389 else: 390 composed_op = Compose(operations[start_ind:end_ind]) 391 composed_op.implementation = Implementation.PY 392 new_ops.append(composed_op) 393 return new_ops 394 395 def parse(self): 396 operations = self.parse_transforms() 397 return cde.ComposeOperation(operations) 398 399 @check_compose_call 400 def _execute_py(self, *args): 401 """ 402 Execute method. 403 404 Returns: 405 lambda function, Lambda function that takes in an args to apply transformations on. 406 """ 407 return util.compose(self.transforms, *args) 408 409 def __call__(self, *args): 410 ''' 411 If PY op exists in self.transforms, should use _execute_py to keep the output types unchanged. 412 ''' 413 if any([t.implementation == Implementation.PY for t in self.transforms]): 414 self.implementation = Implementation.PY 415 return super().__call__(*args) 416 417 def release_resource(self): 418 # release the executor which is used by current thread/process when 419 # use transform in eager mode in map op 420 # this will be call in MapOp::WorkerEntry 421 clean_unused_executors() 422 423 424class Concatenate(TensorOperation): 425 """ 426 Concatenate data with input array along given axis, only 1D data is supported. 427 428 Args: 429 axis (int, optional): The axis along which the arrays will be concatenated. Default: ``0``. 430 prepend (numpy.ndarray, optional): NumPy array to be prepended to the input array. 431 Default: ``None``, not to prepend array. 432 append (numpy.ndarray, optional): NumPy array to be appended to the input array. 433 Default: ``None``, not to append array. 434 435 Raises: 436 TypeError: If `axis` is not of type int. 437 TypeError: If `prepend` is not of type numpy.ndarray. 438 TypeError: If `append` is not of type numpy.ndarray. 439 440 Supported Platforms: 441 ``CPU`` 442 443 Examples: 444 >>> import numpy as np 445 >>> import mindspore.dataset as ds 446 >>> import mindspore.dataset.transforms as transforms 447 >>> 448 >>> # Use the transform in dataset pipeline mode 449 >>> # concatenate string 450 >>> prepend_tensor = np.array(["dw", "df"]) 451 >>> append_tensor = np.array(["dwsdf", "df"]) 452 >>> concatenate_op = transforms.Concatenate(0, prepend_tensor, append_tensor) 453 >>> data = [["This","is","a","string"]] 454 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data) 455 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=concatenate_op) 456 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 457 ... print(item["column_0"].shape, item["column_0"].dtype) 458 (8,) <U6 459 >>> 460 >>> # Use the transform in eager mode 461 >>> data = np.array([1, 2, 3]) 462 >>> prepend_tensor = np.array([10, 20]) 463 >>> append_tensor = np.array([100]) 464 >>> output = transforms.Concatenate(0, prepend_tensor, append_tensor)(data) 465 >>> print(output.shape, output.dtype) 466 (6,) int64 467 """ 468 469 @check_concat_type 470 def __init__(self, axis=0, prepend=None, append=None): 471 super().__init__() 472 self.axis = axis 473 self.prepend = cde.Tensor(np.array(prepend)) if prepend is not None else prepend 474 self.append = cde.Tensor(np.array(append)) if append is not None else append 475 self.implementation = Implementation.C 476 477 def parse(self): 478 return cde.ConcatenateOperation(self.axis, self.prepend, self.append) 479 480 481class Duplicate(TensorOperation): 482 """ 483 Duplicate the input tensor to output, only support transform one column each time. 484 485 Raises: 486 RuntimeError: If given tensor has two columns. 487 488 Supported Platforms: 489 ``CPU`` 490 491 Examples: 492 >>> import numpy as np 493 >>> import mindspore.dataset as ds 494 >>> import mindspore.dataset.transforms as transforms 495 >>> 496 >>> # Use the transform in dataset pipeline mode 497 >>> # Data before 498 >>> # | x | 499 >>> # +---------+ 500 >>> # | [1,2,3] | 501 >>> # +---------+ 502 >>> data = [[1,2,3]] 503 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["x"]) 504 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms.Duplicate(), 505 ... input_columns=["x"], 506 ... output_columns=["x", "y"]) 507 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 508 ... print(item["x"].shape, item["x"].dtype) 509 ... print(item["y"].shape, item["y"].dtype) 510 (3,) int64 511 (3,) int64 512 >>> # Data after 513 >>> # | x | y | 514 >>> # +---------+---------+ 515 >>> # | [1,2,3] | [1,2,3] | 516 >>> # +---------+---------+ 517 >>> 518 >>> # Use the transform in eager mode 519 >>> data = [1, 2, 3] 520 >>> output = transforms.Duplicate()(data) 521 >>> print(np.array(output).shape, np.array(output).dtype) 522 (2, 3) int64 523 """ 524 525 def __init__(self): 526 super().__init__() 527 self.implementation = Implementation.C 528 529 def parse(self): 530 return cde.DuplicateOperation() 531 532 533class Fill(TensorOperation): 534 """ 535 Tensor operation to fill all elements in the tensor with the specified value. 536 The output tensor will have the same shape and type as the input tensor. 537 538 Args: 539 fill_value (Union[str, bytes, int, float, bool]): scalar value 540 to fill the tensor with. 541 542 Raises: 543 TypeError: If `fill_value` is not of type str, float, bool, int or bytes. 544 545 Supported Platforms: 546 ``CPU`` 547 548 549 Examples: 550 >>> import numpy as np 551 >>> import mindspore.dataset as ds 552 >>> import mindspore.dataset.transforms as transforms 553 >>> 554 >>> # Use the transform in dataset pipeline mode 555 >>> # generate a 1D integer numpy array from 0 to 4 556 >>> def generator_1d(): 557 ... for i in range(5): 558 ... yield (np.array([i]),) 559 >>> generator_dataset = ds.GeneratorDataset(generator_1d, column_names="col1") 560 >>> # [[0], [1], [2], [3], [4]] 561 >>> fill_op = transforms.Fill(3) 562 >>> generator_dataset = generator_dataset.map(operations=fill_op) 563 >>> for item in generator_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 564 ... print(item["col1"].shape, item["col1"].dtype) 565 ... break 566 (1,) int64 567 >>> 568 >>> # Use the transform in eager mode 569 >>> data = np.array([1, 2, 3]) 570 >>> output = transforms.Fill(100)(data) 571 >>> print(output.shape, output.dtype) 572 (3,) int64 573 """ 574 575 @check_fill_value 576 def __init__(self, fill_value): 577 super().__init__() 578 self.fill_value = cde.Tensor(np.array(fill_value)) 579 self.implementation = Implementation.C 580 581 def parse(self): 582 return cde.FillOperation(self.fill_value) 583 584 585class Mask(TensorOperation): 586 r""" 587 Mask content of the input tensor with the given predicate. 588 Any element of the tensor that matches the predicate will be evaluated to True, otherwise False. 589 590 Args: 591 operator (Relational): relational operators, it can be ``Relational.EQ``, ``Relational.NE``, ``Relational.LT``, 592 ``Relational.GT``, ``Relational.LE``, ``Relational.GE``, take ``Relational.EQ`` as example, 593 EQ refers to equal. 594 constant (Union[str, int, float, bool]): Constant to be compared to. 595 dtype (mindspore.dtype, optional): Type of the generated mask. Default: ``mstype.bool_``. 596 597 Raises: 598 TypeError: `operator` is not of type Relational. 599 TypeError: `constant` is not of type string int, float or bool. 600 TypeError: `dtype` is not of type mindspore.dtype. 601 602 Supported Platforms: 603 ``CPU`` 604 605 Examples: 606 >>> import mindspore.dataset as ds 607 >>> import mindspore.dataset.transforms as transforms 608 >>> from mindspore.dataset.transforms import Relational 609 >>> 610 >>> # Use the transform in dataset pipeline mode 611 >>> # Data before 612 >>> # | col | 613 >>> # +---------+ 614 >>> # | [1,2,3] | 615 >>> # +---------+ 616 >>> data = [[1, 2, 3]] 617 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["col"]) 618 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms.Mask(Relational.EQ, 2)) 619 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 620 ... print(item["col"].shape, item["col"].dtype) 621 (3,) bool 622 >>> # Data after 623 >>> # | col | 624 >>> # +--------------------+ 625 >>> # | [False,True,False] | 626 >>> # +--------------------+ 627 >>> 628 >>> # Use the transform in eager mode 629 >>> data = [1, 2, 3] 630 >>> output = transforms.Mask(Relational.EQ, 2)(data) 631 >>> print(output.shape, output.dtype) 632 (3,) bool 633 """ 634 635 @check_mask_op_new 636 def __init__(self, operator, constant, dtype=mstype.bool_): 637 super().__init__() 638 self.operator = operator 639 self.dtype = mstype_to_detype(dtype) 640 self.constant = cde.Tensor(np.array(constant)) 641 self.implementation = Implementation.C 642 643 def parse(self): 644 return cde.MaskOperation(DE_C_RELATIONAL.get(self.operator), self.constant, self.dtype) 645 646 647class OneHot(TensorOperation): 648 r""" 649 Apply One-Hot encoding to the input labels. 650 651 For a 1-D input of shape :math:`(*)`, an output of shape :math:`(*, num\_classes)` will be 652 returned, where the elements with index values equal to the input values will be set to 1, 653 and the rest will be set to 0. If a label smoothing rate is specified, the element values 654 are further smoothed to enhance generalization. 655 656 Args: 657 num_classes (int): Total number of classes. Must be greater than the maximum value 658 of the input labels. 659 smoothing_rate (float, optional): The amount of label smoothing. Must be between 660 [0.0, 1.0]. Default: ``0.0``, no label smoothing. 661 662 Raises: 663 TypeError: If `num_classes` is not of type int. 664 TypeError: If `smoothing_rate` is not of type float. 665 ValueError: If `smoothing_rate` is not in range of [0.0, 1.0]. 666 RuntimeError: If input label is not of type int. 667 RuntimeError: If the dimension of the input label is not 1. 668 669 Supported Platforms: 670 ``CPU`` 671 672 Examples: 673 >>> import numpy as np 674 >>> import mindspore.dataset as ds 675 >>> import mindspore.dataset.transforms as transforms 676 >>> 677 >>> # Use the transform in dataset pipeline mode 678 >>> data = [1, 2, 3, 4, 5, 6, 7, 8] 679 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["label"]) 680 >>> 681 >>> # Assume that dataset has 10 classes, thus the label ranges from 0 to 9 682 >>> onehot_op = transforms.OneHot(num_classes=10) 683 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=onehot_op, input_columns=["label"]) 684 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 685 ... print(item["label"].shape, item["label"].dtype) 686 ... break 687 (10,) int64 688 >>> 689 >>> # Use the transform in eager mode 690 >>> data = np.array([1, 2, 3]) 691 >>> output = transforms.OneHot(num_classes=5, smoothing_rate=0)(data) 692 >>> print(output.shape, output.dtype) 693 (3, 5) int64 694 """ 695 696 @check_one_hot_op 697 def __init__(self, num_classes, smoothing_rate=0.0): 698 super().__init__() 699 self.num_classes = num_classes 700 self.random = False 701 self.smoothing_rate = smoothing_rate 702 703 def parse(self): 704 return cde.OneHotOperation(self.num_classes, self.smoothing_rate) 705 706 707class PadEnd(TensorOperation): 708 """ 709 Pad input tensor according to pad_shape, input tensor needs to have same rank. 710 711 Args: 712 pad_shape (list(int)): List of integers representing the shape needed. Dimensions that set to ``None`` will 713 not be padded (i.e., original dim will be used). Shorter dimensions will truncate the values. 714 pad_value (Union[str, bytes, int, float, bool], optional): Value used to pad. Default: ``None``. 715 Default to ``0`` in case of tensors of Numbers, or empty string in case of tensors of strings. 716 717 Raises: 718 TypeError: If `pad_shape` is not of type list. 719 TypeError: If `pad_value` is not of type str, float, bool, int or bytes. 720 TypeError: If elements of `pad_shape` is not of type int. 721 ValueError: If elements of `pad_shape` is not of positive. 722 723 Supported Platforms: 724 ``CPU`` 725 726 Examples: 727 >>> import mindspore.dataset as ds 728 >>> import mindspore.dataset.transforms as transforms 729 >>> 730 >>> # Use the transform in dataset pipeline mode 731 >>> # Data before 732 >>> # | col | 733 >>> # +---------+ 734 >>> # | [1,2,3] | 735 >>> # +---------| 736 >>> data = [[1, 2, 3]] 737 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["col"]) 738 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms.PadEnd(pad_shape=[4], pad_value=10)) 739 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 740 ... print(item["col"].shape, item["col"].dtype) 741 ... break 742 (4,) int64 743 >>> # Data after 744 >>> # | col | 745 >>> # +------------+ 746 >>> # | [1,2,3,10] | 747 >>> # +------------| 748 >>> 749 >>> # Use the transform in eager mode 750 >>> data = [1, 2, 3] 751 >>> output = transforms.PadEnd(pad_shape=[4], pad_value=10)(data) 752 >>> print(output.shape, output.dtype) 753 (4,) int64 754 """ 755 756 @check_pad_end 757 def __init__(self, pad_shape, pad_value=None): 758 super().__init__() 759 self.pad_shape = cde.TensorShape(pad_shape) 760 self.pad_value = cde.Tensor(np.array(pad_value)) if pad_value is not None else pad_value 761 self.implementation = Implementation.C 762 763 def parse(self): 764 return cde.PadEndOperation(self.pad_shape, self.pad_value) 765 766 767class Plugin(TensorOperation): 768 """ 769 Plugin support for MindData. Use this class to dynamically load a .so file (shared library) and execute its symbols. 770 771 Args: 772 lib_path (str): Path to .so file which is compiled to support MindData plugin. 773 func_name (str): Name of the function to load from the .so file. 774 user_args (str, optional): Serialized args to pass to the plugin. Only needed if "func_name" requires one. 775 776 Raises: 777 TypeError: If `lib_path` is not of type string. 778 TypeError: If `func_name` is not of type string. 779 TypeError: If `user_args` is not of type string. 780 781 Supported Platforms: 782 ``CPU`` 783 784 Examples: 785 >>> import mindspore.dataset as ds 786 >>> import mindspore.dataset.transforms as transforms 787 >>> 788 >>> plugin = transforms.Plugin("pluginlib.so", "PluginDecode") 789 >>> image_folder_dataset = ds.ImageFolderDataset("/path/to/image_folder_dataset_directory") 790 >>> image_folder_dataset = image_folder_dataset.map(operations=plugin) 791 """ 792 793 @check_plugin 794 def __init__(self, lib_path, func_name, user_args=None): 795 super().__init__() 796 self.lib_path = lib_path 797 self.func_name = func_name 798 self.user_args = str() if (user_args is None) else user_args 799 self.implementation = Implementation.C 800 801 def parse(self): 802 return cde.PluginOperation(self.lib_path, self.func_name, self.user_args) 803 804 805class RandomApply(CompoundOperation): 806 """ 807 Randomly perform a series of transforms with a given probability. 808 809 Args: 810 transforms (list): List of transformations to be applied. 811 prob (float, optional): The probability to apply the transformation list. Default: ``0.5``. 812 813 Raises: 814 TypeError: If `transforms` is not of type list. 815 ValueError: If `transforms` is empty. 816 TypeError: If elements of `transforms` are neither Python callable objects nor data 817 processing operations in transforms.py. 818 TypeError: If `prob` is not of type float. 819 ValueError: If `prob` is not in range [0.0, 1.0]. 820 821 Supported Platforms: 822 ``CPU`` 823 824 Examples: 825 >>> import numpy as np 826 >>> import mindspore.dataset as ds 827 >>> import mindspore.dataset.transforms as transforms 828 >>> import mindspore.dataset.vision as vision 829 >>> from mindspore.dataset.transforms import Compose 830 >>> 831 >>> # Use the transform in dataset pipeline mode 832 >>> seed = ds.config.get_seed() 833 >>> ds.config.set_seed(12345) 834 >>> transforms_list = [vision.RandomHorizontalFlip(0.5), 835 ... vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)), 836 ... vision.RandomErasing()] 837 >>> composed_transform = Compose([transforms.RandomApply(transforms_list, prob=0.6), 838 ... vision.ToTensor()]) 839 >>> 840 >>> data = np.random.randint(0, 255, size=(1, 100, 100, 3)).astype(np.uint8) 841 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["image"]) 842 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=composed_transform, input_columns=["image"]) 843 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 844 ... print(item["image"].shape, item["image"].dtype) 845 ... break 846 (3, 100, 100) float32 847 >>> 848 >>> # Use the transform in eager mode 849 >>> data = np.random.randint(0, 255, size=(100, 100, 3)).astype(np.uint8) 850 >>> transform = [vision.HsvToRgb(is_hwc=True), vision.Crop((0, 0), 10), vision.ToTensor()] 851 >>> output = transforms.RandomApply(transform, prob=1.0)(data) 852 >>> print(output.shape, output.dtype) 853 (3, 10, 10) float32 854 >>> ds.config.set_seed(seed) 855 """ 856 857 @check_random_transform_ops 858 def __init__(self, transforms, prob=0.5): 859 super().__init__(transforms) 860 self.prob = prob 861 862 def parse(self): 863 operations = self.parse_transforms() 864 return cde.RandomApplyOperation(self.prob, operations) 865 866 def _execute_py(self, img): 867 """ 868 Execute method. 869 870 Args: 871 img (PIL image): Image to be randomly applied a list transformations. 872 873 Returns: 874 img (PIL image), Transformed image. 875 """ 876 return util.random_apply(img, self.transforms, self.prob) 877 878 879class RandomChoice(CompoundOperation): 880 """ 881 Randomly select one transform from a list to apply. 882 883 Args: 884 transforms (list): List of transforms to be selected from. 885 886 Raises: 887 TypeError: If `transforms` is not of type list. 888 ValueError: If `transforms` is empty. 889 TypeError: If elements of `transforms` are neither Python callable objects nor data 890 processing operations in transforms.py. 891 892 Supported Platforms: 893 ``CPU`` 894 895 Examples: 896 >>> import numpy as np 897 >>> import mindspore.dataset as ds 898 >>> import mindspore.dataset.transforms as transforms 899 >>> import mindspore.dataset.vision as vision 900 >>> from mindspore.dataset.transforms import Compose 901 >>> 902 >>> # Use the transform in dataset pipeline mode 903 >>> seed = ds.config.get_seed() 904 >>> ds.config.set_seed(12345) 905 >>> transforms_list = [vision.RandomHorizontalFlip(0.5), 906 ... vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)), 907 ... vision.RandomErasing()] 908 >>> composed_transform = Compose([transforms.RandomChoice(transforms_list), 909 ... vision.ToTensor()]) 910 >>> 911 >>> data = np.random.randint(0, 255, size=(1, 100, 100, 3)).astype(np.uint8) 912 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["image"]) 913 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=composed_transform, input_columns=["image"]) 914 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 915 ... print(item["image"].shape, item["image"].dtype) 916 ... break 917 (3, 100, 100) float32 918 >>> 919 >>> # Use the transform in eager mode 920 >>> data = np.array([1, 2, 3]) 921 >>> output = transforms.RandomChoice([transforms.Fill(100)])(data) 922 >>> print(output.shape, output.dtype) 923 (3,) int64 924 >>> ds.config.set_seed(seed) 925 """ 926 927 @check_random_transform_ops 928 def __init__(self, transforms): 929 super().__init__(transforms) 930 931 def parse(self): 932 operations = self.parse_transforms() 933 return cde.RandomChoiceOperation(operations) 934 935 def _execute_py(self, img): 936 """ 937 Execute method. 938 939 Args: 940 img (PIL image): Image to be applied transformation. 941 942 943 Returns: 944 img (PIL image), Transformed image. 945 """ 946 return util.random_choice(img, self.transforms) 947 948 949class RandomOrder(PyTensorOperation): 950 """ 951 Perform a series of transforms to the input image in a random order. 952 953 Args: 954 transforms (list): List of the transformations to apply. 955 956 Raises: 957 TypeError: If `transforms` is not of type list. 958 TypeError: If elements of `transforms` are neither Python callable objects nor data 959 processing operations in mindspore.dataset.transforms.transforms. 960 ValueError: If `transforms` is empty. 961 962 Supported Platforms: 963 ``CPU`` 964 965 Examples: 966 >>> import numpy as np 967 >>> import mindspore.dataset as ds 968 >>> import mindspore.dataset.transforms as transforms 969 >>> import mindspore.dataset.vision as vision 970 >>> from mindspore.dataset.transforms import Compose, Relational 971 >>> 972 >>> # Use the transform in dataset pipeline mode 973 >>> seed = ds.config.get_seed() 974 >>> ds.config.set_seed(12345) 975 >>> transforms_list = [vision.RandomHorizontalFlip(0.5), 976 ... vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)), 977 ... vision.RandomErasing()] 978 >>> composed_transform = Compose([transforms.RandomOrder(transforms_list), 979 ... vision.ToTensor()]) 980 >>> 981 >>> data = np.random.randint(0, 255, size=(1, 100, 100, 3)).astype(np.uint8) 982 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["image"]) 983 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=composed_transform, input_columns=["image"]) 984 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 985 ... print(item["image"].shape, item["image"].dtype) 986 ... break 987 (3, 100, 100) float32 988 >>> 989 >>> # Use the transform in eager mode 990 >>> data = np.array([1, 2, 3]) 991 >>> output = transforms.RandomOrder([transforms.Mask(Relational.EQ, 100)])(data) 992 >>> print(output.shape, output.dtype) 993 (3,) bool 994 >>> ds.config.set_seed(seed) 995 """ 996 997 @check_random_transform_ops 998 def __init__(self, transforms): 999 super().__init__() 1000 self.transforms = transforms 1001 self.implementation = Implementation.PY 1002 1003 def _execute_py(self, img): 1004 """ 1005 Execute method. 1006 1007 Args: 1008 img (PIL image): Image to apply transformations in a random order. 1009 1010 Returns: 1011 img (PIL image), Transformed image. 1012 """ 1013 return util.random_order(img, self.transforms) 1014 1015 1016class Relational(IntEnum): 1017 """ 1018 Relational operator. 1019 1020 Available values are as follows: 1021 1022 - ``Relational.EQ``: Equal to. 1023 - ``Relational.NE``: Not equal to. 1024 - ``Relational.GT``: Greater than. 1025 - ``Relational.GE``: Greater than or equal to. 1026 - ``Relational.LT``: Less than. 1027 - ``Relational.LE``: Less than or equal to. 1028 """ 1029 EQ = 0 1030 NE = 1 1031 GT = 2 1032 GE = 3 1033 LT = 4 1034 LE = 5 1035 1036 1037DE_C_RELATIONAL = {Relational.EQ: cde.RelationalOp.EQ, 1038 Relational.NE: cde.RelationalOp.NE, 1039 Relational.GT: cde.RelationalOp.GT, 1040 Relational.GE: cde.RelationalOp.GE, 1041 Relational.LT: cde.RelationalOp.LT, 1042 Relational.LE: cde.RelationalOp.LE} 1043 1044 1045class _SliceOption(cde.SliceOption): 1046 """ 1047 Internal class SliceOption to be used with SliceOperation 1048 1049 Args: 1050 _SliceOption(Union[int, list(int), slice, None, Ellipsis, bool, _SliceOption]): 1051 1052 1. :py:obj:`int`: Slice this index only along the dimension. Negative index is supported. 1053 2. :py:obj:`list(int)`: Slice these indices along the dimension. Negative indices are supported. 1054 3. :py:obj:`slice`: Slice the generated indices from the slice object along the dimension. 1055 4. :py:obj:`None`: Slice the whole dimension. Similar to :py:obj:`:` in Python indexing. 1056 5. :py:obj:`Ellipsis`: Slice the whole dimension. Similar to :py:obj:`:` in Python indexing. 1057 6. :py:obj:`boolean`: Slice the whole dimension. Similar to :py:obj:`:` in Python indexing. 1058 """ 1059 1060 @check_slice_option 1061 def __init__(self, slice_option): 1062 if isinstance(slice_option, int) and not isinstance(slice_option, bool): 1063 slice_option = [slice_option] 1064 elif slice_option is Ellipsis: 1065 slice_option = True 1066 elif slice_option is None: 1067 slice_option = True 1068 super().__init__(slice_option) 1069 1070 1071class Slice(TensorOperation): 1072 """ 1073 Extract a slice from the input. 1074 1075 Currently, only 1-D input is supported. 1076 1077 Args: 1078 slices (Union[int, list[int], slice, Ellipsis]): The desired slice. 1079 1080 - If the input type is int, it will slice the element with the specified index value. 1081 Negative index is also supported. 1082 - If the input type is list[int], it will slice all the elements with the specified index values. 1083 Negative index is also supported. 1084 - If the input type is `slice <https://docs.python.org/3.7/library/functions.html#slice>`_ , 1085 it will slice according to its specified start position, stop position and step size. 1086 - If the input type is `Ellipsis <https://docs.python.org/3.7/library/constants.html#Ellipsis>`_ , 1087 all elements will be sliced. 1088 - If the input is None, all elements will be sliced. 1089 1090 Raises: 1091 TypeError: If `slices` is not of type Union[int, list[int], slice, Ellipsis]. 1092 1093 Supported Platforms: 1094 ``CPU`` 1095 1096 Examples: 1097 >>> import numpy as np 1098 >>> import mindspore.dataset as ds 1099 >>> import mindspore.dataset.transforms as transforms 1100 >>> 1101 >>> # Use the transform in dataset pipeline mode 1102 >>> # Data before 1103 >>> # | col | 1104 >>> # +---------+ 1105 >>> # | [1,2,3] | 1106 >>> # +---------| 1107 >>> data = [[1, 2, 3]] 1108 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["col"]) 1109 >>> # slice indices 1 and 2 only 1110 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms.Slice(slice(1,3))) 1111 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 1112 ... print(item["col"].shape, item["col"].dtype) 1113 ... break 1114 (2,) int64 1115 >>> # Data after 1116 >>> # | col | 1117 >>> # +---------+ 1118 >>> # | [2,3] | 1119 >>> # +---------| 1120 >>> 1121 >>> # Use the transform in eager mode 1122 >>> data = np.array([1, 2, 3]) 1123 >>> output = transforms.Slice(slice(1, 3))(data) 1124 >>> print(output.shape, output.dtype) 1125 (2,) int64 1126 """ 1127 1128 @check_slice_op 1129 def __init__(self, *slices): 1130 super().__init__() 1131 slice_input_ = list(slices) 1132 slice_input_ = [_SliceOption(slice_dim) for slice_dim in slice_input_] 1133 self.slice_input_ = slice_input_ 1134 self.implementation = Implementation.C 1135 1136 def parse(self): 1137 return cde.SliceOperation(self.slice_input_) 1138 1139 1140class TypeCast(TensorOperation): 1141 """ 1142 Tensor operation to cast to a given MindSpore data type or NumPy data type. 1143 1144 Note: 1145 This operation is executed on the CPU by default, but it is also supported 1146 to be executed on the GPU or Ascend via heterogeneous acceleration. 1147 1148 Args: 1149 data_type (Union[mindspore.dtype, numpy.dtype]): mindspore.dtype or numpy.dtype (e.g. `numpy.float32`) 1150 to be cast to. 1151 1152 Raises: 1153 TypeError: If `data_type` is not of MindSpore data type bool, int, float, string or type :class:`numpy.dtype` . 1154 1155 Supported Platforms: 1156 ``CPU`` ``GPU`` ``Ascend`` 1157 1158 Examples: 1159 >>> import numpy as np 1160 >>> import mindspore.dataset as ds 1161 >>> import mindspore.dataset.transforms as transforms 1162 >>> from mindspore import dtype as mstype 1163 >>> 1164 >>> # Use the transform in dataset pipeline mode 1165 >>> # Generate 1d int numpy array from 0 - 63 1166 >>> def generator_1d(): 1167 ... for i in range(64): 1168 ... yield (np.array([i]),) 1169 >>> 1170 >>> generator_dataset = ds.GeneratorDataset(generator_1d, column_names='col') 1171 >>> type_cast_op = transforms.TypeCast(mstype.int32) 1172 >>> generator_dataset = generator_dataset.map(operations=type_cast_op) 1173 >>> for item in generator_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 1174 ... print(item["col"].shape, item["col"].dtype) 1175 ... break 1176 (1,) int32 1177 >>> 1178 >>> # Use the transform in eager mode 1179 >>> data = np.array([2.71606445312564e-03, 6.3476562564e-03]).astype(np.float64) 1180 >>> output = transforms.TypeCast(np.float16)(data) 1181 >>> print(output.shape, output.dtype) 1182 (2,) float16 1183 """ 1184 1185 @check_type_cast 1186 def __init__(self, data_type): 1187 super().__init__() 1188 if isinstance(data_type, typing.Type): 1189 data_type = mstype_to_detype(data_type) 1190 else: 1191 data_type = nptype_to_detype(data_type) 1192 self.data_type = str(data_type) 1193 self.implementation = Implementation.C 1194 1195 def parse(self): 1196 return cde.TypeCastOperation(self.data_type) 1197 1198 1199class Unique(TensorOperation): 1200 """ 1201 Perform the unique operation on the input tensor, only support transform one column each time. 1202 1203 Return 3 tensor: unique output tensor, index tensor, count tensor. 1204 1205 - Output tensor contains all the unique elements of the input tensor 1206 in the same order that they occur in the input tensor. 1207 - Index tensor that contains the index of each element of the input tensor in the unique output tensor. 1208 - Count tensor that contains the count of each element of the output tensor in the input tensor. 1209 1210 Note: 1211 Call batch op before calling this function. 1212 1213 Raises: 1214 RuntimeError: If given Tensor has two columns. 1215 1216 Supported Platforms: 1217 ``CPU`` 1218 1219 Examples: 1220 >>> import numpy as np 1221 >>> import mindspore.dataset as ds 1222 >>> import mindspore.dataset.transforms as transforms 1223 >>> 1224 >>> # Use the transform in dataset pipeline mode 1225 >>> # Data before 1226 >>> # | x | 1227 >>> # +--------------------+ 1228 >>> # | [[0,1,2], [1,2,3]] | 1229 >>> # +--------------------+ 1230 >>> data = [[[0,1,2], [1,2,3]]] 1231 >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["x"]) 1232 >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=transforms.Unique(), 1233 ... input_columns=["x"], 1234 ... output_columns=["x", "y", "z"]) 1235 >>> for item in numpy_slices_dataset.create_dict_iterator(num_epochs=1, output_numpy=True): 1236 ... print(item["x"].shape, item["y"].shape, item["z"].shape) 1237 ... print(item["x"].dtype, item["y"].dtype, item["z"].dtype) 1238 (4,) (6,) (4,) 1239 int64 int32 int32 1240 >>> # Data after 1241 >>> # | x | y |z | 1242 >>> # +---------+-----------------+---------+ 1243 >>> # | [0,1,2,3] | [0,1,2,1,2,3] | [1,2,2,1] 1244 >>> # +---------+-----------------+---------+ 1245 >>> 1246 >>> # Use the transform in eager mode 1247 >>> data = [[0, -1, -2, -1, 2], [2, -0, 2, 1, -3]] 1248 >>> output = transforms.Unique()(data) 1249 >>> print(output[0].shape, output[1].shape, output[2].shape) 1250 (6,) (10,) (6,) 1251 >>> print(output[0].dtype, output[1].dtype, output[2].dtype) 1252 int64 int32 int32 1253 """ 1254 1255 def __init__(self): 1256 super().__init__() 1257 self.implementation = Implementation.C 1258 1259 def parse(self): 1260 return cde.UniqueOperation() 1261