Source code for meanap.catnap.loader

"""Load suite2p output files into numpy arrays."""

from dataclasses import dataclass, field
from pathlib import Path

import numpy as np


[docs] @dataclass class Suite2pData: """All arrays loaded from one suite2p/plane0 directory.""" # Raw fluorescence, shape (n_cells_all, n_frames) F: np.ndarray = field(default_factory=lambda: np.empty((0, 0))) # Inferred spike probabilities, shape (n_cells_all, n_frames) spks: np.ndarray = field(default_factory=lambda: np.empty((0, 0))) # iscell[:,0] is 1 for cells, 0 for non-cells; shape (n_rois, 2) iscell: np.ndarray = field(default_factory=lambda: np.empty((0, 2))) # XY centroids, shape (2, n_rois) xy_loc: np.ndarray = field(default_factory=lambda: np.empty((2, 0))) # Sampling rate (Hz) fs: float = 0.0 # Number of frames n_frames: int = 0 # Duration in seconds duration_s: float = 0.0 # Pre-computed denoising outputs (present only if Fdenoised.npy exists) F_denoised: np.ndarray | None = None # (n_rois, n_frames) peak_start_frames: np.ndarray | None = None # (n_rois, max_peaks), NaN-padded peak_end_frames: np.ndarray | None = None peak_heights: np.ndarray | None = None event_areas: np.ndarray | None = None time_points: np.ndarray | None = None # (n_frames,) in seconds # Derived: cell-only views (filtered by iscell) @property def cell_mask(self) -> np.ndarray: return self.iscell[:, 0].astype(bool) @property def n_cells(self) -> int: return int(self.cell_mask.sum()) @property def F_cells(self) -> np.ndarray: """F for labelled cells only, shape (n_cells, n_frames).""" return self.F[self.cell_mask] @property def spks_cells(self) -> np.ndarray: return self.spks[self.cell_mask] @property def xy_cells(self) -> np.ndarray: """XY centroids for cells, shape (n_cells, 2).""" return self.xy_loc[:, self.cell_mask].T @property def F_denoised_cells(self) -> np.ndarray | None: if self.F_denoised is None: return None return self.F_denoised[self.cell_mask]
[docs] def load_suite2p(plane0_dir: str | Path) -> Suite2pData: """ Load all available suite2p outputs from *plane0_dir*. Required files: F.npy, iscell.npy, stat.npy, ops.npy Optional files: spks.npy, Fdenoised.npy, peakStartFrames.npy, peakEndFrames.npy, peakHeights.npy, eventAreas.npy, timePoints.npy """ d = Path(plane0_dir) F = np.load(d / "F.npy") iscell = np.load(d / "iscell.npy") stat = np.load(d / "stat.npy", allow_pickle=True) x_loc = np.array([s["med"][0] for s in stat]) y_loc = np.array([s["med"][1] for s in stat]) xy_loc = np.stack([x_loc, y_loc]) ops = np.load(d / "ops.npy", allow_pickle=True).item() fs = float(ops["fs"]) n_frames = F.shape[1] duration_s = n_frames / fs spks = np.load(d / "spks.npy") if (d / "spks.npy").exists() else np.zeros_like(F) data = Suite2pData( F=F, spks=spks, iscell=iscell, xy_loc=xy_loc, fs=fs, n_frames=n_frames, duration_s=duration_s, ) # Load pre-computed denoising outputs if present if (d / "Fdenoised.npy").exists(): data.F_denoised = np.load(d / "Fdenoised.npy") data.time_points = (np.load(d / "timePoints.npy") if (d / "timePoints.npy").exists() else np.arange(n_frames) / fs) if (d / "peakStartFrames.npy").exists(): data.peak_start_frames = np.load(d / "peakStartFrames.npy") data.peak_end_frames = np.load(d / "peakEndFrames.npy") data.peak_heights = np.load(d / "peakHeights.npy") data.event_areas = np.load(d / "eventAreas.npy") return data