ImagePRO-Python 0.5.2__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,21 +1,15 @@
1
- import sys
2
- import time
1
+ from __future__ import annotations
2
+
3
3
  import random
4
+ import time
4
5
  from pathlib import Path
5
6
 
6
7
  import cv2
7
8
  import mediapipe as mp
8
9
 
9
- # Add parent directory to sys.path for local imports
10
- parent_dir = Path(__file__).resolve().parent.parent
11
- sys.path.append(str(parent_dir))
12
-
13
- from human_analysis.face_analysis.face_detection import detect_faces
14
- from blur import apply_median_blur
15
- from sharpen import apply_laplacian_sharpening
16
- from rotate import rotate_image_custom
17
- from grayscale import convert_to_grayscale
18
- from resize import resize_image
10
+ from ImagePRO.human_analysis.face_analysis.face_detection import detect_faces
11
+ from ImagePRO.pre_processing import blur, grayscale, rotate, sharpen, resize
12
+ from ImagePRO.utils.image import Image
19
13
 
20
14
  # Constants
21
15
  DEFAULT_NUM_IMAGES = 200
@@ -29,6 +23,7 @@ DEFAULT_FACE_ID = "unknown"
29
23
  def capture_bulk_pictures(
30
24
  folder_path: str | Path,
31
25
  face_id: str | int = DEFAULT_FACE_ID,
26
+ *,
32
27
  num_images: int = DEFAULT_NUM_IMAGES,
33
28
  start_index: int = DEFAULT_START_INDEX,
34
29
  min_confidence: float = DEFAULT_MIN_CONFIDENCE,
@@ -38,133 +33,150 @@ def capture_bulk_pictures(
38
33
  apply_sharpen: bool = False,
39
34
  apply_rotate: bool = False,
40
35
  apply_resize: tuple = False,
41
- delay: float = DEFAULT_DELAY,
36
+ delay: float = DEFAULT_DELAY
42
37
  ) -> None:
43
- """
44
- Capture frames from webcam and save cropped face images, with optional preprocessing.
38
+ """Generate a dataset by capturing faces from webcam.
45
39
 
46
- Processing order (if enabled):
40
+ Captures frames and saves preprocessed face images.
41
+ Processing pipeline (if enabled):
47
42
  median blur → laplacian sharpen → grayscale → resize → random rotate
48
43
 
49
- Parameters
50
- ----------
51
- folder_path : str | Path
52
- Base directory where the face-id folder will be created.
53
- face_id : str | int
54
- Subfolder name (e.g., user id). A new folder "<folder_path>/<face_id>" will be created.
55
- num_images : int, default=200
56
- Number of frames to capture/save.
57
- start_index : int, default=0
58
- Starting index for saved filenames (zero-padded).
59
- min_confidence : float, default=0.7
60
- Detection confidence for MediaPipe FaceMesh.
61
- camera_index : int, default=0
62
- OpenCV camera index.
63
- apply_blur : bool, default=False
64
- Apply median blur (filter_size=3) before other steps.
65
- apply_grayscale : bool, default=False
66
- Convert frame to single-channel grayscale.
67
- apply_sharpen : bool, default=False
68
- Apply Laplacian-based sharpening (coefficient=1.0).
69
- apply_rotate : bool, default=False
70
- Apply a random rotation in [-45°, +45°] with random scale {1.0, 1.1, 1.2, 1.3}.
71
- apply_resize : tuple[int, int] | None, default=None
72
- If provided, resize to (width, height) before rotation.
73
- delay : float, default=0.1
74
- Delay between each capture in seconds.
75
-
76
- Raises
77
- ------
78
- ValueError
79
- If arguments are invalid.
80
- FileExistsError
81
- If the destination folder already exists.
82
- RuntimeError
83
- If the webcam cannot be opened.
44
+ Args:
45
+ folder_path: Base directory for dataset.
46
+ face_id: Subject identifier, creates folder "<base_dir>/<face_id>".
47
+ Default: "unknown"
48
+ num_images: Number of images to capture.
49
+ Default: 200
50
+ start_index: Starting number for filenames.
51
+ Default: 0
52
+ min_confidence: Face detection confidence threshold.
53
+ Default: 0.7
54
+ camera_index: OpenCV camera device index.
55
+ Default: 0
56
+ apply_blur: Apply median blur (size=3).
57
+ Default: False
58
+ apply_grayscale: Convert to single-channel.
59
+ Default: False
60
+ apply_sharpen: Apply Laplacian (coef=1.0).
61
+ Default: False
62
+ apply_rotate: Random rotation [-45°,45°].
63
+ Default: False
64
+ apply_resize: Optional (width,height).
65
+ Default: False (no resize)
66
+ delay: Time between captures (seconds).
67
+ Default: 0.1
68
+
69
+ Raises:
70
+ TypeError: If input types are invalid
71
+ ValueError: If numeric values are out of range
72
+ FileExistsError: If output folder exists
73
+ RuntimeError: If camera access fails
84
74
  """
75
+ # Validate numeric parameters
85
76
  if not isinstance(num_images, int) or num_images <= 0:
86
- raise ValueError("'num_images' must be a positive integer.")
77
+ raise ValueError("'num_images' must be a positive integer")
78
+
87
79
  if not isinstance(start_index, int) or start_index < 0:
88
- raise ValueError("'start_index' must be a non-negative integer.")
80
+ raise ValueError("'start_index' must be a non-negative integer")
81
+
89
82
  if not isinstance(min_confidence, (int, float)) or not (0 <= min_confidence <= 1):
90
- raise ValueError("'min_confidence' must be between 0 and 1.")
83
+ raise ValueError("'min_confidence' must be between 0 and 1")
84
+
91
85
  if not isinstance(delay, (int, float)) or delay < 0:
92
- raise ValueError("'delay' must be a non-negative number.")
86
+ raise ValueError("'delay' must be a non-negative number")
93
87
 
88
+ # Setup output directory
94
89
  base_dir = Path(folder_path)
95
90
  face_folder = base_dir / str(face_id)
96
91
 
97
- # Create destination folder (fail if exists to avoid accidental overwrite)
98
92
  try:
99
93
  face_folder.mkdir(parents=True, exist_ok=False)
100
94
  except FileExistsError as e:
101
- raise FileExistsError(f"Destination already exists: {face_folder}") from e
95
+ raise FileExistsError(f"Output folder already exists: {face_folder}") from e
102
96
 
103
- # Open webcam
97
+ # Initialize camera and face detector
104
98
  cap = cv2.VideoCapture(camera_index)
105
99
  if not cap.isOpened():
106
- raise RuntimeError(f"Failed to open webcam (index={camera_index}).")
100
+ raise RuntimeError(f"Cannot access camera (index={camera_index})")
107
101
 
108
- # Initialize FaceMesh for live stream (tracking mode)
109
102
  face_mesh = mp.solutions.face_mesh.FaceMesh(
110
103
  max_num_faces=1,
111
104
  min_detection_confidence=min_confidence,
112
105
  refine_landmarks=True,
113
- static_image_mode=False,
106
+ static_image_mode=False
114
107
  )
115
108
 
116
- saved = 0
109
+ saved_count = 0
117
110
  try:
118
- while saved < num_images:
119
- ok, frame = cap.read()
120
- if not ok:
121
- print("Skipping empty frame.")
111
+ while saved_count < num_images:
112
+ # Capture frame
113
+ success, frame = cap.read()
114
+ if not success:
115
+ print("Failed to capture frame, skipping...")
122
116
  continue
123
117
 
124
- # Optional preprocessing steps
125
- proc = frame
118
+ # Apply preprocessing pipeline
119
+ processed = frame
126
120
 
127
121
  if apply_blur:
128
- # Small kernel to keep facial details while reducing salt-and-pepper noise
129
- proc = apply_median_blur(src_np_image=proc, filter_size=3)
122
+ # Reduce noise while keeping facial features
123
+ processed = blur.apply_average_blur(
124
+ image=Image.from_array(processed),
125
+ filter_size=3
126
+ ).image
130
127
 
131
128
  if apply_sharpen:
132
- # Gentle sharpening; adjust coefficient if needed
133
- proc = apply_laplacian_sharpening(src_np_image=proc, coefficient=1.0)
129
+ # Enhance edges with gentle sharpening
130
+ processed = sharpen.apply_laplacian_sharpening(
131
+ image=Image.from_array(processed),
132
+ coefficient=1.0
133
+ ).image
134
134
 
135
135
  if apply_grayscale:
136
- proc = convert_to_grayscale(src_np_image=proc)
136
+ processed = grayscale.convert_to_grayscale(
137
+ image=Image.from_array(processed)
138
+ ).image
137
139
 
138
140
  if apply_resize is not False:
139
- proc = resize_image(new_size=apply_resize, src_np_image=proc)
141
+ processed = resize.resize_image(
142
+ image=Image.from_array(processed),
143
+ new_size=apply_resize
144
+ ).image
140
145
 
141
146
  if apply_rotate:
147
+ # Apply random rotation with scaling
142
148
  angle = float(random.randint(-45, 45))
143
149
  scale = random.choice([1.0, 1.1, 1.2, 1.3])
144
- proc = rotate_image_custom(src_np_image=proc, angle=angle, scale=scale)
150
+ processed = rotate.rotate_image_custom(
151
+ image=Image.from_array(processed),
152
+ angle=angle,
153
+ scale=scale
154
+ ).image
145
155
 
146
- # Zero-padded filenames for better ordering
147
- filename = f"{start_index + saved:04d}.jpg"
148
- out_path = face_folder / filename
156
+ # Save processed face image
157
+ filename = f"{start_index + saved_count:04d}.jpg"
158
+ output_path = face_folder / filename
149
159
 
150
160
  try:
151
- # Save cropped face image via standardized detect_faces
152
- detect_faces(
161
+ result = detect_faces(
162
+ image=Image.from_array(processed),
153
163
  max_faces=1,
154
164
  min_confidence=min_confidence,
155
- src_np_image=proc,
156
- output_image_path=str(out_path),
157
- face_mesh_obj=face_mesh,
165
+ face_mesh_obj=face_mesh
158
166
  )
159
- saved += 1
167
+
168
+ result.save_as_img(str(output_path))
169
+ saved_count += 1
160
170
 
161
171
  if delay > 0:
162
172
  time.sleep(delay)
163
173
 
164
174
  except ValueError:
165
- # No face detected; skip this frame
175
+ # Skip frames with no detected faces
166
176
  continue
177
+
167
178
  finally:
179
+ # Clean up resources
168
180
  cap.release()
169
181
  cv2.destroyAllWindows()
170
182
 
@@ -1,50 +1,50 @@
1
- import sys
2
- from pathlib import Path
3
- import cv2
4
- import numpy as np
1
+ from __future__ import annotations
5
2
 
6
- # Add parent directory to sys.path for custom module imports
7
- parent_dir = Path(__file__).resolve().parent.parent
8
- sys.path.append(str(parent_dir))
3
+ import cv2
9
4
 
10
- from utils.io_handler import IOHandler
5
+ from ImagePRO.utils.image import Image
6
+ from ImagePRO.utils.result import Result
11
7
 
12
8
 
13
- def convert_to_grayscale(
14
- src_image_path: str | None = None,
15
- src_np_image=None,
16
- output_image_path: str | None = None
17
- ) -> np.ndarray:
9
+ def convert_to_grayscale(image: Image) -> Result:
18
10
  """
19
11
  Convert an image to grayscale.
20
12
 
21
- Parameters
22
- ----------
23
- src_image_path : str | None, optional
24
- Path to input image.
25
- src_np_image : np.ndarray | None, optional
26
- Preloaded BGR image array.
27
- output_image_path : str | None, optional
28
- If provided, save the grayscale image.
29
-
30
- Returns
31
- -------
32
- np.ndarray
33
- Grayscale image.
34
-
35
- Raises
36
- ------
37
- TypeError
38
- If input types are invalid.
39
- FileNotFoundError
40
- If image path does not exist.
41
- IOError
42
- If saving the image fails.
13
+ Converts BGR or RGB image to single-channel grayscale using OpenCV.
14
+ For BGR images (default), uses standard ITU-R BT.601 conversion.
15
+ For RGB images, automatically handles colorspace conversion.
16
+
17
+ Args:
18
+ image (Image):
19
+ Input image to convert. Must be BGR or RGB format.
20
+
21
+ Returns:
22
+ Result: Result object with converted grayscale image.
23
+ - image (np.ndarray): Single-channel grayscale image
24
+ - data (None): No additional data
25
+ - meta (dict): Contains source object and operation info
26
+
27
+ Raises:
28
+ TypeError: If image is not an Image instance
29
+ ValueError: If image colorspace is already GRAY
43
30
  """
44
- np_image = IOHandler.load_image(image_path=src_image_path, np_image=src_np_image)
45
- grayscale = cv2.cvtColor(np_image, cv2.COLOR_BGR2GRAY)
46
-
47
- if output_image_path:
48
- print(IOHandler.save_image(grayscale, output_image_path))
49
-
50
- return grayscale
31
+ if not isinstance(image, Image):
32
+ raise TypeError("'image' must be an Image instance.")
33
+
34
+ if image.colorspace == "GRAY":
35
+ raise ValueError("Image is already in grayscale format.")
36
+
37
+ # Convert based on source colorspace
38
+ if image.colorspace == "RGB":
39
+ grayscale = cv2.cvtColor(image._data, cv2.COLOR_RGB2GRAY)
40
+ else: # BGR is default
41
+ grayscale = cv2.cvtColor(image._data, cv2.COLOR_BGR2GRAY)
42
+
43
+ return Result(
44
+ image=grayscale,
45
+ data=None,
46
+ meta={
47
+ "source": image,
48
+ "operation": "convert_to_grayscale"
49
+ }
50
+ )
@@ -1,62 +1,54 @@
1
- import sys
2
- from pathlib import Path
3
- import cv2
4
- import numpy as np
1
+ from __future__ import annotations
5
2
 
6
- # Add parent directory to sys.path for custom module imports
7
- parent_dir = Path(__file__).resolve().parent.parent
8
- sys.path.append(str(parent_dir))
3
+ import cv2
9
4
 
10
- from utils.io_handler import IOHandler
5
+ from ImagePRO.utils.image import Image
6
+ from ImagePRO.utils.result import Result
11
7
 
12
8
 
13
9
  def resize_image(
14
- new_size: tuple[int, int],
15
- src_image_path: str | None = None,
16
- src_np_image=None,
17
- output_image_path: str | None = None
18
- ) -> np.ndarray:
19
- """
20
- Resize an image to new dimensions.
21
-
22
- Parameters
23
- ----------
24
- new_size : tuple[int, int]
25
- New image size as (width, height).
26
- src_image_path : str | None, optional
27
- Path to input image.
28
- src_np_image : np.ndarray | None, optional
29
- Preloaded BGR image array.
30
- output_image_path : str | None, optional
31
- If provided, save the resized image.
32
-
33
- Returns
34
- -------
35
- np.ndarray
36
- Resized image.
37
-
38
- Raises
39
- ------
40
- ValueError
41
- If new_size is invalid.
42
- TypeError
43
- If input types are invalid.
44
- FileNotFoundError
45
- If image path does not exist.
46
- IOError
47
- If saving the image fails.
10
+ image: Image,
11
+ *,
12
+ new_size: tuple[int, int]
13
+ ) -> Result:
14
+ """Resize an image to specified dimensions.
15
+
16
+ Changes image size while maintaining aspect ratio.
17
+ Uses bilinear interpolation for smooth resizing.
18
+
19
+ Args:
20
+ image: Input image to resize.
21
+ new_size: Target size as (width, height) in pixels.
22
+ Both dimensions must be positive integers.
23
+
24
+ Returns:
25
+ Result object with resized image and metadata:
26
+ - image: Resized image array
27
+ - data: None
28
+ - meta: Operation info and new size used
29
+
30
+ Raises:
31
+ TypeError: If image is not an Image instance
32
+ ValueError: If new_size is not valid (tuple of 2 positive ints)
48
33
  """
34
+ if not isinstance(image, Image):
35
+ raise TypeError("'image' must be an Image instance")
36
+
49
37
  if (
50
38
  not isinstance(new_size, tuple)
51
39
  or len(new_size) != 2
52
40
  or not all(isinstance(dim, int) and dim > 0 for dim in new_size)
53
41
  ):
54
- raise ValueError("'new_size' must be a tuple of two positive integers.")
42
+ raise ValueError("'new_size' must be a tuple of two positive integers")
55
43
 
56
- np_image = IOHandler.load_image(image_path=src_image_path, np_image=src_np_image)
57
- resized = cv2.resize(np_image, new_size, interpolation=cv2.INTER_LINEAR)
58
-
59
- if output_image_path:
60
- print(IOHandler.save_image(resized, output_image_path))
44
+ # Resize using bilinear interpolation
45
+ resized = cv2.resize(image._data, new_size, interpolation=cv2.INTER_LINEAR)
61
46
 
62
- return resized
47
+ return Result(
48
+ image=resized,
49
+ meta={
50
+ "source": image,
51
+ "operation": "resize_image",
52
+ "new_size": new_size
53
+ }
54
+ )