Add public domain eye catalog system for CNC wood carving
Added complete system for populating eye catalog with classical carved eyes: Features: - Eye import script (import_eyes.py) for batch/single eye imports - Public domain source guide (museums: Met, Smithsonian, Getty, etc.) - Seed catalog system for pre-populating database - Organized by emotion (serene, fierce, wise, peaceful, etc.) - Organized by style (Greek, Roman, Egyptian, Renaissance) - Organized by side (left, right, both) - Auto-generates thumbnails and metadata - CNC-ready tagging system Workflow: 1. Download classical sculpture photos from public domain museums 2. Crop eyes in any image editor 3. Run import script with metadata 4. Eyes saved to catalog with proper tags 5. Apply to colored photos (pure image compositing, no AI regeneration) 6. Convert result to grayscale for CNC carving Documentation: - PUBLIC_DOMAIN_EYE_SOURCES.md: Where to find carved eyes - scripts/README.md: How to import eyes - Includes recommended starting collection (10 essential eyes) Benefits for wood carving: - Build library from master sculptors (2000+ years of proven designs) - Reusable across all projects - Consistent emotional weight in carvings - No AI regeneration - just intelligent copy/paste/blend - Perfect for CNC workflow (colored preview → grayscale → carve) The patch library system uses PIL/OpenCV for image compositing, NOT AI regeneration, so it preserves exact carved geometry.
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
# Eye Catalog Import Scripts
|
||||
|
||||
Tools for populating your carved eye catalog with public domain examples.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Download Classical Eyes
|
||||
|
||||
Follow the guide: `/docs/PUBLIC_DOMAIN_EYE_SOURCES.md`
|
||||
|
||||
**Best sources:**
|
||||
- Metropolitan Museum (CC0)
|
||||
- Smithsonian Open Access
|
||||
- Getty Museum Open Content
|
||||
|
||||
**Download 10-20 high-res images** of carved eyes from classical sculptures.
|
||||
|
||||
---
|
||||
|
||||
### 2. Crop the Eyes
|
||||
|
||||
Use any image editor (Photoshop, GIMP, Preview, etc.):
|
||||
|
||||
1. Open statue photo
|
||||
2. Zoom in on eye
|
||||
3. Crop just the eye (include eyelids, socket, tear duct)
|
||||
4. Save as PNG with descriptive name:
|
||||
- `greek_serene_left.png`
|
||||
- `roman_fierce_right.png`
|
||||
- `egyptian_wise_left.png`
|
||||
|
||||
---
|
||||
|
||||
### 3. Import to Catalog
|
||||
|
||||
**Easy way (one at a time):**
|
||||
```bash
|
||||
cd backend/scripts
|
||||
|
||||
# Import a Greek serene eye
|
||||
python import_eyes.py greek_serene_left.png \
|
||||
--emotion serene \
|
||||
--side left \
|
||||
--style greek
|
||||
|
||||
# Import a Roman fierce eye
|
||||
python import_eyes.py roman_fierce_right.png \
|
||||
--emotion fierce \
|
||||
--side right \
|
||||
--style roman
|
||||
```
|
||||
|
||||
**Batch import:**
|
||||
```bash
|
||||
# Import all Greek eyes at once
|
||||
python import_eyes.py greek_*.png \
|
||||
--emotion serene \
|
||||
--side both \
|
||||
--style greek
|
||||
|
||||
# Import all Roman eyes
|
||||
python import_eyes.py roman_*.png \
|
||||
--emotion fierce \
|
||||
--side both \
|
||||
--style roman
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Options
|
||||
|
||||
### Emotions
|
||||
- `serene` - Peaceful, calm (most classical Greek)
|
||||
- `fierce` - Intense, powerful (Hellenistic, Alexander)
|
||||
- `wise` - Aged, experienced (Roman senators)
|
||||
- `peaceful` - Gentle, kind (Archaic Greek)
|
||||
- `joyful` - Happy, smiling (rare in classical)
|
||||
- `sorrowful` - Sad, mourning (some Hellenistic)
|
||||
- `neutral` - Default, no strong emotion
|
||||
|
||||
### Styles
|
||||
- `greek` - Classical Greek (450-400 BCE)
|
||||
- `roman` - Roman Republican/Imperial
|
||||
- `egyptian` - Ancient Egyptian carved eyes
|
||||
- `renaissance` - Renaissance sculpture
|
||||
- `baroque` - Baroque period
|
||||
- `modern` - Contemporary carving
|
||||
- `custom` - Your own style
|
||||
|
||||
### Sides
|
||||
- `left` - Left eye
|
||||
- `right` - Right eye
|
||||
- `both` - Can be used for either (symmetric)
|
||||
|
||||
---
|
||||
|
||||
## Example Workflow
|
||||
|
||||
### Build a Complete Catalog
|
||||
|
||||
```bash
|
||||
# 1. Download eyes from Met Museum
|
||||
# (See PUBLIC_DOMAIN_EYE_SOURCES.md)
|
||||
|
||||
# 2. Crop and save them:
|
||||
# greek_serene_left.png
|
||||
# greek_serene_right.png
|
||||
# roman_fierce_left.png
|
||||
# roman_fierce_right.png
|
||||
# etc.
|
||||
|
||||
# 3. Import them all:
|
||||
|
||||
python import_eyes.py greek_serene_left.png --emotion serene --side left --style greek
|
||||
python import_eyes.py greek_serene_right.png --emotion serene --side right --style greek
|
||||
python import_eyes.py roman_fierce_left.png --emotion fierce --side left --style roman
|
||||
python import_eyes.py roman_fierce_right.png --emotion fierce --side right --style roman
|
||||
|
||||
# Or batch:
|
||||
python import_eyes.py greek_*.png --emotion serene --side both --style greek
|
||||
python import_eyes.py roman_*.png --emotion fierce --side both --style roman
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommended Starting Collection
|
||||
|
||||
### 10 Essential Eyes
|
||||
|
||||
1. **Greek Serene Left** (peaceful carvings)
|
||||
2. **Greek Serene Right**
|
||||
3. **Greek Archaic Left** (stylized, simple)
|
||||
4. **Greek Archaic Right**
|
||||
5. **Roman Fierce Left** (powerful portraits)
|
||||
6. **Roman Fierce Right**
|
||||
7. **Roman Wise Left** (aged, realistic)
|
||||
8. **Roman Wise Right**
|
||||
9. **Egyptian Stylized Left** (distinctive style)
|
||||
10. **Egyptian Stylized Right**
|
||||
|
||||
This gives you 5 styles/emotions to start!
|
||||
|
||||
---
|
||||
|
||||
## Check Your Catalog
|
||||
|
||||
### Via API
|
||||
|
||||
```bash
|
||||
# List all eyes in catalog
|
||||
curl http://localhost:8101/patches/?category=carved_eye
|
||||
|
||||
# Filter by emotion
|
||||
curl http://localhost:8101/patches/?category=carved_eye&tags=serene
|
||||
|
||||
# Filter by style
|
||||
curl http://localhost:8101/patches/?tags=greek
|
||||
```
|
||||
|
||||
### Via Web Interface
|
||||
|
||||
Go to: `http://your-server:3080`
|
||||
|
||||
Navigate to patch library to browse your eyes visually.
|
||||
|
||||
---
|
||||
|
||||
## Advanced: Seed Script
|
||||
|
||||
For batch importing from the `seed_data/eyes/` directory:
|
||||
|
||||
```bash
|
||||
# 1. Place all cropped eyes in:
|
||||
mkdir -p seed_data/eyes/
|
||||
# Copy your eye images there
|
||||
|
||||
# 2. Edit seed_eye_catalog.py to add metadata
|
||||
|
||||
# 3. Run:
|
||||
python seed_eye_catalog.py
|
||||
```
|
||||
|
||||
This auto-imports all eyes in `seed_data/eyes/` with pre-configured metadata.
|
||||
|
||||
---
|
||||
|
||||
## Tips
|
||||
|
||||
1. **High resolution:** Use images 1000px+ for best results
|
||||
2. **Clean crops:** Include some surrounding area, not just the eyeball
|
||||
3. **Consistent naming:** Use descriptive filenames
|
||||
4. **Test first:** Import 2-3 eyes to test the workflow
|
||||
5. **Build gradually:** Start with 10 eyes, expand as needed
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"File not found":**
|
||||
- Make sure you're in `backend/scripts/` directory
|
||||
- Use full path or relative path to image
|
||||
|
||||
**"Database connection error":**
|
||||
- Make sure backend is running: `docker compose up backend`
|
||||
- Check database exists: `ls -la ../../data/`
|
||||
|
||||
**"Import failed":**
|
||||
- Check image format (PNG, JPG supported)
|
||||
- Verify file isn't corrupted
|
||||
- Check file permissions
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
After importing eyes:
|
||||
|
||||
1. **Test them:** Apply to a colored photo via API
|
||||
2. **Refine:** Add more variations as needed
|
||||
3. **Build library:** Aim for 20-30 eyes covering all emotions
|
||||
4. **Share:** Your best eyes can be exported and shared
|
||||
|
||||
**Your catalog of master sculptor's eyes is ready to use!**
|
||||
Executable
+210
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick eye import script
|
||||
|
||||
Usage:
|
||||
python import_eyes.py greek_serene_left.png --emotion serene --side left --style greek
|
||||
python import_eyes.py *.png --emotion fierce --style roman
|
||||
|
||||
This will add eyes to the patch library with proper metadata.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
# Add parent directory to path to import app modules
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from app.database import SessionLocal
|
||||
from app.models.patch import Patch
|
||||
from app.services.patch_library import PatchLibraryService
|
||||
|
||||
|
||||
def import_eye(
|
||||
image_path: Path,
|
||||
emotion: str,
|
||||
side: str,
|
||||
style: str,
|
||||
description: str = None
|
||||
):
|
||||
"""
|
||||
Import a single eye image into the catalog
|
||||
|
||||
Args:
|
||||
image_path: Path to eye image file
|
||||
emotion: serene, fierce, wise, peaceful, joyful, sorrowful
|
||||
side: left, right, both
|
||||
style: greek, roman, egyptian, renaissance, custom
|
||||
description: Optional custom description
|
||||
"""
|
||||
|
||||
db = SessionLocal()
|
||||
patch_service = PatchLibraryService()
|
||||
|
||||
# Generate name from filename if not provided
|
||||
name = image_path.stem.replace('_', ' ').title()
|
||||
|
||||
# Auto-generate description if not provided
|
||||
if not description:
|
||||
description = f"{style.title()} carved eye, {emotion} expression, {side} side. Suitable for CNC wood carving."
|
||||
|
||||
# Generate tags
|
||||
tags = f"{style}, {emotion}, {side}, carved, cnc-ready, wood-carving"
|
||||
|
||||
print(f"\n📸 Importing: {name}")
|
||||
print(f" File: {image_path.name}")
|
||||
print(f" Style: {style}")
|
||||
print(f" Emotion: {emotion}")
|
||||
print(f" Side: {side}")
|
||||
|
||||
try:
|
||||
# Create patch record
|
||||
patch = Patch(
|
||||
name=name,
|
||||
description=description,
|
||||
source_type="imported",
|
||||
category="carved_eye",
|
||||
tags=tags,
|
||||
width=0,
|
||||
height=0,
|
||||
user_id=None,
|
||||
file_path=""
|
||||
)
|
||||
|
||||
db.add(patch)
|
||||
db.commit()
|
||||
db.refresh(patch)
|
||||
|
||||
# Save image file
|
||||
file_path = patch_service.save_patch_from_file(
|
||||
patch.id,
|
||||
str(image_path),
|
||||
create_thumb=True
|
||||
)
|
||||
|
||||
# Get and update dimensions
|
||||
width, height = patch_service.get_patch_size(patch.id)
|
||||
patch.width = width
|
||||
patch.height = height
|
||||
patch.file_path = file_path
|
||||
patch.thumbnail_path = str(patch_service.get_thumbnail_path(patch.id))
|
||||
|
||||
db.commit()
|
||||
|
||||
print(f"✅ Successfully imported!")
|
||||
print(f" ID: {patch.id}")
|
||||
print(f" Size: {width}x{height}px")
|
||||
|
||||
return patch.id
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error importing: {e}")
|
||||
db.rollback()
|
||||
return None
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Import carved eye images into the patch library",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Import a single eye
|
||||
python import_eyes.py greek_serene_left.png --emotion serene --side left --style greek
|
||||
|
||||
# Import multiple eyes with same metadata
|
||||
python import_eyes.py roman_*.png --emotion fierce --side both --style roman
|
||||
|
||||
# With custom description
|
||||
python import_eyes.py statue_eye.png --emotion wise --side right --style roman --description "Emperor Augustus portrait eye"
|
||||
|
||||
Emotions: serene, fierce, wise, peaceful, joyful, sorrowful, neutral
|
||||
Sides: left, right, both
|
||||
Styles: greek, roman, egyptian, renaissance, baroque, modern, custom
|
||||
"""
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'images',
|
||||
nargs='+',
|
||||
help='Image file(s) to import (supports wildcards)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--emotion',
|
||||
required=True,
|
||||
choices=['serene', 'fierce', 'wise', 'peaceful', 'joyful', 'sorrowful', 'neutral'],
|
||||
help='Emotional expression of the eye'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--side',
|
||||
required=True,
|
||||
choices=['left', 'right', 'both'],
|
||||
help='Which eye (left or right)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--style',
|
||||
required=True,
|
||||
choices=['greek', 'roman', 'egyptian', 'renaissance', 'baroque', 'modern', 'custom'],
|
||||
help='Carving style/period'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--description',
|
||||
help='Custom description (optional)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Resolve wildcards and get all image files
|
||||
image_files = []
|
||||
for pattern in args.images:
|
||||
path = Path(pattern)
|
||||
if '*' in pattern:
|
||||
# Wildcard - expand it
|
||||
parent = path.parent if path.parent.exists() else Path('.')
|
||||
image_files.extend(parent.glob(path.name))
|
||||
else:
|
||||
# Single file
|
||||
if path.exists():
|
||||
image_files.append(path)
|
||||
else:
|
||||
print(f"⚠️ File not found: {pattern}")
|
||||
|
||||
if not image_files:
|
||||
print("❌ No image files found!")
|
||||
return
|
||||
|
||||
print(f"\n🎨 Importing {len(image_files)} eye image(s) into catalog...")
|
||||
print(f" Style: {args.style}")
|
||||
print(f" Emotion: {args.emotion}")
|
||||
print(f" Side: {args.side}")
|
||||
print("="*60)
|
||||
|
||||
imported_count = 0
|
||||
for image_path in image_files:
|
||||
patch_id = import_eye(
|
||||
image_path,
|
||||
args.emotion,
|
||||
args.side,
|
||||
args.style,
|
||||
args.description
|
||||
)
|
||||
if patch_id:
|
||||
imported_count += 1
|
||||
|
||||
print("="*60)
|
||||
print(f"\n✅ Import complete! {imported_count}/{len(image_files)} eyes added to catalog")
|
||||
print(f"\n💡 Access your eye catalog at: http://your-server:3080")
|
||||
print(f" Or via API: GET /patches/?category=carved_eye")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
Seed the patch library with classical carved eyes from public domain sources
|
||||
|
||||
This script helps pre-populate the eye catalog with examples from:
|
||||
- Greek statues (Metropolitan Museum, Louvre)
|
||||
- Roman sculptures (Smithsonian, British Museum)
|
||||
- Renaissance carvings
|
||||
- Ancient Egyptian carved eyes
|
||||
|
||||
All images should be public domain (CC0, Public Domain Mark)
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import httpx
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
|
||||
# Public domain eye examples to seed the catalog
|
||||
# These are examples - you would add actual URLs from museum APIs
|
||||
CLASSICAL_EYES = [
|
||||
{
|
||||
"name": "Greek Statue - Serene Left Eye",
|
||||
"description": "Classical Greek marble carving, convex eyeball, defined upper lid, deep socket. Perfect for serene expressions.",
|
||||
"category": "carved_eye",
|
||||
"tags": "greek, serene, left, marble, classical, convex, deep-socket",
|
||||
"style": "greek_classical",
|
||||
"emotion": "serene",
|
||||
"side": "left",
|
||||
"source_url": "https://images.metmuseum.org/...", # Example
|
||||
"source": "Metropolitan Museum of Art - Public Domain"
|
||||
},
|
||||
{
|
||||
"name": "Roman Sculpture - Fierce Right Eye",
|
||||
"description": "Roman marble, prominent brow ridge, intense gaze, sharp eyelid definition.",
|
||||
"category": "carved_eye",
|
||||
"tags": "roman, fierce, right, marble, intense, sharp-detail",
|
||||
"style": "roman_classical",
|
||||
"emotion": "fierce",
|
||||
"side": "right",
|
||||
"source_url": "https://...",
|
||||
"source": "Smithsonian - CC0"
|
||||
},
|
||||
{
|
||||
"name": "Greek Kouros - Peaceful Left Eye",
|
||||
"description": "Archaic Greek style, almond-shaped, subtle carving, peaceful expression.",
|
||||
"category": "carved_eye",
|
||||
"tags": "greek, peaceful, left, archaic, almond-shaped, subtle",
|
||||
"style": "greek_archaic",
|
||||
"emotion": "peaceful",
|
||||
"side": "left",
|
||||
"source_url": "https://...",
|
||||
"source": "Getty Museum - Public Domain"
|
||||
},
|
||||
{
|
||||
"name": "Roman Portrait - Wise Right Eye",
|
||||
"description": "Late Roman period, detailed eyelids, slight downward gaze, wisdom and age.",
|
||||
"category": "carved_eye",
|
||||
"tags": "roman, wise, right, portrait, detailed, aged",
|
||||
"style": "roman_portrait",
|
||||
"emotion": "wise",
|
||||
"side": "right",
|
||||
"source_url": "https://...",
|
||||
"source": "British Museum - CC0"
|
||||
},
|
||||
]
|
||||
|
||||
async def download_image(url: str) -> bytes:
|
||||
"""Download image from URL"""
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
response.raise_for_status()
|
||||
return response.content
|
||||
|
||||
async def crop_eye_from_statue(image_bytes: bytes, crop_box: tuple) -> bytes:
|
||||
"""
|
||||
Crop just the eye from a full statue photo
|
||||
|
||||
Args:
|
||||
image_bytes: Full statue image
|
||||
crop_box: (left, top, right, bottom) coordinates
|
||||
|
||||
Returns:
|
||||
Cropped eye image bytes
|
||||
"""
|
||||
img = Image.open(BytesIO(image_bytes))
|
||||
eye = img.crop(crop_box)
|
||||
|
||||
# Save as PNG
|
||||
buffer = BytesIO()
|
||||
eye.save(buffer, format='PNG')
|
||||
return buffer.getvalue()
|
||||
|
||||
async def seed_eye_catalog():
|
||||
"""
|
||||
Seed the patch library with classical carved eyes
|
||||
|
||||
NOTE: This is a template. You need to:
|
||||
1. Get actual public domain image URLs
|
||||
2. Manually crop the eyes (or provide crop coordinates)
|
||||
3. Run this to populate the catalog
|
||||
"""
|
||||
|
||||
from app.database import SessionLocal
|
||||
from app.models.patch import Patch
|
||||
from app.services.patch_library import PatchLibraryService
|
||||
|
||||
db = SessionLocal()
|
||||
patch_service = PatchLibraryService()
|
||||
|
||||
print("Seeding eye catalog with classical carved eyes...")
|
||||
|
||||
for eye_data in CLASSICAL_EYES:
|
||||
print(f"\nAdding: {eye_data['name']}")
|
||||
|
||||
# Create patch record
|
||||
patch = Patch(
|
||||
name=eye_data['name'],
|
||||
description=eye_data['description'],
|
||||
source_type="imported",
|
||||
category=eye_data['category'],
|
||||
tags=eye_data['tags'],
|
||||
width=0, # Will be set after image save
|
||||
height=0,
|
||||
user_id=None,
|
||||
file_path=""
|
||||
)
|
||||
|
||||
db.add(patch)
|
||||
db.commit()
|
||||
db.refresh(patch)
|
||||
|
||||
# Download and save image
|
||||
# NOTE: You need to manually download/crop these first
|
||||
# This is just the structure
|
||||
|
||||
try:
|
||||
# image_bytes = await download_image(eye_data['source_url'])
|
||||
# cropped_eye = await crop_eye_from_statue(image_bytes, crop_box)
|
||||
|
||||
# For now, you would manually place images in:
|
||||
# ./seed_data/eyes/greek_serene_left.png
|
||||
# ./seed_data/eyes/roman_fierce_right.png
|
||||
# etc.
|
||||
|
||||
seed_image_path = Path(__file__).parent / "seed_data" / "eyes" / f"{eye_data['style']}_{eye_data['emotion']}_{eye_data['side']}.png"
|
||||
|
||||
if seed_image_path.exists():
|
||||
file_path = patch_service.save_patch_from_file(
|
||||
patch.id,
|
||||
str(seed_image_path),
|
||||
create_thumb=True
|
||||
)
|
||||
|
||||
# Get dimensions
|
||||
width, height = patch_service.get_patch_size(patch.id)
|
||||
patch.width = width
|
||||
patch.height = height
|
||||
patch.file_path = file_path
|
||||
patch.thumbnail_path = str(patch_service.get_thumbnail_path(patch.id))
|
||||
|
||||
db.commit()
|
||||
print(f"✅ Added {eye_data['name']}")
|
||||
else:
|
||||
print(f"⚠️ Image not found: {seed_image_path}")
|
||||
print(f" Please download and crop eye from: {eye_data['source']}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error adding {eye_data['name']}: {e}")
|
||||
db.rollback()
|
||||
|
||||
db.close()
|
||||
print("\n✅ Eye catalog seeding complete!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed_eye_catalog())
|
||||
Reference in New Issue
Block a user