1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//! Layout metadata shared by homogeneous array containers (`NumPy` `.npy` / `.npz`, MATLAB `.mat`, etc.).
use serde::{Deserialize, Serialize};
/// Parsed header / layout for a single dense array variable (dtype, shape, data region bounds).
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct ArrayLayoutSummary {
/// Container-specific format version (e.g. `NumPy` `1.0` / `2.0` header, MAT level).
#[serde(skip_serializing_if = "Option::is_none")]
pub format_version: Option<String>,
/// Element type description: `NumPy` `descr` string, MATLAB class name, HDF5 dtype label, etc.
#[serde(skip_serializing_if = "Option::is_none", alias = "descr")]
pub dtype: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shape: Option<Vec<usize>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fortran_order: Option<bool>,
/// Size in bytes of any header/prefix region before raw array bytes (`NumPy` header dict; optional elsewhere).
#[serde(skip_serializing_if = "Option::is_none")]
pub header_region_bytes: Option<usize>,
/// Byte offset where raw array data begins.
#[serde(skip_serializing_if = "Option::is_none")]
pub data_offset: Option<usize>,
/// Bytes from `data_offset` through end of the logical payload (file or archive member size).
#[serde(skip_serializing_if = "Option::is_none")]
pub data_region_bytes: Option<usize>,
/// `itemsize * num_elements` when both can be inferred from `dtype` and `shape`.
#[serde(skip_serializing_if = "Option::is_none")]
pub expected_data_bytes_from_dtype: Option<usize>,
}
impl ArrayLayoutSummary {
/// Rank 0–2 shapes as `(rows, cols)` for tabular sampling; `None` if rank is above two.
#[must_use]
pub fn table_dims(&self) -> Option<(usize, usize)> {
let shape = self.shape.as_deref()?;
match shape.len() {
0 => Some((1, 1)),
1 => Some((shape[0], 1)),
2 => Some((shape[0], shape[1])),
_ => None,
}
}
/// `(row_count, column_count)` for stats: rank ≤ 2 uses [`Self::table_dims`]; higher rank uses flat `prod` × 0.
#[must_use]
pub fn shape_row_col_counts(&self) -> Option<(usize, usize)> {
let shape = self.shape.as_deref()?;
if let Some((r, c)) = self.table_dims() {
Some((r, c))
} else {
let n: usize = shape.iter().product();
Some((n, 0))
}
}
}
/// Backward-compatible alias used when the name “`NumPy` layout” reads clearer than “array layout”.
pub type NpyLayoutSummary = ArrayLayoutSummary;