use std::sync::Arc;
use arrow::array::{
Array as _, ArrayRef, FixedSizeListArray, Float32Array, Float64Array, Int8Array, Int16Array,
Int32Array, Int64Array, ListArray, StringArray, StructArray, UInt8Array, UInt16Array,
UInt32Array, UInt64Array,
};
use arrow::buffer::{OffsetBuffer, ScalarBuffer};
use arrow::datatypes::{Field, Fields};
use hdf5_pure::DType;
use re_sdk_types::{ComponentDescriptor, ComponentIdentifier};
use crate::config::IndexType;
use crate::error::Hdf5Error;
use crate::walk::{DatasetDesc, H5Path};
pub(crate) fn supported_dtype(dtype: &DType) -> bool {
is_numeric_dtype(dtype) | matches!(dtype, DType::String | DType::VariableLengthString)
}
pub(crate) fn is_numeric_dtype(dtype: &DType) -> bool {
matches!(
dtype,
DType::I8
| DType::I16
| DType::I32
| DType::I64
| DType::U8
| DType::U16
| DType::U32
| DType::U64
| DType::F32
| DType::F64
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DatasetDtype {
Int8,
Int16,
Int32,
Int64,
UInt8,
UInt16,
UInt32,
UInt64,
Float32,
Float64,
String,
Unsupported,
}
impl DatasetDtype {
fn as_numpy_str(self) -> &'static str {
match self {
Self::Int8 => "int8",
Self::Int16 => "int16",
Self::Int32 => "int32",
Self::Int64 => "int64",
Self::UInt8 => "uint8",
Self::UInt16 => "uint16",
Self::UInt32 => "uint32",
Self::UInt64 => "uint64",
Self::Float32 => "float32",
Self::Float64 => "float64",
Self::String => "string",
Self::Unsupported => "unsupported",
}
}
}
impl std::fmt::Display for DatasetDtype {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_numpy_str())
}
}
impl From<&DType> for DatasetDtype {
fn from(dtype: &DType) -> Self {
match dtype {
DType::I8 => Self::Int8,
DType::I16 => Self::Int16,
DType::I32 => Self::Int32,
DType::I64 => Self::Int64,
DType::U8 => Self::UInt8,
DType::U16 => Self::UInt16,
DType::U32 => Self::UInt32,
DType::U64 => Self::UInt64,
DType::F32 => Self::Float32,
DType::F64 => Self::Float64,
DType::String | DType::VariableLengthString => Self::String,
DType::Compound(_)
| DType::Enum(_)
| DType::Array(..)
| DType::ObjectReference
| DType::Other(_) => Self::Unsupported,
}
}
}
pub(crate) fn read_row_values(
file: &hdf5_pure::File,
desc: &DatasetDesc,
) -> Result<(Field, ArrayRef), Hdf5Error> {
re_tracing::profile_function!();
let dataset = file
.dataset(&desc.path.as_hdf5())
.map_err(|source| Hdf5Error::read_dataset(&desc.path, source))?;
let flat = read_flat_values(&dataset, &desc.dtype, &desc.path)?;
#[expect(clippy::cast_possible_truncation)]
let num_rows = desc.shape.first().copied().unwrap_or(1) as usize;
let values: ArrayRef = match desc.shape.len() {
0 | 1 => flat,
2 => {
let k = i32::try_from(desc.shape[1]).map_err(|_err| Hdf5Error::ListTooLong {
length: desc.shape[1],
})?;
let item_field = Arc::new(Field::new("item", flat.data_type().clone(), true));
Arc::new(FixedSizeListArray::try_new(item_field, k, flat, None)?)
}
_ => {
#[expect(clippy::cast_possible_truncation)]
let per_row = desc.shape[1..].iter().product::<u64>() as usize;
let item_field = Arc::new(Field::new("item", flat.data_type().clone(), true));
let offsets = OffsetBuffer::from_lengths(std::iter::repeat_n(per_row, num_rows));
Arc::new(ListArray::try_new(item_field, offsets, flat, None)?)
}
};
let field = Field::new(desc.name(), values.data_type().clone(), true);
Ok((field, values))
}
fn read_flat_values(
dataset: &hdf5_pure::Dataset<'_>,
dtype: &DType,
path: &H5Path,
) -> Result<ArrayRef, Hdf5Error> {
let read_err = |source| Hdf5Error::read_dataset(path, source);
Ok(match dtype {
DType::I8 => Arc::new(Int8Array::from(dataset.read_i8().map_err(read_err)?)),
DType::I16 => Arc::new(Int16Array::from(dataset.read_i16().map_err(read_err)?)),
DType::I32 => Arc::new(Int32Array::from(dataset.read_i32().map_err(read_err)?)),
DType::I64 => Arc::new(Int64Array::from(dataset.read_i64().map_err(read_err)?)),
DType::U8 => Arc::new(UInt8Array::from(dataset.read_u8().map_err(read_err)?)),
DType::U16 => Arc::new(UInt16Array::from(dataset.read_u16().map_err(read_err)?)),
DType::U32 => Arc::new(UInt32Array::from(dataset.read_u32().map_err(read_err)?)),
DType::U64 => Arc::new(UInt64Array::from(dataset.read_u64().map_err(read_err)?)),
DType::F32 => Arc::new(Float32Array::from(dataset.read_f32().map_err(read_err)?)),
DType::F64 => Arc::new(Float64Array::from(dataset.read_f64().map_err(read_err)?)),
DType::String | DType::VariableLengthString => {
Arc::new(StringArray::from(dataset.read_string().map_err(read_err)?))
}
unsupported => {
return Err(Hdf5Error::UnsupportedElementType {
dtype: unsupported.to_string(),
});
}
})
}
pub(crate) fn read_dataset_to_list(
file: &hdf5_pure::File,
desc: &DatasetDesc,
) -> Result<(ComponentDescriptor, ListArray), Hdf5Error> {
let (field, values) = read_row_values(file, desc)?;
let list = wrap_one_per_row(field.with_name("item"), values)?;
Ok((partial_descriptor(desc.name())?, list))
}
pub(crate) fn build_struct_component(
columns: Vec<(Field, ArrayRef)>,
) -> Result<(ComponentDescriptor, ListArray), Hdf5Error> {
let (fields, arrays): (Vec<_>, Vec<_>) = columns
.into_iter()
.map(|(field, array)| (Arc::new(field), array))
.unzip();
let struct_array = StructArray::try_new(Fields::from(fields), arrays, None)?;
let item_field = Field::new("item", struct_array.data_type().clone(), true);
let list = wrap_one_per_row(item_field, Arc::new(struct_array))?;
Ok((ComponentDescriptor::partial("data"), list))
}
fn wrap_one_per_row(item_field: Field, values: ArrayRef) -> Result<ListArray, Hdf5Error> {
let offsets = OffsetBuffer::from_lengths(std::iter::repeat_n(1_usize, values.len()));
Ok(ListArray::try_new(
Arc::new(item_field),
offsets,
values,
None,
)?)
}
pub(crate) fn read_index_to_ns(
file: &hdf5_pure::File,
path: &H5Path,
index_type: IndexType,
) -> Result<ScalarBuffer<i64>, Hdf5Error> {
re_tracing::profile_function!();
let read_err = |source| Hdf5Error::read_dataset(path, source);
let dataset = file.dataset(&path.as_hdf5()).map_err(read_err)?;
let multiplier = index_type.ns_multiplier();
fn scale_ints<T: Into<i64>>(values: Vec<T>, multiplier: i64) -> Vec<i64> {
values
.into_iter()
.map(|value| value.into() * multiplier)
.collect()
}
#[expect(clippy::cast_possible_truncation)]
fn scale_floats<T: Into<f64>>(values: Vec<T>, multiplier: i64) -> Vec<i64> {
#[expect(clippy::cast_precision_loss)]
let multiplier = multiplier as f64;
values
.into_iter()
.map(|value| (value.into() * multiplier).round() as i64)
.collect()
}
let values: Vec<i64> = match dataset.dtype().map_err(read_err)? {
DType::I8 => scale_ints(dataset.read_i8().map_err(read_err)?, multiplier),
DType::I16 => scale_ints(dataset.read_i16().map_err(read_err)?, multiplier),
DType::I32 => scale_ints(dataset.read_i32().map_err(read_err)?, multiplier),
DType::I64 => scale_ints(dataset.read_i64().map_err(read_err)?, multiplier),
DType::U8 => scale_ints(dataset.read_u8().map_err(read_err)?, multiplier),
DType::U16 => scale_ints(dataset.read_u16().map_err(read_err)?, multiplier),
DType::U32 => scale_ints(dataset.read_u32().map_err(read_err)?, multiplier),
#[expect(clippy::cast_possible_wrap)]
DType::U64 => dataset
.read_u64()
.map_err(read_err)?
.into_iter()
.map(|value| value as i64 * multiplier)
.collect(),
DType::F32 => scale_floats(dataset.read_f32().map_err(read_err)?, multiplier),
DType::F64 => scale_floats(dataset.read_f64().map_err(read_err)?, multiplier),
non_numeric => {
return Err(Hdf5Error::IndexNotNumeric {
path: path.to_string(),
dtype: non_numeric.to_string(),
});
}
};
Ok(ScalarBuffer::from(values))
}
pub(crate) fn attr_to_component(
name: &str,
value: &hdf5_pure::AttrValue,
) -> Result<(ComponentDescriptor, ListArray), Hdf5Error> {
use hdf5_pure::AttrValue;
let values: ArrayRef = match value {
AttrValue::F64(value) => Arc::new(Float64Array::from(vec![*value])),
AttrValue::I32(value) => Arc::new(Int32Array::from(vec![*value])),
AttrValue::I64(value) => Arc::new(Int64Array::from(vec![*value])),
AttrValue::U32(value) => Arc::new(UInt32Array::from(vec![*value])),
AttrValue::U64(value) => Arc::new(UInt64Array::from(vec![*value])),
AttrValue::String(value) | AttrValue::AsciiString(value) => {
Arc::new(StringArray::from(vec![value.as_str()]))
}
AttrValue::F64Array(values) => {
one_row_fixed_size_list(Arc::new(Float64Array::from(values.clone())))?
}
AttrValue::I64Array(values) => {
one_row_fixed_size_list(Arc::new(Int64Array::from(values.clone())))?
}
AttrValue::StringArray(values)
| AttrValue::AsciiStringArray(values)
| AttrValue::VarLenAsciiArray(values) => one_row_fixed_size_list(Arc::new(
StringArray::from_iter_values(values.iter().map(String::as_str)),
))?,
};
let item_field = Field::new("item", values.data_type().clone(), true);
let list = wrap_one_per_row(item_field, values)?;
Ok((partial_descriptor(name)?, list))
}
fn one_row_fixed_size_list(inner: ArrayRef) -> Result<ArrayRef, Hdf5Error> {
let len = i32::try_from(inner.len()).map_err(|_err| Hdf5Error::ListTooLong {
length: inner.len() as u64,
})?;
let item_field = Arc::new(Field::new("item", inner.data_type().clone(), true));
Ok(Arc::new(FixedSizeListArray::try_new(
item_field, len, inner, None,
)?))
}
fn partial_descriptor(name: &str) -> Result<ComponentDescriptor, Hdf5Error> {
let component = ComponentIdentifier::try_new(name)
.map_err(|source| Hdf5Error::invalid_component_name(name, source))?;
Ok(ComponentDescriptor::partial(component))
}