Skip to main content

Crate xisf_header

Crate xisf_header 

Source
Expand description

§xisf-header

CI Crates.io Docs.rs License

Rust crate that reads and writes XISF (Extensible Image Serialization Format) image-file headers: it extracts the embedded FITS keywords and XISF <Property> elements, supports create/read/update/delete on both, and serializes a header back into an XISF container.

The crate is header-only: it parses and emits the 16-byte XISF preamble plus the UTF-8 XML header, and never reads image/pixel data.

§Features

  • Parse an XISF header from bytes or a file with Header::parse / Header::read_from_file. The XISF0100 signature, the little-endian XML-length field (capped at 8 MiB), and UTF-8 encoding are validated.
  • Strict keyword access. A bare name must be unique or the accessor returns Error::Ambiguous; repeated keywords (e.g. HISTORY) are addressed with an (name, n) key or the get_all/count helpers.
  • Typed reads and writes. One generic get::<T> over the open FromField trait (String, f64, i64, u32, bool, and a date/time), with get_str/get_f64/… wrappers; writes take impl IntoValue, so the Rust type chooses string vs. bare-literal formatting.
  • <Property> round-trip. XISF properties keep their type, comment, and format attributes verbatim. A String property written as child text (<Property id=…>text</Property>) is read the same as the attribute form, and writes normalize it to a value= attribute. Values are stored raw (XISF properties are not FITS-quoted).
  • Two write paths. Edit an existing file in place with update_file — the common case — which splices only the changed keywords/properties into the file’s raw bytes (byte-exact and data-preserving). To create a new file from pixel data you already have, use write_to_file (errors rather than overwriting an existing path); or emit just the header block with to_header_bytes(&hints) for full control over assembly.
  • Enumerate and bulk-edit. Read keywords in document order with keywords/iter; apply atomic batches with set_many/remove_many, and clear every occurrence of a repeated name with remove_all.
  • No unsafe. Dependencies are pure Rust (no C/sys crates): quick-xml, thiserror, time, and optional serde. MSRV 1.82.

§Install

[dependencies]
xisf-header = "0.2"

§Optional features

  • serde — derive Serialize/Deserialize on Header, FitsKeyword, Property, and the value types:

    xisf-header = { version = "0.2", features = ["serde"] }

§Usage

§Parse a header and read keywords

Header::parse reads a byte buffer into a Header.

use xisf_header::Header;

let bytes = std::fs::read("frame.xisf")?;
let header = Header::parse(&bytes)?;

// Typed reads are strict: `?` surfaces a duplicate-keyword ambiguity; the inner
// `Option` is `None` when the keyword is absent or unreadable as that type.
let exposure = header.get_f64("EXPTIME")?;
let image_type = header.get_str("IMAGETYP")?;

// XISF <Property> access.
let focal_length_m = header.property_get::<f64>("Instrument:Telescope:FocalLength");

§Create, read, update, delete

Header::new starts empty; set, set_comment, set_with_comment, and remove edit it in place.

use xisf_header::Header;

let mut header = Header::new();

// `set` upserts: update a unique keyword in place, or append when absent. The
// Rust type of the value chooses its on-disk form — strings are quoted,
// numbers and logicals are bare literals.
header.set("IMAGETYP", "Master Dark")?;
header.set_comment("IMAGETYP", "Type of image")?;
header.set("EXPTIME", 300.0)?;
header.set("GAIN", 100_i64)?;

assert_eq!(header.get_str("IMAGETYP")?, Some("Master Dark"));
assert_eq!(header.get_i64("GAIN")?, Some(100));

header.set("EXPTIME", 600.0)?; // update
header.remove("GAIN")?; // delete

§Repeated keywords

append adds an occurrence unconditionally; read every occurrence with get_all, and select, update, or remove one with an (name, n) key.

use xisf_header::Header;

let mut header = Header::new();
header.append("HISTORY", "reduced with siril")?;
header.append("HISTORY", "stacked 20x300s")?;

// A bare name is ambiguous once it repeats — select an occurrence instead.
assert!(header.get_str("HISTORY").is_err());
assert_eq!(header.get_str(("HISTORY", 1))?, Some("stacked 20x300s"));
assert_eq!(header.count("HISTORY"), 2);

// Read every occurrence in order.
assert_eq!(
    header.get_all::<String>("HISTORY"),
    ["reduced with siril", "stacked 20x300s"],
);
// Update one occurrence in place.
header.set(("HISTORY", 0), "reduced with siril v2")?;
assert_eq!(header.get_str(("HISTORY", 0))?, Some("reduced with siril v2"));
// Remove one occurrence (or clear them all with `remove_all`).
header.remove(("HISTORY", 0))?;
assert_eq!(header.count("HISTORY"), 1);

§FITS keywords vs XISF properties

Two different metadata namespaces:

  • FITS keywords (set/get/append) — embedded <FITSKeyword name=… value=… comment=…/> elements carried over for FITS compatibility. Names are ≤ 8 uppercase ASCII characters and can repeat (e.g. HISTORY). Use these for metadata other FITS-aware tools need to read.
  • XISF properties (set_property/property/set_property_with_type) — native XISF <Property> elements. Ids are colon-delimited and hierarchical (e.g. Observation:Object:Name), carry an explicit XISF type, and are unique per id. Use these for XISF-native structured metadata.

§XISF properties

set_property and set_property_with_type write a Property entry; remove_property deletes one by id.

use xisf_header::Header;

let mut header = Header::new();

// Plain `set_property` creates a `String` property; an explicit XISF type is
// kept on the property and survives round-trips.
header.set_property("Observation:Object:Name", "NGC 7000")?;
header.set_property_with_type("Instrument:Telescope:FocalLength", "0.53", "Float32")?;

assert_eq!(header.property("Observation:Object:Name"), Some("NGC 7000"));
assert_eq!(header.property_get::<f64>("Instrument:Telescope:FocalLength"), Some(0.53));
assert_eq!(header.properties()["Instrument:Telescope:FocalLength"].type_, "Float32");

§Controlled numeric formatting

Fixed and Sci wrap an f64 for fixed-point or scientific-notation output; both implement IntoValue.

use xisf_header::{Fixed, Header};

let mut header = Header::new();
header.set("EXPTIME", Fixed(300.0, 2))?; // fixed-point, 2 decimals
assert_eq!(header.get_str("EXPTIME")?, Some("300.00"));

§Edit a file’s header in place

The common path: change a keyword or property on an existing XISF file without touching its pixel data or unmodeled XML.

Header::update_file reads a file’s header, applies an edit closure, and splices the result back into the file. It is byte-exact and data-preserving: everything outside the edited <FITSKeyword>/<Property> elements — unmodeled XML (Metadata, Resolution, thumbnails, …), whitespace, and the attached pixel data — survives untouched, and a no-op edit reproduces the file byte-for-byte. If an edit changes the header’s length, the <Image location> offset is recomputed and the original data is moved (unchanged) to the new offset.

use xisf_header::Header;

Header::update_file("out.xisf", |h| {
    h.set("OBJECT", "NGC 7000")?;
    Ok(())
})?;

update_file targets the common single-image layout: exactly one <Image location="attachment:…"> element. A file with zero or multiple attachments (e.g. a Thumbnail alongside the Image) is rejected with Error::Unsupported rather than risking data loss.

§Create a new file

Use this only when you’re assembling a fresh XISF file from pixel data you already have — not for editing one that exists (that’s update_file above). Header::write_to_file writes the preamble, the XML header (<Image> filled in from StructuralHints), and your data bytes to a new file; it errors if path already exists rather than overwriting it, and never fabricates pixel data — data is always the caller’s own.

use xisf_header::{Header, StructuralHints};

let mut header = Header::new();
header.set("IMAGETYP", "Master Dark")?;

let hints = StructuralHints::default(); // 1x1x1 8-bit grayscale = 1 byte
let data = [0u8]; // the caller's own pixel data
header.write_to_file("out.xisf", &hints, &data)?;

let reloaded = Header::read_from_file("out.xisf")?;
assert_eq!(reloaded, header);

§Documentation

  • Quickstart guide — a task-oriented walkthrough backed by examples/quickstart.rs.
  • docs.rs/xisf-header — full API documentation generated from the source doc comments, published for every release (all features enabled). Build it locally with cargo doc --no-deps --all-features --open. Every public item is documented; CI fails the build on missing or broken documentation.

§License

License: MPL 2.0

This project is licensed under the Mozilla Public License 2.0 — see LICENSE for details.

You can use this library in closed-source projects. If you modify any of the source files in this library, the modified files must be made available under the MPL-2.0 when distributed.

Re-exports§

pub use time;

Modules§

guide
Quickstart guide

Structs§

FitsKeyword
A single FITS keyword extracted from (or destined for) an XISF header: a name, a value, and an optional comment.
Fixed
Write a float in fixed-point notation with decimals fractional digits.
Header
A parsed XISF header: an ordered list of FitsKeywords plus a map of XISF <Property> elements.
Literal
Write a value as a bare literal exactly as given (escape hatch for pre-formatted or vendor-specific tokens).
Property
A single XISF <Property>: its type, value text, and the optional comment and format attributes, all kept verbatim.
Sci
Write a float in scientific notation with sig_digits significant digits, using the FITS E exponent marker.
StructuralHints
Geometry hints used when serializing a standalone container. A Header stores only keywords and properties — never image structure — so these hints always supply the <Image> element’s geometry, sampleFormat, and colorSpace. Defaults to a minimal 1×1 8-bit grayscale image.

Enums§

Error
Errors that can occur while parsing, reading, or writing an XISF header.
Key
Addresses a keyword either by name (strict: it must be unique) or by a specific occurrence when a name repeats.
Value
A keyword value together with how it should be serialized.

Traits§

FromField
Interpret a keyword’s value text as a Rust type.
IntoValue
Produce a Value for a write, with the on-disk kind chosen by the Rust type: strings become quoted string values, numbers and logicals become bare literals. Use Literal, Fixed, or Sci for controlled formatting.

Type Aliases§

Result
A specialized Result alias for header operations.