Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions nibabies/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,10 @@ def load(cls, settings, init=True, ignore=None):
if k in ignore or v is None:
continue
if k in cls._paths:
setattr(cls, k, Path(v).absolute())
if isinstance(v, (list, tuple)): # Multiple paths
setattr(cls, k, [Path(p).absolute() for p in v])
else:
setattr(cls, k, Path(v).absolute())
elif hasattr(cls, k):
setattr(cls, k, v)

Expand All @@ -221,7 +224,10 @@ def get(cls):
if callable(getattr(cls, k)):
continue
if k in cls._paths:
v = str(v)
if isinstance(v, (list, tuple)): # Multiple paths
v = [str(p) for p in v]
else:
v = str(v)
if isinstance(v, SpatialReferences):
v = " ".join([str(s) for s in v.references]) or None
if isinstance(v, Reference):
Expand Down Expand Up @@ -412,6 +418,7 @@ class execution(_Config):
"anat_derivatives",
"bids_dir",
"bids_database_dir",
"derivatives",
"fs_license_file",
"fs_subjects_dir",
"layout",
Expand Down Expand Up @@ -609,6 +616,8 @@ class seeds(_Config):
"""Master random seed to initialize the Pseudorandom Number Generator (PRNG)"""
ants = None
"""Seed used for antsRegistration, antsAI, antsMotionCorr"""
numpy = None
"""Seed used by NumPy"""

@classmethod
def init(cls):
Expand All @@ -619,6 +628,7 @@ def init(cls):
random.seed(cls.master) # initialize the PRNG
# functions to set program specific seeds
cls.ants = _set_ants_seed()
cls.numpy = _set_numpy_seed()


def _set_ants_seed():
Expand All @@ -628,6 +638,15 @@ def _set_ants_seed():
return val


def _set_numpy_seed():
"""NumPy's random seed is independant from Python's `random` module"""
import numpy as np

val = random.randint(1, 65536)
np.random.seed(val)
return val


def from_dict(settings):
"""Read settings from a flat dictionary."""
nipype.load(settings)
Expand Down Expand Up @@ -707,7 +726,7 @@ def init_spaces(checkpoint=True):
from .utils.misc import cohort_by_months

cohort = cohort_by_months("MNIInfant", workflow.age_months)
spaces.add(Reference, {"cohort": cohort})
spaces.add(Reference("MNIInfant", {"cohort": cohort}))

# Ensure user-defined spatial references for outputs are correctly parsed.
# Certain options require normalization to a space not explicitly defined by users.
Expand Down
80 changes: 80 additions & 0 deletions nibabies/data/tests/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
[environment]
cpu_count = 8
exec_docker_version = "20.10.12"
exec_env = "nibabies-docker"
free_mem = 0.4
overcommit_policy = "heuristic"
overcommit_limit = "50%"
nipype_version = "1.6.1"
templateflow_version = "0.7.2"
version = "21.1.0"

[execution]
bids_dir = "/data"
bids_database_dir = "/tmp/bids_db"
bids_description_hash = "c47e9ebb943ca662556808b2aeac3f6c8bb2a242696c32850c64ec47aba80d9e"
boilerplate_only = false
sloppy = true
debug = []
derivatives = [ "/opt/derivatives/precomputed",]
fs_license_file = "/opt/freesurfer/license.txt"
fs_subjects_dir = "/opt/subjects"
layout = "BIDS Layout: .../data | Subjects: 1 | Sessions: 1 | Runs: 1"
log_dir = "/tmp/logs"
log_level = 20
low_mem = false
md_only_boilerplate = false
nibabies_dir = "/out"
notrack = false
output_dir = "/out"
me_output_echos = false
output_layout = "bids"
output_spaces = "MNIInfant:cohort-1:res-native"
reports_only = false
run_uuid = "20220323-202555_01a7d80d-7ff4-4b13-a99c-ec399045e9ff"
segmentation_atlases_dir = "/opt/segmentations"
participant_label = [ "01",]
templateflow_home = "/home/nibabies/.cache/templateflow"
work_dir = "/scratch"
write_graph = false

[workflow]
anat_only = false
aroma_err_on_warn = false
aroma_melodic_dim = -200
bold2t1w_dof = 6
bold2t1w_init = "register"
cifti_output = false
fd_radius = 45
fmap_bspline = false
force_syn = false
hires = true
ignore = [ "slicetiming",]
longitudinal = false
medial_surface_nan = false
regressors_all_comps = false
regressors_dvars_th = 1.5
regressors_fd_th = 0.5
run_reconall = true
skull_strip_fixed_seed = false
skull_strip_template = "UNCInfant:cohort-1"
skull_strip_t1w = "force"
slice_time_ref = 0.5
spaces = "MNIInfant:cohort-1:res-native MNIInfant:cohort-1"
use_aroma = false
use_bbr = false
use_syn_sdc = false

[nipype]
crashfile_format = "txt"
get_linked_libs = false
memory_gb = 8
nprocs = 4
omp_nthreads = 2
plugin = "MultiProc"
resource_monitor = false
stop_on_first_crash = false

[nipype.plugin_args]
maxtasksperchild = 1
raise_insufficient = false
Empty file added nibabies/tests/__init__.py
Empty file.
136 changes: 136 additions & 0 deletions nibabies/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
#
# Copyright 2021 The NiPreps Developers <nipreps@gmail.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# We support and encourage derived works from this project, please read
# about our expectations at
#
# https://www.nipreps.org/community/licensing/
#
"""Check the configuration module and file."""
import os
from pathlib import Path
from pkg_resources import resource_filename as pkgrf
from unittest.mock import patch

import pytest
from toml import loads
from niworkflows.utils.spaces import format_reference

from .. import config


def _reset_config():
"""
Forcibly reload the configuration module to restore defaults.
.. caution::
`importlib.reload` creates new sets of objects, but will not remove
previous references to those objects."""
import importlib

importlib.reload(config)


def test_reset_config():
execution = config.execution
setattr(execution, 'bids_dir', 'TESTING')
assert config.execution.bids_dir == 'TESTING'
_reset_config()
assert config.execution.bids_dir is None
# Even though the config module was reset,
# previous references to config classes
# have not been touched.
assert execution.bids_dir == 'TESTING'


def test_config_spaces():
"""Check that all necessary spaces are recorded in the config."""
filename = Path(pkgrf('nibabies', 'data/tests/config.toml'))
settings = loads(filename.read_text())
for sectionname, configs in settings.items():
if sectionname != 'environment':
section = getattr(config, sectionname)
section.load(configs, init=False)
config.nipype.init()
config.loggers.init()
config.init_spaces()

spaces = config.workflow.spaces
assert "MNI152NLin6Asym:res-2" not in [str(s) for s in spaces.get_standard(full_spec=True)]

assert "MNI152NLin6Asym_res-2" not in [
format_reference((s.fullname, s.spec))
for s in spaces.references
if s.standard and s.dim == 3
]

config.workflow.use_aroma = True
config.init_spaces()
spaces = config.workflow.spaces

assert "MNI152NLin6Asym:res-2" in [str(s) for s in spaces.get_standard(full_spec=True)]

assert "MNI152NLin6Asym_res-2" in [
format_reference((s.fullname, s.spec))
for s in spaces.references
if s.standard and s.dim == 3
]

config.execution.output_spaces = None
config.workflow.use_aroma = False
config.workflow.age_months = None
config.init_spaces()
spaces = config.workflow.spaces

assert [str(s) for s in spaces.get_standard(full_spec=True)] == []
assert [
format_reference((s.fullname, s.spec))
for s in spaces.references
if s.standard and s.dim == 3
] == []

# but adding age will populate output spaces with a default
config.execution.output_spaces = None
config.workflow.use_aroma = False
config.workflow.age_months = 1
config.init_spaces()
spaces = config.workflow.spaces

assert [str(s) for s in spaces.get_standard(full_spec=True)] == []
assert [
format_reference((s.fullname, s.spec))
for s in spaces.references
if s.standard and s.dim == 3
] == ['MNIInfant_cohort-1']
_reset_config()


@pytest.mark.parametrize(
"master_seed,ants_seed,numpy_seed", [(1, 17612, 8272), (100, 19094, 60232)]
)
def test_prng_seed(master_seed, ants_seed, numpy_seed):
"""Ensure seeds are properly tracked"""
seeds = config.seeds
with patch.dict(os.environ, {}):
seeds.load({'_random_seed': master_seed}, init=True)
assert getattr(seeds, 'master') == master_seed
assert seeds.ants == ants_seed
assert seeds.numpy == numpy_seed
assert os.getenv("ANTS_RANDOM_SEED") == str(ants_seed)

_reset_config()
for seed in ('_random_seed', 'master', 'ants', 'numpy'):
assert getattr(config.seeds, seed) is None
2 changes: 1 addition & 1 deletion wrapper/nibabies_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
MISSING = """
Image '{}' is missing
Would you like to download? [Y/n] """
PKG_PATH = "/usr/local/miniconda/lib/python3.8/site-packages"
PKG_PATH = "/opt/conda/lib/python3.8/site-packages"
TF_TEMPLATES = (
"MNI152Lin",
"MNI152NLin2009cAsym",
Expand Down