Skip to main content

fits_io/
slice_image_hdu.rs

1#[cfg(feature = "tokio")]
2use crate::hdu::NormalisedImageStream;
3use crate::hdu::{HDU, ImageHDU};
4use crate::header::card::Card;
5use crate::header::{BayerPattern, Bitpix, Header, ImageType};
6#[cfg(feature = "tokio")]
7use crate::image::Normalizer;
8use crate::image::{Group, Image};
9#[cfg(feature = "tokio")]
10use futures::StreamExt;
11#[cfg(feature = "tokio")]
12use futures::stream;
13use std::error::Error;
14use std::sync::Arc;
15use std::time::Duration;
16
17/// An image HDU backed by a buffer rather than a file.
18#[derive(Debug, Clone)]
19pub struct SliceImageHDU {
20    header: Header,
21    /// The whole FITS buffer, shared by every HDU in it.
22    data: Arc<Vec<u8>>,
23    /// Where this HDU's data section starts within `data`.
24    data_offset: usize,
25    /// Data set through the `set_raw_images_*` methods.
26    pending: Option<Vec<u8>>,
27}
28
29impl SliceImageHDU {
30    pub(crate) fn new(header: Header, data: Arc<Vec<u8>>, data_offset: usize) -> Self {
31        Self {
32            header,
33            data,
34            data_offset,
35            pending: None,
36        }
37    }
38
39    /// An HDU holding nothing yet, for a FITS file being built from nothing.
40    pub fn empty() -> Self {
41        Self {
42            header: Header::default(),
43            data: Arc::new(Vec::new()),
44            data_offset: 0,
45            pending: Some(Vec::new()),
46        }
47    }
48
49    /// This HDU's whole data section.
50    pub(crate) fn data_bytes(&self) -> &[u8] {
51        if let Some(pending) = &self.pending {
52            return pending;
53        }
54
55        let len = self.header.data_bytes_len();
56        self.data
57            .get(self.data_offset..)
58            .and_then(|data| data.get(..len))
59            .unwrap_or_default()
60    }
61
62    /// Whether this HDU is an image stored compressed inside a table.
63    fn is_compressed(&self) -> bool {
64        self.header.is_compressed_image()
65    }
66
67    /// The width and height of the image, whether it is stored plainly or
68    /// compressed.
69    fn dimensions(&self) -> (u32, u32) {
70        let axis = |index: usize| {
71            let length = if self.is_compressed() {
72                self.header.compressed_naxis_n(index)
73            } else {
74                self.header.naxis_n(index)
75            };
76
77            length
78                .and_then(|length| u32::try_from(length).ok())
79                .unwrap_or(0)
80        };
81
82        (axis(0), axis(1))
83    }
84
85    /// Decompresses the image this HDU's table stands for, and takes plane
86    /// `index` out of it.
87    ///
88    /// The tiles cover the whole array, cube and all, so a cube is decompressed
89    /// once and the plane that was asked for is cut out of the result.
90    fn read_compressed(&self, index: usize) -> Result<Image, Box<dyn Error + Send + Sync>> {
91        let table = crate::bin_table::BinTable::from_u8(&self.header, self.data_bytes().to_vec())?;
92        let (data, image_header) = crate::image::compression::read_data(&self.header, &table)?;
93
94        let size = self.image_data_size() as usize;
95        let start = size.saturating_mul(index);
96
97        let plane = data
98            .get(start..)
99            .and_then(|rest| rest.get(..size))
100            .unwrap_or_default()
101            .to_vec();
102
103        Image::from_data_and_header(plane, &image_header)
104    }
105
106    fn image_bytes(&self, index: usize) -> &[u8] {
107        let size = self.image_data_size() as usize;
108        let start = size.saturating_mul(index);
109
110        self.data_bytes()
111            .get(start..)
112            .and_then(|data| data.get(..size))
113            .unwrap_or_default()
114    }
115
116    /// The bytes of one group of a random-groups HDU.
117    fn group_bytes(
118        &self,
119        index: usize,
120        len: usize,
121    ) -> Result<Vec<u8>, Box<dyn Error + Send + Sync>> {
122        let start = len * index;
123
124        Ok(self
125            .data_bytes()
126            .get(start..)
127            .and_then(|rest| rest.get(..len))
128            .unwrap_or_default()
129            .to_vec())
130    }
131
132    /// Stores `values` as this HDU's data and brings the header into line with
133    /// the shape they are in.
134    fn set_raw_array<T: Copy, const N: usize>(
135        &mut self,
136        bitpix: Bitpix,
137        shape: &[u32],
138        values: &[T],
139        to_be_bytes: impl Fn(T) -> [u8; N],
140    ) -> Result<(), Box<dyn Error + Send + Sync>> {
141        if shape.is_empty() {
142            return self.clear_images();
143        }
144
145        let mut expected = 1_usize;
146        for length in shape {
147            expected = expected
148                .checked_mul(*length as usize)
149                .ok_or("Array dimensions overflow the address space")?;
150        }
151
152        if values.len() != expected {
153            return Err(format!(
154                "An array of {:?} holds {} values, but {} were given",
155                shape,
156                expected,
157                values.len()
158            )
159            .into());
160        }
161
162        let mut data = Vec::with_capacity(expected * N);
163        for value in values {
164            data.extend_from_slice(&to_be_bytes(*value));
165        }
166
167        self.header.set(Card::Bitpix {
168            value: bitpix,
169            comment: None,
170        });
171        self.header.set(Card::NAxis {
172            value: shape.len() as i64,
173            comment: None,
174        });
175
176        // A leftover NAXISn from a larger array would contradict NAXIS, so every
177        // one of them goes before the new set is written.
178        self.header
179            .remove_prefixed(crate::header::card_keys::PREFIX_NAXIS_N);
180
181        for (index, length) in shape.iter().enumerate() {
182            self.header.set(Card::NAxisN {
183                index,
184                value: *length as i64,
185                comment: None,
186            });
187        }
188
189        self.pending = Some(data);
190
191        Ok(())
192    }
193}
194
195impl HDU for SliceImageHDU {
196    fn header(&self) -> &Header {
197        &self.header
198    }
199
200    fn header_mut(&mut self) -> &mut Header {
201        &mut self.header
202    }
203}
204
205impl ImageHDU for SliceImageHDU {
206    fn image_count(&self) -> usize {
207        // A random-groups HDU's data section is groups of parameters, not a run
208        // of images; `read_group` is how that is read.
209        if self.header.is_random_groups() {
210            return 0;
211        }
212
213        if self.is_compressed() {
214            return self.header.compressed_plane_count();
215        }
216
217        self.header.image_plane_count()
218    }
219
220    fn images_width(&self) -> u32 {
221        self.dimensions().0
222    }
223
224    fn images_height(&self) -> u32 {
225        self.dimensions().1
226    }
227
228    fn images_bayer_pattern(&self) -> Option<BayerPattern> {
229        self.header.bayer_pattern()
230    }
231
232    fn images_type(&self) -> Option<&ImageType> {
233        self.header.image_type()
234    }
235
236    fn images_exposure_time(&self) -> Option<Duration> {
237        self.header
238            .exposure()
239            .or_else(|| self.header.exposure_time())
240    }
241
242    fn read_image(&self, index: usize) -> Result<Option<Image>, Box<dyn Error + Send + Sync>> {
243        if index >= self.image_count() {
244            return Ok(None);
245        }
246
247        if self.is_compressed() {
248            return Ok(Some(self.read_compressed(index)?));
249        }
250
251        let bytes = self.image_bytes(index).to_vec();
252
253        Ok(Some(Image::from_data_and_header(bytes, &self.header)?))
254    }
255
256    /// How many groups this HDU holds, under the random-groups convention.
257    fn group_count(&self) -> usize {
258        if !self.header.is_random_groups() {
259            return 0;
260        }
261
262        self.header.group_count().unwrap_or(0).max(0) as usize
263    }
264
265    fn read_group(&self, index: usize) -> Result<Option<Group>, Box<dyn Error + Send + Sync>> {
266        if index >= ImageHDU::group_count(self) {
267            return Ok(None);
268        }
269
270        let Some(bitpix) = self.header.bitpix() else {
271            return Ok(None);
272        };
273
274        // Each group is its parameters followed by its array, both in the
275        // array's own type.
276        let parameters = self.header.pcount().unwrap_or(0).max(0) as usize;
277        let len = (parameters + self.header.group_array_len()) * bitpix.byte_size();
278
279        let bytes = self.group_bytes(index, len)?;
280
281        Ok(crate::image::decode_group(&self.header, &bytes))
282    }
283
284    fn clear_images(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
285        self.header.set(Card::NAxis {
286            value: 0,
287            comment: None,
288        });
289        self.header
290            .remove_prefixed(crate::header::card_keys::PREFIX_NAXIS_N);
291
292        self.pending = Some(Vec::new());
293
294        Ok(())
295    }
296
297    fn set_raw_array_u8(
298        &mut self,
299        shape: &[u32],
300        values: &[u8],
301    ) -> Result<(), Box<dyn Error + Send + Sync>> {
302        self.set_raw_array(Bitpix::U8, shape, values, u8::to_be_bytes)
303    }
304
305    fn set_raw_array_i16(
306        &mut self,
307        shape: &[u32],
308        values: &[i16],
309    ) -> Result<(), Box<dyn Error + Send + Sync>> {
310        self.set_raw_array(Bitpix::I16, shape, values, i16::to_be_bytes)
311    }
312
313    fn set_raw_array_i32(
314        &mut self,
315        shape: &[u32],
316        values: &[i32],
317    ) -> Result<(), Box<dyn Error + Send + Sync>> {
318        self.set_raw_array(Bitpix::I32, shape, values, i32::to_be_bytes)
319    }
320
321    fn set_raw_array_f32(
322        &mut self,
323        shape: &[u32],
324        values: &[f32],
325    ) -> Result<(), Box<dyn Error + Send + Sync>> {
326        self.set_raw_array(Bitpix::F32, shape, values, f32::to_be_bytes)
327    }
328
329    fn set_raw_array_f64(
330        &mut self,
331        shape: &[u32],
332        values: &[f64],
333    ) -> Result<(), Box<dyn Error + Send + Sync>> {
334        self.set_raw_array(Bitpix::F64, shape, values, f64::to_be_bytes)
335    }
336
337    #[cfg(feature = "tokio")]
338    fn stream_normalised_image(
339        &self,
340        index: usize,
341    ) -> Result<Option<NormalisedImageStream<'_>>, Box<dyn Error + Send + Sync>> {
342        if index >= self.image_count() {
343            return Ok(None);
344        }
345
346        let width = self.images_width();
347        if width == 0 {
348            return Ok(None);
349        }
350
351        // A compressed image has to be put back together before any of it can
352        // be streamed.
353        if self.is_compressed() {
354            let image = self.read_compressed(index)?;
355            let normalised = image.normalized();
356
357            let pixels: Vec<_> = normalised
358                .enumerate_pixels()
359                .map(|(x, y, pixel)| (x, y, pixel[0]))
360                .collect();
361
362            return Ok(Some(stream::iter(pixels).boxed()));
363        }
364
365        let bitpix = self
366            .header
367            .bitpix()
368            .ok_or("Cannot stream an image from a header without a BITPIX card")?;
369        let normalizer = Normalizer::from_header(&self.header)?;
370        let pixel_len = bitpix.byte_size();
371
372        // The whole image is already in memory, so there is nothing to read
373        // incrementally; the stream exists to match the file-backed API.
374        let pixels: Vec<_> = self
375            .image_bytes(index)
376            .chunks_exact(pixel_len)
377            .enumerate()
378            .filter_map(|(index, raw)| {
379                let value = bitpix.read_be(raw)?;
380                let x = (index as u64 % width as u64) as u32;
381                let y = (index as u64 / width as u64) as u32;
382
383                Some((x, y, normalizer.normalize(value)))
384            })
385            .collect();
386
387        Ok(Some(stream::iter(pixels).boxed()))
388    }
389
390    fn image_data_size(&self) -> u64 {
391        let bitpix = if self.is_compressed() {
392            self.header.compressed_bitpix()
393        } else {
394            self.header.bitpix()
395        };
396
397        let Some(bitpix) = bitpix else {
398            return 0;
399        };
400
401        self.images_width() as u64 * self.images_height() as u64 * bitpix.byte_size() as u64
402    }
403
404    fn compress(
405        &mut self,
406        options: &crate::image::compression::CompressionOptions,
407    ) -> Result<(), Box<dyn Error + Send + Sync>> {
408        // Compressing what is already compressed would code the tiles a second
409        // time rather than changing how they are coded.
410        self.decompress()?;
411
412        let (header, data) =
413            crate::image::compression::compress_image(&self.header, self.data_bytes(), options)?;
414
415        self.header = header;
416        self.pending = Some(data);
417
418        Ok(())
419    }
420
421    fn decompress(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
422        if !self.is_compressed() {
423            return Ok(());
424        }
425
426        let (header, data) =
427            crate::image::compression::decompress_image(&self.header, self.data_bytes())?;
428
429        self.header = header;
430        self.pending = Some(data);
431
432        Ok(())
433    }
434}