Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 50 additions & 11 deletions pkg/fits/fits.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
/*****************************************************************************************************************/

// @author Michael Roberts <michael@observerly.com>
// @package @observerly/iris/fits
// @license Copyright © 2021-2025 observerly

/*****************************************************************************************************************/

package fits

/*****************************************************************************************************************/

import (
"bytes"
"encoding/binary"
Expand All @@ -16,9 +26,14 @@ import (
"github.com/observerly/iris/pkg/utils"
)

/*****************************************************************************************************************/

const FITS_STANDARD = "FITS Standard 4.0"

// FITS Image struct:
/*****************************************************************************************************************/

// Represents a FITS Image
//
// @see https://fits.gsfc.nasa.gov/fits_primer.html
// @see https://fits.gsfc.nasa.gov/standard40/fits_standard40aa-le.pdf
type FITSImage struct {
Expand All @@ -36,7 +51,10 @@ type FITSImage struct {
Stats *stats.Stats // Image statistics (mean, min, max, stdDev etc)
}

// FITS Observation struct:
/*****************************************************************************************************************/

// Represents a FITS Observation
//
// @see https://fits.gsfc.nasa.gov/standard40/fits_standard40aa-le.pdf
type FITSObservation struct {
DateObs time.Time `json:"dateObs"` // Date of observation e.g., 2022-05-15
Expand All @@ -54,12 +72,17 @@ type FITSObservation struct {
Observer string `json:"observer"` // Who acquired the data
}

/*****************************************************************************************************************/

// Represents a FITS Observer
type FITSObserver struct {
Latitude float32 `json:"latitude"` // Latitude of the observer
Longitude float32 `json:"longitude"` // Longitude of the observer
Elevation float32 `json:"elevation"` // Elevation of the observer
}

/*****************************************************************************************************************/

// Creates a new instance of FITS image initialized with empty header
func NewFITSImage(naxis int32, naxis1 int32, naxis2 int32, adu int32) *FITSImage {
h := NewFITSHeader(naxis, naxis1, naxis2)
Expand Down Expand Up @@ -97,6 +120,8 @@ func NewFITSImage(naxis int32, naxis1 int32, naxis2 int32, adu int32) *FITSImage
}
}

/*****************************************************************************************************************/

// Creates a new instance of FITS image initialized from an io.Reader:
func NewFITSImageFromReader(r io.Reader) *FITSImage {
// Construct a blank FITS Image:
Expand All @@ -112,6 +137,8 @@ func NewFITSImageFromReader(r io.Reader) *FITSImage {
return f
}

/*****************************************************************************************************************/

// Creates a new instance of FITS image from given 2D exposure array
// (Data is not copied, allocated if nil. naxisn is deep copied)
func NewFITSImageFrom2DData(ex [][]uint32, naxis int32, naxis1 int32, naxis2 int32, adu int32) *FITSImage {
Expand Down Expand Up @@ -168,6 +195,8 @@ func NewFITSImageFrom2DData(ex [][]uint32, naxis int32, naxis1 int32, naxis2 int
}
}

/*****************************************************************************************************************/

func (f *FITSImage) AddObservationEntry(observation *FITSObservation) *FITSImage {
f.Header.Dates["DATE-OBS"] = struct {
Value string
Expand Down Expand Up @@ -295,6 +324,8 @@ func (f *FITSImage) AddObservationEntry(observation *FITSObservation) *FITSImage
return f
}

/*****************************************************************************************************************/

func (f *FITSImage) AddObserverEntry(observer *FITSObserver) *FITSImage {
f.Header.Floats["LATITUDE"] = struct {
Value float32
Expand Down Expand Up @@ -323,6 +354,8 @@ func (f *FITSImage) AddObserverEntry(observer *FITSObserver) *FITSImage {
return f
}

/*****************************************************************************************************************/

func (f *FITSImage) ExtractHFR(radius float32, sigma float32, starInOut float32) float32 {
se := photometry.NewStarsExtractor(f.Data, int(f.Naxisn[0]), int(f.Naxisn[1]), radius, f.ADU)

Expand All @@ -333,6 +366,8 @@ func (f *FITSImage) ExtractHFR(radius float32, sigma float32, starInOut float32)
return se.HFR
}

/*****************************************************************************************************************/

func (f *FITSImage) ReadFromFile(fp string) error {
// Check that the filename is not empty:
if fp == "" {
Expand All @@ -355,6 +390,8 @@ func (f *FITSImage) ReadFromFile(fp string) error {
return f.Read(file)
}

/*****************************************************************************************************************/

// Read the FITS image from the given file.
func (f *FITSImage) Read(r io.Reader) error {
// Read Header:
Expand Down Expand Up @@ -435,6 +472,8 @@ func (f *FITSImage) Read(r io.Reader) error {
return nil
}

/*****************************************************************************************************************/

// Writes an in-memory FITS image to an io.Writer output stream
func (f *FITSImage) WriteToBuffer() (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
Expand All @@ -456,6 +495,8 @@ func (f *FITSImage) WriteToBuffer() (*bytes.Buffer, error) {
return buf, nil
}

/*****************************************************************************************************************/

// Writes FITS binary body data in network byte order to buffer
func writeFloat32ArrayToBuffer(buf *bytes.Buffer, data []float32) (*bytes.Buffer, error) {
err := binary.Write(buf, binary.BigEndian, data)
Expand Down Expand Up @@ -486,16 +527,10 @@ func writeFloat32ArrayToBuffer(buf *bytes.Buffer, data []float32) (*bytes.Buffer
return buf, nil
}

/*
*

Reads the FITS binary data from the given io.Reader stream and returns a
slice of float32 values, or error

Note: The data is read in network byte order and only supports 32-bitpix data
/*****************************************************************************************************************/

*
*/
// Reads the FITS binary data from the given io.Reader stream and returns a slice of float32 values, or error.
// Note: The data is read in network byte order and only supports 32-bitpix data
func readData(r io.Reader, bitpix int32, pixels int32) ([]float32, error) {
data := make([]float32, pixels)

Expand Down Expand Up @@ -543,7 +578,11 @@ func readData(r io.Reader, bitpix int32, pixels int32) ([]float32, error) {
return data, err
}

/*****************************************************************************************************************/

// Reads FITS binary body float32 data in network byte order from buffer
func readFloat32ArrayFromBuffer(buf *bytes.Buffer, data []float32) error {
return binary.Read(buf, binary.BigEndian, data)
}

/*****************************************************************************************************************/
43 changes: 43 additions & 0 deletions pkg/fits/fits_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
/*****************************************************************************************************************/

// @author Michael Roberts <michael@observerly.com>
// @package @observerly/iris/fits
// @license Copyright © 2021-2025 observerly

/*****************************************************************************************************************/

package fits

/*****************************************************************************************************************/

import (
"bytes"
"image"
Expand All @@ -11,6 +21,8 @@ import (
"time"
)

/*****************************************************************************************************************/

func GetTestDataFromImage() ([][]uint32, image.Rectangle) {
f, err := os.Open("../../images/noise16.jpeg")

Expand Down Expand Up @@ -46,6 +58,8 @@ func GetTestDataFromImage() ([][]uint32, image.Rectangle) {
return data, bounds
}

/*****************************************************************************************************************/

func TestNewDefaultFITSImageHeaderEnd(t *testing.T) {
var img = NewFITSImage(2, 600, 800, 65535)

Expand All @@ -58,6 +72,8 @@ func TestNewDefaultFITSImageHeaderEnd(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewDefaultFITSImageBScale(t *testing.T) {
var img = NewFITSImage(2, 600, 800, 65535)

Expand All @@ -70,6 +86,8 @@ func TestNewDefaultFITSImageBScale(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewFITSImageFromReader(t *testing.T) {
// Attempt to open the file from the given filepath:
file, err := os.Open("../../samples/noise16.fits")
Expand Down Expand Up @@ -114,6 +132,8 @@ func TestNewFITSImageFromReader(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewFITSImageFrom2DDataID(t *testing.T) {
var ex = [][]uint32{
{1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6},
Expand Down Expand Up @@ -145,6 +165,8 @@ func TestNewFITSImageFrom2DDataID(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewFITSImageFrom2DDataPixels(t *testing.T) {
var ex = [][]uint32{
{1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6},
Expand Down Expand Up @@ -176,6 +198,8 @@ func TestNewFITSImageFrom2DDataPixels(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewFITSImageFrom2DDataData(t *testing.T) {
var ex = [][]uint32{
{1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6},
Expand Down Expand Up @@ -207,6 +231,8 @@ func TestNewFITSImageFrom2DDataData(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewFITSImageFrom2DDataWriteFloatData(t *testing.T) {
data, bounds := GetTestDataFromImage()

Expand All @@ -233,6 +259,8 @@ func TestNewFITSImageFrom2DDataWriteFloatData(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewFITSImageFrom2DDataWrite(t *testing.T) {
data, bounds := GetTestDataFromImage()

Expand Down Expand Up @@ -267,6 +295,9 @@ func TestNewFITSImageFrom2DDataWrite(t *testing.T) {
t.Errorf("Error writing image: %s", err)
}
}

/*****************************************************************************************************************/

func TestNewFITSImageFrom2DStats(t *testing.T) {
data, bounds := GetTestDataFromImage()

Expand Down Expand Up @@ -303,6 +334,8 @@ func TestNewFITSImageFrom2DStats(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewFITSRead(t *testing.T) {
var fit = NewFITSImage(2, 1, 1, 65535)

Expand Down Expand Up @@ -370,6 +403,8 @@ func TestNewFITSRead(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewFITSFromFile(t *testing.T) {
var fit = NewFITSImage(2, 1, 1, 65535)

Expand Down Expand Up @@ -413,6 +448,8 @@ func TestNewFITSFromFile(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewFindStarsFrom2DData(t *testing.T) {
data, bounds := GetTestDataFromImage()

Expand Down Expand Up @@ -441,6 +478,8 @@ func TestNewFindStarsFrom2DData(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewAddObservationEntry(t *testing.T) {
data, bounds := GetTestDataFromImage()

Expand Down Expand Up @@ -528,6 +567,8 @@ func TestNewAddObservationEntry(t *testing.T) {
}
}

/*****************************************************************************************************************/

func TestNewAddObserverEntry(t *testing.T) {
data, bounds := GetTestDataFromImage()

Expand Down Expand Up @@ -584,3 +625,5 @@ func TestNewAddObserverEntry(t *testing.T) {
t.Errorf("Error writing image: %s", err)
}
}

/*****************************************************************************************************************/
Loading
Loading