vkit.element.opt
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 Union, Tuple, Optional, TypeVar 15 16import numpy as np 17 18from .type import Shapable 19 20_T = TypeVar('_T', float, int) 21 22 23def clip_val(val: _T, size: int) -> _T: 24 # Hack to work. 25 return max(0, min(val, size - 1)) # type: ignore 26 27 28def resize_val(val: _T, size: int, resized_size: int): 29 return clip_val(val * resized_size / size, resized_size) 30 31 32def extract_shape_from_shapable_or_shape(shapable_or_shape: Union[Shapable, Tuple[int, int]],): 33 if isinstance(shapable_or_shape, Shapable): 34 shapable = shapable_or_shape 35 height, width = shapable.shape 36 else: 37 height, width = shapable_or_shape 38 return height, width 39 40 41def generate_resized_shape( 42 height: int, 43 width: int, 44 resized_height: Optional[int] = None, 45 resized_width: Optional[int] = None, 46): 47 if not resized_height and not resized_width: 48 raise RuntimeError('Missing resized_height or resized_width.') 49 50 if resized_height is None: 51 assert resized_width 52 resized_height = round(resized_width * height / width) 53 54 if resized_width is None: 55 assert resized_height 56 resized_width = round(resized_height * width / height) 57 58 return resized_height, resized_width 59 60 61def generate_shape_and_resized_shape( 62 shapable_or_shape: Union[Shapable, Tuple[int, int]], 63 resized_height: Optional[int] = None, 64 resized_width: Optional[int] = None, 65): 66 height, width = extract_shape_from_shapable_or_shape(shapable_or_shape) 67 resized_height, resized_width = generate_resized_shape( 68 height=height, 69 width=width, 70 resized_height=resized_height, 71 resized_width=resized_width, 72 ) 73 return ( 74 height, 75 width, 76 resized_height, 77 resized_width, 78 ) 79 80 81def expand_np_mask(mat: np.ndarray, np_mask: np.ndarray): 82 if mat.ndim == 2: 83 # Do nothing. 84 pass 85 86 elif mat.ndim == 3: 87 num_channels = mat.shape[2] 88 np_mask = np.repeat(np.expand_dims(np_mask, axis=-1), num_channels, axis=-1) 89 90 else: 91 raise NotImplementedError() 92 93 return np_mask 94 95 96def prep_value( 97 mat: np.ndarray, 98 value: Union[np.ndarray, Tuple[float, ...], float], 99): 100 if not isinstance(value, np.ndarray): 101 if mat.ndim == 3: 102 num_channels = mat.shape[2] 103 if isinstance(value, tuple) and len(value) != num_channels: 104 raise RuntimeError('value is tuple but len(value) != num_channels.') 105 106 value = np.full_like(mat, value) 107 108 else: 109 if mat.shape != value.shape: 110 raise RuntimeError('value is np.ndarray but shape is not matched.') 111 112 if value.dtype != mat.dtype: 113 value = value.astype(mat.dtype) 114 115 return value 116 117 118def fill_np_array( 119 mat: np.ndarray, 120 value: Union[np.ndarray, Tuple[float, ...], float], 121 np_mask: Optional[np.ndarray] = None, 122 alpha: Union[np.ndarray, float] = 1.0, 123 keep_max_value: bool = False, 124 keep_min_value: bool = False, 125): 126 if not isinstance(value, np.ndarray) \ 127 and np_mask is not None \ 128 and isinstance(alpha, float) \ 129 and alpha == 1.0 \ 130 and not keep_max_value \ 131 and not keep_min_value: 132 # Most common case, hence optimize the performance here. 133 mat[np_mask] = value 134 return 135 136 mat_origin = mat 137 np_value = prep_value(mat, value) 138 139 if np_mask is not None: 140 # NOTE: Boolean indexing makes a copy. 141 mat = mat[np_mask] 142 np_value = np_value[np_mask] 143 if isinstance(alpha, np.ndarray): 144 alpha = alpha[np_mask] 145 146 if isinstance(alpha, float): 147 if alpha < 0.0 or alpha > 1.0: 148 raise RuntimeError(f'alpha={alpha} is invalid.') 149 150 elif alpha == 0.0: 151 return 152 153 elif alpha == 1.0: 154 if np_mask is None and not (keep_max_value or keep_min_value): 155 np.copyto(mat_origin, np_value) 156 157 else: 158 if keep_max_value or keep_min_value: 159 assert not (keep_max_value and keep_min_value) 160 if keep_max_value: 161 np_to_value_mask = (mat < np_value) 162 else: 163 np_to_value_mask = (mat > np_value) 164 np.putmask(mat, np_to_value_mask, np_value) 165 else: 166 mat = np_value 167 168 if np_mask is not None: 169 mat_origin[np_mask] = mat 170 171 else: 172 # 0 < alpha < 1. 173 np_value_weight = np.full( 174 (mat.shape[0], mat.shape[1]), 175 alpha, 176 dtype=np.float32, 177 ) 178 if np_value_weight.shape != mat.shape: 179 assert np_value_weight.ndim + 1 == mat.ndim 180 np_value_weight = np.expand_dims(np_value_weight, -1) 181 182 np_mat_weight = 1 - np_value_weight 183 np_weighted_sum = ( 184 np_mat_weight * mat.astype(np.float32) 185 + np_value_weight * np_value.astype(np.float32) 186 ) 187 np_weighted_sum = np_weighted_sum.astype(mat.dtype) 188 189 if np_mask is not None: 190 mat_origin[np_mask] = np_weighted_sum 191 else: 192 np.copyto(mat_origin, np_weighted_sum) 193 194 else: 195 np_value_weight = alpha 196 if np_value_weight.shape != mat.shape: 197 assert np_value_weight.ndim + 1 == mat.ndim 198 np_value_weight = np.expand_dims(np_value_weight, -1) 199 200 np_mat_weight = 1 - np_value_weight 201 np_weighted_sum = ( 202 np_mat_weight * mat.astype(np.float32) + np_value_weight * np_value.astype(np.float32) 203 ) 204 np_weighted_sum = np_weighted_sum.astype(mat.dtype) 205 206 if np_mask is not None: 207 mat_origin[np_mask] = np_weighted_sum 208 else: 209 np.copyto(mat_origin, np_weighted_sum)
def
clip_val(val: ~_T, size: int) -> ~_T:
def
resize_val(val: ~_T, size: int, resized_size: int):
def
extract_shape_from_shapable_or_shape( shapable_or_shape: Union[vkit.element.type.Shapable, Tuple[int, int]]):
def
generate_resized_shape( height: int, width: int, resized_height: Union[int, NoneType] = None, resized_width: Union[int, NoneType] = None):
42def generate_resized_shape( 43 height: int, 44 width: int, 45 resized_height: Optional[int] = None, 46 resized_width: Optional[int] = None, 47): 48 if not resized_height and not resized_width: 49 raise RuntimeError('Missing resized_height or resized_width.') 50 51 if resized_height is None: 52 assert resized_width 53 resized_height = round(resized_width * height / width) 54 55 if resized_width is None: 56 assert resized_height 57 resized_width = round(resized_height * width / height) 58 59 return resized_height, resized_width
def
generate_shape_and_resized_shape( shapable_or_shape: Union[vkit.element.type.Shapable, Tuple[int, int]], resized_height: Union[int, NoneType] = None, resized_width: Union[int, NoneType] = None):
62def generate_shape_and_resized_shape( 63 shapable_or_shape: Union[Shapable, Tuple[int, int]], 64 resized_height: Optional[int] = None, 65 resized_width: Optional[int] = None, 66): 67 height, width = extract_shape_from_shapable_or_shape(shapable_or_shape) 68 resized_height, resized_width = generate_resized_shape( 69 height=height, 70 width=width, 71 resized_height=resized_height, 72 resized_width=resized_width, 73 ) 74 return ( 75 height, 76 width, 77 resized_height, 78 resized_width, 79 )
def
expand_np_mask(mat: numpy.ndarray, np_mask: numpy.ndarray):
82def expand_np_mask(mat: np.ndarray, np_mask: np.ndarray): 83 if mat.ndim == 2: 84 # Do nothing. 85 pass 86 87 elif mat.ndim == 3: 88 num_channels = mat.shape[2] 89 np_mask = np.repeat(np.expand_dims(np_mask, axis=-1), num_channels, axis=-1) 90 91 else: 92 raise NotImplementedError() 93 94 return np_mask
def
prep_value( mat: numpy.ndarray, value: Union[numpy.ndarray, Tuple[float, ...], float]):
97def prep_value( 98 mat: np.ndarray, 99 value: Union[np.ndarray, Tuple[float, ...], float], 100): 101 if not isinstance(value, np.ndarray): 102 if mat.ndim == 3: 103 num_channels = mat.shape[2] 104 if isinstance(value, tuple) and len(value) != num_channels: 105 raise RuntimeError('value is tuple but len(value) != num_channels.') 106 107 value = np.full_like(mat, value) 108 109 else: 110 if mat.shape != value.shape: 111 raise RuntimeError('value is np.ndarray but shape is not matched.') 112 113 if value.dtype != mat.dtype: 114 value = value.astype(mat.dtype) 115 116 return value
def
fill_np_array( mat: numpy.ndarray, value: Union[numpy.ndarray, Tuple[float, ...], float], np_mask: Union[numpy.ndarray, NoneType] = None, alpha: Union[numpy.ndarray, float] = 1.0, keep_max_value: bool = False, keep_min_value: bool = False):
119def fill_np_array( 120 mat: np.ndarray, 121 value: Union[np.ndarray, Tuple[float, ...], float], 122 np_mask: Optional[np.ndarray] = None, 123 alpha: Union[np.ndarray, float] = 1.0, 124 keep_max_value: bool = False, 125 keep_min_value: bool = False, 126): 127 if not isinstance(value, np.ndarray) \ 128 and np_mask is not None \ 129 and isinstance(alpha, float) \ 130 and alpha == 1.0 \ 131 and not keep_max_value \ 132 and not keep_min_value: 133 # Most common case, hence optimize the performance here. 134 mat[np_mask] = value 135 return 136 137 mat_origin = mat 138 np_value = prep_value(mat, value) 139 140 if np_mask is not None: 141 # NOTE: Boolean indexing makes a copy. 142 mat = mat[np_mask] 143 np_value = np_value[np_mask] 144 if isinstance(alpha, np.ndarray): 145 alpha = alpha[np_mask] 146 147 if isinstance(alpha, float): 148 if alpha < 0.0 or alpha > 1.0: 149 raise RuntimeError(f'alpha={alpha} is invalid.') 150 151 elif alpha == 0.0: 152 return 153 154 elif alpha == 1.0: 155 if np_mask is None and not (keep_max_value or keep_min_value): 156 np.copyto(mat_origin, np_value) 157 158 else: 159 if keep_max_value or keep_min_value: 160 assert not (keep_max_value and keep_min_value) 161 if keep_max_value: 162 np_to_value_mask = (mat < np_value) 163 else: 164 np_to_value_mask = (mat > np_value) 165 np.putmask(mat, np_to_value_mask, np_value) 166 else: 167 mat = np_value 168 169 if np_mask is not None: 170 mat_origin[np_mask] = mat 171 172 else: 173 # 0 < alpha < 1. 174 np_value_weight = np.full( 175 (mat.shape[0], mat.shape[1]), 176 alpha, 177 dtype=np.float32, 178 ) 179 if np_value_weight.shape != mat.shape: 180 assert np_value_weight.ndim + 1 == mat.ndim 181 np_value_weight = np.expand_dims(np_value_weight, -1) 182 183 np_mat_weight = 1 - np_value_weight 184 np_weighted_sum = ( 185 np_mat_weight * mat.astype(np.float32) 186 + np_value_weight * np_value.astype(np.float32) 187 ) 188 np_weighted_sum = np_weighted_sum.astype(mat.dtype) 189 190 if np_mask is not None: 191 mat_origin[np_mask] = np_weighted_sum 192 else: 193 np.copyto(mat_origin, np_weighted_sum) 194 195 else: 196 np_value_weight = alpha 197 if np_value_weight.shape != mat.shape: 198 assert np_value_weight.ndim + 1 == mat.ndim 199 np_value_weight = np.expand_dims(np_value_weight, -1) 200 201 np_mat_weight = 1 - np_value_weight 202 np_weighted_sum = ( 203 np_mat_weight * mat.astype(np.float32) + np_value_weight * np_value.astype(np.float32) 204 ) 205 np_weighted_sum = np_weighted_sum.astype(mat.dtype) 206 207 if np_mask is not None: 208 mat_origin[np_mask] = np_weighted_sum 209 else: 210 np.copyto(mat_origin, np_weighted_sum)