feat: add new examples from pocketflow-academy

This commit is contained in:
Alan ALves
2025-03-19 10:31:04 -03:00
parent 84720ceebd
commit 557a14f695
129 changed files with 13455 additions and 0 deletions
@@ -0,0 +1,75 @@
# Parallel Image Processor (AsyncParallelBatchFlow Example)
This example demonstrates how to use `AsyncParallelBatchFlow` to process multiple images with multiple filters in parallel.
## How it Works
1. **Image Generation**: Creates sample images (gradient, checkerboard, circles)
2. **Filter Application**: Applies different filters (grayscale, blur, sepia) to each image
3. **Parallel Processing**: Processes all image-filter combinations concurrently
### Flow Structure
```mermaid
graph TD
subgraph AsyncParallelBatchFlow[Image Processing Flow]
subgraph AsyncFlow[Per Image-Filter Flow]
A[Load Image] --> B[Apply Filter]
B --> C[Save Image]
end
end
```
### Key Components
1. **LoadImage (AsyncNode)**
- Loads an image from file
- Uses PIL for image handling
2. **ApplyFilter (AsyncNode)**
- Applies the specified filter
- Supports grayscale, blur, and sepia
3. **SaveImage (AsyncNode)**
- Saves the processed image
- Creates output directory if needed
4. **ImageBatchFlow (AsyncParallelBatchFlow)**
- Manages parallel processing of all image-filter combinations
- Returns parameters for each sub-flow
## Running the Example
1. Install dependencies:
```bash
pip install -r requirements.txt
```
2. Run the example:
```bash
python main.py
```
## Sample Output
The example will:
1. Create 3 sample images: `cat.jpg`, `dog.jpg`, `bird.jpg`
2. Apply 3 filters to each image
3. Save results in `output/` directory (9 total images)
Example output structure:
```
output/
├── cat_grayscale.jpg
├── cat_blur.jpg
├── cat_sepia.jpg
├── dog_grayscale.jpg
...etc
```
## Key Concepts
1. **Parallel Flow Execution**: Each image-filter combination runs as a separate flow in parallel
2. **Parameter Management**: The batch flow generates parameters for each sub-flow
3. **Resource Management**: Uses semaphores to limit concurrent image processing
4. **Error Handling**: Gracefully handles failures in individual flows
@@ -0,0 +1,50 @@
"""Flow definitions for parallel image processing."""
from pocketflow import AsyncFlow, AsyncParallelBatchFlow
from nodes import LoadImage, ApplyFilter, SaveImage, NoOp
def create_base_flow():
"""Create flow for processing a single image with one filter."""
# Create nodes
load = LoadImage()
apply_filter = ApplyFilter()
save = SaveImage()
noop = NoOp()
# Connect nodes
load - "apply_filter" >> apply_filter
apply_filter - "save" >> save
save - "default" >> noop
# Create flow
return AsyncFlow(start=load)
class ImageBatchFlow(AsyncParallelBatchFlow):
"""Flow that processes multiple images with multiple filters in parallel."""
async def prep_async(self, shared):
"""Generate parameters for each image-filter combination."""
# Get list of images and filters
images = shared.get("images", [])
filters = ["grayscale", "blur", "sepia"]
# Create parameter combinations
params = []
for image_path in images:
for filter_type in filters:
params.append({
"image_path": image_path,
"filter": filter_type
})
print(f"\nProcessing {len(images)} images with {len(filters)} filters...")
print(f"Total combinations: {len(params)}")
return params
def create_flow():
"""Create the complete parallel processing flow."""
# Create base flow for single image processing
base_flow = create_base_flow()
# Wrap in parallel batch flow
return ImageBatchFlow(start=base_flow)
Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

@@ -0,0 +1,47 @@
import os
import asyncio
import numpy as np
from PIL import Image
from flow import create_flow
def get_image_paths():
"""Get paths of existing images in the images directory."""
images_dir = "images"
if not os.path.exists(images_dir):
raise ValueError(f"Directory '{images_dir}' not found!")
# List all jpg files in the images directory
image_paths = []
for filename in os.listdir(images_dir):
if filename.lower().endswith(('.jpg', '.jpeg', '.png')):
image_paths.append(os.path.join(images_dir, filename))
if not image_paths:
raise ValueError(f"No images found in '{images_dir}' directory!")
print(f"\nFound {len(image_paths)} images:")
for path in image_paths:
print(f"- {path}")
return image_paths
async def main():
"""Run the parallel image processing example."""
print("\nParallel Image Processor")
print("-" * 30)
# Get existing image paths
image_paths = get_image_paths()
# Create shared store with image paths
shared = {"images": image_paths}
# Create and run flow
flow = create_flow()
await flow.run_async(shared)
print("\nProcessing complete! Check the output/ directory for results.")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,112 @@
"""AsyncNode implementations for image processing."""
import os
import asyncio
from PIL import Image, ImageFilter
import numpy as np
from pocketflow import AsyncNode
class NoOp(AsyncNode):
"""Node that does nothing, used as a terminal node."""
async def prep_async(self, shared):
"""No preparation needed."""
return None
async def exec_async(self, prep_res):
"""No execution needed."""
return None
async def post_async(self, shared, prep_res, exec_res):
"""No post-processing needed."""
return None
class LoadImage(AsyncNode):
"""Node that loads an image from file."""
async def prep_async(self, shared):
"""Get image path from parameters."""
image_path = self.params["image_path"]
print(f"\nLoading image: {image_path}")
return image_path
async def exec_async(self, image_path):
"""Load image using PIL."""
# Simulate I/O delay
await asyncio.sleep(0.1)
return Image.open(image_path)
async def post_async(self, shared, prep_res, exec_res):
"""Store image in shared store."""
shared["image"] = exec_res
return "apply_filter"
class ApplyFilter(AsyncNode):
"""Node that applies a filter to an image."""
async def prep_async(self, shared):
"""Get image and filter type."""
image = shared["image"]
filter_type = self.params["filter"]
print(f"Applying {filter_type} filter...")
return image, filter_type
async def exec_async(self, inputs):
"""Apply the specified filter."""
image, filter_type = inputs
# Simulate processing delay
await asyncio.sleep(0.5)
if filter_type == "grayscale":
return image.convert("L")
elif filter_type == "blur":
return image.filter(ImageFilter.BLUR)
elif filter_type == "sepia":
# Convert to array for sepia calculation
img_array = np.array(image)
sepia_matrix = np.array([
[0.393, 0.769, 0.189],
[0.349, 0.686, 0.168],
[0.272, 0.534, 0.131]
])
sepia_array = img_array.dot(sepia_matrix.T)
sepia_array = np.clip(sepia_array, 0, 255).astype(np.uint8)
return Image.fromarray(sepia_array)
else:
raise ValueError(f"Unknown filter: {filter_type}")
async def post_async(self, shared, prep_res, exec_res):
"""Store filtered image."""
shared["filtered_image"] = exec_res
return "save"
class SaveImage(AsyncNode):
"""Node that saves the processed image."""
async def prep_async(self, shared):
"""Prepare output path."""
image = shared["filtered_image"]
base_name = os.path.splitext(os.path.basename(self.params["image_path"]))[0]
filter_type = self.params["filter"]
output_path = f"output/{base_name}_{filter_type}.jpg"
# Create output directory if needed
os.makedirs("output", exist_ok=True)
return image, output_path
async def exec_async(self, inputs):
"""Save the image."""
image, output_path = inputs
# Simulate I/O delay
await asyncio.sleep(0.1)
image.save(output_path)
return output_path
async def post_async(self, shared, prep_res, exec_res):
"""Print success message."""
print(f"Saved: {exec_res}")
return "default"
Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

@@ -0,0 +1,3 @@
pocketflow
Pillow>=10.0.0 # For image processing
numpy>=1.24.0 # For image array operations