Spaces:
Paused
Paused
import io | |
from PIL import Image | |
import base64 | |
import os | |
import uuid | |
from typing import List | |
from fastapi import FastAPI, APIRouter, HTTPException | |
from inference import Inference | |
import uvicorn | |
import logging | |
from typing import Optional | |
from types_io import ClassificationRequest, ImageData | |
logging.basicConfig( | |
level=logging.INFO, | |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
) | |
log = logging.getLogger(__name__) | |
def decode_base64_image(base64_str: str) -> Optional[Image.Image]: | |
""" | |
Decode a base64 encoded string into a PIL Image object. | |
Args: | |
base64_str (str): Base64 encoded image string | |
Returns: | |
Optional[Image.Image]: PIL Image object if successful, None if decoding fails | |
Raises: | |
Exception: Logged and caught internally, returns None on any error | |
""" | |
try: | |
image_data = base64.b64decode(base64_str) | |
image = Image.open(io.BytesIO(image_data)) | |
return image | |
except Exception as e: | |
log.error(f"Error processing image: {str(e)}") | |
return None | |
def save_images_to_disk(images: List[Image.Image], output_dir: str = "temp_images") -> List[str]: | |
""" | |
Save PIL Image objects to disk and return their file paths. | |
Args: | |
images (List[Image.Image]): List of PIL Image objects to save | |
output_dir (str): Directory where images will be saved (default: "temp_images") | |
Returns: | |
List[str]: List of file paths where images were saved | |
Raises: | |
Exception: If there's an error saving images to disk | |
""" | |
try: | |
# Create output directory if it doesn't exist | |
os.makedirs(output_dir, exist_ok=True) | |
saved_paths = [] | |
for i, image in enumerate(images): | |
if image is None: | |
log.warning(f"Skipping None image at index {i}") | |
continue | |
# Generate unique filename | |
filename = f"image_{uuid.uuid4().hex}.png" | |
file_path = os.path.join(output_dir, filename) | |
# Save image to disk | |
image.save(file_path, "PNG") | |
saved_paths.append(file_path) | |
log.info(f"Saved image to: {file_path}") | |
return saved_paths | |
except Exception as e: | |
log.error(f"Error saving images to disk: {str(e)}") | |
raise | |
app = FastAPI(title="Kimi Service", version="1.5.0") | |
inference = Inference() | |
router = APIRouter() | |
async def home(): | |
return {"message": "Welcome to Kimi Service!"} | |
async def classify(request: ClassificationRequest): | |
try: | |
log.info(f"Processing {len(request.images)} images") | |
# Decode images from base64 or load from file paths | |
images = [] | |
for img_str in request.images: | |
img = decode_base64_image(img_str) | |
images.append(img) | |
log.info(f"Decoded {len(images)} images successfully") | |
# Save images and get their paths using a helper method | |
output_dir = os.environ.get("IMAGE_OUTPUT_DIR", "/tmp/temp_images") | |
saved_image_paths = save_images_to_disk(images, output_dir) | |
# Send images to inference | |
res = inference.classify_building(images, saved_image_paths) | |
if res is None: | |
raise HTTPException(status_code=500, detail="Classification failed") | |
return res | |
except ValueError as ve: | |
log.error(f"Validation error: {str(ve)}") | |
raise HTTPException(status_code=400, detail=str(ve)) | |
except Exception as e: | |
log.error(f"Error during classification: {str(e)}") | |
raise HTTPException(status_code=500, detail=str(e)) | |
app.include_router(router) | |
if __name__ == "__main__": | |
uvicorn.run("app:app", reload=True, port=7860, host="0.0.0.0") | |