Files
tensor/examples/fits/main.go
T

99 lines
2.5 KiB
Go
Raw Normal View History

2026-09-03 10:00:00 +02:00
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
// Command fits builds a synthetic star field, saves it as a FITS
// primary image, reads it back and recovers the brightest star's
// position by a centre-of-mass centroid, the first step of any
// aperture photometry pipeline.
//
// Usage: go run ./examples/fits
package main
import (
"fmt"
"log"
"math"
"os"
"path/filepath"
"sourcedock.dev/petrbalvin/tensor"
)
func main() {
const (
size = 128
sigma = 2.0 // pixels, the seeing disk
)
// Three stars of different brightness on a flat sky background.
type star struct {
x, y, flux float64
}
stars := []star{
{40.5, 60.5, 900},
{80.5, 30.5, 300},
{95.5, 95.5, 120},
}
field := make([]float64, size*size)
for i := range size {
for j := range size {
v := 100.0 // sky
for _, s := range stars {
d2 := (float64(i)-s.y)*(float64(i)-s.y) + (float64(j)-s.x)*(float64(j)-s.x)
v += s.flux * math.Exp(-d2/(2*sigma*sigma))
}
field[i*size+j] = v
}
}
img, err := tensor.FromFloats(field, size, size)
if err != nil {
log.Fatal(err)
}
path := filepath.Join(os.TempDir(), "tensor-example-stars.fits")
defer os.Remove(path)
headers := map[string]string{
"OBJECT": "synthetic field",
"EXPTIME": "30",
"FILTER": "V",
}
if err := tensor.SaveFITS(path, img, headers); err != nil {
log.Fatal(err)
}
back, hdr, err := tensor.LoadFITS(path)
if err != nil {
log.Fatal(err)
}
fmt.Printf("wrote and read %s\n", filepath.Base(path))
for _, k := range []string{"OBJECT", "EXPTIME", "FILTER"} {
fmt.Printf(" %s = %s\n", k, hdr[k])
}
if back.Shape()[0] != size || back.Shape()[1] != size {
log.Fatalf("round trip changed the shape: %v", back.Shape())
}
// Locate the brightest pixel, then centroid a 9x9 window around
// it with the sky level subtracted.
best, bestVal := 0, -1.0
for i := range size * size {
if v := back.FloatAt(i); v > bestVal {
best, bestVal = i, v
}
}
by, bx := best/size, best%size
sum, sx, sy := 0.0, 0.0, 0.0
for i := by - 4; i <= by+4; i++ {
for j := bx - 4; j <= bx+4; j++ {
w := back.FloatAt(i*size+j) - 100
if w < 0 {
w = 0
}
sum += w
sx += w * float64(j)
sy += w * float64(i)
}
}
fmt.Printf("\nbrightest star: peak at (x=%d, y=%d), %.0f counts\n", bx, by, bestVal)
fmt.Printf("centroid of the 9x9 window: (x=%.2f, y=%.2f)\n", sx/sum, sy/sum)
fmt.Println("true position: (x=40.50, y=60.50)")
}