![]() |
VOOZH | about |
dotnet add package mostlylucid.llmalttext --version 1.0.0-preview1
NuGet\Install-Package mostlylucid.llmalttext -Version 1.0.0-preview1
<PackageReference Include="mostlylucid.llmalttext" Version="1.0.0-preview1" />
<PackageVersion Include="mostlylucid.llmalttext" Version="1.0.0-preview1" />Directory.Packages.props
<PackageReference Include="mostlylucid.llmalttext" />Project file
paket add mostlylucid.llmalttext --version 1.0.0-preview1
#r "nuget: mostlylucid.llmalttext, 1.0.0-preview1"
#:package mostlylucid.llmalttext@1.0.0-preview1
#addin nuget:?package=mostlylucid.llmalttext&version=1.0.0-preview1&prereleaseInstall as a Cake Addin
#tool nuget:?package=mostlylucid.llmalttext&version=1.0.0-preview1&prereleaseInstall as a Cake Tool
Note: These packages are provided as-is. I'll get them working good enough to release but I can't commit to support. However they are Unlicense so have at it!
AI-powered alt text generation and OCR using Microsoft's Florence-2 Vision Language Model. Automatically generates descriptive, accessible alt text for images and extracts text content.
<img> tags in Razor viewsThis package automatically downloads AI models (~800MB) on first use.
./models (configurable)dotnet add package Mostlylucid.LlmAltText
using Mostlylucid.LlmAltText.Extensions;
// In Program.cs
builder.Services.AddAltTextGeneration();
// Or with custom configuration
builder.Services.AddAltTextGeneration(options =>
{
options.ModelPath = "./my-models"; // Where to store models
options.EnableDiagnosticLogging = true; // Detailed logging
options.MaxWords = 90; // Recommended max words
options.DefaultTaskType = "MORE_DETAILED_CAPTION";
});
using Mostlylucid.LlmAltText.Services;
public class ImageController : ControllerBase
{
private readonly IImageAnalysisService _imageAnalysis;
public ImageController(IImageAnalysisService imageAnalysis)
{
_imageAnalysis = imageAnalysis;
}
[HttpPost("analyze")]
public async Task<IActionResult> AnalyzeImage(IFormFile image)
{
using var stream = image.OpenReadStream();
// Get complete analysis with content classification
var result = await _imageAnalysis.AnalyzeWithClassificationAsync(stream);
return Ok(new
{
AltText = result.AltText,
ExtractedText = result.ExtractedText,
ContentType = result.ContentType.ToString(),
Confidence = result.ContentTypeConfidence,
HasText = result.HasSignificantText
});
}
}
The main service interface for image analysis.
| Property | Type | Description |
|---|---|---|
IsReady |
bool |
Indicates if the service is initialized and ready to process images |
// Generate alt text from a stream
Task<string> GenerateAltTextAsync(Stream imageStream, string taskType = "MORE_DETAILED_CAPTION");
// Extract text via OCR
Task<string> ExtractTextAsync(Stream imageStream);
// Get both alt text and extracted text
Task<(string AltText, string ExtractedText)> AnalyzeImageAsync(Stream imageStream);
// Complete analysis with content type classification
Task<ImageAnalysisResult> AnalyzeWithClassificationAsync(Stream imageStream);
// Classify content type only
Task<(ImageContentType Type, double Confidence)> ClassifyContentTypeAsync(Stream imageStream);
// Generate alt text from a file path
Task<string> GenerateAltTextFromFileAsync(string filePath,
string taskType = "MORE_DETAILED_CAPTION",
CancellationToken cancellationToken = default);
// Extract text from a file
Task<string> ExtractTextFromFileAsync(string filePath,
CancellationToken cancellationToken = default);
// Analyze a file
Task<(string AltText, string ExtractedText)> AnalyzeImageFromFileAsync(string filePath,
CancellationToken cancellationToken = default);
// Complete analysis of a file
Task<ImageAnalysisResult> AnalyzeWithClassificationFromFileAsync(string filePath,
CancellationToken cancellationToken = default);
// Generate alt text from a URL (string or Uri overloads)
Task<string> GenerateAltTextFromUrlAsync(string imageUrl,
string taskType = "MORE_DETAILED_CAPTION",
CancellationToken cancellationToken = default);
Task<string> GenerateAltTextFromUrlAsync(Uri imageUrl,
string taskType = "MORE_DETAILED_CAPTION",
CancellationToken cancellationToken = default);
// Extract text from URL
Task<string> ExtractTextFromUrlAsync(string imageUrl,
CancellationToken cancellationToken = default);
// Analyze image from URL
Task<(string AltText, string ExtractedText)> AnalyzeImageFromUrlAsync(string imageUrl,
CancellationToken cancellationToken = default);
// Complete analysis from URL
Task<ImageAnalysisResult> AnalyzeWithClassificationFromUrlAsync(string imageUrl,
CancellationToken cancellationToken = default);
// Generate alt text from byte array
Task<string> GenerateAltTextAsync(byte[] imageData, string taskType = "MORE_DETAILED_CAPTION");
// Extract text from byte array
Task<string> ExtractTextAsync(byte[] imageData);
// Analyze byte array
Task<(string AltText, string ExtractedText)> AnalyzeImageAsync(byte[] imageData);
// Complete analysis of byte array
Task<ImageAnalysisResult> AnalyzeWithClassificationAsync(byte[] imageData);
| Task Type | Description |
|---|---|
"CAPTION" |
Brief, simple caption |
"DETAILED_CAPTION" |
More detailed description |
"MORE_DETAILED_CAPTION" |
Full descriptive alt text (default, recommended) |
public class ImageAnalysisResult
{
public required string AltText { get; set; }
public required string ExtractedText { get; set; }
public ImageContentType ContentType { get; set; }
public double ContentTypeConfidence { get; set; } // 0-1
public bool HasSignificantText { get; set; }
}
| Type | Description |
|---|---|
Photograph |
Photos of real-world scenes, people, objects |
Document |
Text-heavy documents, forms, PDFs |
Screenshot |
Screenshots of software, websites, UIs |
Chart |
Charts, graphs, data visualizations |
Illustration |
Drawings, artwork, cartoons |
Diagram |
Flowcharts, schematics, UML diagrams |
Unknown |
Unable to classify |
public async Task<string> GetAltTextFromFormFile(IFormFile file)
{
using var stream = file.OpenReadStream();
return await _imageAnalysis.GenerateAltTextAsync(stream);
}
// Single file
var altText = await _imageAnalysis.GenerateAltTextFromFileAsync("/path/to/image.jpg");
// Batch processing
var files = Directory.GetFiles("/images", "*.jpg");
foreach (var file in files)
{
var result = await _imageAnalysis.AnalyzeWithClassificationFromFileAsync(file);
Console.WriteLine($"{Path.GetFileName(file)}: {result.AltText}");
}
// From string URL
var altText = await _imageAnalysis.GenerateAltTextFromUrlAsync(
"https://example.com/photo.jpg");
// From Uri with cancellation
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var result = await _imageAnalysis.AnalyzeWithClassificationFromUrlAsync(
new Uri("https://example.com/photo.jpg"),
cts.Token);
// From HttpClient response
var httpClient = new HttpClient();
var imageBytes = await httpClient.GetByteArrayAsync("https://example.com/image.png");
var altText = await _imageAnalysis.GenerateAltTextAsync(imageBytes);
// From base64
var base64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ...";
var bytes = Convert.FromBase64String(base64);
var result = await _imageAnalysis.AnalyzeWithClassificationAsync(bytes);
using var stream = file.OpenReadStream();
// Brief caption
var brief = await _imageAnalysis.GenerateAltTextAsync(stream, "CAPTION");
// Result: "A dog sitting on grass"
stream.Position = 0;
// Detailed caption
var detailed = await _imageAnalysis.GenerateAltTextAsync(stream, "DETAILED_CAPTION");
// Result: "A golden retriever sitting on green grass in a park"
stream.Position = 0;
// Most detailed (default, best for accessibility)
var full = await _imageAnalysis.GenerateAltTextAsync(stream, "MORE_DETAILED_CAPTION");
// Result: "A happy golden retriever with light fur sitting on lush green grass
// in a sunny park, with trees visible in the background."
var result = await _imageAnalysis.AnalyzeWithClassificationAsync(stream);
switch (result.ContentType)
{
case ImageContentType.Document:
// Prioritize extracted text for screen readers
Console.WriteLine($"Document text: {result.ExtractedText}");
break;
case ImageContentType.Screenshot:
// Include both alt text and extracted UI text
Console.WriteLine($"Screenshot: {result.AltText}");
if (result.HasSignificantText)
Console.WriteLine($"UI Text: {result.ExtractedText}");
break;
case ImageContentType.Photograph:
// Focus on descriptive alt text
Console.WriteLine($"Photo: {result.AltText}");
break;
case ImageContentType.Chart:
// Include data if extracted
Console.WriteLine($"Chart: {result.AltText}");
Console.WriteLine($"Data: {result.ExtractedText}");
break;
}
The TagHelper automatically generates alt text for <img> tags that don't have one.
// In Program.cs
builder.Services.AddAltTextGeneration(options =>
{
options.EnableTagHelper = true;
options.EnableDatabase = true; // Enable caching (recommended)
options.DbProvider = AltTextDbProvider.Sqlite;
options.SqliteDbPath = "./alttext.db";
options.CacheDurationMinutes = 60;
});
// After building the app, migrate the database
var app = builder.Build();
await app.Services.MigrateAltTextDatabaseAsync();
In your _ViewImports.cshtml:
@addTagHelper *, Mostlylucid.LlmAltText
<img src="https://example.com/photo.jpg" />
<img src="https://example.com/photo.jpg" alt="My custom alt text" />
<img src="https://example.com/decorative.jpg" alt="" />
<img src="https://example.com/photo.jpg" data-skip-alt="true" />
<img src="data:image/png;base64,iVBORw0KGgo..." />
| Condition | Action |
|---|---|
alt attribute exists (any value) |
Skipped - respects existing alt |
alt="" (empty) |
Skipped - decorative image per a11y standards |
data-skip-alt="true" |
Skipped - explicitly opted out |
src starts with data: or blob: |
Skipped - data URIs not fetched |
src is relative path |
Skipped - cannot fetch without base URL |
Domain not in AllowedImageDomains |
Skipped - security restriction |
No alt attribute |
Generates - fetches image and creates alt text |
options.AllowedImageDomains = new List<string>
{
"mycdn.example.com",
"images.example.org"
};
// Only images from these domains will be processed
builder.Services.AddAltTextGeneration(options =>
{
options.EnableTagHelper = true;
options.EnableDatabase = true;
options.DbProvider = AltTextDbProvider.PostgreSql;
options.ConnectionString = "Host=localhost;Database=alttext;Username=user;Password=pass";
});
public class AltTextOptions
{
// Model storage location
public string ModelPath { get; set; } = "./models";
// Custom prompt for alt text generation
public string AltTextPrompt { get; set; } = "Provide 2-3 complete...";
// Default task type: CAPTION, DETAILED_CAPTION, MORE_DETAILED_CAPTION
public string DefaultTaskType { get; set; } = "MORE_DETAILED_CAPTION";
// Enable detailed diagnostic logging
public bool EnableDiagnosticLogging { get; set; } = true;
// Maximum recommended word count for alt text
public int MaxWords { get; set; } = 90;
// TagHelper settings
public bool EnableTagHelper { get; set; } = false;
public bool EnableDatabase { get; set; } = false;
public bool AutoMigrateDatabase { get; set; } = true;
public AltTextDbProvider DbProvider { get; set; } = AltTextDbProvider.Sqlite;
public string SqliteDbPath { get; set; } = "alttext.db";
public string? ConnectionString { get; set; }
// Security
public List<string> AllowedImageDomains { get; set; } = new();
public List<string> SkipSrcPrefixes { get; set; } = new() { "data:", "blob:" };
// Caching
public int CacheDurationMinutes { get; set; } = 60;
}
// Production configuration
builder.Services.AddAltTextGeneration(options =>
{
options.ModelPath = "/var/app/ai-models";
options.EnableDiagnosticLogging = false;
options.MaxWords = 100;
options.EnableTagHelper = true;
options.EnableDatabase = true;
options.DbProvider = AltTextDbProvider.PostgreSql;
options.ConnectionString = Environment.GetEnvironmentVariable("ALTTEXT_DB");
});
// Development configuration
builder.Services.AddAltTextGeneration(options =>
{
options.ModelPath = "./models";
options.EnableDiagnosticLogging = true;
options.EnableTagHelper = true;
options.EnableDatabase = true;
options.DbProvider = AltTextDbProvider.Sqlite;
options.SqliteDbPath = "./dev-alttext.db";
});
// Minimal (no database, no TagHelper)
builder.Services.AddAltTextGeneration();
Large images are automatically downscaled before processing:
This ensures consistent processing times and prevents memory issues with very large images.
All formats supported by SixLabors.ImageSharp:
The package includes built-in OpenTelemetry instrumentation:
// Add tracing
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("Mostlylucid.LlmAltText");
});
llmalttext.generate_alt_textllmalttext.extract_textllmalttext.analyze_imagellmalttext.analyze_with_classificationllmalttext.classify_content_typepublic class ImageAnalysisHealthCheck : IHealthCheck
{
private readonly IImageAnalysisService _service;
public ImageAnalysisHealthCheck(IImageAnalysisService service)
{
_service = service;
}
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
if (_service.IsReady)
return Task.FromResult(HealthCheckResult.Healthy("AI model ready"));
return Task.FromResult(HealthCheckResult.Unhealthy("AI model not initialized"));
}
}
| Metric | Typical Value |
|---|---|
| First run | Slower (800MB model download) |
| Initialization | 1-3 seconds (model load) |
| Per-image processing | 500-2000ms |
| Memory usage | 2GB+ recommended |
| Model size | ~800MB on disk |
Problem: Model download fails or times out
Solutions:
ModelPath to a writable locationProblem: IsReady returns false
Solutions:
ModelPathProblem: Generated alt text is not descriptive enough
Solutions:
"MORE_DETAILED_CAPTION" task type (default)AltTextPrompt in optionsProblem: Alt text not generated for images
Solutions:
EnableTagHelper = true in options@addTagHelper in _ViewImports.cshtmlAllowedImageDomains if configureddata-skip-alt attributeWhen using generated alt text:
alt="" for purely decorative imagesUnlicense - Public Domain
Contributions welcome! Please see the main repository: https://github.com/scottgal/mostlylucidweb
Built on:
For issues, questions, or contributions: https://github.com/scottgal/mostlylucidweb/issues
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-preview1 | 177 | 11/24/2025 |
1.0.0
Initial release of Mostlylucid.LlmAltText.
Features:
- AI-powered alt text generation using Florence-2 Vision Language Model
- OCR text extraction from images
- Automatic model download on first use (~800MB)
- Support for common image formats (PNG, JPEG, WebP, GIF, BMP)
- Dependency injection integration with IServiceCollection
- Configurable options via AltTextOptions
- Async processing with proper cancellation support