$ cat node-template.py

Image Vision

// Analyzes images using Florence-2 computer vision via a native GPU service. Generates detailed captions, OCR text extraction, and image descriptions. Output is text that can be connected to prompt inputs of other nodes.

Process
Image
template.py
1import os2import sys3import json4import subprocess5import time6import traceback78try:9    import requests10except ImportError:11    subprocess.check_call([sys.executable, "-m", "pip", "install", "requests"])12    import requests1314NATIVE_IMAGE_VISION_SERVICE_URL = os.getenv(15    "NATIVE_IMAGE_VISION_SERVICE_URL", "http://native-image-vision-service:8104"16)17_EMBLEMA_VERSION = os.getenv("EMBLEMA_VERSION", "dev")18NATIVE_IMAGE_VISION_SERVICE_IMAGE = os.getenv(19    "NATIVE_IMAGE_VISION_SERVICE_IMAGE",20    f"emblema/native-image-vision-service:{_EMBLEMA_VERSION}",21)22HF_CACHE_HOST_PATH = os.getenv("HF_CACHE_HOST_PATH", "/root/.cache/huggingface")23CONTAINER_NAME = "native-image-vision-service"24INPUT_DIR = "/data/input"252627def start_container():28    """Create and start native-image-vision-service, removing any stale container first."""29    subprocess.run(30        ["docker", "rm", "-f", CONTAINER_NAME],31        capture_output=True, text=True32    )3334    hf_token = os.getenv("HUGGINGFACE_TOKEN", "")35    print(f"Creating container {CONTAINER_NAME}...", file=sys.stderr)36    run_cmd = [37        "docker", "run", "-d",38        "--name", CONTAINER_NAME,39        "--network", "emblema",40        "--gpus", "all",41        "-e", "PORT=8104",42        "-e", "DEVICE=cuda",43        "-e", f"HF_TOKEN={hf_token}",44        "-v", f"{HF_CACHE_HOST_PATH}:/root/.cache/huggingface",45        NATIVE_IMAGE_VISION_SERVICE_IMAGE,46    ]47    result = subprocess.run(run_cmd, capture_output=True, text=True)48    if result.returncode != 0:49        print(f"docker run failed (exit {result.returncode}): {result.stderr}", file=sys.stderr)50        raise RuntimeError(f"Failed to start container: {result.stderr}")5152    # Poll health endpoint53    timeout = 18054    interval = 355    elapsed = 056    health_url = f"{NATIVE_IMAGE_VISION_SERVICE_URL}/health"57    while elapsed < timeout:58        try:59            r = requests.get(health_url, timeout=5)60            if r.status_code == 200:61                print(f"Container healthy (waited {elapsed}s).", file=sys.stderr)62                return63        except requests.ConnectionError:64            pass65        time.sleep(interval)66        elapsed += interval6768    raise RuntimeError(f"Container did not become healthy within {timeout}s")697071def stop_container():72    """Remove the container."""73    try:74        subprocess.run(75            ["docker", "rm", "-f", CONTAINER_NAME],76            capture_output=True, text=True, timeout=3077        )78        print(f"Container {CONTAINER_NAME} removed.", file=sys.stderr)79    except Exception as e:80        print(f"Warning: failed to remove container: {e}", file=sys.stderr)818283def main():84    try:85        input_json = sys.stdin.read()86        execution_input = json.loads(input_json)87        inputs = execution_input.get("inputs", {})8889        image = inputs.get("image", "")90        task = inputs.get("task", "more_detailed_caption")9192        if not image:93            raise ValueError("Image is required")9495        valid_tasks = [96            "caption", "detailed_caption", "more_detailed_caption",97            "ocr", "ocr_with_region", "object_detection", "dense_region_caption",98        ]99        if task not in valid_tasks:100            raise ValueError(f"Invalid task '{task}'. Must be one of: {valid_tasks}")101102        local_path = os.path.join(INPUT_DIR, image)103        if not os.path.exists(local_path):104            raise FileNotFoundError(f"Input image not found: {local_path}")105106        # Start the container107        start_container()108109        try:110            # Send image to service111            with open(local_path, "rb") as f:112                resp = requests.post(113                    f"{NATIVE_IMAGE_VISION_SERVICE_URL}/analyze",114                    files={"image": (os.path.basename(local_path), f, "image/png")},115                    data={"task": task},116                    timeout=300,117                )118119            if resp.status_code != 200:120                try:121                    error_detail = resp.json()122                except Exception:123                    error_detail = resp.text124                raise RuntimeError(125                    f"Vision service returned {resp.status_code}: {error_detail}"126                )127128            result_data = resp.json()129            result_text = result_data.get("text", "")130131            print(f"Vision analysis complete: task={task}", file=sys.stderr)132133            # Flat output — keys match OUTPUT_SCHEMA134            output = {135                "text": result_text,136            }137            print(json.dumps(output, indent=2))138139        finally:140            stop_container()141142    except Exception as e:143        error_output = {144            "error": str(e),145            "errorType": type(e).__name__,146            "traceback": traceback.format_exc(),147        }148        print(json.dumps(error_output), file=sys.stderr)149        sys.exit(1)150151152if __name__ == "__main__":153    main()