Source code for photon_mosaic_pipeline.rules.suite2p_run

"""
Snakemake rule for running Suite2P.
"""

import os
import traceback
from pathlib import Path
from typing import Optional

from suite2p import run_s2p
from suite2p.default_ops import default_ops


def _force_cellpose_cpu_if_requested():
    """Force Cellpose onto CPU when ``PHOTON_MOSAIC_FORCE_CPU=1``.

    Suite2p's anatomical detection runs Cellpose on a GPU whenever
    ``cellpose.core.use_gpu()`` reports one is available. On a real
    Apple-Silicon machine the MPS backend works and is the right default, so
    this is **off by default**. But GitHub-hosted macOS runners expose an MPS
    device that torch detects and can allocate on, yet which computes
    Cellpose-SAM incorrectly -- it returns 0 masks, so Suite2p writes no
    ``F.npy`` and the pipeline fails. Setting ``PHOTON_MOSAIC_FORCE_CPU=1``
    pins Cellpose to the CPU, which produces correct masks everywhere. The
    test suite sets this var (see ``tests/conftest.py``); the snakemake
    subprocesses inherit it.
    """
    if os.environ.get("PHOTON_MOSAIC_FORCE_CPU") != "1":
        return
    try:
        import cellpose.core

        cellpose.core.use_gpu = lambda *args, **kwargs: False
    except ImportError:
        # Cellpose isn't importable (e.g. anatomical detection disabled) --
        # nothing to force; suite2p will run its non-Cellpose path.
        pass


[docs] def run_suite2p( stat_path: str, dataset_folder: Path, user_ops_dict: Optional[dict] = None, ): """ This function runs Suite2P on a given dataset folder and saves the results in the specified paths. It also handles any exceptions that may occur during the process and logs them in an error file. Parameters ---------- stat_path : str The path where the Suite2P statistics will be saved. dataset_folder : Path The path to the folder containing the dataset. user_ops_dict : dict, optional A dictionary containing user-provided options to override the default Suite2P options. The default is None. Returns ------- None The function runs Suite2P and saves results to the specified paths. If an error occurs, it logs the error to an error.txt file in the dataset folder. """ save_folder = Path(stat_path).parents[1] _force_cellpose_cpu_if_requested() ops = get_edited_options( input_path=dataset_folder, save_folder=save_folder, user_ops_dict=user_ops_dict, ) try: run_s2p(ops=ops) except Exception as e: with open(dataset_folder / "error.txt", "a") as f: f.write(f"Error: {e}\n") f.write(traceback.format_exc())
[docs] def get_edited_options( input_path: Path, save_folder: Path, user_ops_dict: Optional[dict] = None ) -> dict: """Generate a dictionary of options for Suite2P by loading the default options and then modifying them with user-provided options. The function also sets the required runtime paths for saving the results. Parameters ---------- input_path : Path The path to the input data folder. save_folder : Path The path to the folder where the results will be saved. user_ops_dict : dict, optional A dictionary containing user-provided options to override the default options. The default is None. Returns ------- dict A dictionary containing the Suite2P options, including the user-provided options and the required runtime paths. Raises ------ ValueError If a user-provided option is not valid for Suite2P. """ ops = default_ops() # Override with user-provided subset of keys if user_ops_dict: for key, val in user_ops_dict.items(): if key not in ops: raise ValueError(f"Invalid Suite2p option: {key}") ops[key] = val # Add required runtime paths ops["save_folder"] = str(save_folder) ops["save_path0"] = str(save_folder) ops["fast_disk"] = str(save_folder.parent) ops["data_path"] = [str(input_path)] return ops