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

Sign up to get free protection for your applications and to get access to all the features.
py2ls/ips.py CHANGED
@@ -26,6 +26,7 @@ import re
26
26
  import stat
27
27
  import platform
28
28
 
29
+
29
30
  # only for backup these scripts
30
31
  def backup(
31
32
  src="/Users/macjianfeng/Dropbox/github/python/py2ls/.venv/lib/python3.12/site-packages/py2ls/",
@@ -33,14 +34,16 @@ def backup(
33
34
  kind="py",
34
35
  overwrite=True,
35
36
  reverse=False,
36
- verbose=False
37
- ):
37
+ verbose=False,
38
+ ):
38
39
  if reverse:
39
- src, tar = tar,src
40
+ src, tar = tar, src
40
41
  print(f"reversed")
41
- f = listdir(src, kind, verbose=verbose)
42
+ f = listdir(src, kind=kind, verbose=verbose)
42
43
  [copy(i, tar, overwrite=overwrite, verbose=verbose) for i in f.path]
43
- print(f"all files are copied from {os.path.basename(src)} to {tar}") if verbose else None
44
+
45
+ print(f"backup '*.{kind}'...\nfrom {src} \nto {tar}")
46
+
44
47
 
45
48
  def run_once_within(duration=60, reverse=False): # default 60s
46
49
  import time
@@ -804,14 +807,16 @@ def strcmp(
804
807
  return candidates[best_match_index], best_match_index
805
808
 
806
809
 
807
- def imgcmp(img: list,
808
- method:str ="knn",
809
- thr:float =0.75,
810
- detector: str = "sift",
811
- plot_:bool =True,
812
- figsize=[12, 6],
813
- grid_size=10,# only for grid detector
814
- **kwargs):
810
+ def imgcmp(
811
+ img: list,
812
+ method: str = "knn",
813
+ thr: float = 0.75,
814
+ detector: str = "sift",
815
+ plot_: bool = True,
816
+ figsize=[12, 6],
817
+ grid_size=10, # only for grid detector
818
+ **kwargs,
819
+ ):
815
820
  """
816
821
  Compare two images using SSIM, Feature Matching (SIFT), or KNN Matching.
817
822
 
@@ -832,13 +837,13 @@ def imgcmp(img: list,
832
837
  from skimage.metrics import structural_similarity as ssim
833
838
 
834
839
  # Load images
835
- if isinstance(img, list) and isinstance(img[0],str):
840
+ if isinstance(img, list) and isinstance(img[0], str):
836
841
  image1 = cv2.imread(img[0])
837
842
  image2 = cv2.imread(img[1])
838
- bool_cvt=True
843
+ bool_cvt = True
839
844
  else:
840
- image1, image2 = np.array(img[0]),np.array(img[1])
841
- bool_cvt=False
845
+ image1, image2 = np.array(img[0]), np.array(img[1])
846
+ bool_cvt = False
842
847
 
843
848
  if image1 is None or image2 is None:
844
849
  raise ValueError("Could not load one or both images. Check file paths.")
@@ -873,7 +878,7 @@ def imgcmp(img: list,
873
878
  elif method in ["match", "knn"]:
874
879
  # Convert images to grayscale
875
880
  gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
876
- gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
881
+ gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
877
882
 
878
883
  if detector == "sift":
879
884
  # SIFT detector
@@ -888,11 +893,19 @@ def imgcmp(img: list,
888
893
 
889
894
  for i in range(0, gray1.shape[0], grid_size):
890
895
  for j in range(0, gray1.shape[1], grid_size):
891
- patch1 = gray1[i:i + grid_size, j:j + grid_size]
892
- patch2 = gray2[i:i + grid_size, j:j + grid_size]
896
+ patch1 = gray1[i : i + grid_size, j : j + grid_size]
897
+ patch2 = gray2[i : i + grid_size, j : j + grid_size]
893
898
  if patch1.size > 0 and patch2.size > 0:
894
- keypoints1.append(cv2.KeyPoint(j + grid_size // 2, i + grid_size // 2, grid_size))
895
- keypoints2.append(cv2.KeyPoint(j + grid_size // 2, i + grid_size // 2, grid_size))
899
+ keypoints1.append(
900
+ cv2.KeyPoint(
901
+ j + grid_size // 2, i + grid_size // 2, grid_size
902
+ )
903
+ )
904
+ keypoints2.append(
905
+ cv2.KeyPoint(
906
+ j + grid_size // 2, i + grid_size // 2, grid_size
907
+ )
908
+ )
896
909
  descriptors1.append(np.mean(patch1))
897
910
  descriptors2.append(np.mean(patch2))
898
911
 
@@ -903,12 +916,20 @@ def imgcmp(img: list,
903
916
  # Pixel-based direct comparison
904
917
  descriptors1 = gray1.flatten()
905
918
  descriptors2 = gray2.flatten()
906
- keypoints1 = [cv2.KeyPoint(x, y, 1) for y in range(gray1.shape[0]) for x in range(gray1.shape[1])]
907
- keypoints2 = [cv2.KeyPoint(x, y, 1) for y in range(gray2.shape[0]) for x in range(gray2.shape[1])]
919
+ keypoints1 = [
920
+ cv2.KeyPoint(x, y, 1)
921
+ for y in range(gray1.shape[0])
922
+ for x in range(gray1.shape[1])
923
+ ]
924
+ keypoints2 = [
925
+ cv2.KeyPoint(x, y, 1)
926
+ for y in range(gray2.shape[0])
927
+ for x in range(gray2.shape[1])
928
+ ]
908
929
 
909
930
  else:
910
931
  raise ValueError("Invalid detector. Use 'sift', 'grid', or 'pixel'.")
911
-
932
+
912
933
  # Handle missing descriptors
913
934
  if descriptors1 is None or descriptors2 is None:
914
935
  raise ValueError("Failed to compute descriptors for one or both images.")
@@ -955,16 +976,26 @@ def imgcmp(img: list,
955
976
  # Apply the homography to image2
956
977
  try:
957
978
  # Calculate Homography using RANSAC
958
- homography_matrix, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
979
+ homography_matrix, mask = cv2.findHomography(
980
+ src_pts, dst_pts, cv2.RANSAC, 5.0
981
+ )
959
982
  h, w = image1.shape[:2]
960
983
  warped_image2 = cv2.warpPerspective(image2, homography_matrix, (w, h))
961
984
 
962
985
  # Plot result if needed
963
986
  if plot_:
964
987
  fig, ax = plt.subplots(1, 2, figsize=figsize)
965
- ax[0].imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[0].imshow(image1)
988
+ (
989
+ ax[0].imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB))
990
+ if bool_cvt
991
+ else ax[0].imshow(image1)
992
+ )
966
993
  ax[0].set_title("Image 1")
967
- ax[1].imshow(cv2.cvtColor(warped_image2, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[1].imshow(warped_image2)
994
+ (
995
+ ax[1].imshow(cv2.cvtColor(warped_image2, cv2.COLOR_BGR2RGB))
996
+ if bool_cvt
997
+ else ax[1].imshow(warped_image2)
998
+ )
968
999
  ax[1].set_title("Warped Image 2")
969
1000
  plt.tight_layout()
970
1001
  plt.show()
@@ -977,8 +1008,14 @@ def imgcmp(img: list,
977
1008
  image1, keypoints1, image2, keypoints2, good_matches, None, flags=2
978
1009
  )
979
1010
  plt.figure(figsize=figsize)
980
- plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) if bool_cvt else plt.imshow(result)
981
- plt.title(f"Feature Matches ({len(good_matches)} matches, Score: {similarity_score:.4f})")
1011
+ (
1012
+ plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
1013
+ if bool_cvt
1014
+ else plt.imshow(result)
1015
+ )
1016
+ plt.title(
1017
+ f"Feature Matches ({len(good_matches)} matches, Score: {similarity_score:.4f})"
1018
+ )
982
1019
  plt.axis("off")
983
1020
  plt.show()
984
1021
  # Identify unmatched keypoints
@@ -1021,9 +1058,17 @@ def imgcmp(img: list,
1021
1058
 
1022
1059
  if plot_:
1023
1060
  fig, ax = plt.subplots(1, 2, figsize=figsize)
1024
- ax[0].imshow(cv2.cvtColor(img1_unmatch, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[0].imshow(img1_unmatch)
1061
+ (
1062
+ ax[0].imshow(cv2.cvtColor(img1_unmatch, cv2.COLOR_BGR2RGB))
1063
+ if bool_cvt
1064
+ else ax[0].imshow(img1_unmatch)
1065
+ )
1025
1066
  ax[0].set_title("Unmatched Keypoints (Image 1)")
1026
- ax[1].imshow(cv2.cvtColor(img2_unmatch, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[1].imshow(img2_unmatch)
1067
+ (
1068
+ ax[1].imshow(cv2.cvtColor(img2_unmatch, cv2.COLOR_BGR2RGB))
1069
+ if bool_cvt
1070
+ else ax[1].imshow(img2_unmatch)
1071
+ )
1027
1072
  ax[1].set_title("Unmatched Keypoints (Image 2)")
1028
1073
  ax[0].axis("off")
1029
1074
  ax[1].axis("off")
@@ -1031,15 +1076,23 @@ def imgcmp(img: list,
1031
1076
  plt.show()
1032
1077
  if plot_:
1033
1078
  fig, ax = plt.subplots(1, 2, figsize=figsize)
1034
- ax[0].imshow(cv2.cvtColor(img1_match, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[0].imshow(img1_match)
1079
+ (
1080
+ ax[0].imshow(cv2.cvtColor(img1_match, cv2.COLOR_BGR2RGB))
1081
+ if bool_cvt
1082
+ else ax[0].imshow(img1_match)
1083
+ )
1035
1084
  ax[0].set_title("Matched Keypoints (Image 1)")
1036
- ax[1].imshow(cv2.cvtColor(img2_match, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[1].imshow(img2_match)
1085
+ (
1086
+ ax[1].imshow(cv2.cvtColor(img2_match, cv2.COLOR_BGR2RGB))
1087
+ if bool_cvt
1088
+ else ax[1].imshow(img2_match)
1089
+ )
1037
1090
  ax[1].set_title("Matched Keypoints (Image 2)")
1038
1091
  ax[0].axis("off")
1039
1092
  ax[1].axis("off")
1040
1093
  plt.tight_layout()
1041
1094
  plt.show()
1042
- return good_matches, similarity_score#, homography_matrix
1095
+ return good_matches, similarity_score # , homography_matrix
1043
1096
 
1044
1097
  else:
1045
1098
  raise ValueError("Invalid method. Use 'ssim', 'match', or 'knn'.")
@@ -1312,6 +1365,7 @@ def text2audio(
1312
1365
  print(f"Error opening file: {e}")
1313
1366
  print("done")
1314
1367
 
1368
+
1315
1369
  def str2time(time_str, fmt="24"):
1316
1370
  """
1317
1371
  Convert a time string into the specified format.
@@ -2699,7 +2753,7 @@ def fload(fpath, kind=None, **kwargs):
2699
2753
  def load_excel(fpath, **kwargs):
2700
2754
  engine = kwargs.get("engine", "openpyxl")
2701
2755
  verbose = kwargs.pop("verbose", False)
2702
- password=kwargs.pop("password",None)
2756
+ password = kwargs.pop("password", None)
2703
2757
 
2704
2758
  if not password:
2705
2759
  if run_once_within(reverse=True):
@@ -2712,12 +2766,12 @@ def fload(fpath, kind=None, **kwargs):
2712
2766
  except:
2713
2767
  pass
2714
2768
  return df
2715
- #* needs a password?
2716
- import msoffcrypto # pip install msoffcrypto-tool
2769
+ # * needs a password?
2770
+ import msoffcrypto # pip install msoffcrypto-tool
2717
2771
  from io import BytesIO
2718
2772
 
2719
2773
  # Open the encrypted Excel file
2720
- with open(fpath, 'rb') as f:
2774
+ with open(fpath, "rb") as f:
2721
2775
  try:
2722
2776
  office_file = msoffcrypto.OfficeFile(f)
2723
2777
  office_file.load_key(password=password) # Provide the password
@@ -3317,16 +3371,16 @@ def fsave(
3317
3371
  df = pd.DataFrame(data)
3318
3372
  df.to_csv(fpath, **kwargs_valid)
3319
3373
 
3320
-
3321
3374
  def save_xlsx(fpath, data, password=None, **kwargs):
3322
3375
  import msoffcrypto
3323
3376
  from io import BytesIO
3377
+
3324
3378
  verbose = kwargs.pop("verbose", False)
3325
3379
  sheet_name = kwargs.pop("sheet_name", "Sheet1")
3326
-
3380
+
3327
3381
  if run_once_within(reverse=True):
3328
3382
  use_pd("to_excel", verbose=verbose)
3329
-
3383
+
3330
3384
  if any(kwargs):
3331
3385
  format_excel(df=data, filename=fpath, **kwargs)
3332
3386
  else:
@@ -3351,10 +3405,16 @@ def fsave(
3351
3405
  kwargs.pop(key, None)
3352
3406
 
3353
3407
  df = pd.DataFrame(data)
3354
-
3408
+
3355
3409
  # Write to Excel without password first
3356
3410
  temp_file = BytesIO()
3357
- df.to_excel(temp_file, sheet_name=sheet_name, index=False, engine="xlsxwriter", **kwargs)
3411
+ df.to_excel(
3412
+ temp_file,
3413
+ sheet_name=sheet_name,
3414
+ index=False,
3415
+ engine="xlsxwriter",
3416
+ **kwargs,
3417
+ )
3358
3418
 
3359
3419
  # If a password is provided, encrypt the file
3360
3420
  if password:
@@ -3363,19 +3423,22 @@ def fsave(
3363
3423
  office_file.load_key(password=password) # Provide the password
3364
3424
 
3365
3425
  # Encrypt and save the file
3366
- with open(fpath, 'wb') as encrypted_file:
3426
+ with open(fpath, "wb") as encrypted_file:
3367
3427
  office_file.encrypt(encrypted_file)
3368
3428
  else:
3369
3429
  # Save the file without encryption if no password is provided
3370
3430
  try:
3371
3431
  # Use ExcelWriter with append mode if the file exists
3372
- with pd.ExcelWriter(fpath, engine="openpyxl", mode="a", if_sheet_exists="new") as writer:
3373
- df.to_excel(writer, sheet_name=sheet_name, index=False, **kwargs)
3432
+ with pd.ExcelWriter(
3433
+ fpath, engine="openpyxl", mode="a", if_sheet_exists="new"
3434
+ ) as writer:
3435
+ df.to_excel(
3436
+ writer, sheet_name=sheet_name, index=False, **kwargs
3437
+ )
3374
3438
  except FileNotFoundError:
3375
3439
  # If file doesn't exist, create a new one
3376
3440
  df.to_excel(fpath, sheet_name=sheet_name, index=False, **kwargs)
3377
3441
 
3378
-
3379
3442
  def save_ipynb(fpath, data, **kwargs):
3380
3443
  # Split the content by code fences to distinguish between code and markdown
3381
3444
  import nbformat
@@ -3784,7 +3847,6 @@ def get_os(full=False, verbose=False):
3784
3847
  import subprocess
3785
3848
  from datetime import datetime, timedelta
3786
3849
 
3787
-
3788
3850
  def get_os_type():
3789
3851
  os_name = sys.platform
3790
3852
  if "dar" in os_name:
@@ -3797,8 +3859,10 @@ def get_os(full=False, verbose=False):
3797
3859
  else:
3798
3860
  print(f"{os_name}, returned 'None'")
3799
3861
  return None
3862
+
3800
3863
  if not full:
3801
3864
  return get_os_type()
3865
+
3802
3866
  def get_os_info():
3803
3867
  """Get the detailed OS name, version, and other platform-specific details."""
3804
3868
 
@@ -4451,16 +4515,30 @@ def listdir(
4451
4515
  return Box(f.to_dict(orient="series"))
4452
4516
 
4453
4517
 
4454
- def listfunc(lib_name, opt="call"):
4455
- if opt == "call":
4456
- funcs = [func for func in dir(lib_name) if callable(getattr(lib_name, func))]
4457
- else:
4458
- funcs = dir(lib_name)
4459
- return funcs
4518
+ def listpkg(where="env", verbose=False):
4519
+ """list all pacakages"""
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
-