vkit.mechanism.distortion.geometric.affine
1# Copyright 2022 vkit-x Administrator. All Rights Reserved. 2# 3# This project (vkit-x/vkit) is dual-licensed under commercial and SSPL licenses. 4# 5# The commercial license gives you the full rights to create and distribute software 6# on your own terms without any SSPL license obligations. For more information, 7# please see the "LICENSE_COMMERCIAL.txt" file. 8# 9# This project is also available under Server Side Public License (SSPL). 10# The SSPL licensing is ideal for use cases such as open source projects with 11# SSPL distribution, student/academic purposes, hobby projects, internal research 12# projects without external distribution, or other projects where all SSPL 13# obligations can be met. For more information, please see the "LICENSE_SSPL.txt" file. 14from typing import Optional, Sequence, List, Tuple, Union, Iterable, TypeVar, Type 15import math 16 17import attrs 18import numpy as np 19from numpy.random import Generator as RandomGenerator 20import cv2 as cv 21 22from vkit.element import ( 23 Image, 24 ScoreMap, 25 Mask, 26 Point, 27 PointList, 28 PointTuple, 29 Polygon, 30) 31from ..interface import ( 32 DistortionConfig, 33 DistortionState, 34 Distortion, 35) 36 37 38def affine_mat(trans_mat: np.ndarray, dsize: Tuple[int, int], mat: np.ndarray) -> np.ndarray: 39 if trans_mat.shape[0] == 2: 40 return cv.warpAffine(mat, trans_mat, dsize) 41 else: 42 assert trans_mat.shape[0] == 3 43 return cv.warpPerspective(mat, trans_mat, dsize) 44 45 46def affine_np_points(trans_mat: np.ndarray, np_points: np.ndarray) -> np.ndarray: 47 # (2, *) 48 np_points = np_points.transpose() 49 # (3, *) 50 np_points = np.concatenate(( 51 np_points, 52 np.ones((1, np_points.shape[1]), dtype=np.float32), 53 )) 54 55 new_np_points = np.matmul(trans_mat, np_points) 56 if trans_mat.shape[0] == 2: 57 # new_np_points.shape is (2, *), do nothing. 58 pass 59 60 else: 61 assert trans_mat.shape[0] == 3 62 new_np_points = new_np_points[:2, :] / new_np_points[2, :] 63 64 return new_np_points.transpose() 65 66 67def affine_points(trans_mat: np.ndarray, points: PointTuple): 68 new_np_points = affine_np_points(trans_mat, points.to_smooth_np_array()) 69 return PointTuple.from_np_array(new_np_points) 70 71 72def affine_polygons(trans_mat: np.ndarray, polygons: Sequence[Polygon]) -> Sequence[Polygon]: 73 points_ranges: List[Tuple[int, int]] = [] 74 points = PointList() 75 for polygon in polygons: 76 points_ranges.append((len(points), len(points) + polygon.num_points)) 77 points.extend(polygon.points) 78 79 new_np_points = affine_np_points(trans_mat, points.to_smooth_np_array()) 80 new_polygons = [] 81 for begin, end in points_ranges: 82 new_polygons.append(Polygon.from_np_array(new_np_points[begin:end])) 83 84 return new_polygons 85 86 87def convert_dsize_to_result_shape(dsize: Optional[Tuple[int, int]]): 88 if dsize: 89 return dsize[1], dsize[0] 90 91 92@attrs.define 93class ShearHoriConfig(DistortionConfig): 94 # angle: int, (-90, 90), positive value for rightward direction. 95 angle: int 96 97 @property 98 def is_nop(self): 99 return self.angle == 0 100 101 102class ShearHoriState(DistortionState[ShearHoriConfig]): 103 104 def __init__( 105 self, 106 config: ShearHoriConfig, 107 shape: Tuple[int, int], 108 rng: Optional[RandomGenerator], 109 ): 110 tan_phi = math.tan(math.radians(config.angle)) 111 112 height, width = shape 113 shift_x = abs(height * tan_phi) 114 self.dsize = (math.ceil(width + shift_x), height) 115 116 if config.angle < 0: 117 # Shear left & the negative part. 118 self.trans_mat = np.asarray( 119 [ 120 (1, -tan_phi, 0), 121 (0, 1, 0), 122 ], 123 dtype=np.float32, 124 ) 125 126 elif config.angle > 0: 127 # Shear right. 128 self.trans_mat = np.asarray( 129 [ 130 (1, -tan_phi, shift_x), 131 (0, 1, 0), 132 ], 133 dtype=np.float32, 134 ) 135 136 else: 137 # No need to transform. 138 self.trans_mat = None 139 self.dsize = None 140 141 @property 142 def result_shape(self): 143 return convert_dsize_to_result_shape(self.dsize) 144 145 146@attrs.define 147class ShearVertConfig(DistortionConfig): 148 # angle: int, (-90, 90), positive value for downward direction. 149 angle: int 150 151 @property 152 def is_nop(self): 153 return self.angle == 0 154 155 156class ShearVertState(DistortionState[ShearVertConfig]): 157 158 def __init__( 159 self, 160 config: ShearVertConfig, 161 shape: Tuple[int, int], 162 rng: Optional[RandomGenerator], 163 ): 164 tan_abs_phi = math.tan(math.radians(abs(config.angle))) 165 166 height, width = shape 167 shift_y = width * tan_abs_phi 168 self.dsize = (width, math.ceil(height + shift_y)) 169 170 if config.angle < 0: 171 # Shear up. 172 self.trans_mat = np.asarray( 173 [ 174 (1, 0, 0), 175 (-tan_abs_phi, 1, shift_y), 176 ], 177 dtype=np.float32, 178 ) 179 elif config.angle > 0: 180 # Shear down & the negative part. 181 self.trans_mat = np.asarray( 182 [ 183 (1, 0, 0), 184 (tan_abs_phi, 1, 0), 185 ], 186 dtype=np.float32, 187 ) 188 else: 189 # No need to transform. 190 self.trans_mat = None 191 self.dsize = None 192 193 @property 194 def result_shape(self): 195 return convert_dsize_to_result_shape(self.dsize) 196 197 198@attrs.define 199class RotateConfig(DistortionConfig): 200 # angle: int, [0, 360], clockwise angle. 201 angle: int 202 203 @property 204 def is_nop(self): 205 return self.angle == 0 206 207 208class RotateState(DistortionState[RotateConfig]): 209 210 def __init__( 211 self, 212 config: RotateConfig, 213 shape: Tuple[int, int], 214 rng: Optional[RandomGenerator], 215 ): 216 height, width = shape 217 218 angle = config.angle % 360 219 rad = math.radians(angle) 220 221 shift_x = 0 222 shift_y = 0 223 224 if rad <= math.pi / 2: 225 # In 3-4 quadrant. 226 shift_x = height * math.sin(rad) 227 228 dst_width = height * math.sin(rad) + width * math.cos(rad) 229 dst_height = height * math.cos(rad) + width * math.sin(rad) 230 231 elif rad <= math.pi: 232 # In 2-3 quadrant. 233 shift_rad = rad - math.pi / 2 234 235 shift_x = width * math.sin(shift_rad) + height * math.cos(shift_rad) 236 shift_y = height * math.sin(shift_rad) 237 238 dst_width = shift_x 239 dst_height = shift_y + width * math.cos(shift_rad) 240 241 elif rad < math.pi * 3 / 2: 242 # In 1-2 quadrant. 243 shift_rad = rad - math.pi 244 245 shift_x = width * math.cos(shift_rad) 246 shift_y = width * math.sin(shift_rad) + height * math.cos(shift_rad) 247 248 dst_width = shift_x + height * math.sin(shift_rad) 249 dst_height = shift_y 250 251 else: 252 # In 1-4 quadrant. 253 shift_rad = rad - math.pi * 3 / 2 254 255 shift_y = width * math.cos(shift_rad) 256 257 dst_width = width * math.sin(shift_rad) + height * math.cos(shift_rad) 258 dst_height = shift_y + height * math.sin(shift_rad) 259 260 shift_x = math.ceil(shift_x) 261 shift_y = math.ceil(shift_y) 262 263 self.trans_mat = np.asarray( 264 [ 265 (math.cos(rad), -math.sin(rad), shift_x), 266 (math.sin(rad), math.cos(rad), shift_y), 267 ], 268 dtype=np.float32, 269 ) 270 271 self.dsize = (math.ceil(dst_width), math.ceil(dst_height)) 272 273 @property 274 def result_shape(self): 275 return convert_dsize_to_result_shape(self.dsize) 276 277 278@attrs.define 279class SkewHoriConfig(DistortionConfig): 280 # (-1.0, 0.0], shrink the left side. 281 # [0.0, 1.0), shrink the right side. 282 # The larger abs(ratio), the more to shrink. 283 ratio: float 284 285 @property 286 def is_nop(self): 287 return self.ratio == 0 288 289 290class SkewHoriState(DistortionState[SkewHoriConfig]): 291 292 def __init__( 293 self, 294 config: SkewHoriConfig, 295 shape: Tuple[int, int], 296 rng: Optional[RandomGenerator], 297 ): 298 height, width = shape 299 300 src_xy_pairs = [ 301 (0, 0), 302 (width - 1, 0), 303 (width - 1, height - 1), 304 (0, height - 1), 305 ] 306 307 shrink_size = round(height * abs(config.ratio)) 308 shrink_up = shrink_size // 2 309 shrink_down = shrink_size - shrink_up 310 311 if config.ratio < 0: 312 dst_xy_pairs = [ 313 (0, shrink_up), 314 (width - 1, 0), 315 (width - 1, height - 1), 316 (0, height - shrink_down - 1), 317 ] 318 else: 319 dst_xy_pairs = [ 320 (0, 0), 321 (width - 1, shrink_up), 322 (width - 1, height - shrink_down - 1), 323 (0, height - 1), 324 ] 325 326 self.trans_mat = cv.getPerspectiveTransform( 327 np.asarray(src_xy_pairs, dtype=np.float32), 328 np.asarray(dst_xy_pairs, dtype=np.float32), 329 cv.DECOMP_SVD, 330 ) 331 self.dsize = (width, height) 332 333 @property 334 def result_shape(self): 335 return convert_dsize_to_result_shape(self.dsize) 336 337 338@attrs.define 339class SkewVertConfig(DistortionConfig): 340 # (-1.0, 0.0], shrink the up side. 341 # [0.0, 1.0), shrink the down side. 342 # The larger abs(ratio), the more to shrink. 343 ratio: float 344 345 @property 346 def is_nop(self): 347 return self.ratio == 0 348 349 350class SkewVertState(DistortionState[SkewVertConfig]): 351 352 def __init__( 353 self, 354 config: SkewVertConfig, 355 shape: Tuple[int, int], 356 rng: Optional[RandomGenerator], 357 ): 358 height, width = shape 359 360 src_xy_pairs = [ 361 (0, 0), 362 (width - 1, 0), 363 (width - 1, height - 1), 364 (0, height - 1), 365 ] 366 367 shrink_size = round(width * abs(config.ratio)) 368 shrink_left = shrink_size // 2 369 shrink_right = shrink_size - shrink_left 370 371 if config.ratio < 0: 372 dst_xy_pairs = [ 373 (shrink_left, 0), 374 (width - shrink_right - 1, 0), 375 (width - 1, height - 1), 376 (0, height - 1), 377 ] 378 else: 379 dst_xy_pairs = [ 380 (0, 0), 381 (width - 1, 0), 382 (width - shrink_right - 1, height - 1), 383 (shrink_right, height - 1), 384 ] 385 386 self.trans_mat = cv.getPerspectiveTransform( 387 np.asarray(src_xy_pairs, dtype=np.float32), 388 np.asarray(dst_xy_pairs, dtype=np.float32), 389 cv.DECOMP_SVD, 390 ) 391 self.dsize = (width, height) 392 393 @property 394 def result_shape(self): 395 return convert_dsize_to_result_shape(self.dsize) 396 397 398_T_AFFINE_CONFIG = TypeVar( 399 '_T_AFFINE_CONFIG', 400 ShearHoriConfig, 401 ShearVertConfig, 402 RotateConfig, 403 SkewHoriConfig, 404 SkewVertConfig, 405) 406_T_AFFINE_STATE = TypeVar( 407 '_T_AFFINE_STATE', 408 ShearHoriState, 409 ShearVertState, 410 RotateState, 411 SkewHoriState, 412 SkewVertState, 413) 414 415 416def affine_trait_func_mat( 417 config: _T_AFFINE_CONFIG, 418 state: Optional[_T_AFFINE_STATE], 419 mat: np.ndarray, 420): 421 assert state 422 if config.is_nop: 423 return mat 424 else: 425 assert state.trans_mat is not None 426 assert state.dsize is not None 427 return affine_mat(state.trans_mat, state.dsize, mat) 428 429 430def affine_trait_func_image( 431 config: _T_AFFINE_CONFIG, 432 state: Optional[_T_AFFINE_STATE], 433 image: Image, 434 rng: Optional[RandomGenerator], 435): 436 return Image(mat=affine_trait_func_mat(config, state, image.mat)) 437 438 439def affine_trait_func_score_map( 440 config: _T_AFFINE_CONFIG, 441 state: Optional[_T_AFFINE_STATE], 442 score_map: ScoreMap, 443 rng: Optional[RandomGenerator], 444): 445 assert state 446 return ScoreMap(mat=affine_trait_func_mat(config, state, score_map.mat)) 447 448 449def affine_trait_func_mask( 450 config: _T_AFFINE_CONFIG, 451 state: Optional[_T_AFFINE_STATE], 452 mask: Mask, 453 rng: Optional[RandomGenerator], 454): 455 assert state 456 return Mask(mat=affine_trait_func_mat(config, state, mask.mat)) 457 458 459def affine_trait_func_points( 460 config: _T_AFFINE_CONFIG, 461 state: Optional[_T_AFFINE_STATE], 462 shape: Tuple[int, int], 463 points: Union[PointList, PointTuple, Iterable[Point]], 464 rng: Optional[RandomGenerator], 465): 466 assert state 467 points = PointTuple(points) 468 if config.is_nop: 469 return points 470 else: 471 assert state.trans_mat is not None 472 return affine_points(state.trans_mat, points) 473 474 475def affine_trait_func_polygons( 476 config: _T_AFFINE_CONFIG, 477 state: Optional[_T_AFFINE_STATE], 478 shape: Tuple[int, int], 479 polygons: Iterable[Polygon], 480 rng: Optional[RandomGenerator], 481): 482 assert state 483 polygons = tuple(polygons) 484 if config.is_nop: 485 return polygons 486 else: 487 assert state.trans_mat is not None 488 return affine_polygons(state.trans_mat, polygons) 489 490 491class DistortionAffine(Distortion[_T_AFFINE_CONFIG, _T_AFFINE_STATE]): 492 493 def __init__( 494 self, 495 config_cls: Type[_T_AFFINE_CONFIG], 496 state_cls: Type[_T_AFFINE_STATE], 497 ): 498 super().__init__( 499 config_cls=config_cls, 500 state_cls=state_cls, 501 func_image=affine_trait_func_image, 502 func_mask=affine_trait_func_mask, 503 func_score_map=affine_trait_func_score_map, 504 func_points=affine_trait_func_points, 505 func_polygons=affine_trait_func_polygons, 506 ) 507 508 509shear_hori = DistortionAffine( 510 config_cls=ShearHoriConfig, 511 state_cls=ShearHoriState, 512) 513 514shear_vert = DistortionAffine( 515 config_cls=ShearVertConfig, 516 state_cls=ShearVertState, 517) 518 519rotate = DistortionAffine( 520 config_cls=RotateConfig, 521 state_cls=RotateState, 522) 523 524skew_hori = DistortionAffine( 525 config_cls=SkewHoriConfig, 526 state_cls=SkewHoriState, 527) 528 529skew_vert = DistortionAffine( 530 config_cls=SkewVertConfig, 531 state_cls=SkewVertState, 532)
47def affine_np_points(trans_mat: np.ndarray, np_points: np.ndarray) -> np.ndarray: 48 # (2, *) 49 np_points = np_points.transpose() 50 # (3, *) 51 np_points = np.concatenate(( 52 np_points, 53 np.ones((1, np_points.shape[1]), dtype=np.float32), 54 )) 55 56 new_np_points = np.matmul(trans_mat, np_points) 57 if trans_mat.shape[0] == 2: 58 # new_np_points.shape is (2, *), do nothing. 59 pass 60 61 else: 62 assert trans_mat.shape[0] == 3 63 new_np_points = new_np_points[:2, :] / new_np_points[2, :] 64 65 return new_np_points.transpose()
73def affine_polygons(trans_mat: np.ndarray, polygons: Sequence[Polygon]) -> Sequence[Polygon]: 74 points_ranges: List[Tuple[int, int]] = [] 75 points = PointList() 76 for polygon in polygons: 77 points_ranges.append((len(points), len(points) + polygon.num_points)) 78 points.extend(polygon.points) 79 80 new_np_points = affine_np_points(trans_mat, points.to_smooth_np_array()) 81 new_polygons = [] 82 for begin, end in points_ranges: 83 new_polygons.append(Polygon.from_np_array(new_np_points[begin:end])) 84 85 return new_polygons
94class ShearHoriConfig(DistortionConfig): 95 # angle: int, (-90, 90), positive value for rightward direction. 96 angle: int 97 98 @property 99 def is_nop(self): 100 return self.angle == 0
Inherited Members
103class ShearHoriState(DistortionState[ShearHoriConfig]): 104 105 def __init__( 106 self, 107 config: ShearHoriConfig, 108 shape: Tuple[int, int], 109 rng: Optional[RandomGenerator], 110 ): 111 tan_phi = math.tan(math.radians(config.angle)) 112 113 height, width = shape 114 shift_x = abs(height * tan_phi) 115 self.dsize = (math.ceil(width + shift_x), height) 116 117 if config.angle < 0: 118 # Shear left & the negative part. 119 self.trans_mat = np.asarray( 120 [ 121 (1, -tan_phi, 0), 122 (0, 1, 0), 123 ], 124 dtype=np.float32, 125 ) 126 127 elif config.angle > 0: 128 # Shear right. 129 self.trans_mat = np.asarray( 130 [ 131 (1, -tan_phi, shift_x), 132 (0, 1, 0), 133 ], 134 dtype=np.float32, 135 ) 136 137 else: 138 # No need to transform. 139 self.trans_mat = None 140 self.dsize = None 141 142 @property 143 def result_shape(self): 144 return convert_dsize_to_result_shape(self.dsize)
Abstract base class for generic types.
A generic type is typically declared by inheriting from this class parameterized with one or more type variables. For example, a generic mapping type might be defined as::
class Mapping(Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... # Etc.
This class can then be used as follows::
def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default
105 def __init__( 106 self, 107 config: ShearHoriConfig, 108 shape: Tuple[int, int], 109 rng: Optional[RandomGenerator], 110 ): 111 tan_phi = math.tan(math.radians(config.angle)) 112 113 height, width = shape 114 shift_x = abs(height * tan_phi) 115 self.dsize = (math.ceil(width + shift_x), height) 116 117 if config.angle < 0: 118 # Shear left & the negative part. 119 self.trans_mat = np.asarray( 120 [ 121 (1, -tan_phi, 0), 122 (0, 1, 0), 123 ], 124 dtype=np.float32, 125 ) 126 127 elif config.angle > 0: 128 # Shear right. 129 self.trans_mat = np.asarray( 130 [ 131 (1, -tan_phi, shift_x), 132 (0, 1, 0), 133 ], 134 dtype=np.float32, 135 ) 136 137 else: 138 # No need to transform. 139 self.trans_mat = None 140 self.dsize = None
148class ShearVertConfig(DistortionConfig): 149 # angle: int, (-90, 90), positive value for downward direction. 150 angle: int 151 152 @property 153 def is_nop(self): 154 return self.angle == 0
Inherited Members
157class ShearVertState(DistortionState[ShearVertConfig]): 158 159 def __init__( 160 self, 161 config: ShearVertConfig, 162 shape: Tuple[int, int], 163 rng: Optional[RandomGenerator], 164 ): 165 tan_abs_phi = math.tan(math.radians(abs(config.angle))) 166 167 height, width = shape 168 shift_y = width * tan_abs_phi 169 self.dsize = (width, math.ceil(height + shift_y)) 170 171 if config.angle < 0: 172 # Shear up. 173 self.trans_mat = np.asarray( 174 [ 175 (1, 0, 0), 176 (-tan_abs_phi, 1, shift_y), 177 ], 178 dtype=np.float32, 179 ) 180 elif config.angle > 0: 181 # Shear down & the negative part. 182 self.trans_mat = np.asarray( 183 [ 184 (1, 0, 0), 185 (tan_abs_phi, 1, 0), 186 ], 187 dtype=np.float32, 188 ) 189 else: 190 # No need to transform. 191 self.trans_mat = None 192 self.dsize = None 193 194 @property 195 def result_shape(self): 196 return convert_dsize_to_result_shape(self.dsize)
Abstract base class for generic types.
A generic type is typically declared by inheriting from this class parameterized with one or more type variables. For example, a generic mapping type might be defined as::
class Mapping(Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... # Etc.
This class can then be used as follows::
def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default
159 def __init__( 160 self, 161 config: ShearVertConfig, 162 shape: Tuple[int, int], 163 rng: Optional[RandomGenerator], 164 ): 165 tan_abs_phi = math.tan(math.radians(abs(config.angle))) 166 167 height, width = shape 168 shift_y = width * tan_abs_phi 169 self.dsize = (width, math.ceil(height + shift_y)) 170 171 if config.angle < 0: 172 # Shear up. 173 self.trans_mat = np.asarray( 174 [ 175 (1, 0, 0), 176 (-tan_abs_phi, 1, shift_y), 177 ], 178 dtype=np.float32, 179 ) 180 elif config.angle > 0: 181 # Shear down & the negative part. 182 self.trans_mat = np.asarray( 183 [ 184 (1, 0, 0), 185 (tan_abs_phi, 1, 0), 186 ], 187 dtype=np.float32, 188 ) 189 else: 190 # No need to transform. 191 self.trans_mat = None 192 self.dsize = None
200class RotateConfig(DistortionConfig): 201 # angle: int, [0, 360], clockwise angle. 202 angle: int 203 204 @property 205 def is_nop(self): 206 return self.angle == 0
Inherited Members
209class RotateState(DistortionState[RotateConfig]): 210 211 def __init__( 212 self, 213 config: RotateConfig, 214 shape: Tuple[int, int], 215 rng: Optional[RandomGenerator], 216 ): 217 height, width = shape 218 219 angle = config.angle % 360 220 rad = math.radians(angle) 221 222 shift_x = 0 223 shift_y = 0 224 225 if rad <= math.pi / 2: 226 # In 3-4 quadrant. 227 shift_x = height * math.sin(rad) 228 229 dst_width = height * math.sin(rad) + width * math.cos(rad) 230 dst_height = height * math.cos(rad) + width * math.sin(rad) 231 232 elif rad <= math.pi: 233 # In 2-3 quadrant. 234 shift_rad = rad - math.pi / 2 235 236 shift_x = width * math.sin(shift_rad) + height * math.cos(shift_rad) 237 shift_y = height * math.sin(shift_rad) 238 239 dst_width = shift_x 240 dst_height = shift_y + width * math.cos(shift_rad) 241 242 elif rad < math.pi * 3 / 2: 243 # In 1-2 quadrant. 244 shift_rad = rad - math.pi 245 246 shift_x = width * math.cos(shift_rad) 247 shift_y = width * math.sin(shift_rad) + height * math.cos(shift_rad) 248 249 dst_width = shift_x + height * math.sin(shift_rad) 250 dst_height = shift_y 251 252 else: 253 # In 1-4 quadrant. 254 shift_rad = rad - math.pi * 3 / 2 255 256 shift_y = width * math.cos(shift_rad) 257 258 dst_width = width * math.sin(shift_rad) + height * math.cos(shift_rad) 259 dst_height = shift_y + height * math.sin(shift_rad) 260 261 shift_x = math.ceil(shift_x) 262 shift_y = math.ceil(shift_y) 263 264 self.trans_mat = np.asarray( 265 [ 266 (math.cos(rad), -math.sin(rad), shift_x), 267 (math.sin(rad), math.cos(rad), shift_y), 268 ], 269 dtype=np.float32, 270 ) 271 272 self.dsize = (math.ceil(dst_width), math.ceil(dst_height)) 273 274 @property 275 def result_shape(self): 276 return convert_dsize_to_result_shape(self.dsize)
Abstract base class for generic types.
A generic type is typically declared by inheriting from this class parameterized with one or more type variables. For example, a generic mapping type might be defined as::
class Mapping(Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... # Etc.
This class can then be used as follows::
def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default
211 def __init__( 212 self, 213 config: RotateConfig, 214 shape: Tuple[int, int], 215 rng: Optional[RandomGenerator], 216 ): 217 height, width = shape 218 219 angle = config.angle % 360 220 rad = math.radians(angle) 221 222 shift_x = 0 223 shift_y = 0 224 225 if rad <= math.pi / 2: 226 # In 3-4 quadrant. 227 shift_x = height * math.sin(rad) 228 229 dst_width = height * math.sin(rad) + width * math.cos(rad) 230 dst_height = height * math.cos(rad) + width * math.sin(rad) 231 232 elif rad <= math.pi: 233 # In 2-3 quadrant. 234 shift_rad = rad - math.pi / 2 235 236 shift_x = width * math.sin(shift_rad) + height * math.cos(shift_rad) 237 shift_y = height * math.sin(shift_rad) 238 239 dst_width = shift_x 240 dst_height = shift_y + width * math.cos(shift_rad) 241 242 elif rad < math.pi * 3 / 2: 243 # In 1-2 quadrant. 244 shift_rad = rad - math.pi 245 246 shift_x = width * math.cos(shift_rad) 247 shift_y = width * math.sin(shift_rad) + height * math.cos(shift_rad) 248 249 dst_width = shift_x + height * math.sin(shift_rad) 250 dst_height = shift_y 251 252 else: 253 # In 1-4 quadrant. 254 shift_rad = rad - math.pi * 3 / 2 255 256 shift_y = width * math.cos(shift_rad) 257 258 dst_width = width * math.sin(shift_rad) + height * math.cos(shift_rad) 259 dst_height = shift_y + height * math.sin(shift_rad) 260 261 shift_x = math.ceil(shift_x) 262 shift_y = math.ceil(shift_y) 263 264 self.trans_mat = np.asarray( 265 [ 266 (math.cos(rad), -math.sin(rad), shift_x), 267 (math.sin(rad), math.cos(rad), shift_y), 268 ], 269 dtype=np.float32, 270 ) 271 272 self.dsize = (math.ceil(dst_width), math.ceil(dst_height))
280class SkewHoriConfig(DistortionConfig): 281 # (-1.0, 0.0], shrink the left side. 282 # [0.0, 1.0), shrink the right side. 283 # The larger abs(ratio), the more to shrink. 284 ratio: float 285 286 @property 287 def is_nop(self): 288 return self.ratio == 0
Inherited Members
291class SkewHoriState(DistortionState[SkewHoriConfig]): 292 293 def __init__( 294 self, 295 config: SkewHoriConfig, 296 shape: Tuple[int, int], 297 rng: Optional[RandomGenerator], 298 ): 299 height, width = shape 300 301 src_xy_pairs = [ 302 (0, 0), 303 (width - 1, 0), 304 (width - 1, height - 1), 305 (0, height - 1), 306 ] 307 308 shrink_size = round(height * abs(config.ratio)) 309 shrink_up = shrink_size // 2 310 shrink_down = shrink_size - shrink_up 311 312 if config.ratio < 0: 313 dst_xy_pairs = [ 314 (0, shrink_up), 315 (width - 1, 0), 316 (width - 1, height - 1), 317 (0, height - shrink_down - 1), 318 ] 319 else: 320 dst_xy_pairs = [ 321 (0, 0), 322 (width - 1, shrink_up), 323 (width - 1, height - shrink_down - 1), 324 (0, height - 1), 325 ] 326 327 self.trans_mat = cv.getPerspectiveTransform( 328 np.asarray(src_xy_pairs, dtype=np.float32), 329 np.asarray(dst_xy_pairs, dtype=np.float32), 330 cv.DECOMP_SVD, 331 ) 332 self.dsize = (width, height) 333 334 @property 335 def result_shape(self): 336 return convert_dsize_to_result_shape(self.dsize)
Abstract base class for generic types.
A generic type is typically declared by inheriting from this class parameterized with one or more type variables. For example, a generic mapping type might be defined as::
class Mapping(Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... # Etc.
This class can then be used as follows::
def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default
293 def __init__( 294 self, 295 config: SkewHoriConfig, 296 shape: Tuple[int, int], 297 rng: Optional[RandomGenerator], 298 ): 299 height, width = shape 300 301 src_xy_pairs = [ 302 (0, 0), 303 (width - 1, 0), 304 (width - 1, height - 1), 305 (0, height - 1), 306 ] 307 308 shrink_size = round(height * abs(config.ratio)) 309 shrink_up = shrink_size // 2 310 shrink_down = shrink_size - shrink_up 311 312 if config.ratio < 0: 313 dst_xy_pairs = [ 314 (0, shrink_up), 315 (width - 1, 0), 316 (width - 1, height - 1), 317 (0, height - shrink_down - 1), 318 ] 319 else: 320 dst_xy_pairs = [ 321 (0, 0), 322 (width - 1, shrink_up), 323 (width - 1, height - shrink_down - 1), 324 (0, height - 1), 325 ] 326 327 self.trans_mat = cv.getPerspectiveTransform( 328 np.asarray(src_xy_pairs, dtype=np.float32), 329 np.asarray(dst_xy_pairs, dtype=np.float32), 330 cv.DECOMP_SVD, 331 ) 332 self.dsize = (width, height)
340class SkewVertConfig(DistortionConfig): 341 # (-1.0, 0.0], shrink the up side. 342 # [0.0, 1.0), shrink the down side. 343 # The larger abs(ratio), the more to shrink. 344 ratio: float 345 346 @property 347 def is_nop(self): 348 return self.ratio == 0
Inherited Members
351class SkewVertState(DistortionState[SkewVertConfig]): 352 353 def __init__( 354 self, 355 config: SkewVertConfig, 356 shape: Tuple[int, int], 357 rng: Optional[RandomGenerator], 358 ): 359 height, width = shape 360 361 src_xy_pairs = [ 362 (0, 0), 363 (width - 1, 0), 364 (width - 1, height - 1), 365 (0, height - 1), 366 ] 367 368 shrink_size = round(width * abs(config.ratio)) 369 shrink_left = shrink_size // 2 370 shrink_right = shrink_size - shrink_left 371 372 if config.ratio < 0: 373 dst_xy_pairs = [ 374 (shrink_left, 0), 375 (width - shrink_right - 1, 0), 376 (width - 1, height - 1), 377 (0, height - 1), 378 ] 379 else: 380 dst_xy_pairs = [ 381 (0, 0), 382 (width - 1, 0), 383 (width - shrink_right - 1, height - 1), 384 (shrink_right, height - 1), 385 ] 386 387 self.trans_mat = cv.getPerspectiveTransform( 388 np.asarray(src_xy_pairs, dtype=np.float32), 389 np.asarray(dst_xy_pairs, dtype=np.float32), 390 cv.DECOMP_SVD, 391 ) 392 self.dsize = (width, height) 393 394 @property 395 def result_shape(self): 396 return convert_dsize_to_result_shape(self.dsize)
Abstract base class for generic types.
A generic type is typically declared by inheriting from this class parameterized with one or more type variables. For example, a generic mapping type might be defined as::
class Mapping(Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... # Etc.
This class can then be used as follows::
def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default
353 def __init__( 354 self, 355 config: SkewVertConfig, 356 shape: Tuple[int, int], 357 rng: Optional[RandomGenerator], 358 ): 359 height, width = shape 360 361 src_xy_pairs = [ 362 (0, 0), 363 (width - 1, 0), 364 (width - 1, height - 1), 365 (0, height - 1), 366 ] 367 368 shrink_size = round(width * abs(config.ratio)) 369 shrink_left = shrink_size // 2 370 shrink_right = shrink_size - shrink_left 371 372 if config.ratio < 0: 373 dst_xy_pairs = [ 374 (shrink_left, 0), 375 (width - shrink_right - 1, 0), 376 (width - 1, height - 1), 377 (0, height - 1), 378 ] 379 else: 380 dst_xy_pairs = [ 381 (0, 0), 382 (width - 1, 0), 383 (width - shrink_right - 1, height - 1), 384 (shrink_right, height - 1), 385 ] 386 387 self.trans_mat = cv.getPerspectiveTransform( 388 np.asarray(src_xy_pairs, dtype=np.float32), 389 np.asarray(dst_xy_pairs, dtype=np.float32), 390 cv.DECOMP_SVD, 391 ) 392 self.dsize = (width, height)
417def affine_trait_func_mat( 418 config: _T_AFFINE_CONFIG, 419 state: Optional[_T_AFFINE_STATE], 420 mat: np.ndarray, 421): 422 assert state 423 if config.is_nop: 424 return mat 425 else: 426 assert state.trans_mat is not None 427 assert state.dsize is not None 428 return affine_mat(state.trans_mat, state.dsize, mat)
460def affine_trait_func_points( 461 config: _T_AFFINE_CONFIG, 462 state: Optional[_T_AFFINE_STATE], 463 shape: Tuple[int, int], 464 points: Union[PointList, PointTuple, Iterable[Point]], 465 rng: Optional[RandomGenerator], 466): 467 assert state 468 points = PointTuple(points) 469 if config.is_nop: 470 return points 471 else: 472 assert state.trans_mat is not None 473 return affine_points(state.trans_mat, points)
476def affine_trait_func_polygons( 477 config: _T_AFFINE_CONFIG, 478 state: Optional[_T_AFFINE_STATE], 479 shape: Tuple[int, int], 480 polygons: Iterable[Polygon], 481 rng: Optional[RandomGenerator], 482): 483 assert state 484 polygons = tuple(polygons) 485 if config.is_nop: 486 return polygons 487 else: 488 assert state.trans_mat is not None 489 return affine_polygons(state.trans_mat, polygons)
492class DistortionAffine(Distortion[_T_AFFINE_CONFIG, _T_AFFINE_STATE]): 493 494 def __init__( 495 self, 496 config_cls: Type[_T_AFFINE_CONFIG], 497 state_cls: Type[_T_AFFINE_STATE], 498 ): 499 super().__init__( 500 config_cls=config_cls, 501 state_cls=state_cls, 502 func_image=affine_trait_func_image, 503 func_mask=affine_trait_func_mask, 504 func_score_map=affine_trait_func_score_map, 505 func_points=affine_trait_func_points, 506 func_polygons=affine_trait_func_polygons, 507 )
Abstract base class for generic types.
A generic type is typically declared by inheriting from this class parameterized with one or more type variables. For example, a generic mapping type might be defined as::
class Mapping(Generic[KT, VT]): def __getitem__(self, key: KT) -> VT: ... # Etc.
This class can then be used as follows::
def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT: try: return mapping[key] except KeyError: return default
494 def __init__( 495 self, 496 config_cls: Type[_T_AFFINE_CONFIG], 497 state_cls: Type[_T_AFFINE_STATE], 498 ): 499 super().__init__( 500 config_cls=config_cls, 501 state_cls=state_cls, 502 func_image=affine_trait_func_image, 503 func_mask=affine_trait_func_mask, 504 func_score_map=affine_trait_func_score_map, 505 func_points=affine_trait_func_points, 506 func_polygons=affine_trait_func_polygons, 507 )
Inherited Members
- vkit.mechanism.distortion.interface.Distortion
- prepare_config_and_rng
- get_shape_from_shapable_or_shape
- prepare_internals
- generate_config_and_state
- generate_config
- generate_state
- distort_image_based_on_internals
- distort_image
- distort_score_map_based_on_internals
- distort_score_map
- distort_mask_based_on_internals
- distort_mask
- get_active_mask_based_on_internals
- get_active_mask
- distort_point_based_on_internals
- distort_point
- distort_points_based_on_internals
- distort_points
- distort_polygon_based_on_internals
- distort_polygon
- distort_polygons_based_on_internals
- distort_polygons
- get_shape
- clip_result_elements
- distort