vkit.element.point
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, Tuple, Union, List, Sequence, Iterable 15from itertools import chain 16 17import attrs 18import numpy as np 19 20from .type import Shapable 21from .opt import ( 22 extract_shape_from_shapable_or_shape, 23 clip_val, 24 resize_val, 25 generate_shape_and_resized_shape, 26) 27 28_T = Union[float, str] 29 30 31@attrs.define(frozen=True) 32class Point: 33 # Smooth positioning is crucial for geometric distortion. 34 # 35 # NOTE: Setting `eq=False` to avoid comparing the float fields directly. 36 # In order words, `point0 == point1` checks only `y` and `x` fields. 37 smooth_y: float = attrs.field(eq=False) 38 smooth_x: float = attrs.field(eq=False) 39 40 # NOTE: Setting `hash=False` is necessary since this class is frozen 41 # and these fields will be set in `__attrs_post_init__`. 42 y: int = attrs.field(init=False, hash=False) 43 x: int = attrs.field(init=False, hash=False) 44 45 def __attrs_post_init__(self): 46 object.__setattr__(self, 'y', round(self.smooth_y)) 47 object.__setattr__(self, 'x', round(self.smooth_x)) 48 49 ############### 50 # Constructor # 51 ############### 52 @classmethod 53 def create(cls, y: _T, x: _T): 54 return cls(smooth_y=float(y), smooth_x=float(x)) 55 56 ############## 57 # Conversion # 58 ############## 59 @classmethod 60 def from_xy_pair(cls, xy_pair: Tuple[_T, _T]): 61 x, y = xy_pair 62 return cls.create(y=y, x=x) 63 64 def to_xy_pair(self): 65 return (self.x, self.y) 66 67 def to_smooth_xy_pair(self): 68 return (self.smooth_x, self.smooth_y) 69 70 ############ 71 # Operator # 72 ############ 73 def to_clipped_point(self, shapable_or_shape: Union[Shapable, Tuple[int, int]]): 74 height, width = extract_shape_from_shapable_or_shape(shapable_or_shape) 75 if 0 <= self.y < height and 0 <= self.x < width: 76 return self 77 else: 78 return Point.create( 79 y=clip_val(self.smooth_y, height), 80 x=clip_val(self.smooth_x, width), 81 ) 82 83 def to_shifted_point(self, offset_y: int = 0, offset_x: int = 0): 84 return Point.create( 85 y=self.smooth_y + offset_y, 86 x=self.smooth_x + offset_x, 87 ) 88 89 def to_conducted_resized_point( 90 self, 91 shapable_or_shape: Union[Shapable, Tuple[int, int]], 92 resized_height: Optional[int] = None, 93 resized_width: Optional[int] = None, 94 ): 95 ( 96 height, 97 width, 98 resized_height, 99 resized_width, 100 ) = generate_shape_and_resized_shape( 101 shapable_or_shape=shapable_or_shape, 102 resized_height=resized_height, 103 resized_width=resized_width 104 ) 105 return Point.create( 106 y=resize_val(self.smooth_y, height, resized_height), 107 x=resize_val(self.smooth_x, width, resized_width), 108 ) 109 110 111class PointList(List[Point]): 112 113 ############### 114 # Constructor # 115 ############### 116 @classmethod 117 def from_point(cls, point: Point): 118 return cls((point,)) 119 120 ############## 121 # Conversion # 122 ############## 123 @classmethod 124 def from_xy_pairs(cls, xy_pairs: Iterable[Tuple[_T, _T]]): 125 return cls(Point.from_xy_pair(xy_pair) for xy_pair in xy_pairs) 126 127 def to_xy_pairs(self): 128 return [point.to_xy_pair() for point in self] 129 130 def to_smooth_xy_pairs(self): 131 return [point.to_smooth_xy_pair() for point in self] 132 133 @classmethod 134 def from_flatten_xy_pairs(cls, flatten_xy_pairs: Sequence[_T]): 135 # [x0, y0, x1, y1, ...] 136 flatten_xy_pairs = tuple(flatten_xy_pairs) 137 assert flatten_xy_pairs and len(flatten_xy_pairs) % 2 == 0 138 139 points = PointList() 140 idx = 0 141 while idx < len(flatten_xy_pairs): 142 x = flatten_xy_pairs[idx] 143 y = flatten_xy_pairs[idx + 1] 144 points.append(Point.create(y=y, x=x)) 145 idx += 2 146 147 return points 148 149 def to_flatten_xy_pairs(self): 150 return list(chain.from_iterable(point.to_xy_pair() for point in self)) 151 152 def to_smooth_flatten_xy_pairs(self): 153 return list(chain.from_iterable(point.to_smooth_xy_pair() for point in self)) 154 155 @classmethod 156 def from_np_array(cls, np_points: np.ndarray): 157 points = PointList() 158 for np_point in np_points: 159 x, y = np_point 160 points.append(Point.create(y=y, x=x)) 161 162 if len(points) > 2 and points[0] == points[-1]: 163 # Handle the circled duplicated points generated by package like shapely. 164 points.pop() 165 166 return points 167 168 def to_np_array(self): 169 return np.asarray(self.to_xy_pairs(), dtype=np.int32) 170 171 def to_smooth_np_array(self): 172 return np.asarray(self.to_smooth_xy_pairs(), dtype=np.float32) 173 174 def to_point_tuple(self): 175 return PointTuple(self) 176 177 ############ 178 # Operator # 179 ############ 180 def copy(self): 181 return PointList(self) 182 183 def to_clipped_points(self, shapable_or_shape: Union[Shapable, Tuple[int, int]]): 184 return PointList(point.to_clipped_point(shapable_or_shape) for point in self) 185 186 def to_shifted_points(self, offset_y: int = 0, offset_x: int = 0): 187 return PointList( 188 point.to_shifted_point( 189 offset_y=offset_y, 190 offset_x=offset_x, 191 ) for point in self 192 ) 193 194 def to_relative_points(self, origin_y: int, origin_x: int): 195 return self.to_shifted_points(offset_y=-origin_y, offset_x=-origin_x) 196 197 def to_conducted_resized_points( 198 self, 199 shapable_or_shape: Union[Shapable, Tuple[int, int]], 200 resized_height: Optional[int] = None, 201 resized_width: Optional[int] = None, 202 ): 203 return PointList( 204 point.to_conducted_resized_point( 205 shapable_or_shape=shapable_or_shape, 206 resized_height=resized_height, 207 resized_width=resized_width, 208 ) for point in self 209 ) 210 211 212class PointTuple(Tuple[Point, ...]): 213 214 ############### 215 # Constructor # 216 ############### 217 @classmethod 218 def from_point(cls, point: Point): 219 return cls((point,)) 220 221 ############## 222 # Conversion # 223 ############## 224 @classmethod 225 def from_xy_pairs(cls, xy_pairs: Iterable[Tuple[_T, _T]]): 226 return PointTuple(Point.from_xy_pair(xy_pair) for xy_pair in xy_pairs) 227 228 def to_xy_pairs(self): 229 return tuple(point.to_xy_pair() for point in self) 230 231 def to_smooth_xy_pairs(self): 232 return tuple(point.to_smooth_xy_pair() for point in self) 233 234 @classmethod 235 def from_flatten_xy_pairs(cls, flatten_xy_pairs: Sequence[_T]): 236 return PointList.from_flatten_xy_pairs(flatten_xy_pairs).to_point_tuple() 237 238 def to_flatten_xy_pairs(self): 239 return tuple(chain.from_iterable(point.to_xy_pair() for point in self)) 240 241 def to_smooth_flatten_xy_pairs(self): 242 return tuple(chain.from_iterable(point.to_smooth_xy_pair() for point in self)) 243 244 @classmethod 245 def from_np_array(cls, np_points: np.ndarray): 246 return PointList.from_np_array(np_points).to_point_tuple() 247 248 def to_np_array(self): 249 return np.asarray(self.to_xy_pairs(), dtype=np.int32) 250 251 def to_smooth_np_array(self): 252 return np.asarray(self.to_xy_pairs(), dtype=np.float32) 253 254 ############ 255 # Operator # 256 ############ 257 def to_clipped_points(self, shapable_or_shape: Union[Shapable, Tuple[int, int]]): 258 return PointTuple(point.to_clipped_point(shapable_or_shape) for point in self) 259 260 def to_shifted_points(self, offset_y: int = 0, offset_x: int = 0): 261 return PointTuple( 262 point.to_shifted_point( 263 offset_y=offset_y, 264 offset_x=offset_x, 265 ) for point in self 266 ) 267 268 def to_relative_points(self, origin_y: int, origin_x: int): 269 return self.to_shifted_points(offset_y=-origin_y, offset_x=-origin_x) 270 271 def to_conducted_resized_points( 272 self, 273 shapable_or_shape: Union[Shapable, Tuple[int, int]], 274 resized_height: Optional[int] = None, 275 resized_width: Optional[int] = None, 276 ): 277 return PointTuple( 278 point.to_conducted_resized_point( 279 shapable_or_shape=shapable_or_shape, 280 resized_height=resized_height, 281 resized_width=resized_width, 282 ) for point in self 283 )
class
Point:
33class Point: 34 # Smooth positioning is crucial for geometric distortion. 35 # 36 # NOTE: Setting `eq=False` to avoid comparing the float fields directly. 37 # In order words, `point0 == point1` checks only `y` and `x` fields. 38 smooth_y: float = attrs.field(eq=False) 39 smooth_x: float = attrs.field(eq=False) 40 41 # NOTE: Setting `hash=False` is necessary since this class is frozen 42 # and these fields will be set in `__attrs_post_init__`. 43 y: int = attrs.field(init=False, hash=False) 44 x: int = attrs.field(init=False, hash=False) 45 46 def __attrs_post_init__(self): 47 object.__setattr__(self, 'y', round(self.smooth_y)) 48 object.__setattr__(self, 'x', round(self.smooth_x)) 49 50 ############### 51 # Constructor # 52 ############### 53 @classmethod 54 def create(cls, y: _T, x: _T): 55 return cls(smooth_y=float(y), smooth_x=float(x)) 56 57 ############## 58 # Conversion # 59 ############## 60 @classmethod 61 def from_xy_pair(cls, xy_pair: Tuple[_T, _T]): 62 x, y = xy_pair 63 return cls.create(y=y, x=x) 64 65 def to_xy_pair(self): 66 return (self.x, self.y) 67 68 def to_smooth_xy_pair(self): 69 return (self.smooth_x, self.smooth_y) 70 71 ############ 72 # Operator # 73 ############ 74 def to_clipped_point(self, shapable_or_shape: Union[Shapable, Tuple[int, int]]): 75 height, width = extract_shape_from_shapable_or_shape(shapable_or_shape) 76 if 0 <= self.y < height and 0 <= self.x < width: 77 return self 78 else: 79 return Point.create( 80 y=clip_val(self.smooth_y, height), 81 x=clip_val(self.smooth_x, width), 82 ) 83 84 def to_shifted_point(self, offset_y: int = 0, offset_x: int = 0): 85 return Point.create( 86 y=self.smooth_y + offset_y, 87 x=self.smooth_x + offset_x, 88 ) 89 90 def to_conducted_resized_point( 91 self, 92 shapable_or_shape: Union[Shapable, Tuple[int, int]], 93 resized_height: Optional[int] = None, 94 resized_width: Optional[int] = None, 95 ): 96 ( 97 height, 98 width, 99 resized_height, 100 resized_width, 101 ) = generate_shape_and_resized_shape( 102 shapable_or_shape=shapable_or_shape, 103 resized_height=resized_height, 104 resized_width=resized_width 105 ) 106 return Point.create( 107 y=resize_val(self.smooth_y, height, resized_height), 108 x=resize_val(self.smooth_x, width, resized_width), 109 )
Point(smooth_y: float, smooth_x: float)
2def __init__(self, smooth_y, smooth_x): 3 _setattr = _cached_setattr_get(self) 4 _setattr('smooth_y', smooth_y) 5 _setattr('smooth_x', smooth_x) 6 self.__attrs_post_init__()
Method generated by attrs for class Point.
74 def to_clipped_point(self, shapable_or_shape: Union[Shapable, Tuple[int, int]]): 75 height, width = extract_shape_from_shapable_or_shape(shapable_or_shape) 76 if 0 <= self.y < height and 0 <= self.x < width: 77 return self 78 else: 79 return Point.create( 80 y=clip_val(self.smooth_y, height), 81 x=clip_val(self.smooth_x, width), 82 )
def
to_conducted_resized_point( self, shapable_or_shape: Union[vkit.element.type.Shapable, Tuple[int, int]], resized_height: Union[int, NoneType] = None, resized_width: Union[int, NoneType] = None):
90 def to_conducted_resized_point( 91 self, 92 shapable_or_shape: Union[Shapable, Tuple[int, int]], 93 resized_height: Optional[int] = None, 94 resized_width: Optional[int] = None, 95 ): 96 ( 97 height, 98 width, 99 resized_height, 100 resized_width, 101 ) = generate_shape_and_resized_shape( 102 shapable_or_shape=shapable_or_shape, 103 resized_height=resized_height, 104 resized_width=resized_width 105 ) 106 return Point.create( 107 y=resize_val(self.smooth_y, height, resized_height), 108 x=resize_val(self.smooth_x, width, resized_width), 109 )
class
PointList(typing.List[vkit.element.point.Point]):
112class PointList(List[Point]): 113 114 ############### 115 # Constructor # 116 ############### 117 @classmethod 118 def from_point(cls, point: Point): 119 return cls((point,)) 120 121 ############## 122 # Conversion # 123 ############## 124 @classmethod 125 def from_xy_pairs(cls, xy_pairs: Iterable[Tuple[_T, _T]]): 126 return cls(Point.from_xy_pair(xy_pair) for xy_pair in xy_pairs) 127 128 def to_xy_pairs(self): 129 return [point.to_xy_pair() for point in self] 130 131 def to_smooth_xy_pairs(self): 132 return [point.to_smooth_xy_pair() for point in self] 133 134 @classmethod 135 def from_flatten_xy_pairs(cls, flatten_xy_pairs: Sequence[_T]): 136 # [x0, y0, x1, y1, ...] 137 flatten_xy_pairs = tuple(flatten_xy_pairs) 138 assert flatten_xy_pairs and len(flatten_xy_pairs) % 2 == 0 139 140 points = PointList() 141 idx = 0 142 while idx < len(flatten_xy_pairs): 143 x = flatten_xy_pairs[idx] 144 y = flatten_xy_pairs[idx + 1] 145 points.append(Point.create(y=y, x=x)) 146 idx += 2 147 148 return points 149 150 def to_flatten_xy_pairs(self): 151 return list(chain.from_iterable(point.to_xy_pair() for point in self)) 152 153 def to_smooth_flatten_xy_pairs(self): 154 return list(chain.from_iterable(point.to_smooth_xy_pair() for point in self)) 155 156 @classmethod 157 def from_np_array(cls, np_points: np.ndarray): 158 points = PointList() 159 for np_point in np_points: 160 x, y = np_point 161 points.append(Point.create(y=y, x=x)) 162 163 if len(points) > 2 and points[0] == points[-1]: 164 # Handle the circled duplicated points generated by package like shapely. 165 points.pop() 166 167 return points 168 169 def to_np_array(self): 170 return np.asarray(self.to_xy_pairs(), dtype=np.int32) 171 172 def to_smooth_np_array(self): 173 return np.asarray(self.to_smooth_xy_pairs(), dtype=np.float32) 174 175 def to_point_tuple(self): 176 return PointTuple(self) 177 178 ############ 179 # Operator # 180 ############ 181 def copy(self): 182 return PointList(self) 183 184 def to_clipped_points(self, shapable_or_shape: Union[Shapable, Tuple[int, int]]): 185 return PointList(point.to_clipped_point(shapable_or_shape) for point in self) 186 187 def to_shifted_points(self, offset_y: int = 0, offset_x: int = 0): 188 return PointList( 189 point.to_shifted_point( 190 offset_y=offset_y, 191 offset_x=offset_x, 192 ) for point in self 193 ) 194 195 def to_relative_points(self, origin_y: int, origin_x: int): 196 return self.to_shifted_points(offset_y=-origin_y, offset_x=-origin_x) 197 198 def to_conducted_resized_points( 199 self, 200 shapable_or_shape: Union[Shapable, Tuple[int, int]], 201 resized_height: Optional[int] = None, 202 resized_width: Optional[int] = None, 203 ): 204 return PointList( 205 point.to_conducted_resized_point( 206 shapable_or_shape=shapable_or_shape, 207 resized_height=resized_height, 208 resized_width=resized_width, 209 ) for point in self 210 )
Built-in mutable sequence.
If no argument is given, the constructor creates a new empty list. The argument must be an iterable if specified.
@classmethod
def
from_xy_pairs(cls, xy_pairs: Iterable[Tuple[Union[float, str], Union[float, str]]]):
@classmethod
def
from_flatten_xy_pairs(cls, flatten_xy_pairs: Sequence[Union[float, str]]):
134 @classmethod 135 def from_flatten_xy_pairs(cls, flatten_xy_pairs: Sequence[_T]): 136 # [x0, y0, x1, y1, ...] 137 flatten_xy_pairs = tuple(flatten_xy_pairs) 138 assert flatten_xy_pairs and len(flatten_xy_pairs) % 2 == 0 139 140 points = PointList() 141 idx = 0 142 while idx < len(flatten_xy_pairs): 143 x = flatten_xy_pairs[idx] 144 y = flatten_xy_pairs[idx + 1] 145 points.append(Point.create(y=y, x=x)) 146 idx += 2 147 148 return points
@classmethod
def
from_np_array(cls, np_points: numpy.ndarray):
156 @classmethod 157 def from_np_array(cls, np_points: np.ndarray): 158 points = PointList() 159 for np_point in np_points: 160 x, y = np_point 161 points.append(Point.create(y=y, x=x)) 162 163 if len(points) > 2 and points[0] == points[-1]: 164 # Handle the circled duplicated points generated by package like shapely. 165 points.pop() 166 167 return points
def
to_clipped_points( self, shapable_or_shape: Union[vkit.element.type.Shapable, Tuple[int, int]]):
def
to_conducted_resized_points( self, shapable_or_shape: Union[vkit.element.type.Shapable, Tuple[int, int]], resized_height: Union[int, NoneType] = None, resized_width: Union[int, NoneType] = None):
198 def to_conducted_resized_points( 199 self, 200 shapable_or_shape: Union[Shapable, Tuple[int, int]], 201 resized_height: Optional[int] = None, 202 resized_width: Optional[int] = None, 203 ): 204 return PointList( 205 point.to_conducted_resized_point( 206 shapable_or_shape=shapable_or_shape, 207 resized_height=resized_height, 208 resized_width=resized_width, 209 ) for point in self 210 )
Inherited Members
- builtins.list
- list
- clear
- append
- insert
- extend
- pop
- remove
- index
- count
- reverse
- sort
class
PointTuple(typing.Tuple[vkit.element.point.Point, ...]):
213class PointTuple(Tuple[Point, ...]): 214 215 ############### 216 # Constructor # 217 ############### 218 @classmethod 219 def from_point(cls, point: Point): 220 return cls((point,)) 221 222 ############## 223 # Conversion # 224 ############## 225 @classmethod 226 def from_xy_pairs(cls, xy_pairs: Iterable[Tuple[_T, _T]]): 227 return PointTuple(Point.from_xy_pair(xy_pair) for xy_pair in xy_pairs) 228 229 def to_xy_pairs(self): 230 return tuple(point.to_xy_pair() for point in self) 231 232 def to_smooth_xy_pairs(self): 233 return tuple(point.to_smooth_xy_pair() for point in self) 234 235 @classmethod 236 def from_flatten_xy_pairs(cls, flatten_xy_pairs: Sequence[_T]): 237 return PointList.from_flatten_xy_pairs(flatten_xy_pairs).to_point_tuple() 238 239 def to_flatten_xy_pairs(self): 240 return tuple(chain.from_iterable(point.to_xy_pair() for point in self)) 241 242 def to_smooth_flatten_xy_pairs(self): 243 return tuple(chain.from_iterable(point.to_smooth_xy_pair() for point in self)) 244 245 @classmethod 246 def from_np_array(cls, np_points: np.ndarray): 247 return PointList.from_np_array(np_points).to_point_tuple() 248 249 def to_np_array(self): 250 return np.asarray(self.to_xy_pairs(), dtype=np.int32) 251 252 def to_smooth_np_array(self): 253 return np.asarray(self.to_xy_pairs(), dtype=np.float32) 254 255 ############ 256 # Operator # 257 ############ 258 def to_clipped_points(self, shapable_or_shape: Union[Shapable, Tuple[int, int]]): 259 return PointTuple(point.to_clipped_point(shapable_or_shape) for point in self) 260 261 def to_shifted_points(self, offset_y: int = 0, offset_x: int = 0): 262 return PointTuple( 263 point.to_shifted_point( 264 offset_y=offset_y, 265 offset_x=offset_x, 266 ) for point in self 267 ) 268 269 def to_relative_points(self, origin_y: int, origin_x: int): 270 return self.to_shifted_points(offset_y=-origin_y, offset_x=-origin_x) 271 272 def to_conducted_resized_points( 273 self, 274 shapable_or_shape: Union[Shapable, Tuple[int, int]], 275 resized_height: Optional[int] = None, 276 resized_width: Optional[int] = None, 277 ): 278 return PointTuple( 279 point.to_conducted_resized_point( 280 shapable_or_shape=shapable_or_shape, 281 resized_height=resized_height, 282 resized_width=resized_width, 283 ) for point in self 284 )
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable's items.
If the argument is a tuple, the return value is the same object.
@classmethod
def
from_xy_pairs(cls, xy_pairs: Iterable[Tuple[Union[float, str], Union[float, str]]]):
def
to_clipped_points( self, shapable_or_shape: Union[vkit.element.type.Shapable, Tuple[int, int]]):
def
to_conducted_resized_points( self, shapable_or_shape: Union[vkit.element.type.Shapable, Tuple[int, int]], resized_height: Union[int, NoneType] = None, resized_width: Union[int, NoneType] = None):
272 def to_conducted_resized_points( 273 self, 274 shapable_or_shape: Union[Shapable, Tuple[int, int]], 275 resized_height: Optional[int] = None, 276 resized_width: Optional[int] = None, 277 ): 278 return PointTuple( 279 point.to_conducted_resized_point( 280 shapable_or_shape=shapable_or_shape, 281 resized_height=resized_height, 282 resized_width=resized_width, 283 ) for point in self 284 )
Inherited Members
- builtins.tuple
- index
- count