Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
802f736
add site flag and in qcfc
claraElk Jul 17, 2026
d322b28
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 17, 2026
b63998f
add site_correction in WonkyConnConfig
claraElk Jul 20, 2026
3ac2dcc
Remove print in test
claraElk Jul 20, 2026
ad50da7
Rename variable for pre-commit
claraElk Jul 20, 2026
b34a6b4
Remove site for prediction for now
claraElk Jul 20, 2026
02d2423
draft site correction for prediction
claraElk Jul 22, 2026
80bc3e8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 22, 2026
59d4e53
Fit both train and test
claraElk Jul 23, 2026
d8edae2
Refactor site correction for prediction
claraElk Jul 24, 2026
2ea2be8
Fix typing issues for mypy and pyright
HippocampusGirl Aug 2, 2026
c5c593e
Add stratification by site to CV sampler
HippocampusGirl Aug 2, 2026
39f306e
Fix warnings that come up during unit tests
HippocampusGirl Aug 2, 2026
dbfdac5
Update test code for readability
HippocampusGirl Aug 2, 2026
468177e
[DATALAD] Recorded changes
HippocampusGirl Aug 2, 2026
e1eb6f3
Ensure full code coverage
HippocampusGirl Aug 2, 2026
bea3359
Upload code coverage from unittest run too
HippocampusGirl Aug 2, 2026
24298ca
Fix or ignore all pyright errors
HippocampusGirl Aug 2, 2026
dcd9fd7
Parametrize site correction for smoke tests
HippocampusGirl Aug 2, 2026
9381898
Silence warnings for missing data
HippocampusGirl Aug 2, 2026
253a9a8
Improve readability even more
HippocampusGirl Aug 2, 2026
dedee88
Fix or ignore remaining warnings
HippocampusGirl Aug 2, 2026
05ae4b0
Update outdated docstrings
HippocampusGirl Aug 2, 2026
5c0d2f5
Remove getattr calls for arguments
HippocampusGirl Aug 2, 2026
a2b562b
Expand test case to check for all column errors
HippocampusGirl Aug 2, 2026
c2ce58a
Make stratified split failure more verbose
HippocampusGirl Aug 7, 2026
e221347
Change error to warning and exclude subjects that are the only subjec…
HippocampusGirl Aug 7, 2026
2c17820
Fix stratify for numeric site column
HippocampusGirl Aug 7, 2026
d12c792
Allow specifying specific metrics to run in cli
HippocampusGirl Aug 10, 2026
189a424
Improve index performance
HippocampusGirl Aug 10, 2026
0801919
Add --log-level cli variable as an alternative to --verbosity
HippocampusGirl Aug 10, 2026
b7cb04b
Reduce the amount of log messages
HippocampusGirl Aug 10, 2026
3af2f83
Store the dmn similarity results in a subdirectory
HippocampusGirl Aug 10, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
cache-to: type=gha,mode=max
tags: wonky:conn
load: true
# always use light mode for testing container since the full version was tested during build
# Always use light mode for testing container since the full version was tested during build
- run: |
datalad get data/halfpipe data/atlases
docker run --rm \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
environments: test
frozen: true
- run: pixi run unittest
- run: pixi run smoketestlight
- run: pixi run smoketestlight --cov-append
- uses: codecov/codecov-action@v6
if: ${{ always() }}
with:
Expand Down
8 changes: 7 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ repos:
rev: v1.19.1
hooks:
- id: mypy
additional_dependencies: [pandas-stubs, types-tqdm, types-setuptools, types-Jinja2]
additional_dependencies:
- pandas-stubs
- pytest
- types-tqdm
- types-setuptools
- types-Jinja2
- textual
args: [--config-file=pyproject.toml]
- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
Expand Down
15 changes: 9 additions & 6 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
# # noisy red coverage status on github PRs.
# target: auto
# threshold: 1%
comment: # this is a top-level key
comment: # this is a top-level key
layout: reach, diff, flags, files
behavior: default
require_changes: false # if true: only post the comment if coverage changes
require_base: no # [yes :: must have a base report to post]
require_head: yes # [yes :: must have a head report to post]
require_changes: false # if true: only post the comment if coverage changes
require_base: false # if true: must have a base report to post
require_head: false # if true: must have a head report to post

ignore:
- '*/tests/' # ignore folders related to testing
- '*/data/'
- "**/tests/**"
- "**/test_*.py"
- "**/conftest.py"
- "**/data/**"
- "**/setup.py"
2 changes: 1 addition & 1 deletion data/halfpipe/participants.tsv
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,18 @@ warn_unused_ignores = true
ignore_missing_imports = true
module = [
"bids.*",
"brainspace.*",
"datalad.*",
"joblib",
"matplotlib.*",
"nilearn",
"nilearn.*",
"numba.*",
"patsy.*",
"rich.*",
"scipy.*",
"seaborn.*",
"sklearn.*",
"statsmodels.*",
"templateflow.*",
"nibabel.*",
Expand Down
19 changes: 10 additions & 9 deletions wonkyconn/atlas.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import numpy as np
import pandas as pd
import scipy
from nilearn.image import iter_img, load_img, math_img, resample_to_img # type: ignore[import-not-found]
from nilearn.maskers import NiftiLabelsMasker, NiftiMasker # type: ignore[import-not-found]
from nilearn.image import iter_img, load_img, math_img, resample_to_img
from nilearn.maskers import NiftiLabelsMasker, NiftiMasker
from numpy import typing as npt

from .logger import logger
Expand All @@ -29,7 +29,7 @@ class Atlas(ABC):
"""

seg: str
image: nib.nifti1.Nifti1Image
image: nib.nifti1.Nifti1Image # pyright: ignore[reportAttributeAccessIssue]

structure: npt.NDArray[np.bool_] = field(default_factory=lambda: np.ones((3, 3, 3), dtype=bool))

Expand All @@ -51,7 +51,7 @@ def get_centroids(self) -> npt.NDArray[np.float64]:
npt.NDArray[np.float64]: An array of centroid coordinates.
"""
centroid_points = self.get_centroid_points()
centroid_coordinates = nib.affines.apply_affine(self.image.affine, centroid_points)
centroid_coordinates = nib.affines.apply_affine(self.image.affine, centroid_points) # pyright: ignore[reportAttributeAccessIssue]
return centroid_coordinates

def get_distance_matrix(self) -> npt.NDArray[np.float64]:
Expand All @@ -65,7 +65,7 @@ def get_distance_matrix(self) -> npt.NDArray[np.float64]:
centroids = self.get_centroids()
return scipy.spatial.distance.squareform(scipy.spatial.distance.pdist(centroids))

def load_yeo7_network(self) -> nib.nifti1.Nifti1Image:
def load_yeo7_network(self) -> nib.nifti1.Nifti1Image: # pyright: ignore[reportAttributeAccessIssue]
"""
Load and resample the yeo 7 networks to the atlas's space.

Expand Down Expand Up @@ -102,10 +102,10 @@ def create(seg: str, path: Path) -> "Atlas":
None

"""
image = nib.nifti1.load(path)
image = nib.nifti1.load(path) # pyright: ignore[reportAttributeAccessIssue]

if image.ndim <= 3 or image.shape[3] == 1:
return DsegAtlas(seg, nib.funcs.squeeze_image(image))
return DsegAtlas(seg, nib.funcs.squeeze_image(image)) # pyright: ignore[reportAttributeAccessIssue]
else:
return ProbsegAtlas(seg, image)

Expand Down Expand Up @@ -142,7 +142,8 @@ def get_yeo7_membership(self) -> pd.DataFrame:
for n in network_labels:
cur_region = math_img(f"img=={n}", img=yeo7_nii)
masker = NiftiMasker(cur_region)
atlas_parcel_in_network = masker.fit_transform(self.image)
masker.fit()
atlas_parcel_in_network = masker.transform(self.image)
atlas_parcel_in_network = np.unique(atlas_parcel_in_network)[1:]
region_membership.loc[atlas_parcel_in_network, f"yeo7-{int(n)}"] = 1
return region_membership
Expand All @@ -161,7 +162,7 @@ def _get_centroid_point(self, i: int, array: npt.NDArray[np.float64]) -> tuple[f

def get_centroid_points(self) -> npt.NDArray[np.float64]:
return np.asarray(
[self._get_centroid_point(i, image.get_fdata()) for i, image in enumerate(nib.funcs.four_to_three(self.image))]
[self._get_centroid_point(i, image.get_fdata()) for i, image in enumerate(nib.funcs.four_to_three(self.image))] # pyright: ignore[reportAttributeAccessIssue]
)

def get_yeo7_membership(self) -> pd.DataFrame:
Expand Down
69 changes: 28 additions & 41 deletions wonkyconn/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import argparse
from dataclasses import dataclass, field
from pathlib import Path
from typing import Iterable, Sequence
from typing import Literal, TypeAlias


def _coerce_path(value: str | Path | None) -> Path | None:
Expand All @@ -13,66 +13,53 @@ def _coerce_path(value: str | Path | None) -> Path | None:
return Path(value).expanduser().resolve()


Metric: TypeAlias = Literal["motion", "analytic-insights", "gradients", "prediction"]

light_mode_metrics: set[Metric] = {"motion", "analytic-insights"}
all_metrics: set[Metric] = {"motion", "analytic-insights", "gradients", "prediction"}


@dataclass
class WonkyConnConfig:
class WonkyconnConfig:
"""Shared configuration for CLI and GUI."""

bids_dir: Path | None = None
output_dir: Path | None = None
analysis_level: str = "group"
phenotypes: Path | None = None
atlas: list[tuple[str, Path]] = field(default_factory=list)
verbosity: int = 2
log_level: str | None = None
debug: bool = False
light_mode: bool = False
metrics: set[Metric] | None = None
theme: str | None = None # GUI-only
suppress_warnings: bool = False
site_correction: bool = False

@property
def light_mode(self) -> bool:
return self.metrics == light_mode_metrics

@classmethod
def from_cli_args(cls, args: argparse.Namespace | None) -> "WonkyConnConfig":
def from_cli_args(cls, args: argparse.Namespace | None) -> "WonkyconnConfig":
"""Create a config from argparse args (may be partial when GUI is requested)."""
if args is None:
return cls()

verbosity = args.verbosity
if isinstance(verbosity, Sequence) and not isinstance(verbosity, (str, bytes)):
verbosity = verbosity[0]

atlas_entries: list[tuple[str, Path]] = list()
for label, atlas_path in getattr(args, "atlas", []) or []:
for label, atlas_path in args.atlas or []:
atlas_entries.append((label, Path(atlas_path).expanduser().resolve()))

metrics: set[Metric] = light_mode_metrics if args.light_mode else set(args.metrics)

return cls(
bids_dir=_coerce_path(getattr(args, "bids_dir", None)),
output_dir=_coerce_path(getattr(args, "output_dir", None)),
analysis_level=getattr(args, "analysis_level", "group"),
phenotypes=_coerce_path(getattr(args, "phenotypes", None)),
bids_dir=_coerce_path(args.bids_dir),
output_dir=_coerce_path(args.output_dir),
analysis_level=args.analysis_level,
phenotypes=_coerce_path(args.phenotypes),
atlas=atlas_entries,
verbosity=int(verbosity) if verbosity is not None else 2,
debug=bool(getattr(args, "debug", False)),
light_mode=bool(getattr(args, "light_mode", False)),
suppress_warnings=bool(getattr(args, "suppress_warnings", False)),
)

def to_namespace(self) -> argparse.Namespace:
"""Convert to argparse.Namespace expected by workflow."""
if self.bids_dir is None:
raise ValueError("bids_dir is required")
if self.output_dir is None:
raise ValueError("output_dir is required")
if self.phenotypes is None:
raise ValueError("phenotypes is required")
if not self.atlas:
raise ValueError("At least one atlas entry is required")

atlas_as_str: Iterable[tuple[str, str]] = ((label, str(path)) for label, path in self.atlas)
return argparse.Namespace(
bids_dir=self.bids_dir,
output_dir=self.output_dir,
analysis_level=self.analysis_level,
phenotypes=str(self.phenotypes),
atlas=list(atlas_as_str),
verbosity=self.verbosity,
debug=self.debug,
light_mode=self.light_mode,
log_level=args.log_level,
debug=bool(args.debug),
metrics=metrics,
suppress_warnings=bool(args.suppress_warnings),
site_correction=bool(args.site_correction),
)
Loading
Loading