Claude-skill-registry image-processor-guidelines
Development guidelines for Quantum Skincare's Python FastAPI image processor microservice. Covers FastAPI patterns, Perfect Corp API integration, MediaPipe FaceMesh validation, correlation headers, access control (CIDR + X-Internal-Secret), error handling, Pydantic models, structured logging, mock mode, provider normalization, and testing strategies. Use when working with image-processor code, routes, validation pipeline, Perfect Corp integration, or Python/FastAPI patterns.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/image-processor-guidelines" ~/.claude/skills/majiayu000-claude-skill-registry-image-processor-guidelines && rm -rf "$T"
skills/data/image-processor-guidelines/SKILL.mdImage Processor Guidelines - Quantum Skincare
Purpose
Quick reference for Quantum Skincare's Python FastAPI image processor microservice, emphasizing FastAPI patterns, Perfect Corp API integration, MediaPipe FaceMesh validation, and structured error handling.
When to Use This Skill
- Creating or modifying image processor routes
- Working with Perfect Corp API integration
- Implementing face validation logic
- Adding validation pipeline checks
- Configuring access control and security
- Handling correlation headers (X-Request-Id, X-Analysis-Session, X-Frame-Seq)
- Writing Pydantic models
- Implementing error handling
- Adding structured logging
- Working with mock mode
- Testing image processor endpoints
- Python/FastAPI best practices
Quick Start
New Route Checklist
- Route under
prefix (NEVER unversioned)/v1 - Use
for file uploadsDepends(ensure_valid_upload) - Add correlation headers support (X-Request-Id, X-Analysis-Session, X-Frame-Seq)
- Implement proper error handling with
orAppError_http_exc() - Use Pydantic models with
response_model_exclude_none=True - Add structured logging with
contextrequest_id - Test with both mock and real modes
- Verify access control (CIDR + X-Internal-Secret)
- Document in docstring
New Validation Check Checklist
- Add to validation pipeline (
)validation/pipeline.py - Return structured result with
,success
,reasondetails - Add to diagnosis response
- Test with various failure cases
- Document thresholds and behavior
Service Architecture
Tech Stack
- Framework: FastAPI (ASGI via uvicorn)
- Models: Pydantic v2 with
BaseModel - Logging: Structured JSON logging with contextual
, route, latencyrequestId - Face Detection: MediaPipe FaceMesh with concurrency gate
- Provider: Perfect Corp API with mock/real modes
- Image Processing: PIL (Pillow) for JPEG normalization and resize
- Access Control: CIDR allowlist +
headerX-Internal-Secret - Deployment: Dockerized with
and/livez
probes/readyz
Directory Structure
apps/image-processor/src/ ├── app_http/ │ ├── routes/ # API route handlers │ │ ├── analyze.py # POST /v1/perfect-corp/analyze │ │ ├── validate.py # POST /v1/validate/face │ │ └── health.py # GET /livez, /readyz │ ├── middleware_access.py # CIDR + secret validation │ ├── middleware_request_id.py # X-Request-Id propagation │ ├── headers.py # Header extraction utilities │ └── upload_utils.py # File upload validation ├── config/ │ └── settings.py # Pydantic settings (env vars) ├── providers/ │ ├── perfect_corp/ # Perfect Corp API client │ │ ├── client.py # Main API client │ │ ├── auth.py # RSA token authentication │ │ ├── files.py # File upload │ │ ├── tasks.py # Task polling │ │ ├── normalizer.py # Result normalization │ │ └── http_client.py # HTTP client with retries │ └── storage/ # S3 uploader (optional) ├── validation/ │ ├── pipeline.py # Main validation pipeline │ ├── mesh_runtime.py # FaceMesh singleton │ ├── geometry.py # Pose, ratio, centering │ ├── lighting.py # Lighting analysis │ └── serialization.py # Mesh data serialization ├── main.py # FastAPI app factory ├── entrypoint.py # Uvicorn entrypoint ├── errors.py # Error models and handlers ├── schemas.py # Pydantic response models ├── perfect_corp_types.py # Provider type definitions └── logging_setup.py # Logging configuration
Key Endpoints
POST /v1/perfect-corp/analyze
Full skin analysis via Perfect Corp API:
@router_v1.post( "/perfect-corp/analyze", response_model=PerfectCorpAnalysisResponse, response_model_exclude_none=True, ) async def analyze_skin( request: Request, file: UploadFile = Depends(ensure_valid_upload), ): """ Upload image for full skin analysis. Returns: { success, data: { analysisId, skinType, skinAge, programCode, ... }, meta } """ # 1. Extract correlation headers request_id = request.state.request_id session_id = extract_analysis_session_id(request) # 2. Process image (resize to 1024px width JPEG) image_bytes = process_image_for_provider(file) # 3. Call Perfect Corp API or return mock data result = await client.analyze(image_bytes) # 4. Attach correlation under `meta` return PerfectCorpAnalysisResponse( success=True, data=result, meta=CorrelationMeta( requestId=request_id, analysisSessionId=session_id ) )
Contract:
- Correlation in
(not top-level)meta - Resize to 1024px width, max height 1920, min width 480
- Re-encode if > 10MB after resize
POST /v1/validate/face
Face detection and validation:
@router_v1.post( "/validate/face", response_model=ValidateFaceResponse, response_model_exclude_none=True, ) async def validate_face( request: Request, file: UploadFile = Depends(ensure_valid_upload), yaw_deg: float = Query(...), pitch_deg: float = Query(...), # ... other thresholds include_mesh: bool = Query(False), ): """ Validate face geometry and lighting. Returns: { ok, reason?, diagnosis, mesh?, requestId, analysisSessionId } """ # 1. Extract correlation headers request_id = request.state.request_id session_id = extract_analysis_session_id(request) # 2. Downscale to 512px for performance image = downscale_image(file, max_size=512) # 3. Run validation pipeline result = await validate_face_pipeline(image, thresholds) # 4. Attach correlation at TOP LEVEL (not meta) return ValidateFaceResponse( ok=result.ok, reason=result.reason, diagnosis=result.diagnosis, mesh=result.mesh if include_mesh else None, requestId=request_id, analysisSessionId=session_id, )
Contract:
- Correlation at TOP LEVEL (different from analyze endpoint)
- Downscale to 512px max
- All 8 threshold params required
GET /livez, /readyz
Health probes:
@router.get("/livez") async def liveness(): """Always returns 200 if process is running.""" return {"status": "ok"} @router.get("/readyz") async def readiness(): """Returns 200 after FaceMesh warmup, else 503.""" if not is_ready(): raise _http_exc(503, "SERVICE_UNAVAILABLE", "Service not ready") return {"status": "ready"}
Core Patterns
1. Correlation Headers
Extract and propagate correlation headers:
from app_http.headers import ( extract_request_id, extract_analysis_session_id, extract_frame_seq, ) # In route handler request_id = request.state.request_id # Set by middleware session_id = extract_analysis_session_id(request) frame_seq = extract_frame_seq(request) # Log with correlation logger.info({ "event": "processing_image", "requestId": request_id, "analysisSessionId": session_id, "frameSeq": frame_seq, }) # Return in response (placement depends on endpoint) # For /v1/validate/face: top-level return ValidateFaceResponse( ok=True, requestId=request_id, analysisSessionId=session_id, ) # For /v1/perfect-corp/analyze: under meta return PerfectCorpAnalysisResponse( success=True, data=result, meta=CorrelationMeta( requestId=request_id, analysisSessionId=session_id, ) )
2. Error Handling
Use standardized error models:
from errors import AppError, _http_exc, ERROR_CODES # Option 1: Raise HTTPException with error envelope if not valid_format: raise _http_exc( status_code=400, error_code="VALIDATION_ERROR", message="Invalid image format", request_id=request_id, frame_seq=frame_seq, ) # Option 2: Raise AppError (will be caught by error handler) if timeout: raise AppError( error_code="TIMEOUT_ERROR", message="Request timed out", status_code=408, request_id=request_id, ) # All errors return: # { "detail": { "errorCode": "...", "message": "...", "requestId": "...", "frameSeq": "..." } }
Common error codes:
(400)VALIDATION_ERROR
(401)UNAUTHORIZED
(403)FORBIDDEN
(408)TIMEOUT_ERROR
(500)IMAGE_PROCESSING_ERROR
(500)SKIN_ANALYSIS_FAILED
(503)SERVICE_UNAVAILABLE
3. Access Control
Middleware validates CIDR and shared secret:
# Automatic via middleware_access.py # Validates: # 1. Client IP against IMAGE_PROCESSOR_ALLOWED_CIDRS # 2. X-Internal-Secret header matches IMAGE_PROCESSOR_SHARED_SECRET # In production, both are required # In dev/test with mock mode, can be relaxed
Configuration:
IMAGE_PROCESSOR_ALLOWED_CIDRS="10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32" IMAGE_PROCESSOR_SHARED_SECRET="your-secret-here"
4. Pydantic Models
Define response models with proper nesting:
from pydantic import BaseModel, Field from typing import Optional class DiagnosisData(BaseModel): pose: PoseResult faceRatio: FaceRatioResult centering: CenteringResult lighting: LightingResult class ValidateFaceResponse(BaseModel): ok: bool reason: Optional[str] = None diagnosis: DiagnosisData mesh: Optional[MeshData] = None # Correlation at top-level for this endpoint requestId: Optional[str] = Field(None, alias="requestId") analysisSessionId: Optional[str] = Field(None, alias="analysisSessionId") # Use with response_model_exclude_none=True @router.post("/validate/face", response_model=ValidateFaceResponse, response_model_exclude_none=True)
5. Structured Logging
Use structured JSON logging with context:
import logging logger = logging.getLogger("image_processor.routes.validate") # Always include requestId from request.state logger.info({ "event": "validation_started", "requestId": request.state.request_id, "analysisSessionId": session_id, "frameSeq": frame_seq, "imageSize": len(image_bytes), }) # On error logger.error({ "event": "validation_failed", "requestId": request.state.request_id, "error": str(e), "errorType": type(e).__name__, }) # Never log secrets settings = get_settings() logger.info({ "event": "config_loaded", "mockMode": settings.image_processor_use_mock, # ❌ Don't log: settings.image_processor_shared_secret })
6. Mock Mode
Support mock mode for development:
from config.settings import get_settings settings = get_settings() if settings.image_processor_use_mock: # Return normalized mock data with open(settings.perfect_corp_mock_score_info_path) as f: mock_data = json.load(f) return normalize_provider_response(mock_data) else: # Call real Perfect Corp API result = await client.analyze(image_bytes) return result
Configuration:
IMAGE_PROCESSOR_USE_MOCK=true PERFECT_CORP_MOCK_SCORE_INFO_PATH=score_info.json
7. Image Processing
Resize and normalize images:
from PIL import Image import io def process_image_for_provider(file: UploadFile) -> bytes: """ Resize to 1024px width JPEG, cap height at 1920, min width 480. Re-encode if > 10MB. """ image = Image.open(file.file) # Resize to 1024px width, maintain aspect ratio width, height = image.size if width > 1024: ratio = 1024 / width new_height = int(height * ratio) # Cap height at 1920 if new_height > 1920: ratio = 1920 / height new_height = 1920 width = int(width * ratio) image = image.resize((1024, new_height), Image.Resampling.LANCZOS) # Convert to JPEG buffer = io.BytesIO() image.save(buffer, format="JPEG", quality=95) image_bytes = buffer.getvalue() # Re-encode if > 10MB if len(image_bytes) > 10 * 1024 * 1024: buffer = io.BytesIO() image.save(buffer, format="JPEG", quality=85) image_bytes = buffer.getvalue() return image_bytes
8. Validation Pipeline
Run face validation checks:
from validation.pipeline import validate_face_pipeline from validation.mesh_runtime import get_face_mesh async def validate_face(image: Image.Image, thresholds: dict) -> dict: """ 1. Downscale to 512px 2. MediaPipe FaceMesh detection 3. Pose estimation (PnP solver) 4. Face ratio check 5. Centering validation 6. Lighting analysis 7. Side-lighting gating """ # Downscale for performance image = downscale_to_max(image, 512) # Run FaceMesh (with concurrency gate) mesh = get_face_mesh(static_mode=True) results = mesh.process(np.array(image)) if not results.multi_face_landmarks: return {"ok": False, "reason": "NO_FACE_DETECTED"} # Run validation checks pose_result = check_pose(results, thresholds) ratio_result = check_face_ratio(results, image.size, thresholds) centering_result = check_centering(results, image.size, thresholds) lighting_result = check_lighting(image, thresholds) # Overall success ok = all([ pose_result["success"], ratio_result["success"], centering_result["success"], lighting_result["success"], ]) return { "ok": ok, "diagnosis": { "pose": pose_result, "faceRatio": ratio_result, "centering": centering_result, "lighting": lighting_result, } }
Perfect Corp Integration
Authentication Flow
# 1. Generate RSA-encrypted token token = generate_rsa_token(api_key, secret_key, timestamp) # 2. Cache token and refresh on 401 if response.status_code == 401: token = generate_rsa_token(api_key, secret_key, time.time()) retry_request()
Full Analysis Flow
# 1. Create file file_id = await client.create_file(image_bytes, metadata) # 2. Run task (all 14 conditions by default) task_id = await client.run_task(file_id) # 3. Poll status (immediate + retry with backoff) status = await client.poll_task_status(task_id) # 4. Download ZIP and extract score_info.json result = await client.download_and_parse_results(task_id) # 5. Normalize vendor keys to internal format normalized = normalize_provider_response(result) # Program code: (acne_decile - 1) + ((wrinkle_decile - 1) * 10) + 1 # Severity: ≥95 none, ≥80 mild, ≥60 moderate, <60 severe
Configuration & Environment
All config via
config/settings.py using Pydantic BaseSettings:
from pydantic_settings import BaseSettings from pydantic import SecretStr class Settings(BaseSettings): # Environment image_processor_env: str = "dev" # Mock mode image_processor_use_mock: bool = False perfect_corp_mock_score_info_path: str = "score_info.json" # Perfect Corp API perfect_corp_api_url: str perfect_corp_api_key: str perfect_corp_api_secret_key: SecretStr # Access control image_processor_shared_secret: SecretStr | None = None image_processor_allowed_cidrs: str = "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32" # Face Mesh face_mesh_concurrency: int = 2 face_mesh_refine: bool = False class Config: env_file = ".env"
Testing Strategy
Unit Tests
# Test upload validation def test_upload_validation(): assert validate_file_size(10 * 1024 * 1024) == True # 10MB OK assert validate_file_size(11 * 1024 * 1024) == False # > 10MB # Test normalization def test_normalize_provider_response(): result = normalize_provider_response(mock_score_info) assert result["programCode"] == expected_code assert result["conditions"]["acne"]["severity"] == "mild"
Integration Tests
# Test mock mode async def test_analyze_mock_mode(): response = await client.post("/v1/perfect-corp/analyze", files={"file": image}) assert response.status_code == 200 assert response.json()["success"] == True # Test access control async def test_access_control_missing_secret(): response = await client.post("/v1/validate/face", files={"file": image}) assert response.status_code == 401
Common Contracts (DO NOT BREAK)
- Versioning: All new routes under
only/v1 - Correlation placement:
: TOP LEVEL (/v1/validate/face
,requestId
)analysisSessionId
: Under/v1/perfect-corp/analyzemeta
- Error envelope:
{ "detail": { "errorCode", "message", "requestId?", "frameSeq?" } } - Upload limits: 10MB max, JPEG/PNG only
- Shared secret: Mandatory in production
- Validation params: All 8 threshold params required for
/v1/validate/face - Image resize: 1024px width for analyze, 512px max for validate
Reference Files
For detailed information:
- Comprehensive docs:
apps/image-processor/README.md - Routes:
app_http/routes/*.py - Provider:
providers/perfect_corp/*.py - Validation:
validation/*.py - Cursor rules:
(may be outdated).cursor/rules/image-processor.mdc - CLAUDE.md: Image Processor Service section
Related Skills
- backend-dev-guidelines - Backend integration patterns
- frontend-dev-guidelines - Frontend camera integration
Skill Status: Created for Quantum Skincare ✅ Stack: Python 3.11+, FastAPI, Pydantic v2, MediaPipe, PIL Provider: Perfect Corp API with mock mode support Line Count: Under 500 lines (following Anthropic best practices) ✅