Skip to main content

Header

Struct Header 

Source
pub struct Header { /* private fields */ }
Expand description

A parsed XISF header: an ordered list of FitsKeywords plus a map of XISF <Property> elements.

Keyword access is strict: a bare name must be unique, or the accessor returns Error::Ambiguous. Repeated keywords are reached with an (name, n) key or the get_all/count helpers. Keyword order is preserved; property iteration is ordered by id, not document order.

This struct is an in-memory model only: mutating it (set, append, remove, set_property, …) changes nothing on disk. Persist the result with write_to_file to create a new file, or update_file to splice the change into an existing one.

use xisf_header::Header;

let mut header = Header::new();
header.set("IMAGETYP", "Master Dark")?;
header.set("EXPTIME", 300.0)?;
assert_eq!(header.get_str("IMAGETYP")?, Some("Master Dark"));

Implementations§

Source§

impl Header

Source

pub fn new() -> Self

Create an empty header.

use xisf_header::Header;

let header = Header::new();
assert_eq!(header.keywords().len(), 0);
Source

pub fn get<'a, T: FromField>( &self, key: impl Into<Key<'a>>, ) -> Result<Option<T>>

Interpret the addressed keyword’s value as T.

Returns Ok(None) when the keyword is absent or its value cannot be read as T, and Error::Ambiguous when a bare name matches more than one keyword.

§Errors

Error::Ambiguous on a duplicated bare name; Error::IndexOutOfRange for an (name, n) index past the last occurrence.

use xisf_header::Header;

let mut header = Header::new();
header.set("OBJECT", "NGC 7000")?;
assert_eq!(header.get::<String>("OBJECT")?, Some("NGC 7000".to_owned()));
Source

pub fn get_str<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<&str>>

The addressed keyword’s raw value text.

§Errors

See Header::get.

use xisf_header::Header;

let mut header = Header::new();
header.set("IMAGETYP", "Master Dark")?;
assert_eq!(header.get_str("IMAGETYP")?, Some("Master Dark"));
Source

pub fn get_f64<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<f64>>

The addressed keyword’s value as an f64.

§Errors

See Header::get.

use xisf_header::Header;

let mut header = Header::new();
header.set("EXPTIME", 300.0)?;
assert_eq!(header.get_f64("EXPTIME")?, Some(300.0));
Source

pub fn get_i64<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<i64>>

The addressed keyword’s value as an i64 (accepts 20 and 20.0).

§Errors

See Header::get.

use xisf_header::Header;

let mut header = Header::new();
header.set("GAIN", 100_i64)?;
assert_eq!(header.get_i64("GAIN")?, Some(100));
Source

pub fn get_u32<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<u32>>

The addressed keyword’s value as a u32.

§Errors

See Header::get.

use xisf_header::Header;

let mut header = Header::new();
header.set("XBINNING", 2_u32)?;
assert_eq!(header.get_u32("XBINNING")?, Some(2));
Source

pub fn get_bool<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<bool>>

The addressed keyword’s value as a bool (FITS T/F).

§Errors

See Header::get.

use xisf_header::Header;

let mut header = Header::new();
header.set("SIMPLE", true)?;
assert_eq!(header.get_bool("SIMPLE")?, Some(true));
Source

pub fn get_datetime<'a>( &self, key: impl Into<Key<'a>>, ) -> Result<Option<PrimitiveDateTime>>

The addressed keyword’s value as a civil date/time.

§Errors

See Header::get.

use xisf_header::Header;

let mut header = Header::new();
header.set("DATE-OBS", "2026-07-11T22:15:03")?;
let observed = header.get_datetime("DATE-OBS")?.unwrap();
assert_eq!(observed.year(), 2026);
Source

pub fn get_all<T: FromField>(&self, name: &str) -> Vec<T>

Every value for name, in order, that reads as T.

use xisf_header::Header;

let mut header = Header::new();
header.append("HISTORY", "reduced with siril").unwrap();
header.append("HISTORY", "stacked 20x300s").unwrap();
assert_eq!(
    header.get_all::<String>("HISTORY"),
    vec!["reduced with siril", "stacked 20x300s"]
);
Source

pub fn count(&self, name: &str) -> usize

How many keywords carry name (case-insensitive).

use xisf_header::Header;

let mut header = Header::new();
header.append("HISTORY", "reduced with siril").unwrap();
header.append("HISTORY", "stacked 20x300s").unwrap();
assert_eq!(header.count("HISTORY"), 2);
Source

pub fn keywords(&self) -> &[FitsKeyword]

All keywords in document order.

use xisf_header::Header;

let mut header = Header::new();
header.set("GAIN", 100_i64).unwrap();
assert_eq!(header.keywords().len(), 1);
assert_eq!(header.keywords()[0].name, "GAIN");
Source

pub fn iter(&self) -> Iter<'_, FitsKeyword>

Iterate the keywords in document order.

use xisf_header::Header;

let mut header = Header::new();
header.set("IMAGETYP", "Master Dark").unwrap();
header.set("EXPTIME", 300.0).unwrap();
let names: Vec<&str> = header.iter().map(|k| k.name.as_str()).collect();
assert_eq!(names, ["IMAGETYP", "EXPTIME"]);
Source

pub fn set<'a>( &mut self, key: impl Into<Key<'a>>, value: impl IntoValue, ) -> Result<()>

Set a keyword’s value: update in place when the name is unique, append when absent. The existing comment is preserved.

This changes the in-memory header only — nothing is written to disk until you call write_to_file (new file) or update_file (existing file).

For XISF-native structured metadata, see set_property.

§Errors

Error::Ambiguous when a bare name is duplicated (use (name, n) or set_at-style selection), Error::IndexOutOfRange for a bad occurrence index, or Error::InvalidName when creating an invalid keyword.

use xisf_header::Header;

let mut header = Header::new();
header.set("IMAGETYP", "Master Dark")?; // absent: appended
header.set("IMAGETYP", "Master Flat")?; // unique: updated in place
assert_eq!(header.get_str("IMAGETYP")?, Some("Master Flat"));
assert_eq!(header.keywords().len(), 1);
Source

pub fn append(&mut self, name: &str, value: impl IntoValue) -> Result<()>

Append a keyword unconditionally (allowing duplicate names). This is how commentary keywords such as HISTORY are built up.

This changes the in-memory header only — nothing is written to disk until you call write_to_file (new file) or update_file (existing file).

§Errors

Error::InvalidName if name is not a valid keyword.

use xisf_header::Header;

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

pub fn set_comment<'a>( &mut self, key: impl Into<Key<'a>>, comment: impl Into<String>, ) -> Result<bool>

Set (or clear, with "") the comment on the addressed keyword. Returns true if a keyword was found.

§Errors

See Header::get: Error::Ambiguous on a duplicated bare name, Error::IndexOutOfRange for an out-of-range (name, n) index.

use xisf_header::Header;

let mut header = Header::new();
header.set("IMAGETYP", "Master Dark")?;
assert!(header.set_comment("IMAGETYP", "Type of image")?);
assert_eq!(header.keywords()[0].comment, "Type of image");
Source

pub fn set_with_comment<'a>( &mut self, key: impl Into<Key<'a>>, value: impl IntoValue, comment: impl Into<String>, ) -> Result<()>

Set a keyword’s value and comment together.

§Errors

See Header::set.

use xisf_header::Header;

let mut header = Header::new();
header.set_with_comment("GAIN", 100_i64, "Sensor gain")?;
assert_eq!(header.get_i64("GAIN")?, Some(100));
assert_eq!(header.keywords()[0].comment, "Sensor gain");
Source

pub fn remove<'a>(&mut self, key: impl Into<Key<'a>>) -> Result<bool>

Remove the addressed keyword. Returns true if one was removed.

This changes the in-memory header only — nothing is written to disk until you call write_to_file (new file) or update_file (existing file).

§Errors

See Header::get: Error::Ambiguous on a duplicated bare name, Error::IndexOutOfRange for an out-of-range (name, n) index.

use xisf_header::Header;

let mut header = Header::new();
header.set("GAIN", 100_i64)?;
assert!(header.remove("GAIN")?);
assert_eq!(header.get_i64("GAIN")?, None);
Source

pub fn remove_all(&mut self, name: &str) -> usize

Remove every keyword named name. Returns how many were removed.

use xisf_header::Header;

let mut header = Header::new();
header.append("HISTORY", "reduced with siril").unwrap();
header.append("HISTORY", "stacked 20x300s").unwrap();
assert_eq!(header.remove_all("HISTORY"), 2);
assert_eq!(header.count("HISTORY"), 0);
Source

pub fn set_many<'a, V, I>(&mut self, entries: I) -> Result<()>
where V: IntoValue, I: IntoIterator<Item = (&'a str, V)>,

Apply several single-keyword upserts atomically: validate every entry first, then apply all — or, on any rejection, apply none.

§Errors

Error::InvalidName or Error::Ambiguous for any entry; on error the header is unchanged.

use xisf_header::Header;

let mut header = Header::new();
header.set_many([("IMAGETYP", "Master Dark"), ("OBJECT", "NGC 7000")])?;
assert_eq!(header.get_str("IMAGETYP")?, Some("Master Dark"));
assert_eq!(header.get_str("OBJECT")?, Some("NGC 7000"));
Source

pub fn remove_many<'a, I: IntoIterator<Item = &'a str>>( &mut self, names: I, ) -> Result<usize>

Remove several keywords atomically. Returns how many were removed.

§Errors

Error::Ambiguous if any name is duplicated; on error the header is unchanged.

use xisf_header::Header;

let mut header = Header::new();
header.set_many([("IMAGETYP", "Master Dark"), ("OBJECT", "NGC 7000")])?;
assert_eq!(header.remove_many(["IMAGETYP", "OBJECT"])?, 2);
Source

pub fn properties(&self) -> &BTreeMap<String, Property>

All <Property> entries, keyed by id. Iteration is ordered by id, not by document order.

use xisf_header::Header;

let mut header = Header::new();
header.set_property("Observation:Object:Name", "NGC 7000").unwrap();
assert_eq!(header.properties().len(), 1);
Source

pub fn property(&self, id: &str) -> Option<&str>

A property’s raw value text by id.

use xisf_header::Header;

let mut header = Header::new();
header.set_property("Observation:Object:Name", "NGC 7000").unwrap();
assert_eq!(header.property("Observation:Object:Name"), Some("NGC 7000"));
Source

pub fn property_get<T: FromField>(&self, id: &str) -> Option<T>

A property value interpreted as T.

use xisf_header::Header;

let mut header = Header::new();
header
    .set_property_with_type("Instrument:Telescope:FocalLength", "0.53", "Float32")
    .unwrap();
assert_eq!(
    header.property_get::<f64>("Instrument:Telescope:FocalLength"),
    Some(0.53)
);
Source

pub fn set_property( &mut self, id: impl Into<String>, value: impl Into<String>, ) -> Result<()>

Insert or update a property’s value. An existing property keeps its type, comment, and format; a new one is created with type String.

For embedded FITS header keywords, see set.

§Errors

Error::InvalidName if id is not a valid XISF property id.

use xisf_header::Header;

let mut header = Header::new();
header.set_property("Observation:Object:Name", "NGC 7000")?;
assert_eq!(header.properties()["Observation:Object:Name"].type_, "String");
Source

pub fn set_property_with_type( &mut self, id: impl Into<String>, value: impl Into<String>, type_: impl Into<String>, ) -> Result<()>

Insert or update a property with an explicit XISF type (e.g. Float32, TimePoint). An existing property keeps its comment and format.

§Errors

Error::InvalidName if id is not a valid XISF property id.

use xisf_header::Header;

let mut header = Header::new();
header.set_property_with_type("Instrument:Telescope:FocalLength", "0.53", "Float32")?;
assert_eq!(
    header.properties()["Instrument:Telescope:FocalLength"].type_,
    "Float32"
);
Source

pub fn remove_property(&mut self, id: &str) -> bool

Remove a property by id. Returns true if it existed.

use xisf_header::Header;

let mut header = Header::new();
header.set_property("Observation:Object:Name", "NGC 7000").unwrap();
assert!(header.remove_property("Observation:Object:Name"));
assert!(header.property("Observation:Object:Name").is_none());
Source§

impl Header

Source

pub fn parse(bytes: &[u8]) -> Result<Self>

Parse an XISF header from raw container bytes.

Validates the 16-byte preamble — bytes 0–7 are the XISF0100 signature, bytes 8–11 are the little-endian XML-header length (capped at 8 MiB), bytes 12–15 are reserved and ignored — then decodes the UTF-8 XML header and extracts every <FITSKeyword> and <Property>. Pixel/ attachment data beyond the header is never read.

§Errors

Returns Error::TooSmall if the input is truncated, Error::InvalidSignature on a bad signature, Error::HeaderTooLarge if the declared header exceeds the cap, or an XML/UTF-8 error if the header itself is malformed.

use xisf_header::{Header, StructuralHints};

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

let bytes = header.to_header_bytes(&StructuralHints::default());
let parsed = Header::parse(&bytes)?;
assert_eq!(parsed.get_str("IMAGETYP")?, Some("Master Dark"));
Source

pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self>

Read and parse the header of an XISF file, reading only the preamble and XML header — never the pixel/attachment payload.

§Errors

Propagates I/O errors and any Header::parse error.

use xisf_header::{Header, StructuralHints};

let path = std::env::temp_dir().join("xisf-header-doctest-read.xisf");
let mut header = Header::new();
header.set("IMAGETYP", "Master Dark")?;
std::fs::write(&path, header.to_header_bytes(&StructuralHints::default()))?;

let reloaded = Header::read_from_file(&path)?;
assert_eq!(reloaded.get_str("IMAGETYP")?, Some("Master Dark"));
Source§

impl Header

Source

pub fn to_header_bytes(&self, hints: &StructuralHints) -> Vec<u8>

Serialize the header block — the 16-byte preamble plus the UTF-8 XML header, with no data attached. The <Image location> points at the byte offset immediately after the header, sized per hints, where a caller assembling a new file appends the image data itself.

Header::parse(&header.to_header_bytes(&hints)) round-trips back to header.

use xisf_header::{Header, StructuralHints};

let mut header = Header::new();
header.set("IMAGETYP", "Master Dark").unwrap();
let hints = StructuralHints::default();

let header_only = header.to_header_bytes(&hints);
assert_eq!(Header::parse(&header_only).unwrap(), header);
Source

pub fn write_to_file<P: AsRef<Path>>( &self, path: P, hints: &StructuralHints, data: &[u8], ) -> Result<()>

Write a complete XISF container to a new file: the preamble + XML header (with the <Image> element from hints, and a location attachment SIZE equal to data.len()) followed by data verbatim.

data is the caller’s own pixel bytes — this crate never fabricates image data. Pass &[] for a header-only container (SIZE 0).

This creates a new file only: it fails with Error::Io (ErrorKind::AlreadyExists) if path already exists, rather than overwriting it. To edit an existing file’s header in place, use update_file instead.

§Errors

Propagates any I/O error opening or writing path, including AlreadyExists when the path already exists.

use xisf_header::{Header, StructuralHints};

let path = std::env::temp_dir().join("xisf-header-doctest-write.xisf");
let mut header = Header::new();
header.set("IMAGETYP", "Master Dark")?;
let hints = StructuralHints::default();
let data = [0u8; 4]; // the caller's own pixel bytes

header.write_to_file(&path, &hints, &data)?;

let bytes = std::fs::read(&path)?;
assert_eq!(&bytes[bytes.len() - 4..], &data);
let reloaded = Header::read_from_file(&path)?;
assert_eq!(reloaded, header);

// A second write to the same path never clobbers it.
assert!(header.write_to_file(&path, &hints, &data).is_err());
Source

pub fn update_file<P: AsRef<Path>>( path: P, edit: impl FnOnce(&mut Self) -> Result<()>, ) -> Result<()>

Read a file’s header, apply edit, and splice the result back into the file in place: byte-exact and data-preserving. Every byte outside the edited <FITSKeyword>/<Property> elements — unmodeled XML (Metadata, Resolution, thumbnails, …), whitespace, and the attached data block — survives untouched. A no-op edit reproduces the input file byte-for-byte. If the header’s XML length changes, the <Image location="attachment:OFFSET:SIZE"> offset is recomputed and the original data bytes are moved (unchanged) to the new offset; SIZE never changes.

This requires 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), or whose edit needs to add elements to a self-closing <Image/>, is rejected with Error::Unsupported rather than risking data loss.

The write is atomic — a sibling temp file is renamed over the target — and follows symlinks (a symlinked path stays a symlink to the same target) and preserves the target’s unix permission mode.

§Errors

Propagates any error from reading or re-parsing the file, from edit, or Error::Unsupported for a layout the splice can’t safely target. On error the file is left untouched.

use xisf_header::{Header, StructuralHints};

let path = std::env::temp_dir().join("xisf-header-doctest-update.xisf");
let mut header = Header::new();
header.set("IMAGETYP", "Master Dark")?;
let hints = StructuralHints::default(); // 1x1x1 UInt8 = 1 byte of data
let mut container = header.to_header_bytes(&hints);
container.push(0xAB); // the caller's own pixel data
std::fs::write(&path, &container)?;

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

assert_eq!(std::fs::read(&path)?.last(), Some(&0xAB)); // pixel data preserved
let edited = Header::read_from_file(&path)?;
assert_eq!(edited.get_str("OBJECT")?, Some("NGC 7000"));

Trait Implementations§

Source§

impl Clone for Header

Source§

fn clone(&self) -> Header

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Header

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Header

Source§

fn default() -> Header

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for Header

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for Header

Source§

impl PartialEq for Header

Source§

fn eq(&self, other: &Header) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for Header

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for Header

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.