ImagePRO-Python 1.3.1__py3-none-any.whl → 1.4.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.
ImagePRO/__init__.py CHANGED
@@ -12,10 +12,11 @@ Features:
12
12
  - Pre-processing: resize, crop, rotate, blur, sharpen, contrast enhancement
13
13
  - Human analysis: face mesh, pose estimation, hand tracking
14
14
  - Object detection with YOLO models
15
+ - Reusable pipelines for chaining operations
15
16
  - Real-time processing capabilities
16
17
  """
17
18
 
18
- __version__ = "1.3.1"
19
+ __version__ = "1.4.0"
19
20
  __author__ = "Parsa Safaie"
20
21
  __email__ = "parsasafaie.2568@proton.me"
21
22
 
@@ -24,10 +25,12 @@ from . import utils
24
25
  from . import pre_processing
25
26
  from . import human_analysis
26
27
  from . import object_analysis
28
+ from . import pipeline
27
29
 
28
30
  __all__ = [
29
31
  "utils",
30
32
  "pre_processing",
31
33
  "human_analysis",
32
- "object_analysis"
34
+ "object_analysis",
35
+ "pipeline"
33
36
  ]
@@ -0,0 +1,9 @@
1
+ # Pipeline Module
2
+ # Provides a reusable, ordered chain of image processing operations
3
+ #
4
+ # Uses only base dependencies (numpy); all operations handed to a Pipeline
5
+ # may come from any module, including those with optional backends.
6
+
7
+ from .pipeline import Pipeline, Step
8
+
9
+ __all__ = ["Pipeline", "Step"]
@@ -0,0 +1,362 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Any, Callable, Iterator
6
+
7
+ # Add src directory to path for absolute imports
8
+ _file_path = Path(__file__).resolve()
9
+ _src_path = _file_path.parents[2] # Go up to src directory
10
+ if str(_src_path) not in sys.path:
11
+ sys.path.insert(0, str(_src_path))
12
+
13
+ import numpy as np
14
+
15
+ from ImagePRO.utils.image import Image
16
+ from ImagePRO.utils.result import Result
17
+
18
+
19
+ class Step:
20
+ """
21
+ A single operation in a Pipeline, with optional keyword arguments.
22
+
23
+ Wraps any callable that accepts an Image as its first positional argument
24
+ and returns a Result (or a numpy.ndarray, treated as the output image).
25
+ Keyword arguments given to the constructor are forwarded to the wrapped
26
+ function on every call, so a Step fully describes one stage of a pipeline.
27
+
28
+ Example:
29
+ >>> from ImagePRO.pre_processing.blur import apply_gaussian_blur
30
+ >>> step = Step(apply_gaussian_blur, kernel_size=(7, 7))
31
+ >>> result = step(image) # Same as apply_gaussian_blur(image, kernel_size=(7, 7))
32
+
33
+ Attributes:
34
+ function (Callable):
35
+ The wrapped function to execute.
36
+ kwargs (dict[str, Any]):
37
+ Keyword-only arguments forwarded to the function on each call.
38
+ """
39
+
40
+ def __init__(self, function: Callable[..., Result], **kwargs: Any) -> None:
41
+ """
42
+ Create a Step from a function and its keyword arguments.
43
+
44
+ Args:
45
+ function (Callable):
46
+ Function to wrap. Must be callable and accept an Image as its
47
+ first positional argument.
48
+ **kwargs (Any):
49
+ Keyword-only arguments forwarded to the function on every call.
50
+
51
+ Raises:
52
+ TypeError: If function is not callable.
53
+ """
54
+ if not callable(function):
55
+ raise TypeError("'function' must be a callable.")
56
+ self.function = function
57
+ self.kwargs: dict[str, Any] = dict(kwargs)
58
+
59
+ @property
60
+ def name(self) -> str:
61
+ """
62
+ Human-readable name of the wrapped function, used in pipeline metadata.
63
+
64
+ Returns:
65
+ str: The function's __name__ when available, otherwise the name
66
+ of its type (e.g. 'partial' for functools.partial objects).
67
+ """
68
+ return getattr(self.function, "__name__", type(self.function).__name__)
69
+
70
+ def __call__(self, image: Image) -> Result:
71
+ """
72
+ Execute the wrapped function on an image.
73
+
74
+ Args:
75
+ image (Image):
76
+ Input image passed to the wrapped function.
77
+
78
+ Returns:
79
+ Result: Whatever the wrapped function returns.
80
+
81
+ Raises:
82
+ TypeError: If image is not an Image instance (raised by the
83
+ wrapped function's own validation).
84
+ ValueError: If wrapped function arguments are invalid (raised by
85
+ the wrapped function's own validation).
86
+ """
87
+ return self.function(image, **self.kwargs)
88
+
89
+ def __repr__(self) -> str:
90
+ """
91
+ Return a readable representation of the step.
92
+
93
+ Returns:
94
+ str: Representation including the function name and arguments.
95
+ """
96
+ args = ", ".join(f"{key}={value!r}" for key, value in self.kwargs.items())
97
+ return f"Step({self.name}{', ' + args if args else ''})"
98
+
99
+
100
+ class Pipeline:
101
+ """
102
+ A reusable, ordered chain of image processing operations.
103
+
104
+ A Pipeline is defined once from a sequence of functions and can then be
105
+ run on any input as many times as needed. Each operation receives an
106
+ Image and its output image is automatically wrapped and fed to the next
107
+ operation; the final Result is returned with pipeline metadata attached.
108
+
109
+ Operations are the standard ImagePRO functions (which take an Image and
110
+ return a Result) or any custom callable following the same convention.
111
+ A custom function may also return a plain numpy.ndarray, which is treated
112
+ as the output image. Per-operation keyword arguments are configured with
113
+ Step, or fluently via apply().
114
+
115
+ Pipelines are immutable: apply() returns a new Pipeline and leaves the
116
+ original untouched, and run() never mutates the pipeline, so a single
117
+ Pipeline instance is safe to reuse across many inputs.
118
+
119
+ Example:
120
+ >>> from ImagePRO.pre_processing.blur import apply_gaussian_blur
121
+ >>> from ImagePRO.pre_processing.grayscale import convert_to_grayscale
122
+ >>> from ImagePRO.pre_processing.resize import resize_image
123
+ >>>
124
+ >>> pipeline = Pipeline([
125
+ ... Step(apply_gaussian_blur, kernel_size=(7, 7)),
126
+ ... convert_to_grayscale,
127
+ ... Step(resize_image, new_size=(800, 600)),
128
+ ... ])
129
+ >>> result = pipeline.run("photo.jpg") # Any Image, ndarray or path
130
+ >>> result.save_as_img("processed.jpg")
131
+ """
132
+
133
+ def __init__(self, *operations: Any) -> None:
134
+ """
135
+ Create a Pipeline from one or more operations.
136
+
137
+ Operations can be passed as separate arguments or as a single
138
+ list/tuple. Passing another Pipeline as an operation nests it: the
139
+ inner pipeline runs as a single step.
140
+
141
+ Args:
142
+ *operations (Callable | Step | list | tuple):
143
+ Functions to execute in order. Each element must be callable;
144
+ Step instances carry their keyword arguments. A single
145
+ list/tuple of operations is also accepted.
146
+
147
+ Raises:
148
+ TypeError: If any operation is not callable.
149
+ """
150
+ if len(operations) == 1 and isinstance(operations[0], (list, tuple)):
151
+ operations = tuple(operations[0])
152
+
153
+ steps: list[Step] = []
154
+ for operation in operations:
155
+ if not callable(operation):
156
+ raise TypeError(
157
+ "All pipeline operations must be callable; got "
158
+ f"{type(operation).__name__}. Wrap keyword arguments with "
159
+ "Step, e.g. Step(resize_image, new_size=(800, 600))."
160
+ )
161
+ steps.append(operation if isinstance(operation, Step) else Step(operation))
162
+ self._operations = steps
163
+
164
+ @property
165
+ def operations(self) -> tuple[Step, ...]:
166
+ """
167
+ The ordered steps of this pipeline.
168
+
169
+ Returns:
170
+ tuple[Step, ...]: Step objects in execution order.
171
+ """
172
+ return tuple(self._operations)
173
+
174
+ def apply(self, function: Callable[..., Result], **kwargs: Any) -> Pipeline:
175
+ """
176
+ Return a new Pipeline with one more operation appended.
177
+
178
+ The current pipeline is not modified, following ImagePRO's
179
+ non-destructive design principle.
180
+
181
+ Args:
182
+ function (Callable):
183
+ Function to append. Must be callable and accept an Image as
184
+ its first positional argument.
185
+ **kwargs (Any):
186
+ Keyword-only arguments forwarded to the function on every run.
187
+
188
+ Returns:
189
+ Pipeline: New pipeline containing all existing steps plus this one.
190
+
191
+ Raises:
192
+ TypeError: If function is not callable.
193
+
194
+ Example:
195
+ >>> pipeline = Pipeline(apply_gaussian_blur).apply(
196
+ ... resize_image, new_size=(800, 600)
197
+ ... )
198
+ """
199
+ return Pipeline(*self._operations, Step(function, **kwargs))
200
+
201
+ def run(self, image: Image | np.ndarray | str | Path) -> Result:
202
+ """
203
+ Execute all pipeline operations in order on the given input.
204
+
205
+ The input is first normalized to an Image (file paths are loaded and
206
+ NumPy arrays are wrapped, assuming BGR for 3-channel and GRAY for 2D
207
+ arrays). Each operation then receives an Image built from the previous
208
+ operation's output; 2D intermediates are tagged as GRAY and
209
+ multi-channel ones keep the incoming colorspace.
210
+
211
+ The returned Result carries the final operation's image and data. Its
212
+ metadata contains the pipeline information (operation, steps,
213
+ step_meta, source) merged over the final operation's own metadata.
214
+
215
+ Args:
216
+ image (Image | np.ndarray | str | Path):
217
+ Input to process. Can be an Image instance, a NumPy array
218
+ (BGR for 3-channel, GRAY for 2D), or a path to an image file.
219
+
220
+ Returns:
221
+ Result: Result object of the final operation.
222
+ - image (np.ndarray | list[np.ndarray] | None): Final output image(s)
223
+ - data (Any): Structured data of the final operation
224
+ - meta (dict): Final operation metadata plus pipeline info:
225
+ 'operation' ("pipeline"), 'steps' (list of step names),
226
+ 'step_meta' (per-step metadata list), 'source' (input Image)
227
+
228
+ Raises:
229
+ TypeError: If image is not an Image, ndarray, or path.
230
+ TypeError: If an operation returns an unsupported type.
231
+ ValueError: If the pipeline has no operations.
232
+ ValueError: If a non-final operation returns no image, so the
233
+ chain cannot continue.
234
+ TypeError/ValueError: As raised by the operations themselves for
235
+ invalid inputs or arguments.
236
+ """
237
+ # Normalize input to an Image (cheap validation before any step runs)
238
+ if isinstance(image, Image):
239
+ current = image
240
+ elif isinstance(image, np.ndarray):
241
+ colorspace = "GRAY" if image.ndim == 2 else "BGR"
242
+ current = Image.from_array(image, colorspace=colorspace)
243
+ elif isinstance(image, (str, Path)):
244
+ current = Image.from_path(image)
245
+ else:
246
+ raise TypeError(
247
+ "'image' must be an Image instance, a numpy.ndarray, or a "
248
+ "file path (str or pathlib.Path)."
249
+ )
250
+
251
+ if not self._operations:
252
+ raise ValueError(
253
+ "Cannot run an empty pipeline. Add operations at construction "
254
+ "or with apply()."
255
+ )
256
+
257
+ source = current
258
+ step_names: list[str] = []
259
+ step_metas: list[dict[str, Any]] = []
260
+ final_image: Any = None
261
+ final_data: Any = None
262
+ final_meta: dict[str, Any] = {}
263
+
264
+ for index, step in enumerate(self._operations):
265
+ is_last = index == len(self._operations) - 1
266
+ output = step(current)
267
+
268
+ # Normalize the step output (Result is the convention; a plain
269
+ # ndarray from custom functions is accepted as the output image)
270
+ if isinstance(output, Result):
271
+ final_image = output.image
272
+ final_data = output.data
273
+ final_meta = dict(output.meta)
274
+ elif isinstance(output, np.ndarray):
275
+ final_image = output
276
+ final_data = None
277
+ final_meta = {}
278
+ else:
279
+ raise TypeError(
280
+ f"Step {index} ('{step.name}') returned "
281
+ f"{type(output).__name__}; pipeline operations must "
282
+ "return a Result object or a numpy.ndarray."
283
+ )
284
+
285
+ step_names.append(step.name)
286
+ step_metas.append(final_meta)
287
+
288
+ if is_last:
289
+ break
290
+
291
+ # Prepare the input of the next operation
292
+ if final_image is None:
293
+ raise ValueError(
294
+ f"Step {index} ('{step.name}') returned no image, so the "
295
+ "pipeline cannot continue. Only the final step of a "
296
+ "pipeline may return a result without an image."
297
+ )
298
+ if not isinstance(final_image, np.ndarray):
299
+ raise ValueError(
300
+ f"Step {index} ('{step.name}') did not return a single "
301
+ "image array, so the pipeline cannot continue. Only the "
302
+ "final step of a pipeline may return multi-image results."
303
+ )
304
+
305
+ colorspace = "GRAY" if final_image.ndim == 2 else current.colorspace
306
+ current = Image.from_array(final_image, colorspace=colorspace)
307
+
308
+ meta = dict(final_meta)
309
+ meta.update({
310
+ "source": source,
311
+ "operation": "pipeline",
312
+ "steps": step_names,
313
+ "step_meta": step_metas,
314
+ })
315
+ return Result(image=final_image, data=final_data, meta=meta)
316
+
317
+ def __call__(self, image: Image | np.ndarray | str | Path) -> Result:
318
+ """
319
+ Run the pipeline on an input; shorthand for run().
320
+
321
+ Args:
322
+ image (Image | np.ndarray | str | Path):
323
+ Input to process, as accepted by run().
324
+
325
+ Returns:
326
+ Result: Result object of the final operation.
327
+
328
+ Raises:
329
+ TypeError: If image is not an Image, ndarray, or path.
330
+ ValueError: If the pipeline has no operations or a non-final
331
+ operation returns no image.
332
+ """
333
+ return self.run(image)
334
+
335
+ def __len__(self) -> int:
336
+ """
337
+ Return the number of operations in the pipeline.
338
+
339
+ Returns:
340
+ int: Number of steps.
341
+ """
342
+ return len(self._operations)
343
+
344
+ def __iter__(self) -> Iterator[Step]:
345
+ """
346
+ Iterate over the pipeline's steps in execution order.
347
+
348
+ Returns:
349
+ Iterator[Step]: Iterator of Step objects.
350
+ """
351
+ return iter(self._operations)
352
+
353
+ def __repr__(self) -> str:
354
+ """
355
+ Return a readable representation of the pipeline.
356
+
357
+ Returns:
358
+ str: Representation listing the step names in order.
359
+ """
360
+ if not self._operations:
361
+ return "Pipeline(<empty>)"
362
+ return f"Pipeline({' -> '.join(step.name for step in self._operations)})"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ImagePRO-Python
3
- Version: 1.3.1
3
+ Version: 1.4.0
4
4
  Summary: Professional & modular image processing library with OpenCV, MediaPipe, and YOLO integration.
5
5
  Author-email: Parsa Safaie <parsasafaie.2568@proton.me>
6
6
  License: MIT
@@ -76,6 +76,11 @@ Whether you're working on computer vision pipelines, preprocessing images for AI
76
76
  - **YOLO Integration**: Multiple accuracy levels (nano to extra-large)
77
77
  - **Flexible Models**: Pre-trained or custom model support
78
78
 
79
+ ### **Pipelines**
80
+ - **Reusable Operation Chains**: Define a sequence of functions once, then run it on any input (Image, NumPy array, or file path)
81
+ - **Flexible Steps**: Any ImagePRO function or custom callable; per-step keyword arguments via `Step`
82
+ - **Pipeline Metadata**: Every result lists the executed steps and each step's parameters
83
+
79
84
  ## Installation
80
85
 
81
86
  ### From PyPI
@@ -154,6 +159,22 @@ object_detection_result = detect_objects(
154
159
  )
155
160
  print(f"Detected {len(object_detection_result.data)} objects")
156
161
  object_detection_result.save_as_img("detections.jpg")
162
+
163
+ # Chain operations into a reusable pipeline
164
+ from ImagePRO.pipeline import Pipeline, Step
165
+ from ImagePRO.pre_processing.grayscale import convert_to_grayscale
166
+ from ImagePRO.pre_processing.resize import resize_image
167
+
168
+ pipeline = Pipeline([
169
+ Step(apply_gaussian_blur, kernel_size=(7, 7)),
170
+ convert_to_grayscale,
171
+ Step(resize_image, new_size=(800, 600)),
172
+ ])
173
+
174
+ # Run it on any input: an Image, a NumPy array, or a file path
175
+ pipeline_result = pipeline.run("person_and_objects.jpg")
176
+ pipeline_result.save_as_img("processed_output.jpg")
177
+ print(pipeline_result.meta["steps"]) # Executed step names
157
178
  ```
158
179
 
159
180
  > **Note**: These are basic examples. Each module contains many more functions with extensive customization options. Explore the module-specific README files for detailed documentation.
@@ -167,6 +188,7 @@ Each module includes comprehensive documentation with detailed examples:
167
188
  - [Face Analysis](src/ImagePRO/human_analysis/face_analysis/README.md): Face mesh, pose estimation, eye status, comparison
168
189
  - [Body Analysis](src/ImagePRO/human_analysis/body_analysis/README.md): Body pose and hand tracking
169
190
  - **[Object Analysis](src/ImagePRO/object_analysis/README.md)**: YOLO-based object detection
191
+ - **[Pipeline](src/ImagePRO/pipeline/README.md)**: Reusable chains of image processing operations
170
192
  - **[Utils](src/ImagePRO/utils/README.md)**: Shared utilities and I/O handling
171
193
 
172
194
  For detailed project structure and development guidelines, see [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md).
@@ -178,6 +200,7 @@ ImagePRO is built with a modular architecture designed for extensibility and mai
178
200
  - **Clean Separation of Concerns**: Each module handles a specific domain
179
201
  - **Consistent API Patterns**: All functions follow the same input/output conventions
180
202
  - **Shared Utilities**: Common `Image` and `Result` classes for unified I/O
203
+ - **Reusable Pipelines**: Chain any operations into a `Pipeline` and run it on any input
181
204
  - **Lazy Optional Imports**: Heavy AI dependencies (MediaPipe, Ultralytics, InsightFace) are only imported inside the functions that use them, keeping `import ImagePRO` fast and independent of installed extras
182
205
  - **Professional Error Handling**: Comprehensive validation with clear error messages
183
206
  - **Type Safety**: Full type hints throughout the codebase
@@ -1,4 +1,4 @@
1
- ImagePRO/__init__.py,sha256=pLFmqiQTtz76qzrm8r1jES8RHxXWZlnn9vILHBAKVVY,997
1
+ ImagePRO/__init__.py,sha256=3B8cQH4zhV6EEAvVMKYMsJHok_hpGLTgu4QfP33DV08,1081
2
2
  ImagePRO/human_analysis/__init__.py,sha256=uSU0lM1p1HM6TNGv6u0nPHWy9QY505v43lqTkhClddM,190
3
3
  ImagePRO/human_analysis/body_analysis/__init__.py,sha256=uUWG5eOxLQjZoYddmNkRlF66l6vSVyJpUFz1wDddXkk,374
4
4
  ImagePRO/human_analysis/body_analysis/body_pose_estimation.py,sha256=yqEV18ZEZwV__Oqt1Hi72EGmbTNes-mrY068SBT9RbU,6245
@@ -11,6 +11,8 @@ ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py,sha256=rv08Mx_IBB-hj
11
11
  ImagePRO/human_analysis/face_analysis/head_pose_estimation.py,sha256=2_ZVpwavyIzCIH4x3d2bvCPvv-X5gpmSYxtk5jlgCYw,7688
12
12
  ImagePRO/object_analysis/__init__.py,sha256=eVQpYlHO89RYKT0CHjPfJjjif5X_Jx3AtAUpR5m0Yms,292
13
13
  ImagePRO/object_analysis/object_detection.py,sha256=1qPd3Xfo2Vi4JnBs5wD8-k3WR6ZkYNdT9tco2Ze1ICQ,3385
14
+ ImagePRO/pipeline/__init__.py,sha256=-32hbR492qwxutXau7EfnysXbhW8fKKMywkV0ejIMFE,301
15
+ ImagePRO/pipeline/pipeline.py,sha256=-CRWfWXB_lFdjrqg3gdyYFFvq0vZAednrJnod6b4WYA,13802
14
16
  ImagePRO/pre_processing/__init__.py,sha256=pw6-J5sdbkdBG4RlTxOzQKaspGnlPIGo3m_NXH2bVCE,559
15
17
  ImagePRO/pre_processing/blur.py,sha256=3zNTNpvLZPdrtRtBziB6X8ys8M7cIvyPDhojTqR63jo,7950
16
18
  ImagePRO/pre_processing/contrast.py,sha256=4UT78ggW1rh_iaEI8FObDB4kpEmRRFWjdJES60SnHDM,5064
@@ -24,8 +26,8 @@ ImagePRO/pre_processing/sharpen.py,sha256=hOkY7QhzgMp9LXQwt56AP4enn24ntXlmFLe57B
24
26
  ImagePRO/utils/__init__.py,sha256=McVd-o9KtKOpAVYbLi19KxsatPPFFF-Qbt0gdgY3Ln8,170
25
27
  ImagePRO/utils/image.py,sha256=Y64mD97rTiJb8_uNYlyG227VZodeb4U1aGezMkj7MnM,4025
26
28
  ImagePRO/utils/result.py,sha256=ToQaOmUlTtobJkC65BG0YO_6BkBleMBn-0Qydko7cZc,6138
27
- imagepro_python-1.3.1.dist-info/licenses/LICENSE,sha256=sehkHN7dbe5umrINZJyJjIf9VTemZSgTfJlq6iIsbJQ,1069
28
- imagepro_python-1.3.1.dist-info/METADATA,sha256=-eudLbiwZ6DNW8smB4CwEXdeh-i7EtCvRQH_KS8dLqY,9999
29
- imagepro_python-1.3.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
30
- imagepro_python-1.3.1.dist-info/top_level.txt,sha256=UOuPzsoWrVKXgUupOSI96shJ6wE8Fhz-8bm8oMlLC3s,9
31
- imagepro_python-1.3.1.dist-info/RECORD,,
29
+ imagepro_python-1.4.0.dist-info/licenses/LICENSE,sha256=sehkHN7dbe5umrINZJyJjIf9VTemZSgTfJlq6iIsbJQ,1069
30
+ imagepro_python-1.4.0.dist-info/METADATA,sha256=POgVUYD-3fKBCKxkjSgKTZqCt5BWjvtfDBqlrn7Qg9I,11121
31
+ imagepro_python-1.4.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
32
+ imagepro_python-1.4.0.dist-info/top_level.txt,sha256=UOuPzsoWrVKXgUupOSI96shJ6wE8Fhz-8bm8oMlLC3s,9
33
+ imagepro_python-1.4.0.dist-info/RECORD,,