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
impl Header
Sourcepub fn new() -> Self
pub fn new() -> Self
Create an empty header.
use xisf_header::Header;
let header = Header::new();
assert_eq!(header.keywords().len(), 0);Sourcepub fn get<'a, T: FromField>(
&self,
key: impl Into<Key<'a>>,
) -> Result<Option<T>>
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()));Sourcepub fn get_str<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<&str>>
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"));Sourcepub fn get_f64<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<f64>>
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));Sourcepub fn get_i64<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<i64>>
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));Sourcepub fn get_u32<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<u32>>
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));Sourcepub fn get_bool<'a>(&self, key: impl Into<Key<'a>>) -> Result<Option<bool>>
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));Sourcepub fn get_datetime<'a>(
&self,
key: impl Into<Key<'a>>,
) -> Result<Option<PrimitiveDateTime>>
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);Sourcepub fn get_all<T: FromField>(&self, name: &str) -> Vec<T>
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"]
);Sourcepub fn count(&self, name: &str) -> usize
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);Sourcepub fn keywords(&self) -> &[FitsKeyword]
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");Sourcepub fn iter(&self) -> Iter<'_, FitsKeyword>
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"]);Sourcepub fn set<'a>(
&mut self,
key: impl Into<Key<'a>>,
value: impl IntoValue,
) -> Result<()>
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);Sourcepub fn append(&mut self, name: &str, value: impl IntoValue) -> Result<()>
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);Sourcepub fn set_comment<'a>(
&mut self,
key: impl Into<Key<'a>>,
comment: impl Into<String>,
) -> Result<bool>
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");Sourcepub fn set_with_comment<'a>(
&mut self,
key: impl Into<Key<'a>>,
value: impl IntoValue,
comment: impl Into<String>,
) -> Result<()>
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");Sourcepub fn remove<'a>(&mut self, key: impl Into<Key<'a>>) -> Result<bool>
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);Sourcepub fn remove_all(&mut self, name: &str) -> usize
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);Sourcepub fn set_many<'a, V, I>(&mut self, entries: I) -> Result<()>
pub fn set_many<'a, V, I>(&mut self, entries: I) -> Result<()>
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"));Sourcepub fn remove_many<'a, I: IntoIterator<Item = &'a str>>(
&mut self,
names: I,
) -> Result<usize>
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);Sourcepub fn properties(&self) -> &BTreeMap<String, Property>
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);Sourcepub fn property(&self, id: &str) -> Option<&str>
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"));Sourcepub fn property_get<T: FromField>(&self, id: &str) -> Option<T>
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)
);Sourcepub fn set_property(
&mut self,
id: impl Into<String>,
value: impl Into<String>,
) -> Result<()>
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");Sourcepub fn set_property_with_type(
&mut self,
id: impl Into<String>,
value: impl Into<String>,
type_: impl Into<String>,
) -> Result<()>
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"
);Sourcepub fn remove_property(&mut self, id: &str) -> bool
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
impl Header
Sourcepub fn parse(bytes: &[u8]) -> Result<Self>
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"));Sourcepub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Self>
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
impl Header
Sourcepub fn to_header_bytes(&self, hints: &StructuralHints) -> Vec<u8> ⓘ
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);Sourcepub fn write_to_file<P: AsRef<Path>>(
&self,
path: P,
hints: &StructuralHints,
data: &[u8],
) -> Result<()>
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());Sourcepub fn update_file<P: AsRef<Path>>(
path: P,
edit: impl FnOnce(&mut Self) -> Result<()>,
) -> Result<()>
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"));