Skip to content

Releases: dmarkham/fits

v1.1.0 — stats: hot-path API additions

09 Apr 08:17

Choose a tag to compare

New functions

Five additions to the stats package, all designed to support hot-path stacking pipelines that need to avoid allocations on every call. Existing public APIs (Percentile, Median, MAD, MADWithMedian, MeanStdev) are semantically unchanged.

Zero-alloc *Buf variants

const MaxHistoSize = 65536

func PercentileBuf[T Numeric](data []T, p float64, histo []uint32) float64
func MedianBuf[T Numeric](data []T, histo []uint32) float64
func MADWithMedianBuf[T Numeric](data []T, histo []uint32, absDev []float64) (median, mad float64)

Caller supplies the scratch slices (typically pre-allocated once per goroutine). The histo slice is zeroed and overwritten on each call, so no manual reset is needed between calls. Both buffers are bounds-checked — undersized inputs panic with a clear message.

QuickSelect

func QuickSelect[T Numeric](data []T, k int) T

Generic line-for-line port of Siril's quickmedian_float (sorting.c) — Hoare partition with middle-element pivot, in place. For small-n medians (e.g. the 21–41 pixel rejection columns) it is both faster and more accurate than the histogram method, since it returns an exact element instead of a histogram-interpolated value.

NaN handling: caller must pre-filter; NaN comparisons break partitioning.

MeanStdevSiril

func MeanStdevSiril(data []float32) (mean, stdev float32)

Bit-for-bit port of Siril's siril_stats_float_sd (statistics_float.c). Two-pass algorithm with float32 mean truncation between passes and float32 deviation arithmetic, returning float32 to preserve rejection-boundary precision. Rejection pipelines using this fix a visible artifact (red hotspot in stacked output) traced to ULP-level differences at sigma-clip boundaries vs the generic float64 MeanStdev.

Benchmarks

Measured on AMD Ryzen AI 9 HX 370. n=50 is the rejection-step size; n=500_000 is the overlap-norm size.

Function Allocating Buf Speedup Allocs eliminated
Median (n=50) 229 ns / 208 B / 1 alloc 134 ns / 0 B / 0 1.71× 1 alloc
Median (n=500K) 1411 µs / 262 KB / 1 alloc 1195 µs / 0 B / 0 1.18× 262 KB
MAD (n=50) 680 ns / 624 B / 2 allocs 354 ns / 0 B / 0 1.92× 2 allocs
MAD (n=500K) 6238 µs / 4.27 MB / 2 allocs 5481 µs / 0 B / 0 1.14× 4.27 MB
Percentile p99 (n=50) 240 ns / 208 B / 1 alloc 200 ns / 0 B / 0 1.20× 1 alloc
MeanStdev (n=50) 38 ns / 0 / 0 (Siril) 15 ns / 0 / 0 2.58×
MeanStdev (n=500K) 379 µs / 0 / 0 (Siril) 248 µs / 0 / 0 1.53×
QuickSelect (n=50) 66.84 ns / 0 / 0 2.49× faster than MedianBuf at n=50

Highlights

  • MeanStdevSiril is 2.5× faster than the generic MeanStdev at small n in addition to fixing the rejection-boundary precision — for 150M-call use cases this is both a correctness fix and a perf win.
  • QuickSelect is 2.49× faster than MedianBuf at n=50, and returns an exact element instead of a histogram-interpolated value. Recommended for the small-n median use case.
  • Every Buf variant is verified bit-for-bit equivalent to its allocating counterpart across the full fixture suite.

v1.0.4 — stats: MeanStdev N-1 denominator

09 Apr 07:17

Choose a tag to compare

Bug fix

stats.MeanStdev now uses the Bessel-corrected N-1 denominator (sample standard deviation) to match Siril's siril_stats_float_sd. Previously it used N (population stdev), causing a small systematic difference for any code cross-validating against Siril's stacking pipeline.

  • n < 2 returns 0 stdev
  • Golden fixture stdev values updated; median/MAD/percentile values unchanged

Not changed — worth noting

Cross-validation showed that Median, MAD, and Percentile already match Siril's findMinMaxPercentile bit-for-bit in float32 arithmetic, verified by TestPercentile_Siril (1e-6 tolerance) and TestAutostretch_Siril (1e-4 tolerance on real kstars data). No changes needed there.

v1.0.3

06 Apr 17:27
0258acd

Choose a tag to compare

Fix: WriteImage BSCALE/BZERO round-trip

WriteImage now applies BSCALE/BZERO inverse scaling before serialization when the header contains these keywords. Integer BITPIX formats (8/16/32/64 bit) are clamped to the target type's range to prevent wraparound — matching Siril's savefits() behavior. Float BITPIX (-32/-64) preserves the full range.

This fixes a gap where the doc promised BSCALE/BZERO round-trip fidelity but the code did not apply the inverse scaling.

Full changelog

v1.0.2...v1.0.3

v1.0.2

06 Apr 07:49
7e0fa6d

Choose a tag to compare

New: fits/stats package

Generic, NaN-aware statistics functions for astronomical image data. Extracted from existing cross-validated implementations and validated against Siril's findMinMaxPercentile via a C++ reference harness.

Function Description
MinMax[T] Min and max, skipping NaN
Mean[T], MeanStdev[T] Arithmetic mean and population stdev
Percentile[T], Median[T] Histogram-based interpolated percentile (Siril port)
MAD[T], MADWithMedian[T] Median Absolute Deviation (raw, no 1.4826)
BuildHistogram[T] Returns Histogram struct with CDF() and Percentile() methods
SigmaClip[T] Iterative sigma clipping with mean or median center
FilterNonZero[T] Zero/NaN exclusion (astronomical blank-pixel convention)

Refactor

stretch/mtf.go now calls stats.Median, stats.MAD, stats.FilterNonZero instead of private copies. Zero regression on all 18 Siril stretch golden fixtures.

Full changelog

v1.0.1...v1.0.2

v1.0.1

06 Apr 05:47

Choose a tag to compare

What's new

  • stretch.NormalizeChannels — scales all channels to [0, 1] using a shared global min/max, preserving relative intensity between channels. This is the input-side companion to the existing stretch pipeline helpers.

  • macOS and Windows support — fixed path separator in the journal crash-recovery code. The library is now portable across Linux, macOS, and Windows.

Fixes

  • Hardcoded developer paths replaced with FITS_ASTROPY_PYTHON env var (astropy cross-validation tests skip gracefully when not available)
  • Broken cfitsio submodule reference removed
  • plan.md scrubbed for public visibility

Full changelog

v1.0.0...v1.0.1

v1.0.0

06 Apr 04:52

Choose a tag to compare

fits v1.0.0

Pure-Go FITS library for reading, writing, and processing astronomical data files. No cgo, no external dependencies.

What's included

Package Capability
fits FITS file I/O — images (all BITPIX), binary tables (VLAs, heap), ASCII tables, headers (CONTINUE, HIERARCH), in-place edit, streaming rebuild, image.Image adapter
fits/compress Tile compression — all 6 algorithms (RICE_1, GZIP_1, GZIP_2, HCOMPRESS_1, PLIO_1, NOCOMPRESS), float quantization with SUBTRACTIVE_DITHER_1/2
fits/wcs + fits/wcs/transform World Coordinate System — all 27 Paper II projections + HEALPix, SIP/TPV/TNX distortion, 6 sky frames (ICRS/FK5/FK4/galactic/ecliptic/supergalactic)
fits/healpix HEALPix pixel indexing — RING + NESTED ordering, ang2pix/pix2ang, ring↔nest conversion, 8-connected neighbors
fits/stretch Image stretching — 18 presets (autostretch/MTF, asinh, GHT, linear, CLAHE) matching Siril within 1e-4

Cross-validation

Every algorithm is ported from a reference implementation and validated against it:

  • cfitsio — byte-for-byte round-trip on all fixtures; float quantization bit-exact on 11 golden cases
  • wcslib — 14 golden fixtures, bit-exact on all 27 projections
  • astropy — bidirectional compression interop (Go writes → astropy reads, and vice versa)
  • healpy — 42 golden fixtures, integer-exact pixel indices, 1e-10 rad angles
  • Siril — 18 stretch presets validated within 1e-4

Platforms

Linux, macOS, Windows. Pure Go — go get github.com/dmarkham/fits@v1.0.0

Philosophy

This library is not trying to replace cfitsio, wcslib, healpy, or Siril. The goal is to bring Go onto level footing with the existing ecosystem while staying as compatible as possible.