Skip to content

ASN OptoDAS (.hdf5) OOI-RCA 2024

Interrogator: Alcatel Submarine Networks (ASN) OptoDAS
Format: HDF5 (.hdf5)
Profile key: optodas_v2
Example dataset: Wilcock, W., & Ocean Observatories Initiative. (2024) — OOI-RCA muxDAS 2024

Reader function

read_optodas_v2

read_optodas_v2(
    path: str,
    stride: Optional[int] = None,
    read_dmin_m: Optional[float] = None,
    read_dmax_m: Optional[float] = None,
    **kwargs
) -> DASDataset

Read a DAS acquisition from an OptoDAS interrogator (Alcatel Subsea Networks), HDF5 format as used in the 2024/2025 OOI RCA experiments.

HDF5 structure (confirmed on UW OOI 2025 files)

/data int16 (n_time, n_channels) /header/dt float64 sampling period [s] /header/dx float64 nominal channel spacing [m] /header/time float64 UTC start time [Unix seconds] /header/gaugeLength float64 gauge length [m] /header/dataScale float64 int16 -> rad/(s.m) scale factor /header/wavelength float64 laser wavelength [m] /header/unit str physical unit string /cableSpec/sensorDistances float64 (n_channels,) actual dist per channel [m]

Units

Raw int16 values are scaled by header/dataScale to give phase rate per distance [rad/(s.m)]. This is returned directly as the data unit.

Parameters

path : str Path to the OptoDAS HDF5 file (.hdf5). stride : int, optional Channel subsampling factor.

Returns

DASDataset

Source code in dasexplorer\core\readers_lib\optodas.py
def read_optodas_v2(
    path: str,
    stride: Optional[int] = None,
    read_dmin_m: Optional[float] = None,
    read_dmax_m: Optional[float] = None,
    **kwargs,
) -> DASDataset:

    ######################################################################
    ### OPTODAS - ASN/ ALCATEL SUBMARINE NETWORK (.hdf5) OOI RCA 2025 (preliminary)
    # https://oceanobservatories.org/2025/01/regional-cabled-array-monitoring-axial-seamount-in-real-time/
    ######################################################################

    """
    Read a DAS acquisition from an OptoDAS interrogator (Alcatel Subsea Networks),
    HDF5 format as used in the 2024/2025 OOI RCA experiments.

    HDF5 structure (confirmed on UW OOI 2025 files)
    ------------------------------------------------
    /data                          int16  (n_time, n_channels)
    /header/dt                     float64  sampling period [s]
    /header/dx                     float64  nominal channel spacing [m]
    /header/time                   float64  UTC start time [Unix seconds]
    /header/gaugeLength            float64  gauge length [m]
    /header/dataScale              float64  int16 -> rad/(s.m) scale factor
    /header/wavelength             float64  laser wavelength [m]
    /header/unit                   str      physical unit string
    /cableSpec/sensorDistances     float64  (n_channels,) actual dist per channel [m]

    Units
    -----
    Raw int16 values are scaled by header/dataScale to give phase rate per
    distance [rad/(s.m)].  This is returned directly as the data unit.

    Parameters
    ----------
    path : str
        Path to the OptoDAS HDF5 file (.hdf5).
    stride : int, optional
        Channel subsampling factor.

    Returns
    -------
    DASDataset
    """
    import h5py

    with h5py.File(path, "r") as f:
        hdr = f["header"]

        # Sampling parameters
        dt_s   = float(hdr["dt"][()])
        fs_hz  = 1.0 / dt_s
        dx_m   = float(hdr["dx"][()])
        gl_m   = float(hdr["gaugeLength"][()])
        scale  = float(hdr["dataScale"][()])
        wl_m   = float(hdr["wavelength"][()])

        # Unit string
        raw_unit = hdr["unit"][()]
        units = raw_unit.decode() if isinstance(raw_unit, bytes) else str(raw_unit)

        # Start time: Unix seconds -> UTC datetime
        t0_s     = float(hdr["time"][()])
        start_dt = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + \
                   datetime.timedelta(seconds=t0_s)

        # Data: int16 (n_time, n_channels) -> float32 (n_channels, n_time)
        raw = f["data"][:]
        tr  = (raw.T.astype(np.float32)) * scale

        # Channel distances: use real per-channel sensorDistances when available
        if "cableSpec/sensorDistances" in f:
            dist_m = f["cableSpec/sensorDistances"][:].astype(np.float64)
            n_ch = tr.shape[0]
            if dist_m.shape[0] >= n_ch:
                dist_m = dist_m[:n_ch]
            else:
                dist_m = np.arange(n_ch) * dx_m
        else:
            dist_m = np.arange(tr.shape[0]) * dx_m

    # Time axis
    time_s = np.arange(tr.shape[1]) * dt_s

    # Optional channel stride
    downsample = None
    if stride is not None and stride > 1:
        tr         = tr[::stride, :]
        dist_m     = dist_m[::stride]
        downsample = stride

    # Spatial crop
    channel_offset = 0
    if read_dmin_m is not None or read_dmax_m is not None:
        dmin = read_dmin_m if read_dmin_m is not None else float(dist_m[0])
        dmax = read_dmax_m if read_dmax_m is not None else float(dist_m[-1])
        mask = (dist_m >= dmin) & (dist_m <= dmax)
        first_idx = int(np.argmax(mask))
        channel_offset = first_idx * int(downsample or 1)
        tr = tr[mask, :]
        dist_m = dist_m[mask]

    return DASDataset(
        tr=tr,
        dist_m=dist_m,
        time_s=time_s,
        fs_hz=fs_hz,
        start_datetime_utc=start_dt,
        filename=os.path.basename(path),
        reader="optodas",
        downsample=downsample,
        channel_offset=channel_offset,
        metadata={
            "dx_m":           dx_m,
            "gauge_length_m": gl_m,
            "wavelength_m":   wl_m,
            "data_scale":     scale,
        },
        units=units,
    )