Advanced example
Mode understanding
Modes can be set by providing mode=.. keyword or explicitly by specifying read_only and overwrite options.
'w' mode is the same as
>>> sd = DH5('somedata.h5', read_only=False)
'a' mode is the same as
>>> sd = DH5('somedata.h5', read_only=False, overwrite=False)
'r' mode is the same as
>>> sd = DH5('somedata.h5', read_only=True)
DH5.open_overwrite method is the same as
>>> sd = DH5('somedata.h5', read_only=False, overwrite=True)
or
>>> sd = DH5('somedata.h5', mode='w', overwrite=True)
Mirroring acquisitions with lifecycle hooks
Use LifecycleHooks on AcquisitionAnalysisManager: create the manager, then MirrorDirectoryPlugin(mirror_root).attach(aqm.hooks). No backend= argument.
MirrorDirectoryPlugin below is example code — it is not shipped with the labmate package. Copy it into your project or notebook (see also use_local_mirror_backend.ipynb).
The plugin copies the HDF5 and any same-stem sidecar files into mirror_root / <experiment_dir_name> / after saves, and before each analysis load it may copy a missing .h5 back from that mirror.
from __future__ import annotations
import logging
import shutil
from concurrent.futures import Future, ThreadPoolExecutor
from pathlib import Path
from typing import Any, Union
from labmate.acquisition import NotebookAcquisitionData, LifecycleHooks
logger = logging.getLogger(__name__)
class MirrorDirectoryPlugin:
"""Mirrors acquisition files to a second directory and can restore ``.h5`` before analysis."""
def __init__(self, mirror_root: Union[str, Path]) -> None:
self._mirror_root = Path(mirror_root)
def attach(self, hooks: LifecycleHooks) -> None:
hooks.add_acquisition_saved(self._mirror_after_persist)
hooks.add_figure_saved(self._mirror_after_persist)
hooks.add_analysis_data_loading(self._on_analysis_data_loading)
def _mirror_after_persist(self, acquisition: NotebookAcquisitionData, **kwargs: Any) -> None:
"""Schedule mirror copy on a worker thread so the notebook thread is not blocked."""
executor = ThreadPoolExecutor(max_workers=1)
def copy_job() -> None:
self._mirror_copy_to_destination(acquisition)
def shutdown_executor(_future: Future) -> None:
executor.shutdown(wait=False)
future = executor.submit(copy_job)
future.add_done_callback(shutdown_executor)
def _mirror_copy_to_destination(self, acquisition: NotebookAcquisitionData) -> None:
source_prefix = Path(acquisition.filepath)
source_dir = source_prefix.parent
prefix_name = source_prefix.name
destination_dir = self._mirror_root / source_dir.name
destination_dir.mkdir(parents=True, exist_ok=True)
for src in source_dir.glob(f"{prefix_name}*"):
if src.is_file():
shutil.copy2(src, destination_dir / src.name)
def _on_analysis_data_loading(self, full_h5_path: str, **kwargs: Any) -> None:
"""Restore missing local ``.h5`` from mirror; must stay synchronous before open."""
target = Path(full_h5_path)
if target.is_file():
return
mirror_path = self._mirror_root / target.parent.name / target.name
if not mirror_path.is_file():
return
logger.info("Restoring %s from mirror at %s", target, self._mirror_root)
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(mirror_path, target)
from labmate.acquisition_notebook import AcquisitionAnalysisManager
aqm = AcquisitionAnalysisManager("/data/labmate")
MirrorDirectoryPlugin("/mnt/labmate-mirror").attach(aqm.hooks)
For custom behavior, register callbacks on aqm.hooks with add_acquisition_saved, add_figure_saved, add_analysis_data_loading, etc.