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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Ergonomic N-dimensional array I/O via the [`ndarray`] crate.
//!
//! Enabled by the `ndarray` feature. This module adds:
//!
//! * [`DatasetBuilder::with_ndarray`](crate::writer::FileBuilder) — write any
//! [`ndarray`] array (owned or a view) of a supported scalar type, inferring
//! both the dataset shape and datatype from the array.
//! * [`Dataset::read_array`](crate::Dataset::read_array) /
//! [`Dataset::read_array_dyn`](crate::Dataset::read_array_dyn) — read a
//! dataset back into a typed [`ndarray::Array`] (fixed rank) or
//! [`ndarray::ArrayD`] (runtime rank).
//!
//! # Memory order
//!
//! HDF5 stores dataset elements in row-major (C) order, which is also
//! `ndarray`'s default layout, so reads and writes are a flat copy with no
//! transpose. On write, non-standard-layout inputs (Fortran-order, transposed,
//! or strided views) are repacked into row-major order via
//! [`ndarray::ArrayBase::as_standard_layout`]; arrays that are already standard
//! layout are borrowed without copying.
//!
//! # Example
//!
//! ```
//! use hdf5_pure::{File, FileBuilder};
//! use ndarray::{array, Array2};
//!
//! let a: Array2<f64> = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
//!
//! let mut fb = FileBuilder::new();
//! fb.create_dataset("m").with_ndarray(&a);
//! let bytes = fb.finish().unwrap();
//!
//! let file = File::from_bytes(bytes).unwrap();
//! let back: Array2<f64> = file.dataset("m").unwrap().read_array().unwrap();
//! assert_eq!(a, back);
//! ```
use ndarray::{Array, ArrayBase, ArrayD, Data, Dimension, IxDyn};
use crate::convert::TryToUsize;
use crate::element::H5Element;
use crate::error::Error;
use crate::reader::Dataset;
use crate::type_builders::DatasetBuilder;
impl Dataset<'_> {
/// Read the dataset into a statically-ranked [`ndarray::Array`].
///
/// The dimensionality `D` (e.g. [`ndarray::Ix2`]) is usually inferred from
/// the binding, so a call site reads as `let m: Array2<f64> =
/// ds.read_array()?;`.
///
/// `T` is the type you want the elements *delivered as*, not an assertion
/// about the dataset's stored type. The stored bytes are coerced into `T`
/// using the same rules as [`Dataset::read_f64`](crate::Dataset::read_f64)
/// and its siblings, so the conversion can be lossy: reading an `f64`
/// dataset as `i32` truncates, and reading an `i32` dataset as `f64`
/// widens. There is no check that `T` matches the on-disk datatype, so pick
/// `T` to match the stored type when you need an exact, lossless read.
///
/// # Errors
///
/// Returns [`Error::Shape`] if the dataset's runtime rank does not match
/// `D`. Use [`read_array_dyn`](Self::read_array_dyn) when the rank is not
/// known at compile time.
pub fn read_array<T: H5Element, D: Dimension>(&self) -> Result<Array<T, D>, Error> {
self.read_array_dyn::<T>()?
.into_dimensionality::<D>()
.map_err(|e| Error::Shape(e.to_string()))
}
/// Read the dataset into a dynamically-ranked [`ndarray::ArrayD`].
///
/// The array's shape is taken from the dataset's dataspace, so this works
/// for any rank without naming it at compile time. As with
/// [`read_array`](Self::read_array), `T` is the requested element type and
/// the stored bytes are coerced into it (possibly lossily) following the
/// [`Dataset::read_f64`](crate::Dataset::read_f64) conversion rules, with
/// no check that `T` matches the on-disk datatype.
pub fn read_array_dyn<T: H5Element>(&self) -> Result<ArrayD<T>, Error> {
let shape: Vec<usize> = self
.shape()?
.iter()
.map(|&d| d.to_usize())
.collect::<Result<Vec<_>, _>>()?;
let data = T::read_from(self)?;
ArrayD::from_shape_vec(IxDyn(&shape), data).map_err(|e| Error::Shape(e.to_string()))
}
}
impl DatasetBuilder {
/// Set the dataset's data, datatype, and shape from an [`ndarray`] array.
///
/// Accepts any owned array or view of a supported scalar type by reference
/// (e.g. `&Array2<f64>`, `&arr.view()`). The dataset's shape is taken from
/// the array's shape and its datatype from the element type, so this is the
/// N-dimensional counterpart of the flat `with_*_data` methods.
///
/// Data is written in row-major (C) order. Inputs that are not already in
/// standard layout (transposed, Fortran-order, or strided views) are
/// repacked once into row-major order; standard-layout inputs are used
/// without copying. The builder is returned so chunking and compression
/// can be chained, e.g. `b.with_ndarray(&a).with_chunks(&[64, 64])`.
pub fn with_ndarray<T, S, D>(&mut self, arr: &ArrayBase<S, D>) -> &mut Self
where
T: H5Element,
S: Data<Elem = T>,
D: Dimension,
{
let shape: Vec<u64> = arr.shape().iter().map(|&d| d as u64).collect();
// `as_standard_layout` borrows when `arr` is already row-major
// contiguous and copies into a fresh row-major buffer otherwise, so the
// resulting slice is always the C-order data HDF5 expects.
let standard = arr.as_standard_layout();
let flat = standard
.as_slice()
.expect("as_standard_layout yields a contiguous standard-layout array");
T::write_into(self, flat);
self.shape = Some(shape);
self
}
}