py2ls 0.2.4.37__py3-none-any.whl → 0.2.4.40__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
py2ls/ips.py CHANGED
@@ -26,6 +26,7 @@ import re
26
26
  import stat
27
27
  import platform
28
28
 
29
+
29
30
  # only for backup these scripts
30
31
  def backup(
31
32
  src="/Users/macjianfeng/Dropbox/github/python/py2ls/.venv/lib/python3.12/site-packages/py2ls/",
@@ -33,14 +34,16 @@ def backup(
33
34
  kind="py",
34
35
  overwrite=True,
35
36
  reverse=False,
36
- verbose=False
37
- ):
37
+ verbose=False,
38
+ ):
38
39
  if reverse:
39
- src, tar = tar,src
40
+ src, tar = tar, src
40
41
  print(f"reversed")
41
- f = listdir(src, kind, verbose=verbose)
42
+ f = listdir(src, kind=kind, verbose=verbose)
42
43
  [copy(i, tar, overwrite=overwrite, verbose=verbose) for i in f.path]
43
- print(f"all files are copied from {os.path.basename(src)} to {tar}") if verbose else None
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(img: list,
808
- method:str ="knn",
809
- thr:float =0.75,
810
- detector: str = "sift",
811
- plot_:bool =True,
812
- figsize=[12, 6],
813
- grid_size=10,# only for grid detector
814
- **kwargs):
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(cv2.KeyPoint(j + grid_size // 2, i + grid_size // 2, grid_size))
895
- keypoints2.append(cv2.KeyPoint(j + grid_size // 2, i + grid_size // 2, grid_size))
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 = [cv2.KeyPoint(x, y, 1) for y in range(gray1.shape[0]) for x in range(gray1.shape[1])]
907
- keypoints2 = [cv2.KeyPoint(x, y, 1) for y in range(gray2.shape[0]) for x in range(gray2.shape[1])]
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(src_pts, dst_pts, cv2.RANSAC, 5.0)
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
- ax[0].imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[0].imshow(image1)
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
- ax[1].imshow(cv2.cvtColor(warped_image2, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[1].imshow(warped_image2)
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
- plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) if bool_cvt else plt.imshow(result)
981
- plt.title(f"Feature Matches ({len(good_matches)} matches, Score: {similarity_score:.4f})")
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
- ax[0].imshow(cv2.cvtColor(img1_unmatch, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[0].imshow(img1_unmatch)
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
- ax[1].imshow(cv2.cvtColor(img2_unmatch, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[1].imshow(img2_unmatch)
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
- ax[0].imshow(cv2.cvtColor(img1_match, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[0].imshow(img1_match)
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
- ax[1].imshow(cv2.cvtColor(img2_match, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[1].imshow(img2_match)
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#, homography_matrix
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
- #* needs a password?
2716
- import msoffcrypto # pip install msoffcrypto-tool
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, 'rb') as f:
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(temp_file, sheet_name=sheet_name, index=False, engine="xlsxwriter", **kwargs)
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, 'wb') as encrypted_file:
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(fpath, engine="openpyxl", mode="a", if_sheet_exists="new") as writer:
3373
- df.to_excel(writer, sheet_name=sheet_name, index=False, **kwargs)
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 listfunc(lib_name, opt="call"):
4455
- if opt == "call":
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
- def func_list(lib_name, opt="call"):
4463
- return list_func(lib_name, opt=opt)
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
- if isinstance(fpath,str):
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
- def str2words(content, method="combined", custom_dict=None, sym_spell_params=None, use_threading=True):
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(executor.map(lambda x: process_string(x, method, sym_spell, custom_dict), content))
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 [process_string(item, method, sym_spell, custom_dict) for item in content]
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(["b" in filter_name.lower(),"ur" in filter_name.lower(), "box" not in filter_name.lower()]):
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'processing {filter_name}')
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)[:,:,:3]
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=['mean','median','pca','gradient orientation','template matching','moments','fft']
5487
- by=strcmp(by, methods)[0]
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
- def imgsets(img,
5577
- auto:bool=True,
5578
- size=None,
5579
- figsize=None,
5580
- dpi:int=200,
5581
- show_axis:bool=False,
5582
- plot_:bool=True,
5583
- verbose:bool=False,
5584
- model:str="isnet-general-use",
5585
- **kwargs,
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 = [int((i / float(v_max)) ** inv_gamma * int(v_max)) for i in range(int(v_max))]
5678
- return lut #image.point(lut)
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,# autocontrasted
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 = sum(i * w for i, w in enumerate(channel_histogram)) / total_pixels
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(2.0, max(0.5, 1.0 + avg_edge_intensity / normalization_factor))
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=["sharp","color","contrast","bright","crop","rotate",'size',"resize",
5780
- "thumbnail","cover","contain","filter","fit","pad",
5781
- "rem","rm","back","bg_color","cut","gamma","flip","booster"]
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] # correct the param name
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 'flip' in k.lower():
5814
- if 'l' in value and 'r' in value:
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(['u' in value and'd' in value, 't' in value and 'b' in value]):
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(): # filter
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(img_update, filter_name, filter_value,verbose=verbose)
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(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.")
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, session=session
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], session=session
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], session=session
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(img_update, session=new_session(strcmp(value, models_support_rem)[0]))
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 'boost' in k.lower():
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(device=device,
5915
- scale=scale,
5916
- model_path=model_path,
5917
- model="RealESRGAN_x4plus"
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
- "Header 1": [10, 2000000, 30],
6556
- "Header 2": [
6557
- 40000,
6558
- '"start_color": "4F81BD", "end_color","start_color": "a1cd1e","start_color": "4F81BD", "end_color","start_color": "a1cd1e"',
6559
- 60,
6560
- ],
6561
- "Header 3": [70, 80, 90],
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
- (slice(0, 3), slice(1, 3)): {
6604
- "font": {
6605
- "name": "Arial", # Font name
6606
- "size": 12, # Font size
6607
- "bold": False, # Bold text
6608
- "italic": True, # Italic text
6609
- "underline": None, # No underline
6610
- "color": "#000000", # Font color
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
- "fill": {
6613
- "start_color": "#c61313", # Background color
6614
- "end_color": "#490606", # End color
6615
- "fill_type": "solid", # Fill type
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
- "alignment": {
6618
- "horizontal": "left", # Horizontal alignment
6619
- "vertical": "top", # Vertical alignment
6620
- "wrap_text": True, # Enable text wrapping
6621
- "shrink_to_fit": False, # Disable shrink to fit
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
- "border": {
6626
- "left": "thin", # Left border style
6627
- "right": "thin", # Right border style
6628
- "top": "thin", # Top border style
6629
- "bottom": "thin", # Bottom border style
6630
- "color": "000000", # Border color
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
- # * border settings
6634
- # "thin": Thin border line
6635
- # "medium": Medium border line
6636
- # "thick": Thick border line
6637
- # "dotted": Dotted border line
6638
- # "dashed": Dashed border line
6639
- # "hair": Hairline border (very thin)
6640
- # "mediumDashed": Medium dashed border line
6641
- # "dashDot": Dash-dot border line
6642
- # "dashDotDot": Dash-dot-dot border line
6643
- # "slantDashDot": Slant dash-dot border line
6644
- },
6645
- ],
6646
- width={2: 30}, # when it is None, it will automatic adjust width
6647
- height={2: 50, 3: 25}, # keys are columns
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
- len_ = [len(str(cell.value)) for cell in col if cell.value]
6770
- if len_:
6771
- # scaling_factor = 1.2
6772
- max_length = max(len_) # * scaling_factor
6773
- ws.column_dimensions[get_column_letter(col[0].column)].width = (
6774
- max_length
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'Error: {e}')
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
- def keyboard(*args, action='press', n_click=1,interval=0,verbose=False,**kwargs):
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
- input_key = args
10587
-
10588
- actions = ['press','keyDown','keyUp', 'hold','release', 'hotkey','shortcut']
10589
- action = strcmp(action,actions)[0]
10590
- keyboard_keys_=['\t', '\n', '\r', ' ', '!', '"', '#', '$', '%', '&', "'", '(',
10591
- ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7',
10592
- '8', '9', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`',
10593
- 'a', 'b', 'c', 'd', 'e','f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
10594
- 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
10595
- 'accept', 'add', 'alt', 'altleft', 'altright', 'apps', 'backspace',
10596
- 'browserback', 'browserfavorites', 'browserforward', 'browserhome',
10597
- 'browserrefresh', 'browsersearch', 'browserstop', 'capslock', 'clear',
10598
- 'convert', 'ctrl', 'ctrlleft', 'ctrlright', 'decimal', 'del', 'delete',
10599
- 'divide', 'down', 'end', 'enter', 'esc', 'escape', 'execute', 'f1', 'f10',
10600
- 'f11', 'f12', 'f13', 'f14', 'f15', 'f16', 'f17', 'f18', 'f19', 'f2', 'f20',
10601
- 'f21', 'f22', 'f23', 'f24', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9',
10602
- 'final', 'fn', 'hanguel', 'hangul', 'hanja', 'help', 'home', 'insert', 'junja',
10603
- 'kana', 'kanji', 'launchapp1', 'launchapp2', 'launchmail',
10604
- 'launchmediaselect', 'left', 'modechange', 'multiply', 'nexttrack',
10605
- 'nonconvert', 'num0', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6',
10606
- 'num7', 'num8', 'num9', 'numlock', 'pagedown', 'pageup', 'pause', 'pgdn',
10607
- 'pgup', 'playpause', 'prevtrack', 'print', 'printscreen', 'prntscrn',
10608
- 'prtsc', 'prtscr', 'return', 'right', 'scrolllock', 'select', 'separator',
10609
- 'shift', 'shiftleft', 'shiftright', 'sleep', 'space', 'stop', 'subtract', 'tab',
10610
- 'up', 'volumedown', 'volumemute', 'volumeup', 'win', 'winleft', 'winright', 'yen',
10611
- 'command', 'option', 'optionleft', 'optionright']
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 ['hotkey','shortcut']:
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 = ['command', 'option', 'optionleft', 'optionright','win', 'winleft', 'winright','ctrl', 'ctrlleft', 'ctrlright']
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='hotkey'
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 ['keyDown','hold']:
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 ['keyUp','release']:
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 ['hotkey','shortcut']:
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
- print(region)
10738
- try:
10739
- print(image_path)
10740
- location = pyautogui.locateOnScreen(
10741
- image_path, confidence=confidence, region=region, grayscale=grayscale
10742
- )
10743
- print(pyautogui.center(location))
10744
- except Exception as e:
10745
- location = None
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
- y += y_offset if y_offset else 0
10752
- x_offset, y_offset = x,y
10753
- print(action)
10754
- if action in ['locate']:
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(x=x_offset, y=y_offset, clicks=n_click, interval=interval, button=button)
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(x=x_offset, y=y_offset, interval=interval, button=button)
10777
- elif action=='triple_click':
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(x=x_offset, y=y_offset, interval=interval, button=button)
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
- raise ValueError("Text must be provided for the 'type' action.")
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",'hold','press']:
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 ['up','release']:
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
- extra_data: list = None,
10835
- hidden_imports: list = None,
10836
- plugins:list=None,
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
- extra_data (list): List of additional data files or directories in "source:dest" format.
10853
- hidden_imports (list): List of hidden imports to include.
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
- extra_data=["dat/*.xlsx:dat"],
10881
- hidden_imports=["msoffcrypto", "tkinter", "pandas", "numpy"],
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
- # Clean build and dist directories if requested
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(["docker", "--version"], check=True, capture_output=True, text=True)
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", "run", "--rm",
10935
- "-v", volumes[0],
10936
- "-v", volumes[1],
11361
+ "docker",
11362
+ "run",
11363
+ "--rm",
11364
+ "-v",
11365
+ volumes[0],
11366
+ "-v",
11367
+ volumes[1],
10937
11368
  docker_image,
10938
- "bash", "-c",
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 extra_data:
10951
- for data in extra_data:
11382
+ if include_data:
11383
+ for data in include_data:
10952
11384
  cmd.extend(["--add-data", f"/app/{data}"])
10953
- if hidden_imports:
10954
- for hidden in hidden_imports:
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
- cmd.append("--standalone") # Make sure to use --standalone for independent environments
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 onefile:
10984
- cmd.append("--onefile")
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
- # cmd.append("--windows-disable-console") # Disabled based on your request
10987
- pass
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}"]) # Correct the space issue here
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
- cmd.extend([f"--windows-icon-from-ico={icon_path}"])
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 extra_data:
10997
- for data in extra_data:
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
- if hidden_imports:
11014
- cmd.extend([f"--nofollow-import-to={','.join(hidden_imports)}"])
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
- cmd.extend([f"--plugin-enable={plugin}"])
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=not verbose,
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
-