1use std::collections::HashMap;
2use std::fs::File;
3use std::io::{BufReader, Read, Seek, SeekFrom};
4use std::path::Path;
5
6use flate2::read::ZlibDecoder;
7
8use crate::chunk::{read_chunk, read_chunkmap, ChunkMap};
9use crate::constants::{JP2_MAGIC, ND2_CHUNK_MAGIC, ND2_FILE_SIGNATURE};
10use crate::error::{Nd2Error, Result};
11use crate::meta_parse::{parse_attributes, parse_experiment, parse_text_info};
12use crate::parse::ClxLiteParser;
13use crate::types::{Attributes, CompressionType, ExpLoop, TextInfo};
14
15const AXIS_T: &str = "T";
17const AXIS_P: &str = "P";
18const AXIS_C: &str = "C";
19const AXIS_Z: &str = "Z";
20const AXIS_Y: &str = "Y";
21const AXIS_X: &str = "X";
22
23pub struct Nd2File {
25 reader: BufReader<File>,
26 version: (u32, u32),
27 chunkmap: ChunkMap,
28 attributes: Option<Attributes>,
30 experiment: Option<Vec<ExpLoop>>,
31 text_info: Option<TextInfo>,
32}
33
34impl Nd2File {
35 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
37 let file = File::open(path)?;
38 let mut reader = BufReader::new(file);
39
40 let version = Self::read_version(&mut reader)?;
42
43 if version.0 < 2 || version.0 > 3 {
45 return Err(Nd2Error::UnsupportedVersion {
46 major: version.0,
47 minor: version.1,
48 });
49 }
50
51 let chunkmap = read_chunkmap(&mut reader)?;
53
54 Ok(Self {
55 reader,
56 version,
57 chunkmap,
58 attributes: None,
59 experiment: None,
60 text_info: None,
61 })
62 }
63
64 pub fn version(&self) -> (u32, u32) {
66 self.version
67 }
68
69 pub fn attributes(&mut self) -> Result<&Attributes> {
71 if self.attributes.is_none() {
72 let chunk_name: &[u8] = if self.version.0 >= 3 {
73 b"ImageAttributesLV!"
74 } else {
75 b"ImageAttributes!"
76 };
77 let data = read_chunk(&mut self.reader, &self.chunkmap, chunk_name)?;
78 let parser = ClxLiteParser::new(false);
79 let clx = parser.parse(&data)?;
80 self.attributes = Some(parse_attributes(clx)?);
81 }
82 Ok(self.attributes.as_ref().unwrap())
83 }
84
85 pub fn experiment(&mut self) -> Result<&Vec<ExpLoop>> {
87 if self.experiment.is_none() {
88 let chunk_name: &[u8] = if self.version.0 >= 3 {
89 b"ImageMetadataLV!"
90 } else {
91 b"ImageMetadata!"
92 };
93
94 if !self.chunkmap.contains_key(chunk_name) {
95 self.experiment = Some(Vec::new());
96 } else {
97 let data = read_chunk(&mut self.reader, &self.chunkmap, chunk_name)?;
98 let parser = ClxLiteParser::new(false);
99 let clx = parser.parse(&data)?;
100 let to_parse = if self.version.0 >= 3 {
102 match clx.as_object().and_then(|o| o.get("SLxExperiment")) {
103 Some(inner) if inner.as_object().is_some() => inner.clone(),
104 _ => clx.clone(),
105 }
106 } else {
107 clx.clone()
108 };
109 let mut exp = parse_experiment(to_parse).unwrap_or_default();
110 if exp.is_empty() && self.version.0 >= 3 {
112 exp = parse_experiment(clx).unwrap_or_default();
113 }
114 self.experiment = Some(exp);
115 }
116 }
117 Ok(self.experiment.as_ref().unwrap())
118 }
119
120 pub fn text_info(&mut self) -> Result<&TextInfo> {
122 if self.text_info.is_none() {
123 let chunk_name: &[u8] = if self.version.0 >= 3 {
124 b"ImageTextInfoLV!"
125 } else {
126 b"ImageTextInfo!"
127 };
128
129 if !self.chunkmap.contains_key(chunk_name) {
130 self.text_info = Some(TextInfo::default());
131 } else {
132 let data = read_chunk(&mut self.reader, &self.chunkmap, chunk_name)?;
133 let parser = ClxLiteParser::new(false);
134 let clx = parser.parse(&data)?;
135 self.text_info = Some(parse_text_info(clx)?);
136 }
137 }
138 Ok(self.text_info.as_ref().unwrap())
139 }
140
141 pub fn chunk_names(&self) -> Vec<String> {
143 self.chunkmap
144 .keys()
145 .filter_map(|k| String::from_utf8(k.clone()).ok())
146 .collect()
147 }
148
149 pub fn read_raw_chunk(&mut self, name: &[u8]) -> Result<Vec<u8>> {
151 read_chunk(&mut self.reader, &self.chunkmap, name)
152 }
153
154 pub fn sizes(&mut self) -> Result<HashMap<String, usize>> {
157 let attrs = self.attributes()?.clone();
158 let exp = self.experiment()?.clone();
159
160 let n_chan = attrs.channel_count.unwrap_or(attrs.component_count);
161 let height = attrs.height_px as usize;
162 let width = attrs.width_px.or(attrs.width_bytes.map(|w| {
163 let bpp = attrs.bits_per_component_in_memory / 8;
164 w / (bpp * attrs.component_count)
165 })).unwrap_or(0) as usize;
166
167 let mut sizes: HashMap<String, usize> = HashMap::new();
168
169 if exp.is_empty() {
170 let total = attrs.sequence_count as usize;
172 let n_z: usize = 1;
173 let n_pos: usize = 1;
174 let n_chan_usize = n_chan as usize;
175 let n_time = total / (n_pos * n_chan_usize * n_z).max(1);
176 sizes.insert(AXIS_P.to_string(), n_pos);
177 sizes.insert(AXIS_T.to_string(), n_time);
178 sizes.insert(AXIS_C.to_string(), n_chan_usize);
179 sizes.insert(AXIS_Z.to_string(), n_z);
180 } else {
181 for loop_ in exp {
182 match loop_ {
183 ExpLoop::TimeLoop(t) => {
184 sizes.insert(AXIS_T.to_string(), t.count as usize);
185 }
186 ExpLoop::XYPosLoop(xy) => {
187 sizes.insert(AXIS_P.to_string(), xy.count as usize);
188 }
189 ExpLoop::ZStackLoop(z) => {
190 sizes.insert(AXIS_Z.to_string(), z.count as usize);
191 }
192 ExpLoop::NETimeLoop(n) => {
193 sizes.insert(AXIS_T.to_string(), n.count as usize);
194 }
195 ExpLoop::CustomLoop(_) => {}
196 }
197 }
198 if !sizes.contains_key(AXIS_C) {
199 sizes.insert(AXIS_C.to_string(), n_chan as usize);
200 }
201 if !sizes.contains_key(AXIS_P) {
202 sizes.insert(AXIS_P.to_string(), 1);
203 }
204 if !sizes.contains_key(AXIS_T) {
205 sizes.insert(AXIS_T.to_string(), 1);
206 }
207 if !sizes.contains_key(AXIS_Z) {
208 sizes.insert(AXIS_Z.to_string(), 1);
209 }
210 }
211
212 sizes.insert(AXIS_Y.to_string(), height);
213 sizes.insert(AXIS_X.to_string(), width);
214
215 Ok(sizes)
216 }
217
218 pub fn loop_indices(&mut self) -> Result<Vec<HashMap<String, usize>>> {
221 let (axis_order, coord_shape) = self.coord_axis_order()?;
222 let total: usize = coord_shape.iter().product();
223
224 let mut out = Vec::with_capacity(total);
225 let n = axis_order.len();
226
227 for seq in 0..total {
228 let mut idx = seq;
229 let mut m = HashMap::new();
230 for i in (0..n).rev() {
232 let coord = idx % coord_shape[i];
233 idx /= coord_shape[i];
234 m.insert(axis_order[i].to_string(), coord);
235 }
236 out.push(m);
237 }
238
239 Ok(out)
240 }
241
242 pub fn read_frame(&mut self, index: usize) -> Result<Vec<u16>> {
244 let attrs = self.attributes()?.clone();
245 let chunk_name = format!("ImageDataSeq|{}!", index);
246 let chunk_key = chunk_name.as_bytes();
247
248 let h = attrs.height_px as usize;
249 let w = attrs.width_px.unwrap_or(0) as usize;
250 let (n_c, n_comp) = match attrs.channel_count {
251 Some(ch) if ch > 0 => (
252 ch as usize,
253 (attrs.component_count / ch) as usize,
254 ),
255 _ => (attrs.component_count as usize, 1),
256 };
257 let frame_size = h * w * n_c * n_comp;
258 let expected_raw = frame_size * (attrs.bits_per_component_in_memory / 8) as usize;
259
260 let data = self.read_raw_chunk(chunk_key)?;
261
262 let pixel_bytes = if attrs.compression_type == Some(CompressionType::Lossless) {
263 let mut decoder = ZlibDecoder::new(&data[8..]);
264 let mut decompressed = Vec::new();
265 decoder.read_to_end(&mut decompressed)?;
266 decompressed
267 } else if data.len() == expected_raw {
268 data
269 } else if data.len() >= 8 && (data.len() - 8) == expected_raw {
270 data[8..].to_vec()
271 } else {
272 data
273 };
274
275 if pixel_bytes.len() / 2 < frame_size {
276 return Err(Nd2Error::InvalidFormat(format!(
277 "Frame {}: expected {} pixels ({} bytes), got {} bytes",
278 index,
279 frame_size,
280 frame_size * 2,
281 pixel_bytes.len()
282 )));
283 }
284
285 let mut pixels: Vec<u16> = vec![0; pixel_bytes.len() / 2];
286 for (i, chunk) in pixel_bytes.chunks_exact(2).enumerate() {
287 pixels[i] = u16::from_le_bytes([chunk[0], chunk[1]]);
288 }
289
290 if pixels.len() < frame_size {
291 return Err(Nd2Error::InvalidFormat(format!(
292 "Frame {}: pixel count {} < expected {}",
293 index, pixels.len(), frame_size
294 )));
295 }
296
297 let mut out = vec![0u16; frame_size];
298 for y in 0..h {
299 for x in 0..w {
300 for c in 0..n_c {
301 for comp in 0..n_comp {
302 let src_idx = (y * w * n_c * n_comp) + (x * n_c * n_comp) + (c * n_comp) + comp;
303 let dst_idx = (c * n_comp + comp) * (h * w) + y * w + x;
304 out[dst_idx] = pixels[src_idx];
305 }
306 }
307 }
308 }
309
310 Ok(out)
311 }
312
313 fn coord_axis_order(&mut self) -> Result<(Vec<&'static str>, Vec<usize>)> {
318 let attrs = self.attributes()?.clone();
319 let exp = self.experiment()?.clone();
320 let n_chan = attrs.channel_count.unwrap_or(attrs.component_count) as usize;
321 let seq_count = attrs.sequence_count as usize;
322
323 let mut axis_order: Vec<&'static str> = Vec::new();
324 let mut coord_shape: Vec<usize> = Vec::new();
325
326 if exp.is_empty() {
327 let n_z = 1;
329 let n_pos = 1;
330 let n_time = seq_count / (n_pos * n_chan * n_z).max(1);
331 axis_order.extend([AXIS_P, AXIS_T, AXIS_C, AXIS_Z]);
332 coord_shape.extend([n_pos, n_time, n_chan, n_z]);
333 } else {
334 for loop_ in &exp {
335 match loop_ {
336 crate::types::ExpLoop::TimeLoop(t) => {
337 axis_order.push(AXIS_T);
338 coord_shape.push(t.count as usize);
339 }
340 crate::types::ExpLoop::NETimeLoop(n) => {
341 axis_order.push(AXIS_T);
342 coord_shape.push(n.count as usize);
343 }
344 crate::types::ExpLoop::XYPosLoop(xy) => {
345 axis_order.push(AXIS_P);
346 coord_shape.push(xy.count as usize);
347 }
348 crate::types::ExpLoop::ZStackLoop(z) => {
349 axis_order.push(AXIS_Z);
350 coord_shape.push(z.count as usize);
351 }
352 crate::types::ExpLoop::CustomLoop(_) => {}
353 }
354 }
355 if !axis_order.contains(&AXIS_P) {
357 axis_order.push(AXIS_P);
358 coord_shape.push(1);
359 }
360 if !axis_order.contains(&AXIS_T) {
361 axis_order.push(AXIS_T);
362 coord_shape.push(1);
363 }
364 if !axis_order.contains(&AXIS_Z) {
365 axis_order.push(AXIS_Z);
366 coord_shape.push(1);
367 }
368 let exp_product: usize = coord_shape.iter().product();
370 if exp_product > 0 && exp_product * n_chan <= seq_count {
371 axis_order.push(AXIS_C);
372 coord_shape.push(n_chan);
373 }
374 if !axis_order.contains(&AXIS_Z) {
375 axis_order.push(AXIS_Z);
376 coord_shape.push(1);
377 }
378 }
379
380 Ok((axis_order, coord_shape))
381 }
382
383 fn seq_index_from_coords(
385 &mut self,
386 p: usize,
387 t: usize,
388 c: usize,
389 z: usize,
390 ) -> Result<usize> {
391 let (axis_order, coord_shape) = self.coord_axis_order()?;
392 let coords: Vec<usize> = axis_order
393 .iter()
394 .map(|&ax| match ax {
395 AXIS_P => p,
396 AXIS_T => t,
397 AXIS_C => c,
398 AXIS_Z => z,
399 _ => 0,
400 })
401 .collect();
402
403 if coords.len() != coord_shape.len() {
404 return Err(Nd2Error::InvalidFormat(
405 "Coord/axis length mismatch".to_string(),
406 ));
407 }
408
409 let mut seq = 0usize;
410 let mut stride = 1;
411 for i in (0..coords.len()).rev() {
412 seq += coords[i] * stride;
413 stride *= coord_shape[i];
414 }
415 Ok(seq)
416 }
417
418 pub fn read_frame_2d(&mut self, p: usize, t: usize, c: usize, z: usize) -> Result<Vec<u16>> {
420 let sizes = self.sizes()?;
421 let height = *sizes.get(AXIS_Y).unwrap_or(&1);
422 let width = *sizes.get(AXIS_X).unwrap_or(&1);
423 let seq_index = self.seq_index_from_coords(p, t, c, z)?;
424
425 let frame = self.read_frame(seq_index)?;
426 let len = height * width;
427 let start = (c * len).min(frame.len());
429 let end = ((c + 1) * len).min(frame.len());
430 Ok(frame[start..end].to_vec())
431 }
432
433 fn read_version<R: Read + Seek>(reader: &mut R) -> Result<(u32, u32)> {
434 reader.seek(SeekFrom::Start(0))?;
435
436 let mut header = [0u8; 112]; reader.read_exact(&mut header).map_err(|e| {
438 Nd2Error::InvalidFormat(format!(
439 "Failed to read file header (expected 112 bytes): {}",
440 e
441 ))
442 })?;
443
444 let magic = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
445
446 if magic == JP2_MAGIC {
447 return Ok((1, 0)); }
449
450 if magic != ND2_CHUNK_MAGIC {
451 return Err(Nd2Error::InvalidMagic {
452 expected: ND2_CHUNK_MAGIC,
453 actual: magic,
454 });
455 }
456
457 let name_length = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
458 let data_length = u64::from_le_bytes([
459 header[8], header[9], header[10], header[11], header[12], header[13], header[14],
460 header[15],
461 ]);
462
463 if name_length != 32 || data_length != 64 {
465 return Err(Nd2Error::InvalidFormat(
466 "Corrupt file header".to_string(),
467 ));
468 }
469
470 let name = &header[16..48];
472 if name != ND2_FILE_SIGNATURE {
473 return Err(Nd2Error::InvalidFormat(
474 "Invalid file signature".to_string(),
475 ));
476 }
477
478 let data = &header[48..112];
480 let major = (data[3] as char).to_digit(10).unwrap_or(0);
481 let minor = (data[5] as char).to_digit(10).unwrap_or(0);
482
483 Ok((major, minor))
484 }
485}
486
487impl Drop for Nd2File {
488 fn drop(&mut self) {
489 }
491}