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
- 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
-