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};
12use crate::parse::ClxLiteParser;
13use crate::types::{Attributes, CompressionType, DatasetSummary, ExpLoop, SummaryChannel};
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
23use crate::io::ReadSeek;
24
25pub struct Nd2File {
27 reader: BufReader<Box<dyn ReadSeek>>,
28 version: (u32, u32),
29 chunkmap: ChunkMap,
30 attributes: Option<Attributes>,
32 experiment: Option<Vec<ExpLoop>>,
33}
34
35impl Nd2File {
36 pub fn open_reader<R>(reader: R) -> Result<Self>
38 where
39 R: Read + Seek + 'static,
40 {
41 Self::open_buffered(BufReader::new(Box::new(reader)))
42 }
43
44 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
46 Self::open_reader(File::open(path)?)
47 }
48
49 fn open_buffered(mut reader: BufReader<Box<dyn ReadSeek>>) -> Result<Self> {
50 let version = Self::read_version(&mut reader)?;
51 if version.0 < 2 || version.0 > 3 {
52 return Err(Nd2Error::unsupported_version(version.0, version.1));
53 }
54 let chunkmap = read_chunkmap(&mut reader)?;
55 Ok(Self {
56 reader,
57 version,
58 chunkmap,
59 attributes: None,
60 experiment: None,
61 })
62 }
63
64 pub fn version(&self) -> (u32, u32) {
66 self.version
67 }
68
69 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 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 summary(&mut self) -> Result<DatasetSummary> {
122 let sizes = self.sizes()?;
123 let attrs = self.attributes()?.clone();
124 let logical_frame_count = self.loop_indices()?.len();
125
126 let pixel_type = Some(format!(
127 "{}{}",
128 match attrs.pixel_data_type {
129 crate::types::PixelDataType::Float => "Float",
130 crate::types::PixelDataType::Unsigned => "Unsigned",
131 },
132 attrs.bits_per_component_in_memory
133 ));
134 let channel_count = *sizes.get(AXIS_C).unwrap_or(&1);
135 let channels = (0..channel_count)
136 .map(|index| SummaryChannel {
137 index,
138 name: None,
139 color: None,
140 pixel_type: pixel_type.clone(),
141 })
142 .collect();
143
144 Ok(DatasetSummary {
145 version_major: self.version.0,
146 version_minor: self.version.1,
147 sizes: sizes.into_iter().collect(),
148 logical_frame_count,
149 channels,
150 pixel_type,
151 scaling: None,
152 })
153 }
154
155 fn read_raw_chunk(&mut self, name: &[u8]) -> Result<Vec<u8>> {
157 read_chunk(&mut self.reader, &self.chunkmap, name)
158 }
159
160 fn sizes(&mut self) -> Result<HashMap<String, usize>> {
163 let attrs = self.attributes()?.clone();
164 let exp = self.experiment()?.clone();
165
166 let n_chan = attrs.channel_count.unwrap_or(attrs.component_count);
167 let height = attrs.height_px as usize;
168 let width = attrs
169 .width_px
170 .or(attrs.width_bytes.map(|w| {
171 let bpp = attrs.bits_per_component_in_memory / 8;
172 w / (bpp * attrs.component_count)
173 }))
174 .unwrap_or(0) as usize;
175
176 let mut sizes: HashMap<String, usize> = HashMap::new();
177
178 if exp.is_empty() {
179 let total = attrs.sequence_count as usize;
181 let n_z: usize = 1;
182 let n_pos: usize = 1;
183 let n_chan_usize = n_chan as usize;
184 let n_time = total / (n_pos * n_chan_usize * n_z).max(1);
185 sizes.insert(AXIS_P.to_string(), n_pos);
186 sizes.insert(AXIS_T.to_string(), n_time);
187 sizes.insert(AXIS_C.to_string(), n_chan_usize);
188 sizes.insert(AXIS_Z.to_string(), n_z);
189 } else {
190 for loop_ in exp {
191 match loop_ {
192 ExpLoop::TimeLoop(t) => {
193 sizes.insert(AXIS_T.to_string(), t.count as usize);
194 }
195 ExpLoop::XYPosLoop(xy) => {
196 sizes.insert(AXIS_P.to_string(), xy.count as usize);
197 }
198 ExpLoop::ZStackLoop(z) => {
199 sizes.insert(AXIS_Z.to_string(), z.count as usize);
200 }
201 ExpLoop::NETimeLoop(n) => {
202 sizes.insert(AXIS_T.to_string(), n.count as usize);
203 }
204 ExpLoop::CustomLoop(_) => {}
205 }
206 }
207 if !sizes.contains_key(AXIS_C) {
208 sizes.insert(AXIS_C.to_string(), n_chan as usize);
209 }
210 if !sizes.contains_key(AXIS_P) {
211 sizes.insert(AXIS_P.to_string(), 1);
212 }
213 if !sizes.contains_key(AXIS_T) {
214 sizes.insert(AXIS_T.to_string(), 1);
215 }
216 if !sizes.contains_key(AXIS_Z) {
217 sizes.insert(AXIS_Z.to_string(), 1);
218 }
219 }
220
221 sizes.insert(AXIS_Y.to_string(), height);
222 sizes.insert(AXIS_X.to_string(), width);
223
224 Ok(sizes)
225 }
226
227 fn loop_indices(&mut self) -> Result<Vec<HashMap<String, usize>>> {
230 let (axis_order, coord_shape) = self.coord_axis_order()?;
231 let total: usize = coord_shape.iter().product();
232
233 let mut out = Vec::with_capacity(total);
234 let n = axis_order.len();
235
236 for seq in 0..total {
237 let mut idx = seq;
238 let mut m = HashMap::new();
239 for i in (0..n).rev() {
241 let coord = idx % coord_shape[i];
242 idx /= coord_shape[i];
243 m.insert(axis_order[i].to_string(), coord);
244 }
245 out.push(m);
246 }
247
248 Ok(out)
249 }
250
251 pub fn read_frame(&mut self, index: usize) -> Result<Vec<u16>> {
253 let attrs = self.attributes()?.clone();
254 let max_seq = attrs.sequence_count as usize;
255 let chunk_name = format!("ImageDataSeq|{}!", index);
256 let chunk_key = chunk_name.as_bytes();
257
258 let h = attrs.height_px as usize;
259 let w = attrs.width_px.unwrap_or(0) as usize;
260 let (n_c, n_comp) = match attrs.channel_count {
261 Some(ch) if ch > 0 => (ch as usize, (attrs.component_count / ch) as usize),
262 _ => (attrs.component_count as usize, 1),
263 };
264 let bytes_per_pixel = (attrs.bits_per_component_in_memory / 8) as usize;
265 if bytes_per_pixel == 0 {
266 return Err(Nd2Error::file_invalid_format(
267 "Invalid bits_per_component_in_memory".to_string(),
268 ));
269 }
270 let raw_row_bytes = attrs.width_bytes.map(|w| w as usize).unwrap_or_else(|| {
271 w.saturating_mul(n_c)
272 .saturating_mul(n_comp)
273 .saturating_mul(bytes_per_pixel)
274 });
275 if raw_row_bytes == 0 {
276 return Err(Nd2Error::file_invalid_format(
277 "Invalid frame row stride".to_string(),
278 ));
279 }
280 if raw_row_bytes % bytes_per_pixel != 0 {
281 return Err(Nd2Error::file_invalid_format(format!(
282 "Frame row stride {} is not divisible by bytes per pixel {}",
283 raw_row_bytes, bytes_per_pixel
284 )));
285 }
286 let raw_row_pixels = raw_row_bytes / bytes_per_pixel;
287
288 let frame_size = h
289 .checked_mul(w)
290 .and_then(|v| v.checked_mul(n_c))
291 .and_then(|v| v.checked_mul(n_comp))
292 .ok_or_else(|| {
293 Nd2Error::file_invalid_format("Frame dimensions overflow".to_string())
294 })?;
295 let expected_raw = h
296 .checked_mul(raw_row_bytes)
297 .ok_or_else(|| Nd2Error::file_invalid_format("Frame byte size overflow".to_string()))?;
298 let frame_area = h
299 .checked_mul(w)
300 .ok_or_else(|| Nd2Error::file_invalid_format("Frame area overflow".to_string()))?;
301 let n_c_n_comp = n_c.checked_mul(n_comp).ok_or_else(|| {
302 Nd2Error::file_invalid_format("Frame channel/component overflow".to_string())
303 })?;
304 if raw_row_pixels < n_c_n_comp.saturating_mul(w) {
305 return Err(Nd2Error::file_invalid_format(format!(
306 "Frame row stride {} pixels is smaller than required width {}",
307 raw_row_pixels,
308 n_c_n_comp.saturating_mul(w)
309 )));
310 }
311
312 let pixel_bytes = match attrs.compression_type {
313 Some(CompressionType::Lossless) => {
314 let data = match self.read_raw_chunk(chunk_key) {
315 Ok(data) => data,
316 Err(err) => {
317 if matches!(
318 err,
319 Nd2Error::File {
320 source: crate::error::FileError::ChunkNotFound { .. },
321 }
322 ) {
323 return Err(Nd2Error::input_out_of_range(
324 "sequence index",
325 index,
326 max_seq,
327 ));
328 }
329 return Err(err);
330 }
331 };
332
333 if data.len() < 8 {
334 return Err(Nd2Error::file_invalid_format(format!(
335 "Frame {} compressed chunk too short ({} bytes)",
336 index,
337 data.len()
338 )));
339 }
340 let mut decoder = ZlibDecoder::new(&data[8..]);
341 let mut decompressed = Vec::new();
342 decoder.read_to_end(&mut decompressed)?;
343 decompressed
344 }
345 _ => match self.read_uncompressed_frame_bytes(chunk_key, expected_raw) {
346 Ok(data) => data,
347 Err(err) => {
348 if matches!(
349 err,
350 Nd2Error::File {
351 source: crate::error::FileError::ChunkNotFound { .. },
352 }
353 ) {
354 return Err(Nd2Error::input_out_of_range(
355 "sequence index",
356 index,
357 max_seq,
358 ));
359 }
360 return Err(err);
361 }
362 },
363 };
364
365 if pixel_bytes.len() % 2 != 0 {
366 return Err(Nd2Error::file_invalid_format(format!(
367 "Frame {}: pixel data length {} is not divisible by 2",
368 index,
369 pixel_bytes.len()
370 )));
371 }
372
373 if pixel_bytes.len() / 2 < frame_size {
374 return Err(Nd2Error::file_invalid_format(format!(
375 "Frame {}: expected {} pixels ({} bytes), got {} bytes",
376 index,
377 frame_size,
378 frame_size * 2,
379 pixel_bytes.len()
380 )));
381 }
382
383 let mut pixels: Vec<u16> = vec![0; pixel_bytes.len() / 2];
384 for (i, chunk) in pixel_bytes.chunks_exact(2).enumerate() {
385 pixels[i] = u16::from_le_bytes([chunk[0], chunk[1]]);
386 }
387
388 if pixels.len() < frame_size {
389 return Err(Nd2Error::file_invalid_format(format!(
390 "Frame {}: pixel count {} < expected {}",
391 index,
392 pixels.len(),
393 frame_size
394 )));
395 }
396
397 let mut out = vec![0u16; frame_size];
398 let row_pixels = raw_row_pixels;
399
400 for y in 0..h {
401 let y_offset = y.checked_mul(row_pixels).ok_or_else(|| {
402 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
403 })?;
404 let y_plane_offset = y.checked_mul(w).ok_or_else(|| {
405 Nd2Error::file_invalid_format("Frame plane offset overflow".to_string())
406 })?;
407 for x in 0..w {
408 let x_offset = x.checked_mul(n_c_n_comp).ok_or_else(|| {
409 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
410 })?;
411 for c in 0..n_c {
412 let c_offset = c.checked_mul(n_comp).ok_or_else(|| {
413 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
414 })?;
415 for comp in 0..n_comp {
416 let src_idx = y_offset
417 .checked_add(x_offset)
418 .and_then(|v| v.checked_add(c_offset))
419 .and_then(|v| v.checked_add(comp))
420 .ok_or_else(|| {
421 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
422 })?;
423 let dst_x = y_plane_offset.checked_add(x).ok_or_else(|| {
424 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
425 })?;
426 let c_plane = c_offset.checked_add(comp).ok_or_else(|| {
427 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
428 })?;
429 let dst_idx = c_plane
430 .checked_mul(frame_area)
431 .and_then(|v| v.checked_add(dst_x))
432 .ok_or_else(|| {
433 Nd2Error::file_invalid_format("Frame offset overflow".to_string())
434 })?;
435 out[dst_idx] = pixels[src_idx];
436 }
437 }
438 }
439 }
440
441 Ok(out)
442 }
443
444 fn read_uncompressed_frame_bytes(
445 &mut self,
446 chunk_key: &[u8],
447 expected_raw: usize,
448 ) -> Result<Vec<u8>> {
449 let file_size = self.reader.seek(SeekFrom::End(0))?;
450 let offset = self
451 .chunkmap
452 .get(chunk_key)
453 .map(|(offset, _)| *offset)
454 .ok_or_else(|| Nd2Error::file_chunk_not_found(String::from_utf8_lossy(chunk_key)))?;
455
456 let pixel_offset = match self.read_image_chunk_payload_offset(offset)? {
457 Some(payload_offset) => payload_offset.checked_add(8).ok_or_else(|| {
458 Nd2Error::file_invalid_format("Frame payload offset overflow".to_string())
459 })?,
460 None => offset.checked_add(4096).ok_or_else(|| {
461 Nd2Error::file_invalid_format("Frame fallback offset overflow".to_string())
462 })?,
463 };
464
465 let pixel_end = pixel_offset
466 .checked_add(expected_raw as u64)
467 .ok_or_else(|| Nd2Error::file_invalid_format("Frame bounds overflow".to_string()))?;
468 if pixel_end > file_size {
469 return Err(Nd2Error::file_invalid_format(format!(
470 "Frame chunk '{}' exceeds file bounds",
471 String::from_utf8_lossy(chunk_key)
472 )));
473 }
474
475 self.reader.seek(SeekFrom::Start(pixel_offset))?;
476 let mut pixel_bytes = vec![0u8; expected_raw];
477 self.reader.read_exact(&mut pixel_bytes)?;
478 Ok(pixel_bytes)
479 }
480
481 fn coord_axis_order(&mut self) -> Result<(Vec<&'static str>, Vec<usize>)> {
486 let attrs = self.attributes()?.clone();
487 let exp = self.experiment()?.clone();
488 let n_chan = attrs.channel_count.unwrap_or(attrs.component_count) as usize;
489 let seq_count = attrs.sequence_count as usize;
490
491 let mut axis_order: Vec<&'static str> = Vec::new();
492 let mut coord_shape: Vec<usize> = Vec::new();
493
494 if exp.is_empty() {
495 let n_z = 1;
497 let n_pos = 1;
498 let n_time = seq_count / (n_pos * n_chan * n_z).max(1);
499 axis_order.extend([AXIS_P, AXIS_T, AXIS_C, AXIS_Z]);
500 coord_shape.extend([n_pos, n_time, n_chan, n_z]);
501 } else {
502 for loop_ in &exp {
503 match loop_ {
504 crate::types::ExpLoop::TimeLoop(t) => {
505 axis_order.push(AXIS_T);
506 coord_shape.push(t.count as usize);
507 }
508 crate::types::ExpLoop::NETimeLoop(n) => {
509 axis_order.push(AXIS_T);
510 coord_shape.push(n.count as usize);
511 }
512 crate::types::ExpLoop::XYPosLoop(xy) => {
513 axis_order.push(AXIS_P);
514 coord_shape.push(xy.count as usize);
515 }
516 crate::types::ExpLoop::ZStackLoop(z) => {
517 axis_order.push(AXIS_Z);
518 coord_shape.push(z.count as usize);
519 }
520 crate::types::ExpLoop::CustomLoop(_) => {}
521 }
522 }
523 if !axis_order.contains(&AXIS_P) {
525 axis_order.push(AXIS_P);
526 coord_shape.push(1);
527 }
528 if !axis_order.contains(&AXIS_T) {
529 axis_order.push(AXIS_T);
530 coord_shape.push(1);
531 }
532 if !axis_order.contains(&AXIS_Z) {
533 axis_order.push(AXIS_Z);
534 coord_shape.push(1);
535 }
536 let exp_product: usize = coord_shape.iter().product();
538 if exp_product > 0 && exp_product * n_chan <= seq_count {
539 axis_order.push(AXIS_C);
540 coord_shape.push(n_chan);
541 }
542 if !axis_order.contains(&AXIS_Z) {
543 axis_order.push(AXIS_Z);
544 coord_shape.push(1);
545 }
546 }
547
548 Ok((axis_order, coord_shape))
549 }
550
551 fn seq_index_from_coords(&mut self, p: usize, t: usize, c: usize, z: usize) -> Result<usize> {
553 let (axis_order, coord_shape) = self.coord_axis_order()?;
554 let coords: Vec<usize> = axis_order
555 .iter()
556 .map(|&ax| match ax {
557 AXIS_P => p,
558 AXIS_T => t,
559 AXIS_C => c,
560 AXIS_Z => z,
561 _ => 0,
562 })
563 .collect();
564
565 if coords.len() != coord_shape.len() {
566 return Err(Nd2Error::file_invalid_format(
567 "Coord/axis length mismatch".to_string(),
568 ));
569 }
570
571 for (idx, (&coord, &shape)) in coords.iter().zip(coord_shape.iter()).enumerate() {
572 if shape == 0 {
573 return Err(Nd2Error::file_invalid_format(format!(
574 "Invalid axis length: {} has size 0",
575 axis_order[idx]
576 )));
577 }
578 if coord >= shape {
579 return Err(Nd2Error::input_out_of_range(
580 format!("axis {}", axis_order[idx]),
581 coord,
582 shape,
583 ));
584 }
585 }
586
587 let mut seq = 0usize;
588 let mut stride = 1;
589 for i in (0..coords.len()).rev() {
590 let next = coords[i]
591 .checked_mul(stride)
592 .ok_or_else(|| Nd2Error::internal_overflow("sequence index multiply"))?;
593 seq = seq
594 .checked_add(next)
595 .ok_or_else(|| Nd2Error::internal_overflow("sequence index add"))?;
596 stride = stride
597 .checked_mul(coord_shape[i])
598 .ok_or_else(|| Nd2Error::internal_overflow("sequence stride multiply"))?;
599 }
600 Ok(seq)
601 }
602
603 fn read_image_chunk_payload_offset(&mut self, offset: u64) -> Result<Option<u64>> {
604 self.reader.seek(SeekFrom::Start(offset))?;
605
606 let header = match crate::chunk::ChunkHeader::read(&mut self.reader) {
607 Ok(header) => header,
608 Err(_) => return Ok(None),
609 };
610
611 if header.magic != ND2_CHUNK_MAGIC {
612 return Ok(None);
613 }
614
615 let payload_offset = offset
616 .checked_add(16)
617 .and_then(|v| v.checked_add(header.name_length as u64))
618 .ok_or_else(|| {
619 Nd2Error::file_invalid_format("Frame payload offset overflow".to_string())
620 })?;
621
622 Ok(Some(payload_offset))
623 }
624
625 pub fn read_frame_2d(&mut self, p: usize, t: usize, c: usize, z: usize) -> Result<Vec<u16>> {
627 let sizes = self.sizes()?;
628 let height = *sizes.get(AXIS_Y).ok_or_else(|| {
629 Nd2Error::file_invalid_format("Missing height (Y) dimension".to_string())
630 })?;
631 let width = *sizes.get(AXIS_X).ok_or_else(|| {
632 Nd2Error::file_invalid_format("Missing width (X) dimension".to_string())
633 })?;
634 let n_pos = *sizes.get(AXIS_P).ok_or_else(|| {
635 Nd2Error::file_invalid_format("Missing position (P) dimension".to_string())
636 })?;
637 let n_time = *sizes.get(AXIS_T).ok_or_else(|| {
638 Nd2Error::file_invalid_format("Missing time (T) dimension".to_string())
639 })?;
640 let n_chan = *sizes.get(AXIS_C).ok_or_else(|| {
641 Nd2Error::file_invalid_format("Missing channel (C) dimension".to_string())
642 })?;
643 let n_z = *sizes
644 .get(AXIS_Z)
645 .ok_or_else(|| Nd2Error::file_invalid_format("Missing Z dimension".to_string()))?;
646
647 if p >= n_pos {
648 return Err(Nd2Error::input_out_of_range("position index", p, n_pos));
649 }
650 if t >= n_time {
651 return Err(Nd2Error::input_out_of_range("time index", t, n_time));
652 }
653 if c >= n_chan {
654 return Err(Nd2Error::input_out_of_range("channel index", c, n_chan));
655 }
656 if z >= n_z {
657 return Err(Nd2Error::input_out_of_range("z index", z, n_z));
658 }
659
660 let seq_index = self.seq_index_from_coords(p, t, c, z)?;
661
662 let frame = self.read_frame(seq_index)?;
663 let len = height.checked_mul(width).ok_or_else(|| {
664 Nd2Error::file_invalid_format("Frame dimensions overflow".to_string())
665 })?;
666
667 let start = c.checked_mul(len).ok_or_else(|| {
669 Nd2Error::file_invalid_format("Frame slice start overflow".to_string())
670 })?;
671 let end = (c + 1)
672 .checked_mul(len)
673 .ok_or_else(|| Nd2Error::file_invalid_format("Frame slice end overflow".to_string()))?;
674 if end > frame.len() {
675 return Err(Nd2Error::file_invalid_format(format!(
676 "Frame data too short for requested channel: frame {} < {}",
677 frame.len(),
678 end
679 )));
680 }
681 Ok(frame[start..end].to_vec())
682 }
683
684 fn read_version<R: Read + Seek>(reader: &mut R) -> Result<(u32, u32)> {
685 reader.seek(SeekFrom::Start(0))?;
686
687 let mut header = [0u8; 112]; reader.read_exact(&mut header).map_err(|e| {
689 Nd2Error::file_invalid_format(format!(
690 "Failed to read file header (expected 112 bytes): {}",
691 e
692 ))
693 })?;
694
695 let magic = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
696
697 if magic == JP2_MAGIC {
698 return Ok((1, 0)); }
700
701 if magic != ND2_CHUNK_MAGIC {
702 return Err(Nd2Error::file_invalid_magic(ND2_CHUNK_MAGIC, magic));
703 }
704
705 let name_length = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
706 let data_length = u64::from_le_bytes([
707 header[8], header[9], header[10], header[11], header[12], header[13], header[14],
708 header[15],
709 ]);
710
711 if name_length != 32 || data_length != 64 {
713 return Err(Nd2Error::file_invalid_format(
714 "Corrupt file header".to_string(),
715 ));
716 }
717
718 let name = &header[16..48];
720 if name != ND2_FILE_SIGNATURE {
721 return Err(Nd2Error::file_invalid_format(
722 "Invalid file signature".to_string(),
723 ));
724 }
725
726 let data = &header[48..112];
728 let major = (data[3] as char).to_digit(10).unwrap_or(0);
729 let minor = (data[5] as char).to_digit(10).unwrap_or(0);
730
731 Ok((major, minor))
732 }
733}
734
735impl Drop for Nd2File {
736 fn drop(&mut self) {
737 }
739}