1use binrw::binrw;
2use getset::{Getters, Setters};
3use std::fmt::{Display, Formatter};
4
5use crate::{FixedString, Frame, FrameFormat, PixelDepth, Timestamp};
6
7#[binrw]
18#[brw(little)]
19#[derive(Getters, Setters)]
20pub struct Ser {
21 #[brw(magic = b"LUCAM-RECORDER")]
29 file_id: (),
30
31 #[getset(get = "pub", set = "pub")]
39 lu_id: i32,
40
41 color_id: ColorId,
47
48 little_endian: PixelEndian,
57
58 image_width: i32,
66
67 image_height: i32,
75
76 pixel_depth_per_plane: PixelDepth,
84
85 #[br(temp)]
93 #[bw(calc(image_data.len() as _))]
94 frame_count: i32,
95
96 #[getset(get = "pub", set = "pub")]
104 observer: FixedString<40>,
105
106 #[getset(get = "pub", set = "pub")]
114 instrument: FixedString<40>,
115
116 #[getset(get = "pub", set = "pub")]
124 telescope: FixedString<40>,
125
126 #[getset(get = "pub")]
137 datetime: Timestamp,
138
139 #[getset(get = "pub")]
147 datetime_utc: Timestamp,
148
149 #[br(args {
155 count: frame_count as usize,
156 inner: FrameFormat::new(
157 color_id.clone(),
158 pixel_depth_per_plane.clone(),
159 little_endian.clone(),
160 image_width as _,
161 image_height as _,
162 )
163 })]
164 image_data: Vec<Frame>,
165
166 #[br(count = match frame_count { f if datetime.is_valid() => f as usize, _ => 0})]
183 trailer: Vec<Timestamp>,
184}
185
186pub struct DatesMut<'a> {
188 datetime: &'a mut Timestamp,
189 datetime_utc: &'a mut Timestamp,
190 frame_count: usize,
191 frame_times: &'a mut Vec<Timestamp>,
192}
193
194#[derive(Debug)]
195pub enum DateErrors {
196 InvalidDatetime,
197 IncorrectTimestamps,
198}
199
200pub struct FramesMut<'a> {
202 format: FrameFormat,
203 has_trailer: bool,
204 frames: &'a mut Vec<Frame>,
205 frame_times: &'a mut Vec<Timestamp>,
206}
207
208#[derive(Debug)]
209pub enum FramePushErrors {
210 Incompatible,
211 TimestampExpected,
212 TimestampUnexpected,
213}
214
215#[binrw]
217#[allow(non_camel_case_types)]
218#[derive(Debug, Clone)]
219pub enum ColorId {
220 #[brw(magic = 0i32)]
221 MONO,
222 #[brw(magic = 8i32)]
223 BAYER_RGGB,
224 #[brw(magic = 9i32)]
225 BAYER_GRBG,
226 #[brw(magic = 10i32)]
227 BAYER_GBRG,
228 #[brw(magic = 11i32)]
229 BAYER_BGGR,
230 #[brw(magic = 16i32)]
231 BAYER_CYYM,
232 #[brw(magic = 17i32)]
233 BAYER_YCMY,
234 #[brw(magic = 18i32)]
235 BAYER_YMCY,
236 #[brw(magic = 19i32)]
237 BAYER_MYYC,
238 #[brw(magic = 100i32)]
239 RGB,
240 #[brw(magic = 101i32)]
241 BGR,
242}
243
244#[binrw]
246#[allow(non_camel_case_types)]
247#[derive(Clone)]
248pub enum PixelEndian {
249 #[brw(magic = 0i32)]
250 Big,
251 #[brw(magic = 1i32)]
252 Little,
253}
254
255impl Ser {
256 pub fn with_format(format: FrameFormat) -> Self {
257 Self {
258 file_id: (),
259 lu_id: 0,
260 color_id: format.color().clone(),
261 little_endian: format.endian().clone(),
262 image_width: *format.width() as _,
263 image_height: *format.height() as _,
264 pixel_depth_per_plane: format.depth().clone(),
265 observer: FixedString::default(),
266 instrument: FixedString::default(),
267 telescope: FixedString::default(),
268 datetime: Timestamp::default(),
269 datetime_utc: Timestamp::default(),
270 image_data: Vec::new(),
271 trailer: Vec::new(),
272 }
273 }
274
275 pub fn frame_format(&self) -> FrameFormat {
277 FrameFormat::new(
278 self.color_id.clone(),
279 self.pixel_depth_per_plane.clone(),
280 self.little_endian.clone(),
281 self.image_width as _,
282 self.image_height as _,
283 )
284 }
285
286 pub fn has_frame_timestamps(&self) -> bool {
289 self.datetime.is_valid()
290 }
291
292 pub fn frame_count(&self) -> usize {
294 self.image_data.len()
295 }
296
297 pub fn iter(&self) -> impl Iterator<Item = (&Frame, Option<&Timestamp>)> {
300 let mut times = self.trailer.iter();
301 self.image_data
302 .iter()
303 .zip(std::iter::from_fn(move || Some(times.next())))
304 }
305
306 pub fn into_iter(self) -> impl Iterator<Item = (Frame, Option<Timestamp>)> {
309 let mut times = self.trailer.into_iter();
310 self.image_data
311 .into_iter()
312 .zip(std::iter::from_fn(move || Some(times.next())))
313 }
314
315 pub fn frames_mut<'a>(&'a mut self) -> FramesMut<'a> {
317 FramesMut {
318 format: self.frame_format(),
319 has_trailer: self.has_frame_timestamps(),
320 frames: &mut self.image_data,
321 frame_times: &mut self.trailer,
322 }
323 }
324
325 pub fn dates_mut<'a>(&'a mut self) -> DatesMut<'a> {
327 DatesMut {
328 datetime: &mut self.datetime,
329 datetime_utc: &mut self.datetime_utc,
330 frame_count: self.image_data.len(),
331 frame_times: &mut self.trailer,
332 }
333 }
334}
335
336impl<'a> FramesMut<'a> {
337 pub fn format(&self) -> &FrameFormat {
339 &self.format
340 }
341
342 pub fn try_push(
351 &mut self,
352 frame: Frame,
353 timestamp: Option<Timestamp>,
354 ) -> Result<(), FramePushErrors> {
355 if self.format != frame {
356 return Err(FramePushErrors::Incompatible);
357 }
358
359 if self.has_trailer {
360 match timestamp {
361 Some(ts) => self.frame_times.push(ts),
362 None => return Err(FramePushErrors::TimestampExpected),
363 }
364 } else {
365 match timestamp {
366 Some(_) => return Err(FramePushErrors::TimestampUnexpected),
367 None => (),
368 }
369 };
370
371 self.frames.push(frame);
372
373 Ok(())
374 }
375}
376
377impl<'a> DatesMut<'a> {
378 pub fn clear(&mut self) {
380 *self.datetime = Timestamp::default();
381 *self.datetime_utc = Timestamp::default();
382 self.frame_times.clear();
383 }
384
385 pub fn try_set_dates(
391 &mut self,
392 datetime: Timestamp,
393 datetime_utc: Timestamp,
394 frame_times: Vec<Timestamp>,
395 ) -> Result<(), DateErrors> {
396 if !datetime.is_valid() || !datetime_utc.is_valid() {
397 return Err(DateErrors::InvalidDatetime);
398 }
399
400 if frame_times.len() != self.frame_count {
401 return Err(DateErrors::IncorrectTimestamps);
402 }
403
404 *self.datetime = datetime;
405 *self.datetime_utc = datetime_utc;
406 *self.frame_times = frame_times;
407
408 Ok(())
409 }
410}
411
412impl std::error::Error for FramePushErrors {}
413
414impl Display for FramePushErrors {
415 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
416 match self {
417 FramePushErrors::Incompatible => {
418 f.write_str("Frame incompatible. All frames must have the same format.")
419 }
420 FramePushErrors::TimestampExpected => f.write_str(
421 "Timestamps MUST be added for each frame when the SER's datetime is valid.",
422 ),
423 FramePushErrors::TimestampUnexpected => {
424 f.write_str("Timestamps MAY NOT be added when the SER's datetime is invalid.")
425 }
426 }
427 }
428}
429
430impl std::error::Error for DateErrors {}
431
432impl Display for DateErrors {
433 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
434 match self {
435 DateErrors::InvalidDatetime => f.write_str("Cannot set datetime to invalid timestamp. To clear the datetimes, use the `.clear()` method."),
436 DateErrors::IncorrectTimestamps => f.write_str("Frame timestamps do not match the number of the frames."),
437 }
438 }
439}
440
441impl PixelEndian {
442 pub fn host_endian() -> Self {
443 if cfg!(target_endian = "big") {
444 PixelEndian::Big
445 } else {
446 PixelEndian::Little
447 }
448 }
449}
450
451impl From<FrameFormat> for Ser {
452 fn from(value: FrameFormat) -> Self {
453 Self::with_format(value)
454 }
455}