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.

c_transforms.py 19 kB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. # Copyright 2019-2021 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.c_transforms provides common operations, including OneHotOp and TypeCast.
  17. """
  18. from enum import IntEnum
  19. import numpy as np
  20. from mindspore.common import dtype as mstype
  21. import mindspore._c_dataengine as cde
  22. from .validators import check_num_classes, check_ms_type, check_fill_value, check_slice_option, check_slice_op, \
  23. check_mask_op, check_pad_end, check_concat_type, check_random_transform_ops, check_plugin
  24. from ..core.datatypes import mstype_to_detype
  25. class TensorOperation:
  26. """
  27. Base class Tensor Ops
  28. """
  29. def __call__(self, *input_tensor_list):
  30. tensor_row = []
  31. for tensor in input_tensor_list:
  32. try:
  33. tensor_row.append(cde.Tensor(np.asarray(tensor)))
  34. except RuntimeError:
  35. raise TypeError("Invalid user input. Got {}: {}, cannot be converted into tensor." \
  36. .format(type(tensor), tensor))
  37. callable_op = cde.Execute(self.parse())
  38. output_tensor_list = callable_op(tensor_row)
  39. for i, element in enumerate(output_tensor_list):
  40. arr = element.as_array()
  41. if arr.dtype.char == 'S':
  42. output_tensor_list[i] = np.char.decode(arr)
  43. else:
  44. output_tensor_list[i] = arr
  45. return output_tensor_list[0] if len(output_tensor_list) == 1 else tuple(output_tensor_list)
  46. def parse(self):
  47. """parse function - not yet implemented"""
  48. raise NotImplementedError("TensorOperation has to implement parse() method.")
  49. class OneHot(TensorOperation):
  50. """
  51. Tensor operation to apply one hot encoding.
  52. Args:
  53. num_classes (int): Number of classes of objects in dataset.
  54. It should be larger than the largest label number in the dataset.
  55. Raises:
  56. RuntimeError: feature size is bigger than num_classes.
  57. Examples:
  58. >>> # Assume that dataset has 10 classes, thus the label ranges from 0 to 9
  59. >>> onehot_op = c_transforms.OneHot(num_classes=10)
  60. >>> mnist_dataset = mnist_dataset.map(operations=onehot_op, input_columns=["label"])
  61. """
  62. @check_num_classes
  63. def __init__(self, num_classes):
  64. self.num_classes = num_classes
  65. def parse(self):
  66. return cde.OneHotOperation(self.num_classes)
  67. class Fill(TensorOperation):
  68. """
  69. Tensor operation to fill all elements in the tensor with the specified value.
  70. The output tensor will have the same shape and type as the input tensor.
  71. Args:
  72. fill_value (Union[str, bytes, int, float, bool]) : scalar value
  73. to fill the tensor with.
  74. Examples:
  75. >>> import numpy as np
  76. >>> # generate a 1D integer numpy array from 0 to 4
  77. >>> def generator_1d():
  78. ... for i in range(5):
  79. ... yield (np.array([i]),)
  80. >>> generator_dataset = ds.GeneratorDataset(generator_1d, column_names="col1")
  81. >>> # [[0], [1], [2], [3], [4]]
  82. >>> fill_op = c_transforms.Fill(3)
  83. >>> generator_dataset = generator_dataset.map(operations=fill_op)
  84. >>> # [[3], [3], [3], [3], [3]]
  85. """
  86. @check_fill_value
  87. def __init__(self, fill_value):
  88. self.fill_value = cde.Tensor(np.array(fill_value))
  89. def parse(self):
  90. return cde.FillOperation(self.fill_value)
  91. class TypeCast(TensorOperation):
  92. """
  93. Tensor operation to cast to a given MindSpore data type.
  94. Args:
  95. data_type (mindspore.dtype): mindspore.dtype to be cast to.
  96. Examples:
  97. >>> import numpy as np
  98. >>> from mindspore import dtype as mstype
  99. >>>
  100. >>> # Generate 1d int numpy array from 0 - 63
  101. >>> def generator_1d():
  102. ... for i in range(64):
  103. ... yield (np.array([i]),)
  104. >>>
  105. >>> dataset = ds.GeneratorDataset(generator_1d, column_names='col')
  106. >>> type_cast_op = c_transforms.TypeCast(mstype.int32)
  107. >>> dataset = dataset.map(operations=type_cast_op)
  108. """
  109. @check_ms_type
  110. def __init__(self, data_type):
  111. data_type = mstype_to_detype(data_type)
  112. self.data_type = str(data_type)
  113. def parse(self):
  114. return cde.TypeCastOperation(self.data_type)
  115. class _SliceOption(cde.SliceOption):
  116. """
  117. Internal class SliceOption to be used with SliceOperation
  118. Args:
  119. _SliceOption(Union[int, list(int), slice, None, Ellipsis, bool, _SliceOption]):
  120. 1. :py:obj:`int`: Slice this index only along the dimension. Negative index is supported.
  121. 2. :py:obj:`list(int)`: Slice these indices along the dimension. Negative indices are supported.
  122. 3. :py:obj:`slice`: Slice the generated indices from the slice object along the dimension.
  123. 4. :py:obj:`None`: Slice the whole dimension. Similar to :py:obj:`:` in Python indexing.
  124. 5. :py:obj:`Ellipsis`: Slice the whole dimension. Similar to :py:obj:`:` in Python indexing.
  125. 6. :py:obj:`boolean`: Slice the whole dimension. Similar to :py:obj:`:` in Python indexing.
  126. """
  127. @check_slice_option
  128. def __init__(self, slice_option):
  129. if isinstance(slice_option, int) and not isinstance(slice_option, bool):
  130. slice_option = [slice_option]
  131. elif slice_option is Ellipsis:
  132. slice_option = True
  133. elif slice_option is None:
  134. slice_option = True
  135. super().__init__(slice_option)
  136. class Slice(TensorOperation):
  137. """
  138. Slice operation to extract a tensor out using the given n slices.
  139. The functionality of Slice is similar to NumPy's indexing feature (Currently only rank-1 tensors are supported).
  140. Args:
  141. slices (Union[int, list[int], slice, None, Ellipsis]):
  142. Maximum `n` number of arguments to slice a tensor of rank `n` .
  143. One object in slices can be one of:
  144. 1. :py:obj:`int`: Slice this index only along the first dimension. Negative index is supported.
  145. 2. :py:obj:`list(int)`: Slice these indices along the first dimension. Negative indices are supported.
  146. 3. :py:obj:`slice`: Slice the generated indices from the slice object along the first dimension.
  147. Similar to start:stop:step.
  148. 4. :py:obj:`None`: Slice the whole dimension. Similar to :py:obj:`[:]` in Python indexing.
  149. 5. :py:obj:`Ellipsis`: Slice the whole dimension, same result with `None`.
  150. Examples:
  151. >>> # Data before
  152. >>> # | col |
  153. >>> # +---------+
  154. >>> # | [1,2,3] |
  155. >>> # +---------|
  156. >>> data = [[1, 2, 3]]
  157. >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["col"])
  158. >>> # slice indices 1 and 2 only
  159. >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=c_transforms.Slice(slice(1,3)))
  160. >>> # Data after
  161. >>> # | col |
  162. >>> # +---------+
  163. >>> # | [2,3] |
  164. >>> # +---------|
  165. """
  166. @check_slice_op
  167. def __init__(self, *slices):
  168. slice_input_ = list(slices)
  169. slice_input_ = [_SliceOption(slice_dim) for slice_dim in slice_input_]
  170. self.slice_input_ = slice_input_
  171. def parse(self):
  172. return cde.SliceOperation(self.slice_input_)
  173. class Relational(IntEnum):
  174. """
  175. Relationship operator.
  176. Possible enumeration values are: Relational.EQ, Relational.NE, Relational.GT, Relational.GE, Relational.LT,
  177. Relational.LE.
  178. - Relational.EQ: refers to Equality.
  179. - Relational.NE: refers not equal, or Inequality.
  180. - Relational.GT: refers to Greater than.
  181. - Relational.GE: refers to Greater than or equal to.
  182. - Relational.LT: refers to Less than.
  183. - Relational.LE: refers to Less than or equal to.
  184. """
  185. EQ = 0
  186. NE = 1
  187. GT = 2
  188. GE = 3
  189. LT = 4
  190. LE = 5
  191. DE_C_RELATIONAL = {Relational.EQ: cde.RelationalOp.EQ,
  192. Relational.NE: cde.RelationalOp.NE,
  193. Relational.GT: cde.RelationalOp.GT,
  194. Relational.GE: cde.RelationalOp.GE,
  195. Relational.LT: cde.RelationalOp.LT,
  196. Relational.LE: cde.RelationalOp.LE}
  197. class Mask(TensorOperation):
  198. r"""
  199. Mask content of the input tensor with the given predicate.
  200. Any element of the tensor that matches the predicate will be evaluated to True, otherwise False.
  201. Args:
  202. operator (Relational): relational operators, it can be any of [Relational.EQ, Relational.NE, Relational.LT,
  203. Relational.GT, Relational.LE, Relational.GE], take Relational.EQ as example, EQ refers to equal.
  204. constant (Union[str, int, float, bool]): Constant to be compared to.
  205. Constant will be cast to the type of the input tensor.
  206. dtype (mindspore.dtype, optional): Type of the generated mask (Default mstype.bool\_).
  207. Examples:
  208. >>> from mindspore.dataset.transforms.c_transforms import Relational
  209. >>> # Data before
  210. >>> # | col |
  211. >>> # +---------+
  212. >>> # | [1,2,3] |
  213. >>> # +---------+
  214. >>> data = [[1, 2, 3]]
  215. >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["col"])
  216. >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=c_transforms.Mask(Relational.EQ, 2))
  217. >>> # Data after
  218. >>> # | col |
  219. >>> # +--------------------+
  220. >>> # | [False,True,False] |
  221. >>> # +--------------------+
  222. """
  223. @check_mask_op
  224. def __init__(self, operator, constant, dtype=mstype.bool_):
  225. self.operator = operator
  226. self.dtype = mstype_to_detype(dtype)
  227. self.constant = cde.Tensor(np.array(constant))
  228. def parse(self):
  229. return cde.MaskOperation(DE_C_RELATIONAL[self.operator], self.constant, self.dtype)
  230. class PadEnd(TensorOperation):
  231. """
  232. Pad input tensor according to pad_shape, input tensor needs to have same rank.
  233. Args:
  234. pad_shape (list(int)): List of integers representing the shape needed. Dimensions that set to `None` will
  235. not be padded (i.e., original dim will be used). Shorter dimensions will truncate the values.
  236. pad_value (Union[str, bytes, int, float, bool]), optional): Value used to pad. Default to 0 or empty
  237. string in case of tensors of strings.
  238. Examples:
  239. >>> # Data before
  240. >>> # | col |
  241. >>> # +---------+
  242. >>> # | [1,2,3] |
  243. >>> # +---------|
  244. >>> data = [[1, 2, 3]]
  245. >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["col"])
  246. >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=c_transforms.PadEnd(pad_shape=[4],
  247. ... pad_value=10))
  248. >>> # Data after
  249. >>> # | col |
  250. >>> # +------------+
  251. >>> # | [1,2,3,10] |
  252. >>> # +------------|
  253. """
  254. @check_pad_end
  255. def __init__(self, pad_shape, pad_value=None):
  256. self.pad_shape = cde.TensorShape(pad_shape)
  257. self.pad_value = cde.Tensor(np.array(pad_value)) if pad_value is not None else pad_value
  258. def parse(self):
  259. return cde.PadEndOperation(self.pad_shape, self.pad_value)
  260. class Concatenate(TensorOperation):
  261. """
  262. Tensor operation that concatenates all columns into a single tensor.
  263. Args:
  264. axis (int, optional): Concatenate the tensors along given axis (Default=0).
  265. prepend (numpy.array, optional): NumPy array to be prepended to the already concatenated tensors
  266. (Default=None).
  267. append (numpy.array, optional): NumPy array to be appended to the already concatenated tensors (Default=None).
  268. Examples:
  269. >>> import numpy as np
  270. >>> # concatenate string
  271. >>> prepend_tensor = np.array(["dw", "df"], dtype='S')
  272. >>> append_tensor = np.array(["dwsdf", "df"], dtype='S')
  273. >>> concatenate_op = c_transforms.Concatenate(0, prepend_tensor, append_tensor)
  274. >>> data = [["This","is","a","string"]]
  275. >>> dataset = ds.NumpySlicesDataset(data)
  276. >>> dataset = dataset.map(operations=concatenate_op)
  277. """
  278. @check_concat_type
  279. def __init__(self, axis=0, prepend=None, append=None):
  280. self.axis = axis
  281. self.prepend = cde.Tensor(np.array(prepend)) if prepend is not None else prepend
  282. self.append = cde.Tensor(np.array(append)) if append is not None else append
  283. def parse(self):
  284. return cde.ConcatenateOperation(self.axis, self.prepend, self.append)
  285. class Duplicate(TensorOperation):
  286. """
  287. Duplicate the input tensor to output, only support transform one column each time.
  288. Examples:
  289. >>> # Data before
  290. >>> # | x |
  291. >>> # +---------+
  292. >>> # | [1,2,3] |
  293. >>> # +---------+
  294. >>> data = [[1,2,3]]
  295. >>> numpy_slices_dataset = ds.NumpySlicesDataset(data, ["x"])
  296. >>> numpy_slices_dataset = numpy_slices_dataset.map(operations=c_transforms.Duplicate(),
  297. ... input_columns=["x"],
  298. ... output_columns=["x", "y"],
  299. ... column_order=["x", "y"])
  300. >>> # Data after
  301. >>> # | x | y |
  302. >>> # +---------+---------+
  303. >>> # | [1,2,3] | [1,2,3] |
  304. >>> # +---------+---------+
  305. """
  306. def parse(self):
  307. return cde.DuplicateOperation()
  308. class Unique(TensorOperation):
  309. """
  310. Perform the unique operation on the input tensor, only support transform one column each time.
  311. Return 3 tensor: unique output tensor, index tensor, count tensor.
  312. Unique output tensor contains all the unique elements of the input tensor
  313. in the same order that they occur in the input tensor.
  314. Index tensor that contains the index of each element of the input tensor in the unique output tensor.
  315. Count tensor that contains the count of each element of the output tensor in the input tensor.
  316. Note:
  317. Call batch op before calling this function.
  318. Examples:
  319. >>> # Data before
  320. >>> # | x |
  321. >>> # +--------------------+
  322. >>> # | [[0,1,2], [1,2,3]] |
  323. >>> # +--------------------+
  324. >>> data = [[[0,1,2], [1,2,3]]]
  325. >>> dataset = ds.NumpySlicesDataset(data, ["x"])
  326. >>> dataset = dataset.map(operations=c_transforms.Unique(),
  327. ... input_columns=["x"],
  328. ... output_columns=["x", "y", "z"],
  329. ... column_order=["x", "y", "z"])
  330. >>> # Data after
  331. >>> # | x | y |z |
  332. >>> # +---------+-----------------+---------+
  333. >>> # | [0,1,2,3] | [0,1,2,1,2,3] | [1,2,2,1]
  334. >>> # +---------+-----------------+---------+
  335. """
  336. def parse(self):
  337. return cde.UniqueOperation()
  338. class Compose(TensorOperation):
  339. """
  340. Compose a list of transforms into a single transform.
  341. Args:
  342. transforms (list): List of transformations to be applied.
  343. Examples:
  344. >>> compose = c_transforms.Compose([c_vision.Decode(), c_vision.RandomCrop(512)])
  345. >>> image_folder_dataset = image_folder_dataset.map(operations=compose)
  346. """
  347. @check_random_transform_ops
  348. def __init__(self, transforms):
  349. self.transforms = transforms
  350. def parse(self):
  351. operations = []
  352. for op in self.transforms:
  353. if op and getattr(op, 'parse', None):
  354. operations.append(op.parse())
  355. else:
  356. operations.append(op)
  357. return cde.ComposeOperation(operations)
  358. class RandomApply(TensorOperation):
  359. """
  360. Randomly perform a series of transforms with a given probability.
  361. Args:
  362. transforms (list): List of transformations to be applied.
  363. prob (float, optional): The probability to apply the transformation list (default=0.5).
  364. Examples:
  365. >>> rand_apply = c_transforms.RandomApply([c_vision.RandomCrop(512)])
  366. >>> image_folder_dataset = image_folder_dataset.map(operations=rand_apply)
  367. """
  368. @check_random_transform_ops
  369. def __init__(self, transforms, prob=0.5):
  370. self.transforms = transforms
  371. self.prob = prob
  372. def parse(self):
  373. operations = []
  374. for op in self.transforms:
  375. if op and getattr(op, 'parse', None):
  376. operations.append(op.parse())
  377. else:
  378. operations.append(op)
  379. return cde.RandomApplyOperation(self.prob, operations)
  380. class RandomChoice(TensorOperation):
  381. """
  382. Randomly select one transform from a list of transforms to perform operation.
  383. Args:
  384. transforms (list): List of transformations to be chosen from to apply.
  385. Examples:
  386. >>> rand_choice = c_transforms.RandomChoice([c_vision.CenterCrop(50), c_vision.RandomCrop(512)])
  387. >>> image_folder_dataset = image_folder_dataset.map(operations=rand_choice)
  388. """
  389. @check_random_transform_ops
  390. def __init__(self, transforms):
  391. self.transforms = transforms
  392. def parse(self):
  393. operations = []
  394. for op in self.transforms:
  395. if op and getattr(op, 'parse', None):
  396. operations.append(op.parse())
  397. else:
  398. operations.append(op)
  399. return cde.RandomChoiceOperation(operations)
  400. class Plugin(TensorOperation):
  401. """
  402. Plugin support for MindData. Use this class to dynamically load a .so file (shared library) and execute its symbols.
  403. Args:
  404. lib_path (str): Path to .so file which is compiled to support MindData plugin.
  405. func_name (str): Name of the function to load from the .so file.
  406. user_args (str, optional): Serialized args to pass to the plugin. Only needed if "func_name" requires one.
  407. Examples:
  408. >>> plugin = c_transforms.Plugin("pluginlib.so", "PluginDecode")
  409. >>> image_folder_dataset = image_folder_dataset.map(operations=plugin)
  410. """
  411. @check_plugin
  412. def __init__(self, lib_path, func_name, user_args=None):
  413. self.lib_path = lib_path
  414. self.func_name = func_name
  415. self.user_args = str() if (user_args is None) else user_args
  416. def parse(self):
  417. return cde.PluginOperation(self.lib_path, self.func_name, self.user_args)