py2ls 0.2.4.37__py3-none-any.whl → 0.2.4.40__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
|
-
funcs = [func for func in dir(lib_name) if callable(getattr(lib_name, func))]
|
4457
|
-
else:
|
4458
|
-
funcs = dir(lib_name)
|
4459
|
-
return funcs
|
4518
|
+
def listpkg(where="env", verbose=False):
|
4519
|
+
"""list all pacakages"""
|
4460
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
|
4461
4533
|
|
4462
|
-
|
4463
|
-
|
4534
|
+
if any([i in where.lower() for i in ["all", "env"]]):
|
4535
|
+
import pkg_resources
|
4536
|
+
|
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
|
|
@@ -6175,6 +6330,7 @@ def format_excel(
|
|
6175
6330
|
protect=None, # dict
|
6176
6331
|
number_format=None, # dict: e.g., {1:"0.00", 2:"#,##0",3:"0%",4:"$#,##0.00"}
|
6177
6332
|
data_validation=None, # dict
|
6333
|
+
apply_filter=True, # add filter
|
6178
6334
|
conditional_format=None, # dict
|
6179
6335
|
**kwargs,
|
6180
6336
|
):
|
@@ -6185,7 +6341,7 @@ def format_excel(
|
|
6185
6341
|
from openpyxl.utils import get_column_letter
|
6186
6342
|
from openpyxl.worksheet.datavalidation import DataValidation
|
6187
6343
|
from openpyxl.comments import Comment
|
6188
|
-
from openpyxl.formatting.rule import ColorScaleRule
|
6344
|
+
from openpyxl.formatting.rule import ColorScaleRule, DataBarRule, IconSetRule,IconSet
|
6189
6345
|
|
6190
6346
|
def convert_indices_to_range(row_slice, col_slice):
|
6191
6347
|
"""Convert numerical row and column slices to Excel-style range strings."""
|
@@ -6550,159 +6706,159 @@ def format_excel(
|
|
6550
6706
|
filename = str(datetime.now().strftime("%y%m%d_%H.xlsx"))
|
6551
6707
|
# !show usage:
|
6552
6708
|
func_xample = """
|
6553
|
-
# Example usage
|
6554
|
-
data = {
|
6555
|
-
|
6556
|
-
|
6557
|
-
|
6558
|
-
|
6559
|
-
|
6560
|
-
|
6561
|
-
|
6562
|
-
}
|
6563
|
-
df = pd.DataFrame(data)
|
6564
|
-
|
6565
|
-
|
6566
|
-
format_excel(
|
6567
|
-
df,
|
6568
|
-
filename="example.xlsx",
|
6569
|
-
sheet_name="Sheet1",
|
6570
|
-
cell=[
|
6571
|
-
{
|
6572
|
-
(slice(0, 1), slice(0, len(df.columns))): {
|
6573
|
-
"font": {
|
6574
|
-
"name": "Calibri", # Font name
|
6575
|
-
"size": 14, # Font size
|
6576
|
-
"bold": True, # Bold text
|
6577
|
-
"italic": False, # Italic text
|
6578
|
-
"underline": "single", # Underline (single, double)
|
6579
|
-
"color": "#FFFFFF", # Font color
|
6580
|
-
},
|
6581
|
-
"fill": {
|
6582
|
-
"start_color": "a1cd1e", # Starting color
|
6583
|
-
"end_color": "4F81BD", # Ending color (useful for gradients)
|
6584
|
-
"fill_type": "solid", # Fill type (solid, gradient, etc.)
|
6585
|
-
},
|
6586
|
-
"alignment": {
|
6587
|
-
"horizontal": "center", # Horizontal alignment (left, center, right)
|
6588
|
-
"vertical": "center", # Vertical alignment (top, center, bottom)
|
6589
|
-
"wrap_text": True, # Wrap text in the cell
|
6590
|
-
"shrink_to_fit": True, # Shrink text to fit within cell
|
6591
|
-
"text_rotation": 0, # Text rotation angle
|
6592
|
-
},
|
6593
|
-
"border": {
|
6594
|
-
"left": "thin",
|
6595
|
-
"right": "thin",
|
6596
|
-
"top": "thin",
|
6597
|
-
"bottom": "thin",
|
6598
|
-
"color": "000000", # Border color
|
6599
|
-
},
|
6709
|
+
# Example usage
|
6710
|
+
data = {
|
6711
|
+
"Header 1": [10, 2000000, 30],
|
6712
|
+
"Header 2": [
|
6713
|
+
40000,
|
6714
|
+
'"start_color": "4F81BD", "end_color","start_color": "a1cd1e","start_color": "4F81BD", "end_color","start_color": "a1cd1e"',
|
6715
|
+
60,
|
6716
|
+
],
|
6717
|
+
"Header 3": [70, 80, 90],
|
6600
6718
|
}
|
6601
|
-
|
6602
|
-
|
6603
|
-
|
6604
|
-
|
6605
|
-
|
6606
|
-
|
6607
|
-
|
6608
|
-
|
6609
|
-
|
6610
|
-
|
6719
|
+
df = pd.DataFrame(data)
|
6720
|
+
|
6721
|
+
|
6722
|
+
format_excel(
|
6723
|
+
df,
|
6724
|
+
filename="example.xlsx",
|
6725
|
+
sheet_name="Sheet1",
|
6726
|
+
cell=[
|
6727
|
+
{
|
6728
|
+
(slice(0, 1), slice(0, len(df.columns))): {
|
6729
|
+
"font": {
|
6730
|
+
"name": "Calibri", # Font name
|
6731
|
+
"size": 14, # Font size
|
6732
|
+
"bold": True, # Bold text
|
6733
|
+
"italic": False, # Italic text
|
6734
|
+
"underline": "single", # Underline (single, double)
|
6735
|
+
"color": "#FFFFFF", # Font color
|
6736
|
+
},
|
6737
|
+
"fill": {
|
6738
|
+
"start_color": "a1cd1e", # Starting color
|
6739
|
+
"end_color": "4F81BD", # Ending color (useful for gradients)
|
6740
|
+
"fill_type": "solid", # Fill type (solid, gradient, etc.)
|
6741
|
+
},
|
6742
|
+
"alignment": {
|
6743
|
+
"horizontal": "center", # Horizontal alignment (left, center, right)
|
6744
|
+
"vertical": "center", # Vertical alignment (top, center, bottom)
|
6745
|
+
"wrap_text": True, # Wrap text in the cell
|
6746
|
+
"shrink_to_fit": True, # Shrink text to fit within cell
|
6747
|
+
"text_rotation": 0, # Text rotation angle
|
6748
|
+
},
|
6749
|
+
"border": {
|
6750
|
+
"left": "thin",
|
6751
|
+
"right": "thin",
|
6752
|
+
"top": "thin",
|
6753
|
+
"bottom": "thin",
|
6754
|
+
"color": "000000", # Border color
|
6755
|
+
},
|
6756
|
+
}
|
6757
|
+
},
|
6758
|
+
{
|
6759
|
+
(slice(0, 3), slice(1, 3)): {
|
6760
|
+
"font": {
|
6761
|
+
"name": "Arial", # Font name
|
6762
|
+
"size": 12, # Font size
|
6763
|
+
"bold": False, # Bold text
|
6764
|
+
"italic": True, # Italic text
|
6765
|
+
"underline": None, # No underline
|
6766
|
+
"color": "#000000", # Font color
|
6767
|
+
},
|
6768
|
+
"fill": {
|
6769
|
+
"start_color": "#c61313", # Background color
|
6770
|
+
"end_color": "#490606", # End color
|
6771
|
+
"fill_type": "solid", # Fill type
|
6772
|
+
},
|
6773
|
+
"alignment": {
|
6774
|
+
"horizontal": "left", # Horizontal alignment
|
6775
|
+
"vertical": "top", # Vertical alignment
|
6776
|
+
"wrap_text": True, # Enable text wrapping
|
6777
|
+
"shrink_to_fit": False, # Disable shrink to fit
|
6778
|
+
"indent": 1, # Indentation level
|
6779
|
+
"text_rotation": 0, # Text rotation angle
|
6780
|
+
},
|
6781
|
+
"border": {
|
6782
|
+
"left": "thin", # Left border style
|
6783
|
+
"right": "thin", # Right border style
|
6784
|
+
"top": "thin", # Top border style
|
6785
|
+
"bottom": "thin", # Bottom border style
|
6786
|
+
"color": "000000", # Border color
|
6787
|
+
},
|
6788
|
+
}
|
6789
|
+
# * border settings
|
6790
|
+
# "thin": Thin border line
|
6791
|
+
# "medium": Medium border line
|
6792
|
+
# "thick": Thick border line
|
6793
|
+
# "dotted": Dotted border line
|
6794
|
+
# "dashed": Dashed border line
|
6795
|
+
# "hair": Hairline border (very thin)
|
6796
|
+
# "mediumDashed": Medium dashed border line
|
6797
|
+
# "dashDot": Dash-dot border line
|
6798
|
+
# "dashDotDot": Dash-dot-dot border line
|
6799
|
+
# "slantDashDot": Slant dash-dot border line
|
6800
|
+
},
|
6801
|
+
],
|
6802
|
+
width={2: 30}, # when it is None, it will automatic adjust width
|
6803
|
+
height={2: 50, 3: 25}, # keys are columns
|
6804
|
+
merge=(slice(0, 1), slice(1, 3)),
|
6805
|
+
shade={
|
6806
|
+
(slice(1, 4), slice(1, 3)): {
|
6807
|
+
"bg_color": "FFFF00", # Background color
|
6808
|
+
"pattern_type": "solid", # Fill pattern (e.g., solid, darkGrid, lightGrid)
|
6809
|
+
"fg_color": "#0000FF", # Foreground color, used in patterns
|
6810
|
+
"end_color": "0000FF", # End color, useful for gradients
|
6811
|
+
"fill_type": "solid", # Type of fill (solid, gradient, etc.)
|
6812
|
+
}
|
6611
6813
|
},
|
6612
|
-
"
|
6613
|
-
|
6614
|
-
|
6615
|
-
"
|
6814
|
+
comment={(2, 4): "This is a comment"},
|
6815
|
+
link={(2, 2): "https://example.com"},
|
6816
|
+
protect={
|
6817
|
+
"password": "123", # Password for sheet protection
|
6818
|
+
"sheet": False, # True, # Protect the sheet
|
6819
|
+
"objects": False, # True, # Protect objects
|
6820
|
+
"scenarios": False, # True, # Protect scenarios
|
6821
|
+
"formatCells": False, # Disable formatting cells
|
6822
|
+
"formatColumns": False, # Disable formatting columns
|
6823
|
+
"formatRows": False, # Disable formatting rows
|
6824
|
+
"insertColumns": False, # Disable inserting columns
|
6825
|
+
"insertRows": False, # Disable inserting rows
|
6826
|
+
"deleteColumns": False, # Disable deleting columns
|
6827
|
+
"deleteRows": False, # Disable deleting rows
|
6616
6828
|
},
|
6617
|
-
|
6618
|
-
|
6619
|
-
|
6620
|
-
"
|
6621
|
-
"
|
6622
|
-
"indent": 1, # Indentation level
|
6623
|
-
"text_rotation": 0, # Text rotation angle
|
6829
|
+
number_format={
|
6830
|
+
1: "0.00", # Two decimal places for column index 1
|
6831
|
+
2: "#,##0", # Thousands separator
|
6832
|
+
3: "0%", # Percentage format
|
6833
|
+
4: "$#,##0.00", # Currency format
|
6624
6834
|
},
|
6625
|
-
|
6626
|
-
|
6627
|
-
|
6628
|
-
|
6629
|
-
|
6630
|
-
|
6835
|
+
data_validation={
|
6836
|
+
(slice(1, 2), slice(2, 10)): {
|
6837
|
+
"type": "list",
|
6838
|
+
"formula1": '"Option1,Option2,Option3"', # List of options
|
6839
|
+
"allow_blank": True,
|
6840
|
+
"showDropDown": True,
|
6841
|
+
"showErrorMessage": True,
|
6842
|
+
"errorTitle": "Invalid input",
|
6843
|
+
"error": "Please select a valid option.",
|
6844
|
+
}
|
6631
6845
|
},
|
6632
|
-
|
6633
|
-
|
6634
|
-
|
6635
|
-
|
6636
|
-
|
6637
|
-
|
6638
|
-
|
6639
|
-
|
6640
|
-
|
6641
|
-
|
6642
|
-
|
6643
|
-
|
6644
|
-
|
6645
|
-
|
6646
|
-
|
6647
|
-
|
6648
|
-
merge=(slice(0, 1), slice(1, 3)),
|
6649
|
-
shade={
|
6650
|
-
(slice(1, 4), slice(1, 3)): {
|
6651
|
-
"bg_color": "FFFF00", # Background color
|
6652
|
-
"pattern_type": "solid", # Fill pattern (e.g., solid, darkGrid, lightGrid)
|
6653
|
-
"fg_color": "#0000FF", # Foreground color, used in patterns
|
6654
|
-
"end_color": "0000FF", # End color, useful for gradients
|
6655
|
-
"fill_type": "solid", # Type of fill (solid, gradient, etc.)
|
6656
|
-
}
|
6657
|
-
},
|
6658
|
-
comment={(2, 4): "This is a comment"},
|
6659
|
-
link={(2, 2): "https://example.com"},
|
6660
|
-
protect={
|
6661
|
-
"password": "123", # Password for sheet protection
|
6662
|
-
"sheet": False, # True, # Protect the sheet
|
6663
|
-
"objects": False, # True, # Protect objects
|
6664
|
-
"scenarios": False, # True, # Protect scenarios
|
6665
|
-
"formatCells": False, # Disable formatting cells
|
6666
|
-
"formatColumns": False, # Disable formatting columns
|
6667
|
-
"formatRows": False, # Disable formatting rows
|
6668
|
-
"insertColumns": False, # Disable inserting columns
|
6669
|
-
"insertRows": False, # Disable inserting rows
|
6670
|
-
"deleteColumns": False, # Disable deleting columns
|
6671
|
-
"deleteRows": False, # Disable deleting rows
|
6672
|
-
},
|
6673
|
-
number_format={
|
6674
|
-
1: "0.00", # Two decimal places for column index 1
|
6675
|
-
2: "#,##0", # Thousands separator
|
6676
|
-
3: "0%", # Percentage format
|
6677
|
-
4: "$#,##0.00", # Currency format
|
6678
|
-
},
|
6679
|
-
data_validation={
|
6680
|
-
(slice(1, 2), slice(2, 10)): {
|
6681
|
-
"type": "list",
|
6682
|
-
"formula1": '"Option1,Option2,Option3"', # List of options
|
6683
|
-
"allow_blank": True,
|
6684
|
-
"showDropDown": True,
|
6685
|
-
"showErrorMessage": True,
|
6686
|
-
"errorTitle": "Invalid input",
|
6687
|
-
"error": "Please select a valid option.",
|
6688
|
-
}
|
6689
|
-
},
|
6690
|
-
conditional_format={
|
6691
|
-
(slice(1, 2), slice(2, 10)): [
|
6692
|
-
{
|
6693
|
-
"color_scale": {
|
6694
|
-
"start_type": "min", # Type of start point (min, max, percent)
|
6695
|
-
"start_color": "#FF0000", # Starting color
|
6696
|
-
"end_type": "max", # End type (min, max, percent)
|
6697
|
-
"end_color": "00FF00", # Ending color
|
6698
|
-
"mid_type": "percentile", # Midpoint type (optional)
|
6699
|
-
"mid_value": 50, # Midpoint value (optional)
|
6700
|
-
"mid_color": "FFFF00", # Midpoint color (optional)
|
6701
|
-
}
|
6702
|
-
}
|
6703
|
-
]
|
6704
|
-
},
|
6705
|
-
)
|
6846
|
+
conditional_format={
|
6847
|
+
(slice(1, 2), slice(2, 10)): [
|
6848
|
+
{
|
6849
|
+
"color_scale": {
|
6850
|
+
"start_type": "min", # Type of start point (min, max, percent)
|
6851
|
+
"start_color": "#FF0000", # Starting color
|
6852
|
+
"end_type": "max", # End type (min, max, percent)
|
6853
|
+
"end_color": "00FF00", # Ending color
|
6854
|
+
"mid_type": "percentile", # Midpoint type (optional)
|
6855
|
+
"mid_value": 50, # Midpoint value (optional)
|
6856
|
+
"mid_color": "FFFF00", # Midpoint color (optional)
|
6857
|
+
}
|
6858
|
+
}
|
6859
|
+
]
|
6860
|
+
},
|
6861
|
+
)
|
6706
6862
|
"""
|
6707
6863
|
if usage:
|
6708
6864
|
print(func_xample)
|
@@ -6762,17 +6918,49 @@ format_excel(
|
|
6762
6918
|
col_letter = get_column_letter(col_idx)
|
6763
6919
|
for cell in ws[col_letter][1:]: # Skip the header
|
6764
6920
|
cell.number_format = fmt
|
6921
|
+
|
6922
|
+
if apply_filter:
|
6923
|
+
if isinstance(apply_filter, bool):
|
6924
|
+
# Default: Apply filter to the entire first row (header)
|
6925
|
+
filter_range = f"A1:{get_column_letter(ws.max_column)}1"
|
6926
|
+
ws.auto_filter.ref = filter_range
|
6927
|
+
|
6928
|
+
elif isinstance(apply_filter, tuple):
|
6929
|
+
row_slice, col_slice = apply_filter
|
6930
|
+
|
6931
|
+
# Extract the start and end indices for rows and columns
|
6932
|
+
start_row, end_row = row_slice.start, row_slice.stop
|
6933
|
+
start_col_idx, end_col_idx = col_slice.start, col_slice.stop
|
6934
|
+
|
6935
|
+
# Ensure valid row and column indices
|
6936
|
+
if start_row < 1: start_row = 1 # Row should not be less than 1
|
6937
|
+
if end_row > ws.max_row: end_row = ws.max_row # Ensure within sheet's row limits
|
6938
|
+
if start_col_idx < 1: start_col_idx = 1 # Column should not be less than 1
|
6939
|
+
if end_col_idx > ws.max_column: end_col_idx = ws.max_column # Ensure within sheet's column limits
|
6940
|
+
|
6941
|
+
# Get column letters based on indices
|
6942
|
+
start_col = get_column_letter(start_col_idx)
|
6943
|
+
end_col = get_column_letter(end_col_idx)
|
6944
|
+
|
6945
|
+
# Define the filter range based on specific rows and columns
|
6946
|
+
filter_range = f"{start_col}{start_row}:{end_col}{end_row}"
|
6765
6947
|
|
6948
|
+
# Apply the filter
|
6949
|
+
ws.auto_filter.ref = filter_range
|
6766
6950
|
# !widths
|
6767
6951
|
if width is None: # automatic adust width
|
6768
6952
|
for col in ws.columns:
|
6769
|
-
|
6770
|
-
|
6771
|
-
|
6772
|
-
|
6773
|
-
|
6774
|
-
|
6775
|
-
|
6953
|
+
max_length = 0
|
6954
|
+
column = col[0].column_letter # Get the column letter
|
6955
|
+
for cell_ in col:
|
6956
|
+
try:
|
6957
|
+
if cell_.value:
|
6958
|
+
max_length = max(max_length, len(str(cell_.value)))
|
6959
|
+
except Exception:
|
6960
|
+
pass
|
6961
|
+
adjusted_width = max_length + 2 # You can adjust the padding value as needed
|
6962
|
+
ws.column_dimensions[column].width = adjusted_width
|
6963
|
+
|
6776
6964
|
else:
|
6777
6965
|
for col_idx, width_ in width.items():
|
6778
6966
|
col_letter = get_column_letter(col_idx)
|
@@ -6862,6 +7050,7 @@ format_excel(
|
|
6862
7050
|
for indices, rules in conditional_format.items():
|
6863
7051
|
cell_range = convert_indices_to_range(*indices)
|
6864
7052
|
for rule in rules:
|
7053
|
+
# Handle color scale
|
6865
7054
|
if "color_scale" in rule:
|
6866
7055
|
color_scale = rule["color_scale"]
|
6867
7056
|
start_color = hex2argb(color_scale.get("start_color", "FFFFFF"))
|
@@ -6880,6 +7069,32 @@ format_excel(
|
|
6880
7069
|
end_color=end_color,
|
6881
7070
|
)
|
6882
7071
|
ws.conditional_formatting.add(cell_range, color_scale_rule)
|
7072
|
+
# Handle data bar
|
7073
|
+
if "data_bar" in rule:
|
7074
|
+
data_bar = rule["data_bar"]
|
7075
|
+
bar_color = hex2argb(data_bar.get("color", "638EC6"))
|
7076
|
+
|
7077
|
+
data_bar_rule = DataBarRule(
|
7078
|
+
start_type=data_bar.get("start_type", "min"),
|
7079
|
+
start_value=data_bar.get("start_value"),
|
7080
|
+
end_type=data_bar.get("end_type", "max"),
|
7081
|
+
end_value=data_bar.get("end_value"),
|
7082
|
+
color=bar_color,
|
7083
|
+
showValue=data_bar.get("show_value", True),
|
7084
|
+
)
|
7085
|
+
ws.conditional_formatting.add(cell_range, data_bar_rule)
|
7086
|
+
|
7087
|
+
# Handle icon set
|
7088
|
+
if "icon_set" in rule:
|
7089
|
+
icon_set = rule["icon_set"]
|
7090
|
+
icon_set_rule = IconSet(
|
7091
|
+
iconSet=icon_set.get("iconSet", "3TrafficLights1"), # Corrected
|
7092
|
+
showValue=icon_set.get("show_value", True), # Corrected
|
7093
|
+
reverse=icon_set.get("reverse", False) # Corrected
|
7094
|
+
)
|
7095
|
+
ws.conditional_formatting.add(cell_range, icon_set_rule)
|
7096
|
+
|
7097
|
+
|
6883
7098
|
# Save the workbook
|
6884
7099
|
wb.save(filename)
|
6885
7100
|
print(f"Formatted Excel file saved as:\n{filename}")
|
@@ -10320,7 +10535,7 @@ def get_loc(input_data, user_agent="0413@mygmail.com)", verbose=True):
|
|
10320
10535
|
print(f"Could not find {input_data}.")
|
10321
10536
|
return location
|
10322
10537
|
except Exception as e:
|
10323
|
-
print(f
|
10538
|
+
print(f"Error: {e}")
|
10324
10539
|
return
|
10325
10540
|
|
10326
10541
|
# Case 2: Input is latitude and longitude (float or tuple)
|
@@ -10516,6 +10731,7 @@ def depass(encrypted_code: str, method: str = "AES", key: str = None):
|
|
10516
10731
|
else:
|
10517
10732
|
raise ValueError("Unsupported decryption method")
|
10518
10733
|
|
10734
|
+
|
10519
10735
|
def get_clip(dir_save=None):
|
10520
10736
|
"""
|
10521
10737
|
Master function to extract content from the clipboard (text, URL, or image).
|
@@ -10537,6 +10753,7 @@ def get_clip(dir_save=None):
|
|
10537
10753
|
import pyperclip
|
10538
10754
|
from PIL import ImageGrab, Image
|
10539
10755
|
import validators
|
10756
|
+
|
10540
10757
|
# 1. Check for text in the clipboard
|
10541
10758
|
clipboard_content = pyperclip.paste()
|
10542
10759
|
if clipboard_content:
|
@@ -10568,7 +10785,8 @@ def get_clip(dir_save=None):
|
|
10568
10785
|
print(f"An error occurred: {e}")
|
10569
10786
|
return result
|
10570
10787
|
|
10571
|
-
|
10788
|
+
|
10789
|
+
def keyboard(*args, action="press", n_click=1, interval=0, verbose=False, **kwargs):
|
10572
10790
|
"""
|
10573
10791
|
Simulates keyboard input using pyautogui.
|
10574
10792
|
|
@@ -10583,45 +10801,231 @@ def keyboard(*args, action='press', n_click=1,interval=0,verbose=False,**kwargs)
|
|
10583
10801
|
keyboard("command", "d", action="shorcut")
|
10584
10802
|
"""
|
10585
10803
|
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
|
-
|
10804
|
+
|
10805
|
+
input_key = args
|
10806
|
+
|
10807
|
+
actions = ["press", "keyDown", "keyUp", "hold", "release", "hotkey", "shortcut"]
|
10808
|
+
action = strcmp(action, actions)[0]
|
10809
|
+
keyboard_keys_ = [
|
10810
|
+
"\t",
|
10811
|
+
"\n",
|
10812
|
+
"\r",
|
10813
|
+
" ",
|
10814
|
+
"!",
|
10815
|
+
'"',
|
10816
|
+
"#",
|
10817
|
+
"$",
|
10818
|
+
"%",
|
10819
|
+
"&",
|
10820
|
+
"'",
|
10821
|
+
"(",
|
10822
|
+
")",
|
10823
|
+
"*",
|
10824
|
+
"+",
|
10825
|
+
",",
|
10826
|
+
"-",
|
10827
|
+
".",
|
10828
|
+
"/",
|
10829
|
+
"0",
|
10830
|
+
"1",
|
10831
|
+
"2",
|
10832
|
+
"3",
|
10833
|
+
"4",
|
10834
|
+
"5",
|
10835
|
+
"6",
|
10836
|
+
"7",
|
10837
|
+
"8",
|
10838
|
+
"9",
|
10839
|
+
":",
|
10840
|
+
";",
|
10841
|
+
"<",
|
10842
|
+
"=",
|
10843
|
+
">",
|
10844
|
+
"?",
|
10845
|
+
"@",
|
10846
|
+
"[",
|
10847
|
+
"\\",
|
10848
|
+
"]",
|
10849
|
+
"^",
|
10850
|
+
"_",
|
10851
|
+
"`",
|
10852
|
+
"a",
|
10853
|
+
"b",
|
10854
|
+
"c",
|
10855
|
+
"d",
|
10856
|
+
"e",
|
10857
|
+
"f",
|
10858
|
+
"g",
|
10859
|
+
"h",
|
10860
|
+
"i",
|
10861
|
+
"j",
|
10862
|
+
"k",
|
10863
|
+
"l",
|
10864
|
+
"m",
|
10865
|
+
"n",
|
10866
|
+
"o",
|
10867
|
+
"p",
|
10868
|
+
"q",
|
10869
|
+
"r",
|
10870
|
+
"s",
|
10871
|
+
"t",
|
10872
|
+
"u",
|
10873
|
+
"v",
|
10874
|
+
"w",
|
10875
|
+
"x",
|
10876
|
+
"y",
|
10877
|
+
"z",
|
10878
|
+
"{",
|
10879
|
+
"|",
|
10880
|
+
"}",
|
10881
|
+
"~",
|
10882
|
+
"accept",
|
10883
|
+
"add",
|
10884
|
+
"alt",
|
10885
|
+
"altleft",
|
10886
|
+
"altright",
|
10887
|
+
"apps",
|
10888
|
+
"backspace",
|
10889
|
+
"browserback",
|
10890
|
+
"browserfavorites",
|
10891
|
+
"browserforward",
|
10892
|
+
"browserhome",
|
10893
|
+
"browserrefresh",
|
10894
|
+
"browsersearch",
|
10895
|
+
"browserstop",
|
10896
|
+
"capslock",
|
10897
|
+
"clear",
|
10898
|
+
"convert",
|
10899
|
+
"ctrl",
|
10900
|
+
"ctrlleft",
|
10901
|
+
"ctrlright",
|
10902
|
+
"decimal",
|
10903
|
+
"del",
|
10904
|
+
"delete",
|
10905
|
+
"divide",
|
10906
|
+
"down",
|
10907
|
+
"end",
|
10908
|
+
"enter",
|
10909
|
+
"esc",
|
10910
|
+
"escape",
|
10911
|
+
"execute",
|
10912
|
+
"f1",
|
10913
|
+
"f10",
|
10914
|
+
"f11",
|
10915
|
+
"f12",
|
10916
|
+
"f13",
|
10917
|
+
"f14",
|
10918
|
+
"f15",
|
10919
|
+
"f16",
|
10920
|
+
"f17",
|
10921
|
+
"f18",
|
10922
|
+
"f19",
|
10923
|
+
"f2",
|
10924
|
+
"f20",
|
10925
|
+
"f21",
|
10926
|
+
"f22",
|
10927
|
+
"f23",
|
10928
|
+
"f24",
|
10929
|
+
"f3",
|
10930
|
+
"f4",
|
10931
|
+
"f5",
|
10932
|
+
"f6",
|
10933
|
+
"f7",
|
10934
|
+
"f8",
|
10935
|
+
"f9",
|
10936
|
+
"final",
|
10937
|
+
"fn",
|
10938
|
+
"hanguel",
|
10939
|
+
"hangul",
|
10940
|
+
"hanja",
|
10941
|
+
"help",
|
10942
|
+
"home",
|
10943
|
+
"insert",
|
10944
|
+
"junja",
|
10945
|
+
"kana",
|
10946
|
+
"kanji",
|
10947
|
+
"launchapp1",
|
10948
|
+
"launchapp2",
|
10949
|
+
"launchmail",
|
10950
|
+
"launchmediaselect",
|
10951
|
+
"left",
|
10952
|
+
"modechange",
|
10953
|
+
"multiply",
|
10954
|
+
"nexttrack",
|
10955
|
+
"nonconvert",
|
10956
|
+
"num0",
|
10957
|
+
"num1",
|
10958
|
+
"num2",
|
10959
|
+
"num3",
|
10960
|
+
"num4",
|
10961
|
+
"num5",
|
10962
|
+
"num6",
|
10963
|
+
"num7",
|
10964
|
+
"num8",
|
10965
|
+
"num9",
|
10966
|
+
"numlock",
|
10967
|
+
"pagedown",
|
10968
|
+
"pageup",
|
10969
|
+
"pause",
|
10970
|
+
"pgdn",
|
10971
|
+
"pgup",
|
10972
|
+
"playpause",
|
10973
|
+
"prevtrack",
|
10974
|
+
"print",
|
10975
|
+
"printscreen",
|
10976
|
+
"prntscrn",
|
10977
|
+
"prtsc",
|
10978
|
+
"prtscr",
|
10979
|
+
"return",
|
10980
|
+
"right",
|
10981
|
+
"scrolllock",
|
10982
|
+
"select",
|
10983
|
+
"separator",
|
10984
|
+
"shift",
|
10985
|
+
"shiftleft",
|
10986
|
+
"shiftright",
|
10987
|
+
"sleep",
|
10988
|
+
"space",
|
10989
|
+
"stop",
|
10990
|
+
"subtract",
|
10991
|
+
"tab",
|
10992
|
+
"up",
|
10993
|
+
"volumedown",
|
10994
|
+
"volumemute",
|
10995
|
+
"volumeup",
|
10996
|
+
"win",
|
10997
|
+
"winleft",
|
10998
|
+
"winright",
|
10999
|
+
"yen",
|
11000
|
+
"command",
|
11001
|
+
"option",
|
11002
|
+
"optionleft",
|
11003
|
+
"optionright",
|
11004
|
+
]
|
10612
11005
|
if verbose:
|
10613
11006
|
print(f"supported keys: {keyboard_keys_}")
|
10614
11007
|
|
10615
|
-
if action not in [
|
11008
|
+
if action not in ["hotkey", "shortcut"]:
|
10616
11009
|
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
|
11010
|
+
input_key = list(input_key)
|
11011
|
+
input_key = [strcmp(i, keyboard_keys_)[0] for i in input_key]
|
10619
11012
|
|
10620
11013
|
# correct action
|
10621
|
-
cmd_keys = [
|
11014
|
+
cmd_keys = [
|
11015
|
+
"command",
|
11016
|
+
"option",
|
11017
|
+
"optionleft",
|
11018
|
+
"optionright",
|
11019
|
+
"win",
|
11020
|
+
"winleft",
|
11021
|
+
"winright",
|
11022
|
+
"ctrl",
|
11023
|
+
"ctrlleft",
|
11024
|
+
"ctrlright",
|
11025
|
+
]
|
10622
11026
|
try:
|
10623
11027
|
if any([i in cmd_keys for i in input_key]):
|
10624
|
-
action=
|
11028
|
+
action = "hotkey"
|
10625
11029
|
except:
|
10626
11030
|
pass
|
10627
11031
|
|
@@ -10633,23 +11037,24 @@ def keyboard(*args, action='press', n_click=1,interval=0,verbose=False,**kwargs)
|
|
10633
11037
|
for key in input_key:
|
10634
11038
|
pyautogui.press(key)
|
10635
11039
|
pyautogui.sleep(interval)
|
10636
|
-
elif action in [
|
11040
|
+
elif action in ["keyDown", "hold"]:
|
10637
11041
|
# pyautogui.keyDown(input_key)
|
10638
11042
|
for _ in range(n_click):
|
10639
11043
|
for key in input_key:
|
10640
11044
|
pyautogui.keyDown(key)
|
10641
11045
|
pyautogui.sleep(interval)
|
10642
|
-
|
10643
|
-
elif action in [
|
11046
|
+
|
11047
|
+
elif action in ["keyUp", "release"]:
|
10644
11048
|
# pyautogui.keyUp(input_key)
|
10645
11049
|
for _ in range(n_click):
|
10646
11050
|
for key in input_key:
|
10647
11051
|
pyautogui.keyUp(key)
|
10648
11052
|
pyautogui.sleep(interval)
|
10649
|
-
|
10650
|
-
elif action in [
|
11053
|
+
|
11054
|
+
elif action in ["hotkey", "shortcut"]:
|
10651
11055
|
pyautogui.hotkey(input_key)
|
10652
|
-
|
11056
|
+
|
11057
|
+
|
10653
11058
|
def mouse(
|
10654
11059
|
*args, # loc
|
10655
11060
|
action: str = "move",
|
@@ -10657,7 +11062,7 @@ def mouse(
|
|
10657
11062
|
loc_type: str = "absolute", # 'absolute', 'relative'
|
10658
11063
|
region: tuple = None, # (tuple, optional): A region (x, y, width, height) to search for the image.
|
10659
11064
|
image_path: str = None,
|
10660
|
-
wait:float = 0,
|
11065
|
+
wait: float = 0,
|
10661
11066
|
text: str = None,
|
10662
11067
|
confidence: float = 0.8,
|
10663
11068
|
button: str = "left",
|
@@ -10666,6 +11071,8 @@ def mouse(
|
|
10666
11071
|
scroll_amount: int = -500,
|
10667
11072
|
fail_safe: bool = True,
|
10668
11073
|
grayscale: bool = False,
|
11074
|
+
n_try: int = 10,
|
11075
|
+
verbose: bool = True,
|
10669
11076
|
**kwargs,
|
10670
11077
|
):
|
10671
11078
|
"""
|
@@ -10692,6 +11099,9 @@ def mouse(
|
|
10692
11099
|
import pyautogui
|
10693
11100
|
import time
|
10694
11101
|
|
11102
|
+
# import logging
|
11103
|
+
# logging.basicConfig(level=logging.DEBUG, filename="debug.log")
|
11104
|
+
|
10695
11105
|
pyautogui.FAILSAFE = fail_safe # Enable/disable fail-safe
|
10696
11106
|
loc_type = "absolute" if "abs" in loc_type else "relative"
|
10697
11107
|
if len(args) == 1:
|
@@ -10700,7 +11110,6 @@ def mouse(
|
|
10700
11110
|
x_offset, y_offset = None, None
|
10701
11111
|
else:
|
10702
11112
|
x_offset, y_offset = args
|
10703
|
-
|
10704
11113
|
elif len(args) == 2:
|
10705
11114
|
x_offset, y_offset = args
|
10706
11115
|
elif len(args) == 3:
|
@@ -10725,59 +11134,62 @@ def mouse(
|
|
10725
11134
|
"up",
|
10726
11135
|
"hold",
|
10727
11136
|
"press",
|
10728
|
-
"release"
|
11137
|
+
"release",
|
10729
11138
|
]
|
10730
11139
|
action = strcmp(action, what_action)[0]
|
10731
11140
|
# get the locations
|
10732
11141
|
location = None
|
10733
11142
|
if any([x_offset is None, y_offset is None]):
|
10734
11143
|
if region is None:
|
10735
|
-
w,h=pyautogui.size()
|
10736
|
-
region=(0,0,w,h)
|
10737
|
-
|
10738
|
-
|
10739
|
-
|
10740
|
-
|
10741
|
-
|
10742
|
-
|
10743
|
-
|
10744
|
-
|
10745
|
-
|
11144
|
+
w, h = pyautogui.size()
|
11145
|
+
region = (0, 0, w, h)
|
11146
|
+
retries = 0
|
11147
|
+
while location is None and retries <= n_try:
|
11148
|
+
try:
|
11149
|
+
confidence_ = round(float(confidence - 0.05 * retries), 2)
|
11150
|
+
location = pyautogui.locateOnScreen(
|
11151
|
+
image_path,
|
11152
|
+
confidence=confidence_,
|
11153
|
+
region=region,
|
11154
|
+
grayscale=grayscale,
|
11155
|
+
)
|
11156
|
+
except Exception as e:
|
11157
|
+
if verbose:
|
11158
|
+
print(f"confidence={confidence_},{e}")
|
11159
|
+
location = None
|
11160
|
+
retries += 1
|
10746
11161
|
|
10747
11162
|
# try:
|
10748
11163
|
if location:
|
10749
11164
|
x, y = pyautogui.center(location)
|
10750
11165
|
x += x_offset if x_offset else 0
|
10751
|
-
|
10752
|
-
|
10753
|
-
|
10754
|
-
|
11166
|
+
if x_offset is not None:
|
11167
|
+
x += x_offset
|
11168
|
+
if y_offset is not None:
|
11169
|
+
y += y_offset
|
11170
|
+
x_offset, y_offset = x, y
|
11171
|
+
print(action) if verbose else None
|
11172
|
+
if action in ["locate"]:
|
10755
11173
|
x, y = pyautogui.position()
|
10756
|
-
elif action in ["click", "double_click","triple_click"]:
|
10757
|
-
# if location:
|
10758
|
-
# x, y = pyautogui.center(location)
|
10759
|
-
# x += x_offset
|
10760
|
-
# y += y_offset
|
10761
|
-
# pyautogui.moveTo(x, y, duration=duration)
|
10762
|
-
# if action == "click":
|
10763
|
-
# pyautogui.click(x=x, y=y, clicks=n_click, interval=interval, button=button)
|
10764
|
-
# elif action == "double_click":
|
10765
|
-
# pyautogui.doubleClick(x=x, y=y, interval=interval, button=button)
|
10766
|
-
# elif action=='triple_click':
|
10767
|
-
# pyautogui.tripleClick(x=x,y=y,interval=interval, button=button)
|
10768
|
-
# else:
|
11174
|
+
elif action in ["click", "double_click", "triple_click"]:
|
10769
11175
|
if action == "click":
|
10770
11176
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10771
11177
|
time.sleep(wait)
|
10772
|
-
pyautogui.click(
|
11178
|
+
pyautogui.click(
|
11179
|
+
x=x_offset, y=y_offset, clicks=n_click, interval=interval, button=button
|
11180
|
+
)
|
10773
11181
|
elif action == "double_click":
|
10774
11182
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10775
11183
|
time.sleep(wait)
|
10776
|
-
pyautogui.doubleClick(
|
10777
|
-
|
11184
|
+
pyautogui.doubleClick(
|
11185
|
+
x=x_offset, y=y_offset, interval=interval, button=button
|
11186
|
+
)
|
11187
|
+
elif action == "triple_click":
|
10778
11188
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10779
11189
|
time.sleep(wait)
|
10780
|
-
pyautogui.tripleClick(
|
11190
|
+
pyautogui.tripleClick(
|
11191
|
+
x=x_offset, y=y_offset, interval=interval, button=button
|
11192
|
+
)
|
10781
11193
|
|
10782
11194
|
elif action in ["type", "write", "input"]:
|
10783
11195
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
@@ -10785,7 +11197,7 @@ def mouse(
|
|
10785
11197
|
if text is not None:
|
10786
11198
|
pyautogui.typewrite(text, interval=interval)
|
10787
11199
|
else:
|
10788
|
-
|
11200
|
+
print("Text must be provided for the 'type' action.") if verbose else None
|
10789
11201
|
|
10790
11202
|
elif action == "drag":
|
10791
11203
|
if loc_type == "absolute":
|
@@ -10804,12 +11216,12 @@ def mouse(
|
|
10804
11216
|
time.sleep(wait)
|
10805
11217
|
pyautogui.scroll(scroll_amount)
|
10806
11218
|
|
10807
|
-
elif action in ["down",
|
11219
|
+
elif action in ["down", "hold", "press"]:
|
10808
11220
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10809
11221
|
time.sleep(wait)
|
10810
11222
|
pyautogui.mouseDown(x_offset, y_offset, button=button, duration=duration)
|
10811
11223
|
|
10812
|
-
elif action in [
|
11224
|
+
elif action in ["up", "release"]:
|
10813
11225
|
pyautogui.moveTo(x_offset, y_offset, duration=duration)
|
10814
11226
|
time.sleep(wait)
|
10815
11227
|
pyautogui.mouseUp(x_offset, y_offset, button=button, duration=duration)
|
@@ -10817,29 +11229,23 @@ def mouse(
|
|
10817
11229
|
else:
|
10818
11230
|
raise ValueError(f"Unsupported action: {action}")
|
10819
11231
|
|
10820
|
-
# except pyautogui.ImageNotFoundException:
|
10821
|
-
# print(
|
10822
|
-
# "Image not found. Ensure the image is visible and parameters are correct."
|
10823
|
-
# )
|
10824
|
-
# except Exception as e:
|
10825
|
-
# print(f"An error occurred: {e}")
|
10826
|
-
|
10827
|
-
|
10828
11232
|
|
10829
11233
|
def py2installer(
|
10830
|
-
script_path: str=None,
|
10831
|
-
flatform:str="mingw64",
|
11234
|
+
script_path: str = None,
|
11235
|
+
flatform: str = "mingw64",
|
10832
11236
|
output_dir: str = "dist",
|
10833
11237
|
icon_path: str = None,
|
10834
|
-
|
10835
|
-
|
10836
|
-
|
11238
|
+
include_data: list = None,
|
11239
|
+
include_import: list = None,
|
11240
|
+
exclude_import: list = None,
|
11241
|
+
plugins: list = None,
|
10837
11242
|
use_nuitka: bool = True,
|
10838
|
-
onefile: bool = True,
|
10839
11243
|
console: bool = True,
|
10840
11244
|
clean_build: bool = False,
|
10841
11245
|
additional_args: list = None,
|
10842
11246
|
verbose: bool = True,
|
11247
|
+
standalone: bool = True,
|
11248
|
+
onefile: bool = False,
|
10843
11249
|
use_docker: bool = False,
|
10844
11250
|
docker_image: str = "python:3.12-slim",
|
10845
11251
|
):
|
@@ -10849,11 +11255,10 @@ def py2installer(
|
|
10849
11255
|
script_path (str): Path to the Python script to package.
|
10850
11256
|
output_dir (str): Directory where the executable will be stored.
|
10851
11257
|
icon_path (str): Path to the .ico file for the executable icon.
|
10852
|
-
|
10853
|
-
|
11258
|
+
include_data (list): List of additional data files or directories in "source:dest" format.
|
11259
|
+
exclude_import (list): List of hidden imports to include.
|
10854
11260
|
plugins (list): List of plugins imports to include.e.g., 'tk-inter'
|
10855
11261
|
use_nuitka (bool): Whether to use Nuitka instead of PyInstaller.
|
10856
|
-
onefile (bool): If True, produces a single executable file.
|
10857
11262
|
console (bool): If False, hides the console window (GUI mode).
|
10858
11263
|
clean_build (bool): If True, cleans previous build and dist directories.
|
10859
11264
|
additional_args (list): Additional arguments for PyInstaller/Nuitka.
|
@@ -10870,15 +11275,16 @@ def py2installer(
|
|
10870
11275
|
import sys
|
10871
11276
|
import glob
|
10872
11277
|
from pathlib import Path
|
11278
|
+
|
10873
11279
|
if run_once_within():
|
10874
|
-
usage_str="""
|
10875
|
-
# build locally
|
11280
|
+
usage_str = """
|
11281
|
+
# build locally
|
10876
11282
|
py2installer(
|
10877
11283
|
script_path="update_tab.py",
|
10878
11284
|
output_dir="dist",
|
10879
11285
|
icon_path="icon4app.ico",
|
10880
|
-
|
10881
|
-
|
11286
|
+
include_data=["dat/*.xlsx:dat"],
|
11287
|
+
exclude_import=["msoffcrypto", "tkinter", "pandas", "numpy"],
|
10882
11288
|
onefile=True,
|
10883
11289
|
console=False,
|
10884
11290
|
clean_build=True,
|
@@ -10893,6 +11299,26 @@ def py2installer(
|
|
10893
11299
|
use_docker=True,
|
10894
11300
|
docker_image="python:3.12-slim"
|
10895
11301
|
)
|
11302
|
+
# 尽量不要使用--include-package,这可能导致冲突
|
11303
|
+
py2installer(
|
11304
|
+
script_path="update_tab.py",
|
11305
|
+
# flatform=None,
|
11306
|
+
output_dir="dist_simp_subprocess",
|
11307
|
+
icon_path="icon4app.ico",
|
11308
|
+
standalone=True,
|
11309
|
+
onefile=False,
|
11310
|
+
include_data=["dat/*.xlsx=dat"],
|
11311
|
+
plugins=[
|
11312
|
+
"tk-inter",
|
11313
|
+
],
|
11314
|
+
use_nuitka=True,
|
11315
|
+
console=True,
|
11316
|
+
clean_build=False,
|
11317
|
+
verbose=0,
|
11318
|
+
)
|
11319
|
+
# 最终文件大小对比
|
11320
|
+
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;
|
11321
|
+
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
11322
|
"""
|
10897
11323
|
print(usage_str)
|
10898
11324
|
if verbose:
|
@@ -10904,7 +11330,7 @@ def py2installer(
|
|
10904
11330
|
if not script_path.exists():
|
10905
11331
|
raise FileNotFoundError(f"Script '{script_path}' not found.")
|
10906
11332
|
|
10907
|
-
|
11333
|
+
# Clean build and dist directories if requested
|
10908
11334
|
if clean_build:
|
10909
11335
|
for folder in ["build", "dist"]:
|
10910
11336
|
folder_path = Path(folder)
|
@@ -10914,12 +11340,13 @@ def py2installer(
|
|
10914
11340
|
for folder in ["build", "dist"]:
|
10915
11341
|
folder_path = Path(folder)
|
10916
11342
|
folder_path.mkdir(parents=True, exist_ok=True)
|
10917
|
-
|
10918
11343
|
|
10919
11344
|
if use_docker:
|
10920
11345
|
# Ensure Docker is installed
|
10921
11346
|
try:
|
10922
|
-
subprocess.run(
|
11347
|
+
subprocess.run(
|
11348
|
+
["docker", "--version"], check=True, capture_output=True, text=True
|
11349
|
+
)
|
10923
11350
|
except FileNotFoundError:
|
10924
11351
|
raise EnvironmentError("Docker is not installed or not in the PATH.")
|
10925
11352
|
|
@@ -10931,11 +11358,16 @@ def py2installer(
|
|
10931
11358
|
f"{dist_path}:/output:rw",
|
10932
11359
|
]
|
10933
11360
|
docker_cmd = [
|
10934
|
-
"docker",
|
10935
|
-
"
|
10936
|
-
"
|
11361
|
+
"docker",
|
11362
|
+
"run",
|
11363
|
+
"--rm",
|
11364
|
+
"-v",
|
11365
|
+
volumes[0],
|
11366
|
+
"-v",
|
11367
|
+
volumes[1],
|
10937
11368
|
docker_image,
|
10938
|
-
"bash",
|
11369
|
+
"bash",
|
11370
|
+
"-c",
|
10939
11371
|
]
|
10940
11372
|
|
10941
11373
|
# Build the packaging command inside the container
|
@@ -10947,11 +11379,11 @@ def py2installer(
|
|
10947
11379
|
cmd.extend(["--distpath", "/output"])
|
10948
11380
|
if icon_path:
|
10949
11381
|
cmd.extend(["--icon", f"/app/{Path(icon_path).name}"])
|
10950
|
-
if
|
10951
|
-
for data in
|
11382
|
+
if include_data:
|
11383
|
+
for data in include_data:
|
10952
11384
|
cmd.extend(["--add-data", f"/app/{data}"])
|
10953
|
-
if
|
10954
|
-
for hidden in
|
11385
|
+
if exclude_import:
|
11386
|
+
for hidden in exclude_import:
|
10955
11387
|
cmd.extend(["--hidden-import", hidden])
|
10956
11388
|
if additional_args:
|
10957
11389
|
cmd.extend(additional_args)
|
@@ -10976,25 +11408,32 @@ def py2installer(
|
|
10976
11408
|
raise
|
10977
11409
|
else:
|
10978
11410
|
# Handle local packaging (native build)
|
10979
|
-
cmd = ["nuitka"]
|
10980
|
-
|
10981
|
-
if 'min' in flatform.lower():
|
11411
|
+
cmd = ["nuitka"] if use_nuitka else ["pyinstaller"]
|
11412
|
+
if "min" in flatform.lower() and use_nuitka:
|
10982
11413
|
cmd.append("--mingw64")
|
10983
|
-
if
|
10984
|
-
|
11414
|
+
cmd.append("--standalone") if use_nuitka and standalone else None
|
11415
|
+
cmd.append("--onefile") if onefile else None
|
10985
11416
|
if not console:
|
10986
|
-
|
10987
|
-
|
11417
|
+
cmd.append("--windows-console-mode=disable")
|
11418
|
+
else:
|
11419
|
+
cmd.append("--windows-console-mode=attach")
|
10988
11420
|
|
10989
|
-
cmd.extend([f"--output-dir={output_dir}"])
|
11421
|
+
cmd.extend(["--show-progress", f"--output-dir={output_dir}"])
|
10990
11422
|
if icon_path:
|
10991
11423
|
icon_path = Path(icon_path)
|
10992
11424
|
if not icon_path.exists():
|
10993
11425
|
raise FileNotFoundError(f"Icon file '{icon_path}' not found.")
|
10994
|
-
|
11426
|
+
if sys.platform == "darwin": # macOS platform
|
11427
|
+
cmd.extend(
|
11428
|
+
["--macos-create-app-bundle", f"--macos-app-icon={icon_path}"]
|
11429
|
+
)
|
11430
|
+
elif sys.platform == "win32": # Windows platform
|
11431
|
+
cmd.extend([f"--windows-icon-from-ico={icon_path}"])
|
11432
|
+
elif sys.platform == "linux": # Linux platform
|
11433
|
+
cmd.append("--linux-onefile")
|
10995
11434
|
|
10996
|
-
if
|
10997
|
-
for data in
|
11435
|
+
if include_data:
|
11436
|
+
for data in include_data:
|
10998
11437
|
if "*" in data:
|
10999
11438
|
matches = glob.glob(data.split(":")[0])
|
11000
11439
|
for match in matches:
|
@@ -11009,30 +11448,47 @@ def py2installer(
|
|
11009
11448
|
cmd.extend(
|
11010
11449
|
["--include-data-file=" if use_nuitka else "--add-data", data]
|
11011
11450
|
)
|
11012
|
-
|
11013
|
-
|
11014
|
-
|
11015
|
-
|
11451
|
+
if exclude_import is not None:
|
11452
|
+
if any(exclude_import):
|
11453
|
+
cmd.extend([f"--nofollow-import-to={','.join(exclude_import)}"])
|
11454
|
+
if include_import is not None:
|
11455
|
+
if any(
|
11456
|
+
include_import
|
11457
|
+
): # are included in the final build. Some packages may require manual inclusion.
|
11458
|
+
cmd.extend([f"--include-package={','.join(include_import)}"])
|
11016
11459
|
if plugins:
|
11017
11460
|
for plugin in plugins:
|
11018
|
-
|
11461
|
+
# Adds support for tkinter, ensuring it works correctly in the standalone build.
|
11462
|
+
cmd.extend([f"--enable-plugin={plugin}"])
|
11019
11463
|
|
11020
11464
|
if additional_args:
|
11021
11465
|
cmd.extend(additional_args)
|
11022
11466
|
|
11467
|
+
# # clean
|
11468
|
+
# cmd.extend(
|
11469
|
+
# [ "--noinclude-numba-mode=nofollow", #Prevents the inclusion of the numba library and its dependencies, reducing the executable size.
|
11470
|
+
# "--noinclude-dask-mode=nofollow",#Excludes the dask library
|
11471
|
+
# "--noinclude-IPython-mode=nofollow",#Excludes the IPython library and its dependencies.
|
11472
|
+
# "--noinclude-unittest-mode=nofollow",#Excludes the unittest module (used for testing) from the build
|
11473
|
+
# "--noinclude-pytest-mode=nofollow",#Excludes the pytest library (used for testing) from the build.
|
11474
|
+
# "--noinclude-setuptools-mode=nofollow",#Excludes setuptools, which is not needed for the standalone executable.
|
11475
|
+
# "--lto=no",#Disables Link-Time Optimization (LTO), which reduces the compilation time but may slightly increase the size of the output.
|
11476
|
+
# ]
|
11477
|
+
# )
|
11478
|
+
|
11479
|
+
if clean_build:
|
11480
|
+
cmd.append(
|
11481
|
+
"--remove-output"
|
11482
|
+
) # Removes intermediate files created during the build process, keeping only the final executable.
|
11023
11483
|
# Add the script path (final positional argument)
|
11024
11484
|
cmd.append(str(script_path))
|
11025
|
-
|
11026
11485
|
# Ensure Windows shell compatibility
|
11027
11486
|
shell_flag = sys.platform.startswith("win")
|
11028
|
-
|
11029
|
-
# Run the command
|
11030
|
-
if verbose:
|
11031
|
-
print(f"Running command: {' '.join(cmd)}")
|
11487
|
+
print(f"Running command: ⤵ \n{' '.join(cmd)}\n")
|
11032
11488
|
try:
|
11033
11489
|
result = subprocess.run(
|
11034
11490
|
cmd,
|
11035
|
-
capture_output=
|
11491
|
+
capture_output=True,
|
11036
11492
|
text=True,
|
11037
11493
|
shell=shell_flag,
|
11038
11494
|
check=True,
|
@@ -11045,4 +11501,3 @@ def py2installer(
|
|
11045
11501
|
raise
|
11046
11502
|
|
11047
11503
|
print("\nPackaging complete. Check the output directory for the executable.")
|
11048
|
-
|