# Batch Photo Upscaler and Restoration (`automation-lab/batch-photo-upscale-restore`) Actor

Batch upscale authorized product photos with bundled CPU FSRCNN super-resolution or non-neural Lanczos. Set a pixel budget and denoise strength; get per-image dimensions, model versions, download links and error manifests. No external AI API.

- **URL**: https://apify.com/automation-lab/batch-photo-upscale-restore.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.76 / 1,000 image upscaleds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Batch Photo Upscaler and Restoration

Upscale a batch of authorized product photos on Apify compute, without sending them to an external AI API. Choose a target pixel budget, optional denoising and PNG, JPEG or WebP output. Get an ordered per-image manifest with dimensions, download links, bytes, elapsed time and model version.

For catalog teams receiving small supplier photos every day, this provides repeatable enlargement with explicit limits and visible failures. It does not promise to recover missing product detail.

### What this Actor does

- Downloads direct public image URLs or reads binary uploads from an accessible Apify key-value store.
- Runs bundled FSRCNN neural super-resolution on CPU, with no runtime model download.
- Offers a non-neural Lanczos option when conservative interpolation is preferred.
- Preserves aspect ratio and PNG/WebP transparency, with integer dimension rounding.
- Writes one manifest entry per attempted image and stores successful files in the run key-value store.
- Reports failures without charging an image event for them.

### Who is it for?

E-commerce catalog teams can normalize small supplier assets before a human quality review. Merchandising teams can enlarge approved images for layouts. Developers can put image enlargement between an ingestion job and an asset-management upload.

This is not a generative image editor, forensic tool, OCR recovery system, face restoration service, scratch remover or colorizer. Never rely on enlarged labels, serial numbers or texture as newly recovered evidence.

### Why use this Actor?

The model and compute are self-contained. No paid AI account or external model API is needed. Every attempted image has an explicit outcome, making batch reconciliation straightforward.

FSRCNN is a compact regression CNN rather than a GAN or diffusion model. It can sharpen edges and also create artifacts; non-generative does not mean authentic detail recovery. Select `lanczos` to avoid neural reconstruction entirely. Denoising is off by default to protect small product labels and texture.

### Getting started

1. Supply direct public JPEG, PNG or WebP URLs you are authorized to process.
2. Choose a target larger than the original image but no more than 16 times its pixel count.
3. Leave denoising at zero for your first product-detail comparison.
4. Start the Actor and inspect the dataset manifest.
5. Download successful `outputUrl` files and review them against the originals before use.

The prefilled fruit photograph is a public OpenCV sample. The following input requests approximately one million output pixels:

```json
{
  "imageUrls": ["https://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg"],
  "targetPixels": 1000000,
  "mode": "fsrcnn",
  "denoiseStrength": 0,
  "outputFormat": "png",
  "maxImages": 100
}
```

### Input parameters

| Field | Meaning |
|---|---|
| `imageUrls` | Direct HTTP(S) image links. Private/local addresses, credentials, custom ports and unsafe redirects are rejected. |
| `imageKeys` | Binary upload keys. Read from `inputStoreId` or the current run default key-value store. |
| `inputStoreId` | Select your Apify upload store to grant the run read-only access under limited permissions. Used only for upload keys; source store is never written. |
| `targetPixels` | Output pixel budget, 10,000–2,000,000; default 1,000,000. Must be larger than each original and at most 16 times its pixel count. |
| `mode` | `fsrcnn` (default) or `lanczos`. No automatic method switching. |
| `denoiseStrength` | 0–1; default 0. OpenCV non-local-means pre-filter, h=10×strength. Applies to both methods. |
| `outputFormat` | `png` default, `jpeg` or `webp`. JPEG/WebP quality 92. JPEG uses white behind transparency. |
| `maxImages` | First 1–100 combined entries attempted, URLs then keys. Default 100. Includes failed entries and duplicates; zero/unlimited unsupported. |

At least one URL or key is required; combined input cannot exceed 100 entries. Entries beyond `maxImages` are not attempted or included in the manifest. Duplicates are processed separately and incur separate image events when successful.

### Uploaded images

Upload binary records into your own Apify key-value store before starting the Actor, then pass that store ID and image keys:

```json
{
  "inputStoreId": "YOUR17CHARSTOREID1",
  "imageKeys": ["product-front", "product-back"],
  "targetPixels": 1000000,
  "mode": "lanczos"
}
```

Select the store in the upload-store picker (or supply its ID through the API input). The Actor requests only READ permission on that selected store under limited permissions, not full account access. Your calling token must itself be authorized for the selected store. Unselected stores and source-store writes are not granted. If no store ID is supplied, keys refer only to this run's default store. `INPUT` and keys beginning `upscaled-` are reserved. Uploads must be binary images, not JSON containing a URL.

### Output manifest

The default dataset contains successful and failed entries in attempted order. Successful entries contain:

| Fields | Description |
|---|---|
| `index`, `sourceType`, `source` | Zero-based batch index and original URL or key. Original URL queries remain in the dataset; keep it private if sensitive. |
| `status`, `error` | `success` or `error`; error text is present on failed entries. |
| `originalWidth`, `originalHeight` | Dimensions after EXIF orientation. |
| `outputWidth`, `outputHeight` | Actual encoded output dimensions. |
| `scaleFactor`, `scaleFactorY` | Actual width/height multipliers, slightly different when rounded. |
| `inputBytes`, `outputBytes` | File sizes in bytes. |
| `outputKey`, `outputUrl` | Run storage record and downloadable file URL. |
| `model`, `modelVersion` | Actual FSRCNN native scale and pinned source commit, or Lanczos/Pillow version. |
| `nonGenerative` | No GAN/diffusion used, not a guarantee of recovered authenticity. |
| `elapsedMs` | Per-entry download, processing and storage time. |

For example, the tested 512×480 fruit sample at a 600,000 pixel budget produces 800×750 pixels using FSRCNN x2. A representative manifest shape is:

```json
{
  "index": 0,
  "sourceType": "url",
  "source": "https://example.com/product.jpg",
  "status": "success",
  "originalWidth": 512,
  "originalHeight": 480,
  "outputWidth": 800,
  "outputHeight": 750,
  "scaleFactor": 1.5625,
  "scaleFactorY": 1.5625,
  "inputBytes": 82429,
  "outputBytes": 1000000,
  "outputKey": "upscaled-1.png",
  "outputUrl": "https://api.apify.com/v2/key-value-stores/EXAMPLESTORE12345A/records/upscaled-1.png",
  "model": "FSRCNN x2",
  "modelVersion": "6a4812c4ef1c4f5947d79beafa32a05a6eb4a94d",
  "nonGenerative": true,
  "elapsedMs": 1066
}
```

The URL and output byte count above are illustrative, not a permanent download. Use your run's values. Failed entries omit output dimensions/files/model and contain a reason. If an image event cannot be charged because of a spending limit, its file is deleted and an uncharged error row records that attempted image; later images are not attempted. Partial success is possible; a run in which every entry fails exits FAILED.

### Image limits and quality

Inputs: static JPEG, PNG or WebP, at most 10 MB, one million pixels and 4096 pixels on either side. Animation and other formats are rejected. Outputs: at most two million pixels, 8192 pixels on either side and 4x original dimensions.

The target is a maximum pixel budget, not an exact size promise; integer rounding may reduce it slightly. Already-larger images and excessive enlargement requests produce an error instead of silent downscaling. A 128×128 image cannot reach one million pixels within the supported 4x enlargement: use a target up to 262,144.

FSRCNN selects native 2x inference for requested scale up to 2, otherwise 4x, and resizes to the requested budget. Tiled inference bounds activation memory. Lanczos enlarges without neural inference. There is no quality fallback, proxy fallback, face model or subscription service behind either mode.

### Denoising and product-detail preservation

Start with zero denoising. Positive values apply a non-local-means pre-filter and can remove both noise and useful detail. The effect depends on the image; this is not scratch repair. Compare all outputs with originals, especially labels, fabric, logos, fine patterns and sharp edges. Larger dimensions do not create trustworthy missing information.

PNG is the safest lossless delivery format. WebP and JPEG introduce compression. Alpha is preserved in PNG/WebP, interpolated independently from color; JPEG composites transparency onto white. EXIF orientation is applied; metadata is stripped from processed copies. Retain originals and their provenance records separately. This Actor is not a provenance-removal or authenticity-bypass tool.

### How much does it cost to upscale product photos?

A one-time **$0.005 start event** applies per run. Each successfully processed and stored image incurs one **image upscaled** event. Failed entries have no image event charge; the start event can still apply when all images fail. No separate dataset-row event is charged.

BRONZE image price is **$0.0096 per image**. At this price, 1 image costs $0.0146, 10 images $0.101, and 100 images $0.965 in Actor event charges. Your applicable subscription tier determines the image rate; consult the live Pricing tab. Platform usage, storage, transfer and applicable taxes depend on Apify billing rules. The price is the same for either method and any supported pixel budget.

### Integrations

- In n8n or Make, send the daily supplier image list, wait for the same run to finish, then filter manifest rows with `status=success` before uploading files to your asset manager.
- Join results back to your catalog using `index` and `source`; preserve failures for review instead of silently dropping SKUs.
- Use the Apify dataset JSON/CSV export for reconciliation; the dataset contains metadata, not inline image bytes.
- Download images before the run storage expires. Treat shared output links as access-bearing links and follow your retention policy.

### Related Actors

This Actor is standalone: it does not scrape suppliers, publish products or require any other Actor. No companion Actor is required; use your existing catalog ingestion and asset-management pipeline.

### API examples

Use your own Apify token. Start once and retain the returned run ID and storage IDs.

#### cURL

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~batch-photo-upscale-restore/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \
  -d '{"imageUrls":["https://example.com/product.jpg"],"targetPixels":1000000}'
```

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/batch-photo-upscale-restore').call({
  imageUrls: ['https://example.com/product.jpg'], targetPixels: 1000000
});
if (run.status !== 'SUCCEEDED') throw new Error(`Run ${run.id}: ${run.status}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 100 });
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/batch-photo-upscale-restore').call(
    run_input={'imageUrls': ['https://example.com/product.jpg'], 'targetPixels': 1000000})
if run['status'] != 'SUCCEEDED':
    raise RuntimeError(run['status'])
print(client.dataset(run['defaultDatasetId']).list_items(limit=100).items)
```

### MCP setup

For Claude Code:

```bash
claude mcp add --transport http apify "https://mcp.apify.com?tools=automation-lab/batch-photo-upscale-restore"
```

For Claude Desktop or Cursor, configure an HTTP-capable MCP client with:

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/batch-photo-upscale-restore"}}}
```

For VS Code, use:

```json
{"servers":{"apify":{"type":"http","url":"https://mcp.apify.com?tools=automation-lab/batch-photo-upscale-restore"}}}
```

Authenticate with your own Apify account. Discover actual tool names and schemas with scoped `tools/list`: Actor selection also includes run/dataset/storage/abort tools, not exactly one tool. Discovery itself is read-only.

Example prompt: “Upscale these authorized product images to one million pixels with denoise off, then report success/error counts and download links.” Start only once. Keep the returned run ID; metadata IDs are not results. A call can return before completion. Poll that same run with bounded backoff (2,4,8 seconds, capped at 10) up to a stated 120-second consumer deadline. Recover the known run after client timeout instead of starting a duplicate. At deadline, report pending and stop; terminal failures mean any rows are partial.

After success, read bounded dataset pages: 20 source rows, at most 100 rows and 64 KiB of serialized UTF-8 content admitted to model context, whichever comes first. Enforce bytes host-side, including oversized pages, and disclose truncation and continuation offsets. If your client cannot intercept oversized responses, do not promise a hard byte limit. Keep full exports and image files outside model context. Discovery/caching behavior is client-dependent; no token-savings guarantee is claimed.

### Legality, privacy and responsible use

Process only images you own or are authorized to transform. Respect copyrights, depicted people, supplier contracts and platform rules. Never use enlarged output as forensic or medical evidence. No watermark-removal, document reconstruction or provenance-bypass capability is offered.

Image processing occurs in the Actor container. Images are retrieved from supplied hosts or Apify storage and output files are stored in Apify. Failed operations send sanitized diagnostic input, exceptions and Actor/build/run IDs to our private GlitchTip service for repair; secret fields and URL queries are removed and reports are retained for 30 days. Image bytes are not included in these diagnostics. The output dataset retains the original input references, so protect it appropriately.

Bundled FSRCNN models are distributed under Apache-2.0 from Saafke/FSRCNN_Tensorflow; model versions and hashes are documented in `models/README.md`, with the upstream license included. OpenCV, Pillow and NumPy license notices are included in installed distributions. No proprietary model API or runtime download is used. Contact us through the Actor's Apify Issues tab for bugs or usage questions. Released model licensing is not a warranty about all training-image provenance or your rights to input/output assets.

### Troubleshooting and FAQ

**Why is my image rejected?** Check direct-image URL access, single-frame format, 10 MB/1 MP input limits and target-size range. Tiny thumbnails need a smaller target; already-large inputs need a larger target within two million pixels. Private/internal hosts and unsafe redirects are intentionally unsupported.

**Can I upload instead of expose a public URL?** Yes, use binary records in an accessible Apify store and supply its ID and keys. Uploaded keys are not file paths or JSON documents.

**Does success guarantee every image worked?** No. Inspect each row's `status`. Partial batches can succeed; all-error batches fail. Network requests time out after 30 seconds, allow at most three redirects and do not blindly retry permanent failures.

**Will this recover text or faces?** No such claim is made. Use original higher-resolution sources wherever accuracy matters. Lanczos is the non-neural alternative, not detail recovery.

**Are outputs permanent?** No. Apify storage retention applies. Download and retain permitted results in your own system.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/batch-photo-upscale-restore/changelog.md

# Actor input Schema

## `imageUrls` (type: `array`):

Direct public HTTP(S) JPEG, PNG or WebP URLs, not web pages. Combined URLs and keys: 1–100 entries, max 10 MB and 1 MP per image. Local/private addresses and custom ports are rejected, including redirects. URLs are processed first. Duplicates are processed and billed separately.

## `imageKeys` (type: `array`):

Binary image keys in inputStoreId, or the current run default key-value store when omitted. Same limits as URLs. Keys follow all URLs; INPUT and upscaled- prefixed keys are reserved. No external model account needed.

## `inputStoreId` (type: `string`):

Optional Apify key-value store containing your binary image uploads. Select the store to grant this run read-only access under limited permissions; no write permission is requested. Used only for imageKeys. Omit to read the run default store.

## `targetPixels` (type: `integer`):

Maximum output width × height, 10000–2000000. Aspect ratio preserved with integer rounding. Must exceed each original pixel count and be no more than 16 times it (up to 4x dimensions). No silent downscaling. Output side limit 8192 pixels; incompatible images get free error rows.

## `mode` (type: `string`):

fsrcnn uses a bundled Apache-2.0 regression CNN (no GAN or diffusion), with native 2x/4x inference then resizing to targetPixels. lanczos uses ordinary non-neural interpolation. Neither recovers lost text or authentic detail. Both honor denoiseStrength.

## `denoiseStrength` (type: `number`):

0 disables filtering (best for product labels). 0–1 controls an OpenCV non-local-means pre-filter, h=10×strength, before either method. Strong filtering can remove real detail; not face or scratch restoration.

## `outputFormat` (type: `string`):

PNG is lossless. JPEG/WebP use quality 92. PNG/WebP preserve alpha; JPEG composites onto white. Single-frame images only. Orientation applied and metadata stripped; keep originals and provenance separately.

## `maxImages` (type: `integer`):

Global number of entries attempted, including failed entries and duplicates, from URLs followed by keys. First N only; later entries are not processed. Range 1–100; zero/unlimited unsupported. Batch is sequential.

## Actor input object example

```json
{
  "imageUrls": [
    "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg"
  ],
  "targetPixels": 1000000,
  "mode": "fsrcnn",
  "denoiseStrength": 0,
  "outputFormat": "png",
  "maxImages": 100
}
```

# Actor output Schema

## `dataset` (type: `string`):

Successful and failed image entries.

## `files` (type: `string`):

Default key-value store containing successful processed files.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "imageUrls": [
        "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/batch-photo-upscale-restore").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "imageUrls": ["https://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg"] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/batch-photo-upscale-restore").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "imageUrls": [
    "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/fruits.jpg"
  ]
}' |
apify call automation-lab/batch-photo-upscale-restore --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/batch-photo-upscale-restore"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/dmUKzBvEcAIgz8Vjp/builds/YMoeTWYbIAdiabvb3/openapi.json
