From 6fd01d4e0d4bb74f89facb7bf395cd340f07e407 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 10 Jul 2026 01:52:55 +0530 Subject: [PATCH 1/8] feat(creative): add offline background removal skill --- CHANGELOG.md | 4 + docs/skills/README.md | 7 ++ docs/skills/bg_remover.md | 45 +++++++++ skills/creative/__init__.py | 0 skills/creative/bg_remover/__init__.py | 0 skills/creative/bg_remover/card.json | 18 ++++ skills/creative/bg_remover/instructions.md | 19 ++++ skills/creative/bg_remover/manifest.yaml | 78 ++++++++++++++ skills/creative/bg_remover/skill.py | 77 ++++++++++++++ skills/creative/bg_remover/test_skill.py | 112 +++++++++++++++++++++ 10 files changed, 360 insertions(+) create mode 100644 docs/skills/bg_remover.md create mode 100644 skills/creative/__init__.py create mode 100644 skills/creative/bg_remover/__init__.py create mode 100644 skills/creative/bg_remover/card.json create mode 100644 skills/creative/bg_remover/instructions.md create mode 100644 skills/creative/bg_remover/manifest.yaml create mode 100644 skills/creative/bg_remover/skill.py create mode 100644 skills/creative/bg_remover/test_skill.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eef7b8..31b5786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Contributors add user-facing entries under `[Unreleased]` in the same PR. Mainta ### Added +- Added `creative/bg_remover`, an offline background removal skill powered by rembg. + +### Added + - **Tests**: `tests/test_registry_docs.py` — CI doc-drift guards that verify skill catalog, examples index, and agent-loops reference matrix stay in sync with the registry (#183). - **Documentation**: Cross-linked `tests/test_registry_docs.py` in `ai_native_workflow.md` and `CONTRIBUTING.md` so contributors know doc-drift guards run in CI; added explicit PR checklist reminder in `CONTRIBUTING.md` (#193). diff --git a/docs/skills/README.md b/docs/skills/README.md index 8246f24..9866ec7 100644 --- a/docs/skills/README.md +++ b/docs/skills/README.md @@ -7,6 +7,13 @@ Browse by category below, or run `skillware list` after `pip install skillware` ## Office Skills for document processing, email automation, and productivity. +## Creative +Skills for image processing, media editing, and creative utilities. + +| Skill | ID | Issuer | Description | +| :--- | :--- | :--- | :--- | +| **[Background Remover](bg_remover.md)** | `creative/bg_remover` | [@AyushSrivastava1818](https://github.com/AyushSrivastava1818) | Removes image backgrounds locally using rembg and returns transparent PNGs. | + | Skill | ID | Issuer | Description | | :--- | :--- | :--- | :--- | | **[PDF Form Filler](pdf_form_filler.md)** | `office/pdf_form_filler` | [@rosspeili](https://github.com/rosspeili) ([@ARPAHLS](https://github.com/ARPAHLS)) | Fills AcroForm-based PDFs by mapping user instructions to detected form fields using LLM-based semantic understanding. | diff --git a/docs/skills/bg_remover.md b/docs/skills/bg_remover.md new file mode 100644 index 0000000..c2bf7c1 --- /dev/null +++ b/docs/skills/bg_remover.md @@ -0,0 +1,45 @@ +# Background Remover + +## Overview + +`creative/bg_remover` removes the background from still images locally using rembg and returns a transparent PNG. + +## Inputs + +- image (Base64) +- input_path +- output_path +- model +- alpha_matting + +## Outputs + +- success +- image_base64 +- mime_type +- width +- height +- model_used + +## Example + +```json +{ + "image": "" +} +``` + +Returns + +```json +{ + "success": true, + "mime_type": "image/png" +} +``` + +## Notes + +- Runs completely offline. +- First execution may download the ONNX model. +- Unit tests mock rembg and do not download models. \ No newline at end of file diff --git a/skills/creative/__init__.py b/skills/creative/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/skills/creative/bg_remover/__init__.py b/skills/creative/bg_remover/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/skills/creative/bg_remover/card.json b/skills/creative/bg_remover/card.json new file mode 100644 index 0000000..ba6d2ec --- /dev/null +++ b/skills/creative/bg_remover/card.json @@ -0,0 +1,18 @@ +{ + "name": "creative/bg_remover", + "description": "Remove image backgrounds locally using rembg.", + "category": "creative", + "tags": [ + "image", + "background", + "rembg", + "offline", + "png" + ], + "issuer": { + "name": "Ayush Srivastava", + "email": "ayush.sri0705@gmail.com", + "github": "AyushSrivastava1818", + "org": "" + } +} \ No newline at end of file diff --git a/skills/creative/bg_remover/instructions.md b/skills/creative/bg_remover/instructions.md new file mode 100644 index 0000000..f141622 --- /dev/null +++ b/skills/creative/bg_remover/instructions.md @@ -0,0 +1,19 @@ +# Background Remover + +Use this skill whenever a user asks to: + +- remove the background +- make an image transparent +- isolate a product +- create a PNG with transparency +- cut out a person or object + +Do NOT use this skill for: + +- videos +- batch image processing +- cloud-only image editing + +Input should be a Base64 image whenever possible. + +Return a transparent PNG. \ No newline at end of file diff --git a/skills/creative/bg_remover/manifest.yaml b/skills/creative/bg_remover/manifest.yaml new file mode 100644 index 0000000..d4dbf13 --- /dev/null +++ b/skills/creative/bg_remover/manifest.yaml @@ -0,0 +1,78 @@ +name: "creative/bg_remover" +version: "0.1.0" +description: "Remove image backgrounds locally using rembg." +short_description: "Offline background removal using rembg with transparent PNG output." + +issuer: + name: Ayush Srivastava + email: "ayush.sri0705@gmail.com" + github: AyushSrivastava1818 + org: "" + +parameters: + type: object + properties: + image: + type: string + description: "Base64 encoded input image." + + input_path: + type: string + description: "Optional local input image." + + output_path: + type: string + description: "Optional output PNG path." + + model: + type: string + enum: + - isnet-general-use + - u2net + - u2net_human_seg + - silueta + default: isnet-general-use + + alpha_matting: + type: boolean + default: false + +outputs: + success: + type: boolean + + image_base64: + type: string + + mime_type: + type: string + + output_path: + type: string + + width: + type: integer + + height: + type: integer + + model_used: + type: string + + error: + type: string + + error_code: + type: string + +requirements: + - rembg>=2.0.0 + - pillow + - onnxruntime + +constitution: | + 1. IMAGE_ONLY: Process still images only. + 2. LOCAL_FIRST: Never use cloud APIs. + 3. PRIVACY: Never store image bytes. + 4. DETERMINISTIC: Same input produces same output. + 5. FAIL_CLOSED: Return structured errors on invalid input. \ No newline at end of file diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py new file mode 100644 index 0000000..1d52cf7 --- /dev/null +++ b/skills/creative/bg_remover/skill.py @@ -0,0 +1,77 @@ +import base64 +import io +from pathlib import Path +from typing import Any, Dict + +from PIL import Image +from rembg import remove + +from skillware.core.base_skill import BaseSkill + +class BackgroundRemover(BaseSkill): + """Remove image backgrounds locally using rembg.""" + + @property + def manifest(self) -> Dict[str, Any]: + return { + "name": "creative/bg_remover", + "version": "0.1.0", + "description": ( + "Remove image backgrounds locally using rembg " + "and return a transparent PNG." + ), + } + + def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: + """Execute background removal.""" + + try: + image_b64 = params.get("image") + input_path = params.get("input_path") + output_path = params.get("output_path") + + if not image_b64 and not input_path: + return { + "success": False, + "error": "Either image or input_path must be provided.", + "error_code": "INVALID_INPUT", + } + + # Read image bytes + if image_b64: + image_bytes = base64.b64decode(image_b64) + else: + image_bytes = Path(input_path).read_bytes() + + # Remove background + output_bytes = remove(image_bytes) + + # Read PNG + image = Image.open(io.BytesIO(output_bytes)) + + # Convert back to base64 + buffer = io.BytesIO() + image.save(buffer, format="PNG") + + encoded = base64.b64encode(buffer.getvalue()).decode("utf-8") + + # Optional save + if output_path: + Path(output_path).write_bytes(buffer.getvalue()) + + return { + "success": True, + "image_base64": encoded, + "mime_type": "image/png", + "output_path": output_path, + "width": image.width, + "height": image.height, + "model_used": params.get("model", "isnet-general-use"), + } + + except Exception as e: + return { + "success": False, + "error": str(e), + "error_code": "PROCESSING_FAILED", + } \ No newline at end of file diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py new file mode 100644 index 0000000..4267d1c --- /dev/null +++ b/skills/creative/bg_remover/test_skill.py @@ -0,0 +1,112 @@ +import base64 +import io +import os + +import pytest +import yaml +from PIL import Image + +from .skill import BackgroundRemover +from . import skill as bg_skill + +print("Loaded module:", bg_skill.__name__) + +@pytest.fixture(autouse=True) +def mock_remove(monkeypatch): + """Mock rembg.remove so tests stay offline.""" + + def fake_remove(image_bytes): + img = Image.new("RGBA", (100, 100), (255, 0, 0, 0)) + buffer = io.BytesIO() + img.save(buffer, format="PNG") + return buffer.getvalue() + + monkeypatch.setattr( + "skills.creative.bg_remover.skill.remove", + fake_remove, + ) + +@pytest.fixture +def skill(): + return BackgroundRemover() + +@pytest.fixture +def manifest(): + manifest_path = os.path.join( + os.path.dirname(__file__), + "manifest.yaml", + ) + + with open(manifest_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) + +def test_manifest(skill, manifest): + assert skill.manifest["name"] == manifest["name"] + assert skill.manifest["version"] == manifest["version"] + + +def test_missing_input(skill): + result = skill.execute({}) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" + +def create_image(): + + image = Image.new("RGB", (64, 64), "white") + + buffer = io.BytesIO() + + image.save(buffer, format="PNG") + + return base64.b64encode( + buffer.getvalue() + ).decode() + +def test_base64_image(skill): + + img = create_image() + + result = skill.execute( + { + "image": img + } + ) + + assert result["success"] is True + assert result["mime_type"] == "image/png" + assert result["width"] > 0 + assert result["height"] > 0 + +def test_output_keys(skill): + + img = create_image() + + result = skill.execute( + { + "image": img + } + ) + + expected = { + "success", + "image_base64", + "mime_type", + "output_path", + "width", + "height", + "model_used", + } + + assert expected.issubset(result.keys()) + +def test_invalid_base64(skill): + + result = skill.execute( + { + "image": "not_base64" + } + ) + + assert result["success"] is False + assert result["error_code"] == "PROCESSING_FAILED" \ No newline at end of file From 87ded29b29a2e14203c3eee82daa89aa5c2b1d30 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Sun, 12 Jul 2026 23:33:57 +0530 Subject: [PATCH 2/8] feat(creative): address review feedback for bg_remover --- docs/skills/README.md | 22 +- docs/skills/bg_remover.md | 355 +++++++++++++++++++-- skills/creative/bg_remover/instructions.md | 25 +- skills/creative/bg_remover/skill.py | 54 ++-- skills/creative/bg_remover/test_skill.py | 107 +++++-- 5 files changed, 484 insertions(+), 79 deletions(-) diff --git a/docs/skills/README.md b/docs/skills/README.md index 9866ec7..97aeb75 100644 --- a/docs/skills/README.md +++ b/docs/skills/README.md @@ -2,11 +2,15 @@ Welcome to the official catalog of Skillware capabilities. New here? Start with the [project README](../../README.md). -Browse by category below, or run `skillware list` after `pip install skillware` to see locally available skills. +Browse by category below, or run `skillware list` after `pip install skillware` to see locally available skills. When contributing a new skill, see [Choosing a category](../../CONTRIBUTING.md#choosing-a-category) in CONTRIBUTING.md. ## Office Skills for document processing, email automation, and productivity. +| Skill | ID | Issuer | Description | +| :--- | :--- | :--- | :--- | +| **[PDF Form Filler](pdf_form_filler.md)** | `office/pdf_form_filler` | [@rosspeili](https://github.com/rosspeili) ([@ARPAHLS](https://github.com/ARPAHLS)) | Fills AcroForm-based PDFs by mapping user instructions to detected form fields using LLM-based semantic understanding. | + ## Creative Skills for image processing, media editing, and creative utilities. @@ -14,16 +18,13 @@ Skills for image processing, media editing, and creative utilities. | :--- | :--- | :--- | :--- | | **[Background Remover](bg_remover.md)** | `creative/bg_remover` | [@AyushSrivastava1818](https://github.com/AyushSrivastava1818) | Removes image backgrounds locally using rembg and returns transparent PNGs. | -| Skill | ID | Issuer | Description | -| :--- | :--- | :--- | :--- | -| **[PDF Form Filler](pdf_form_filler.md)** | `office/pdf_form_filler` | [@rosspeili](https://github.com/rosspeili) ([@ARPAHLS](https://github.com/ARPAHLS)) | Fills AcroForm-based PDFs by mapping user instructions to detected form fields using LLM-based semantic understanding. | - ## Finance Tools for financial analysis, blockchain interaction, and regulatory compliance. | Skill | ID | Issuer | Description | | :--- | :--- | :--- | :--- | | **[Wallet Screening](wallet_screening.md)** | `finance/wallet_screening` | [@rosspeili](https://github.com/rosspeili) ([@ARPAHLS](https://github.com/ARPAHLS)) | Comprehensive risk assessment for Ethereum wallets. Checks sanctions lists (OFAC, FBI) and identifies interactions with malicious contracts (Mixers, Scams). | +| **[UK Companies House Handler](uk_companies_house_handler.md)** | `finance/uk_companies_house_handler` | [@Areen-09](https://github.com/Areen-09) ([@ARPAHLS](https://github.com/ARPAHLS)) | Deterministic UK Companies House API handler: company search, officers, PSC, filing history, and UK terminology mapping via structured actions. | ## DeFi On-chain execution and trading for dedicated agent wallets (structured intent, previews, confirmations). @@ -63,6 +64,13 @@ Skills that assist developers in understanding codebases, planning changes, and | :--- | :--- | :--- | :--- | | **[Issue Resolver](issue_resolver.md)** | `dev_tools/issue_resolver` | [@rosspeili](https://github.com/rosspeili) ([@ARPAHLS](https://github.com/ARPAHLS)) | GitHub issue URL prep, nine-stage agent workflow, conditional verify/commit gates, and commit-message validation. | +## Monitoring +Observability and guardrails for long-running autonomous agent loops. + +| Skill | ID | Issuer | Description | +| :--- | :--- | :--- | :--- | +| **[Token Limiter](token_limiter.md)** | `monitoring/token_limiter` | [@rosspeili](https://github.com/rosspeili) ([@ARPAHLS](https://github.com/ARPAHLS)) | Deterministic token budget gate that returns CONTINUE, WARN, or FORCE_TERMINATE for host loops. | + ## Wellness Supportive coaching guardrails, crisis triage, and grounded psychoeducation for host agents. @@ -80,7 +88,9 @@ Registry skills live under `skills///` in the repository a from skillware.core.loader import SkillLoader # Load by registry ID (category/skill_name) -skill = SkillLoader.load_skill("finance/wallet_screening") +bundle = SkillLoader.load_skill("finance/wallet_screening") +skill = bundle["class"]() +# Or: skill = bundle["module"].WalletScreeningSkill() ``` --- diff --git a/docs/skills/bg_remover.md b/docs/skills/bg_remover.md index c2bf7c1..d85aa99 100644 --- a/docs/skills/bg_remover.md +++ b/docs/skills/bg_remover.md @@ -1,27 +1,64 @@ # Background Remover +**Domain:** `creative` +**Skill ID:** `creative/bg_remover` +**Issuer:** [@AyushSrivastava1818](https://github.com/AyushSrivastava1818) + +[Skill Library](README.md) · [Testing](../TESTING.md) + ## Overview -`creative/bg_remover` removes the background from still images locally using rembg and returns a transparent PNG. +`creative/bg_remover` is a deterministic image-processing skill that removes the background from still images locally using `rembg`. It accepts Base64 image data or local image paths and produces transparent PNG output without requiring cloud services after the initial ONNX model download. + +## Capabilities + +- Removes image backgrounds locally using `rembg` +- Accepts Base64 images or local file paths +- Produces transparent PNG output +- Supports optional `alpha_matting` +- Works completely offline after the initial ONNX model download + +## Integration Guide + +### Environment + +This skill does not require API keys or cloud credentials. + +Install runtime dependencies: -## Inputs +```bash +pip install rembg pillow onnxruntime -- image (Base64) -- input_path -- output_path -- model -- alpha_matting +The first execution downloads the selected ONNX model (approximately 176 MB). Once cached, subsequent executions are fully offline. -## Outputs +Provider API keys are only required when using the provider integration examples below. -- success -- image_base64 -- mime_type -- width -- height -- model_used +``` + +## Input Parameters + +| Parameter | Required | Description | +| :--- | :---: | :--- | +| `image` | No | Base64-encoded input image | +| `input_path` | No | Local input image path | +| `output_path` | No | Local output PNG path | +| `model` | No | Optional rembg model | +| `alpha_matting` | No | Enable alpha matting if supported | + +## Output Schema -## Example +| Field | Description | +| :--- | :--- | +| `success` | Indicates whether processing completed successfully | +| `image_base64` | Base64-encoded PNG when `output_path` is not provided | +| `mime_type` | Output MIME type (`image/png`) | +| `width` | Output image width | +| `height` | Output image height | +| `model_used` | rembg model used for processing | + +## Input and Output Recipes + +### Base64 input ```json { @@ -29,17 +66,295 @@ } ``` -Returns +### Local file input + +```json +{ + "input_path": "input.png" +} +``` + +### Save output locally + +```json +{ + "input_path": "input.png", + "output_path": "output.png" +} +``` + +## Data Schema ```json { "success": true, - "mime_type": "image/png" + "image_base64": "", + "mime_type": "image/png", + "width": 1024, + "height": 768, + "model_used": "u2net" +} +``` + +## Internal Architecture + +The skill lives in `skills/creative/bg_remover/`. + +### The Mind (`instructions.md`) + +Guides the host agent on when to invoke the skill, accepted inputs, and expected output. + +### The Body (`skill.py`) + +Processes still images locally using `rembg`, supports Base64 and file-based workflows, and returns transparent PNG output. + +## Cloud Storage Recipes + +### AWS S3 + +Download the source image from S3 to a local temporary file, execute the skill using `input_path`, then upload the generated PNG from `output_path` back to S3. + +```json +{ + "input_path": "/tmp/input.png", + "output_path": "/tmp/output.png" +} +``` + +### Google Cloud Storage + +Download the object from GCS, process it locally with `input_path`, then upload the generated PNG. + +```json +{ + "input_path": "/tmp/input.png", + "output_path": "/tmp/output.png" +} +``` + +### Azure Blob Storage + +Download the blob to local storage before invoking the skill and upload the generated PNG afterwards. + +```json +{ + "input_path": "/tmp/input.png", + "output_path": "/tmp/output.png" +} +``` + +### Cloudflare R2 + +Download the object locally, invoke the skill, then upload the resulting transparent PNG back to the bucket. + +```json +{ + "input_path": "/tmp/input.png", + "output_path": "/tmp/output.png" } ``` ## Notes -- Runs completely offline. -- First execution may download the ONNX model. -- Unit tests mock rembg and do not download models. \ No newline at end of file +- Runs completely offline after the required model is available. +- The first execution downloads the required ONNX model (approximately 176 MB). Later executions reuse the cached model and work completely offline. +- Unit tests mock `rembg` and do not download ONNX models. +- The optional `model` and `alpha_matting` parameters are forwarded to `rembg` when supported by the installed version. + +## Usage Examples + +Guides: [Usage index](../usage/README.md) · [Agent loops](../usage/agent_loops.md) · [API keys](../usage/api_keys.md) + +Use `bundle["class"]()` in the snippets below. + +### Runnable examples + +See [examples/README.md](../../examples/README.md) for the current runnable provider examples. + +Sample user request: + +> Remove the background from `product.png` and save the transparent PNG. + +### Direct execute + +```python +from skillware.core.loader import SkillLoader + +bundle = SkillLoader.load_skill("creative/bg_remover") +skill = bundle["class"]() + +result = skill.execute({ + "input_path": "product.png", + "output_path": "product_no_bg.png", +}) +``` + +print(result) + +### Gemini + +```python +import google.genai as genai +from google.genai import types + +from skillware.core.env import load_env_file +from skillware.core.loader import SkillLoader + +load_env_file() + +bundle = SkillLoader.load_skill("creative/bg_remover") +skill = bundle["class"]() + +client = genai.Client() +tool = SkillLoader.to_gemini_tool(bundle) +tool_name = SkillLoader._sanitize_gemini_tool_name( + bundle["manifest"]["name"] +) + +response = client.models.generate_content( + model="gemini-2.5-flash", + contents=( + "Remove the background from product.png and save the result " + "as product_no_bg.png." + ), + config=types.GenerateContentConfig( + tools=[tool], + system_instruction=bundle["instructions"], + ), +) + +for part in response.candidates[0].content.parts: + if part.function_call and part.function_call.name == tool_name: + result = skill.execute(dict(part.function_call.args)) + + follow_up = client.models.generate_content( + model="gemini-2.5-flash", + contents=[ + "Use this tool result to answer the original request.", + { + "function_response": { + "name": part.function_call.name, + "response": {"result": result}, + } + }, + ], + config=types.GenerateContentConfig( + tools=[tool], + system_instruction=bundle["instructions"], + ), + ) + + print(follow_up.text) +``` + +### Claude + +```python +import os +import anthropic + +from skillware.core.env import load_env_file +from skillware.core.loader import SkillLoader + +load_env_file() + +bundle = SkillLoader.load_skill("creative/bg_remover") +skill = bundle["class"]() + +client = anthropic.Anthropic( + api_key=os.environ.get("ANTHROPIC_API_KEY"), +) + +tools = [SkillLoader.to_claude_tool(bundle)] + +# On tool_use: +# result = skill.execute(tool_use.input) +# Return the tool result to Claude. +``` + +### OpenAI + +```python +import os +from openai import OpenAI + +from skillware.core.env import load_env_file +from skillware.core.loader import SkillLoader + +load_env_file() + +bundle = SkillLoader.load_skill("creative/bg_remover") +skill = bundle["class"]() + +client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY")) + +tool = SkillLoader.to_openai_tool(bundle) + +# Match tool_call.function.name and execute: +# result = skill.execute(args) +``` + +### DeepSeek + +```python +import os +from openai import OpenAI + +from skillware.core.env import load_env_file +from skillware.core.loader import SkillLoader + +load_env_file() + +bundle = SkillLoader.load_skill("creative/bg_remover") +skill = bundle["class"]() + +client = OpenAI( + api_key=os.environ.get("DEEPSEEK_API_KEY"), + base_url="https://api.deepseek.com", +) + +tool = SkillLoader.to_deepseek_tool(bundle) + +# Match tool_call.function.name and execute: +# result = skill.execute(args) +``` + +### Ollama (prompt mode) + +```python +import json + +from skillware.core.loader import SkillLoader + +bundle = SkillLoader.load_skill("creative/bg_remover") +skill = bundle["class"]() + +prompt = SkillLoader.to_ollama_prompt(bundle) + +print(prompt) +print("User: Remove the background from product.png and save it as product_no_bg.png.") + +# When the model emits JSON tool arguments, +# pass them to execute(): + +result = skill.execute({ + "input_path": "product.png", + "output_path": "product_no_bg.png", +}) + +print(json.dumps(result, indent=2)) +``` + +## Limitations + +- Supports still images only. +- Video processing is not supported. +- Batch processing is not supported. +- The first execution downloads the selected ONNX model. +- Output quality depends on the selected `rembg` model. + +--- + +## Enterprise disclaimer + +This skill is provided for demonstration and integration purposes. It is intended as a starting point that you can adapt to your own workflows and image-processing requirements. For an enterprise-grade version of this skill with dedicated support, SLAs, and customization, contact skills@arpacorp.net. \ No newline at end of file diff --git a/skills/creative/bg_remover/instructions.md b/skills/creative/bg_remover/instructions.md index f141622..e69de3e 100644 --- a/skills/creative/bg_remover/instructions.md +++ b/skills/creative/bg_remover/instructions.md @@ -2,18 +2,25 @@ Use this skill whenever a user asks to: -- remove the background -- make an image transparent -- isolate a product -- create a PNG with transparency -- cut out a person or object +- remove the background from an image +- isolate an object or person +- create a transparent PNG +- cut out a product +- prepare an image for design or e-commerce workflows -Do NOT use this skill for: +Do not use this skill for: - videos - batch image processing -- cloud-only image editing +- cloud-only editing workflows -Input should be a Base64 image whenever possible. +Accepted inputs: -Return a transparent PNG. \ No newline at end of file +- Base64 image data (`image`) +- Local file (`input_path`) + +If `output_path` is supplied, save the generated transparent PNG there. + +Otherwise return the PNG as a Base64 string. + +This skill processes images locally using `rembg` and produces PNG output with transparency. \ No newline at end of file diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index 1d52cf7..6ee75aa 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -3,12 +3,10 @@ from pathlib import Path from typing import Any, Dict -from PIL import Image -from rembg import remove - from skillware.core.base_skill import BaseSkill -class BackgroundRemover(BaseSkill): + +class BackgroundRemover(BaseSkill): """Remove image backgrounds locally using rembg.""" @property @@ -24,41 +22,61 @@ def manifest(self) -> Dict[str, Any]: def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: """Execute background removal.""" - + try: + try: + from PIL import Image + from rembg import new_session, remove + except ImportError: + return { + "success": False, + "error": ( + "The 'rembg' dependency is not installed. " + "Install it with: pip install rembg pillow onnxruntime" + ), + "error_code": "MISSING_DEPENDENCY", + } + image_b64 = params.get("image") input_path = params.get("input_path") output_path = params.get("output_path") - + model = params.get("model", "isnet-general-use") + alpha_matting = params.get("alpha_matting", False) + if not image_b64 and not input_path: return { "success": False, "error": "Either image or input_path must be provided.", "error_code": "INVALID_INPUT", } - + # Read image bytes if image_b64: image_bytes = base64.b64decode(image_b64) else: image_bytes = Path(input_path).read_bytes() - - # Remove background - output_bytes = remove(image_bytes) - + + session = new_session(model) + + output_bytes = remove( + image_bytes, + session=session, + alpha_matting=alpha_matting, + ) + # Read PNG image = Image.open(io.BytesIO(output_bytes)) - + # Convert back to base64 buffer = io.BytesIO() image.save(buffer, format="PNG") - + encoded = base64.b64encode(buffer.getvalue()).decode("utf-8") - + # Optional save if output_path: Path(output_path).write_bytes(buffer.getvalue()) - + return { "success": True, "image_base64": encoded, @@ -66,12 +84,12 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: "output_path": output_path, "width": image.width, "height": image.height, - "model_used": params.get("model", "isnet-general-use"), + "model_used": model, } - + except Exception as e: return { "success": False, "error": str(e), "error_code": "PROCESSING_FAILED", - } \ No newline at end of file + } diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index 4267d1c..fc77943 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -7,29 +7,37 @@ from PIL import Image from .skill import BackgroundRemover -from . import skill as bg_skill -print("Loaded module:", bg_skill.__name__) +import sys +import types + @pytest.fixture(autouse=True) def mock_remove(monkeypatch): - """Mock rembg.remove so tests stay offline.""" + """Mock rembg so tests stay offline.""" - def fake_remove(image_bytes): + def fake_remove(image_bytes, *args, **kwargs): img = Image.new("RGBA", (100, 100), (255, 0, 0, 0)) buffer = io.BytesIO() img.save(buffer, format="PNG") return buffer.getvalue() - monkeypatch.setattr( - "skills.creative.bg_remover.skill.remove", - fake_remove, + def fake_new_session(model_name="u2net", *args, **kwargs): + return object() + + fake_module = types.SimpleNamespace( + remove=fake_remove, + new_session=fake_new_session, ) + monkeypatch.setitem(sys.modules, "rembg", fake_module) + + @pytest.fixture def skill(): return BackgroundRemover() + @pytest.fixture def manifest(): manifest_path = os.path.join( @@ -39,7 +47,8 @@ def manifest(): with open(manifest_path, "r", encoding="utf-8") as f: return yaml.safe_load(f) - + + def test_manifest(skill, manifest): assert skill.manifest["name"] == manifest["name"] assert skill.manifest["version"] == manifest["version"] @@ -50,7 +59,8 @@ def test_missing_input(skill): assert result["success"] is False assert result["error_code"] == "INVALID_INPUT" - + + def create_image(): image = Image.new("RGB", (64, 64), "white") @@ -59,34 +69,26 @@ def create_image(): image.save(buffer, format="PNG") - return base64.b64encode( - buffer.getvalue() - ).decode() + return base64.b64encode(buffer.getvalue()).decode() + def test_base64_image(skill): img = create_image() - result = skill.execute( - { - "image": img - } - ) + result = skill.execute({"image": img}) assert result["success"] is True assert result["mime_type"] == "image/png" assert result["width"] > 0 - assert result["height"] > 0 + assert result["height"] > 0 + def test_output_keys(skill): img = create_image() - result = skill.execute( - { - "image": img - } - ) + result = skill.execute({"image": img}) expected = { "success", @@ -100,13 +102,66 @@ def test_output_keys(skill): assert expected.issubset(result.keys()) + def test_invalid_base64(skill): + result = skill.execute({"image": "not_base64"}) + + assert result["success"] is False + assert result["error_code"] == "PROCESSING_FAILED" + +def test_missing_dependency(monkeypatch, skill): + """Returns MISSING_DEPENDENCY when optional libraries are unavailable.""" + + monkeypatch.delitem(sys.modules, "rembg", raising=False) + monkeypatch.delitem(sys.modules, "PIL", raising=False) + monkeypatch.delitem(sys.modules, "PIL.Image", raising=False) + + original_import = __import__ + + def fake_import(name, *args, **kwargs): + if name in ("rembg", "PIL", "PIL.Image"): + raise ImportError + return original_import(name, *args, **kwargs) + + monkeypatch.setattr("builtins.__import__", fake_import) + + result = skill.execute({"image": create_image()}) + + assert result["success"] is False + assert result["error_code"] == "MISSING_DEPENDENCY" + +def test_input_path(skill, tmp_path): + image = Image.new("RGB", (64, 64), "white") + + input_file = tmp_path / "input.png" + image.save(input_file) + result = skill.execute( { - "image": "not_base64" + "input_path": str(input_file), } ) - assert result["success"] is False - assert result["error_code"] == "PROCESSING_FAILED" \ No newline at end of file + assert result["success"] is True + assert result["mime_type"] == "image/png" + + +def test_output_path(skill, tmp_path): + image = Image.new("RGB", (64, 64), "white") + + input_file = tmp_path / "input.png" + output_file = tmp_path / "output.png" + + image.save(input_file) + + result = skill.execute( + { + "input_path": str(input_file), + "output_path": str(output_file), + } + ) + + assert result["success"] is True + assert output_file.exists() + assert result["output_path"] == str(output_file) From 71f712beb6c8eca57c775c1da3ca6a21bcdbb624 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 24 Jul 2026 15:23:03 +0530 Subject: [PATCH 3/8] feat(bg_remover): cache rembg sessions --- skills/creative/bg_remover/skill.py | 15 +++++++++-- skills/creative/bg_remover/test_skill.py | 33 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index 6ee75aa..1798c5b 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -8,6 +8,17 @@ class BackgroundRemover(BaseSkill): """Remove image backgrounds locally using rembg.""" + _sessions = {} + + @classmethod + def _get_session(cls, model: str): + """Load and reuse rembg sessions across executions.""" + if model not in cls._sessions: + from rembg import new_session + + cls._sessions[model] = new_session(model) + + return cls._sessions[model] @property def manifest(self) -> Dict[str, Any]: @@ -26,7 +37,7 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: try: try: from PIL import Image - from rembg import new_session, remove + from rembg import remove except ImportError: return { "success": False, @@ -56,7 +67,7 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: else: image_bytes = Path(input_path).read_bytes() - session = new_session(model) + session = self._get_session(model) output_bytes = remove( image_bytes, diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index fc77943..5368c1a 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -165,3 +165,36 @@ def test_output_path(skill, tmp_path): assert result["success"] is True assert output_file.exists() assert result["output_path"] == str(output_file) + +def test_session_reuse(monkeypatch, skill): + """Verify rembg sessions are reused for the same model.""" + + call_count = 0 + + def fake_new_session(model_name="u2net", *args, **kwargs): + nonlocal call_count + call_count += 1 + return object() + + def fake_remove(image_bytes, *args, **kwargs): + img = Image.new("RGBA", (100, 100), (255, 0, 0, 0)) + buffer = io.BytesIO() + img.save(buffer, format="PNG") + return buffer.getvalue() + + fake_module = types.SimpleNamespace( + remove=fake_remove, + new_session=fake_new_session, + ) + + monkeypatch.setitem(sys.modules, "rembg", fake_module) + + # Reset cached sessions so the test starts clean + BackgroundRemover._sessions.clear() + + image = create_image() + + skill.execute({"image": image}) + skill.execute({"image": image}) + + assert call_count == 1 From a33af60436986353b435638922bb00b25fa6d903 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 24 Jul 2026 15:44:59 +0530 Subject: [PATCH 4/8] feat(bg_remover): validate input images --- skills/creative/bg_remover/skill.py | 35 +++++++++++++++++++-- skills/creative/bg_remover/test_skill.py | 39 +++++++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index 1798c5b..9ce0701 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -63,10 +63,41 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: # Read image bytes if image_b64: - image_bytes = base64.b64decode(image_b64) + try: + image_bytes = base64.b64decode(image_b64, validate=True) + except Exception: + return { + "success": False, + "error": "Invalid base64 image.", + "error_code": "INVALID_INPUT", + } else: - image_bytes = Path(input_path).read_bytes() + input_file = Path(input_path) + if not input_file.exists(): + return { + "success": False, + "error": f"Input file '{input_path}' was not found.", + "error_code": "FILE_NOT_FOUND", + } + + image_bytes = input_file.read_bytes() + if len(image_bytes) == 0: + return { + "success": False, + "error": "Input image is empty.", + "error_code": "INVALID_INPUT", + } + try: + image = Image.open(io.BytesIO(image_bytes)) + image.verify() + except Exception: + return { + "success": False, + "error": "Input is not a valid image.", + "error_code": "INVALID_INPUT", + } + session = self._get_session(model) output_bytes = remove( diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index 5368c1a..d4b8629 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -108,7 +108,7 @@ def test_invalid_base64(skill): result = skill.execute({"image": "not_base64"}) assert result["success"] is False - assert result["error_code"] == "PROCESSING_FAILED" + assert result["error_code"] == "INVALID_INPUT" def test_missing_dependency(monkeypatch, skill): """Returns MISSING_DEPENDENCY when optional libraries are unavailable.""" @@ -198,3 +198,40 @@ def fake_remove(image_bytes, *args, **kwargs): skill.execute({"image": image}) assert call_count == 1 + +def test_missing_input_file(skill): + result = skill.execute( + { + "input_path": "does_not_exist.png", + } + ) + + assert result["success"] is False + assert result["error_code"] == "FILE_NOT_FOUND" + +def test_empty_input_file(skill, tmp_path): + empty_file = tmp_path / "empty.png" + empty_file.write_bytes(b"") + + result = skill.execute( + { + "input_path": str(empty_file), + } + ) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" + +def test_invalid_image(skill): + invalid_image = base64.b64encode( + b"this is not an image" + ).decode() + + result = skill.execute( + { + "image": invalid_image, + } + ) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" \ No newline at end of file From 72f97a0fb708cedceaedab4d6fd2c4edb36127ca Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 24 Jul 2026 15:48:14 +0530 Subject: [PATCH 5/8] feat(bg_remover): improve input and path validation --- skills/creative/bg_remover/skill.py | 10 +++++++- skills/creative/bg_remover/test_skill.py | 30 +++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index 9ce0701..8724321 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -73,6 +73,12 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: } else: input_file = Path(input_path) + if input_file.is_dir(): + return { + "success": False, + "error": "Input path must be a file, not a directory.", + "error_code": "INVALID_INPUT", + } if not input_file.exists(): return { @@ -117,7 +123,9 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: # Optional save if output_path: - Path(output_path).write_bytes(buffer.getvalue()) + output_file = Path(output_path) + output_file.parent.mkdir(parents=True, exist_ok=True) + output_file.write_bytes(buffer.getvalue()) return { "success": True, diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index d4b8629..8cc088f 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -234,4 +234,32 @@ def test_invalid_image(skill): ) assert result["success"] is False - assert result["error_code"] == "INVALID_INPUT" \ No newline at end of file + assert result["error_code"] == "INVALID_INPUT" + +def test_input_path_is_directory(skill, tmp_path): + result = skill.execute( + { + "input_path": str(tmp_path), + } + ) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" + +def test_output_parent_created(skill, tmp_path): + image = Image.new("RGB", (64, 64), "white") + + input_file = tmp_path / "input.png" + image.save(input_file) + + output_file = tmp_path / "nested" / "folder" / "output.png" + + result = skill.execute( + { + "input_path": str(input_file), + "output_path": str(output_file), + } + ) + + assert result["success"] is True + assert output_file.exists() \ No newline at end of file From 7dc951ec5fbf7037cc564a2dade7fb58fe9d1863 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 24 Jul 2026 15:52:33 +0530 Subject: [PATCH 6/8] feat(bg_remover): enforce input size limits --- skills/creative/bg_remover/skill.py | 1 + skills/creative/bg_remover/test_skill.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index 8724321..4ef182e 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -4,6 +4,7 @@ from typing import Any, Dict from skillware.core.base_skill import BaseSkill +MAX_IMAGE_BYTES = 25 * 1024 * 1024 # 25 MB class BackgroundRemover(BaseSkill): diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index 8cc088f..0797a90 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -262,4 +262,16 @@ def test_output_parent_created(skill, tmp_path): ) assert result["success"] is True - assert output_file.exists() \ No newline at end of file + assert output_file.exists() + +def test_image_too_large(skill): + large_image = base64.b64encode(b"x" * (26 * 1024 * 1024)).decode() + + result = skill.execute( + { + "image": large_image, + } + ) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" \ No newline at end of file From 47e223baf51b978be50de50e395311af95d16f18 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 24 Jul 2026 16:09:26 +0530 Subject: [PATCH 7/8] docs(bg_remover): update metadata and usage instructions --- skills/creative/bg_remover/instructions.md | 15 ++++++++++++--- skills/creative/bg_remover/manifest.yaml | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/skills/creative/bg_remover/instructions.md b/skills/creative/bg_remover/instructions.md index e69de3e..5f574ba 100644 --- a/skills/creative/bg_remover/instructions.md +++ b/skills/creative/bg_remover/instructions.md @@ -19,8 +19,17 @@ Accepted inputs: - Base64 image data (`image`) - Local file (`input_path`) -If `output_path` is supplied, save the generated transparent PNG there. +Input validation includes: -Otherwise return the PNG as a Base64 string. +- Invalid Base64 detection +- Missing input files +- Empty input files +- Invalid or corrupt images +- Directory path rejection +- Maximum input size enforcement (25 MB) -This skill processes images locally using `rembg` and produces PNG output with transparency. \ No newline at end of file +If `output_path` is supplied, parent directories are created automatically if needed, and the generated transparent PNG is saved there. + +Otherwise, the PNG is returned as a Base64 string. + +This skill processes images entirely locally using `rembg`, reuses cached inference sessions for improved performance, and always produces PNG output with transparency. \ No newline at end of file diff --git a/skills/creative/bg_remover/manifest.yaml b/skills/creative/bg_remover/manifest.yaml index d4dbf13..fb1992d 100644 --- a/skills/creative/bg_remover/manifest.yaml +++ b/skills/creative/bg_remover/manifest.yaml @@ -1,6 +1,6 @@ name: "creative/bg_remover" -version: "0.1.0" -description: "Remove image backgrounds locally using rembg." +version: "0.2.0" +description: "Remove image backgrounds locally using rembg with cached inference sessions and robust input validation." short_description: "Offline background removal using rembg with transparent PNG output." issuer: From 6fc2002617c7d487164f4bd35f9d50d55364074a Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 24 Jul 2026 16:25:20 +0530 Subject: [PATCH 8/8] feat(bg_remover): harden skill to v0.2.0 --- CHANGELOG.md | 6 ++++++ docs/skills/bg_remover.md | 9 ++++++++- skills/creative/bg_remover/skill.py | 16 +++++++++++++--- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31b5786..b6e203f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ Contributors add user-facing entries under `[Unreleased]` in the same PR. Mainta - Added `creative/bg_remover`, an offline background removal skill powered by rembg. +### Changed + +- **`creative/bg_remover`**: Hardened the skill with cached rembg sessions, improved input and path validation, automatic output directory creation, and input size limits. + +- **Documentation**: README Stats section and live PyPI download badges (pepy / PyPI Stats dashboards, header `DLs ↓` total). + ### Added - **Tests**: `tests/test_registry_docs.py` — CI doc-drift guards that verify skill catalog, examples index, and agent-loops reference matrix stay in sync with the registry (#183). diff --git a/docs/skills/bg_remover.md b/docs/skills/bg_remover.md index d85aa99..ab72fd2 100644 --- a/docs/skills/bg_remover.md +++ b/docs/skills/bg_remover.md @@ -14,6 +14,8 @@ - Removes image backgrounds locally using `rembg` - Accepts Base64 images or local file paths +- Validates Base64, file paths, image contents, and input size +- Reuses cached inference sessions for improved performance - Produces transparent PNG output - Supports optional `alpha_matting` - Works completely offline after the initial ONNX model download @@ -43,6 +45,7 @@ Provider API keys are only required when using the provider integration examples | `input_path` | No | Local input image path | | `output_path` | No | Local output PNG path | | `model` | No | Optional rembg model | + | `alpha_matting` | No | Enable alpha matting if supported | ## Output Schema @@ -55,6 +58,8 @@ Provider API keys are only required when using the provider integration examples | `width` | Output image width | | `height` | Output image height | | `model_used` | rembg model used for processing | +| `output_path` | Output file path when provided | +| `error_code` | Structured error code for failed requests | ## Input and Output Recipes @@ -106,7 +111,7 @@ Guides the host agent on when to invoke the skill, accepted inputs, and expected ### The Body (`skill.py`) -Processes still images locally using `rembg`, supports Base64 and file-based workflows, and returns transparent PNG output. +Processes still images locally using `rembg`, validates inputs, reuses cached inference sessions, supports Base64 and file-based workflows, and returns transparent PNG output. ## Cloud Storage Recipes @@ -158,6 +163,8 @@ Download the object locally, invoke the skill, then upload the resulting transpa - Runs completely offline after the required model is available. - The first execution downloads the required ONNX model (approximately 176 MB). Later executions reuse the cached model and work completely offline. +- Input validation rejects invalid Base64, missing files, empty files, corrupt images, directory paths, and inputs larger than 25 MB. +- Parent directories for `output_path` are created automatically when needed. - Unit tests mock `rembg` and do not download ONNX models. - The optional `model` and `alpha_matting` parameters are forwarded to `rembg` when supported by the installed version. diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index 4ef182e..bf9cada 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -4,11 +4,13 @@ from typing import Any, Dict from skillware.core.base_skill import BaseSkill + MAX_IMAGE_BYTES = 25 * 1024 * 1024 # 25 MB class BackgroundRemover(BaseSkill): """Remove image backgrounds locally using rembg.""" + _sessions = {} @classmethod @@ -25,7 +27,7 @@ def _get_session(cls, model: str): def manifest(self) -> Dict[str, Any]: return { "name": "creative/bg_remover", - "version": "0.1.0", + "version": "0.2.0", "description": ( "Remove image backgrounds locally using rembg " "and return a transparent PNG." @@ -88,7 +90,15 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: "error_code": "FILE_NOT_FOUND", } - image_bytes = input_file.read_bytes() + image_bytes = input_file.read_bytes() + + if len(image_bytes) > MAX_IMAGE_BYTES: + return { + "success": False, + "error": f"Input image exceeds the maximum size of {MAX_IMAGE_BYTES // (1024 * 1024)} MB.", + "error_code": "INVALID_INPUT", + } + if len(image_bytes) == 0: return { "success": False, @@ -104,7 +114,7 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: "error": "Input is not a valid image.", "error_code": "INVALID_INPUT", } - + session = self._get_session(model) output_bytes = remove(