• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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