pub struct Header { /* private fields */ }Expand description
An ordered FITS header unit: Records in appearance order, with strict keyword
access (via Key) and CRUD.
A Header is an in-memory value. set, append, remove, set_many, and
set_comment change it in memory only — nothing is written to disk. Persist it with
update_file to edit an existing file’s header in place (the
common case), or write_to_file to create a new file from a
header you built plus pixel data you already have.
to_header_bytes is the lower-level building block behind
both, for callers who assemble the file bytes themselves.
Equality is semantic (records compare by content, not by retained bytes).
Implementations§
Source§impl Header
impl Header
Sourcepub fn parse(bytes: &[u8]) -> Result<Header>
pub fn parse(bytes: &[u8]) -> Result<Header>
Parse one FITS header unit from raw bytes.
Reads 80-byte cards in order, stops at END, and retains every card (including
commentary, HIERARCH, and unrecognized cards) so untouched cards serialize verbatim.
CONTINUE runs are reassembled into a single logical value. Bytes after END (the data
unit, later HDUs) are ignored — this crate is header-only and never inspects them.
§Examples
use fits_header::Header;
let mut bytes = Vec::new();
for card in ["OBJECT = 'M31 '", "EXPTIME = 120.0", "END"] {
let mut c = card.as_bytes().to_vec();
c.resize(80, b' ');
bytes.extend(c);
}
let header = Header::parse(&bytes).unwrap();
assert_eq!(header.get_str("OBJECT").unwrap(), Some("M31"));
assert_eq!(header.get::<f64>("EXPTIME").unwrap(), Some(120.0));Sourcepub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Header>
pub fn read_from_file<P: AsRef<Path>>(path: P) -> Result<Header>
Read a FITS header from a file on disk.
Reads the whole file and parses it; parsing already stops at END, so
the data unit and any later HDUs are read but never interpreted.
§Examples
use fits_header::Header;
let mut bytes = Vec::new();
for card in ["OBJECT = 'M31 '", "END"] {
let mut c = card.as_bytes().to_vec();
c.resize(80, b' ');
bytes.extend(c);
}
while bytes.len() % fits_header::BLOCK_LEN != 0 {
bytes.push(b' ');
}
bytes.extend_from_slice(&[0u8; 4]); // stand-in pixel data
let path = std::env::temp_dir().join("fits-header-doctest-read_from_file.fits");
std::fs::write(&path, &bytes).unwrap();
let header = Header::read_from_file(&path).unwrap();
assert_eq!(header.get_str("OBJECT").unwrap(), Some("M31"));
std::fs::remove_file(&path).ok();Sourcepub fn update_file<P: AsRef<Path>>(
path: P,
edit: impl FnOnce(&mut Header) -> Result<()>,
) -> Result<()>
pub fn update_file<P: AsRef<Path>>( path: P, edit: impl FnOnce(&mut Header) -> Result<()>, ) -> Result<()>
Edit a FITS file’s header in place, preserving its data unit (and any later HDUs) byte-for-byte.
Reads the whole file, locates the header region by scanning for the END card, parses
only that region, runs edit on it, then writes the re-serialized header back followed
by every byte that came after the original header — untouched, regardless of what edit
did.
The write is atomic and edits the real file in place: it writes a temp file in the
target’s directory and renames it over the target. It follows symlinks (a symlinked
path stays a symlink; its target is edited) and, on Unix, preserves the target’s file
mode. A crash or interruption cannot leave a truncated file.
Errors with FitsError::MissingEnd if the file has no END card, or
FitsError::TruncatedHeader if it has one but ends before the header’s 2880-byte
block is complete.
§Examples
use fits_header::Header;
let mut bytes = Vec::new();
for card in ["OBJECT = 'M31 '", "END"] {
let mut c = card.as_bytes().to_vec();
c.resize(80, b' ');
bytes.extend(c);
}
while bytes.len() % fits_header::BLOCK_LEN != 0 {
bytes.push(b' ');
}
let data = [1u8, 2, 3, 4]; // stand-in pixel data
bytes.extend_from_slice(&data);
let path = std::env::temp_dir().join("fits-header-doctest-update_file.fits");
std::fs::write(&path, &bytes).unwrap();
Header::update_file(&path, |h| {
h.set("OBJECT", "NGC 7000")?;
Ok(())
})
.unwrap();
let after = std::fs::read(&path).unwrap();
let header = Header::parse(&after).unwrap();
assert_eq!(header.get_str("OBJECT").unwrap(), Some("NGC 7000"));
assert_eq!(&after[after.len() - data.len()..], &data, "data unit preserved");
std::fs::remove_file(&path).ok();Sourcepub fn write_to_file<P: AsRef<Path>>(&self, path: P, data: &[u8]) -> Result<()>
pub fn write_to_file<P: AsRef<Path>>(&self, path: P, data: &[u8]) -> Result<()>
Create a new FITS file: the serialized header block followed by data.
This is the convenience for the rarer case where you already have pixel data and
are writing it for the first time. It creates path and errors if it already
exists — via OpenOptions::create_new, which is
race-free — so this method can never overwrite or corrupt an existing file’s
contents. To edit a file that already exists, use update_file
instead.
data is the caller’s own pixel bytes, written immediately after the header block;
pass &[] for a header-only file. This crate is header-only and never fabricates
pixel data itself.
Errors with FitsError::Io if the write fails, including when path already
exists (io::ErrorKind::AlreadyExists).
§Examples
use fits_header::Header;
let mut header = Header::new();
header.set("OBJECT", "M31").unwrap();
let path = std::env::temp_dir().join("fits-header-doctest-write_to_file.fits");
header.write_to_file(&path, &[0u8; 4]).unwrap();
let bytes = std::fs::read(&path).unwrap();
assert_eq!(&bytes[bytes.len() - 4..], &[0u8; 4], "pixel data survived");
let back = Header::parse(&bytes).unwrap();
assert_eq!(back.get_str("OBJECT").unwrap(), Some("M31"));
// Writing to the same path again errors instead of overwriting it.
assert!(header.write_to_file(&path, &[1u8; 4]).is_err());
std::fs::remove_file(&path).ok();Sourcepub fn cards(&self) -> &[Record]
pub fn cards(&self) -> &[Record]
The records in order (read-only escape hatch).
§Examples
let mut h = Header::new();
h.set("OBJECT", "M31").unwrap();
assert_eq!(h.cards()[0].keyword(), Some("OBJECT"));Sourcepub fn iter(&self) -> impl Iterator<Item = &Record>
pub fn iter(&self) -> impl Iterator<Item = &Record>
Iterate the records in order.
§Examples
let mut h = Header::new();
h.set("OBJECT", "M31").unwrap();
h.set("EXPTIME", 120.0).unwrap();
let names: Vec<&str> = h.iter().filter_map(|r| r.keyword()).collect();
assert_eq!(names, vec!["OBJECT", "EXPTIME"]);Sourcepub fn count(&self, name: &str) -> usize
pub fn count(&self, name: &str) -> usize
How many records carry this keyword.
§Examples
let mut h = Header::new();
h.append("HISTORY", "dark subtracted").unwrap();
h.append("HISTORY", "flat fielded").unwrap();
assert_eq!(h.count("HISTORY"), 2);
assert_eq!(h.count("OBJECT"), 0);Sourcepub fn get<T: FromCard>(&self, key: impl Into<Key>) -> Result<Option<T>>
pub fn get<T: FromCard>(&self, key: impl Into<Key>) -> Result<Option<T>>
Read a keyword as T. Err only on an ambiguous bare name; Ok(None) when absent or the
value does not convert; never panics.
§Examples
let mut h = Header::new();
h.set("EXPTIME", 120.0).unwrap();
assert_eq!(h.get::<f64>("EXPTIME").unwrap(), Some(120.0));
assert_eq!(h.get::<i64>("MISSING").unwrap(), None);Sourcepub fn get_str(&self, key: impl Into<Key>) -> Result<Option<&str>>
pub fn get_str(&self, key: impl Into<Key>) -> Result<Option<&str>>
Borrow a keyword’s string value (Str content, non-empty); None for empty or a literal.
§Examples
let mut h = Header::new();
h.set("OBJECT", "M31").unwrap();
h.set("EXPTIME", 120.0).unwrap(); // a Literal, not Str content
assert_eq!(h.get_str("OBJECT").unwrap(), Some("M31"));
assert_eq!(h.get_str("EXPTIME").unwrap(), None);Sourcepub fn get_all<T: FromCard>(&self, name: &str) -> Vec<T>
pub fn get_all<T: FromCard>(&self, name: &str) -> Vec<T>
Every value for a keyword, in order. Unlike get, never errors on a
duplicated keyword — that is the point of calling it.
§Examples
let mut h = Header::new();
h.append("HISTORY", "dark subtracted").unwrap();
h.append("HISTORY", "flat fielded").unwrap();
assert_eq!(
h.get_all::<String>("HISTORY"),
vec!["dark subtracted".to_string(), "flat fielded".to_string()]
);Sourcepub fn set(&mut self, key: impl Into<Key>, value: impl IntoValue) -> Result<()>
pub fn set(&mut self, key: impl Into<Key>, value: impl IntoValue) -> Result<()>
Updates the addressed record (or appends when the unique name is absent) in the
in-memory header; nothing is written to disk — see Header to persist. The
keyword must be FITS-standard (≤8, A-Z 0-9 - _); use set_raw
for vendor keys.
§Examples
let mut h = Header::new();
h.set("OBJECT", "M31").unwrap(); // appends
h.set("OBJECT", "NGC 7000").unwrap(); // updates the in-memory record
assert_eq!(h.count("OBJECT"), 1);
let err = h.set("object", 1); // lowercase is not FITS-standard
assert!(matches!(err, Err(FitsError::InvalidKeyword { .. })));Sourcepub fn append(&mut self, name: &str, value: impl IntoValue) -> Result<()>
pub fn append(&mut self, name: &str, value: impl IntoValue) -> Result<()>
Always add a record to the in-memory header (a value card, or a commentary card
for COMMENT/HISTORY/blank).
§Examples
let mut h = Header::new();
h.append("HISTORY", "dark subtracted").unwrap();
h.append("HISTORY", "flat fielded").unwrap();
assert_eq!(h.get_all::<String>("HISTORY").len(), 2);Sourcepub fn set_comment(
&mut self,
key: impl Into<Key>,
comment: impl Into<String>,
) -> Result<()>
pub fn set_comment( &mut self, key: impl Into<Key>, comment: impl Into<String>, ) -> Result<()>
Set or replace the addressed value card’s inline comment. No-op if the keyword is absent or not a value card.
§Examples
let mut h = Header::new();
h.set("EXPTIME", 120.0).unwrap();
h.set_comment("EXPTIME", "seconds").unwrap();
assert_eq!(h.cards()[0].comment(), Some("seconds"));Sourcepub fn remove(&mut self, key: impl Into<Key>) -> Result<bool>
pub fn remove(&mut self, key: impl Into<Key>) -> Result<bool>
Remove the addressed record from the in-memory header. Returns whether anything was removed.
§Examples
let mut h = Header::new();
h.set("AIRMASS", 1.2).unwrap();
assert!(h.remove("AIRMASS").unwrap());
assert!(!h.remove("AIRMASS").unwrap());Sourcepub fn set_many<K, V>(
&mut self,
entries: impl IntoIterator<Item = (K, V)>,
) -> Result<()>
pub fn set_many<K, V>( &mut self, entries: impl IntoIterator<Item = (K, V)>, ) -> Result<()>
Apply several mutations to the in-memory header atomically: validate every entry first, then apply all or none.
§Examples
let mut h = Header::new();
h.set_many([("FILTER", "Ha"), ("TELESCOP", "EdgeHD 8")]).unwrap();
// A rejected batch leaves the header untouched.
assert!(h.set_many([("GAIN", "1"), ("TOOLONGKEY", "2")]).is_err());
assert_eq!(h.count("GAIN"), 0);Sourcepub fn remove_many<K: Into<Key>>(
&mut self,
keys: impl IntoIterator<Item = K>,
) -> Result<usize>
pub fn remove_many<K: Into<Key>>( &mut self, keys: impl IntoIterator<Item = K>, ) -> Result<usize>
Remove several keys atomically (validation only guards ambiguity). Returns the count removed.
§Examples
let mut h = Header::new();
h.set("OBJECT", "M31").unwrap();
h.set("EXPTIME", 120.0).unwrap();
assert_eq!(h.remove_many(["OBJECT", "MISSING"]).unwrap(), 1);
assert_eq!(h.count("OBJECT"), 0);Sourcepub fn to_header_bytes(&self) -> Vec<u8> ⓘ
pub fn to_header_bytes(&self) -> Vec<u8> ⓘ
Serialize the header block only (cards, END, padded to a 2880 multiple).
This is the lower-level building block: the bytes alone, for callers who assemble
the rest of the file themselves. To write a header plus pixel data straight to a
new file, use write_to_file instead — it wraps this and
errors rather than overwriting an existing path.
update_file edits an existing file’s header while preserving
its data unit.
§Examples
let mut h = Header::new();
h.set("OBJECT", "M31").unwrap();
let bytes = h.to_header_bytes();
assert_eq!(bytes.len() % fits_header::BLOCK_LEN, 0);