rustafits
High-performance FITS/XISF to JPEG/PNG converter for astronomical images with auto-stretch, Bayer debayering, and SIMD acceleration. Pure Rust — no system dependencies.
Features
- FITS & XISF Support: Native readers for both formats (no external libraries)
- Auto-Stretch: Median-based statistical stretching (PixInsight STF compatible)
- Bayer Debayering: Super-pixel 2x2 block averaging (RGGB, BGGR, GBRG, GRBG)
- Preview Mode: 2x2 binning for fast previews
- SIMD Optimized: SSE2/AVX2 (x86_64) and NEON (aarch64) with automatic detection
- In-Memory API: Get raw pixel data without file I/O — ideal for GUI apps
Supported Formats
| Format | Extensions | Data Types |
|---|---|---|
| FITS | .fits, .fit |
8/16/32-bit int, 32/64-bit float |
| XISF | .xisf |
All sample formats, zlib/LZ4/Zstd compression |
Installation
Cargo (Recommended)
No system dependencies needed — everything is pure Rust:
From Source
Homebrew (macOS/Linux)
CLI Usage
# Basic conversion
# Fast preview (2x2 binning)
# Downscaled output
# Options
)
)
Library Usage
Add to your Cargo.toml:
[]
= "0.4"
File output
use ImageConverter;
new
.with_preview_mode
.with_quality
.convert?;
In-memory processing
Get raw RGB pixel data without writing to disk — useful for GUI viewers, web backends, and Tauri apps:
use ;
let image: ProcessedImage = new
.with_downscale
.process?;
// image.data - Vec<u8>, interleaved RGB bytes
// image.width - pixel width
// image.height - pixel height
// image.is_color - true if debayered/RGB, false if mono (gray replicated to RGB)
Builder methods
| Method | Description |
|---|---|
with_downscale(n) |
Downscale by factor n |
with_quality(q) |
JPEG quality 1-100 |
without_debayer() |
Skip Bayer debayering |
with_preview_mode() |
2x2 binning for fast previews |
with_thread_pool(pool) |
Use a custom rayon thread pool (see below) |
Multi-image concurrent processing
By default, all parallel work (debayering, stretch, binning, byte conversion) runs on rayon's global thread pool. This works well for single-image processing, but when processing multiple images concurrently from separate threads, they all compete for the same pool — causing thread oversubscription and degraded throughput.
Use with_thread_pool() to route all parallel work to a dedicated or shared pool:
use Arc;
use ;
// Create a shared pool once at startup
let pool = new;
// Process multiple images concurrently
let handles: = paths.iter.map.collect;
let results: = handles.into_iter
.map
.collect;
Recommendations by concurrency level:
| Concurrent images | Strategy |
|---|---|
| 1-3 | Default global pool is fine |
| 4-8 | Shared pool via with_thread_pool() with num_cpus threads |
| 8+ | Shared pool + limit concurrency with a semaphore or channel |
Memory budget: Each full-resolution image (e.g. 4096x3072 16-bit) uses ~150 MB peak. For 10 concurrent images, budget ~1.5 GB. Use with_preview_mode() or with_downscale() to reduce memory usage.
Performance
Benchmarks on Apple M4 (6252x4176 16-bit images):
| Mode | Time |
|---|---|
| FITS | ~460ms |
| FITS (preview) | ~130ms |
| XISF (LZ4 compressed) | ~290ms |
SIMD Acceleration
SIMD is used across the processing pipeline with automatic runtime dispatch:
| Operation | SSE2 | AVX2 | NEON |
|---|---|---|---|
| Stretch | 4 px/iter | 8 px/iter | 4 px/iter |
| Binning | yes | yes | yes |
| u16 to f32 | yes | yes | yes |
| Gray to RGB | scalar | pshufb | yes |
| Debayer (f32) | yes | — | yes |
Architecture
rustafits/
├── src/
│ ├── lib.rs # Library entry + public API
│ ├── types.rs # Core types (PixelData, ProcessedImage, etc.)
│ ├── converter.rs # ImageConverter builder
│ ├── pipeline.rs # Processing pipeline
│ ├── output.rs # JPEG/PNG file output
│ ├── bin/rustafits.rs # CLI tool
│ ├── formats/
│ │ ├── mod.rs # Format dispatch
│ │ ├── fits.rs # FITS reader
│ │ └── xisf.rs # XISF reader (zlib/LZ4/Zstd)
│ └── processing/
│ ├── mod.rs # Processing module
│ ├── stretch.rs # Auto-stretch (SIMD)
│ ├── debayer.rs # Bayer debayering (SIMD)
│ ├── binning.rs # 2x2 binning (SIMD)
│ ├── downscale.rs # Integer downscaling
│ └── color.rs # Color conversions (SIMD)
Dependencies (all pure Rust): anyhow, flate2 (rust_backend), lz4_flex, ruzstd, image, quick-xml, base64
Troubleshooting
Slow conversion: Use --preview for mono images or --downscale 2
Black/white output: Run with --log to check stretch parameters
References
- PixInsight — Screen Transfer Function documentation
- FITS Standard
- XISF Specification
License
Apache-2.0