py2ls 0.2.4.37__py3-none-any.whl → 0.2.4.39__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
py2ls/ips.py
CHANGED
@@ -26,6 +26,7 @@ import re
|
|
26
26
|
import stat
|
27
27
|
import platform
|
28
28
|
|
29
|
+
|
29
30
|
# only for backup these scripts
|
30
31
|
def backup(
|
31
32
|
src="/Users/macjianfeng/Dropbox/github/python/py2ls/.venv/lib/python3.12/site-packages/py2ls/",
|
@@ -33,14 +34,16 @@ def backup(
|
|
33
34
|
kind="py",
|
34
35
|
overwrite=True,
|
35
36
|
reverse=False,
|
36
|
-
verbose=False
|
37
|
-
):
|
37
|
+
verbose=False,
|
38
|
+
):
|
38
39
|
if reverse:
|
39
|
-
src, tar = tar,src
|
40
|
+
src, tar = tar, src
|
40
41
|
print(f"reversed")
|
41
|
-
f = listdir(src, kind, verbose=verbose)
|
42
|
+
f = listdir(src, kind=kind, verbose=verbose)
|
42
43
|
[copy(i, tar, overwrite=overwrite, verbose=verbose) for i in f.path]
|
43
|
-
|
44
|
+
|
45
|
+
print(f"backup '*.{kind}'...\nfrom {src} \nto {tar}")
|
46
|
+
|
44
47
|
|
45
48
|
def run_once_within(duration=60, reverse=False): # default 60s
|
46
49
|
import time
|
@@ -804,14 +807,16 @@ def strcmp(
|
|
804
807
|
return candidates[best_match_index], best_match_index
|
805
808
|
|
806
809
|
|
807
|
-
def imgcmp(
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
810
|
+
def imgcmp(
|
811
|
+
img: list,
|
812
|
+
method: str = "knn",
|
813
|
+
thr: float = 0.75,
|
814
|
+
detector: str = "sift",
|
815
|
+
plot_: bool = True,
|
816
|
+
figsize=[12, 6],
|
817
|
+
grid_size=10, # only for grid detector
|
818
|
+
**kwargs,
|
819
|
+
):
|
815
820
|
"""
|
816
821
|
Compare two images using SSIM, Feature Matching (SIFT), or KNN Matching.
|
817
822
|
|
@@ -832,13 +837,13 @@ def imgcmp(img: list,
|
|
832
837
|
from skimage.metrics import structural_similarity as ssim
|
833
838
|
|
834
839
|
# Load images
|
835
|
-
if isinstance(img, list) and isinstance(img[0],str):
|
840
|
+
if isinstance(img, list) and isinstance(img[0], str):
|
836
841
|
image1 = cv2.imread(img[0])
|
837
842
|
image2 = cv2.imread(img[1])
|
838
|
-
bool_cvt=True
|
843
|
+
bool_cvt = True
|
839
844
|
else:
|
840
|
-
image1, image2 = np.array(img[0]),np.array(img[1])
|
841
|
-
bool_cvt=False
|
845
|
+
image1, image2 = np.array(img[0]), np.array(img[1])
|
846
|
+
bool_cvt = False
|
842
847
|
|
843
848
|
if image1 is None or image2 is None:
|
844
849
|
raise ValueError("Could not load one or both images. Check file paths.")
|
@@ -873,7 +878,7 @@ def imgcmp(img: list,
|
|
873
878
|
elif method in ["match", "knn"]:
|
874
879
|
# Convert images to grayscale
|
875
880
|
gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
|
876
|
-
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
|
881
|
+
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
|
877
882
|
|
878
883
|
if detector == "sift":
|
879
884
|
# SIFT detector
|
@@ -888,11 +893,19 @@ def imgcmp(img: list,
|
|
888
893
|
|
889
894
|
for i in range(0, gray1.shape[0], grid_size):
|
890
895
|
for j in range(0, gray1.shape[1], grid_size):
|
891
|
-
patch1 = gray1[i:i + grid_size, j:j + grid_size]
|
892
|
-
patch2 = gray2[i:i + grid_size, j:j + grid_size]
|
896
|
+
patch1 = gray1[i : i + grid_size, j : j + grid_size]
|
897
|
+
patch2 = gray2[i : i + grid_size, j : j + grid_size]
|
893
898
|
if patch1.size > 0 and patch2.size > 0:
|
894
|
-
keypoints1.append(
|
895
|
-
|
899
|
+
keypoints1.append(
|
900
|
+
cv2.KeyPoint(
|
901
|
+
j + grid_size // 2, i + grid_size // 2, grid_size
|
902
|
+
)
|
903
|
+
)
|
904
|
+
keypoints2.append(
|
905
|
+
cv2.KeyPoint(
|
906
|
+
j + grid_size // 2, i + grid_size // 2, grid_size
|
907
|
+
)
|
908
|
+
)
|
896
909
|
descriptors1.append(np.mean(patch1))
|
897
910
|
descriptors2.append(np.mean(patch2))
|
898
911
|
|
@@ -903,12 +916,20 @@ def imgcmp(img: list,
|
|
903
916
|
# Pixel-based direct comparison
|
904
917
|
descriptors1 = gray1.flatten()
|
905
918
|
descriptors2 = gray2.flatten()
|
906
|
-
keypoints1 = [
|
907
|
-
|
919
|
+
keypoints1 = [
|
920
|
+
cv2.KeyPoint(x, y, 1)
|
921
|
+
for y in range(gray1.shape[0])
|
922
|
+
for x in range(gray1.shape[1])
|
923
|
+
]
|
924
|
+
keypoints2 = [
|
925
|
+
cv2.KeyPoint(x, y, 1)
|
926
|
+
for y in range(gray2.shape[0])
|
927
|
+
for x in range(gray2.shape[1])
|
928
|
+
]
|
908
929
|
|
909
930
|
else:
|
910
931
|
raise ValueError("Invalid detector. Use 'sift', 'grid', or 'pixel'.")
|
911
|
-
|
932
|
+
|
912
933
|
# Handle missing descriptors
|
913
934
|
if descriptors1 is None or descriptors2 is None:
|
914
935
|
raise ValueError("Failed to compute descriptors for one or both images.")
|
@@ -955,16 +976,26 @@ def imgcmp(img: list,
|
|
955
976
|
# Apply the homography to image2
|
956
977
|
try:
|
957
978
|
# Calculate Homography using RANSAC
|
958
|
-
homography_matrix, mask = cv2.findHomography(
|
979
|
+
homography_matrix, mask = cv2.findHomography(
|
980
|
+
src_pts, dst_pts, cv2.RANSAC, 5.0
|
981
|
+
)
|
959
982
|
h, w = image1.shape[:2]
|
960
983
|
warped_image2 = cv2.warpPerspective(image2, homography_matrix, (w, h))
|
961
984
|
|
962
985
|
# Plot result if needed
|
963
986
|
if plot_:
|
964
987
|
fig, ax = plt.subplots(1, 2, figsize=figsize)
|
965
|
-
|
988
|
+
(
|
989
|
+
ax[0].imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB))
|
990
|
+
if bool_cvt
|
991
|
+
else ax[0].imshow(image1)
|
992
|
+
)
|
966
993
|
ax[0].set_title("Image 1")
|
967
|
-
|
994
|
+
(
|
995
|
+
ax[1].imshow(cv2.cvtColor(warped_image2, cv2.COLOR_BGR2RGB))
|
996
|
+
if bool_cvt
|
997
|
+
else ax[1].imshow(warped_image2)
|
998
|
+
)
|
968
999
|
ax[1].set_title("Warped Image 2")
|
969
1000
|
plt.tight_layout()
|
970
1001
|
plt.show()
|
@@ -977,8 +1008,14 @@ def imgcmp(img: list,
|
|
977
1008
|
image1, keypoints1, image2, keypoints2, good_matches, None, flags=2
|
978
1009
|
)
|
979
1010
|
plt.figure(figsize=figsize)
|
980
|
-
|
981
|
-
|
1011
|
+
(
|
1012
|
+
plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
|
1013
|
+
if bool_cvt
|
1014
|
+
else plt.imshow(result)
|
1015
|
+
)
|
1016
|
+
plt.title(
|
1017
|
+
f"Feature Matches ({len(good_matches)} matches, Score: {similarity_score:.4f})"
|
1018
|
+
)
|
982
1019
|
plt.axis("off")
|
983
1020
|
plt.show()
|
984
1021
|
# Identify unmatched keypoints
|
@@ -1021,9 +1058,17 @@ def imgcmp(img: list,
|
|
1021
1058
|
|
1022
1059
|
if plot_:
|
1023
1060
|
fig, ax = plt.subplots(1, 2, figsize=figsize)
|
1024
|
-
|
1061
|
+
(
|
1062
|
+
ax[0].imshow(cv2.cvtColor(img1_unmatch, cv2.COLOR_BGR2RGB))
|
1063
|
+
if bool_cvt
|
1064
|
+
else ax[0].imshow(img1_unmatch)
|
1065
|
+
)
|
1025
1066
|
ax[0].set_title("Unmatched Keypoints (Image 1)")
|
1026
|
-
|
1067
|
+
(
|
1068
|
+
ax[1].imshow(cv2.cvtColor(img2_unmatch, cv2.COLOR_BGR2RGB))
|
1069
|
+
if bool_cvt
|
1070
|
+
else ax[1].imshow(img2_unmatch)
|
1071
|
+
)
|
1027
1072
|
ax[1].set_title("Unmatched Keypoints (Image 2)")
|
1028
1073
|
ax[0].axis("off")
|
1029
1074
|
ax[1].axis("off")
|
@@ -1031,15 +1076,23 @@ def imgcmp(img: list,
|
|
1031
1076
|
plt.show()
|
1032
1077
|
if plot_:
|
1033
1078
|
fig, ax = plt.subplots(1, 2, figsize=figsize)
|
1034
|
-
|
1079
|
+
(
|
1080
|
+
ax[0].imshow(cv2.cvtColor(img1_match, cv2.COLOR_BGR2RGB))
|
1081
|
+
if bool_cvt
|
1082
|
+
else ax[0].imshow(img1_match)
|
1083
|
+
)
|
1035
1084
|
ax[0].set_title("Matched Keypoints (Image 1)")
|
1036
|
-
|
1085
|
+
(
|
1086
|
+
ax[1].imshow(cv2.cvtColor(img2_match, cv2.COLOR_BGR2RGB))
|
1087
|
+
if bool_cvt
|
1088
|
+
else ax[1].imshow(img2_match)
|
1089
|
+
)
|
1037
1090
|
ax[1].set_title("Matched Keypoints (Image 2)")
|
1038
1091
|
ax[0].axis("off")
|
1039
1092
|
ax[1].axis("off")
|
1040
1093
|
plt.tight_layout()
|
1041
1094
|
plt.show()
|
1042
|
-
return good_matches, similarity_score
|
1095
|
+
return good_matches, similarity_score # , homography_matrix
|
1043
1096
|
|
1044
1097
|
else:
|
1045
1098
|
raise ValueError("Invalid method. Use 'ssim', 'match', or 'knn'.")
|
@@ -1312,6 +1365,7 @@ def text2audio(
|
|
1312
1365
|
print(f"Error opening file: {e}")
|
1313
1366
|
print("done")
|
1314
1367
|
|
1368
|
+
|
1315
1369
|
def str2time(time_str, fmt="24"):
|
1316
1370
|
"""
|
1317
1371
|
Convert a time string into the specified format.
|
@@ -2699,7 +2753,7 @@ def fload(fpath, kind=None, **kwargs):
|
|
2699
2753
|
def load_excel(fpath, **kwargs):
|
2700
2754
|
engine = kwargs.get("engine", "openpyxl")
|
2701
2755
|
verbose = kwargs.pop("verbose", False)
|
2702
|
-
password=kwargs.pop("password",None)
|
2756
|
+
password = kwargs.pop("password", None)
|
2703
2757
|
|
2704
2758
|
if not password:
|
2705
2759
|
if run_once_within(reverse=True):
|
@@ -2712,12 +2766,12 @@ def fload(fpath, kind=None, **kwargs):
|
|
2712
2766
|
except:
|
2713
2767
|
pass
|
2714
2768
|
return df
|
2715
|
-
|
2716
|
-
import msoffcrypto
|
2769
|
+
# * needs a password?
|
2770
|
+
import msoffcrypto # pip install msoffcrypto-tool
|
2717
2771
|
from io import BytesIO
|
2718
2772
|
|
2719
2773
|
# Open the encrypted Excel file
|
2720
|
-
with open(fpath,
|
2774
|
+
with open(fpath, "rb") as f:
|
2721
2775
|
try:
|
2722
2776
|
office_file = msoffcrypto.OfficeFile(f)
|
2723
2777
|
office_file.load_key(password=password) # Provide the password
|
@@ -3317,16 +3371,16 @@ def fsave(
|
|
3317
3371
|
df = pd.DataFrame(data)
|
3318
3372
|
df.to_csv(fpath, **kwargs_valid)
|
3319
3373
|
|
3320
|
-
|
3321
3374
|
def save_xlsx(fpath, data, password=None, **kwargs):
|
3322
3375
|
import msoffcrypto
|
3323
3376
|
from io import BytesIO
|
3377
|
+
|
3324
3378
|
verbose = kwargs.pop("verbose", False)
|
3325
3379
|
sheet_name = kwargs.pop("sheet_name", "Sheet1")
|
3326
|
-
|
3380
|
+
|
3327
3381
|
if run_once_within(reverse=True):
|
3328
3382
|
use_pd("to_excel", verbose=verbose)
|
3329
|
-
|
3383
|
+
|
3330
3384
|
if any(kwargs):
|
3331
3385
|
format_excel(df=data, filename=fpath, **kwargs)
|
3332
3386
|
else:
|
@@ -3351,10 +3405,16 @@ def fsave(
|
|
3351
3405
|
kwargs.pop(key, None)
|
3352
3406
|
|
3353
3407
|
df = pd.DataFrame(data)
|
3354
|
-
|
3408
|
+
|
3355
3409
|
# Write to Excel without password first
|
3356
3410
|
temp_file = BytesIO()
|
3357
|
-
df.to_excel(
|
3411
|
+
df.to_excel(
|
3412
|
+
temp_file,
|
3413
|
+
sheet_name=sheet_name,
|
3414
|
+
index=False,
|
3415
|
+
engine="xlsxwriter",
|
3416
|
+
**kwargs,
|
3417
|
+
)
|
3358
3418
|
|
3359
3419
|
# If a password is provided, encrypt the file
|
3360
3420
|
if password:
|
@@ -3363,19 +3423,22 @@ def fsave(
|
|
3363
3423
|
office_file.load_key(password=password) # Provide the password
|
3364
3424
|
|
3365
3425
|
# Encrypt and save the file
|
3366
|
-
with open(fpath,
|
3426
|
+
with open(fpath, "wb") as encrypted_file:
|
3367
3427
|
office_file.encrypt(encrypted_file)
|
3368
3428
|
else:
|
3369
3429
|
# Save the file without encryption if no password is provided
|
3370
3430
|
try:
|
3371
3431
|
# Use ExcelWriter with append mode if the file exists
|
3372
|
-
with pd.ExcelWriter(
|
3373
|
-
|
3432
|
+
with pd.ExcelWriter(
|
3433
|
+
fpath, engine="openpyxl", mode="a", if_sheet_exists="new"
|
3434
|
+
) as writer:
|
3435
|
+
df.to_excel(
|
3436
|
+
writer, sheet_name=sheet_name, index=False, **kwargs
|
3437
|
+
)
|
3374
3438
|
except FileNotFoundError:
|
3375
3439
|
# If file doesn't exist, create a new one
|
3376
3440
|
df.to_excel(fpath, sheet_name=sheet_name, index=False, **kwargs)
|
3377
3441
|
|
3378
|
-
|
3379
3442
|
def save_ipynb(fpath, data, **kwargs):
|
3380
3443
|
# Split the content by code fences to distinguish between code and markdown
|
3381
3444
|
import nbformat
|
@@ -3784,7 +3847,6 @@ def get_os(full=False, verbose=False):
|
|
3784
3847
|
import subprocess
|
3785
3848
|
from datetime import datetime, timedelta
|
3786
3849
|
|
3787
|
-
|
3788
3850
|
def get_os_type():
|
3789
3851
|
os_name = sys.platform
|
3790
3852
|
if "dar" in os_name:
|
@@ -3797,8 +3859,10 @@ def get_os(full=False, verbose=False):
|
|
3797
3859
|
else:
|
3798
3860
|
print(f"{os_name}, returned 'None'")
|
3799
3861
|
return None
|
3862
|
+
|
3800
3863
|
if not full:
|
3801
3864
|
return get_os_type()
|
3865
|
+
|
3802
3866
|
def get_os_info():
|
3803
3867
|
"""Get the detailed OS name, version, and other platform-specific details."""
|
3804
3868
|
|
@@ -4451,16 +4515,30 @@ def listdir(
|
|
4451
4515
|
return Box(f.to_dict(orient="series"))
|
4452
4516
|
|
4453
4517
|
|
4454
|
-
def
|
4455
|
-
|
4456
|
-
|
4457
|
-
|
4458
|
-
|
4459
|
-
|
4518
|
+
def listpkg(where="env", verbose=False):
|
4519
|
+
"""list all pacakages"""
|
4520
|
+
|
4521
|
+
def listfunc_(lib_name, opt="call"):
|
4522
|
+
"""list functions in specific lib"""
|
4523
|
+
if opt == "call":
|
4524
|
+
funcs = [
|
4525
|
+
func
|
4526
|
+
for func in dir(lib_name)
|
4527
|
+
if callable(getattr(lib_name, func))
|
4528
|
+
if not func.startswith("__")
|
4529
|
+
]
|
4530
|
+
else:
|
4531
|
+
funcs = dir(lib_name)
|
4532
|
+
return funcs
|
4460
4533
|
|
4534
|
+
if any([i in where.lower() for i in ["all", "env"]]):
|
4535
|
+
import pkg_resources
|
4461
4536
|
|
4462
|
-
|
4463
|
-
|
4537
|
+
lst_pkg = [pkg.key for pkg in pkg_resources.working_set]
|
4538
|
+
else:
|
4539
|
+
lst_pkg = listfunc_(where)
|
4540
|
+
print(lst_pkg) if verbose else None
|
4541
|
+
return lst_pkg
|
4464
4542
|
|
4465
4543
|
|
4466
4544
|
def copy(src, dst, overwrite=False, verbose=True):
|
@@ -4839,7 +4917,8 @@ def is_image(fpath):
|
|
4839
4917
|
bool: True if the file is a recognized image, False otherwise.
|
4840
4918
|
"""
|
4841
4919
|
from PIL import Image
|
4842
|
-
|
4920
|
+
|
4921
|
+
if isinstance(fpath, str):
|
4843
4922
|
import mimetypes
|
4844
4923
|
|
4845
4924
|
# Known image MIME types
|
@@ -4896,6 +4975,7 @@ def is_image(fpath):
|
|
4896
4975
|
|
4897
4976
|
return False
|
4898
4977
|
|
4978
|
+
|
4899
4979
|
def is_video(fpath):
|
4900
4980
|
"""
|
4901
4981
|
Determine if a given file is a video based on MIME type and file extension.
|
@@ -5205,7 +5285,14 @@ def str2list(str_):
|
|
5205
5285
|
[l.append(x) for x in str_]
|
5206
5286
|
return l
|
5207
5287
|
|
5208
|
-
|
5288
|
+
|
5289
|
+
def str2words(
|
5290
|
+
content,
|
5291
|
+
method="combined",
|
5292
|
+
custom_dict=None,
|
5293
|
+
sym_spell_params=None,
|
5294
|
+
use_threading=True,
|
5295
|
+
):
|
5209
5296
|
"""
|
5210
5297
|
Ultimate text correction function supporting multiple methods,
|
5211
5298
|
lists or strings, and domain-specific corrections.
|
@@ -5241,7 +5328,7 @@ def str2words(content, method="combined", custom_dict=None, sym_spell_params=Non
|
|
5241
5328
|
"""Segment concatenated words into separate words."""
|
5242
5329
|
segmented = sym_spell.word_segmentation(text)
|
5243
5330
|
return segmented.corrected_string
|
5244
|
-
|
5331
|
+
|
5245
5332
|
@lru_cache(maxsize=1000) # Cache results for repeated corrections
|
5246
5333
|
def advanced_correction(word, sym_spell):
|
5247
5334
|
"""Correct a single word using SymSpell."""
|
@@ -5251,6 +5338,7 @@ def str2words(content, method="combined", custom_dict=None, sym_spell_params=Non
|
|
5251
5338
|
def apply_custom_corrections(word, custom_dict):
|
5252
5339
|
"""Apply domain-specific corrections using a custom dictionary."""
|
5253
5340
|
return custom_dict.get(word.lower(), word)
|
5341
|
+
|
5254
5342
|
def preserve_case(original_word, corrected_word):
|
5255
5343
|
"""
|
5256
5344
|
Preserve the case of the original word in the corrected word.
|
@@ -5261,6 +5349,7 @@ def str2words(content, method="combined", custom_dict=None, sym_spell_params=Non
|
|
5261
5349
|
return corrected_word.capitalize()
|
5262
5350
|
else:
|
5263
5351
|
return corrected_word.lower()
|
5352
|
+
|
5264
5353
|
def process_string(text, method, sym_spell=None, custom_dict=None):
|
5265
5354
|
"""
|
5266
5355
|
Process a single string for spelling corrections.
|
@@ -5298,13 +5387,21 @@ def str2words(content, method="combined", custom_dict=None, sym_spell_params=Non
|
|
5298
5387
|
if isinstance(content, list):
|
5299
5388
|
if use_threading:
|
5300
5389
|
with ThreadPoolExecutor() as executor:
|
5301
|
-
corrected_content = list(
|
5390
|
+
corrected_content = list(
|
5391
|
+
executor.map(
|
5392
|
+
lambda x: process_string(x, method, sym_spell, custom_dict),
|
5393
|
+
content,
|
5394
|
+
)
|
5395
|
+
)
|
5302
5396
|
return corrected_content
|
5303
5397
|
else:
|
5304
|
-
return [
|
5398
|
+
return [
|
5399
|
+
process_string(item, method, sym_spell, custom_dict) for item in content
|
5400
|
+
]
|
5305
5401
|
else:
|
5306
5402
|
return process_string(content, method, sym_spell, custom_dict)
|
5307
5403
|
|
5404
|
+
|
5308
5405
|
def load_img(fpath):
|
5309
5406
|
"""
|
5310
5407
|
Load an image from the specified file path.
|
@@ -5327,7 +5424,7 @@ def load_img(fpath):
|
|
5327
5424
|
raise OSError(f"Unable to open file '{fpath}' or it is not a valid image file.")
|
5328
5425
|
|
5329
5426
|
|
5330
|
-
def apply_filter(img, *args,verbose=True):
|
5427
|
+
def apply_filter(img, *args, verbose=True):
|
5331
5428
|
# def apply_filter(img, filter_name, filter_value=None):
|
5332
5429
|
"""
|
5333
5430
|
Apply the specified filter to the image.
|
@@ -5341,7 +5438,13 @@ def apply_filter(img, *args,verbose=True):
|
|
5341
5438
|
from PIL import ImageFilter
|
5342
5439
|
|
5343
5440
|
def correct_filter_name(filter_name):
|
5344
|
-
if all(
|
5441
|
+
if all(
|
5442
|
+
[
|
5443
|
+
"b" in filter_name.lower(),
|
5444
|
+
"ur" in filter_name.lower(),
|
5445
|
+
"box" not in filter_name.lower(),
|
5446
|
+
]
|
5447
|
+
):
|
5345
5448
|
return "BLUR"
|
5346
5449
|
elif "cont" in filter_name.lower():
|
5347
5450
|
return "Contour"
|
@@ -5409,7 +5512,7 @@ def apply_filter(img, *args,verbose=True):
|
|
5409
5512
|
else:
|
5410
5513
|
filter_value = arg
|
5411
5514
|
if verbose:
|
5412
|
-
print(f
|
5515
|
+
print(f"processing {filter_name}")
|
5413
5516
|
filter_name = filter_name.upper() # Ensure filter name is uppercase
|
5414
5517
|
|
5415
5518
|
# Supported filters
|
@@ -5470,8 +5573,8 @@ def detect_angle(image, by="median", template=None):
|
|
5470
5573
|
import cv2
|
5471
5574
|
|
5472
5575
|
# Convert to grayscale
|
5473
|
-
if np.array(image).shape[-1]>3:
|
5474
|
-
image=np.array(image)[
|
5576
|
+
if np.array(image).shape[-1] > 3:
|
5577
|
+
image = np.array(image)[:, :, :3]
|
5475
5578
|
gray_image = rgb2gray(image)
|
5476
5579
|
|
5477
5580
|
# Detect edges using Canny edge detector
|
@@ -5483,8 +5586,16 @@ def detect_angle(image, by="median", template=None):
|
|
5483
5586
|
if not lines and any(["me" in by, "pca" in by]):
|
5484
5587
|
print("No lines detected. Adjust the edge detection parameters.")
|
5485
5588
|
return 0
|
5486
|
-
methods=[
|
5487
|
-
|
5589
|
+
methods = [
|
5590
|
+
"mean",
|
5591
|
+
"median",
|
5592
|
+
"pca",
|
5593
|
+
"gradient orientation",
|
5594
|
+
"template matching",
|
5595
|
+
"moments",
|
5596
|
+
"fft",
|
5597
|
+
]
|
5598
|
+
by = strcmp(by, methods)[0]
|
5488
5599
|
# Hough Transform-based angle detection (Median/Mean)
|
5489
5600
|
if "me" in by.lower():
|
5490
5601
|
angles = []
|
@@ -5573,17 +5684,19 @@ def detect_angle(image, by="median", template=None):
|
|
5573
5684
|
print(f"Unknown method {by}: supported methods: {methods}")
|
5574
5685
|
return 0
|
5575
5686
|
|
5576
|
-
|
5577
|
-
|
5578
|
-
|
5579
|
-
|
5580
|
-
|
5581
|
-
|
5582
|
-
|
5583
|
-
|
5584
|
-
|
5585
|
-
|
5586
|
-
|
5687
|
+
|
5688
|
+
def imgsets(
|
5689
|
+
img,
|
5690
|
+
auto: bool = True,
|
5691
|
+
size=None,
|
5692
|
+
figsize=None,
|
5693
|
+
dpi: int = 200,
|
5694
|
+
show_axis: bool = False,
|
5695
|
+
plot_: bool = True,
|
5696
|
+
verbose: bool = False,
|
5697
|
+
model: str = "isnet-general-use",
|
5698
|
+
**kwargs,
|
5699
|
+
):
|
5587
5700
|
"""
|
5588
5701
|
Apply various enhancements and filters to an image using PIL's ImageEnhance and ImageFilter modules.
|
5589
5702
|
|
@@ -5619,7 +5732,8 @@ def imgsets(img,
|
|
5619
5732
|
"""
|
5620
5733
|
|
5621
5734
|
import matplotlib.pyplot as plt
|
5622
|
-
from PIL import ImageEnhance, ImageOps,Image
|
5735
|
+
from PIL import ImageEnhance, ImageOps, Image
|
5736
|
+
|
5623
5737
|
supported_filters = [
|
5624
5738
|
"BLUR",
|
5625
5739
|
"CONTOUR",
|
@@ -5658,8 +5772,8 @@ def imgsets(img,
|
|
5658
5772
|
"birefnet-cod": "concealed object detection (COD).",
|
5659
5773
|
"birefnet-massive": "A pre-trained model with massive dataset.",
|
5660
5774
|
}
|
5661
|
-
models_support_rem=list(rem_models.keys())
|
5662
|
-
str_usage="""
|
5775
|
+
models_support_rem = list(rem_models.keys())
|
5776
|
+
str_usage = """
|
5663
5777
|
imgsets(dir_img, auto=1, color=1.5, plot_=0)
|
5664
5778
|
imgsets(dir_img, color=2)
|
5665
5779
|
imgsets(dir_img, pad=(300, 300), bgcolor=(73, 162, 127), plot_=0)
|
@@ -5674,8 +5788,11 @@ def imgsets(img,
|
|
5674
5788
|
def gamma_correction(image, gamma=1.0, v_max=255):
|
5675
5789
|
# adjust gama value
|
5676
5790
|
inv_gamma = 1.0 / gamma
|
5677
|
-
lut = [
|
5678
|
-
|
5791
|
+
lut = [
|
5792
|
+
int((i / float(v_max)) ** inv_gamma * int(v_max)) for i in range(int(v_max))
|
5793
|
+
]
|
5794
|
+
return lut # image.point(lut)
|
5795
|
+
|
5679
5796
|
def auto_enhance(img):
|
5680
5797
|
"""
|
5681
5798
|
Automatically enhances the image based on its characteristics, including brightness,
|
@@ -5690,6 +5807,7 @@ def imgsets(img,
|
|
5690
5807
|
"""
|
5691
5808
|
from PIL import Image, ImageEnhance, ImageOps, ImageFilter
|
5692
5809
|
import numpy as np
|
5810
|
+
|
5693
5811
|
# Determine the bit depth based on the image mode
|
5694
5812
|
try:
|
5695
5813
|
if img.mode in ["1", "L", "P", "RGB", "YCbCr", "LAB", "HSV"]:
|
@@ -5706,10 +5824,10 @@ def imgsets(img,
|
|
5706
5824
|
# Initialize enhancement factors
|
5707
5825
|
enhancements = {
|
5708
5826
|
"brightness": 1.0,
|
5709
|
-
"contrast": 0
|
5827
|
+
"contrast": 0, # autocontrasted
|
5710
5828
|
"color": 1.35,
|
5711
5829
|
"sharpness": 1.0,
|
5712
|
-
"gamma": 1.0
|
5830
|
+
"gamma": 1.0,
|
5713
5831
|
}
|
5714
5832
|
|
5715
5833
|
# Calculate brightness and contrast for each channel
|
@@ -5719,7 +5837,9 @@ def imgsets(img,
|
|
5719
5837
|
for channel in range(num_channels):
|
5720
5838
|
channel_histogram = img.split()[channel].histogram()
|
5721
5839
|
total_pixels = sum(channel_histogram)
|
5722
|
-
brightness =
|
5840
|
+
brightness = (
|
5841
|
+
sum(i * w for i, w in enumerate(channel_histogram)) / total_pixels
|
5842
|
+
)
|
5723
5843
|
channel_min, channel_max = img.split()[channel].getextrema()
|
5724
5844
|
contrast = channel_max - channel_min
|
5725
5845
|
# Adjust calculations based on bit depth
|
@@ -5752,7 +5872,9 @@ def imgsets(img,
|
|
5752
5872
|
# Use edge detection to estimate sharpness need
|
5753
5873
|
edges = img.filter(ImageFilter.FIND_EDGES).convert("L")
|
5754
5874
|
avg_edge_intensity = np.mean(np.array(edges))
|
5755
|
-
enhancements["sharpness"] = min(
|
5875
|
+
enhancements["sharpness"] = min(
|
5876
|
+
2.0, max(0.5, 1.0 + avg_edge_intensity / normalization_factor)
|
5877
|
+
)
|
5756
5878
|
# img = sharpness_enhancer.enhance(enhancements["sharpness"])
|
5757
5879
|
|
5758
5880
|
# # Apply gamma correction
|
@@ -5768,19 +5890,39 @@ def imgsets(img,
|
|
5768
5890
|
# Return the enhancements and the enhanced image
|
5769
5891
|
return enhancements
|
5770
5892
|
|
5771
|
-
|
5772
5893
|
# Load image if input is a file path
|
5773
5894
|
if isinstance(img, str):
|
5774
5895
|
img = load_img(img)
|
5775
|
-
img_update = img.copy()
|
5896
|
+
img_update = img.copy()
|
5776
5897
|
|
5777
5898
|
if auto:
|
5778
5899
|
kwargs = {**auto_enhance(img_update), **kwargs}
|
5779
|
-
params=[
|
5780
|
-
|
5781
|
-
|
5900
|
+
params = [
|
5901
|
+
"sharp",
|
5902
|
+
"color",
|
5903
|
+
"contrast",
|
5904
|
+
"bright",
|
5905
|
+
"crop",
|
5906
|
+
"rotate",
|
5907
|
+
"size",
|
5908
|
+
"resize",
|
5909
|
+
"thumbnail",
|
5910
|
+
"cover",
|
5911
|
+
"contain",
|
5912
|
+
"filter",
|
5913
|
+
"fit",
|
5914
|
+
"pad",
|
5915
|
+
"rem",
|
5916
|
+
"rm",
|
5917
|
+
"back",
|
5918
|
+
"bg_color",
|
5919
|
+
"cut",
|
5920
|
+
"gamma",
|
5921
|
+
"flip",
|
5922
|
+
"booster",
|
5923
|
+
]
|
5782
5924
|
for k, value in kwargs.items():
|
5783
|
-
k = strcmp(k, params)[0]
|
5925
|
+
k = strcmp(k, params)[0] # correct the param name
|
5784
5926
|
if "shar" in k.lower():
|
5785
5927
|
enhancer = ImageEnhance.Sharpness(img_update)
|
5786
5928
|
img_update = enhancer.enhance(value)
|
@@ -5810,11 +5952,11 @@ def imgsets(img,
|
|
5810
5952
|
value = detect_angle(img_update, by=value)
|
5811
5953
|
print(f"rotated by {value}°")
|
5812
5954
|
img_update = img_update.rotate(value)
|
5813
|
-
elif
|
5814
|
-
if
|
5955
|
+
elif "flip" in k.lower():
|
5956
|
+
if "l" in value and "r" in value:
|
5815
5957
|
# left/right
|
5816
5958
|
img_update = img_update.transpose(Image.FLIP_LEFT_RIGHT)
|
5817
|
-
elif any([
|
5959
|
+
elif any(["u" in value and "d" in value, "t" in value and "b" in value]):
|
5818
5960
|
# up/down or top/bottom
|
5819
5961
|
img_update = img_update.transpose(Image.FLIP_TOP_BOTTOM)
|
5820
5962
|
elif "si" in k.lower():
|
@@ -5828,12 +5970,14 @@ def imgsets(img,
|
|
5828
5970
|
img_update = ImageOps.cover(img_update, size=value)
|
5829
5971
|
elif "contain" in k.lower():
|
5830
5972
|
img_update = ImageOps.contain(img_update, size=value)
|
5831
|
-
elif "fi" in k.lower() and "t" in k.lower():
|
5973
|
+
elif "fi" in k.lower() and "t" in k.lower(): # filter
|
5832
5974
|
if isinstance(value, dict):
|
5833
5975
|
if verbose:
|
5834
5976
|
print(f"supported filter: {supported_filters}")
|
5835
5977
|
for filter_name, filter_value in value.items():
|
5836
|
-
img_update = apply_filter(
|
5978
|
+
img_update = apply_filter(
|
5979
|
+
img_update, filter_name, filter_value, verbose=verbose
|
5980
|
+
)
|
5837
5981
|
else:
|
5838
5982
|
img_update = ImageOps.fit(img_update, size=value)
|
5839
5983
|
elif "pad" in k.lower():
|
@@ -5842,11 +5986,12 @@ def imgsets(img,
|
|
5842
5986
|
img_update = ImageOps.pad(img_update, size=value)
|
5843
5987
|
elif "rem" in k.lower() or "rm" in k.lower() or "back" in k.lower():
|
5844
5988
|
from rembg import remove, new_session
|
5989
|
+
|
5845
5990
|
if verbose:
|
5846
5991
|
preview(rem_models)
|
5847
|
-
|
5992
|
+
|
5848
5993
|
print(f"supported modles: {models_support_rem}")
|
5849
|
-
model=strcmp(model, models_support_rem)[0]
|
5994
|
+
model = strcmp(model, models_support_rem)[0]
|
5850
5995
|
session = new_session(model)
|
5851
5996
|
if isinstance(value, bool):
|
5852
5997
|
print(f"using model:{model}")
|
@@ -5854,21 +5999,25 @@ def imgsets(img,
|
|
5854
5999
|
elif value and isinstance(value, (int, float, list)):
|
5855
6000
|
if verbose:
|
5856
6001
|
print("https://github.com/danielgatis/rembg/blob/main/USAGE.md")
|
5857
|
-
print(
|
6002
|
+
print(
|
6003
|
+
f"rm=True # using default setting;\nrm=(240,10,10)\n'foreground_threshold'(240) and 'background_threshold' (10) values used to determine foreground and background pixels. \nThe 'erode_structure_size'(10) parameter specifies the size of the erosion structure to be applied to the mask."
|
6004
|
+
)
|
5858
6005
|
if isinstance(value, int):
|
5859
6006
|
value = [value]
|
5860
6007
|
if len(value) < 2:
|
5861
6008
|
img_update = remove(
|
5862
6009
|
img_update,
|
5863
6010
|
alpha_matting=True,
|
5864
|
-
alpha_matting_background_threshold=value,
|
6011
|
+
alpha_matting_background_threshold=value,
|
6012
|
+
session=session,
|
5865
6013
|
)
|
5866
6014
|
elif 2 <= len(value) < 3:
|
5867
6015
|
img_update = remove(
|
5868
6016
|
img_update,
|
5869
6017
|
alpha_matting=True,
|
5870
6018
|
alpha_matting_background_threshold=value[0],
|
5871
|
-
alpha_matting_foreground_threshold=value[1],
|
6019
|
+
alpha_matting_foreground_threshold=value[1],
|
6020
|
+
session=session,
|
5872
6021
|
)
|
5873
6022
|
elif 3 <= len(value) < 4:
|
5874
6023
|
img_update = remove(
|
@@ -5876,7 +6025,8 @@ def imgsets(img,
|
|
5876
6025
|
alpha_matting=True,
|
5877
6026
|
alpha_matting_background_threshold=value[0],
|
5878
6027
|
alpha_matting_foreground_threshold=value[1],
|
5879
|
-
alpha_matting_erode_size=value[2],
|
6028
|
+
alpha_matting_erode_size=value[2],
|
6029
|
+
session=session,
|
5880
6030
|
)
|
5881
6031
|
elif isinstance(value, tuple): # replace the background color
|
5882
6032
|
if len(value) == 3:
|
@@ -5885,7 +6035,10 @@ def imgsets(img,
|
|
5885
6035
|
elif isinstance(value, str):
|
5886
6036
|
# use custom model
|
5887
6037
|
print(f"using model:{strcmp(value, models_support_rem)[0]}")
|
5888
|
-
img_update = remove(
|
6038
|
+
img_update = remove(
|
6039
|
+
img_update,
|
6040
|
+
session=new_session(strcmp(value, models_support_rem)[0]),
|
6041
|
+
)
|
5889
6042
|
elif "bg" in k.lower() and "color" in k.lower():
|
5890
6043
|
from rembg import remove
|
5891
6044
|
|
@@ -5895,30 +6048,32 @@ def imgsets(img,
|
|
5895
6048
|
if len(value) == 3:
|
5896
6049
|
value += (255,)
|
5897
6050
|
img_update = remove(img_update, bgcolor=value)
|
5898
|
-
elif
|
6051
|
+
elif "boost" in k.lower():
|
5899
6052
|
import torch
|
5900
6053
|
from realesrgan import RealESRGANer
|
6054
|
+
|
5901
6055
|
if verbose:
|
5902
6056
|
print("Applying Real-ESRGAN for image reconstruction...")
|
5903
6057
|
if isinstance(value, bool):
|
5904
|
-
scale=4
|
6058
|
+
scale = 4
|
5905
6059
|
elif isinstance(value, (float, int)):
|
5906
|
-
scale=value
|
6060
|
+
scale = value
|
5907
6061
|
else:
|
5908
|
-
scale=4
|
6062
|
+
scale = 4
|
5909
6063
|
|
5910
6064
|
# try:
|
5911
6065
|
device = "cuda" if torch.cuda.is_available() else "cpu"
|
5912
6066
|
dir_curr_script = os.path.dirname(os.path.abspath(__file__))
|
5913
6067
|
model_path = dir_curr_script + "/data/RealESRGAN_x4plus.pth"
|
5914
|
-
model_RealESRGAN = RealESRGANer(
|
5915
|
-
|
5916
|
-
|
5917
|
-
|
5918
|
-
|
6068
|
+
model_RealESRGAN = RealESRGANer(
|
6069
|
+
device=device,
|
6070
|
+
scale=scale,
|
6071
|
+
model_path=model_path,
|
6072
|
+
model="RealESRGAN_x4plus",
|
6073
|
+
)
|
5919
6074
|
# https://github.com/xinntao/Real-ESRGAN?tab=readme-ov-file#python-script
|
5920
6075
|
|
5921
|
-
img_update = model_RealESRGAN.enhance(np.array(img_update))[0]
|
6076
|
+
img_update = model_RealESRGAN.enhance(np.array(img_update))[0]
|
5922
6077
|
# except Exception as e:
|
5923
6078
|
# print(f"Failed to apply Real-ESRGAN: {e}")
|
5924
6079
|
|
@@ -10320,7 +10475,7 @@ def get_loc(input_data, user_agent="0413@mygmail.com)", verbose=True):
|
|
10320
10475
|
print(f"Could not find {input_data}.")
|
10321
10476
|
return location
|
10322
10477
|
except Exception as e:
|
10323
|
-
print(f
|
10478
|
+
print(f"Error: {e}")
|
10324
10479
|
return
|
10325
10480
|
|
10326
10481
|
# Case 2: Input is latitude and longitude (float or tuple)
|
@@ -10516,6 +10671,7 @@ def depass(encrypted_code: str, method: str = "AES", key: str = None):
|
|
10516
10671
|
else:
|
10517
10672
|
raise ValueError("Unsupported decryption method")
|
10518
10673
|
|
10674
|
+
|
10519
10675
|
def get_clip(dir_save=None):
|
10520
10676
|
"""
|
10521
10677
|
Master function to extract content from the clipboard (text, URL, or image).
|
@@ -10537,6 +10693,7 @@ def get_clip(dir_save=None):
|
|
10537
10693
|
import pyperclip
|
10538
10694
|
from PIL import ImageGrab, Image
|
10539
10695
|
import validators
|
10696
|
+
|
10540
10697
|
# 1. Check for text in the clipboard
|
10541
10698
|
clipboard_content = pyperclip.paste()
|
10542
10699
|
if clipboard_content:
|
@@ -10568,7 +10725,8 @@ def get_clip(dir_save=None):
|
|
10568
10725
|
print(f"An error occurred: {e}")
|
10569
10726
|
return result
|
10570
10727
|
|
10571
|
-
|
10728
|
+
|
10729
|
+
def keyboard(*args, action="press", n_click=1, interval=0, verbose=False, **kwargs):
|
10572
10730
|
"""
|
10573
10731
|
Simulates keyboard input using pyautogui.
|
10574
10732
|
|
@@ -10583,45 +10741,231 @@ def keyboard(*args, action='press', n_click=1,interval=0,verbose=False,**kwargs)
|
|
10583
10741
|
keyboard("command", "d", action="shorcut")
|
10584
10742
|
"""
|
10585
10743
|
import pyautogui
|
10586
|
-
|
10587
|
-
|
10588
|
-
|
10589
|
-
|
10590
|
-
|
10591
|
-
|
10592
|
-
|
10593
|
-
|
10594
|
-
|
10595
|
-
|
10596
|
-
|
10597
|
-
|
10598
|
-
|
10599
|
-
|
10600
|
-
|
10601
|
-
|
10602
|
-
|
10603
|
-
|
10604
|
-
|
10605
|
-
|
10606
|
-
|
10607
|
-
|
10608
|
-
|
10609
|
-
|
10610
|
-
|
10611
|
-
|
10744
|
+
|
10745
|
+
input_key = args
|
10746
|
+
|
10747
|
+
actions = ["press", "keyDown", "keyUp", "hold", "release", "hotkey", "shortcut"]
|
10748
|
+
action = strcmp(action, actions)[0]
|
10749
|
+
keyboard_keys_ = [
|
10750
|
+
"\t",
|
10751
|
+
"\n",
|
10752
|
+
"\r",
|
10753
|
+
" ",
|
10754
|
+
"!",
|
10755
|
+
'"',
|
10756
|
+
"#",
|
10757
|
+
"$",
|
10758
|
+
"%",
|
10759
|
+
"&",
|
10760
|
+
"'",
|
10761
|
+
"(",
|
10762
|
+
")",
|
10763
|
+
"*",
|
10764
|
+
"+",
|
10765
|
+
",",
|
10766
|
+
"-",
|
10767
|
+
".",
|
10768
|
+
"/",
|
10769
|
+
"0",
|
10770
|
+
"1",
|
10771
|
+
"2",
|
10772
|
+
"3",
|
10773
|
+
"4",
|
10774
|
+
"5",
|
10775
|
+
"6",
|
10776
|
+
"7",
|
10777
|
+
"8",
|
10778
|
+
"9",
|
10779
|
+
":",
|
10780
|
+
";",
|
10781
|
+
"<",
|
10782
|
+
"=",
|
10783
|
+
">",
|
10784
|
+
"?",
|
10785
|
+
"@",
|
10786
|
+
"[",
|
10787
|
+
"\\",
|
10788
|
+
"]",
|
10789
|
+
"^",
|
10790
|
+
"_",
|
10791
|
+
"`",
|
10792
|
+
"a",
|
10793
|
+
"b",
|
10794
|
+
"c",
|
10795
|
+
"d",
|
10796
|
+
"e",
|
10797
|
+
"f",
|
10798
|
+
"g",
|
10799
|
+
"h",
|
10800
|
+
"i",
|
10801
|
+
"j",
|
10802
|
+
"k",
|
10803
|
+
"l",
|
10804
|
+
"m",
|
10805
|
+
"n",
|
10806
|
+
"o",
|
10807
|
+
"p",
|
10808
|
+
"q",
|
10809
|
+
"r",
|
10810
|
+
"s",
|
10811
|
+
"t",
|
10812
|
+
"u",
|
10813
|
+
"v",
|
10814
|
+
"w",
|
10815
|
+
"x",
|
10816
|
+
"y",
|
10817
|
+
"z",
|
10818
|
+
"{",
|
10819
|
+
"|",
|
10820
|
+
"}",
|
10821
|
+
"~",
|
10822
|
+
"accept",
|
10823
|
+
"add",
|
10824
|
+
"alt",
|
10825
|
+
"altleft",
|
10826
|
+
"altright",
|
10827
|
+
"apps",
|
10828
|
+
"backspace",
|
10829
|
+
"browserback",
|
10830
|
+
"browserfavorites",
|
10831
|
+
"browserforward",
|
10832
|
+
"browserhome",
|
10833
|
+
"browserrefresh",
|
10834
|
+
"browsersearch",
|
10835
|
+
"browserstop",
|
10836
|
+
"capslock",
|
10837
|
+
"clear",
|
10838
|
+
"convert",
|
10839
|
+
"ctrl",
|
10840
|
+
"ctrlleft",
|
10841
|
+
"ctrlright",
|
10842
|
+
"decimal",
|
10843
|
+
"del",
|
10844
|
+
"delete",
|
10845
|
+
"divide",
|
10846
|
+
"down",
|
10847
|
+
"end",
|
10848
|
+
"enter",
|
10849
|
+
"esc",
|
10850
|
+
"escape",
|
10851
|
+
"execute",
|
10852
|
+
"f1",
|
10853
|
+
"f10",
|
10854
|
+
"f11",
|
10855
|
+
"f12",
|
10856
|
+
"f13",
|
10857
|
+
"f14",
|
10858
|
+
"f15",
|
10859
|
+
"f16",
|
10860
|
+
"f17",
|
10861
|
+
"f18",
|
10862
|
+
"f19",
|
10863
|
+
"f2",
|
10864
|
+
"f20",
|
10865
|
+
"f21",
|
10866
|
+
"f22",
|
10867
|
+
"f23",
|
10868
|
+
"f24",
|
10869
|
+
"f3",
|
10870
|
+
"f4",
|
10871
|
+
"f5",
|
10872
|
+
"f6",
|
10873
|
+
"f7",
|
10874
|
+
"f8",
|
10875
|
+
"f9",
|
10876
|
+
"final",
|
10877
|
+
"fn",
|
10878
|
+
"hanguel",
|
10879
|
+
"hangul",
|
10880
|
+
"hanja",
|
10881
|
+
"help",
|
10882
|
+
"home",
|
10883
|
+
"insert",
|
10884
|
+
"junja",
|
10885
|
+
"kana",
|
10886
|
+
"kanji",
|
10887
|
+
"launchapp1",
|
10888
|
+
"launchapp2",
|
10889
|
+
"launchmail",
|
10890
|
+
"launchmediaselect",
|
10891
|
+
"left",
|
10892
|
+
"modechange",
|
10893
|
+
"multiply",
|
10894
|
+
"nexttrack",
|
10895
|
+
"nonconvert",
|
10896
|
+
"num0",
|
10897
|
+
"num1",
|
10898
|
+
"num2",
|
10899
|
+
"num3",
|
10900
|
+
"num4",
|
10901
|
+
"num5",
|
10902
|
+
"num6",
|
10903
|
+
"num7",
|
10904
|
+
"num8",
|
10905
|
+
"num9",
|
10906
|
+
"numlock",
|
10907
|
+
"pagedown",
|
10908
|
+
"pageup",
|
10909
|
+
"pause",
|
10910
|
+
"pgdn",
|
10911
|
+
"pgup",
|
10912
|
+
"playpause",
|
10913
|
+
"prevtrack",
|
10914
|
+
"print",
|
10915
|
+
"printscreen",
|
10916
|
+
"prntscrn",
|
10917
|
+
"prtsc",
|
10918
|
+
"prtscr",
|
10919
|
+
"return",
|
10920
|
+
"right",
|
10921
|
+
"scrolllock",
|
10922
|
+
"select",
|
10923
|
+
"separator",
|
10924
|
+
"shift",
|
10925
|
+
"shiftleft",
|
10926
|
+
"shiftright",
|
10927
|
+
"sleep",
|
10928
|
+
"space",
|
10929
|
+
"stop",
|
10930
|
+
"subtract",
|
10931
|
+
"tab",
|
10932
|
+
"up",
|
10933
|
+
"volumedown",
|
10934
|
+
"volumemute",
|
10935
|
+
"volumeup",
|
10936
|
+
"win",
|
10937
|
+
"winleft",
|
10938
|
+
"winright",
|
10939
|
+
"yen",
|
10940
|
+
"command",
|
10941
|
+
"option",
|
10942
|
+
"optionleft",
|
10943
|
+
"optionright",
|
10944
|
+
]
|
10612
10945
|
if verbose:
|
10613
10946
|
print(f"supported keys: {keyboard_keys_}")
|
10614
10947
|
|
10615
|
-
if action not in [
|
10948
|
+
if action not in ["hotkey", "shortcut"]:
|
10616
10949
|
if not isinstance(input_key, list):
|
10617
|
-
input_key=list(input_key)
|
10618
|
-
input_key = [strcmp(i, keyboard_keys_)[0] for i in input_key
|
10950
|
+
input_key = list(input_key)
|
10951
|
+
input_key = [strcmp(i, keyboard_keys_)[0] for i in input_key]
|
10619
10952
|
|
10620
10953
|
# correct action
|
10621
|
-
cmd_keys = [
|
10954
|
+
cmd_keys = [
|
10955
|
+
"command",
|
10956
|
+
"option",
|
10957
|
+
"optionleft",
|
10958
|
+
"optionright",
|
10959
|
+
"win",
|
10960
|
+
"winleft",
|
10961
|
+
"winright",
|
10962
|
+
"ctrl",
|
10963
|
+
"ctrlleft",
|
10964
|
+
"ctrlright",
|
10965
|
+
]
|
10622
10966
|
try:
|
10623
10967
|
if any([i in cmd_keys for i in input_key]):
|
10624
|
-
action=
|
10968
|
+
action = "hotkey"
|
10625
10969
|
except:
|
10626
10970
|
pass
|
10627
10971
|
|
@@ -10633,23 +10977,24 @@ def keyboard(*args, action='press', n_click=1,interval=0,verbose=False,**kwargs)
|
|
10633
10977
|
for key in input_key:
|
10634
10978
|
pyautogui.press(key)
|
10635
10979
|
pyautogui.sleep(interval)
|
10636
|
-
elif action in [
|
10980
|
+
elif action in ["keyDown", "hold"]:
|
10637
10981
|
# pyautogui.keyDown(input_key)
|
10638
10982
|
for _ in range(n_click):
|
10639
10983
|
for key in input_key:
|
10640
10984
|
pyautogui.keyDown(key)
|
10641
10985
|
pyautogui.sleep(interval)
|
10642
|
-
|
10643
|
-
elif action in [
|
10986
|
+
|
10987
|
+
elif action in ["keyUp", "release"]:
|
10644
10988
|
# pyautogui.keyUp(input_key)
|
10645
10989
|
for _ in range(n_click):
|
10646
10990
|
for key in input_key:
|
10647
10991
|
pyautogui.keyUp(key)
|
10648
10992
|
pyautogui.sleep(interval)
|
10649
|
-
|
10650
|
-
elif action in [
|
10993
|
+
|
10994
|
+
elif action in ["hotkey", "shortcut"]:
|
10651
10995
|
pyautogui.hotkey(input_key)
|
10652
|
-
|
10996
|
+
|
10997
|
+
|
10653
10998
|
def mouse(
|
10654
10999
|
*args, # loc
|
10655
11000
|
action: str = "move",
|
@@ -10657,7 +11002,7 @@ def mouse(
|
|
10657
11002
|
loc_type: str = "absolute", # 'absolute', 'relative'
|
10658
11003
|
region: tuple = None, # (tuple, optional): A region (x, y, width, height) to search for the image.
|
10659
11004
|
image_path: str = None,
|
10660
|
-
wait:float = 0,
|
11005
|
+
wait: float = 0,
|
10661
11006
|
text: str = None,
|
10662
11007
|
confidence: float = 0.8,
|
10663
11008
|
button: str = "left",
|
@@ -10725,15 +11070,15 @@ def mouse(
|
|
10725
11070
|
"up",
|
10726
11071
|
"hold",
|
10727
11072
|
"press",
|
10728
|
-
"release"
|
11073
|
+
"release",
|
10729
11074
|
]
|
10730
11075
|
action = strcmp(action, what_action)[0]
|
10731
11076
|
# get the locations
|
10732
11077
|
location = None
|
10733
11078
|
if any([x_offset is None, y_offset is None]):
|
10734
11079
|
if region is None:
|
10735
|
-
w,h=pyautogui.size()
|
10736
|
-
region=(0,0,w,h)
|
11080
|
+
w, h = pyautogui.size()
|
11081
|
+
region = (0, 0, w, h)
|
10737
11082
|
print(region)
|
10738
11083
|
try:
|
10739
11084
|
print(image_path)
|
@@ -10749,16 +11094,16 @@ def mouse(
|
|
10749
11094
|
x, y = pyautogui.center(location)
|
10750
11095
|
x += x_offset if x_offset else 0
|
10751
11096
|
y += y_offset if y_offset else 0
|
10752
|
-
x_offset, y_offset = x,y
|
11097
|
+
x_offset, y_offset = x, y
|
10753
11098
|
print(action)
|
10754
|
-
if action in [
|
11099
|
+
if action in ["locate"]:
|
10755
11100
|
x, y = pyautogui.position()
|
10756
|
-
elif action in ["click", "double_click","triple_click"]:
|
11101
|
+
elif action in ["click", "double_click", "triple_click"]:
|
10757
11102
|
# if location:
|
10758
11103
|
# x, y = pyautogui.center(location)
|
10759
11104
|
# x += x_offset
|
10760
11105
|
# y += y_offset
|
10761
|
-
# pyautogui.moveTo(x, y, duration=duration)
|
11106
|
+
# pyautogui.moveTo(x, y, duration=duration)
|
10762
11107
|
# if action == "click":
|
10763
11108
|
# pyautogui.click(x=x, y=y, clicks=n_click, interval=interval, button=button)
|
10764
11109
|
# elif action == "double_click":
|
@@ -10769,15 +11114,21 @@ def mouse(
|
|
10769
11114
|
if action == "click":
|
10770
11115
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10771
11116
|
time.sleep(wait)
|
10772
|
-
pyautogui.click(
|
11117
|
+
pyautogui.click(
|
11118
|
+
x=x_offset, y=y_offset, clicks=n_click, interval=interval, button=button
|
11119
|
+
)
|
10773
11120
|
elif action == "double_click":
|
10774
11121
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10775
11122
|
time.sleep(wait)
|
10776
|
-
pyautogui.doubleClick(
|
10777
|
-
|
11123
|
+
pyautogui.doubleClick(
|
11124
|
+
x=x_offset, y=y_offset, interval=interval, button=button
|
11125
|
+
)
|
11126
|
+
elif action == "triple_click":
|
10778
11127
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10779
11128
|
time.sleep(wait)
|
10780
|
-
pyautogui.tripleClick(
|
11129
|
+
pyautogui.tripleClick(
|
11130
|
+
x=x_offset, y=y_offset, interval=interval, button=button
|
11131
|
+
)
|
10781
11132
|
|
10782
11133
|
elif action in ["type", "write", "input"]:
|
10783
11134
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
@@ -10804,12 +11155,12 @@ def mouse(
|
|
10804
11155
|
time.sleep(wait)
|
10805
11156
|
pyautogui.scroll(scroll_amount)
|
10806
11157
|
|
10807
|
-
elif action in ["down",
|
11158
|
+
elif action in ["down", "hold", "press"]:
|
10808
11159
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10809
11160
|
time.sleep(wait)
|
10810
11161
|
pyautogui.mouseDown(x_offset, y_offset, button=button, duration=duration)
|
10811
11162
|
|
10812
|
-
elif action in [
|
11163
|
+
elif action in ["up", "release"]:
|
10813
11164
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10814
11165
|
time.sleep(wait)
|
10815
11166
|
pyautogui.mouseUp(x_offset, y_offset, button=button, duration=duration)
|
@@ -10825,21 +11176,23 @@ def mouse(
|
|
10825
11176
|
# print(f"An error occurred: {e}")
|
10826
11177
|
|
10827
11178
|
|
10828
|
-
|
11179
|
+
|
10829
11180
|
def py2installer(
|
10830
|
-
script_path: str=None,
|
10831
|
-
flatform:str="mingw64",
|
11181
|
+
script_path: str = None,
|
11182
|
+
flatform: str = "mingw64",
|
10832
11183
|
output_dir: str = "dist",
|
10833
11184
|
icon_path: str = None,
|
10834
|
-
|
10835
|
-
|
10836
|
-
|
11185
|
+
include_data: list = None,
|
11186
|
+
include_import: list = None,
|
11187
|
+
exclude_import: list = None,
|
11188
|
+
plugins: list = None,
|
10837
11189
|
use_nuitka: bool = True,
|
10838
|
-
onefile: bool = True,
|
10839
11190
|
console: bool = True,
|
10840
11191
|
clean_build: bool = False,
|
10841
11192
|
additional_args: list = None,
|
10842
11193
|
verbose: bool = True,
|
11194
|
+
standalone: bool = True,
|
11195
|
+
onefile: bool = False,
|
10843
11196
|
use_docker: bool = False,
|
10844
11197
|
docker_image: str = "python:3.12-slim",
|
10845
11198
|
):
|
@@ -10849,11 +11202,10 @@ def py2installer(
|
|
10849
11202
|
script_path (str): Path to the Python script to package.
|
10850
11203
|
output_dir (str): Directory where the executable will be stored.
|
10851
11204
|
icon_path (str): Path to the .ico file for the executable icon.
|
10852
|
-
|
10853
|
-
|
11205
|
+
include_data (list): List of additional data files or directories in "source:dest" format.
|
11206
|
+
exclude_import (list): List of hidden imports to include.
|
10854
11207
|
plugins (list): List of plugins imports to include.e.g., 'tk-inter'
|
10855
11208
|
use_nuitka (bool): Whether to use Nuitka instead of PyInstaller.
|
10856
|
-
onefile (bool): If True, produces a single executable file.
|
10857
11209
|
console (bool): If False, hides the console window (GUI mode).
|
10858
11210
|
clean_build (bool): If True, cleans previous build and dist directories.
|
10859
11211
|
additional_args (list): Additional arguments for PyInstaller/Nuitka.
|
@@ -10870,15 +11222,16 @@ def py2installer(
|
|
10870
11222
|
import sys
|
10871
11223
|
import glob
|
10872
11224
|
from pathlib import Path
|
11225
|
+
|
10873
11226
|
if run_once_within():
|
10874
|
-
usage_str="""
|
10875
|
-
# build locally
|
11227
|
+
usage_str = """
|
11228
|
+
# build locally
|
10876
11229
|
py2installer(
|
10877
11230
|
script_path="update_tab.py",
|
10878
11231
|
output_dir="dist",
|
10879
11232
|
icon_path="icon4app.ico",
|
10880
|
-
|
10881
|
-
|
11233
|
+
include_data=["dat/*.xlsx:dat"],
|
11234
|
+
exclude_import=["msoffcrypto", "tkinter", "pandas", "numpy"],
|
10882
11235
|
onefile=True,
|
10883
11236
|
console=False,
|
10884
11237
|
clean_build=True,
|
@@ -10893,6 +11246,26 @@ def py2installer(
|
|
10893
11246
|
use_docker=True,
|
10894
11247
|
docker_image="python:3.12-slim"
|
10895
11248
|
)
|
11249
|
+
# 尽量不要使用--include-package,这可能导致冲突
|
11250
|
+
py2installer(
|
11251
|
+
script_path="update_tab.py",
|
11252
|
+
# flatform=None,
|
11253
|
+
output_dir="dist_simp_subprocess",
|
11254
|
+
icon_path="icon4app.ico",
|
11255
|
+
standalone=True,
|
11256
|
+
onefile=False,
|
11257
|
+
include_data=["dat/*.xlsx=dat"],
|
11258
|
+
plugins=[
|
11259
|
+
"tk-inter",
|
11260
|
+
],
|
11261
|
+
use_nuitka=True,
|
11262
|
+
console=True,
|
11263
|
+
clean_build=False,
|
11264
|
+
verbose=0,
|
11265
|
+
)
|
11266
|
+
# 最终文件大小对比
|
11267
|
+
900 MB: nuitka --mingw64 --standalone --windows-console-mode=attach --show-progress --output-dir=dist --macos-create-app-bundle --macos-app-icon=icon4app.ico --nofollow-import-to=timm,paddle,torch,torchmetrics,torchvision,tensorflow,tensorboard,tensorboardx,tensorboard-data-server,textblob,PIL,sklearn,scienceplots,scikit-image,scikit-learn,scikit-surprise,scipy,spikeinterface,spike-sort-lfpy,stanza,statsmodels,streamlit,streamlit-autorefresh,streamlit-folium,pkg2ls,plotly --include-package=msoffcrypto,tkinter,datetime,pandas,numpy --enable-plugin=tk-inter update_tab.py;
|
11268
|
+
470 MB: nuitka --mingw64 --standalone --windows-console-mode=attach --show-progress --output-dir=dist_direct_nuitka --macos-create-app-bundle --macos-app-icon=icon4app.ico --enable-plugin=tk-inter update_taby ;
|
10896
11269
|
"""
|
10897
11270
|
print(usage_str)
|
10898
11271
|
if verbose:
|
@@ -10904,7 +11277,7 @@ def py2installer(
|
|
10904
11277
|
if not script_path.exists():
|
10905
11278
|
raise FileNotFoundError(f"Script '{script_path}' not found.")
|
10906
11279
|
|
10907
|
-
|
11280
|
+
# Clean build and dist directories if requested
|
10908
11281
|
if clean_build:
|
10909
11282
|
for folder in ["build", "dist"]:
|
10910
11283
|
folder_path = Path(folder)
|
@@ -10914,12 +11287,13 @@ def py2installer(
|
|
10914
11287
|
for folder in ["build", "dist"]:
|
10915
11288
|
folder_path = Path(folder)
|
10916
11289
|
folder_path.mkdir(parents=True, exist_ok=True)
|
10917
|
-
|
10918
11290
|
|
10919
11291
|
if use_docker:
|
10920
11292
|
# Ensure Docker is installed
|
10921
11293
|
try:
|
10922
|
-
subprocess.run(
|
11294
|
+
subprocess.run(
|
11295
|
+
["docker", "--version"], check=True, capture_output=True, text=True
|
11296
|
+
)
|
10923
11297
|
except FileNotFoundError:
|
10924
11298
|
raise EnvironmentError("Docker is not installed or not in the PATH.")
|
10925
11299
|
|
@@ -10931,11 +11305,16 @@ def py2installer(
|
|
10931
11305
|
f"{dist_path}:/output:rw",
|
10932
11306
|
]
|
10933
11307
|
docker_cmd = [
|
10934
|
-
"docker",
|
10935
|
-
"
|
10936
|
-
"
|
11308
|
+
"docker",
|
11309
|
+
"run",
|
11310
|
+
"--rm",
|
11311
|
+
"-v",
|
11312
|
+
volumes[0],
|
11313
|
+
"-v",
|
11314
|
+
volumes[1],
|
10937
11315
|
docker_image,
|
10938
|
-
"bash",
|
11316
|
+
"bash",
|
11317
|
+
"-c",
|
10939
11318
|
]
|
10940
11319
|
|
10941
11320
|
# Build the packaging command inside the container
|
@@ -10947,11 +11326,11 @@ def py2installer(
|
|
10947
11326
|
cmd.extend(["--distpath", "/output"])
|
10948
11327
|
if icon_path:
|
10949
11328
|
cmd.extend(["--icon", f"/app/{Path(icon_path).name}"])
|
10950
|
-
if
|
10951
|
-
for data in
|
11329
|
+
if include_data:
|
11330
|
+
for data in include_data:
|
10952
11331
|
cmd.extend(["--add-data", f"/app/{data}"])
|
10953
|
-
if
|
10954
|
-
for hidden in
|
11332
|
+
if exclude_import:
|
11333
|
+
for hidden in exclude_import:
|
10955
11334
|
cmd.extend(["--hidden-import", hidden])
|
10956
11335
|
if additional_args:
|
10957
11336
|
cmd.extend(additional_args)
|
@@ -10976,25 +11355,32 @@ def py2installer(
|
|
10976
11355
|
raise
|
10977
11356
|
else:
|
10978
11357
|
# Handle local packaging (native build)
|
10979
|
-
cmd = ["nuitka"]
|
10980
|
-
|
10981
|
-
if 'min' in flatform.lower():
|
11358
|
+
cmd = ["nuitka"] if use_nuitka else ["pyinstaller"]
|
11359
|
+
if "min" in flatform.lower() and use_nuitka:
|
10982
11360
|
cmd.append("--mingw64")
|
10983
|
-
if
|
10984
|
-
|
11361
|
+
cmd.append("--standalone") if use_nuitka and standalone else None
|
11362
|
+
cmd.append("--onefile") if onefile else None
|
10985
11363
|
if not console:
|
10986
|
-
|
10987
|
-
|
11364
|
+
cmd.append("--windows-console-mode=disable")
|
11365
|
+
else:
|
11366
|
+
cmd.append("--windows-console-mode=attach")
|
10988
11367
|
|
10989
|
-
cmd.extend([f"--output-dir={output_dir}"])
|
11368
|
+
cmd.extend(["--show-progress", f"--output-dir={output_dir}"])
|
10990
11369
|
if icon_path:
|
10991
11370
|
icon_path = Path(icon_path)
|
10992
11371
|
if not icon_path.exists():
|
10993
11372
|
raise FileNotFoundError(f"Icon file '{icon_path}' not found.")
|
10994
|
-
|
11373
|
+
if sys.platform == "darwin": # macOS platform
|
11374
|
+
cmd.extend(
|
11375
|
+
["--macos-create-app-bundle", f"--macos-app-icon={icon_path}"]
|
11376
|
+
)
|
11377
|
+
elif sys.platform == "win32": # Windows platform
|
11378
|
+
cmd.extend([f"--windows-icon-from-ico={icon_path}"])
|
11379
|
+
elif sys.platform == "linux": # Linux platform
|
11380
|
+
cmd.append("--linux-onefile")
|
10995
11381
|
|
10996
|
-
if
|
10997
|
-
for data in
|
11382
|
+
if include_data:
|
11383
|
+
for data in include_data:
|
10998
11384
|
if "*" in data:
|
10999
11385
|
matches = glob.glob(data.split(":")[0])
|
11000
11386
|
for match in matches:
|
@@ -11009,30 +11395,43 @@ def py2installer(
|
|
11009
11395
|
cmd.extend(
|
11010
11396
|
["--include-data-file=" if use_nuitka else "--add-data", data]
|
11011
11397
|
)
|
11012
|
-
|
11013
|
-
|
11014
|
-
|
11015
|
-
|
11398
|
+
if exclude_import is not None:
|
11399
|
+
if any(exclude_import):
|
11400
|
+
cmd.extend([f"--nofollow-import-to={','.join(exclude_import)}"])
|
11401
|
+
if include_import is not None:
|
11402
|
+
if any(include_import):#are included in the final build. Some packages may require manual inclusion.
|
11403
|
+
cmd.extend([f"--include-package={','.join(include_import)}"])
|
11016
11404
|
if plugins:
|
11017
11405
|
for plugin in plugins:
|
11018
|
-
|
11406
|
+
#Adds support for tkinter, ensuring it works correctly in the standalone build.
|
11407
|
+
cmd.extend([f"--enable-plugin={plugin}"])
|
11019
11408
|
|
11020
11409
|
if additional_args:
|
11021
11410
|
cmd.extend(additional_args)
|
11022
11411
|
|
11412
|
+
# # clean
|
11413
|
+
# cmd.extend(
|
11414
|
+
# [ "--noinclude-numba-mode=nofollow", #Prevents the inclusion of the numba library and its dependencies, reducing the executable size.
|
11415
|
+
# "--noinclude-dask-mode=nofollow",#Excludes the dask library
|
11416
|
+
# "--noinclude-IPython-mode=nofollow",#Excludes the IPython library and its dependencies.
|
11417
|
+
# "--noinclude-unittest-mode=nofollow",#Excludes the unittest module (used for testing) from the build
|
11418
|
+
# "--noinclude-pytest-mode=nofollow",#Excludes the pytest library (used for testing) from the build.
|
11419
|
+
# "--noinclude-setuptools-mode=nofollow",#Excludes setuptools, which is not needed for the standalone executable.
|
11420
|
+
# "--lto=no",#Disables Link-Time Optimization (LTO), which reduces the compilation time but may slightly increase the size of the output.
|
11421
|
+
# ]
|
11422
|
+
# )
|
11423
|
+
|
11424
|
+
if clean_build:
|
11425
|
+
cmd.append("--remove-output")#Removes intermediate files created during the build process, keeping only the final executable.
|
11023
11426
|
# Add the script path (final positional argument)
|
11024
11427
|
cmd.append(str(script_path))
|
11025
|
-
|
11026
11428
|
# Ensure Windows shell compatibility
|
11027
11429
|
shell_flag = sys.platform.startswith("win")
|
11028
|
-
|
11029
|
-
# Run the command
|
11030
|
-
if verbose:
|
11031
|
-
print(f"Running command: {' '.join(cmd)}")
|
11430
|
+
print(f"Running command: ⤵ \n{' '.join(cmd)}\n")
|
11032
11431
|
try:
|
11033
11432
|
result = subprocess.run(
|
11034
11433
|
cmd,
|
11035
|
-
capture_output=
|
11434
|
+
capture_output=True,
|
11036
11435
|
text=True,
|
11037
11436
|
shell=shell_flag,
|
11038
11437
|
check=True,
|
@@ -11045,4 +11444,3 @@ def py2installer(
|
|
11045
11444
|
raise
|
11046
11445
|
|
11047
11446
|
print("\nPackaging complete. Check the output directory for the executable.")
|
11048
|
-
|