py2ls 0.2.4.32__py3-none-any.whl → 0.2.4.33__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
@@ -1,6 +1,7 @@
1
1
  import numpy as np
2
2
  import pandas as pd
3
- import sys, os
3
+ import sys
4
+ import os
4
5
  from IPython.display import display
5
6
  from typing import List, Optional, Union
6
7
 
@@ -17,12 +18,13 @@ import warnings
17
18
  warnings.simplefilter("ignore", category=pd.errors.SettingWithCopyWarning)
18
19
  warnings.filterwarnings("ignore", category=pd.errors.PerformanceWarning)
19
20
  warnings.filterwarnings("ignore")
20
- import os
21
21
  import shutil
22
22
  import logging
23
23
  from pathlib import Path
24
24
  from datetime import datetime
25
-
25
+ import re
26
+ import stat
27
+ import platform
26
28
 
27
29
  def run_once_within(duration=60, reverse=False): # default 60s
28
30
  import time
@@ -786,13 +788,22 @@ def strcmp(
786
788
  return candidates[best_match_index], best_match_index
787
789
 
788
790
 
789
- def imgcmp(img: list, method="knn", plot_=True, figsize=[12, 6]):
791
+ def imgcmp(img: list,
792
+ method:str ="knn",
793
+ thr:float =0.75,
794
+ detector: str = "sift",
795
+ plot_:bool =True,
796
+ figsize=[12, 6],
797
+ grid_size=10,# only for grid detector
798
+ **kwargs):
790
799
  """
791
800
  Compare two images using SSIM, Feature Matching (SIFT), or KNN Matching.
792
801
 
793
802
  Parameters:
794
- - img (list): List containing two image file paths [img1, img2].
803
+ - img (list): List containing two image file paths [img1, img2] or two numpy arrays.
795
804
  - method (str): Comparison method ('ssim', 'match', or 'knn').
805
+ - detector (str): Feature detector ('sift', 'grid', 'pixel').
806
+ - thr (float): Threshold for filtering matches.
796
807
  - plot_ (bool): Whether to display the results visually.
797
808
  - figsize (list): Size of the figure for plots.
798
809
 
@@ -805,8 +816,13 @@ def imgcmp(img: list, method="knn", plot_=True, figsize=[12, 6]):
805
816
  from skimage.metrics import structural_similarity as ssim
806
817
 
807
818
  # Load images
808
- image1 = cv2.imread(img[0])
809
- image2 = cv2.imread(img[1])
819
+ if isinstance(img, list) and isinstance(img[0],str):
820
+ image1 = cv2.imread(img[0])
821
+ image2 = cv2.imread(img[1])
822
+ bool_cvt=True
823
+ else:
824
+ image1, image2 = np.array(img[0]),np.array(img[1])
825
+ bool_cvt=False
810
826
 
811
827
  if image1 is None or image2 is None:
812
828
  raise ValueError("Could not load one or both images. Check file paths.")
@@ -841,21 +857,53 @@ def imgcmp(img: list, method="knn", plot_=True, figsize=[12, 6]):
841
857
  elif method in ["match", "knn"]:
842
858
  # Convert images to grayscale
843
859
  gray1 = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
844
- gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
845
-
846
- # Initialize SIFT detector
847
- sift = cv2.SIFT_create()
860
+ gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
861
+
862
+ if detector == "sift":
863
+ # SIFT detector
864
+ sift = cv2.SIFT_create()
865
+ keypoints1, descriptors1 = sift.detectAndCompute(gray1, None)
866
+ keypoints2, descriptors2 = sift.detectAndCompute(gray2, None)
867
+
868
+ elif detector == "grid":
869
+ # Grid-based detection
870
+ keypoints1, descriptors1 = [], []
871
+ keypoints2, descriptors2 = [], []
872
+
873
+ for i in range(0, gray1.shape[0], grid_size):
874
+ for j in range(0, gray1.shape[1], grid_size):
875
+ patch1 = gray1[i:i + grid_size, j:j + grid_size]
876
+ patch2 = gray2[i:i + grid_size, j:j + grid_size]
877
+ if patch1.size > 0 and patch2.size > 0:
878
+ keypoints1.append(cv2.KeyPoint(j + grid_size // 2, i + grid_size // 2, grid_size))
879
+ keypoints2.append(cv2.KeyPoint(j + grid_size // 2, i + grid_size // 2, grid_size))
880
+ descriptors1.append(np.mean(patch1))
881
+ descriptors2.append(np.mean(patch2))
882
+
883
+ descriptors1 = np.array(descriptors1).reshape(-1, 1)
884
+ descriptors2 = np.array(descriptors2).reshape(-1, 1)
885
+
886
+ elif detector == "pixel":
887
+ # Pixel-based direct comparison
888
+ descriptors1 = gray1.flatten()
889
+ descriptors2 = gray2.flatten()
890
+ keypoints1 = [cv2.KeyPoint(x, y, 1) for y in range(gray1.shape[0]) for x in range(gray1.shape[1])]
891
+ keypoints2 = [cv2.KeyPoint(x, y, 1) for y in range(gray2.shape[0]) for x in range(gray2.shape[1])]
848
892
 
849
- # Detect and compute features
850
- keypoints1, descriptors1 = sift.detectAndCompute(gray1, None)
851
- keypoints2, descriptors2 = sift.detectAndCompute(gray2, None)
852
-
853
- if len(keypoints1) == 0 or len(keypoints2) == 0:
854
- raise ValueError("No keypoints found in one or both images.")
893
+ else:
894
+ raise ValueError("Invalid detector. Use 'sift', 'grid', or 'pixel'.")
895
+
896
+ # Handle missing descriptors
897
+ if descriptors1 is None or descriptors2 is None:
898
+ raise ValueError("Failed to compute descriptors for one or both images.")
899
+ # Ensure descriptors are in the correct data type
900
+ if descriptors1.dtype != np.float32:
901
+ descriptors1 = descriptors1.astype(np.float32)
902
+ if descriptors2.dtype != np.float32:
903
+ descriptors2 = descriptors2.astype(np.float32)
855
904
 
856
905
  # BFMatcher initialization
857
906
  bf = cv2.BFMatcher()
858
-
859
907
  if method == "match": # Cross-check matching
860
908
  bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
861
909
  matches = bf.match(descriptors1, descriptors2)
@@ -863,13 +911,14 @@ def imgcmp(img: list, method="knn", plot_=True, figsize=[12, 6]):
863
911
 
864
912
  # Filter good matches
865
913
  good_matches = [
866
- m for m in matches if m.distance < 0.75 * matches[-1].distance
914
+ m for m in matches if m.distance < thr * matches[-1].distance
867
915
  ]
868
916
 
869
917
  elif method == "knn": # KNN matching with ratio test
918
+ bf = cv2.BFMatcher()
870
919
  matches = bf.knnMatch(descriptors1, descriptors2, k=2)
871
920
  # Apply Lowe's ratio test
872
- good_matches = [m for m, n in matches if m.distance < 0.75 * n.distance]
921
+ good_matches = [m for m, n in matches if m.distance < thr * n.distance]
873
922
 
874
923
  # Calculate similarity score
875
924
  similarity_score = len(good_matches) / min(len(keypoints1), len(keypoints2))
@@ -887,23 +936,24 @@ def imgcmp(img: list, method="knn", plot_=True, figsize=[12, 6]):
887
936
  dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in good_matches]).reshape(
888
937
  -1, 1, 2
889
938
  )
890
-
891
- # Calculate Homography using RANSAC
892
- homography_matrix, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
893
-
894
939
  # Apply the homography to image2
895
- h, w = image1.shape[:2]
896
- warped_image2 = cv2.warpPerspective(image2, homography_matrix, (w, h))
897
-
898
- # Plot result if needed
899
- if plot_:
900
- fig, ax = plt.subplots(1, 2, figsize=figsize)
901
- ax[0].imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB))
902
- ax[0].set_title("Image 1")
903
- ax[1].imshow(cv2.cvtColor(warped_image2, cv2.COLOR_BGR2RGB))
904
- ax[1].set_title("Warped Image 2")
905
- plt.tight_layout()
906
- plt.show()
940
+ try:
941
+ # Calculate Homography using RANSAC
942
+ homography_matrix, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0)
943
+ h, w = image1.shape[:2]
944
+ warped_image2 = cv2.warpPerspective(image2, homography_matrix, (w, h))
945
+
946
+ # Plot result if needed
947
+ if plot_:
948
+ fig, ax = plt.subplots(1, 2, figsize=figsize)
949
+ ax[0].imshow(cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[0].imshow(image1)
950
+ ax[0].set_title("Image 1")
951
+ ax[1].imshow(cv2.cvtColor(warped_image2, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[1].imshow(warped_image2)
952
+ ax[1].set_title("Warped Image 2")
953
+ plt.tight_layout()
954
+ plt.show()
955
+ except Exception as e:
956
+ print(e)
907
957
 
908
958
  # Plot matches if needed
909
959
  if plot_:
@@ -911,28 +961,41 @@ def imgcmp(img: list, method="knn", plot_=True, figsize=[12, 6]):
911
961
  image1, keypoints1, image2, keypoints2, good_matches, None, flags=2
912
962
  )
913
963
  plt.figure(figsize=figsize)
914
- plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB))
915
- plt.title(
916
- f"Feature Matches ({len(good_matches)} matches, Score: {similarity_score:.4f})"
917
- )
964
+ plt.imshow(cv2.cvtColor(result, cv2.COLOR_BGR2RGB)) if bool_cvt else plt.imshow(result)
965
+ plt.title(f"Feature Matches ({len(good_matches)} matches, Score: {similarity_score:.4f})")
918
966
  plt.axis("off")
919
967
  plt.show()
920
968
  # Identify unmatched keypoints
921
969
  matched_idx1 = [m.queryIdx for m in good_matches]
922
970
  matched_idx2 = [m.trainIdx for m in good_matches]
923
-
971
+ matched_kp1 = [kp for i, kp in enumerate(keypoints1) if i in matched_idx1]
972
+ matched_kp2 = [kp for i, kp in enumerate(keypoints2) if i in matched_idx2]
924
973
  unmatched_kp1 = [kp for i, kp in enumerate(keypoints1) if i not in matched_idx1]
925
974
  unmatched_kp2 = [kp for i, kp in enumerate(keypoints2) if i not in matched_idx2]
926
975
 
927
- # Mark unmatched keypoints on the images
928
- img1_marked = cv2.drawKeypoints(
976
+ # Mark keypoints on the images
977
+ img1_match = cv2.drawKeypoints(
978
+ image1,
979
+ matched_kp1,
980
+ None,
981
+ color=(0, 0, 255),
982
+ flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,
983
+ )
984
+ img2_match = cv2.drawKeypoints(
985
+ image2,
986
+ matched_kp2,
987
+ None,
988
+ color=(0, 0, 255),
989
+ flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,
990
+ )
991
+ img1_unmatch = cv2.drawKeypoints(
929
992
  image1,
930
993
  unmatched_kp1,
931
994
  None,
932
995
  color=(0, 0, 255),
933
996
  flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,
934
997
  )
935
- img2_marked = cv2.drawKeypoints(
998
+ img2_unmatch = cv2.drawKeypoints(
936
999
  image2,
937
1000
  unmatched_kp2,
938
1001
  None,
@@ -940,16 +1003,27 @@ def imgcmp(img: list, method="knn", plot_=True, figsize=[12, 6]):
940
1003
  flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS,
941
1004
  )
942
1005
 
943
- # Display results
944
1006
  if plot_:
945
1007
  fig, ax = plt.subplots(1, 2, figsize=figsize)
946
- ax[0].imshow(cv2.cvtColor(img1_marked, cv2.COLOR_BGR2RGB))
1008
+ ax[0].imshow(cv2.cvtColor(img1_unmatch, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[0].imshow(img1_unmatch)
947
1009
  ax[0].set_title("Unmatched Keypoints (Image 1)")
948
- ax[1].imshow(cv2.cvtColor(img2_marked, cv2.COLOR_BGR2RGB))
1010
+ ax[1].imshow(cv2.cvtColor(img2_unmatch, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[1].imshow(img2_unmatch)
949
1011
  ax[1].set_title("Unmatched Keypoints (Image 2)")
1012
+ ax[0].axis("off")
1013
+ ax[1].axis("off")
1014
+ plt.tight_layout()
1015
+ plt.show()
1016
+ if plot_:
1017
+ fig, ax = plt.subplots(1, 2, figsize=figsize)
1018
+ ax[0].imshow(cv2.cvtColor(img1_match, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[0].imshow(img1_match)
1019
+ ax[0].set_title("Matched Keypoints (Image 1)")
1020
+ ax[1].imshow(cv2.cvtColor(img2_match, cv2.COLOR_BGR2RGB)) if bool_cvt else ax[1].imshow(img2_match)
1021
+ ax[1].set_title("Matched Keypoints (Image 2)")
1022
+ ax[0].axis("off")
1023
+ ax[1].axis("off")
950
1024
  plt.tight_layout()
951
1025
  plt.show()
952
- return good_matches, similarity_score, homography_matrix
1026
+ return good_matches, similarity_score#, homography_matrix
953
1027
 
954
1028
  else:
955
1029
  raise ValueError("Invalid method. Use 'ssim', 'match', or 'knn'.")
@@ -969,9 +1043,7 @@ def cn2pinyin(
969
1043
  Args:
970
1044
  cn_str (str): Chinese string to convert.
971
1045
  sep (str): Separator for the output Pinyin string.
972
- style (Style): "normal","tone", "tone2","tone3",
973
- "finals","finals_tone","finals_tone2","finals_tone3",
974
- "initials","bopomofo","bopomofo_first","cyrillic","pl",
1046
+ fmt (Style): "normal","tone", "tone2","tone3","finals","finals_tone","finals_tone2","finals_tone3","initials","bopomofo","bopomofo_first","cyrillic","pl",
975
1047
  Returns:
976
1048
  cn_str: The Pinyin representation of the Chinese string.
977
1049
  """
@@ -1224,7 +1296,6 @@ def text2audio(
1224
1296
  print(f"Error opening file: {e}")
1225
1297
  print("done")
1226
1298
 
1227
-
1228
1299
  def str2time(time_str, fmt="24"):
1229
1300
  """
1230
1301
  Convert a time string into the specified format.
@@ -3649,8 +3720,8 @@ def get_os(full=False, verbose=False):
3649
3720
  import os
3650
3721
  import subprocess
3651
3722
  from datetime import datetime, timedelta
3652
- from collections import defaultdict
3653
3723
 
3724
+
3654
3725
  def get_os_type():
3655
3726
  os_name = sys.platform
3656
3727
  if "dar" in os_name:
@@ -3663,7 +3734,8 @@ def get_os(full=False, verbose=False):
3663
3734
  else:
3664
3735
  print(f"{os_name}, returned 'None'")
3665
3736
  return None
3666
-
3737
+ if not full:
3738
+ return get_os_type()
3667
3739
  def get_os_info():
3668
3740
  """Get the detailed OS name, version, and other platform-specific details."""
3669
3741
 
@@ -4074,11 +4146,6 @@ def get_os(full=False, verbose=False):
4074
4146
  return res
4075
4147
 
4076
4148
 
4077
- import re
4078
- import stat
4079
- import platform
4080
-
4081
-
4082
4149
  def listdir(
4083
4150
  rootdir,
4084
4151
  kind=None,
@@ -4695,57 +4762,64 @@ def is_image(fpath):
4695
4762
  Returns:
4696
4763
  bool: True if the file is a recognized image, False otherwise.
4697
4764
  """
4698
- import mimetypes
4765
+ from PIL import Image
4766
+ if isinstance(fpath,str):
4767
+ import mimetypes
4768
+
4769
+ # Known image MIME types
4770
+ image_mime_types = {
4771
+ "image/jpeg",
4772
+ "image/png",
4773
+ "image/gif",
4774
+ "image/bmp",
4775
+ "image/webp",
4776
+ "image/tiff",
4777
+ "image/x-icon",
4778
+ "image/svg+xml",
4779
+ "image/heic",
4780
+ "image/heif",
4781
+ }
4699
4782
 
4700
- # Known image MIME types
4701
- image_mime_types = {
4702
- "image/jpeg",
4703
- "image/png",
4704
- "image/gif",
4705
- "image/bmp",
4706
- "image/webp",
4707
- "image/tiff",
4708
- "image/x-icon",
4709
- "image/svg+xml",
4710
- "image/heic",
4711
- "image/heif",
4712
- }
4783
+ # Known image file extensions
4784
+ image_extensions = {
4785
+ ".jpg",
4786
+ ".jpeg",
4787
+ ".png",
4788
+ ".gif",
4789
+ ".bmp",
4790
+ ".webp",
4791
+ ".tif",
4792
+ ".tiff",
4793
+ ".ico",
4794
+ ".svg",
4795
+ ".heic",
4796
+ ".heif",
4797
+ ".fig",
4798
+ ".jpg",
4799
+ }
4713
4800
 
4714
- # Known image file extensions
4715
- image_extensions = {
4716
- ".jpg",
4717
- ".jpeg",
4718
- ".png",
4719
- ".gif",
4720
- ".bmp",
4721
- ".webp",
4722
- ".tif",
4723
- ".tiff",
4724
- ".ico",
4725
- ".svg",
4726
- ".heic",
4727
- ".heif",
4728
- ".fig",
4729
- ".jpg",
4730
- }
4801
+ # Get MIME type using mimetypes
4802
+ mime_type, _ = mimetypes.guess_type(fpath)
4731
4803
 
4732
- # Get MIME type using mimetypes
4733
- mime_type, _ = mimetypes.guess_type(fpath)
4804
+ # Check MIME type
4805
+ if mime_type in image_mime_types:
4806
+ return True
4734
4807
 
4735
- # Check MIME type
4736
- if mime_type in image_mime_types:
4737
- return True
4808
+ # Fallback: Check file extension
4809
+ ext = os.path.splitext(fpath)[
4810
+ -1
4811
+ ].lower() # Get the file extension and ensure lowercase
4812
+ if ext in image_extensions:
4813
+ return True
4738
4814
 
4739
- # Fallback: Check file extension
4740
- ext = os.path.splitext(fpath)[
4741
- -1
4742
- ].lower() # Get the file extension and ensure lowercase
4743
- if ext in image_extensions:
4815
+ return False
4816
+
4817
+ elif isinstance(fpath, Image.Image):
4818
+ # If the input is a PIL Image object
4744
4819
  return True
4745
4820
 
4746
4821
  return False
4747
4822
 
4748
-
4749
4823
  def is_video(fpath):
4750
4824
  """
4751
4825
  Determine if a given file is a video based on MIME type and file extension.
@@ -5055,6 +5129,105 @@ def str2list(str_):
5055
5129
  [l.append(x) for x in str_]
5056
5130
  return l
5057
5131
 
5132
+ def str2words(content, method="combined", custom_dict=None, sym_spell_params=None, use_threading=True):
5133
+ """
5134
+ Ultimate text correction function supporting multiple methods,
5135
+ lists or strings, and domain-specific corrections.
5136
+
5137
+ Parameters:
5138
+ content (str or list): Input text or list of strings to correct.
5139
+ method (str): Correction method ('textblob', 'sym', 'combined').
5140
+ custom_dict (dict): Custom dictionary for domain-specific corrections.
5141
+ sym_spell_params (dict): Parameters for initializing SymSpell.
5142
+
5143
+ Returns:
5144
+ str or list: Corrected text or list of corrected strings.
5145
+ """
5146
+ from textblob import TextBlob
5147
+ from symspellpy import SymSpell, Verbosity
5148
+ from functools import lru_cache
5149
+ import pkg_resources
5150
+ from concurrent.futures import ThreadPoolExecutor
5151
+
5152
+ def initialize_symspell(max_edit_distance=2, prefix_length=7):
5153
+ """Initialize SymSpell for advanced spelling correction."""
5154
+ sym_spell = SymSpell(max_edit_distance, prefix_length)
5155
+ dictionary_path = pkg_resources.resource_filename(
5156
+ "symspellpy",
5157
+ # "frequency_bigramdictionary_en_243_342.txt",
5158
+ "frequency_dictionary_en_82_765.txt",
5159
+ )
5160
+
5161
+ sym_spell.load_dictionary(dictionary_path, term_index=0, count_index=1)
5162
+ return sym_spell
5163
+
5164
+ def segment_words(text, sym_spell):
5165
+ """Segment concatenated words into separate words."""
5166
+ segmented = sym_spell.word_segmentation(text)
5167
+ return segmented.corrected_string
5168
+
5169
+ @lru_cache(maxsize=1000) # Cache results for repeated corrections
5170
+ def advanced_correction(word, sym_spell):
5171
+ """Correct a single word using SymSpell."""
5172
+ suggestions = sym_spell.lookup(word, Verbosity.CLOSEST, max_edit_distance=2)
5173
+ return suggestions[0].term if suggestions else word
5174
+
5175
+ def apply_custom_corrections(word, custom_dict):
5176
+ """Apply domain-specific corrections using a custom dictionary."""
5177
+ return custom_dict.get(word.lower(), word)
5178
+ def preserve_case(original_word, corrected_word):
5179
+ """
5180
+ Preserve the case of the original word in the corrected word.
5181
+ """
5182
+ if original_word.isupper():
5183
+ return corrected_word.upper()
5184
+ elif original_word[0].isupper():
5185
+ return corrected_word.capitalize()
5186
+ else:
5187
+ return corrected_word.lower()
5188
+ def process_string(text, method, sym_spell=None, custom_dict=None):
5189
+ """
5190
+ Process a single string for spelling corrections.
5191
+ Handles TextBlob, SymSpell, and custom corrections.
5192
+ """
5193
+ if method in ("sym", "combined") and sym_spell:
5194
+ text = segment_words(text, sym_spell)
5195
+
5196
+ if method in ("textblob", "combined"):
5197
+ text = str(TextBlob(text).correct())
5198
+
5199
+ corrected_words = []
5200
+ for word in text.split():
5201
+ original_word = word
5202
+ if method in ("sym", "combined") and sym_spell:
5203
+ word = advanced_correction(word, sym_spell)
5204
+
5205
+ # Step 3: Apply custom corrections
5206
+ if custom_dict:
5207
+ word = apply_custom_corrections(word, custom_dict)
5208
+ # Preserve original case
5209
+ word = preserve_case(original_word, word)
5210
+ corrected_words.append(word)
5211
+
5212
+ return " ".join(corrected_words)
5213
+
5214
+ # Initialize SymSpell if needed
5215
+ sym_spell = None
5216
+ if method in ("sym", "combined"):
5217
+ if not sym_spell_params:
5218
+ sym_spell_params = {"max_edit_distance": 2, "prefix_length": 7}
5219
+ sym_spell = initialize_symspell(**sym_spell_params)
5220
+
5221
+ # Process lists or strings
5222
+ if isinstance(content, list):
5223
+ if use_threading:
5224
+ with ThreadPoolExecutor() as executor:
5225
+ corrected_content = list(executor.map(lambda x: process_string(x, method, sym_spell, custom_dict), content))
5226
+ return corrected_content
5227
+ else:
5228
+ return [process_string(item, method, sym_spell, custom_dict) for item in content]
5229
+ else:
5230
+ return process_string(content, method, sym_spell, custom_dict)
5058
5231
 
5059
5232
  def load_img(fpath):
5060
5233
  """
@@ -5078,7 +5251,7 @@ def load_img(fpath):
5078
5251
  raise OSError(f"Unable to open file '{fpath}' or it is not a valid image file.")
5079
5252
 
5080
5253
 
5081
- def apply_filter(img, *args):
5254
+ def apply_filter(img, *args,verbose=True):
5082
5255
  # def apply_filter(img, filter_name, filter_value=None):
5083
5256
  """
5084
5257
  Apply the specified filter to the image.
@@ -5092,7 +5265,7 @@ def apply_filter(img, *args):
5092
5265
  from PIL import ImageFilter
5093
5266
 
5094
5267
  def correct_filter_name(filter_name):
5095
- if "bl" in filter_name.lower() and "box" not in filter_name.lower():
5268
+ if all(["b" in filter_name.lower(),"ur" in filter_name.lower(), "box" not in filter_name.lower()]):
5096
5269
  return "BLUR"
5097
5270
  elif "cont" in filter_name.lower():
5098
5271
  return "Contour"
@@ -5156,10 +5329,11 @@ def apply_filter(img, *args):
5156
5329
 
5157
5330
  for arg in args:
5158
5331
  if isinstance(arg, str):
5159
- filter_name = arg
5160
- filter_name = correct_filter_name(filter_name)
5332
+ filter_name = correct_filter_name(arg)
5161
5333
  else:
5162
5334
  filter_value = arg
5335
+ if verbose:
5336
+ print(f'processing {filter_name}')
5163
5337
  filter_name = filter_name.upper() # Ensure filter name is uppercase
5164
5338
 
5165
5339
  # Supported filters
@@ -5203,7 +5377,7 @@ def apply_filter(img, *args):
5203
5377
  bands = filter_value if filter_value is not None else None
5204
5378
  return img.filter(supported_filters[filter_name](bands))
5205
5379
  else:
5206
- if filter_value is not None:
5380
+ if filter_value is not None and verbose:
5207
5381
  print(
5208
5382
  f"{filter_name} doesn't require a value for {filter_value}, but it remains unaffected"
5209
5383
  )
@@ -5220,6 +5394,8 @@ def detect_angle(image, by="median", template=None):
5220
5394
  import cv2
5221
5395
 
5222
5396
  # Convert to grayscale
5397
+ if np.array(image).shape[-1]>3:
5398
+ image=np.array(image)[:,:,:3]
5223
5399
  gray_image = rgb2gray(image)
5224
5400
 
5225
5401
  # Detect edges using Canny edge detector
@@ -5231,9 +5407,10 @@ def detect_angle(image, by="median", template=None):
5231
5407
  if not lines and any(["me" in by, "pca" in by]):
5232
5408
  print("No lines detected. Adjust the edge detection parameters.")
5233
5409
  return 0
5234
-
5410
+ methods=['mean','median','pca','gradient orientation','template matching','moments','fft']
5411
+ by=strcmp(by, methods)[0]
5235
5412
  # Hough Transform-based angle detection (Median/Mean)
5236
- if "me" in by:
5413
+ if "me" in by.lower():
5237
5414
  angles = []
5238
5415
  for line in lines:
5239
5416
  (x0, y0), (x1, y1) = line
@@ -5256,7 +5433,7 @@ def detect_angle(image, by="median", template=None):
5256
5433
  return rotation_angle
5257
5434
 
5258
5435
  # PCA-based angle detection
5259
- elif "pca" in by:
5436
+ elif "pca" in by.lower():
5260
5437
  y, x = np.nonzero(edges)
5261
5438
  if len(x) == 0:
5262
5439
  return 0
@@ -5266,14 +5443,14 @@ def detect_angle(image, by="median", template=None):
5266
5443
  return angle
5267
5444
 
5268
5445
  # Gradient Orientation-based angle detection
5269
- elif "gra" in by:
5446
+ elif "gra" in by.lower():
5270
5447
  gx, gy = np.gradient(gray_image)
5271
5448
  angles = np.arctan2(gy, gx) * 180 / np.pi
5272
5449
  hist, bin_edges = np.histogram(angles, bins=360, range=(-180, 180))
5273
5450
  return bin_edges[np.argmax(hist)]
5274
5451
 
5275
5452
  # Template Matching-based angle detection
5276
- elif "temp" in by:
5453
+ elif "temp" in by.lower():
5277
5454
  if template is None:
5278
5455
  # Automatically extract a template from the center of the image
5279
5456
  height, width = gray_image.shape
@@ -5296,7 +5473,7 @@ def detect_angle(image, by="median", template=None):
5296
5473
  return best_angle
5297
5474
 
5298
5475
  # Image Moments-based angle detection
5299
- elif "mo" in by:
5476
+ elif "mo" in by.lower():
5300
5477
  moments = measure.moments_central(gray_image)
5301
5478
  angle = (
5302
5479
  0.5
@@ -5307,7 +5484,7 @@ def detect_angle(image, by="median", template=None):
5307
5484
  return angle
5308
5485
 
5309
5486
  # Fourier Transform-based angle detection
5310
- elif "fft" in by:
5487
+ elif "fft" in by.lower():
5311
5488
  f = fft2(gray_image)
5312
5489
  fshift = fftshift(f)
5313
5490
  magnitude_spectrum = np.log(np.abs(fshift) + 1)
@@ -5317,11 +5494,19 @@ def detect_angle(image, by="median", template=None):
5317
5494
  return angle
5318
5495
 
5319
5496
  else:
5320
- print(f"Unknown method {by}")
5497
+ print(f"Unknown method {by}: supported methods: {methods}")
5321
5498
  return 0
5322
5499
 
5323
5500
 
5324
- def imgsets(img, **kwargs):
5501
+ def imgsets(img,
5502
+ auto:bool=True,
5503
+ size=None,
5504
+ figsize=None,
5505
+ dpi:int=200,
5506
+ show_axis:bool=False,
5507
+ plot_:bool=True,
5508
+ verbose:bool=False,
5509
+ **kwargs):
5325
5510
  """
5326
5511
  Apply various enhancements and filters to an image using PIL's ImageEnhance and ImageFilter modules.
5327
5512
 
@@ -5355,6 +5540,9 @@ def imgsets(img, **kwargs):
5355
5540
  Note:
5356
5541
  The "color" and "enhance" enhancements are not implemented in this function.
5357
5542
  """
5543
+
5544
+ import matplotlib.pyplot as plt
5545
+ from PIL import ImageEnhance, ImageOps,Image
5358
5546
  supported_filters = [
5359
5547
  "BLUR",
5360
5548
  "CONTOUR",
@@ -5374,8 +5562,22 @@ def imgsets(img, **kwargs):
5374
5562
  "BOX_BLUR",
5375
5563
  "MEDIAN_FILTER",
5376
5564
  ]
5377
- print('usage: imgsets(dir_img, contrast="auto", rm=True, color=2.2)')
5378
- print("\nlog:\n")
5565
+ str_usage="""
5566
+ imgsets(dir_img, auto=1, color=1.5, plot_=0)
5567
+ imgsets(dir_img, color=2)
5568
+ imgsets(dir_img, pad=(300, 300), bgcolor=(73, 162, 127), plot_=0)
5569
+ imgsets(dir_img, contrast=0, color=1.2, plot_=0)
5570
+ imgsets(get_clip(), flip="tb")# flip top and bottom
5571
+ imgsets(get_clip(), contrast=1, rm=[100, 5, 2]) #'foreground_threshold', 'background_threshold' and 'erode_structure_size'
5572
+ """
5573
+ if run_once_within():
5574
+ print(str_usage)
5575
+
5576
+ def gamma_correction(image, gamma=1.0, v_max=255):
5577
+ # adjust gama value
5578
+ inv_gamma = 1.0 / gamma
5579
+ lut = [int((i / float(v_max)) ** inv_gamma * int(v_max)) for i in range(int(v_max))]
5580
+ return lut #image.point(lut)
5379
5581
 
5380
5582
  def confirm_rembg_models(model_name):
5381
5583
  models_support = [
@@ -5399,37 +5601,52 @@ def imgsets(img, **kwargs):
5399
5601
 
5400
5602
  def auto_enhance(img):
5401
5603
  """
5402
- Automatically enhances the image based on its characteristics.
5604
+ Automatically enhances the image based on its characteristics, including brightness,
5605
+ contrast, color range, sharpness, and gamma correction.
5606
+
5403
5607
  Args:
5404
5608
  img (PIL.Image): The input image.
5609
+
5405
5610
  Returns:
5406
- dict: A dictionary containing the optimal enhancement values.
5611
+ dict: A dictionary containing the optimal enhancement values applied.
5612
+ PIL.Image: The enhanced image.
5407
5613
  """
5614
+ from PIL import Image, ImageEnhance, ImageOps, ImageFilter
5615
+ import numpy as np
5408
5616
  # Determine the bit depth based on the image mode
5409
- if img.mode in ["1", "L", "P", "RGB", "YCbCr", "LAB", "HSV"]:
5410
- # 8-bit depth per channel
5411
- bit_depth = 8
5412
- elif img.mode in ["RGBA", "CMYK"]:
5413
- # 8-bit depth per channel + alpha (RGBA) or additional channels (CMYK)
5617
+ try:
5618
+ if img.mode in ["1", "L", "P", "RGB", "YCbCr", "LAB", "HSV"]:
5619
+ bit_depth = 8
5620
+ elif img.mode in ["RGBA", "CMYK"]:
5621
+ bit_depth = 8
5622
+ elif img.mode in ["I", "F"]:
5623
+ bit_depth = 16
5624
+ else:
5625
+ raise ValueError("Unsupported image mode")
5626
+ except:
5414
5627
  bit_depth = 8
5415
- elif img.mode in ["I", "F"]:
5416
- # 16-bit depth per channel (integer or floating-point)
5417
- bit_depth = 16
5418
- else:
5419
- raise ValueError("Unsupported image mode")
5420
- # Calculate the brightness and contrast for each channel
5628
+
5629
+ # Initialize enhancement factors
5630
+ enhancements = {
5631
+ "brightness": 1.0,
5632
+ "contrast": 0,# autocontrasted
5633
+ "color": 1.35,
5634
+ "sharpness": 1.0,
5635
+ "gamma": 1.0
5636
+ }
5637
+
5638
+ # Calculate brightness and contrast for each channel
5421
5639
  num_channels = len(img.getbands())
5422
5640
  brightness_factors = []
5423
5641
  contrast_factors = []
5424
5642
  for channel in range(num_channels):
5425
5643
  channel_histogram = img.split()[channel].histogram()
5426
- brightness = sum(i * w for i, w in enumerate(channel_histogram)) / sum(
5427
- channel_histogram
5428
- )
5644
+ total_pixels = sum(channel_histogram)
5645
+ brightness = sum(i * w for i, w in enumerate(channel_histogram)) / total_pixels
5429
5646
  channel_min, channel_max = img.split()[channel].getextrema()
5430
5647
  contrast = channel_max - channel_min
5431
5648
  # Adjust calculations based on bit depth
5432
- normalization_factor = 2**bit_depth - 1 # Max value for the given bit depth
5649
+ normalization_factor = 2**bit_depth - 1
5433
5650
  brightness_factor = (
5434
5651
  1.0 + (brightness - normalization_factor / 2) / normalization_factor
5435
5652
  )
@@ -5438,37 +5655,62 @@ def imgsets(img, **kwargs):
5438
5655
  )
5439
5656
  brightness_factors.append(brightness_factor)
5440
5657
  contrast_factors.append(contrast_factor)
5441
- # Calculate the average brightness and contrast factors across channels
5442
- avg_brightness_factor = sum(brightness_factors) / num_channels
5443
- avg_contrast_factor = sum(contrast_factors) / num_channels
5444
- return {"brightness": avg_brightness_factor, "contrast": avg_contrast_factor}
5445
5658
 
5446
- import matplotlib.pyplot as plt
5447
- from PIL import ImageEnhance, ImageOps
5659
+ # Calculate average brightness and contrast factors across channels
5660
+ enhancements["brightness"] = sum(brightness_factors) / num_channels
5661
+ # Adjust brightness and contrast
5662
+ img = ImageEnhance.Brightness(img).enhance(enhancements["brightness"])
5663
+
5664
+ # # Automatic color enhancement (saturation)
5665
+ # if img.mode == "RGB":
5666
+ # color_enhancer = ImageEnhance.Color(img)
5667
+ # color_histogram = np.array(img.histogram()).reshape(3, -1)
5668
+ # avg_saturation = np.mean([np.std(channel) for channel in color_histogram]) / normalization_factor
5669
+ # print(avg_saturation)
5670
+ # enhancements["color"] = min(0, max(0.5, 1.0 + avg_saturation)) # Clamp to a reasonable range
5671
+ # # img = color_enhancer.enhance(enhancements["color"])
5672
+
5673
+ # Adjust sharpness
5674
+ sharpness_enhancer = ImageEnhance.Sharpness(img)
5675
+ # Use edge detection to estimate sharpness need
5676
+ edges = img.filter(ImageFilter.FIND_EDGES).convert("L")
5677
+ avg_edge_intensity = np.mean(np.array(edges))
5678
+ enhancements["sharpness"] = min(2.0, max(0.5, 1.0 + avg_edge_intensity / normalization_factor))
5679
+ # img = sharpness_enhancer.enhance(enhancements["sharpness"])
5680
+
5681
+ # # Apply gamma correction
5682
+ # def gamma_correction(image, gamma):
5683
+ # inv_gamma = 1.0 / gamma
5684
+ # lut = [min(255, max(0, int((i / 255.0) ** inv_gamma * 255))) for i in range(256)]
5685
+ # return image.point(lut)
5686
+
5687
+ # avg_brightness = np.mean(np.array(img.convert("L"))) / 255
5688
+ # enhancements["gamma"] = min(2.0, max(0.5, 1.0 if avg_brightness > 0.5 else 1.2 - avg_brightness))
5689
+ # img = gamma_correction(img, enhancements["gamma"])
5690
+
5691
+ # Return the enhancements and the enhanced image
5692
+ return enhancements
5693
+
5448
5694
 
5449
5695
  # Load image if input is a file path
5450
5696
  if isinstance(img, str):
5451
5697
  img = load_img(img)
5452
- img_update = img.copy()
5453
- # Auto-enhance image if requested
5454
-
5455
- auto = kwargs.get("auto", False)
5456
- show = kwargs.get("show", True)
5457
- show_axis = kwargs.get("show_axis", False)
5458
- size = kwargs.get("size", None)
5459
- figsize = kwargs.get("figsize", None)
5460
- dpi = kwargs.get("dpi", 100)
5698
+ img_update = img.copy()
5461
5699
 
5462
5700
  if auto:
5463
5701
  kwargs = {**auto_enhance(img_update), **kwargs}
5464
-
5702
+ params=["sharp","color","contrast","bright","crop","rotate",'size',"resize",
5703
+ "thumbnail","cover","contain","filter","fit","pad",
5704
+ "rem","rm","back","bg_color","cut",'gamma','flip']
5465
5705
  for k, value in kwargs.items():
5706
+ k = strcmp(k, params)[0] # correct the param name
5466
5707
  if "shar" in k.lower():
5467
5708
  enhancer = ImageEnhance.Sharpness(img_update)
5468
5709
  img_update = enhancer.enhance(value)
5469
5710
  elif all(
5470
5711
  ["col" in k.lower(), "bg" not in k.lower(), "background" not in k.lower()]
5471
5712
  ):
5713
+ # *color
5472
5714
  enhancer = ImageEnhance.Color(img_update)
5473
5715
  img_update = enhancer.enhance(value)
5474
5716
  elif "contr" in k.lower():
@@ -5476,8 +5718,11 @@ def imgsets(img, **kwargs):
5476
5718
  enhancer = ImageEnhance.Contrast(img_update)
5477
5719
  img_update = enhancer.enhance(value)
5478
5720
  else:
5479
- print("autocontrasted")
5480
- img_update = ImageOps.autocontrast(img_update)
5721
+ try:
5722
+ img_update = ImageOps.autocontrast(img_update)
5723
+ print("autocontrasted")
5724
+ except Exception as e:
5725
+ print(f"Failed 'autocontrasted':{e}")
5481
5726
  elif "bri" in k.lower():
5482
5727
  enhancer = ImageEnhance.Brightness(img_update)
5483
5728
  img_update = enhancer.enhance(value)
@@ -5488,7 +5733,13 @@ def imgsets(img, **kwargs):
5488
5733
  value = detect_angle(img_update, by=value)
5489
5734
  print(f"rotated by {value}°")
5490
5735
  img_update = img_update.rotate(value)
5491
-
5736
+ elif 'flip' in k.lower():
5737
+ if 'l' in value and 'r' in value:
5738
+ # left/right
5739
+ img_update = img_update.transpose(Image.FLIP_LEFT_RIGHT)
5740
+ elif any(['u' in value and'd' in value, 't' in value and 'b' in value]):
5741
+ # up/down or top/bottom
5742
+ img_update = img_update.transpose(Image.FLIP_TOP_BOTTOM)
5492
5743
  elif "si" in k.lower():
5493
5744
  if isinstance(value, tuple):
5494
5745
  value = list(value)
@@ -5500,13 +5751,17 @@ def imgsets(img, **kwargs):
5500
5751
  img_update = ImageOps.cover(img_update, size=value)
5501
5752
  elif "contain" in k.lower():
5502
5753
  img_update = ImageOps.contain(img_update, size=value)
5503
- elif "fit" in k.lower():
5754
+ elif "fi" in k.lower() and "t" in k.lower(): # filter
5504
5755
  if isinstance(value, dict):
5756
+ if verbose:
5757
+ print(f"supported filter: {supported_filters}")
5505
5758
  for filter_name, filter_value in value.items():
5506
- img_update = apply_filter(img_update, filter_name, filter_value)
5759
+ img_update = apply_filter(img_update, filter_name, filter_value,verbose=verbose)
5507
5760
  else:
5508
5761
  img_update = ImageOps.fit(img_update, size=value)
5509
5762
  elif "pad" in k.lower():
5763
+ # *ImageOps.pad ensures that the resized image has the exact size specified by the size parameter while maintaining the aspect ratio.
5764
+ # size: A tuple specifying the target size (width, height).
5510
5765
  img_update = ImageOps.pad(img_update, size=value)
5511
5766
  elif "rem" in k.lower() or "rm" in k.lower() or "back" in k.lower():
5512
5767
  from rembg import remove, new_session
@@ -5515,7 +5770,9 @@ def imgsets(img, **kwargs):
5515
5770
  session = new_session("isnet-general-use")
5516
5771
  img_update = remove(img_update, session=session)
5517
5772
  elif value and isinstance(value, (int, float, list)):
5518
- print("https://github.com/danielgatis/rembg/blob/main/USAGE.md")
5773
+ if verbose:
5774
+ print("https://github.com/danielgatis/rembg/blob/main/USAGE.md")
5775
+ 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.")
5519
5776
  if isinstance(value, int):
5520
5777
  value = [value]
5521
5778
  if len(value) < 2:
@@ -5557,8 +5814,11 @@ def imgsets(img, **kwargs):
5557
5814
  if len(value) == 3:
5558
5815
  value += (255,)
5559
5816
  img_update = remove(img_update, bgcolor=value)
5817
+
5818
+ # elif "ga" in k.lower() and "m" in k.lower():
5819
+ # img_update = gamma_correction(img_update, gamma=value)
5560
5820
  # Display the image if requested
5561
- if show:
5821
+ if plot_:
5562
5822
  if figsize is None:
5563
5823
  plt.figure(dpi=dpi)
5564
5824
  else:
@@ -9944,13 +10204,17 @@ def get_loc(input_data, user_agent="0413@mygmail.com)", verbose=True):
9944
10204
  # Case 1: Input is a city name (string)
9945
10205
  if isinstance(input_data, str) and not re.match(r"^\d+(\.\d+)?$", input_data):
9946
10206
  location = geolocator.geocode(input_data)
9947
- if verbose:
9948
- print(
9949
- f"Latitude and Longitude for {input_data}: {location.latitude}, {location.longitude}"
9950
- )
9951
- else:
9952
- print(f"Could not find {input_data}.")
9953
- return location
10207
+ try:
10208
+ if verbose:
10209
+ print(
10210
+ f"Latitude and Longitude for {input_data}: {location.latitude}, {location.longitude}"
10211
+ )
10212
+ else:
10213
+ print(f"Could not find {input_data}.")
10214
+ return location
10215
+ except Exception as e:
10216
+ print(f'Error: {e}')
10217
+ return
9954
10218
 
9955
10219
  # Case 2: Input is latitude and longitude (float or tuple)
9956
10220
  elif isinstance(input_data, (float, tuple)):
@@ -10144,3 +10408,311 @@ def depass(encrypted_code: str, method: str = "AES", key: str = None):
10144
10408
  raise ValueError("SHA256 is a hash function and cannot be decrypted.")
10145
10409
  else:
10146
10410
  raise ValueError("Unsupported decryption method")
10411
+
10412
+ def get_clip(dir_save=None):
10413
+ """
10414
+ Master function to extract content from the clipboard (text, URL, or image).
10415
+
10416
+ Parameters:
10417
+ dir_save (str, optional): If an image is found, save it to this path.
10418
+
10419
+ Returns:
10420
+ dict: A dictionary with extracted content:
10421
+ {
10422
+ "type": "text" | "url" | "image" | "none",
10423
+ "content": <str|Image|None>,
10424
+ "saved_to": <str|None> # Path if an image is saved
10425
+ }
10426
+ """
10427
+ result = {"type": "none", "content": None, "saved_to": None}
10428
+
10429
+ try:
10430
+ import pyperclip
10431
+ from PIL import ImageGrab, Image
10432
+ import validators
10433
+ # 1. Check for text in the clipboard
10434
+ clipboard_content = pyperclip.paste()
10435
+ if clipboard_content:
10436
+ if validators.url(clipboard_content.strip()):
10437
+ result["type"] = "url"
10438
+ result["content"] = clipboard_content.strip()
10439
+
10440
+ else:
10441
+ result["type"] = "text"
10442
+ result["content"] = clipboard_content.strip()
10443
+ return clipboard_content.strip()
10444
+
10445
+ # 2. Check for image in the clipboard
10446
+ image = ImageGrab.grabclipboard()
10447
+ if isinstance(image, Image.Image):
10448
+ result["type"] = "image"
10449
+ result["content"] = image
10450
+ if dir_save:
10451
+ image.save(dir_save)
10452
+ result["saved_to"] = dir_save
10453
+ print(f"Image saved to {dir_save}.")
10454
+ else:
10455
+ print("Image detected in clipboard but not saved.")
10456
+ return image
10457
+ print("No valid text, URL, or image found in clipboard.")
10458
+ return result
10459
+
10460
+ except Exception as e:
10461
+ print(f"An error occurred: {e}")
10462
+ return result
10463
+
10464
+ def keyboard(*args, action='press', n_click=1,interval=0,verbose=False,**kwargs):
10465
+ """
10466
+ Simulates keyboard input using pyautogui.
10467
+
10468
+ Parameters:
10469
+ input_key (str): The key to simulate. Check the list of supported keys with verbose=True.
10470
+ action (str): The action to perform. Options are 'press', 'keyDown', or 'keyUp'.
10471
+ n_click (int): Number of times to press the key (only for 'press' action).
10472
+ interval (float): Time interval between key presses for 'press' action.
10473
+ verbose (bool): Print detailed output, including supported keys and debug info.
10474
+ kwargs: Additional arguments (reserved for future extensions).
10475
+
10476
+ keyboard("command", "d", action="shorcut")
10477
+ """
10478
+ import pyautogui
10479
+ input_key = args
10480
+
10481
+ actions = ['press','keyDown','keyUp', 'hold','release', 'hotkey','shortcut']
10482
+ action = strcmp(action,actions)[0]
10483
+ keyboard_keys_=['\t', '\n', '\r', ' ', '!', '"', '#', '$', '%', '&', "'", '(',
10484
+ ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7',
10485
+ '8', '9', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`',
10486
+ 'a', 'b', 'c', 'd', 'e','f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
10487
+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~',
10488
+ 'accept', 'add', 'alt', 'altleft', 'altright', 'apps', 'backspace',
10489
+ 'browserback', 'browserfavorites', 'browserforward', 'browserhome',
10490
+ 'browserrefresh', 'browsersearch', 'browserstop', 'capslock', 'clear',
10491
+ 'convert', 'ctrl', 'ctrlleft', 'ctrlright', 'decimal', 'del', 'delete',
10492
+ 'divide', 'down', 'end', 'enter', 'esc', 'escape', 'execute', 'f1', 'f10',
10493
+ 'f11', 'f12', 'f13', 'f14', 'f15', 'f16', 'f17', 'f18', 'f19', 'f2', 'f20',
10494
+ 'f21', 'f22', 'f23', 'f24', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9',
10495
+ 'final', 'fn', 'hanguel', 'hangul', 'hanja', 'help', 'home', 'insert', 'junja',
10496
+ 'kana', 'kanji', 'launchapp1', 'launchapp2', 'launchmail',
10497
+ 'launchmediaselect', 'left', 'modechange', 'multiply', 'nexttrack',
10498
+ 'nonconvert', 'num0', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6',
10499
+ 'num7', 'num8', 'num9', 'numlock', 'pagedown', 'pageup', 'pause', 'pgdn',
10500
+ 'pgup', 'playpause', 'prevtrack', 'print', 'printscreen', 'prntscrn',
10501
+ 'prtsc', 'prtscr', 'return', 'right', 'scrolllock', 'select', 'separator',
10502
+ 'shift', 'shiftleft', 'shiftright', 'sleep', 'space', 'stop', 'subtract', 'tab',
10503
+ 'up', 'volumedown', 'volumemute', 'volumeup', 'win', 'winleft', 'winright', 'yen',
10504
+ 'command', 'option', 'optionleft', 'optionright']
10505
+ if verbose:
10506
+ print(f"supported keys: {keyboard_keys_}")
10507
+
10508
+ if action not in ['hotkey','shortcut']:
10509
+ if not isinstance(input_key, list):
10510
+ input_key=list(input_key)
10511
+ input_key = [strcmp(i, keyboard_keys_)[0] for i in input_key ]
10512
+
10513
+ # correct action
10514
+ cmd_keys = ['command', 'option', 'optionleft', 'optionright','win', 'winleft', 'winright','ctrl', 'ctrlleft', 'ctrlright']
10515
+ try:
10516
+ if any([i in cmd_keys for i in input_key]):
10517
+ action='hotkey'
10518
+ except:
10519
+ pass
10520
+
10521
+ print(f"\n{action}: {input_key}")
10522
+ # keyboard
10523
+ if action in ["press"]:
10524
+ # pyautogui.press(input_key, presses=n_click,interval=interval)
10525
+ for _ in range(n_click):
10526
+ for key in input_key:
10527
+ pyautogui.press(key)
10528
+ pyautogui.sleep(interval)
10529
+ elif action in ['keyDown','hold']:
10530
+ # pyautogui.keyDown(input_key)
10531
+ for _ in range(n_click):
10532
+ for key in input_key:
10533
+ pyautogui.keyDown(key)
10534
+ pyautogui.sleep(interval)
10535
+
10536
+ elif action in ['keyUp','release']:
10537
+ # pyautogui.keyUp(input_key)
10538
+ for _ in range(n_click):
10539
+ for key in input_key:
10540
+ pyautogui.keyUp(key)
10541
+ pyautogui.sleep(interval)
10542
+
10543
+ elif action in ['hotkey','shortcut']:
10544
+ pyautogui.hotkey(input_key)
10545
+
10546
+ def mouse(
10547
+ *args, # loc
10548
+ action: str = "move",
10549
+ duration: float = 0.5,
10550
+ loc_type: str = "absolute", # 'absolute', 'relative'
10551
+ region: tuple = None, # (tuple, optional): A region (x, y, width, height) to search for the image.
10552
+ image_path: str = None,
10553
+ wait:float = 0,
10554
+ text: str = None,
10555
+ confidence: float = 0.8,
10556
+ button: str = "left",
10557
+ n_click: int = 1, # number of clicks
10558
+ interval: float = 0.25, # time between clicks
10559
+ scroll_amount: int = -500,
10560
+ fail_safe: bool = True,
10561
+ grayscale: bool = False,
10562
+ **kwargs,
10563
+ ):
10564
+ """
10565
+ Master function to handle pyautogui actions.
10566
+
10567
+ Parameters:
10568
+ action (str): The action to perform ('click', 'double_click', 'type', 'drag', 'scroll', 'move', 'locate', etc.).
10569
+ image_path (str, optional): Path to the image for 'locate' or 'click' actions.
10570
+ text (str, optional): Text to type for 'type' action.
10571
+ confidence (float, optional): Confidence level for image recognition (default 0.8).
10572
+ duration (float, optional): Duration for smooth movements in seconds (default 0.5).
10573
+ region (tuple, optional): A region (x, y, width, height) to search for the image.
10574
+ button (str, optional): Mouse button to use ('left', 'right', 'middle').
10575
+ n_click (int, optional): Number of times to click for 'click' actions.
10576
+ interval (float, optional): Interval between clicks for 'click' actions.
10577
+ offset (tuple, optional): Horizontal offset from the located image. y_offset (int, optional): Vertical offset from the located image.
10578
+ scroll_amount (int, optional): Amount to scroll (positive for up, negative for down).
10579
+ fail_safe (bool, optional): Enable/disable pyautogui's fail-safe feature.
10580
+ grayscale (bool, optional): Search for the image in grayscale mode.
10581
+
10582
+ Returns:
10583
+ tuple or None: Returns coordinates for 'locate' actions, otherwise None.
10584
+ """
10585
+ import pyautogui
10586
+ import time
10587
+
10588
+ pyautogui.FAILSAFE = fail_safe # Enable/disable fail-safe
10589
+ loc_type = "absolute" if "abs" in loc_type else "relative"
10590
+ if len(args) == 1:
10591
+ if isinstance(args[0], str):
10592
+ image_path = args[0]
10593
+ x_offset, y_offset = None, None
10594
+ else:
10595
+ x_offset, y_offset = args
10596
+
10597
+ elif len(args) == 2:
10598
+ x_offset, y_offset = args
10599
+ elif len(args) == 3:
10600
+ x_offset, y_offset, action = args
10601
+ elif len(args) == 4:
10602
+ x_offset, y_offset, action, duration = args
10603
+ else:
10604
+ x_offset, y_offset = None, None
10605
+
10606
+ what_action = [
10607
+ "locate",
10608
+ "click",
10609
+ "double_click",
10610
+ "triple_click",
10611
+ "input",
10612
+ "write",
10613
+ "type",
10614
+ "drag",
10615
+ "move",
10616
+ "scroll",
10617
+ "down",
10618
+ "up",
10619
+ "hold",
10620
+ "press",
10621
+ "release"
10622
+ ]
10623
+ action = strcmp(action, what_action)[0]
10624
+ # get the locations
10625
+ location = None
10626
+ if any([x_offset is None, y_offset is None]):
10627
+ if region is None:
10628
+ w,h=pyautogui.size()
10629
+ region=(0,0,w,h)
10630
+ print(region)
10631
+ try:
10632
+ print(image_path)
10633
+ location = pyautogui.locateOnScreen(
10634
+ image_path, confidence=confidence, region=region, grayscale=grayscale
10635
+ )
10636
+ print(pyautogui.center(location))
10637
+ except Exception as e:
10638
+ location = None
10639
+
10640
+ # try:
10641
+ if location:
10642
+ x, y = pyautogui.center(location)
10643
+ x += x_offset if x_offset else 0
10644
+ y += y_offset if y_offset else 0
10645
+ x_offset, y_offset = x,y
10646
+ print(action)
10647
+ if action in ['locate']:
10648
+ x, y = pyautogui.position()
10649
+ elif action in ["click", "double_click","triple_click"]:
10650
+ # if location:
10651
+ # x, y = pyautogui.center(location)
10652
+ # x += x_offset
10653
+ # y += y_offset
10654
+ # pyautogui.moveTo(x, y, duration=duration)
10655
+ # if action == "click":
10656
+ # pyautogui.click(x=x, y=y, clicks=n_click, interval=interval, button=button)
10657
+ # elif action == "double_click":
10658
+ # pyautogui.doubleClick(x=x, y=y, interval=interval, button=button)
10659
+ # elif action=='triple_click':
10660
+ # pyautogui.tripleClick(x=x,y=y,interval=interval, button=button)
10661
+ # else:
10662
+ if action == "click":
10663
+ pyautogui.moveTo(x_offset, y_offset, duration=duration)
10664
+ time.sleep(wait)
10665
+ pyautogui.click(x=x_offset, y=y_offset, clicks=n_click, interval=interval, button=button)
10666
+ elif action == "double_click":
10667
+ pyautogui.moveTo(x_offset, y_offset, duration=duration)
10668
+ time.sleep(wait)
10669
+ pyautogui.doubleClick(x=x_offset, y=y_offset, interval=interval, button=button)
10670
+ elif action=='triple_click':
10671
+ pyautogui.moveTo(x_offset, y_offset, duration=duration)
10672
+ time.sleep(wait)
10673
+ pyautogui.tripleClick(x=x_offset, y=y_offset, interval=interval, button=button)
10674
+
10675
+ elif action in ["type", "write", "input"]:
10676
+ pyautogui.moveTo(x_offset, y_offset, duration=duration)
10677
+ time.sleep(wait)
10678
+ if text is not None:
10679
+ pyautogui.typewrite(text, interval=interval)
10680
+ else:
10681
+ raise ValueError("Text must be provided for the 'type' action.")
10682
+
10683
+ elif action == "drag":
10684
+ if loc_type == "absolute":
10685
+ pyautogui.dragTo(x_offset, y_offset, duration=duration, button=button)
10686
+ else:
10687
+ pyautogui.dragRel(x_offset, y_offset, duration=duration, button=button)
10688
+
10689
+ elif action in ["move"]:
10690
+ if loc_type == "absolute":
10691
+ pyautogui.moveTo(x_offset, y_offset, duration=duration)
10692
+ else:
10693
+ pyautogui.moveRel(x_offset, y_offset, duration=duration)
10694
+
10695
+ elif action == "scroll":
10696
+ pyautogui.moveTo(x_offset, y_offset, duration=duration)
10697
+ time.sleep(wait)
10698
+ pyautogui.scroll(scroll_amount)
10699
+
10700
+ elif action in ["down",'hold','press']:
10701
+ pyautogui.moveTo(x_offset, y_offset, duration=duration)
10702
+ time.sleep(wait)
10703
+ pyautogui.mouseDown(x_offset, y_offset, button=button, duration=duration)
10704
+
10705
+ elif action in ['up','release']:
10706
+ pyautogui.moveTo(x_offset, y_offset, duration=duration)
10707
+ time.sleep(wait)
10708
+ pyautogui.mouseUp(x_offset, y_offset, button=button, duration=duration)
10709
+
10710
+ else:
10711
+ raise ValueError(f"Unsupported action: {action}")
10712
+
10713
+ # except pyautogui.ImageNotFoundException:
10714
+ # print(
10715
+ # "Image not found. Ensure the image is visible and parameters are correct."
10716
+ # )
10717
+ # except Exception as e:
10718
+ # print(f"An error occurred: {e}")