py2ls 0.2.4.37__py3-none-any.whl → 0.2.4.39__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/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
|
-
|