Skip to main content

This package contains MCP tools for image generation. So Fuuga (or other agents) can gain image generation capabilities via other (Stable Diffusion) models from for example HuggingFace.

Total Downloads
1.4K
1,425
Published
Oct 22, 2026
8d ago

Get Started

dotnet add package Fuuga.Image.cuda-linux --version 1.0.44
ProductVersions
.NET
net10.0

README

Fuuga.Image

This is image generation MCP client for Fuuga (or other agents) to make the agent capable of producing images via other (Stable Diffusion) models from for example HuggingFace.

Standalone CLI tool for image generation and captioning, built on stable-diffusion.cpp and Phi-3.5-vision.

CPU vs CUDA

The compute backend is decided by which package you install, not by a runtime flag:

  • Fuuga.Image (NuGet) β€” CUDA natives (CUDA 12.8, win-x64; needs an NVIDIA GPU).
  • Fuuga.Image.cuda-linux (NuGet) β€” the same for Linux x64 (GPU rentals and servers).
  • Fuuga.Image.cpu (NuGet) β€” CPU natives, runs anywhere.

Building from source, the TorchBackend MSBuild property makes the same choice: dotnet build -p:TorchBackend=cuda for Windows CUDA, -p:TorchBackend=cuda-linux for Linux CUDA, plain dotnet build for the CPU default. The main library follows the identical split (Fuuga / Fuuga.cuda-linux / Fuuga.cpu).

macOS (Apple Silicon): Fuuga.Image.cpu is the package to use, and it is partial. libtorch ships osx-arm64 CPU natives, so the from-scratch SD pipeline (scratch-sd-demo.fsx, the scratch subcommand) and captioning (ONNX Runtime GenAI has osx-arm64) run, at CPU speed β€” there is no Metal/MPS backend in TorchSharp. The stable-diffusion.cpp backend behind txt2img / img2img / video ships no osx-arm64 natives and StableDiffusion.NET publishes no Metal backend, so those commands fail to load their native library on a Mac. Intel Macs are not covered by libtorch 2.10 at all.

This applies to every subcommand: diffusion generation (stable-diffusion.cpp natives), captioning (ONNX Runtime GenAI natives), and the from-scratch pipeline (libtorch natives) all use CUDA in the CUDA package when a GPU is present, and CPU otherwise β€” no flags involved.

Commands

fuuga-image txt2img     Generate an image from a text prompt
fuuga-image img2img     Transform an image guided by a text prompt
fuuga-image txt2video   Generate a video from a text prompt (video models only)
fuuga-image img2video   Animate a still image into a video (video models only)
fuuga-image vid2vid     Edit/continue a video guided by text (VACE; video models only)
fuuga-image caption     Generate a text description of an image
fuuga-image items       Caption a folder of images/videos/audio into decide items (items.jsonl)
fuuga-image scratch     Generate with the from-scratch SD 1.5 implementation (see below)
fuuga-image info        Display model metadata

Quick Start

dotnet build

# Text-to-image
dotnet run -- txt2img --model path/to/model.ckpt --prompt "a cat sitting on a windowsill" --steps 20

# Image-to-image
dotnet run -- img2img --model path/to/model.ckpt --source input.png --prompt "in watercolor style" --strength 0.75

# Text-to-video (requires a video model, e.g. a Wan2.1/2.2 T2V GGUF)
dotnet run -- txt2video --model path/to/wan-t2v.gguf --prompt "a wave crashing on rocks" --frames 16 --fps 8 --format mp4

# Image-to-video (animate a still)
dotnet run -- img2video --model path/to/wan-i2v.gguf --source photo.png --prompt "gentle zoom" --frames 24 --format gif

# Video-to-video (edit/continue a video guided by text; --source is a video file or a frames dir)
dotnet run -- vid2vid --model path/to/wan-vace.gguf --source clip.mp4 --prompt "make it snowy" --format mp4

# Attach an audio track to a generated mp4 (models don't make audio; this muxes a provided file)
dotnet run -- txt2video --model path/to/wan-t2v.gguf --prompt "a marching band" --format mp4 --audio track.mp3

# Caption an image
dotnet run -- caption --model path/to/vision-model/ --image photo.jpg --detail standard

# Caption a video (samples frames and describes them; --video is a file or a frames dir)
dotnet run -- caption --model path/to/vision-model/ --video clip.mp4 --frame-samples 8 --detail detailed

# Transcribe/describe audio (needs an audio-capable model, e.g. Phi-4-multimodal β€” same runtime)
dotnet run -- caption --model path/to/phi-4-multimodal/ --audio speech.wav

# Caption every image, video and audio file under a folder into typed-decision items
# ({id: path, state: caption} per line) for `fuuga decide predict|label`; a rerun resumes
dotnet run -- items --model path/to/vision-model/ --input photos/ --items items.jsonl

# Model info
dotnet run -- info --model path/to/model.ckpt

Common Options

Option Description Default
--model <path> Path to model file or directory (required)
--output <dir> Output directory output
--format <fmt> Output format: png or jpeg png
--verbose Enable detailed logging off

txt2img Options

Option Description Default
--prompt <text> Text description of desired image (required)
--negative <text> What to avoid in the image none
--width <int> Image width in pixels (multiple of 8) 512
--height <int> Image height in pixels (multiple of 8) 512
--steps <int> Denoising steps 20
--cfg <float> Classifier-free guidance scale 7.0
--seed <int> Random seed (-1 for random) -1
--sampler <name> euler, euler_a, heun, dpm2, dpm++2s, lcm euler_a
--batch <int> Candidate images to generate on ONE model load, each with a distinct reported seed 1
--threads <int> Native compute threads (physical core count is usually fastest) logical cores
--vae <path> External VAE model none
--lora <path> LoRA weights none
--quantization <q> native (keep the file's own type), f32, f16, q8_0, q5_1, q4_0 native

Pick-the-best flow: generate cheap candidates, then refine the winner β€” generation is seed-deterministic, so the reported seed is all you need:

fuuga-image txt2img --model sd15.gguf --prompt "..." --batch 4 --steps 12   # 4 quick candidates
fuuga-image txt2img --model sd15.gguf --prompt "..." --seed <best> --steps 30  # refine the pick

img2img Options

Same as txt2img, plus:

Option Description Default
--source <path> Source image to transform (required)
--strength <float> Denoising strength 0.0-1.0 0.75

(--batch is txt2img-only; --quantization and --threads work here too.)

caption Options

Captioning uses whatever multimodal model directory you pass to --model. Provide one of --image (a still), --video (a video file decoded via ffmpeg, or a directory of frames, sampled to --frame-samples), or --audio (transcribe/describe an audio clip). Audio needs an audio-capable model such as Phi-4-multimodal β€” it runs on the same ONNX-Runtime-GenAI runtime already bundled (no extra dependency), you just point --model at it.

Option Description Default
--image <path> Image to caption (one of image/video/audio)
--video <path> Video file or frames directory to describe (one of image/video/audio)
--audio <path> Audio file to transcribe/describe (audio-capable model) (one of image/video/audio)
--frame-samples <N> Frames sampled from the video 8
--detail <level> brief, standard, detailed standard
--max-tokens <int> Maximum tokens to generate 256
--prompt <text> Custom prompt for image captioning (must contain <|image_1|>) none

txt2video / img2video / vid2vid Options

Video is generated only when the loaded model supports it (e.g. Wan2.1/2.2 text-to-video, image-to-video, or VACE GGUF models); the tool checks the model's capability and errors clearly otherwise. img2video additionally takes --source <path> (the starting image); vid2vid takes --source <path> (a video file or a directory of frame images) plus --vace-strength <f>.

Video models produce no audio. --audio <path> muxes a user-provided audio file into an mp4/webp output via ffmpeg (ignored for gif / image sequences).

Option Description Default
--frames <int> Number of frames to generate 16
--fps <int> Frames per second of the output 8
--width / --height Frame size 512
--steps / --cfg / --seed / --sampler Same as txt2img
--quantization <q> native (keep the file's own type; none is an alias), f32, f16, q8_0, q5_1, q4_0 native
--threads <int> Native compute threads (physical core count is usually fastest) logical cores
--format <fmt> png (image sequence, default), mp4, gif, webp png

Output formats. png writes an image sequence (frame_0000.png …) into a per-run subdirectory β€” this is dependency-free and always available. mp4, gif, and webp are encoded from those frames with ffmpeg, which is an optional external tool (see below). If a single-file format is requested but ffmpeg is not found, the tool falls back to an image sequence and prints a note.

Optional: ffmpeg for mp4 / gif / webp

ffmpeg is not a .NET dependency and is not bundled. To enable single-file video output, install it and make sure it is on PATH (or point FUUGA_FFMPEG at the executable):

  • Windows: winget install Gyan.FFmpeg (or choco install ffmpeg), or download from https://ffmpeg.org/download.html and add the bin folder to PATH.
  • macOS: brew install ffmpeg
  • Linux: sudo apt install ffmpeg (Debian/Ubuntu) or your distro's package manager.
  • Explicit path: set FUUGA_FFMPEG=/full/path/to/ffmpeg to use a specific build.

Without ffmpeg, --format png (image sequence) still works and can be turned into a video later with any tool.

From-Scratch Stable Diffusion (scratch)

The scratch subcommand runs a complete Stable Diffusion 1.5 implementation written from first principles in F# on TorchSharp β€” no stable-diffusion.cpp, no Python. It implements the CLIP text encoder, the VAE encoder/decoder, the latent-diffusion UNet (with self/cross attention), CLIP's BPE tokenizer, and DDIM/DDPM samplers with classifier-free guidance, and loads the standard SD v1.x single-file safetensors checkpoint directly. It exists to be read, hacked, and extended (in the spirit of "stable diffusion from scratch" tutorials) β€” use txt2img/img2img for fast production generation.

You need two downloads:

  1. The checkpoint β€” an SD v1.x single-file .safetensors, e.g. v1-5-pruned-emaonly-fp16.safetensors (2.1 GB; the fp32 v1-5-pruned-emaonly.safetensors also works).
  2. The CLIP tokenizer tables β€” vocab.json + merges.txt from openai/clip-vit-large-patch14 into one directory.
# Text-to-image
dotnet run -- scratch --model models/v1-5-pruned-emaonly-fp16.safetensors \
    --clip-tokenizer models/clip-tokenizer \
    --prompt "a photograph of an astronaut riding a horse" \
    --steps 30 --cfg 7.5 --width 512 --height 512 --seed 42

# Image-to-image
dotnet run -- scratch --model models/v1-5-pruned-emaonly-fp16.safetensors \
    --clip-tokenizer models/clip-tokenizer \
    --prompt "in watercolor style" --image input.png --strength 0.7
Option Description Default
--model <path> SD v1.x checkpoint: single-file .safetensors or a stable-diffusion.cpp .gguf (F32/F16/Q4_0/Q8_0, dequantized on load) β€” the same GGUF txt2img uses works here (required)
--clip-tokenizer <dir> Directory with CLIP vocab.json + merges.txt (required)
--prompt / --negative Prompt / negative prompt ""
--steps <int> DDPM denoising steps 30
--cfg <float> Classifier-free guidance scale 7.5
--seed <int> Random seed 42
--width / --height Output size (multiples of 8) 512
--image <path> img2img source image none (txt2img)
--strength <float> img2img noise strength 0–1 0.8
--sampler <name> ddim (deterministic, converges in ~10–20 steps) or ddpm (ancestral; needs ~30+ steps) ddim
--preview-every <N> Progressive rendering: write an approximate preview PNG every N steps (fast latent→RGB projection, no VAE decode) off

Notes:

  • The device follows the installed backend automatically: a CUDA build/package with a GPU present runs on CUDA, otherwise CPU. CPU generation is slow (the UNet runs in float32); a CUDA build is strongly recommended for full-size images. Small sizes are fine for CPU experiments β€” 64Γ—64 runs in ~10 s and is ideal for pipeline smoke tests; 256Γ—256 takes ~1–2 min.
  • Any width/height that is a multiple of 8 works, including non-square aspect ratios (e.g. --width 128 --height 192). Quality is best at the model's native 512Γ—512; far below it images become abstract, far above it content repeats.
  • Progressive rendering: pass --preview-every N to write intermediate preview PNGs during denoising, or consume ScratchProgress.PreviewPng from the generate callback in library use.
  • Also exposed as the fuuga_image_scratch MCP tool (via --mcp-server) and from the main Fuuga CLI as fuuga image --scratch <prompt> --model <safetensors> --clip-tokenizer <dir>.
  • Library usage from F# scripts: see examples/scratch-sd-demo.fsx.
  • Fine-tuning: ScratchSdPipeline.diffusionTrainingLoss implements the standard epsilon-prediction objective β€” noise VAE-encoded images to a random timestep and MSE-score the UNet's noise prediction. Pair it with torch.optim.AdamW over unet.parameters() to retrain, then export β€” train slow here, serve fast there. RL-style preference tuning (DDPO / reward-weighted) would build on the same loss and is not included.
  • Export: ScratchSdWeights.exportSd15 writes a single-file F16 safetensors; exportSd15Gguf writes a stable-diffusion.cpp-compatible GGUF (F16 or Q8_0, ~1 byte/weight). Both are loadable by fuuga-image txt2img, ComfyUI, A1111, and back into this module (verified: sd.cpp generates from a scratch-exported Q8_0 GGUF). Formats deliberately NOT supported: .ckpt (Python pickle β€” unsafe to parse outside Python), diffusers multi-folder layout (different key naming; convert to single-file with any standard tool), and ONNX (TorchSharp has no graph tracer; ONNX SD serving is a different backend, not an export target).
  • GroupNorm is implemented from primitive ops (ScratchGroupNorm): the fused TorchSharp/libtorch kernel on win-x64 CPU can return garbage in forward and silently zero input-gradients in backward, which would break training entirely.
  • Module names deliberately mirror the checkpoint key layout (model.diffusion_model.*, first_stage_model.*, cond_stage_model.transformer.*), so weight loading is a prefix strip instead of a mapping table β€” and the key names are unit-tested against the real SD 1.5 layout.
  • Source layout: ScratchSdTokenizer.fs (CLIP BPE) β†’ ScratchSdModels.fs (CLIP/VAE/UNet) β†’ ScratchSdWeights.fs (safetensors reader + loader) β†’ ScratchSdPipeline.fs (DDPM sampler + txt2img/img2img pipeline).

MCP Server Mode

Fuuga.Image can run as an MCP tool server over stdio, enabling integration with Fuuga LLM via the MCP protocol:

dotnet run -- --mcp-server

This exposes these tools via JSON-RPC 2.0 over stdin/stdout:

  • fuuga_image_txt2img -- text-to-image generation (supports batch for pick-the-best candidates)
  • fuuga_image_img2img -- image-to-image transformation
  • fuuga_image_txt2video -- text-to-video generation (video models only)
  • fuuga_image_img2video -- image-to-video animation (video models only)
  • fuuga_image_vid2vid -- video editing/continuation via Wan-VACE (video models only)
  • fuuga_image_caption -- image captioning
  • fuuga_image_scratch -- from-scratch TorchSharp Stable Diffusion 1.5 (educational path)
  • fuuga_image_items -- caption a folder of images, videos and audio into decide items (items.jsonl) for fuuga decide predict|label; resumes, and retries failed files on the next call

In server mode the most recently used diffusion and caption models are kept resident between tool calls (reloaded only when the model path or its options change), so repeated generations skip the model-load cost entirely.

See architecture.md Section 18 for the full integration design.

F# Script Demo

An interactive demo script shows how to use Fuuga.Image as an F# library β€” calling the modules directly with record-based configuration, validation, and I/O:

dotnet fsi examples/image-demo.fsx

The script runs without models (dry-run mode), exercising types, validation, image I/O, and prompt templates. Uncomment Section 5 and set your model paths for actual generation. See examples/image-demo.fsx for the full source.

Project Structure

Fuuga.Image/
  Types.fs                Layer 0: Domain types, error handling (ImageError DU)
  Config.fs               Layer 1: CLI argument parsing
  ImageIO.fs              Layer 2: Image load/save, format conversion, validation
  VideoIO.fs              Layer 2b: Video frame I/O (ffmpeg optional)
  ScratchSdTokenizer.fs   Layer 2c: CLIP BPE tokenizer (from-scratch SD)
  ScratchSdModels.fs      Layer 2c: CLIP/VAE/UNet architecture in TorchSharp
  ScratchSdWeights.fs     Layer 2c: safetensors reader + checkpoint loading
  ScratchSdPipeline.fs    Layer 2c: DDPM sampler + txt2img/img2img pipeline
  Diffusion.fs            Layer 3: Stable Diffusion model wrapper (txt2img, img2img)
  Caption.fs              Layer 4: Phi-3.5-vision captioning (ONNX Runtime GenAI)
  McpServer.fs            Layer 5: MCP JSON-RPC 2.0 server over stdio
  Program.fs              Layer 6: Entry point, subcommand routing, error handling

Architecture

Downloads by Version

Similar & Alternative Packages

Hand-picked matching NuGet libraries based on tags & ecosystem

Depended On By

Find packages that declare this package as a dependency. NuGet does not provide a reverse-dependency endpoint, so candidates are checked on demand.

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

Compatibility metadata lists 1 target framework; confirm the exact target used by your application.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.