Source code for gunz_cm.compressions.bsc_decoder

"""
BSC Decoder wrapper for GZCM v3 compression.

Uses BSC CLI subprocess with LD_LIBRARY_PATH for libomp.

Examples
--------
"""

__author__ = "Yeremia Gunawan Adhisantoso"
__email__ = "adhisant@tnt.uni-hannover.de"
__license__ = "Clear BSD"

import subprocess
import tempfile
import pathlib
import numpy as np

from ._paths import resolve_bsc_binary, make_bsc_env


[docs]class BscDecoder: """BSC decoder for contact matrix tiles. Uses bsc CLI subprocess for true BSC (Block Sorting Compression) decompression. Parameters ---------- tile_size : int, default=512 Tile size for block processing. resolution : int, default=50000 Hi-C resolution in bp. dtype : np.dtype, default=np.uint32 Data type for decoded tiles. bsc_bin : str or pathlib.Path, optional Explicit path to the `bsc` binary. If None, resolved via GUNZ_CM_BSC_BIN env var or system PATH. ld_library_path : str or pathlib.Path, optional Explicit LD_LIBRARY_PATH for the bsc subprocess. If None, resolved via GUNZ_CM_BSC_LD_LIBRARY_PATH env var. Examples -------- """ def __init__( self, tile_size: int = 512, resolution: int = 50000, dtype: np.dtype = np.uint32, bsc_bin: str | pathlib.Path | None = None, ld_library_path: str | pathlib.Path | None = None, ): """ Examples -------- """ self.tile_size = tile_size self.resolution = resolution self.dtype = np.dtype(dtype) self._bsc_path = resolve_bsc_binary(bsc_bin) self._env = make_bsc_env(ld_library_path)
[docs] def decode_tile( self, payload: bytes, shape: tuple[int, int] | None = None, ) -> np.ndarray: """Decode a single BSC-compressed tile. Parameters ---------- payload : bytes BSC-compressed bitstream. When ``shape`` is provided (v3 GZCM path), the first 8 bytes encode ``(rows, cols)`` as ``np.int32`` and are stripped before decompression. When ``shape`` is ``None`` the payload is treated as raw compressed bytes (legacy / codec tests). shape : tuple of (int, int), optional Actual tile shape ``(rows, cols)``. Required for edge tiles where ``rows != self.tile_size`` or ``cols != self.tile_size``. If ``None``, falls back to ``(self.tile_size, self.tile_size)`` and assumes no 8-byte header is present. Returns ------- np.ndarray Decoded contact matrix tile with the requested shape. Examples -------- """ if shape is not None: # v3 GZCM path: payload = 8-byte (rows, cols) int32 header + body rows, cols = shape body = payload[8:] else: # Legacy / direct codec API: raw compressed bytes, square tile. rows = cols = self.tile_size body = payload with tempfile.NamedTemporaryFile(suffix=".dat", delete=False) as f_in: f_in.write(body) f_in.flush() in_path = pathlib.Path(f_in.name) with tempfile.NamedTemporaryFile(suffix=".dat", delete=False) as f_out: out_path = pathlib.Path(f_out.name) try: subprocess.run([str(self._bsc_path), "d", str(in_path), str(out_path)], check=True, capture_output=True, env=self._env) with open(out_path, "rb") as f: data = f.read() return np.frombuffer(data, dtype=self.dtype).reshape(rows, cols) finally: in_path.unlink(missing_ok=True) out_path.unlink(missing_ok=True)
[docs] def decode_tiles( self, payloads: list[bytes], shapes: list[tuple[int, int]] | None = None, ) -> np.ndarray: """Decode multiple tiles into a 4D array. Parameters ---------- payloads : list[bytes] List of encoded bitstreams. shapes : list of tuple of (int, int), optional Per-tile shapes matching ``payloads``. When provided, each payload is assumed to carry the 8-byte header. Defaults to ``None`` (legacy: square tiles, no header). Returns ------- np.ndarray 4D array of decoded tiles (n_tile_rows, n_tile_cols, tile_rows, tile_cols). Examples -------- """ n_tiles = len(payloads) if shapes is None: decoded = [self.decode_tile(p) for p in payloads] else: decoded = [self.decode_tile(p, shape=s) for p, s in zip(payloads, shapes, strict=True)] tile_shape = decoded[0].shape tile_rows = int(np.sqrt(n_tiles)) tile_cols = n_tiles // tile_rows if tile_rows > 0 else 1 result = np.empty((tile_rows, tile_cols, *tile_shape), dtype=self.dtype) idx = 0 for i in range(tile_rows): for j in range(tile_cols): result[i, j] = decoded[idx] idx += 1 return result