moonastrofits

    A pure MoonBit FITS astronomy data parser, validator, and toolkit.

    fits
    astronomy
    scientific-data
    binary-format
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    6 hours ago
    Downloads
    1

    #MoonAstroFITS

    CI

    MoonAstroFITS is a pure MoonBit toolkit for reading, validating, and eventually writing Flexible Image Transport System (FITS) astronomy data. The reusable core is byte-oriented and does not depend on a filesystem or foreign runtime, so the same parser can run on MoonBit's portable targets.

    #Implemented

    • strict 80-byte FITS card parsing;
    • = value indicators, undefined values, comments, and commentary cards;
    • quoted string awareness when separating values from comments;
    • ordered and repeated header keywords;
    • END detection and 2880-byte header padding validation;
    • source-aware structural errors;
    • parsing from byte zero or an explicit absolute offset.
    • primary and extension HDU traversal with absolute byte boundaries;
    • required SIMPLE/XTENSION, BITPIX, NAXIS, and NAXISn ordering;
    • checked image geometry and 2880-byte data-unit padding;
    • overflow and truncated-payload rejection before data access.
    • big-endian 8/16/32/64-bit integer image decoding;
    • explicit BSCALE, BZERO, and BLANK physical-value semantics.
    • big-endian IEEE binary32/binary64 image decoding;
    • preservation of floating-point NaN, infinities, and signed zero.
    • deterministic 80-byte card and 2880-byte header encoding;
    • lexical parse → encode → parse header round trips.
    • range-checked big-endian integer pixel encoding;
    • byte-exact integer encode → decode round trips.
    • IEEE binary32/binary64 pixel encoding with big-endian output;
    • binary32 finite-overflow rejection and floating encode → decode tests.
    • complete primary-HDU assembly with exact payload-length validation;
    • standards-aligned space header padding and zero data padding.
    • IMAGE and generic extension-HDU assembly;
    • required extension keyword ordering and parameter-byte sizing.
    • fixed-width BINTABLE schema parsing for A, L, X, B, I, J, K, E, and D column formats, including zero-repeat empty fields;
    • typed binary-table row decoding with repeat counts, names, and checked column offsets.
    • byte-exact fixed-width binary-table row encoding with ASCII padding and numeric range checks;
    • complete row-set encoding for direct extension-HDU assembly.
    • tri-state logical column round trips using T, F, and null bytes.
    • MSB-first packed bit-array column round trips with zeroed padding bits.
    • checked P/Q variable-length binary-table array decoding from the heap, including optional THEAP gaps and declared maximum lengths.
    • P/Q heap encoding with deterministic row-major packing, capacity checks, and byte-exact reproduction of the official 64-bit sample's table data units.
    • complete BINTABLE extension construction from field formats and typed rows, deriving NAXIS1, NAXIS2, PCOUNT, descriptor offsets, and FITS padding.

    Run the checks and the in-memory example:

    moon check --deny-warn moon test --deny-warn moon run cmd/main

    Run the real-file preflight against the NASA/GSFC 64-bit FITS sample with the portable Node.js/JS host example:

    moon run cmd/preflight-js --target js -- conformance/fixtures/test64bit1.fit

    For a complete comparison against the 15 Astropy reference rows, run:

    moon run cmd/preflight-js --target js -- --check-sample conformance/fixtures/test64bit1.fit

    Generate a standalone two-HDU FITS example for independent validation:

    moon run cmd/table-demo-js --target js -- table-demo.fits fitscheck --compliance --ignore-missing table-demo.fits fitsverify table-demo.fits

    The Astropy and FITSverify comparison, expected values, and fixture provenance are recorded in conformance/README.md.

    The next milestones will add more independent real-file fixtures and broader FITS binary-table types.

    #Scope and origin

    This is an original MoonBit implementation based on the public FITS Standard 4.0. It is not a port. The repository includes one unmodified NASA/GSFC sample only as an interoperability fixture; its source and hash are documented in conformance/fixtures/README.md.

    See ECOSYSTEM.md for the dated public overlap check and DESIGN.md for format invariants.

    #License

    Apache-2.0.

    BinaryTable

    pub(all) struct BinaryTable {
    columns : Array[TableColumn]
    row_length : Int
    row_count : Int
    data_offset : Int
    heap_offset : Int
    heap_length : Int
    } derive(Eq,
    Debug
    )

    Row and heap geometry plus column schema for a BINTABLE extension.

    BinaryTable::equal

    fn BinaryTable::equal(BinaryTable, BinaryTable) -> Bool

    BinaryTable::not_equal

    fn BinaryTable::not_equal(x : BinaryTable, y : BinaryTable) -> Bool

    Card

    pub(all) struct Card {
    keyword : String
    value : String?
    comment : String?
    record_index : Int
    } derive(Eq,
    Debug
    )

    One 80-byte FITS header card.

    value is the trimmed lexical value after the = indicator. An empty string represents the standard's undefined value. Cards without a value indicator use None and can retain commentary in comment.

    Card::equal

    fn Card::equal(Card, Card) -> Bool

    Explicitly preserve derived methods under the current MoonBit toolchain.

    Card::not_equal

    fn Card::not_equal(x : Card, y : Card) -> Bool

    Explicitly preserve derived methods under the current MoonBit toolchain.

    Card::to_repr

    FitsError

    pub(all) enum FitsError {
    Truncated(Int, Int, Int)
    NonAscii(Int, Byte)
    InvalidKeyword(Int, String)
    InvalidCard(Int, String)
    MissingEnd(Int)
    InvalidPadding(Int, Byte)
    MissingKeyword(String)
    InvalidInteger(String, String)
    InvalidLogical(String, String)
    InvalidString(String, String)
    InvalidRequiredOrder(Int, String, String)
    UnsupportedBitpix(Int)
    InvalidAxis(Int, Int)
    InvalidParameter(String, Int)
    SizeOverflow(String)
    NotImageHdu(Int)
    UnsupportedIntegerBitpix(Int)
    UnsupportedFloatingBitpix(Int)
    UnsupportedImageLayout(Int)
    InvalidReal(String, String)
    CardTooLong(Int, Int)
    InvalidSampleCount(Int, Int)
    PixelOutOfRange(Int, Int64, Int)
    BlankOutOfRange(Int64, Int)
    FloatingPixelOverflow(Int, Int)
    InvalidDataLength(Int, Int)
    NotBinaryTable(Int)
    UnsupportedTableFormat(String, String)
    InvalidTableRowWidth(Int, Int)
    TableRowOutOfRange(Int, Int)
    InvalidTableRowCount(Int, Int)
    InvalidTableCellCount(Int, Int)
    InvalidTableCell(Int, String)
    InvalidHeapDescriptor(Int, Int, String)
    } derive(Eq,
    Debug
    )

    Source-aware failures from FITS structural parsing.

    FitsError::equal

    fn FitsError::equal(FitsError, FitsError) -> Bool

    FitsError::message

    fn FitsError::message(self : FitsError) -> String

    FitsError::not_equal

    fn FitsError::not_equal(x : FitsError, y : FitsError) -> Bool

    FitsFile

    pub(all) struct FitsFile {
    hdus : Array[Hdu]
    } derive(Eq,
    Debug
    )

    A structurally validated sequence of FITS HDUs.

    FitsFile::equal

    fn FitsFile::equal(FitsFile, FitsFile) -> Bool

    FitsFile::not_equal

    fn FitsFile::not_equal(x : FitsFile, y : FitsFile) -> Bool

    FitsFile::primary

    fn FitsFile::primary(self : FitsFile) -> Hdu?

    FitsFile::to_repr

    FloatingImage

    pub(all) struct FloatingImage {
    axes : Array[Int]
    raw : Array[Double]
    bscale : Double
    bzero : Double
    } derive(Eq,
    Debug
    )

    Decoded IEEE floating-point image samples and FITS linear scaling.

    FloatingImage::equal

    FloatingImage::not_equal

    fn FloatingImage::not_equal(x : FloatingImage, y : FloatingImage) -> Bool

    FloatingImage::physical_values

    fn FloatingImage::physical_values(self : FloatingImage) -> Array[Double]

    Apply FITS linear scaling while preserving IEEE special values.

    Hdu

    pub(all) struct Hdu {
    index : Int
    kind : HduKind
    header : Header
    image : ImageLayout?
    data_offset : Int
    data_length : Int
    next_hdu_offset : Int
    } derive(Eq,
    Debug
    )

    One parsed FITS header/data unit with absolute byte boundaries.

    Hdu::equal

    fn Hdu::equal(Hdu, Hdu) -> Bool

    Hdu::not_equal

    fn Hdu::not_equal(x : Hdu, y : Hdu) -> Bool

    Hdu::to_repr

    HduKind

    pub(all) enum HduKind {
    Primary
    ImageExtension
    OtherExtension(String)
    } derive(Eq,
    Debug
    )

    The structural role of one FITS header/data unit.

    HduKind::equal

    fn HduKind::equal(HduKind, HduKind) -> Bool

    HduKind::not_equal

    fn HduKind::not_equal(x : HduKind, y : HduKind) -> Bool

    HduKind::to_repr

    pub(all) struct Header {
    cards : Array[Card]
    data_offset : Int
    } derive(Eq,
    Debug
    )

    A parsed FITS header and the absolute offset of its following data unit.

    Header::equal

    fn Header::equal(Header, Header) -> Bool

    Header::get

    fn Header::get(self : Header, keyword : String) -> Card?

    Return the first card with the requested keyword.

    Header::get_all

    fn Header::get_all(self : Header, keyword : String) -> Array[Card]

    Return every card with the requested keyword in source order.

    Header::not_equal

    fn Header::not_equal(x : Header, y : Header) -> Bool

    Header::to_repr

    ImageLayout

    pub(all) struct ImageLayout {
    bitpix : Int
    axes : Array[Int]
    pixel_count : Int
    byte_length : Int
    } derive(Eq,
    Debug
    )

    Checked geometry for a primary array or IMAGE extension.

    ImageLayout::equal

    fn ImageLayout::equal(ImageLayout, ImageLayout) -> Bool

    ImageLayout::not_equal

    fn ImageLayout::not_equal(x : ImageLayout, y : ImageLayout) -> Bool

    IntegerImage

    pub(all) struct IntegerImage {
    axes : Array[Int]
    raw : Array[Int64]
    bscale : Double
    bzero : Double
    blank : Int64?
    } derive(Eq,
    Debug
    )

    Decoded integer image samples and their FITS linear-scaling metadata.

    IntegerImage::equal

    IntegerImage::not_equal

    fn IntegerImage::not_equal(x : IntegerImage, y : IntegerImage) -> Bool

    IntegerImage::physical_values

    fn IntegerImage::physical_values(self : IntegerImage) -> Array[Double?]

    Convert every non-blank raw sample to its physical FITS value.

    TableColumn

    pub(all) struct TableColumn {
    index : Int
    name : String?
    format : String
    repeat : Int
    offset : Int
    width : Int
    kind : TableColumnKind
    max_elements : Int?
    } derive(Eq,
    Debug
    )

    A validated FITS binary-table column, fixed-width or heap-backed.

    TableColumn::equal

    fn TableColumn::equal(TableColumn, TableColumn) -> Bool

    TableColumn::not_equal

    fn TableColumn::not_equal(x : TableColumn, y : TableColumn) -> Bool

    TableColumnKind

    pub(all) enum TableColumnKind {
    Ascii
    Logical
    Bit
    UnsignedByte
    Int16
    Int32
    Int64
    Float32
    Float64
    Variable32(TableColumnKind)
    Variable64(TableColumnKind)
    } derive(Eq,
    Debug
    )

    The fixed-width storage representation of one FITS binary-table column.

    TableColumnKind::equal

    TableColumnKind::not_equal

    fn TableColumnKind::not_equal(x : TableColumnKind, y : TableColumnKind) -> Bool

    TableField

    pub(all) struct TableField {
    name : String?
    format : String
    } derive(Eq,
    Debug
    )

    User-facing schema for constructing a binary-table extension.

    TableField::equal

    fn TableField::equal(TableField, TableField) -> Bool

    TableField::not_equal

    fn TableField::not_equal(x : TableField, y : TableField) -> Bool

    TableValue

    pub(all) enum TableValue {
    Text(String)
    Logicals(Array[Bool?])
    Bits(Array[Bool])
    Integers(Array[Int64])
    Reals(Array[Double])
    } derive(Eq,
    Debug
    )

    One decoded binary-table cell.

    TableValue::equal

    fn TableValue::equal(TableValue, TableValue) -> Bool

    TableValue::not_equal

    fn TableValue::not_equal(x : TableValue, y : TableValue) -> Bool

    decode_binary_row

    fn decode_binary_row(data : Bytes, table : BinaryTable, row_index : Int) -> Result[Array[TableValue], FitsError]

    Decode one zero-based binary-table row into typed cells.

    decode_floating_image

    fn decode_floating_image(data : Bytes, hdu : Hdu) -> Result[FloatingImage, FitsError]

    Decode a contiguous IEEE floating-point primary array or IMAGE extension.

    BITPIX=-32 values are widened exactly from IEEE binary32 to Double; BITPIX=-64 values retain their binary64 representation. NaN, infinities, and signed zero are preserved in raw before FITS linear scaling.

    decode_integer_image

    fn decode_integer_image(data : Bytes, hdu : Hdu) -> Result[IntegerImage, FitsError]

    Decode a contiguous integer primary array or IMAGE extension.

    FITS stores samples in big-endian order. BITPIX=8 is unsigned; 16, 32, and 64-bit samples use two's-complement signed integers. Linear scaling and blank detection are retained explicitly rather than changing the raw data.

    encode_binary_row

    fn encode_binary_row(table : BinaryTable, cells : Array[TableValue]) -> Result[Bytes, FitsError]

    Encode one fixed-width binary-table row in FITS network byte order.

    Text is padded with spaces to its declared width. Numeric cells must supply exactly the column repeat count, and narrowing conversions are checked.

    encode_binary_rows

    fn encode_binary_rows(table : BinaryTable, rows : Array[Array[TableValue]]) -> Result[Bytes, FitsError]

    Encode every row of a fixed-width binary table as one contiguous data unit.

    encode_binary_table_data

    fn encode_binary_table_data(table : BinaryTable, rows : Array[Array[TableValue]]) -> Result[Bytes, FitsError]

    Encode all rows, an optional THEAP gap, and the heap into one BINTABLE data unit. The supplied table's PCOUNT/THEAP geometry is authoritative: heap payloads are packed in row-major order and unused bytes are zero-filled. This function does not write FITS header cards or 2880-byte HDU padding.

    encode_binary_table_extension

    fn encode_binary_table_extension(fields : Array[TableField], rows : Array[Array[TableValue]]) -> Result[Bytes, FitsError]

    Construct a complete BINTABLE extension from column formats and typed rows. Row width, row count, PCOUNT and P/Q heap offsets are derived automatically; the heap is packed without a THEAP gap. The result includes a canonical header and FITS block padding, ready to append after a primary HDU.

    Example

    test {
    let extension = @moonastrofits.encode_binary_table_extension(
    [{ name: Some("FLUX"), format: "1E", }],
    [[@moonastrofits.Reals([1.5])]],
    ).unwrap()
    assert_eq(extension.length(), 5760)
    }

    encode_extension_hdu

    fn encode_extension_hdu(header : Header, data : Bytes) -> Result[Bytes, FitsError]

    Assemble one complete extension HDU from a header and encoded data unit.

    The header must begin with XTENSION and contain ordered BITPIX, NAXIS, each NAXISn, PCOUNT, and GCOUNT cards. Both IMAGE and non-image extensions use the general FITS data-length formula before zero padding.

    encode_floating_pixels

    fn encode_floating_pixels(image : FloatingImage, bitpix : Int) -> Result[Bytes, FitsError]

    Encode floating-point image samples in FITS big-endian byte order.

    BITPIX=-32 performs the standard nearest binary32 conversion and rejects finite values that would become infinity. BITPIX=-64 preserves every binary64 bit. NaN, infinities, and signed zero remain valid FITS samples.

    encode_header

    fn encode_header(header : Header) -> Result[Bytes, FitsError]

    Deterministically encode a parsed FITS header.

    Values retain their lexical representation. Non-string values shorter than 20 bytes are right-aligned in the conventional FITS value field; quoted strings begin immediately after = . Every card is padded to 80 bytes and the completed header is padded with spaces to a 2880-byte boundary.

    encode_integer_pixels

    fn encode_integer_pixels(image : IntegerImage, bitpix : Int) -> Result[Bytes, FitsError]

    Encode raw integer image samples in FITS big-endian byte order.

    The declared axes must contain exactly one entry per raw sample. Values and an optional BLANK sentinel are range-checked for the requested positive integer BITPIX; no clipping or scaling is performed.

    encode_primary_hdu

    fn encode_primary_hdu(header : Header, data : Bytes) -> Result[Bytes, FitsError]

    Assemble one complete primary HDU from a header and encoded data unit.

    Structural cards and the exact unpadded data length are validated with the same rules used by parse_fits. Header padding is ASCII space; data padding is zero. The supplied header's stored data_offset is ignored because the canonical encoded header determines the output offset.

    parse_binary_table

    fn parse_binary_table(hdu : Hdu) -> Result[BinaryTable, FitsError]

    Parse the schema and heap geometry of a BINTABLE extension.

    Supports fixed A/L/X/B/I/J/K/E/D fields and P/Q descriptors for these element types. Complex values remain unsupported.

    parse_fits

    fn parse_fits(data : Bytes) -> Result[FitsFile, FitsError]

    Parse and structurally validate every HDU in a complete FITS byte stream.

    parse_header

    fn parse_header(data : Bytes) -> Result[Header, FitsError]

    Parse a FITS header at byte zero.

    parse_header_at

    fn parse_header_at(data : Bytes, start : Int) -> Result[Header, FitsError]

    Parse a FITS header at an absolute byte offset.