Laravel Python Image Analysis Demo
#Laravel + Filament + Python Image Analysis Demo
This demo lets an admin upload an image in Filament, queues a Laravel job, hands the image to Python for analysis with Pillow and Tesseract, stores the JSON result, and displays it back in Filament.
Laravel can reach Python through two interchangeable transports, selected in .env:
| Driver | How it runs | Class |
|---|---|---|
process |
Boots a new Python process per image (python analyze_image.py <path>) |
App\Services\PythonImageAnalyzer |
http |
Uploads the image to a persistent FastAPI service | App\Services\HttpImageAnalyzer |
Both implement App\Services\ImageAnalyzer and return the same payload, so nothing else in the app changes when you switch.
#Setup
composer install cp .env.example .env php artisan key:generate php artisan migrate php artisan storage:link cd python && uv sync && cd .. php artisan queue:work
The Python project lives in python/ and is managed by uv: python/pyproject.toml is the single source of truth for its dependencies (there is no requirements.txt). OCR also needs the Tesseract binary — brew install tesseract on macOS.
#Choosing the Transport
#process (default)
PYTHON_ANALYZER_DRIVER=process PYTHON_BINARY=python
PYTHON_BINARY must point at a Python that has the dependencies installed. With uv, that is the project virtual environment:
PYTHON_BINARY=/absolute/path/to/project/python/.venv/bin/python
#http
Start the FastAPI service (it refuses to start without API_TOKEN, so it never runs unauthenticated):
cd python API_TOKEN=secret uv run fastapi dev api.py # development, port 8000 API_TOKEN=secret uv run fastapi run api.py # production, behind Supervisor
Then point Laravel at it with the same token:
PYTHON_ANALYZER_DRIVER=http PYTHON_API_URL=http://127.0.0.1:8000 PYTHON_API_TOKEN=secret
The service exposes GET /health (open) and POST /analyze (bearer token, multipart image upload):
curl -H "Authorization: Bearer secret" -F "file=@/path/to/image.jpg" http://127.0.0.1:8000/analyze
#How It Works
Upload image ↓ Laravel stores file ↓ Queue Job ↓ Python ↓ JSON ↓ Laravel saves result ↓ Filament displays analysis
The Filament resource is available in the admin panel under Image Analyses. Uploaded files are stored on the public disk under:
storage/app/public/image-analyses
#Python Analyzer
Both transports share the same core in python/analysis.py. Run the CLI manually with:
cd python && uv run python analyze_image.py /absolute/path/to/image.jpg
It returns JSON containing width, height, format, mode, dominant colors, brightness, a dark/light classification, and OCR text. On failure it prints {"error": "..."} and exits with code 1.
Run the Python tests with:
cd python && uv run pytest
#Created Files
app/Models/ImageAnalysis.phpapp/Jobs/AnalyzeImageJob.phpapp/Services/ImageAnalyzer.phpapp/Services/PythonImageAnalyzer.phpapp/Services/HttpImageAnalyzer.phpapp/Filament/Resources/ImageAnalyses/ImageAnalysisResource.phpapp/Filament/Resources/ImageAnalyses/Schemas/ImageAnalysisForm.phpapp/Filament/Resources/ImageAnalyses/Tables/ImageAnalysesTable.phpapp/Filament/Resources/ImageAnalyses/Pages/CreateImageAnalysis.phpapp/Filament/Resources/ImageAnalyses/Pages/EditImageAnalysis.phpapp/Filament/Resources/ImageAnalyses/Pages/ListImageAnalyses.phpdatabase/migrations/2026_06_25_035517_create_image_analyses_table.phppython/analysis.pypython/analyze_image.pypython/api.pypython/pyproject.tomlpython/tests/test_api.py
#Troubleshooting
- If thumbnails do not load, run
php artisan storage:linkand confirmAPP_URLmatches the Herd site URL. - If jobs stay pending, run
php artisan queue:work. - If analysis fails with
No module named PIL, runcd python && uv sync, and pointPYTHON_BINARYatpython/.venv/bin/python. - If OCR text is always empty, install the Tesseract binary (
brew install tesseract). - If the
httpdriver reports the service is unreachable, confirm the FastAPI process is running andPYTHON_API_URLmatches its port. - If the service returns 401,
PYTHON_API_TOKENin.envandAPI_TOKENin the service environment do not match. - If configuration changes are not picked up, run
php artisan config:clear.