You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

py_transforms.py 13 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. # Copyright 2019 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. """
  16. The module transforms.py_transform is implemented based on Python. It provides common
  17. operations including OneHotOp.
  18. """
  19. import json
  20. import sys
  21. import numpy as np
  22. from .validators import check_one_hot_op, check_compose_list, check_random_apply, check_transforms_list, \
  23. check_compose_call
  24. from . import py_transforms_util as util
  25. from .c_transforms import TensorOperation
  26. def not_random(function):
  27. """
  28. Specify the function as "not random", i.e., it produces deterministic result.
  29. A Python function can only be cached after it is specified as "not random".
  30. """
  31. function.random = False
  32. return function
  33. class PyTensorOperation:
  34. """
  35. Base Python Tensor Operations class
  36. """
  37. def to_json(self):
  38. """
  39. Base to_json for Python tensor operations class
  40. """
  41. json_obj = {}
  42. json_trans = {}
  43. if "transforms" in self.__dict__.keys():
  44. # operations which have transforms as input, need to call _to_json() for each transform to serialize
  45. json_list = []
  46. for transform in self.transforms:
  47. json_list.append(json.loads(transform.to_json()))
  48. json_trans["transforms"] = json_list
  49. self.__dict__.pop("transforms")
  50. if "output_type" in self.__dict__.keys():
  51. json_trans["output_type"] = np.dtype(
  52. self.__dict__["output_type"]).name
  53. self.__dict__.pop("output_type")
  54. json_obj["tensor_op_params"] = self.__dict__
  55. # append transforms to the tensor_op_params of the operation
  56. json_obj["tensor_op_params"].update(json_trans)
  57. json_obj["tensor_op_name"] = self.__class__.__name__
  58. json_obj["python_module"] = self.__class__.__module__
  59. return json.dumps(json_obj)
  60. @classmethod
  61. def from_json(cls, json_string):
  62. """
  63. Base from_json for Python tensor operations class
  64. """
  65. json_obj = json.loads(json_string)
  66. new_op = cls.__new__(cls)
  67. new_op.__dict__ = json_obj
  68. if "transforms" in json_obj.keys():
  69. # operations which have transforms as input, need to call _from_json() for each transform to deseriallize
  70. transforms = []
  71. for json_op in json_obj["transforms"]:
  72. transforms.append(getattr(
  73. sys.modules[json_op["python_module"]], json_op["tensor_op_name"]).from_json(
  74. json.dumps(json_op["tensor_op_params"])))
  75. new_op.transforms = transforms
  76. if "output_type" in json_obj.keys():
  77. output_type = np.dtype(json_obj["output_type"])
  78. new_op.output_type = output_type
  79. return new_op
  80. class OneHotOp(PyTensorOperation):
  81. """
  82. Apply one hot encoding transformation to the input label, make label be more smoothing and continuous.
  83. Args:
  84. num_classes (int): Number of classes of objects in dataset.
  85. It should be larger than the largest label number in the dataset.
  86. smoothing_rate (float, optional): Adjustable hyperparameter for label smoothing level.
  87. (Default=0.0 means no smoothing is applied.)
  88. Examples:
  89. >>> # Assume that dataset has 10 classes, thus the label ranges from 0 to 9
  90. >>> transforms_list = [py_transforms.OneHotOp(num_classes=10, smoothing_rate=0.1)]
  91. >>> transform = py_transforms.Compose(transforms_list)
  92. >>> mnist_dataset = mnist_dataset.map(input_columns=["label"], operations=transform)
  93. """
  94. @check_one_hot_op
  95. def __init__(self, num_classes, smoothing_rate=0.0):
  96. self.num_classes = num_classes
  97. self.smoothing_rate = smoothing_rate
  98. self.random = False
  99. def __call__(self, label):
  100. """
  101. Call method.
  102. Args:
  103. label (numpy.ndarray): label to be applied label smoothing.
  104. Returns:
  105. label (numpy.ndarray), label after being Smoothed.
  106. """
  107. return util.one_hot_encoding(label, self.num_classes, self.smoothing_rate)
  108. class Compose(PyTensorOperation):
  109. """
  110. Compose a list of transforms.
  111. .. Note::
  112. Compose takes a list of transformations either provided in py_transforms or from user-defined implementation;
  113. each can be an initialized transformation class or a lambda function, as long as the output from the last
  114. transformation is a single tensor of type numpy.ndarray. See below for an example of how to use Compose
  115. with py_transforms classes and check out FiveCrop or TenCrop for the use of them in conjunction with lambda
  116. functions.
  117. Args:
  118. transforms (list): List of transformations to be applied.
  119. Examples:
  120. >>> image_folder_dataset_dir = "/path/to/image_folder_dataset_directory"
  121. >>> # create a dataset that reads all files in dataset_dir with 8 threads
  122. >>> image_folder_dataset = ds.ImageFolderDataset(image_folder_dataset_dir, num_parallel_workers=8)
  123. >>> # create a list of transformations to be applied to the image data
  124. >>> transform = py_transforms.Compose([py_vision.Decode(),
  125. ... py_vision.RandomHorizontalFlip(0.5),
  126. ... py_vision.ToTensor(),
  127. ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)),
  128. ... py_vision.RandomErasing()])
  129. >>> # apply the transform to the dataset through dataset.map function
  130. >>> image_folder_dataset = image_folder_dataset.map(operations=transform, input_columns=["image"])
  131. >>>
  132. >>> # Compose is also be invoked implicitly, by just passing in a list of ops
  133. >>> # the above example then becomes:
  134. >>> transforms_list = [py_vision.Decode(),
  135. ... py_vision.RandomHorizontalFlip(0.5),
  136. ... py_vision.ToTensor(),
  137. ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)),
  138. ... py_vision.RandomErasing()]
  139. >>>
  140. >>> # apply the transform to the dataset through dataset.map()
  141. >>> image_folder_dataset_1 = image_folder_dataset_1.map(operations=transforms_list, input_columns=["image"])
  142. >>>
  143. >>> # Certain C++ and Python ops can be combined, but not all of them
  144. >>> # An example of combined operations
  145. >>> arr = [0, 1]
  146. >>> dataset = ds.NumpySlicesDataset(arr, column_names=["cols"], shuffle=False)
  147. >>> transformed_list = [py_transforms.OneHotOp(2), c_transforms.Mask(c_transforms.Relational.EQ, 1)]
  148. >>> dataset = dataset.map(operations=transformed_list, input_columns=["cols"])
  149. >>>
  150. >>> # Here is an example of mixing vision ops
  151. >>> import numpy as np
  152. >>> op_list=[c_vision.Decode(),
  153. ... c_vision.Resize((224, 244)),
  154. ... py_vision.ToPIL(),
  155. ... np.array, # need to convert PIL image to a NumPy array to pass it to C++ operation
  156. ... c_vision.Resize((24, 24))]
  157. >>> image_folder_dataset = image_folder_dataset.map(operations=op_list, input_columns=["image"])
  158. """
  159. @check_compose_list
  160. def __init__(self, transforms):
  161. self.transforms = transforms
  162. if all(hasattr(transform, "random") and not transform.random for transform in self.transforms):
  163. self.random = False
  164. @check_compose_call
  165. def __call__(self, *args):
  166. """
  167. Call method.
  168. Returns:
  169. lambda function, Lambda function that takes in an args to apply transformations on.
  170. """
  171. return util.compose(self.transforms, *args)
  172. @staticmethod
  173. def reduce(operations):
  174. """
  175. Wraps adjacent Python operations in a Compose to allow mixing of Python and C++ operations.
  176. Args:
  177. operations (list): list of tensor operations.
  178. Returns:
  179. list, the reduced list of operations.
  180. """
  181. if len(operations) == 1:
  182. if str(operations).find("c_transform") >= 0 or isinstance(operations[0], TensorOperation):
  183. return operations
  184. return [util.FuncWrapper(operations[0])]
  185. new_ops, start_ind, end_ind = [], 0, 0
  186. for i, op in enumerate(operations):
  187. if str(op).find("c_transform") >= 0:
  188. # reset counts
  189. if start_ind != end_ind:
  190. new_ops.append(Compose(operations[start_ind:end_ind]))
  191. new_ops.append(op)
  192. start_ind, end_ind = i + 1, i + 1
  193. else:
  194. end_ind += 1
  195. # do additional check in case the last operation is a Python operation
  196. if start_ind != end_ind:
  197. new_ops.append(Compose(operations[start_ind:end_ind]))
  198. return new_ops
  199. class RandomApply(PyTensorOperation):
  200. """
  201. Randomly perform a series of transforms with a given probability.
  202. Args:
  203. transforms (list): List of transformations to apply.
  204. prob (float, optional): The probability to apply the transformation list (default=0.5).
  205. Examples:
  206. >>> from mindspore.dataset.transforms.py_transforms import Compose
  207. >>> transforms_list = [py_vision.RandomHorizontalFlip(0.5),
  208. ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)),
  209. ... py_vision.RandomErasing()]
  210. >>> transforms = Compose([py_vision.Decode(),
  211. ... py_transforms.RandomApply(transforms_list, prob=0.6),
  212. ... py_vision.ToTensor()])
  213. >>> image_folder_dataset = image_folder_dataset.map(operations=transforms, input_columns=["image"])
  214. """
  215. @check_random_apply
  216. def __init__(self, transforms, prob=0.5):
  217. self.prob = prob
  218. self.transforms = transforms
  219. def __call__(self, img):
  220. """
  221. Call method.
  222. Args:
  223. img (PIL image): Image to be randomly applied a list transformations.
  224. Returns:
  225. img (PIL image), Transformed image.
  226. """
  227. return util.random_apply(img, self.transforms, self.prob)
  228. class RandomChoice(PyTensorOperation):
  229. """
  230. Randomly select one transform from a series of transforms and applies that on the image.
  231. Args:
  232. transforms (list): List of transformations to be chosen from to apply.
  233. Examples:
  234. >>> from mindspore.dataset.transforms.py_transforms import Compose
  235. >>> transforms_list = [py_vision.RandomHorizontalFlip(0.5),
  236. ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)),
  237. ... py_vision.RandomErasing()]
  238. >>> transforms = Compose([py_vision.Decode(),
  239. ... py_transforms.RandomChoice(transforms_list),
  240. ... py_vision.ToTensor()])
  241. >>> image_folder_dataset = image_folder_dataset.map(operations=transforms, input_columns=["image"])
  242. """
  243. @check_transforms_list
  244. def __init__(self, transforms):
  245. self.transforms = transforms
  246. def __call__(self, img):
  247. """
  248. Call method.
  249. Args:
  250. img (PIL image): Image to be applied transformation.
  251. Returns:
  252. img (PIL image), Transformed image.
  253. """
  254. return util.random_choice(img, self.transforms)
  255. class RandomOrder(PyTensorOperation):
  256. """
  257. Perform a series of transforms to the input PIL image in a random order.
  258. Args:
  259. transforms (list): List of the transformations to apply.
  260. Examples:
  261. >>> from mindspore.dataset.transforms.py_transforms import Compose
  262. >>> transforms_list = [py_vision.RandomHorizontalFlip(0.5),
  263. ... py_vision.Normalize((0.491, 0.482, 0.447), (0.247, 0.243, 0.262)),
  264. ... py_vision.RandomErasing()]
  265. >>> transforms = Compose([py_vision.Decode(),
  266. ... py_transforms.RandomOrder(transforms_list),
  267. ... py_vision.ToTensor()])
  268. >>> image_folder_dataset = image_folder_dataset.map(operations=transforms, input_columns=["image"])
  269. """
  270. @check_transforms_list
  271. def __init__(self, transforms):
  272. self.transforms = transforms
  273. def __call__(self, img):
  274. """
  275. Call method.
  276. Args:
  277. img (PIL image): Image to apply transformations in a random order.
  278. Returns:
  279. img (PIL image), Transformed image.
  280. """
  281. return util.random_order(img, self.transforms)