"""Helpers for dense NXlog views over full-length metadata streams."""
from __future__ import annotations
from typing import Optional, Sequence, Tuple
import h5py
import numpy as np
from coseda.nexus.groups import ensure_nexus_parents
_GONIOMETER_LOGS: Sequence[Tuple[str, str]] = (
("stagepos_x", "stage_x_log"),
("stagepos_x_refined", "stage_x_refined_log"),
("stagepos_y", "stage_y_log"),
("stagepos_y_refined", "stage_y_refined_log"),
("stagepos_z", "stage_z_log"),
("alphatilt", "alpha_tilt_log"),
("betatilt", "beta_tilt_log"),
)
_DETECTOR_LOGS: Sequence[Tuple[str, str]] = (
("center_x", "beam_center_x_log"),
("center_y", "beam_center_y_log"),
("det_shift_x_mm", "detector_shift_x_log"),
("det_shift_y_mm", "detector_shift_y_log"),
)
_TIME_CANDIDATES = (
"/entry/data/timestamp_image",
"/entry/data/timestamp",
"/entry/data/frame_id",
)
def _find_time_source(h5file: h5py.File, length: int) -> Optional[h5py.Dataset]:
for path in _TIME_CANDIDATES:
if path in h5file:
ds = h5file[path]
if ds.shape and ds.shape[0] == length:
return ds
return None
def _ensure_value_link(log_group: h5py.Group, value_ds: h5py.Dataset) -> None:
if "value" in log_group:
try:
if log_group["value"].id == value_ds.id:
return
except Exception:
pass
del log_group["value"]
log_group["value"] = value_ds
def _ensure_time_dataset(log_group: h5py.Group, length: int, source: Optional[h5py.Dataset]) -> None:
if source is not None:
if "time" in log_group:
try:
if log_group["time"].id == source.id:
return
except Exception:
pass
del log_group["time"]
log_group["time"] = source
return
if "time" in log_group:
ds = log_group["time"]
if ds.shape == (length,):
return
del log_group["time"]
ds = log_group.create_dataset("time", data=np.arange(length, dtype=np.int64))
ds.attrs["units"] = "index"
ds.attrs["long_name"] = "Frame index"
[docs]
def ensure_dense_logs(h5file: h5py.File) -> None:
"""
Create NXlog views for dense metadata streams under /entry/instrument.
"""
ensure_nexus_parents(h5file)
goniometer = h5file.require_group("entry/instrument/goniometer")
goniometer.attrs["NX_class"] = "NXgoniometer"
for dataset_name, log_name in _GONIOMETER_LOGS:
source_path = f"/entry/data/{dataset_name}"
if source_path not in h5file:
continue
value_ds = h5file[source_path]
if not value_ds.shape:
continue
log_group = goniometer.require_group(log_name)
log_group.attrs["NX_class"] = "NXlog"
_ensure_value_link(log_group, value_ds)
time_source = _find_time_source(h5file, value_ds.shape[0])
_ensure_time_dataset(log_group, value_ds.shape[0], time_source)
detector = h5file.require_group("entry/instrument/detector")
detector.attrs["NX_class"] = "NXdetector"
for dataset_name, log_name in _DETECTOR_LOGS:
source_path = f"/entry/data/{dataset_name}"
if source_path not in h5file:
continue
value_ds = h5file[source_path]
if not value_ds.shape:
continue
log_group = detector.require_group(log_name)
log_group.attrs["NX_class"] = "NXlog"
_ensure_value_link(log_group, value_ds)
time_source = _find_time_source(h5file, value_ds.shape[0])
_ensure_time_dataset(log_group, value_ds.shape[0], time_source)