Source code for CPAC.pipeline.random_state.seed

# Copyright (C) 2022-2023  C-PAC Developers

# This file is part of C-PAC.

# C-PAC is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.

# C-PAC is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
# License for more details.

# You should have received a copy of the GNU Lesser General Public
# License along with C-PAC. If not, see <https://www.gnu.org/licenses/>.
"""Functions to set, check, and log random seed."""

import random

import numpy as np
from nipype.interfaces.ants.registration import Registration
from nipype.interfaces.ants.segmentation import Atropos
from nipype.interfaces.freesurfer.preprocess import ApplyVolTransform, ReconAll
from nipype.interfaces.fsl.maths import MathsCommand
from nipype.interfaces.fsl.utils import ImageMaths

from CPAC.utils.interfaces.ants import AI
from CPAC.utils.monitoring.custom_logging import getLogger, set_up_logger

_seed = {"seed": None}


def random_random_seed():
    """Returns a random postive integer up to 2147483647.

    Parameters
    ----------
    None

    Returns
    -------
    random_seed : int

    Examples
    --------
    >>> 0 < random_random_seed() <= np.iinfo(np.int32).max
    True
    """
    return random.randint(1, np.iinfo(np.int32).max)


def random_seed():
    """Function to access current random seed.

    Parameters
    ----------
    None

    Returns
    -------
    seed : int or None
    """
    if _seed["seed"] == "random":
        _seed["seed"] = random_random_seed()
    return _seed["seed"]


[docs] def random_seed_flags(): """Function to return dictionary of flags with current random seed. Parameters ---------- None Returns ------- dict of {'functions': {function: function}, 'interfaces': {list or tuple}} Developer note: sequence matters here! Only the first match will be applied! In the 'functions' sub-dictionary, each key should be a function used in a :py:class:`CPAC.utils.interfaces.function.Function`, and each value should be a function that takes a single parameter (a function) to apply to the key-function such that, once the value-function is applied, the random seed is applied when the key-function is run. In the 'interfaces' sub-dictionary, each key should be an :py:class:`nipype.interfaces.base.core.Interface`, and each value should be either a list of strings to add to the interface's flags/args or a tuple of (list of strings to add to, list of strings to remove from) the interface's flags/args. Examples -------- >>> list(random_seed_flags().keys()) ['functions', 'interfaces'] >>> all([isinstance(random_seed_flags()[key], dict) for key in [ ... 'functions', 'interfaces']]) True >>> rs = set_up_random_state('random') >>> list(random_seed_flags().keys()) ['functions', 'interfaces'] >>> all([isinstance(random_seed_flags()[key], dict) for key in [ ... 'functions', 'interfaces']]) True """ from CPAC.registration.utils import hardcoded_reg seed = random_seed() if seed is None: return {"functions": {}, "interfaces": {}} return { "functions": { # function: lambda function to apply to function source hardcoded_reg: lambda fn_string: fn_string.replace( 'regcmd = ["antsRegistration"]', f'regcmd = ["antsRegistration", "--random-seed", "{seed}"]', ) }, "interfaces": { # interface: [flags to apply] # OR # interface: ([flags to apply], [flags to remove]) # # ANTs # NOTE: Atropos gives the option "Initialize internal random number # generator with a random seed. Otherwise, initialize with a # constant seed number," so for Atropos nodes, the built-in # Atropos constant seed is used if a seed is specified for # C-PAC AI: _reusable_flags()["ANTs"], Registration: _reusable_flags()["ANTs"], Atropos: ( ["--use-random-seed 0"], [ flag for one in ["", " 1"] for flag in [f"--use-random-seed{one}", f"-r{one}"] ], ), # FreeSurfer ReconAll: ["-norandomness", f"-rng-seed {seed}"], ApplyVolTransform: [f"--seed {seed}"], # FSL ImageMaths: _reusable_flags()["FSL"], MathsCommand: _reusable_flags()["FSL"], }, }
def _reusable_flags(): seed = random_seed() return {"ANTs": [f"--random-seed {seed}"], "FSL": [f"-seed {seed}"]} def set_up_random_state(seed): """Set global random seed. Parameters ---------- seed : int, 'random', or None Returns ------- seed: int or None Examples -------- >>> isinstance(set_up_random_state('random'), int) True >>> set_up_random_state('rando') Traceback (most recent call last): ValueError: Valid random seeds are positive integers up to 2147483647, "random", or None, not rando >>> set_up_random_state(100) 100 >>> set_up_random_state(0) Traceback (most recent call last): ValueError: Valid random seeds are positive integers up to 2147483647, "random", or None, not 0 >>> set_up_random_state(None) """ # pylint: disable=line-too-long if seed is not None: if seed == "random": seed = random_random_seed() else: try: seed = int(seed) assert 0 < seed <= np.iinfo(np.int32).max except (ValueError, TypeError, AssertionError): msg = ( "Valid random seeds are positive integers up to " f'2147483647, "random", or None, not {seed}' ) raise ValueError(msg) _seed["seed"] = seed return random_seed() def set_up_random_state_logger(log_dir): """Prepare C-PAC for logging random seed use. Parameters ---------- log_dir : str """ set_up_logger("random", level="info", log_dir=log_dir, mock=True) getLogger("random").info("seed: %s", random_seed())