frame_source/
pv_tiff_stack.rs1use std::{io::Cursor, path::Path};
5
6use super::*;
7
8const IJIJINFO_KEY: u16 = 50839;
9const MAGICSTR: &str = "IJIJinfo";
10
11pub fn from_path_pattern(pattern: &str) -> Result<PvTiffStack> {
12 PvTiffStack::new(pattern)
13}
14
15#[derive(Clone, PartialEq)]
16pub struct TiffImage {
17 pub buf: Vec<u8>,
20 pub width: u32,
21 pub height: u32,
22 pub metadata: TiffMetadata,
23}
24
25#[derive(Clone, PartialEq)]
26pub struct TiffMetadata {
27 json: serde_json::Value,
31 framenumber: usize,
33 pub timestamp: std::time::Duration,
40 pub bit_depth: u8,
42}
43
44pub struct PvTiffStack {
49 paths: Vec<std::path::PathBuf>,
50 frame0_time: chrono::DateTime<chrono::FixedOffset>,
52 frame0_timestamp_offset: std::time::Duration,
57 width: u32,
58 height: u32,
59 tiff_image0: TiffImage,
60}
61
62impl FrameDataSource for PvTiffStack {
63 fn width(&self) -> u32 {
64 self.width
65 }
66 fn height(&self) -> u32 {
67 self.height
68 }
69 fn decode_order_iter<'a>(&'a mut self) -> Box<dyn Iterator<Item = Result<FrameData>> + 'a> {
70 Box::new(ImageStackIter::new(self))
71 }
72 fn skip_n_frames(&mut self, n_frames: usize) -> Result<()> {
73 let paths = self.paths.split_off(n_frames);
74 let new_self = Self::new_from_paths(paths)?;
75 *self = new_self;
76 Ok(())
77 }
78 fn frame0_time(&self) -> Option<chrono::DateTime<chrono::FixedOffset>> {
79 Some(self.frame0_time)
80 }
81 fn average_framerate(&self) -> Option<f64> {
82 None
83 }
84 fn estimate_luminance_range(&mut self) -> Result<(u16, u16)> {
85 let n_images = self.paths.len().min(5);
87 let step_size = self.paths.len() / n_images;
88 let (mut low, mut high) = self.tiff_image0.luminance_range()?;
89 for this_frame in self.decode_order_iter().step_by(step_size) {
90 let image = match this_frame?.image {
91 ImageData::Tiff(image) => image,
92 _ => {
93 return Err(crate::Error::ExpectedTiffImage);
94 }
95 };
96 let (this_low, this_high) = image.luminance_range()?;
97 if this_low < low {
98 low = this_low;
99 }
100 if this_high > high {
101 high = this_high;
102 }
103 }
104 Ok((low, high))
105 }
106 fn timestamp_source(&self) -> &str {
107 "PVCAM-FMD-TimestampBofPs"
108 }
109 fn has_timestamps(&self) -> bool {
110 true
111 }
112}
113
114impl PvTiffStack {
115 fn new(pattern: &str) -> Result<Self> {
116 let mut paths = vec![];
117 for path in glob::glob_with(
118 pattern,
119 glob::MatchOptions {
120 case_sensitive: false,
121 require_literal_separator: true,
122 require_literal_leading_dot: true,
123 },
124 )? {
125 paths.push(path?);
126 }
127 if paths.is_empty() {
128 return Err(crate::Error::NoFilesFound);
129 }
130 Self::new_from_paths(paths)
131 }
132 fn new_from_paths(mut paths: Vec<PathBuf>) -> Result<Self> {
133 paths.sort();
134
135 let file0_buf = read_file(&paths[0])?;
136 let frame0_metadata = extract_tiff_metadata(&file0_buf)?;
137
138 let mut frame0_time = frame0_metadata.json["ReceivedTime"]
142 .as_str()
143 .ok_or_else(json_parse_err)?
144 .parse()?;
145
146 let pvcam_fmd_exposure_str =
147 frame0_metadata.json["UserData"]["PVCAM-FMD-ExposureTimePs"]["scalar"]
148 .as_str()
149 .ok_or_else(json_parse_err)?;
150 let exposure_duration =
151 chrono::Duration::from_std(parse_picosecs(pvcam_fmd_exposure_str)?)?;
152 frame0_time -= exposure_duration;
153
154 let frame0_timestamp_offset = frame0_metadata.timestamp;
155 let tiff_image0 = read_tiff_image(&file0_buf, frame0_metadata)?;
156 let width = tiff_image0.width;
157 let height = tiff_image0.height;
158
159 Ok(Self {
160 paths,
161 frame0_time,
162 frame0_timestamp_offset,
163 width,
164 height,
165 tiff_image0,
166 })
167 }
168 pub fn len(&self) -> usize {
169 self.paths.len()
170 }
171 pub fn is_empty(&self) -> bool {
172 self.paths.is_empty()
173 }
174}
175
176struct ImageStackIter<'a> {
177 idx: usize,
178 frame0_timestamp_offset: std::time::Duration,
179 inner: std::slice::Iter<'a, std::path::PathBuf>,
180}
181
182impl<'a> ImageStackIter<'a> {
183 fn new(parent: &'a PvTiffStack) -> Self {
184 Self {
185 idx: 0,
186 frame0_timestamp_offset: parent.frame0_timestamp_offset,
187 inner: parent.paths.iter(),
188 }
189 }
190}
191
192impl Iterator for ImageStackIter<'_> {
193 type Item = Result<FrameData>;
194 fn next(&mut self) -> Option<Self::Item> {
195 let result = self
196 .inner
197 .next()
198 .map(|path| path_to_tiff(path, self.frame0_timestamp_offset, self.idx));
199 self.idx += 1;
200 result
201 }
202 fn size_hint(&self) -> (usize, Option<usize>) {
203 self.inner.size_hint()
204 }
205}
206
207impl TiffImage {
208 fn luminance_range(&self) -> Result<(u16, u16)> {
209 let mut decoder = tiff::decoder::Decoder::new(Cursor::new(&self.buf))?;
212
213 let buf = decoder.read_image()?;
214 let color = decoder.colortype()?;
215
216 let (width, _height) = decoder.dimensions()?;
217 let width: usize = width.try_into()?;
218
219 let mut min = 255;
224 let mut max = 0;
225 match (color, buf) {
226 (tiff::ColorType::Gray(16), tiff::decoder::DecodingResult::U16(vals)) => {
227 for row in vals.chunks_exact(width) {
228 for val_u16 in row {
229 if *val_u16 < min {
230 min = *val_u16;
231 }
232 if *val_u16 > max {
233 max = *val_u16;
234 }
235 }
236 }
237 }
238 _ => {
239 return Err(crate::Error::UnsupportedForEsimatingLuminangeRange);
240 }
241 }
242 Ok((min, max))
243 }
244}
245
246fn read_tiff_image(buf: &[u8], metadata: TiffMetadata) -> Result<TiffImage> {
247 let mut decoder = tiff::decoder::Decoder::new(Cursor::new(buf))?;
248 let (width, height) = decoder.dimensions()?;
249 Ok(TiffImage {
250 buf: buf.to_vec(),
251 width,
252 height,
253 metadata,
254 })
255}
256
257fn extract_tiff_metadata(buf: &[u8]) -> Result<TiffMetadata> {
258 let mut rdr = Cursor::new(buf);
259 let exifreader = exif::Reader::new();
260 let exif = exifreader.read_from_container(&mut rdr)?;
261
262 let imagej_metadata = exif.get_field(
263 exif::Tag(exif::Context::Tiff, IJIJINFO_KEY),
264 exif::In::PRIMARY,
265 );
266 let bytes = if let Some(imagej_metadata) = imagej_metadata {
267 if let exif::Value::Byte(bytes) = &imagej_metadata.value {
268 bytes
269 } else {
270 return Err(crate::Error::ImageJDataExpectedToBeBytes);
271 }
272 } else {
273 return Err(crate::Error::FailedToReadMetadata);
274 };
275 let mystr = String::from_utf8(bytes.clone())?.replace(['\x00', '\x01'], "");
276 if !mystr.starts_with(MAGICSTR) {
277 return Err(crate::Error::ExifMetadataFailsMagic);
278 }
279 let mystr = &mystr[MAGICSTR.len()..];
280 let json: serde_json::Value = serde_json::from_str(mystr)?;
281 let framenumber = json["UserData"]["PVCAM-FMD-FrameNr"]["scalar"]
284 .as_str()
285 .ok_or_else(json_parse_err)?
286 .parse()?;
287 let pvcam_fmd_timestamp_str = json["UserData"]["PVCAM-FMD-TimestampBofPs"]["scalar"]
288 .as_str()
289 .ok_or_else(json_parse_err)?;
290 let timestamp = parse_picosecs(pvcam_fmd_timestamp_str)?;
291
292 let bit_depth = json["UserData"]["PVCAM-FMD-BitDepth"]["scalar"]
293 .as_str()
294 .ok_or_else(json_parse_err)?
295 .parse()?;
296 Ok(TiffMetadata {
297 json,
298 framenumber,
299 timestamp,
300 bit_depth,
301 })
302}
303
304fn path_to_tiff(
305 path: &std::path::PathBuf,
306 frame0_timestamp_offset: std::time::Duration,
307 assign_idx: usize,
308) -> Result<FrameData> {
309 let buf = read_file(path)?;
310 let buf_len = buf.len();
311 let mut metadata = extract_tiff_metadata(&buf)?;
312 metadata.timestamp -= frame0_timestamp_offset;
313 let timestamp = Timestamp::Duration(metadata.timestamp);
314 Ok(FrameData {
315 image: ImageData::Tiff(read_tiff_image(&buf, metadata)?),
316 timestamp,
317 buf_len,
318 idx: assign_idx,
319 poc: None,
320 })
321}
322
323fn json_parse_err() -> crate::Error {
324 crate::Error::JsonParseError
325}
326
327fn read_file<P: AsRef<Path>>(path: P) -> Result<Vec<u8>> {
328 Ok(std::fs::read(path.as_ref())?)
329}
330
331fn parse_picosecs(picosecs_str: &str) -> Result<std::time::Duration> {
332 let elapsed_picosecs: u128 = picosecs_str.parse()?;
333 let elapsed_msecs: f64 = elapsed_picosecs as f64 / 1_000_000_000f64;
334 Ok(std::time::Duration::from_secs_f64(elapsed_msecs / 1000.0))
335}