Skip to main content

fits_io/hdu/
image_hdu.rs

1use crate::hdu::hdu::HDU;
2use crate::header::{BayerPattern, ImageType};
3use crate::image::{Group, Image};
4use image::{ImageBuffer, Luma, Primitive};
5use std::error::Error;
6use std::fmt;
7
8/// A stream of `(x, y, value)` triples, with `value` normalised to `0.0..=1.0`.
9#[cfg(feature = "tokio")]
10pub type NormalisedImageStream<'a> = futures::stream::BoxStream<'a, (u32, u32, f64)>;
11
12/// An HDU whose data section is an image, or a stack of them.
13pub trait ImageHDU: HDU + fmt::Debug + Send + Sync {
14    /// How many images this HDU holds.
15    fn image_count(&self) -> usize;
16    /// The width of every image here, from NAXIS1.
17    fn images_width(&self) -> u32;
18    /// The height of every image here, from NAXIS2.
19    fn images_height(&self) -> u32;
20    /// The colour filter layout over the sensor, or `None` if it was monochrome.
21    fn images_bayer_pattern(&self) -> Option<BayerPattern>;
22    /// Whether these are light, dark, flat or bias frames.
23    fn images_type(&self) -> Option<&ImageType>;
24    /// How long the exposure lasted.
25    fn images_exposure_time(&self) -> Option<std::time::Duration>;
26    /// Reads one image, or `None` past the last one.
27    fn read_image(&self, index: usize) -> Result<Option<Image>, Box<dyn Error + Send + Sync>>;
28
29    /// How many groups this HDU holds, under the random-groups convention.
30    ///
31    /// Zero for an ordinary image HDU, which is nearly all of them; see
32    /// [`Group`](crate::image::Group).
33    fn group_count(&self) -> usize {
34        0
35    }
36
37    /// Reads one group, or `None` past the last one.
38    fn read_group(&self, index: usize) -> Result<Option<Group>, Box<dyn Error + Send + Sync>>;
39
40    /// Replaces the images with 8-bit ones, taking their size from the first.
41    fn set_images_u8(
42        &mut self,
43        images: &[&ImageBuffer<Luma<u8>, Vec<u8>>],
44    ) -> Result<(), Box<dyn Error + Send + Sync>> {
45        get_raw_data_from_image(self, images, Self::set_raw_images_u8)
46    }
47    /// Replaces the images with signed 16-bit ones.
48    fn set_images_i16(
49        &mut self,
50        images: &[&ImageBuffer<Luma<i16>, Vec<i16>>],
51    ) -> Result<(), Box<dyn Error + Send + Sync>> {
52        get_raw_data_from_image(self, images, Self::set_raw_images_i16)
53    }
54    /// Replaces the images with signed 32-bit ones.
55    fn set_images_i32(
56        &mut self,
57        images: &[&ImageBuffer<Luma<i32>, Vec<i32>>],
58    ) -> Result<(), Box<dyn Error + Send + Sync>> {
59        get_raw_data_from_image(self, images, Self::set_raw_images_i32)
60    }
61    /// Replaces the images with single precision floating point ones.
62    fn set_images_f32(
63        &mut self,
64        images: &[&ImageBuffer<Luma<f32>, Vec<f32>>],
65    ) -> Result<(), Box<dyn Error + Send + Sync>> {
66        get_raw_data_from_image(self, images, Self::set_raw_images_f32)
67    }
68    /// Replaces the images with double precision floating point ones.
69    fn set_images_f64(
70        &mut self,
71        images: &[&ImageBuffer<Luma<f64>, Vec<f64>>],
72    ) -> Result<(), Box<dyn Error + Send + Sync>> {
73        get_raw_data_from_image(self, images, Self::set_raw_images_f64)
74    }
75
76    /// Removes every image, leaving a header-only HDU.
77    fn clear_images(&mut self) -> Result<(), Box<dyn Error + Send + Sync>>;
78
79    /// Replaces the data with an array of `shape`, given as raw 8-bit samples.
80    ///
81    /// `shape` is the NAXISn cards in order, fastest-varying axis first, so a
82    /// stack of three 640 by 480 images is `[640, 480, 3]`. Any number of axes
83    /// is allowed; [`set_raw_images_u8`] is the everyday two- and three-axis
84    /// form of the same thing.
85    ///
86    /// The header's BITPIX and NAXISn cards are brought into line with the data.
87    ///
88    /// [`set_raw_images_u8`]: ImageHDU::set_raw_images_u8
89    fn set_raw_array_u8(
90        &mut self,
91        shape: &[u32],
92        values: &[u8],
93    ) -> Result<(), Box<dyn Error + Send + Sync>>;
94
95    /// Replaces the data with an array of `shape`, given as raw signed 16-bit
96    /// samples.
97    fn set_raw_array_i16(
98        &mut self,
99        shape: &[u32],
100        values: &[i16],
101    ) -> Result<(), Box<dyn Error + Send + Sync>>;
102
103    /// Replaces the data with an array of `shape`, given as raw signed 32-bit
104    /// samples.
105    fn set_raw_array_i32(
106        &mut self,
107        shape: &[u32],
108        values: &[i32],
109    ) -> Result<(), Box<dyn Error + Send + Sync>>;
110
111    /// Replaces the data with an array of `shape`, given as raw single precision
112    /// samples.
113    fn set_raw_array_f32(
114        &mut self,
115        shape: &[u32],
116        values: &[f32],
117    ) -> Result<(), Box<dyn Error + Send + Sync>>;
118
119    /// Replaces the data with an array of `shape`, given as raw double precision
120    /// samples.
121    fn set_raw_array_f64(
122        &mut self,
123        shape: &[u32],
124        values: &[f64],
125    ) -> Result<(), Box<dyn Error + Send + Sync>>;
126
127    /// Replaces the images with raw 8-bit samples, `width` by `height` each.
128    ///
129    /// The header's BITPIX and NAXISn cards are brought into line with them.
130    fn set_raw_images_u8(
131        &mut self,
132        width: u32,
133        height: u32,
134        images: &[&[u8]],
135    ) -> Result<(), Box<dyn Error + Send + Sync>> {
136        set_planes(self, width, height, images, Self::set_raw_array_u8)
137    }
138    /// Replaces the images with raw signed 16-bit samples.
139    fn set_raw_images_i16(
140        &mut self,
141        width: u32,
142        height: u32,
143        images: &[&[i16]],
144    ) -> Result<(), Box<dyn Error + Send + Sync>> {
145        set_planes(self, width, height, images, Self::set_raw_array_i16)
146    }
147    /// Replaces the images with raw signed 32-bit samples.
148    fn set_raw_images_i32(
149        &mut self,
150        width: u32,
151        height: u32,
152        images: &[&[i32]],
153    ) -> Result<(), Box<dyn Error + Send + Sync>> {
154        set_planes(self, width, height, images, Self::set_raw_array_i32)
155    }
156    /// Replaces the images with raw single precision samples.
157    fn set_raw_images_f32(
158        &mut self,
159        width: u32,
160        height: u32,
161        images: &[&[f32]],
162    ) -> Result<(), Box<dyn Error + Send + Sync>> {
163        set_planes(self, width, height, images, Self::set_raw_array_f32)
164    }
165    /// Replaces the images with raw double precision samples.
166    fn set_raw_images_f64(
167        &mut self,
168        width: u32,
169        height: u32,
170        images: &[&[f64]],
171    ) -> Result<(), Box<dyn Error + Send + Sync>> {
172        set_planes(self, width, height, images, Self::set_raw_array_f64)
173    }
174
175    /// Streams one image as `(x, y, value)` triples, normalised to `0.0..=1.0`.
176    #[cfg(feature = "tokio")]
177    fn stream_normalised_image(
178        &self,
179        index: usize,
180    ) -> Result<Option<NormalisedImageStream<'_>>, Box<dyn Error + Send + Sync>>;
181    /// How many bytes one image occupies.
182    fn image_data_size(&self) -> u64;
183
184    /// Whether this HDU's image is stored tile-compressed inside a table.
185    fn is_compressed(&self) -> bool {
186        self.header().is_compressed_image()
187    }
188
189    /// Stores this HDU's image tile-compressed, as `fpack` would.
190    ///
191    /// The image is cut into tiles, each tile is compressed on its own, and the
192    /// result is written as a binary table whose header says what image it
193    /// stands for. Everything that reads an image here goes on working — the
194    /// HDU is still an image as far as this crate is concerned, and it is
195    /// written out as a compressed image extension.
196    ///
197    /// Compressing an already compressed HDU decompresses it first, so that
198    /// changing the settings does not compress the tiles twice.
199    ///
200    /// ```no_run
201    /// # #[cfg(feature = "fs")]
202    /// # fn example() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
203    /// use fits_io::Fits;
204    /// use fits_io::fs::FsFits;
205    /// use fits_io::hdu::ImageHDU;
206    /// use fits_io::image::compression::{Compression, CompressionOptions};
207    ///
208    /// let mut fits = FsFits::open("observation.fits".as_ref())?;
209    ///
210    /// fits.primary_hdu_mut()
211    ///     .compress(&CompressionOptions::new(Compression::Rice))?;
212    ///
213    /// let smaller = fits.to_vec()?;
214    /// # Ok(())
215    /// # }
216    /// # fn main() {}
217    /// ```
218    ///
219    /// # Errors
220    ///
221    /// Returns an error when the image cannot be compressed the way the options
222    /// ask — Rice coding a floating point image without quantising it, say —
223    /// and when its data cannot be read.
224    fn compress(
225        &mut self,
226        options: &crate::image::compression::CompressionOptions,
227    ) -> Result<(), Box<dyn Error + Send + Sync>>;
228
229    /// Stores this HDU's image plainly again, undoing [`ImageHDU::compress`].
230    ///
231    /// An HDU that was not compressed is left alone.
232    ///
233    /// # Errors
234    ///
235    /// Returns an error when the compressed data cannot be read.
236    fn decompress(&mut self) -> Result<(), Box<dyn Error + Send + Sync>>;
237}
238
239/// Lays a set of equally sized planes out as one array and stores it.
240///
241/// This is what the `set_raw_images_*` methods are: a shape of two axes for one
242/// image and three for a stack of them.
243fn set_planes<T: Copy, S: ImageHDU + ?Sized>(
244    hdu: &mut S,
245    width: u32,
246    height: u32,
247    images: &[&[T]],
248    set: impl FnOnce(&mut S, &[u32], &[T]) -> Result<(), Box<dyn Error + Send + Sync>>,
249) -> Result<(), Box<dyn Error + Send + Sync>> {
250    if images.is_empty() {
251        return hdu.clear_images();
252    }
253
254    let pixels = (width as usize)
255        .checked_mul(height as usize)
256        .ok_or("Image dimensions overflow the address space")?;
257
258    // Checked here rather than left to the shape, so that a ragged set says
259    // which image is the wrong size.
260    for (index, image) in images.iter().enumerate() {
261        if image.len() != pixels {
262            return Err(format!(
263                "Image {} has {} pixels, but a {}x{} image has {}",
264                index,
265                image.len(),
266                width,
267                height,
268                pixels
269            )
270            .into());
271        }
272    }
273
274    let mut shape = vec![width, height];
275    if images.len() > 1 {
276        shape.push(images.len() as u32);
277    }
278
279    let values: Vec<T> = images
280        .iter()
281        .flat_map(|image| image.iter().copied())
282        .collect();
283
284    set(hdu, &shape, &values)
285}
286
287fn get_raw_data_from_image<
288    'a,
289    T: Primitive,
290    S: ImageHDU + ?Sized,
291    CB: FnOnce(&mut S, u32, u32, &[&[T]]) -> Result<(), Box<dyn Error + Send + Sync>>,
292>(
293    hdu: &mut S,
294    images: &'a [&'a ImageBuffer<Luma<T>, Vec<T>>],
295    callback: CB,
296) -> Result<(), Box<dyn Error + Send + Sync>> {
297    if images.is_empty() {
298        hdu.clear_images()?;
299        Ok(())
300    } else {
301        let width = images[0].width();
302        let height = images[0].height();
303
304        let data = images
305            .iter()
306            .map(|image| image.iter().as_slice())
307            .collect::<Vec<_>>();
308
309        callback(hdu, width, height, &data)
310    }
311}