py2ls 0.2.4.36__py3-none-any.whl → 0.2.4.39__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
py2ls/ips.py CHANGED
@@ -26,6 +26,7 @@ import re
26
26
  import stat
27
27
  import platform
28
28
 
29
+
29
30
  # only for backup these scripts
30
31
  def backup(
31
32
  src="/Users/macjianfeng/Dropbox/github/python/py2ls/.venv/lib/python3.12/site-packages/py2ls/",
@@ -33,14 +34,16 @@ def backup(
33
34
  kind="py",
34
35
  overwrite=True,
35
36
  reverse=False,
36
- verbose=False
37
- ):
37
+ verbose=False,
38
+ ):
38
39
  if reverse:
39
- src, tar = tar,src
40
+ src, tar = tar, src
40
41
  print(f"reversed")
41
- f = listdir(src, kind, verbose=verbose)
42
+ f = listdir(src, kind=kind, verbose=verbose)
42
43
  [copy(i, tar, overwrite=overwrite, verbose=verbose) for i in f.path]
43
- 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"""
4520
+
4521
+ def listfunc_(lib_name, opt="call"):
4522
+ """list functions in specific lib"""
4523
+ if opt == "call":
4524
+ funcs = [
4525
+ func
4526
+ for func in dir(lib_name)
4527
+ if callable(getattr(lib_name, func))
4528
+ if not func.startswith("__")
4529
+ ]
4530
+ else:
4531
+ funcs = dir(lib_name)
4532
+ return funcs
4460
4533
 
4534
+ if any([i in where.lower() for i in ["all", "env"]]):
4535
+ import pkg_resources
4461
4536
 
4462
- def func_list(lib_name, opt="call"):
4463
- return list_func(lib_name, opt=opt)
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
 
@@ -10320,7 +10475,7 @@ def get_loc(input_data, user_agent="0413@mygmail.com)", verbose=True):
10320
10475
  print(f"Could not find {input_data}.")
10321
10476
  return location
10322
10477
  except Exception as e:
10323
- print(f'Error: {e}')
10478
+ print(f"Error: {e}")
10324
10479
  return
10325
10480
 
10326
10481
  # Case 2: Input is latitude and longitude (float or tuple)
@@ -10516,6 +10671,7 @@ def depass(encrypted_code: str, method: str = "AES", key: str = None):
10516
10671
  else:
10517
10672
  raise ValueError("Unsupported decryption method")
10518
10673
 
10674
+
10519
10675
  def get_clip(dir_save=None):
10520
10676
  """
10521
10677
  Master function to extract content from the clipboard (text, URL, or image).
@@ -10537,6 +10693,7 @@ def get_clip(dir_save=None):
10537
10693
  import pyperclip
10538
10694
  from PIL import ImageGrab, Image
10539
10695
  import validators
10696
+
10540
10697
  # 1. Check for text in the clipboard
10541
10698
  clipboard_content = pyperclip.paste()
10542
10699
  if clipboard_content:
@@ -10568,7 +10725,8 @@ def get_clip(dir_save=None):
10568
10725
  print(f"An error occurred: {e}")
10569
10726
  return result
10570
10727
 
10571
- def keyboard(*args, action='press', n_click=1,interval=0,verbose=False,**kwargs):
10728
+
10729
+ def keyboard(*args, action="press", n_click=1, interval=0, verbose=False, **kwargs):
10572
10730
  """
10573
10731
  Simulates keyboard input using pyautogui.
10574
10732
 
@@ -10583,45 +10741,231 @@ def keyboard(*args, action='press', n_click=1,interval=0,verbose=False,**kwargs)
10583
10741
  keyboard("command", "d", action="shorcut")
10584
10742
  """
10585
10743
  import pyautogui
10586
- 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']
10744
+
10745
+ input_key = args
10746
+
10747
+ actions = ["press", "keyDown", "keyUp", "hold", "release", "hotkey", "shortcut"]
10748
+ action = strcmp(action, actions)[0]
10749
+ keyboard_keys_ = [
10750
+ "\t",
10751
+ "\n",
10752
+ "\r",
10753
+ " ",
10754
+ "!",
10755
+ '"',
10756
+ "#",
10757
+ "$",
10758
+ "%",
10759
+ "&",
10760
+ "'",
10761
+ "(",
10762
+ ")",
10763
+ "*",
10764
+ "+",
10765
+ ",",
10766
+ "-",
10767
+ ".",
10768
+ "/",
10769
+ "0",
10770
+ "1",
10771
+ "2",
10772
+ "3",
10773
+ "4",
10774
+ "5",
10775
+ "6",
10776
+ "7",
10777
+ "8",
10778
+ "9",
10779
+ ":",
10780
+ ";",
10781
+ "<",
10782
+ "=",
10783
+ ">",
10784
+ "?",
10785
+ "@",
10786
+ "[",
10787
+ "\\",
10788
+ "]",
10789
+ "^",
10790
+ "_",
10791
+ "`",
10792
+ "a",
10793
+ "b",
10794
+ "c",
10795
+ "d",
10796
+ "e",
10797
+ "f",
10798
+ "g",
10799
+ "h",
10800
+ "i",
10801
+ "j",
10802
+ "k",
10803
+ "l",
10804
+ "m",
10805
+ "n",
10806
+ "o",
10807
+ "p",
10808
+ "q",
10809
+ "r",
10810
+ "s",
10811
+ "t",
10812
+ "u",
10813
+ "v",
10814
+ "w",
10815
+ "x",
10816
+ "y",
10817
+ "z",
10818
+ "{",
10819
+ "|",
10820
+ "}",
10821
+ "~",
10822
+ "accept",
10823
+ "add",
10824
+ "alt",
10825
+ "altleft",
10826
+ "altright",
10827
+ "apps",
10828
+ "backspace",
10829
+ "browserback",
10830
+ "browserfavorites",
10831
+ "browserforward",
10832
+ "browserhome",
10833
+ "browserrefresh",
10834
+ "browsersearch",
10835
+ "browserstop",
10836
+ "capslock",
10837
+ "clear",
10838
+ "convert",
10839
+ "ctrl",
10840
+ "ctrlleft",
10841
+ "ctrlright",
10842
+ "decimal",
10843
+ "del",
10844
+ "delete",
10845
+ "divide",
10846
+ "down",
10847
+ "end",
10848
+ "enter",
10849
+ "esc",
10850
+ "escape",
10851
+ "execute",
10852
+ "f1",
10853
+ "f10",
10854
+ "f11",
10855
+ "f12",
10856
+ "f13",
10857
+ "f14",
10858
+ "f15",
10859
+ "f16",
10860
+ "f17",
10861
+ "f18",
10862
+ "f19",
10863
+ "f2",
10864
+ "f20",
10865
+ "f21",
10866
+ "f22",
10867
+ "f23",
10868
+ "f24",
10869
+ "f3",
10870
+ "f4",
10871
+ "f5",
10872
+ "f6",
10873
+ "f7",
10874
+ "f8",
10875
+ "f9",
10876
+ "final",
10877
+ "fn",
10878
+ "hanguel",
10879
+ "hangul",
10880
+ "hanja",
10881
+ "help",
10882
+ "home",
10883
+ "insert",
10884
+ "junja",
10885
+ "kana",
10886
+ "kanji",
10887
+ "launchapp1",
10888
+ "launchapp2",
10889
+ "launchmail",
10890
+ "launchmediaselect",
10891
+ "left",
10892
+ "modechange",
10893
+ "multiply",
10894
+ "nexttrack",
10895
+ "nonconvert",
10896
+ "num0",
10897
+ "num1",
10898
+ "num2",
10899
+ "num3",
10900
+ "num4",
10901
+ "num5",
10902
+ "num6",
10903
+ "num7",
10904
+ "num8",
10905
+ "num9",
10906
+ "numlock",
10907
+ "pagedown",
10908
+ "pageup",
10909
+ "pause",
10910
+ "pgdn",
10911
+ "pgup",
10912
+ "playpause",
10913
+ "prevtrack",
10914
+ "print",
10915
+ "printscreen",
10916
+ "prntscrn",
10917
+ "prtsc",
10918
+ "prtscr",
10919
+ "return",
10920
+ "right",
10921
+ "scrolllock",
10922
+ "select",
10923
+ "separator",
10924
+ "shift",
10925
+ "shiftleft",
10926
+ "shiftright",
10927
+ "sleep",
10928
+ "space",
10929
+ "stop",
10930
+ "subtract",
10931
+ "tab",
10932
+ "up",
10933
+ "volumedown",
10934
+ "volumemute",
10935
+ "volumeup",
10936
+ "win",
10937
+ "winleft",
10938
+ "winright",
10939
+ "yen",
10940
+ "command",
10941
+ "option",
10942
+ "optionleft",
10943
+ "optionright",
10944
+ ]
10612
10945
  if verbose:
10613
10946
  print(f"supported keys: {keyboard_keys_}")
10614
10947
 
10615
- if action not in ['hotkey','shortcut']:
10948
+ if action not in ["hotkey", "shortcut"]:
10616
10949
  if not isinstance(input_key, list):
10617
- input_key=list(input_key)
10618
- input_key = [strcmp(i, keyboard_keys_)[0] for i in input_key ]
10950
+ input_key = list(input_key)
10951
+ input_key = [strcmp(i, keyboard_keys_)[0] for i in input_key]
10619
10952
 
10620
10953
  # correct action
10621
- cmd_keys = ['command', 'option', 'optionleft', 'optionright','win', 'winleft', 'winright','ctrl', 'ctrlleft', 'ctrlright']
10954
+ cmd_keys = [
10955
+ "command",
10956
+ "option",
10957
+ "optionleft",
10958
+ "optionright",
10959
+ "win",
10960
+ "winleft",
10961
+ "winright",
10962
+ "ctrl",
10963
+ "ctrlleft",
10964
+ "ctrlright",
10965
+ ]
10622
10966
  try:
10623
10967
  if any([i in cmd_keys for i in input_key]):
10624
- action='hotkey'
10968
+ action = "hotkey"
10625
10969
  except:
10626
10970
  pass
10627
10971
 
@@ -10633,23 +10977,24 @@ def keyboard(*args, action='press', n_click=1,interval=0,verbose=False,**kwargs)
10633
10977
  for key in input_key:
10634
10978
  pyautogui.press(key)
10635
10979
  pyautogui.sleep(interval)
10636
- elif action in ['keyDown','hold']:
10980
+ elif action in ["keyDown", "hold"]:
10637
10981
  # pyautogui.keyDown(input_key)
10638
10982
  for _ in range(n_click):
10639
10983
  for key in input_key:
10640
10984
  pyautogui.keyDown(key)
10641
10985
  pyautogui.sleep(interval)
10642
-
10643
- elif action in ['keyUp','release']:
10986
+
10987
+ elif action in ["keyUp", "release"]:
10644
10988
  # pyautogui.keyUp(input_key)
10645
10989
  for _ in range(n_click):
10646
10990
  for key in input_key:
10647
10991
  pyautogui.keyUp(key)
10648
10992
  pyautogui.sleep(interval)
10649
-
10650
- elif action in ['hotkey','shortcut']:
10993
+
10994
+ elif action in ["hotkey", "shortcut"]:
10651
10995
  pyautogui.hotkey(input_key)
10652
-
10996
+
10997
+
10653
10998
  def mouse(
10654
10999
  *args, # loc
10655
11000
  action: str = "move",
@@ -10657,7 +11002,7 @@ def mouse(
10657
11002
  loc_type: str = "absolute", # 'absolute', 'relative'
10658
11003
  region: tuple = None, # (tuple, optional): A region (x, y, width, height) to search for the image.
10659
11004
  image_path: str = None,
10660
- wait:float = 0,
11005
+ wait: float = 0,
10661
11006
  text: str = None,
10662
11007
  confidence: float = 0.8,
10663
11008
  button: str = "left",
@@ -10725,15 +11070,15 @@ def mouse(
10725
11070
  "up",
10726
11071
  "hold",
10727
11072
  "press",
10728
- "release"
11073
+ "release",
10729
11074
  ]
10730
11075
  action = strcmp(action, what_action)[0]
10731
11076
  # get the locations
10732
11077
  location = None
10733
11078
  if any([x_offset is None, y_offset is None]):
10734
11079
  if region is None:
10735
- w,h=pyautogui.size()
10736
- region=(0,0,w,h)
11080
+ w, h = pyautogui.size()
11081
+ region = (0, 0, w, h)
10737
11082
  print(region)
10738
11083
  try:
10739
11084
  print(image_path)
@@ -10749,16 +11094,16 @@ def mouse(
10749
11094
  x, y = pyautogui.center(location)
10750
11095
  x += x_offset if x_offset else 0
10751
11096
  y += y_offset if y_offset else 0
10752
- x_offset, y_offset = x,y
11097
+ x_offset, y_offset = x, y
10753
11098
  print(action)
10754
- if action in ['locate']:
11099
+ if action in ["locate"]:
10755
11100
  x, y = pyautogui.position()
10756
- elif action in ["click", "double_click","triple_click"]:
11101
+ elif action in ["click", "double_click", "triple_click"]:
10757
11102
  # if location:
10758
11103
  # x, y = pyautogui.center(location)
10759
11104
  # x += x_offset
10760
11105
  # y += y_offset
10761
- # pyautogui.moveTo(x, y, duration=duration)
11106
+ # pyautogui.moveTo(x, y, duration=duration)
10762
11107
  # if action == "click":
10763
11108
  # pyautogui.click(x=x, y=y, clicks=n_click, interval=interval, button=button)
10764
11109
  # elif action == "double_click":
@@ -10769,15 +11114,21 @@ def mouse(
10769
11114
  if action == "click":
10770
11115
  pyautogui.moveTo(x_offset, y_offset, duration=duration)
10771
11116
  time.sleep(wait)
10772
- pyautogui.click(x=x_offset, y=y_offset, clicks=n_click, interval=interval, button=button)
11117
+ pyautogui.click(
11118
+ x=x_offset, y=y_offset, clicks=n_click, interval=interval, button=button
11119
+ )
10773
11120
  elif action == "double_click":
10774
11121
  pyautogui.moveTo(x_offset, y_offset, duration=duration)
10775
11122
  time.sleep(wait)
10776
- pyautogui.doubleClick(x=x_offset, y=y_offset, interval=interval, button=button)
10777
- elif action=='triple_click':
11123
+ pyautogui.doubleClick(
11124
+ x=x_offset, y=y_offset, interval=interval, button=button
11125
+ )
11126
+ elif action == "triple_click":
10778
11127
  pyautogui.moveTo(x_offset, y_offset, duration=duration)
10779
11128
  time.sleep(wait)
10780
- pyautogui.tripleClick(x=x_offset, y=y_offset, interval=interval, button=button)
11129
+ pyautogui.tripleClick(
11130
+ x=x_offset, y=y_offset, interval=interval, button=button
11131
+ )
10781
11132
 
10782
11133
  elif action in ["type", "write", "input"]:
10783
11134
  pyautogui.moveTo(x_offset, y_offset, duration=duration)
@@ -10804,12 +11155,12 @@ def mouse(
10804
11155
  time.sleep(wait)
10805
11156
  pyautogui.scroll(scroll_amount)
10806
11157
 
10807
- elif action in ["down",'hold','press']:
11158
+ elif action in ["down", "hold", "press"]:
10808
11159
  pyautogui.moveTo(x_offset, y_offset, duration=duration)
10809
11160
  time.sleep(wait)
10810
11161
  pyautogui.mouseDown(x_offset, y_offset, button=button, duration=duration)
10811
11162
 
10812
- elif action in ['up','release']:
11163
+ elif action in ["up", "release"]:
10813
11164
  pyautogui.moveTo(x_offset, y_offset, duration=duration)
10814
11165
  time.sleep(wait)
10815
11166
  pyautogui.mouseUp(x_offset, y_offset, button=button, duration=duration)
@@ -10825,21 +11176,23 @@ def mouse(
10825
11176
  # print(f"An error occurred: {e}")
10826
11177
 
10827
11178
 
10828
-
11179
+
10829
11180
  def py2installer(
10830
- script_path: str=None,
10831
- flatform:str="mingw64",
11181
+ script_path: str = None,
11182
+ flatform: str = "mingw64",
10832
11183
  output_dir: str = "dist",
10833
11184
  icon_path: str = None,
10834
- extra_data: list = None,
10835
- hidden_imports: list = None,
10836
- plugins:list=None,
11185
+ include_data: list = None,
11186
+ include_import: list = None,
11187
+ exclude_import: list = None,
11188
+ plugins: list = None,
10837
11189
  use_nuitka: bool = True,
10838
- onefile: bool = True,
10839
11190
  console: bool = True,
10840
11191
  clean_build: bool = False,
10841
11192
  additional_args: list = None,
10842
11193
  verbose: bool = True,
11194
+ standalone: bool = True,
11195
+ onefile: bool = False,
10843
11196
  use_docker: bool = False,
10844
11197
  docker_image: str = "python:3.12-slim",
10845
11198
  ):
@@ -10849,11 +11202,10 @@ def py2installer(
10849
11202
  script_path (str): Path to the Python script to package.
10850
11203
  output_dir (str): Directory where the executable will be stored.
10851
11204
  icon_path (str): Path to the .ico file for the executable icon.
10852
- extra_data (list): List of additional data files or directories in "source:dest" format.
10853
- hidden_imports (list): List of hidden imports to include.
11205
+ include_data (list): List of additional data files or directories in "source:dest" format.
11206
+ exclude_import (list): List of hidden imports to include.
10854
11207
  plugins (list): List of plugins imports to include.e.g., 'tk-inter'
10855
11208
  use_nuitka (bool): Whether to use Nuitka instead of PyInstaller.
10856
- onefile (bool): If True, produces a single executable file.
10857
11209
  console (bool): If False, hides the console window (GUI mode).
10858
11210
  clean_build (bool): If True, cleans previous build and dist directories.
10859
11211
  additional_args (list): Additional arguments for PyInstaller/Nuitka.
@@ -10870,15 +11222,16 @@ def py2installer(
10870
11222
  import sys
10871
11223
  import glob
10872
11224
  from pathlib import Path
11225
+
10873
11226
  if run_once_within():
10874
- usage_str="""
10875
- # build locally
11227
+ usage_str = """
11228
+ # build locally
10876
11229
  py2installer(
10877
11230
  script_path="update_tab.py",
10878
11231
  output_dir="dist",
10879
11232
  icon_path="icon4app.ico",
10880
- extra_data=["dat/*.xlsx:dat"],
10881
- hidden_imports=["msoffcrypto", "tkinter", "pandas", "numpy"],
11233
+ include_data=["dat/*.xlsx:dat"],
11234
+ exclude_import=["msoffcrypto", "tkinter", "pandas", "numpy"],
10882
11235
  onefile=True,
10883
11236
  console=False,
10884
11237
  clean_build=True,
@@ -10893,6 +11246,26 @@ def py2installer(
10893
11246
  use_docker=True,
10894
11247
  docker_image="python:3.12-slim"
10895
11248
  )
11249
+ # 尽量不要使用--include-package,这可能导致冲突
11250
+ py2installer(
11251
+ script_path="update_tab.py",
11252
+ # flatform=None,
11253
+ output_dir="dist_simp_subprocess",
11254
+ icon_path="icon4app.ico",
11255
+ standalone=True,
11256
+ onefile=False,
11257
+ include_data=["dat/*.xlsx=dat"],
11258
+ plugins=[
11259
+ "tk-inter",
11260
+ ],
11261
+ use_nuitka=True,
11262
+ console=True,
11263
+ clean_build=False,
11264
+ verbose=0,
11265
+ )
11266
+ # 最终文件大小对比
11267
+ 900 MB: nuitka --mingw64 --standalone --windows-console-mode=attach --show-progress --output-dir=dist --macos-create-app-bundle --macos-app-icon=icon4app.ico --nofollow-import-to=timm,paddle,torch,torchmetrics,torchvision,tensorflow,tensorboard,tensorboardx,tensorboard-data-server,textblob,PIL,sklearn,scienceplots,scikit-image,scikit-learn,scikit-surprise,scipy,spikeinterface,spike-sort-lfpy,stanza,statsmodels,streamlit,streamlit-autorefresh,streamlit-folium,pkg2ls,plotly --include-package=msoffcrypto,tkinter,datetime,pandas,numpy --enable-plugin=tk-inter update_tab.py;
11268
+ 470 MB: nuitka --mingw64 --standalone --windows-console-mode=attach --show-progress --output-dir=dist_direct_nuitka --macos-create-app-bundle --macos-app-icon=icon4app.ico --enable-plugin=tk-inter update_taby ;
10896
11269
  """
10897
11270
  print(usage_str)
10898
11271
  if verbose:
@@ -10904,7 +11277,7 @@ def py2installer(
10904
11277
  if not script_path.exists():
10905
11278
  raise FileNotFoundError(f"Script '{script_path}' not found.")
10906
11279
 
10907
- # Clean build and dist directories if requested
11280
+ # Clean build and dist directories if requested
10908
11281
  if clean_build:
10909
11282
  for folder in ["build", "dist"]:
10910
11283
  folder_path = Path(folder)
@@ -10914,12 +11287,13 @@ def py2installer(
10914
11287
  for folder in ["build", "dist"]:
10915
11288
  folder_path = Path(folder)
10916
11289
  folder_path.mkdir(parents=True, exist_ok=True)
10917
-
10918
11290
 
10919
11291
  if use_docker:
10920
11292
  # Ensure Docker is installed
10921
11293
  try:
10922
- subprocess.run(["docker", "--version"], check=True, capture_output=True, text=True)
11294
+ subprocess.run(
11295
+ ["docker", "--version"], check=True, capture_output=True, text=True
11296
+ )
10923
11297
  except FileNotFoundError:
10924
11298
  raise EnvironmentError("Docker is not installed or not in the PATH.")
10925
11299
 
@@ -10931,11 +11305,16 @@ def py2installer(
10931
11305
  f"{dist_path}:/output:rw",
10932
11306
  ]
10933
11307
  docker_cmd = [
10934
- "docker", "run", "--rm",
10935
- "-v", volumes[0],
10936
- "-v", volumes[1],
11308
+ "docker",
11309
+ "run",
11310
+ "--rm",
11311
+ "-v",
11312
+ volumes[0],
11313
+ "-v",
11314
+ volumes[1],
10937
11315
  docker_image,
10938
- "bash", "-c",
11316
+ "bash",
11317
+ "-c",
10939
11318
  ]
10940
11319
 
10941
11320
  # Build the packaging command inside the container
@@ -10947,11 +11326,11 @@ def py2installer(
10947
11326
  cmd.extend(["--distpath", "/output"])
10948
11327
  if icon_path:
10949
11328
  cmd.extend(["--icon", f"/app/{Path(icon_path).name}"])
10950
- if extra_data:
10951
- for data in extra_data:
11329
+ if include_data:
11330
+ for data in include_data:
10952
11331
  cmd.extend(["--add-data", f"/app/{data}"])
10953
- if hidden_imports:
10954
- for hidden in hidden_imports:
11332
+ if exclude_import:
11333
+ for hidden in exclude_import:
10955
11334
  cmd.extend(["--hidden-import", hidden])
10956
11335
  if additional_args:
10957
11336
  cmd.extend(additional_args)
@@ -10976,25 +11355,32 @@ def py2installer(
10976
11355
  raise
10977
11356
  else:
10978
11357
  # Handle local packaging (native build)
10979
- cmd = ["nuitka"]
10980
- cmd.append("--standalone") # Make sure to use --standalone for independent environments
10981
- if 'min' in flatform.lower():
11358
+ cmd = ["nuitka"] if use_nuitka else ["pyinstaller"]
11359
+ if "min" in flatform.lower() and use_nuitka:
10982
11360
  cmd.append("--mingw64")
10983
- if onefile:
10984
- cmd.append("--onefile")
11361
+ cmd.append("--standalone") if use_nuitka and standalone else None
11362
+ cmd.append("--onefile") if onefile else None
10985
11363
  if not console:
10986
- # cmd.append("--windows-disable-console") # Disabled based on your request
10987
- pass
11364
+ cmd.append("--windows-console-mode=disable")
11365
+ else:
11366
+ cmd.append("--windows-console-mode=attach")
10988
11367
 
10989
- cmd.extend([f"--output-dir={output_dir}"]) # Correct the space issue here
11368
+ cmd.extend(["--show-progress", f"--output-dir={output_dir}"])
10990
11369
  if icon_path:
10991
11370
  icon_path = Path(icon_path)
10992
11371
  if not icon_path.exists():
10993
11372
  raise FileNotFoundError(f"Icon file '{icon_path}' not found.")
10994
- cmd.extend([f"--windows-icon-from-ico={icon_path}"])
11373
+ if sys.platform == "darwin": # macOS platform
11374
+ cmd.extend(
11375
+ ["--macos-create-app-bundle", f"--macos-app-icon={icon_path}"]
11376
+ )
11377
+ elif sys.platform == "win32": # Windows platform
11378
+ cmd.extend([f"--windows-icon-from-ico={icon_path}"])
11379
+ elif sys.platform == "linux": # Linux platform
11380
+ cmd.append("--linux-onefile")
10995
11381
 
10996
- if extra_data:
10997
- for data in extra_data:
11382
+ if include_data:
11383
+ for data in include_data:
10998
11384
  if "*" in data:
10999
11385
  matches = glob.glob(data.split(":")[0])
11000
11386
  for match in matches:
@@ -11009,30 +11395,43 @@ def py2installer(
11009
11395
  cmd.extend(
11010
11396
  ["--include-data-file=" if use_nuitka else "--add-data", data]
11011
11397
  )
11012
-
11013
- if hidden_imports:
11014
- cmd.extend([f"--nofollow-import-to={','.join(hidden_imports)}"])
11015
-
11398
+ if exclude_import is not None:
11399
+ if any(exclude_import):
11400
+ cmd.extend([f"--nofollow-import-to={','.join(exclude_import)}"])
11401
+ if include_import is not None:
11402
+ if any(include_import):#are included in the final build. Some packages may require manual inclusion.
11403
+ cmd.extend([f"--include-package={','.join(include_import)}"])
11016
11404
  if plugins:
11017
11405
  for plugin in plugins:
11018
- cmd.extend([f"--plugin-enable={plugin}"])
11406
+ #Adds support for tkinter, ensuring it works correctly in the standalone build.
11407
+ cmd.extend([f"--enable-plugin={plugin}"])
11019
11408
 
11020
11409
  if additional_args:
11021
11410
  cmd.extend(additional_args)
11022
11411
 
11412
+ # # clean
11413
+ # cmd.extend(
11414
+ # [ "--noinclude-numba-mode=nofollow", #Prevents the inclusion of the numba library and its dependencies, reducing the executable size.
11415
+ # "--noinclude-dask-mode=nofollow",#Excludes the dask library
11416
+ # "--noinclude-IPython-mode=nofollow",#Excludes the IPython library and its dependencies.
11417
+ # "--noinclude-unittest-mode=nofollow",#Excludes the unittest module (used for testing) from the build
11418
+ # "--noinclude-pytest-mode=nofollow",#Excludes the pytest library (used for testing) from the build.
11419
+ # "--noinclude-setuptools-mode=nofollow",#Excludes setuptools, which is not needed for the standalone executable.
11420
+ # "--lto=no",#Disables Link-Time Optimization (LTO), which reduces the compilation time but may slightly increase the size of the output.
11421
+ # ]
11422
+ # )
11423
+
11424
+ if clean_build:
11425
+ cmd.append("--remove-output")#Removes intermediate files created during the build process, keeping only the final executable.
11023
11426
  # Add the script path (final positional argument)
11024
11427
  cmd.append(str(script_path))
11025
-
11026
11428
  # Ensure Windows shell compatibility
11027
11429
  shell_flag = sys.platform.startswith("win")
11028
-
11029
- # Run the command
11030
- if verbose:
11031
- print(f"Running command: {' '.join(cmd)}")
11430
+ print(f"Running command: ⤵ \n{' '.join(cmd)}\n")
11032
11431
  try:
11033
11432
  result = subprocess.run(
11034
11433
  cmd,
11035
- capture_output=not verbose,
11434
+ capture_output=True,
11036
11435
  text=True,
11037
11436
  shell=shell_flag,
11038
11437
  check=True,
@@ -11045,4 +11444,3 @@ def py2installer(
11045
11444
  raise
11046
11445
 
11047
11446
  print("\nPackaging complete. Check the output directory for the executable.")
11048
-