Skip to content

Commit

Permalink
👔 Make nuisance regression space non-forkable
Browse files Browse the repository at this point in the history
  • Loading branch information
shnizzedy committed Nov 9, 2023
1 parent ecfc583 commit 94d539f
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Uses highest resolution available locally as reference when resampling a template to a non-packaged resolution (was always using 1mm reference before)
- Updates config boolean validation from anything-truthy-is-True (e.g., `[True, False]`, or `[False]`, or a typo like `Offf`) to only accepting bools, ints, and YAML boolean strings like "On" and "Off" as boolean
- When applying a filter to motion parameters, now C-PAC reports both the original and the filtered motion parameters and uses the original parameters for qc. Previous versions only reported the filtered parameters and used the filtered parameters for qc.
- Makes nuisance regression space non-forkable. In v1.8.5, nuisance regression forking was broken, so this change should not cause backwards-compatibility issues.

### Added dependencies

Expand Down
8 changes: 4 additions & 4 deletions CPAC/nuisance/nuisance.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ def choose_nuisance_blocks(cfg, rpool, generate_only=False):
nuisance.append((nuisance_regressors_generation_EPItemplate,
input_interface))

if not generate_only and 'native' in cfg['nuisance_corrections',
'2-nuisance_regression',
'space']:
if not generate_only and cfg['nuisance_corrections',
'2-nuisance_regression',
'space'] == 'native':
nuisance.append((nuisance_regression_native, input_interface))

return nuisance


Expand Down
7 changes: 3 additions & 4 deletions CPAC/pipeline/cpac_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -1356,7 +1356,7 @@ def build_workflow(subject_id, sub_dict, cfg, pipeline_name=None,

pipeline_blocks += [func_despike_template]

if 'Template' in target_space_alff and 'Native' in target_space_nuis:
if 'Template' in target_space_alff and target_space_nuis == 'native':
pipeline_blocks += [warp_denoiseNofilt_to_T1template]

template = cfg.registration_workflows['functional_registration'][
Expand Down Expand Up @@ -1392,9 +1392,8 @@ def build_workflow(subject_id, sub_dict, cfg, pipeline_name=None,
warp_deriv_mask_to_EPItemplate]

# Template-space nuisance regression
nuisance_template = 'template' in cfg[
'nuisance_corrections', '2-nuisance_regression', 'space'
] and not generate_only
nuisance_template = (cfg['nuisance_corrections', '2-nuisance_regression',
'space'] == 'template') and (not generate_only)
if nuisance_template:
pipeline_blocks += [nuisance_regression_template]
# pipeline_blocks += [(nuisance_regression_template,
Expand Down
28 changes: 20 additions & 8 deletions CPAC/pipeline/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
import numpy as np
from pathvalidate import sanitize_filename
from voluptuous import All, ALLOW_EXTRA, Any, BooleanInvalid, Capitalize, \
Coerce, ExclusiveInvalid, In, Length, LengthInvalid, \
Lower, Match, Maybe, Optional, Range, Required, \
Schema, Title
Coerce, CoerceInvalid, ExclusiveInvalid, In, Length, \
LengthInvalid, Lower, Match, Maybe, MultipleInvalid, \
Optional, Range, Required, Schema, Title
from CPAC import docs_prefix
from CPAC.utils.datatypes import ListFromItem
from CPAC.utils.datatypes import ItemFromList, ListFromItem
from CPAC.utils.utils import YAML_BOOLS

# 1 or more digits, optional decimal, 'e', optional '-', 1 or more digits
Expand Down Expand Up @@ -134,6 +134,8 @@ def str_to_bool1_1(x): # pylint: disable=invalid-name
},
'target_space': ['Native', 'Template']
}
valid_options['space'] = list({option.lower() for option in
valid_options['target_space']})
mutex = { # mutually exclusive booleans
'FSL-BET': {
# exactly zero or one of each of the following can be True for FSL-BET
Expand Down Expand Up @@ -886,8 +888,8 @@ def sanitize(filename):
},
'2-nuisance_regression': {
'run': forkable,
'space': All(Coerce(ListFromItem),
[All(Lower, In({'native', 'template'}))]),
'space': All(Coerce(ItemFromList),
Lower, In({'native', 'template'})),
'create_regressors': bool1_1,
'ingress_regressors': {
'run': bool1_1,
Expand Down Expand Up @@ -1096,7 +1098,17 @@ def schema(config_dict):
dict
'''
from CPAC.utils.utils import _changes_1_8_0_to_1_8_1
partially_validated = latest_schema(_changes_1_8_0_to_1_8_1(config_dict))
try:
partially_validated = latest_schema(
_changes_1_8_0_to_1_8_1(config_dict))
except MultipleInvalid as multiple_invalid:
if (multiple_invalid.path == ['nuisance_corrections',
'2-nuisance_regression', 'space'] and
isinstance(multiple_invalid.errors[0], CoerceInvalid)):
raise CoerceInvalid(
'Nusiance regression space is not forkable. Please choose '
f'only one of {valid_options["space"]}',
path=multiple_invalid.path) from multiple_invalid
try:
if (partially_validated['registration_workflows'][
'functional_registration'
Expand All @@ -1110,7 +1122,7 @@ def schema(config_dict):
if True in partially_validated['nuisance_corrections'][
'2-nuisance_regression']['run'] and partially_validated[
'nuisance_corrections'
]['2-nuisance_regression']['space'] != ['template']:
]['2-nuisance_regression']['space'] != 'template':
raise ExclusiveInvalid(
'``single_step_resampling_from_stc`` requires '
'template-space nuisance regression. Either set '
Expand Down
7 changes: 3 additions & 4 deletions CPAC/resources/configs/pipeline_config_default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1367,13 +1367,12 @@ nuisance_corrections:
# run: [On, Off] - this will run both and fork the pipeline
run: [On]

# this is a fork point
# this is not a fork point
# Run nuisance regression in native or template space
# - If set to [native, template], the number of outputs will be double what it would be if only one space were chosen. Nuisance regression will only be run once per fork.
# - If set to template, will use the brain mask configured in
# ``functional_preproc: func_masking: FSL_AFNI: brain_mask``
# - If ``registration_workflows: functional_registration: func_registration_to_template: apply_trasnform: using: single_step_resampling_from_stc``, this must only be set to template
space: [native]
# - If ``registration_workflows: functional_registration: func_registration_to_template: apply_trasnform: using: single_step_resampling_from_stc``, this must be set to template
space: native

# switch to Off if nuisance regression is off and you don't want to write out the regressors
create_regressors: On
Expand Down
28 changes: 28 additions & 0 deletions CPAC/utils/datatypes.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
"""Custom datatypes for C-PAC"""
class ItemFromList: # pylint: disable=too-few-public-methods
"""Coerce single-item lists into just the only item in the list.
Returns item if item is not a list, set, or tuple.
Raises CoerceInvalid if impossible.
Examples
--------
>>> ItemFromList(['seagull'])
'seagull'
>>> ItemFromList(['two', 'seagulls'])
Traceback (most recent call last):
...
voluptuous.error.CoerceInvalid: Cannot coerce list of length 2 to item
>>> ItemFromList('string')
'string'
"""
def __new__(cls, list_of_one, msg=None):
"""Initialize item from list"""
from voluptuous import CoerceInvalid, Length, LengthInvalid
if not isinstance(list_of_one, (list, set, tuple)):
return list_of_one
try:
Length(max=1)(list_of_one)
except LengthInvalid as length_invalid:
raise CoerceInvalid(
f'Cannot coerce list of length {len(list_of_one)} to item'
if msg is None else msg) from length_invalid
return list_of_one[0]


class ListFromItem(list):
Expand Down

0 comments on commit 94d539f

Please sign in to comment.