///|
/// One inclusive block in a serialized HDF5 hyperslab selection.
#derive(Eq, @debug.Debug)
pub(all) struct Hdf5RegionBlock {
start : Array[UInt64]
end : Array[UInt64]
}
///|
pub extend Hdf5RegionBlock with Eq::{not_equal, equal}
///|
pub extend Hdf5RegionBlock with @debug.Debug::{to_repr}
///|
/// The dataspace selection stored in an HDF5 1.14 region reference.
#derive(Eq, @debug.Debug)
pub(all) enum Hdf5RegionSelection {
RegionEmpty
RegionAll
RegionPoints(Array[Array[UInt64]])
RegionHyperslab(Array[UInt64], Array[UInt64], Array[UInt64], Array[UInt64])
RegionBlocks(Array[Hdf5RegionBlock])
}
///|
pub extend Hdf5RegionSelection with Eq::{not_equal, equal}
///|
pub extend Hdf5RegionSelection with @debug.Debug::{to_repr}
///|
/// The dataset and dataspace selection reached by a region reference.
#derive(@debug.Debug)
pub(all) struct Hdf5RegionReferenceTarget {
address : UInt64
kind : Hdf5ObjectKind
path : String?
dataspace : Dataspace
selection : Hdf5RegionSelection
}
///|
pub extend Hdf5RegionReferenceTarget with @debug.Debug::{to_repr}
///|
fn validate_region_encoding_size(enc_size : Int32) -> Unit raise Hdf5Error {
guard enc_size == 2 || enc_size == 4 || enc_size == 8 else {
raise UnsupportedFeature(
"region selection encoding width must be 2, 4 or 8 bytes",
)
}
}
///|
fn read_region_rank(reader : Reader) -> Int32 raise Hdf5Error {
let rank = reader.read_u32_le().reinterpret_as_int()
guard rank >= 0 && rank <= 32 else {
raise InvalidField("region selection rank is outside 0..32")
}
rank
}
///|
fn read_region_vector(
reader : Reader,
rank : Int32,
enc_size : Int32,
) -> Array[UInt64] raise Hdf5Error {
let values : Array[UInt64] = []
for _ in 0.. Hdf5RegionBlock raise Hdf5Error {
let start = read_region_vector(reader, rank, enc_size)
let end = read_region_vector(reader, rank, enc_size)
{ start, end, }
}
///|
fn read_region_count(
reader : Reader,
enc_size : Int32,
) -> Int32 raise Hdf5Error {
let value = reader.read_uint_le(enc_size)
guard value <= 0x7FFFFFFFUL else {
raise InvalidField("region selection contains too many entries")
}
value.to_int()
}
///|
fn parse_region_points(
reader : Reader,
version : Int32,
) -> Hdf5RegionSelection raise Hdf5Error {
let enc_size = if version == 1 {
reader.skip(8)
4
} else if version == 2 {
reader.read_u8().to_int()
} else {
raise UnsupportedFeature("unsupported region point-selection version")
}
validate_region_encoding_size(enc_size)
let rank = read_region_rank(reader)
let point_count = read_region_count(reader, enc_size)
let points : Array[Array[UInt64]] = []
for _ in 0.. Hdf5RegionSelection raise Hdf5Error {
if version == 1 {
reader.skip(4)
let _length = reader.read_u32_le()
let rank = read_region_rank(reader)
let block_count = reader.read_u32_le().reinterpret_as_int()
guard block_count >= 0 else {
raise InvalidField("region hyperslab block count is too large")
}
let blocks : Array[Hdf5RegionBlock] = []
for _ in 0.. Hdf5RegionSelection raise Hdf5Error {
guard version == 1 else {
raise UnsupportedFeature("unsupported region selection version")
}
let _padding = reader.read_u32_le()
let length = reader.read_u32_le()
guard length == 0U else {
raise InvalidField("region all/none selection has trailing data")
}
selection
}
///|
fn validate_region_selection(
dataspace : Dataspace,
selection : Hdf5RegionSelection,
) -> Unit raise Hdf5Error {
let dimensions = dataspace.dimensions
let rank = dimensions.length()
match selection {
RegionEmpty | RegionAll => ()
RegionPoints(points) =>
for point in points {
guard point.length() == rank else {
raise InvalidField("region point rank does not match the dataset")
}
for axis in 0.. {
guard start.length() == rank &&
stride.length() == rank &&
count.length() == rank &&
block.length() == rank else {
raise InvalidField("region hyperslab rank does not match the dataset")
}
for axis in 0.. 0UL else {
raise InvalidField("region hyperslab stride must be positive")
}
guard start[axis] < dimensions[axis] else {
raise InvalidField("region hyperslab start is outside the dataset")
}
let steps = count[axis] - 1UL
guard steps <= (0xFFFFFFFFFFFFFFFFUL - start[axis]) / stride[axis] else {
raise InvalidField("region hyperslab coordinate overflow")
}
let stride_end = start[axis] + steps * stride[axis]
guard block[axis] - 1UL <= 0xFFFFFFFFFFFFFFFFUL - stride_end else {
raise InvalidField("region hyperslab coordinate overflow")
}
guard stride_end + block[axis] - 1UL < dimensions[axis] else {
raise InvalidField("region hyperslab extends outside the dataset")
}
}
}
RegionBlocks(blocks) =>
for block in blocks {
guard block.start.length() == rank && block.end.length() == rank else {
raise InvalidField("region block rank does not match the dataset")
}
for axis in 0.. Hdf5RegionSelection raise Hdf5Error {
let selection_type = reader.read_u32_le().reinterpret_as_int()
let version = reader.read_u32_le().reinterpret_as_int()
let selection = match selection_type {
0 => parse_region_empty_selection(reader, version, RegionEmpty)
1 => parse_region_points(reader, version)
2 => parse_region_hyperslab(reader, version)
3 => parse_region_empty_selection(reader, version, RegionAll)
_ => raise UnsupportedFeature("unsupported region selection type")
}
guard reader.remaining() == 0 else {
raise InvalidField("region selection has trailing bytes")
}
validate_region_selection(dataspace, selection)
selection
}
///|
fn Hdf5File::read_reference_heap_object(
self : Hdf5File,
heap_address : UInt64,
object_index : Int32,
) -> Bytes raise Hdf5Error {
match self.source {
Some(source) =>
read_source_global_heap_object(
source,
heap_address,
object_index,
self.superblock.length_size,
self.superblock.base_address,
)
None =>
read_global_heap_object(
self.bytes,
heap_address,
object_index,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
}
}
///|
/// Resolves a legacy HDF5 dataset-region reference.
///
/// The all-zero null reference returns `None`. The target must be a dataset in
/// the same file. Region selection versions 1-3 for hyperslabs, versions 1-2
/// for points, and all/none selections are supported. Version-4 encoded
/// references are not supported.
pub fn Hdf5File::resolve_region_reference(
self : Hdf5File,
reference : Bytes,
) -> Hdf5RegionReferenceTarget? raise Hdf5Error {
let offset_size = self.superblock.offset_size
let reference_size = offset_size + 4
guard reference.length() == reference_size else {
raise InvalidField(
"region reference must be " +
reference_size.to_string() +
" bytes, found " +
reference.length().to_string(),
)
}
if reference_is_null(reference) {
return None
}
let reader = Reader::new(reference)
let heap_address = reader.read_uint_le(offset_size)
let object_index_value = reader.read_u32_le()
guard heap_address != 0UL &&
heap_address != undefined_address &&
object_index_value > 0U &&
object_index_value <= 0xFFFFU else {
raise InvalidField("invalid region-reference heap identifier")
}
let payload = self.read_reference_heap_object(
heap_address,
object_index_value.reinterpret_as_int(),
)
guard payload.length() > offset_size else {
raise InvalidField("region-reference heap object is too short")
}
let payload_reader = Reader::new(payload)
let address = payload_reader.read_uint_le(offset_size)
let header = self.object_header_at(address) catch {
error =>
raise InvalidField(
"region-reference target is not a valid object header: " +
error.message(),
)
}
let kind = object_kind(header)
guard kind == Dataset else {
raise InvalidField("region-reference target is not a dataset")
}
let metadata = read_dataset_metadata(
header,
offset_size,
self.superblock.length_size,
)
let selection = parse_region_selection(payload_reader, metadata.dataspace)
let path = self.find_reference_path(address)
Some({ address, kind, path, dataspace: metadata.dataspace, selection, })
}
///|
fn all_region_indices(dataspace : Dataspace) -> Array[UInt64] raise Hdf5Error {
let count = dataspace.element_count()
guard count >= 0 && count <= 0x7FFFFFFFL else {
raise InvalidField("region selection contains too many elements")
}
let indices : Array[UInt64] = []
for index in 0.. Array[UInt64] {
indices.sort_by((left, right) => {
if left < right {
-1
} else if left > right {
1
} else {
0
}
})
let unique : Array[UInt64] = []
for index in indices {
if unique.length() == 0 || unique[unique.length() - 1] != index {
unique.push(index)
}
}
unique
}
///|
fn regular_region_selection_indices(
dataspace : Dataspace,
start : Array[UInt64],
stride : Array[UInt64],
count : Array[UInt64],
block : Array[UInt64],
) -> (Array[UInt64], Array[UInt64]) raise Hdf5Error {
let rank = dataspace.dimensions.length()
let dimensions : Array[UInt64] = []
for axis in 0..= 0 && total <= 0x7FFFFFFFL else {
raise InvalidField("region selection contains too many elements")
}
let indices : Array[UInt64] = []
if total == 0L {
return (dimensions, indices)
}
let positions = Array::make(rank, 0UL)
for _ in 0..= 0 {
positions[axis] = positions[axis] + 1UL
if positions[axis] < dimensions[axis] {
break
}
positions[axis] = 0UL
axis = axis - 1
}
}
(dimensions, sort_unique_region_indices(indices))
}
///|
fn block_region_selection_indices(
dataspace : Dataspace,
blocks : Array[Hdf5RegionBlock],
) -> (Array[UInt64], Array[UInt64]) raise Hdf5Error {
let rank = dataspace.dimensions.length()
let indices : Array[UInt64] = []
let block_dimensions : Array[Array[UInt64]] = []
let mut total = 0L
for block in blocks {
let dimensions : Array[UInt64] = []
for axis in 0..= 0 && total <= 0x7FFFFFFFL - count else {
raise InvalidField("region selection contains too many elements")
}
total = total + count
block_dimensions.push(dimensions)
}
for block_index in 0..= 0 {
positions[axis] = positions[axis] + 1UL
if positions[axis] < dimensions[axis] {
break
}
positions[axis] = 0UL
axis = axis - 1
}
}
}
let indices = sort_unique_region_indices(indices)
([indices.length().to_uint64()], indices)
}
///|
fn region_selection_indices(
dataspace : Dataspace,
selection : Hdf5RegionSelection,
) -> (Array[UInt64], Array[UInt64]) raise Hdf5Error {
match selection {
RegionEmpty => ([0UL], [])
RegionAll => (dataspace.dimensions.copy(), all_region_indices(dataspace))
RegionPoints(points) => {
let indices : Array[UInt64] = []
for point in points {
indices.push(coordinate_linear_index(point, dataspace.dimensions))
}
([points.length().to_uint64()], indices)
}
RegionHyperslab(start, stride, count, block) =>
regular_region_selection_indices(dataspace, start, stride, count, block)
RegionBlocks(blocks) => block_region_selection_indices(dataspace, blocks)
}
}
///|
/// Reads the values selected by a legacy HDF5 dataset-region reference.
///
/// A null reference returns `None`. The result uses the selection shape for a
/// regular hyperslab and a flat element count for points or irregular blocks.
pub fn Hdf5File::read_region_reference(
self : Hdf5File,
reference : Bytes,
) -> DatasetSelectionResult? raise Hdf5Error {
let target = match self.resolve_region_reference(reference) {
Some(target) => target
None => return None
}
let header = self.object_header_at(target.address)
let metadata = read_dataset_metadata(
header,
self.superblock.offset_size,
self.superblock.length_size,
)
let (dimensions, indices) = region_selection_indices(
metadata.dataspace,
target.selection,
)
let result = match self.source {
Some(source) =>
read_source_dataset_indices(
source,
header,
metadata,
dimensions,
indices,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
None =>
read_dataset_indices(
self.bytes,
header,
metadata,
dimensions,
indices,
self.superblock.offset_size,
self.superblock.length_size,
self.superblock.base_address,
)
}
Some(result)
}