py2ls 0.1.9.7__py3-none-any.whl → 0.1.9.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py2ls/data/styles/style10.json +213 -0
- py2ls/data/styles/style11.json +213 -0
- py2ls/data/styles/style12.json +192 -0
- py2ls/ich2ls.py +136 -0
- py2ls/ips.py +394 -221
- py2ls/netfinder.py +39 -8
- py2ls/plot.py +216 -61
- py2ls/stats.py +1 -3
- {py2ls-0.1.9.7.dist-info → py2ls-0.1.9.9.dist-info}/METADATA +1 -1
- {py2ls-0.1.9.7.dist-info → py2ls-0.1.9.9.dist-info}/RECORD +11 -7
- {py2ls-0.1.9.7.dist-info → py2ls-0.1.9.9.dist-info}/WHEEL +0 -0
py2ls/ips.py
CHANGED
@@ -160,10 +160,10 @@ def search(
|
|
160
160
|
kind="text",
|
161
161
|
output="df",
|
162
162
|
verbose=False,
|
163
|
-
download=
|
163
|
+
download=False,
|
164
164
|
dir_save=dir_save,
|
165
|
+
**kwargs,
|
165
166
|
):
|
166
|
-
from duckduckgo_search import DDGS
|
167
167
|
|
168
168
|
if "te" in kind.lower():
|
169
169
|
results = DDGS().text(query, max_results=limit)
|
@@ -173,8 +173,8 @@ def search(
|
|
173
173
|
print(f'searching "{query}": got the results below\n{res}')
|
174
174
|
if download:
|
175
175
|
try:
|
176
|
-
|
177
|
-
url=res.links.tolist(), dir_save=dir_save, verbose=verbose
|
176
|
+
downloader(
|
177
|
+
url=res.links.tolist(), dir_save=dir_save, verbose=verbose, **kwargs
|
178
178
|
)
|
179
179
|
except:
|
180
180
|
if verbose:
|
@@ -1146,6 +1146,7 @@ def fload(fpath, kind=None, **kwargs):
|
|
1146
1146
|
"spider",
|
1147
1147
|
"tga",
|
1148
1148
|
"tiff",
|
1149
|
+
"tif",
|
1149
1150
|
"webp",
|
1150
1151
|
"json",
|
1151
1152
|
]
|
@@ -2067,223 +2068,391 @@ def apply_filter(img, *args):
|
|
2067
2068
|
return img.filter(supported_filters[filter_name])
|
2068
2069
|
|
2069
2070
|
|
2070
|
-
|
2071
|
-
|
2072
|
-
|
2073
|
-
|
2074
|
-
|
2075
|
-
|
2076
|
-
|
2077
|
-
|
2078
|
-
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
|
2091
|
-
|
2092
|
-
|
2093
|
-
|
2094
|
-
|
2095
|
-
|
2096
|
-
|
2097
|
-
|
2098
|
-
|
2099
|
-
|
2100
|
-
|
2101
|
-
|
2102
|
-
|
2103
|
-
|
2104
|
-
|
2105
|
-
|
2106
|
-
|
2107
|
-
|
2108
|
-
|
2109
|
-
|
2110
|
-
|
2111
|
-
|
2112
|
-
|
2113
|
-
|
2114
|
-
|
2115
|
-
|
2116
|
-
|
2117
|
-
|
2118
|
-
|
2119
|
-
|
2120
|
-
|
2121
|
-
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2125
|
-
|
2126
|
-
|
2127
|
-
|
2128
|
-
|
2129
|
-
|
2130
|
-
|
2131
|
-
|
2132
|
-
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
|
2139
|
-
|
2140
|
-
|
2141
|
-
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2145
|
-
|
2146
|
-
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2150
|
-
|
2151
|
-
|
2152
|
-
|
2153
|
-
|
2154
|
-
|
2155
|
-
|
2156
|
-
|
2157
|
-
|
2158
|
-
|
2159
|
-
|
2160
|
-
|
2161
|
-
|
2162
|
-
|
2163
|
-
|
2164
|
-
|
2165
|
-
|
2166
|
-
|
2167
|
-
|
2168
|
-
#
|
2169
|
-
|
2170
|
-
#
|
2171
|
-
|
2172
|
-
|
2173
|
-
#
|
2174
|
-
|
2175
|
-
|
2176
|
-
#
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2180
|
-
#
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
#
|
2192
|
-
#
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
2197
|
-
|
2198
|
-
|
2199
|
-
|
2200
|
-
|
2201
|
-
#
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
|
2206
|
-
#
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2210
|
-
#
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
|
2215
|
-
|
2216
|
-
|
2217
|
-
|
2218
|
-
|
2219
|
-
|
2220
|
-
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2226
|
-
|
2227
|
-
|
2228
|
-
|
2229
|
-
|
2230
|
-
|
2231
|
-
|
2232
|
-
|
2233
|
-
|
2234
|
-
|
2235
|
-
|
2236
|
-
|
2237
|
-
|
2238
|
-
|
2239
|
-
|
2240
|
-
|
2241
|
-
|
2242
|
-
|
2243
|
-
|
2244
|
-
|
2245
|
-
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2249
|
-
|
2250
|
-
|
2251
|
-
|
2252
|
-
|
2253
|
-
|
2254
|
-
|
2255
|
-
#
|
2256
|
-
#
|
2257
|
-
#
|
2258
|
-
#
|
2259
|
-
#
|
2260
|
-
#
|
2261
|
-
#
|
2262
|
-
#
|
2263
|
-
#
|
2264
|
-
#
|
2265
|
-
#
|
2266
|
-
|
2267
|
-
|
2268
|
-
|
2269
|
-
|
2270
|
-
|
2271
|
-
|
2272
|
-
|
2273
|
-
|
2274
|
-
|
2275
|
-
|
2276
|
-
|
2277
|
-
|
2278
|
-
|
2279
|
-
|
2280
|
-
|
2281
|
-
|
2282
|
-
|
2283
|
-
|
2284
|
-
|
2285
|
-
|
2286
|
-
|
2071
|
+
def imgsetss(
|
2072
|
+
img,
|
2073
|
+
sets=None,
|
2074
|
+
show=True,
|
2075
|
+
show_axis=False,
|
2076
|
+
size=None,
|
2077
|
+
dpi=100,
|
2078
|
+
figsize=None,
|
2079
|
+
auto=False,
|
2080
|
+
filter_kws=None,
|
2081
|
+
):
|
2082
|
+
"""
|
2083
|
+
Apply various enhancements and filters to an image using PIL's ImageEnhance and ImageFilter modules.
|
2084
|
+
|
2085
|
+
Args:
|
2086
|
+
img (PIL.Image): The input image.
|
2087
|
+
sets (dict): A dictionary specifying the enhancements, filters, and their parameters.
|
2088
|
+
show (bool): Whether to display the enhanced image.
|
2089
|
+
show_axis (bool): Whether to display axes on the image plot.
|
2090
|
+
size (tuple): The size of the thumbnail, cover, contain, or fit operation.
|
2091
|
+
dpi (int): Dots per inch for the displayed image.
|
2092
|
+
figsize (tuple): The size of the figure for displaying the image.
|
2093
|
+
auto (bool): Whether to automatically enhance the image based on its characteristics.
|
2094
|
+
|
2095
|
+
Returns:
|
2096
|
+
PIL.Image: The enhanced image.
|
2097
|
+
|
2098
|
+
Supported enhancements and filters:
|
2099
|
+
- "sharpness": Adjusts the sharpness of the image. Values > 1 increase sharpness, while values < 1 decrease sharpness.
|
2100
|
+
- "contrast": Adjusts the contrast of the image. Values > 1 increase contrast, while values < 1 decrease contrast.
|
2101
|
+
- "brightness": Adjusts the brightness of the image. Values > 1 increase brightness, while values < 1 decrease brightness.
|
2102
|
+
- "color": Adjusts the color saturation of the image. Values > 1 increase saturation, while values < 1 decrease saturation.
|
2103
|
+
- "rotate": Rotates the image by the specified angle.
|
2104
|
+
- "crop" or "cut": Crops the image. The value should be a tuple specifying the crop box as (left, upper, right, lower).
|
2105
|
+
- "size": Resizes the image to the specified dimensions.
|
2106
|
+
- "thumbnail": Resizes the image to fit within the given size while preserving aspect ratio.
|
2107
|
+
- "cover": Resizes and crops the image to fill the specified size.
|
2108
|
+
- "contain": Resizes the image to fit within the specified size, adding borders if necessary.
|
2109
|
+
- "fit": Resizes and pads the image to fit within the specified size.
|
2110
|
+
- "filter": Applies various filters to the image (e.g., BLUR, CONTOUR, EDGE_ENHANCE).
|
2111
|
+
|
2112
|
+
Note:
|
2113
|
+
The "color" and "enhance" enhancements are not implemented in this function.
|
2114
|
+
"""
|
2115
|
+
supported_filters = [
|
2116
|
+
"BLUR",
|
2117
|
+
"CONTOUR",
|
2118
|
+
"DETAIL",
|
2119
|
+
"EDGE_ENHANCE",
|
2120
|
+
"EDGE_ENHANCE_MORE",
|
2121
|
+
"EMBOSS",
|
2122
|
+
"FIND_EDGES",
|
2123
|
+
"SHARPEN",
|
2124
|
+
"SMOOTH",
|
2125
|
+
"SMOOTH_MORE",
|
2126
|
+
"MIN_FILTER",
|
2127
|
+
"MAX_FILTER",
|
2128
|
+
"MODE_FILTER",
|
2129
|
+
"MULTIBAND_FILTER",
|
2130
|
+
"GAUSSIAN_BLUR",
|
2131
|
+
"BOX_BLUR",
|
2132
|
+
"MEDIAN_FILTER",
|
2133
|
+
]
|
2134
|
+
print(
|
2135
|
+
"sets: a dict,'sharp:1.2','color','contrast:'auto' or 1.2','bright', 'crop: x_upperleft,y_upperleft, x_lowerright, y_lowerright','rotation','resize','rem or background'"
|
2136
|
+
)
|
2137
|
+
print(f"usage: filter_kws 'dict' below:")
|
2138
|
+
pp([str(i).lower() for i in supported_filters])
|
2139
|
+
print("\nlog:\n")
|
2140
|
+
|
2141
|
+
def confirm_rembg_models(model_name):
|
2142
|
+
models_support = [
|
2143
|
+
"u2net",
|
2144
|
+
"u2netp",
|
2145
|
+
"u2net_human_seg",
|
2146
|
+
"u2net_cloth_seg",
|
2147
|
+
"silueta",
|
2148
|
+
"isnet-general-use",
|
2149
|
+
"isnet-anime",
|
2150
|
+
"sam",
|
2151
|
+
]
|
2152
|
+
if model_name in models_support:
|
2153
|
+
print(f"model_name: {model_name}")
|
2154
|
+
return model_name
|
2155
|
+
else:
|
2156
|
+
print(
|
2157
|
+
f"{model_name} cannot be found, check the name:{models_support}, default('isnet-general-use') has been used"
|
2158
|
+
)
|
2159
|
+
return "isnet-general-use"
|
2160
|
+
|
2161
|
+
def auto_enhance(img):
|
2162
|
+
"""
|
2163
|
+
Automatically enhances the image based on its characteristics.
|
2164
|
+
Args:
|
2165
|
+
img (PIL.Image): The input image.
|
2166
|
+
Returns:
|
2167
|
+
dict: A dictionary containing the optimal enhancement values.
|
2168
|
+
"""
|
2169
|
+
# Determine the bit depth based on the image mode
|
2170
|
+
if img.mode in ["1", "L", "P", "RGB", "YCbCr", "LAB", "HSV"]:
|
2171
|
+
# 8-bit depth per channel
|
2172
|
+
bit_depth = 8
|
2173
|
+
elif img.mode in ["RGBA", "CMYK"]:
|
2174
|
+
# 8-bit depth per channel + alpha (RGBA) or additional channels (CMYK)
|
2175
|
+
bit_depth = 8
|
2176
|
+
elif img.mode in ["I", "F"]:
|
2177
|
+
# 16-bit depth per channel (integer or floating-point)
|
2178
|
+
bit_depth = 16
|
2179
|
+
else:
|
2180
|
+
raise ValueError("Unsupported image mode")
|
2181
|
+
# Calculate the brightness and contrast for each channel
|
2182
|
+
num_channels = len(img.getbands())
|
2183
|
+
brightness_factors = []
|
2184
|
+
contrast_factors = []
|
2185
|
+
for channel in range(num_channels):
|
2186
|
+
channel_histogram = img.split()[channel].histogram()
|
2187
|
+
brightness = sum(i * w for i, w in enumerate(channel_histogram)) / sum(
|
2188
|
+
channel_histogram
|
2189
|
+
)
|
2190
|
+
channel_min, channel_max = img.split()[channel].getextrema()
|
2191
|
+
contrast = channel_max - channel_min
|
2192
|
+
# Adjust calculations based on bit depth
|
2193
|
+
normalization_factor = 2**bit_depth - 1 # Max value for the given bit depth
|
2194
|
+
brightness_factor = (
|
2195
|
+
1.0 + (brightness - normalization_factor / 2) / normalization_factor
|
2196
|
+
)
|
2197
|
+
contrast_factor = (
|
2198
|
+
1.0 + (contrast - normalization_factor / 2) / normalization_factor
|
2199
|
+
)
|
2200
|
+
brightness_factors.append(brightness_factor)
|
2201
|
+
contrast_factors.append(contrast_factor)
|
2202
|
+
# Calculate the average brightness and contrast factors across channels
|
2203
|
+
avg_brightness_factor = sum(brightness_factors) / num_channels
|
2204
|
+
avg_contrast_factor = sum(contrast_factors) / num_channels
|
2205
|
+
return {"brightness": avg_brightness_factor, "contrast": avg_contrast_factor}
|
2206
|
+
|
2207
|
+
# Load image if input is a file path
|
2208
|
+
if isinstance(img, str):
|
2209
|
+
img = load_img(img)
|
2210
|
+
img_update = img.copy()
|
2211
|
+
# Auto-enhance image if requested
|
2212
|
+
if auto:
|
2213
|
+
auto_params = auto_enhance(img_update)
|
2214
|
+
sets.update(auto_params)
|
2215
|
+
if sets is None:
|
2216
|
+
sets = {}
|
2217
|
+
for k, value in sets.items():
|
2218
|
+
if "shar" in k.lower():
|
2219
|
+
enhancer = ImageEnhance.Sharpness(img_update)
|
2220
|
+
img_update = enhancer.enhance(value)
|
2221
|
+
elif "col" in k.lower() and "bg" not in k.lower():
|
2222
|
+
enhancer = ImageEnhance.Color(img_update)
|
2223
|
+
img_update = enhancer.enhance(value)
|
2224
|
+
elif "contr" in k.lower():
|
2225
|
+
if value and isinstance(value, (float, int)):
|
2226
|
+
enhancer = ImageEnhance.Contrast(img_update)
|
2227
|
+
img_update = enhancer.enhance(value)
|
2228
|
+
else:
|
2229
|
+
print("autocontrasted")
|
2230
|
+
img_update = ImageOps.autocontrast(img_update)
|
2231
|
+
elif "bri" in k.lower():
|
2232
|
+
enhancer = ImageEnhance.Brightness(img_update)
|
2233
|
+
img_update = enhancer.enhance(value)
|
2234
|
+
elif "cro" in k.lower() or "cut" in k.lower():
|
2235
|
+
img_update = img_update.crop(value)
|
2236
|
+
elif "rota" in k.lower():
|
2237
|
+
img_update = img_update.rotate(value)
|
2238
|
+
elif "si" in k.lower():
|
2239
|
+
img_update = img_update.resize(value)
|
2240
|
+
elif "thum" in k.lower():
|
2241
|
+
img_update.thumbnail(value)
|
2242
|
+
elif "cover" in k.lower():
|
2243
|
+
img_update = ImageOps.cover(img_update, size=value)
|
2244
|
+
elif "contain" in k.lower():
|
2245
|
+
img_update = ImageOps.contain(img_update, size=value)
|
2246
|
+
elif "fit" in k.lower():
|
2247
|
+
img_update = ImageOps.fit(img_update, size=value)
|
2248
|
+
elif "pad" in k.lower():
|
2249
|
+
img_update = ImageOps.pad(img_update, size=value)
|
2250
|
+
elif "rem" in k.lower() or "rm" in k.lower() or "back" in k.lower():
|
2251
|
+
if value and isinstance(value, (int, float, list)):
|
2252
|
+
print(
|
2253
|
+
'example usage: {"rm":[alpha_matting_background_threshold(20),alpha_matting_foreground_threshold(270),alpha_matting_erode_sive(11)]}'
|
2254
|
+
)
|
2255
|
+
print("https://github.com/danielgatis/rembg/blob/main/USAGE.md")
|
2256
|
+
# ### Parameters:
|
2257
|
+
# data (Union[bytes, PILImage, np.ndarray]): The input image data.
|
2258
|
+
# alpha_matting (bool, optional): Flag indicating whether to use alpha matting. Defaults to False.
|
2259
|
+
# alpha_matting_foreground_threshold (int, optional): Foreground threshold for alpha matting. Defaults to 240.
|
2260
|
+
# alpha_matting_background_threshold (int, optional): Background threshold for alpha matting. Defaults to 10.
|
2261
|
+
# alpha_matting_erode_size (int, optional): Erosion size for alpha matting. Defaults to 10.
|
2262
|
+
# session (Optional[BaseSession], optional): A session object for the 'u2net' model. Defaults to None.
|
2263
|
+
# only_mask (bool, optional): Flag indicating whether to return only the binary masks. Defaults to False.
|
2264
|
+
# post_process_mask (bool, optional): Flag indicating whether to post-process the masks. Defaults to False.
|
2265
|
+
# bgcolor (Optional[Tuple[int, int, int, int]], optional): Background color for the cutout image. Defaults to None.
|
2266
|
+
# ###
|
2267
|
+
if isinstance(value, int):
|
2268
|
+
value = [value]
|
2269
|
+
if len(value) < 2:
|
2270
|
+
img_update = remove(
|
2271
|
+
img_update,
|
2272
|
+
alpha_matting=True,
|
2273
|
+
alpha_matting_background_threshold=value,
|
2274
|
+
)
|
2275
|
+
elif 2 <= len(value) < 3:
|
2276
|
+
img_update = remove(
|
2277
|
+
img_update,
|
2278
|
+
alpha_matting=True,
|
2279
|
+
alpha_matting_background_threshold=value[0],
|
2280
|
+
alpha_matting_foreground_threshold=value[1],
|
2281
|
+
)
|
2282
|
+
elif 3 <= len(value) < 4:
|
2283
|
+
img_update = remove(
|
2284
|
+
img_update,
|
2285
|
+
alpha_matting=True,
|
2286
|
+
alpha_matting_background_threshold=value[0],
|
2287
|
+
alpha_matting_foreground_threshold=value[1],
|
2288
|
+
alpha_matting_erode_size=value[2],
|
2289
|
+
)
|
2290
|
+
if isinstance(value, tuple): # replace the background color
|
2291
|
+
if len(value) == 3:
|
2292
|
+
value += (255,)
|
2293
|
+
img_update = remove(img_update, bgcolor=value)
|
2294
|
+
if isinstance(value, str):
|
2295
|
+
if confirm_rembg_models(value):
|
2296
|
+
img_update = remove(img_update, session=new_session(value))
|
2297
|
+
else:
|
2298
|
+
img_update = remove(img_update)
|
2299
|
+
elif "bgcolor" in k.lower():
|
2300
|
+
if isinstance(value, list):
|
2301
|
+
value = tuple(value)
|
2302
|
+
if isinstance(value, tuple): # replace the background color
|
2303
|
+
if len(value) == 3:
|
2304
|
+
value += (255,)
|
2305
|
+
img_update = remove(img_update, bgcolor=value)
|
2306
|
+
if filter_kws:
|
2307
|
+
for filter_name, filter_value in filter_kws.items():
|
2308
|
+
img_update = apply_filter(img_update, filter_name, filter_value)
|
2309
|
+
# Display the image if requested
|
2310
|
+
if show:
|
2311
|
+
if figsize is None:
|
2312
|
+
plt.figure(dpi=dpi)
|
2313
|
+
else:
|
2314
|
+
plt.figure(figsize=figsize, dpi=dpi)
|
2315
|
+
plt.imshow(img_update)
|
2316
|
+
plt.axis("on") if show_axis else plt.axis("off")
|
2317
|
+
return img_update
|
2318
|
+
|
2319
|
+
|
2320
|
+
from sklearn.decomposition import PCA
|
2321
|
+
from skimage import transform, feature, filters, measure
|
2322
|
+
from skimage.color import rgb2gray
|
2323
|
+
from scipy.fftpack import fftshift, fft2
|
2324
|
+
import numpy as np
|
2325
|
+
import cv2 # Used for template matching
|
2326
|
+
|
2327
|
+
|
2328
|
+
def crop_black_borders(image):
|
2329
|
+
"""Crop the black borders from a rotated image."""
|
2330
|
+
# Convert the image to grayscale if it's not already
|
2331
|
+
if image.ndim == 3:
|
2332
|
+
gray_image = color.rgb2gray(image)
|
2333
|
+
else:
|
2334
|
+
gray_image = image
|
2335
|
+
|
2336
|
+
# Find all the non-black (non-zero) pixels
|
2337
|
+
mask = gray_image > 0 # Mask for non-black pixels (assuming black is zero)
|
2338
|
+
coords = np.column_stack(np.where(mask))
|
2339
|
+
|
2340
|
+
# Get the bounding box of non-black pixels
|
2341
|
+
if coords.any(): # Check if there are any non-black pixels
|
2342
|
+
y_min, x_min = coords.min(axis=0)
|
2343
|
+
y_max, x_max = coords.max(axis=0)
|
2344
|
+
|
2345
|
+
# Crop the image to the bounding box
|
2346
|
+
cropped_image = image[y_min : y_max + 1, x_min : x_max + 1]
|
2347
|
+
else:
|
2348
|
+
# If the image is completely black (which shouldn't happen), return the original image
|
2349
|
+
cropped_image = image
|
2350
|
+
|
2351
|
+
return cropped_image
|
2352
|
+
|
2353
|
+
|
2354
|
+
def detect_angle(image, by="median", template=None):
|
2355
|
+
"""Detect the angle of rotation using various methods."""
|
2356
|
+
# Convert to grayscale
|
2357
|
+
gray_image = rgb2gray(image)
|
2358
|
+
|
2359
|
+
# Detect edges using Canny edge detector
|
2360
|
+
edges = feature.canny(gray_image, sigma=2)
|
2361
|
+
|
2362
|
+
# Use Hough transform to detect lines
|
2363
|
+
lines = transform.probabilistic_hough_line(edges)
|
2364
|
+
|
2365
|
+
if not lines and any(["me" in by, "pca" in by]):
|
2366
|
+
print("No lines detected. Adjust the edge detection parameters.")
|
2367
|
+
return 0
|
2368
|
+
|
2369
|
+
# Hough Transform-based angle detection (Median/Mean)
|
2370
|
+
if "me" in by:
|
2371
|
+
angles = []
|
2372
|
+
for line in lines:
|
2373
|
+
(x0, y0), (x1, y1) = line
|
2374
|
+
angle = np.arctan2(y1 - y0, x1 - x0) * 180 / np.pi
|
2375
|
+
if 80 < abs(angle) < 100:
|
2376
|
+
angles.append(angle)
|
2377
|
+
if not angles:
|
2378
|
+
return 0
|
2379
|
+
if "di" in by:
|
2380
|
+
median_angle = np.median(angles)
|
2381
|
+
rotation_angle = (
|
2382
|
+
90 - median_angle if median_angle > 0 else -90 - median_angle
|
2383
|
+
)
|
2384
|
+
|
2385
|
+
return rotation_angle
|
2386
|
+
else:
|
2387
|
+
mean_angle = np.mean(angles)
|
2388
|
+
rotation_angle = 90 - mean_angle if mean_angle > 0 else -90 - mean_angle
|
2389
|
+
|
2390
|
+
return rotation_angle
|
2391
|
+
|
2392
|
+
# PCA-based angle detection
|
2393
|
+
elif "pca" in by:
|
2394
|
+
y, x = np.nonzero(edges)
|
2395
|
+
if len(x) == 0:
|
2396
|
+
return 0
|
2397
|
+
pca = PCA(n_components=2)
|
2398
|
+
pca.fit(np.vstack((x, y)).T)
|
2399
|
+
angle = np.arctan2(pca.components_[0, 1], pca.components_[0, 0]) * 180 / np.pi
|
2400
|
+
return angle
|
2401
|
+
|
2402
|
+
# Gradient Orientation-based angle detection
|
2403
|
+
elif "gra" in by:
|
2404
|
+
gx, gy = np.gradient(gray_image)
|
2405
|
+
angles = np.arctan2(gy, gx) * 180 / np.pi
|
2406
|
+
hist, bin_edges = np.histogram(angles, bins=360, range=(-180, 180))
|
2407
|
+
return bin_edges[np.argmax(hist)]
|
2408
|
+
|
2409
|
+
# Template Matching-based angle detection
|
2410
|
+
elif "temp" in by:
|
2411
|
+
if template is None:
|
2412
|
+
# Automatically extract a template from the center of the image
|
2413
|
+
height, width = gray_image.shape
|
2414
|
+
center_x, center_y = width // 2, height // 2
|
2415
|
+
size = (
|
2416
|
+
min(height, width) // 4
|
2417
|
+
) # Size of the template as a fraction of image size
|
2418
|
+
template = gray_image[
|
2419
|
+
center_y - size : center_y + size, center_x - size : center_x + size
|
2420
|
+
]
|
2421
|
+
best_angle = None
|
2422
|
+
best_corr = -1
|
2423
|
+
for angle in range(0, 180, 1): # Checking every degree
|
2424
|
+
rotated_template = transform.rotate(template, angle)
|
2425
|
+
res = cv2.matchTemplate(gray_image, rotated_template, cv2.TM_CCOEFF)
|
2426
|
+
_, max_val, _, _ = cv2.minMaxLoc(res)
|
2427
|
+
if max_val > best_corr:
|
2428
|
+
best_corr = max_val
|
2429
|
+
best_angle = angle
|
2430
|
+
return best_angle
|
2431
|
+
|
2432
|
+
# Image Moments-based angle detection
|
2433
|
+
elif "mo" in by:
|
2434
|
+
moments = measure.moments_central(gray_image)
|
2435
|
+
angle = (
|
2436
|
+
0.5
|
2437
|
+
* np.arctan2(2 * moments[1, 1], moments[0, 2] - moments[2, 0])
|
2438
|
+
* 180
|
2439
|
+
/ np.pi
|
2440
|
+
)
|
2441
|
+
return angle
|
2442
|
+
|
2443
|
+
# Fourier Transform-based angle detection
|
2444
|
+
elif "fft" in by:
|
2445
|
+
f = fft2(gray_image)
|
2446
|
+
fshift = fftshift(f)
|
2447
|
+
magnitude_spectrum = np.log(np.abs(fshift) + 1)
|
2448
|
+
rows, cols = magnitude_spectrum.shape
|
2449
|
+
r, c = np.unravel_index(np.argmax(magnitude_spectrum), (rows, cols))
|
2450
|
+
angle = np.arctan2(r - rows // 2, c - cols // 2) * 180 / np.pi
|
2451
|
+
return angle
|
2452
|
+
|
2453
|
+
else:
|
2454
|
+
print(f"Unknown method {by}")
|
2455
|
+
return 0
|
2287
2456
|
|
2288
2457
|
|
2289
2458
|
def imgsets(img, **kwargs):
|
@@ -2444,7 +2613,11 @@ def imgsets(img, **kwargs):
|
|
2444
2613
|
elif "cro" in k.lower() or "cut" in k.lower():
|
2445
2614
|
img_update = img_update.crop(value)
|
2446
2615
|
elif "rota" in k.lower():
|
2616
|
+
if isinstance(value, str):
|
2617
|
+
value = detect_angle(img_update, by=value)
|
2618
|
+
print(f"rotated by {value}°")
|
2447
2619
|
img_update = img_update.rotate(value)
|
2620
|
+
|
2448
2621
|
elif "si" in k.lower():
|
2449
2622
|
img_update = img_update.resize(value)
|
2450
2623
|
elif "thum" in k.lower():
|