Showing preview only (964K chars total). Download the full file or copy to clipboard to get everything.
Repository: neuronets/nobrainer
Branch: master
Commit: 3109460d048b
Files: 220
Total size: 898.7 KB
Directory structure:
gitextract_7t995rdz/
├── .autorc
├── .dockerignore
├── .flake8
├── .gitattributes
├── .github/
│ ├── EC2_GPU_RUNNER.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── documentation.md
│ │ ├── feature_request.md
│ │ ├── maintenance.md
│ │ └── question.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ ├── ci.yml
│ ├── guide-notebooks-ec2.yml
│ ├── kwyk-reproduction-ec2.yml
│ ├── publish.yml
│ ├── release.yml
│ └── validate-book.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .zenodo.json
├── CHANGELOG.md
├── CITATION
├── CLAUDE.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── conftest.py
├── docker/
│ ├── README.md
│ ├── cpu.Dockerfile
│ └── gpu.Dockerfile
├── nobrainer/
│ ├── __init__.py
│ ├── _version.py
│ ├── augmentation/
│ │ ├── __init__.py
│ │ ├── profiles.py
│ │ ├── synthseg.py
│ │ └── transforms.py
│ ├── cli/
│ │ ├── __init__.py
│ │ ├── main.py
│ │ └── tests/
│ │ ├── __init__.py
│ │ └── main_test.py
│ ├── dataset.py
│ ├── datasets/
│ │ ├── __init__.py
│ │ ├── openneuro.py
│ │ └── zarr_store.py
│ ├── distributed_learning/
│ │ └── dwc.py
│ ├── experiment.py
│ ├── gpu.py
│ ├── io.py
│ ├── layers/
│ │ ├── InstanceNorm.py
│ │ ├── __init__.py
│ │ ├── bernoulli_dropout.py
│ │ ├── concrete_dropout.py
│ │ ├── gaussian_dropout.py
│ │ ├── maxpool4d.py
│ │ ├── padding.py
│ │ └── tests/
│ │ └── __init__.py
│ ├── losses.py
│ ├── metrics.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── _constants.py
│ │ ├── _utils.py
│ │ ├── autoencoder.py
│ │ ├── bayesian/
│ │ │ ├── __init__.py
│ │ │ ├── bayesian_meshnet.py
│ │ │ ├── bayesian_vnet.py
│ │ │ ├── kwyk_meshnet.py
│ │ │ ├── layers.py
│ │ │ ├── utils.py
│ │ │ ├── vwn_layers.py
│ │ │ └── warmstart.py
│ │ ├── generative/
│ │ │ ├── __init__.py
│ │ │ ├── dcgan.py
│ │ │ └── progressivegan.py
│ │ ├── highresnet.py
│ │ ├── meshnet.py
│ │ ├── segformer3d.py
│ │ ├── segmentation.py
│ │ ├── simsiam.py
│ │ └── tests/
│ │ └── __init__.py
│ ├── prediction.py
│ ├── processing/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── croissant.py
│ │ ├── dataset.py
│ │ ├── generation.py
│ │ └── segmentation.py
│ ├── research/
│ │ ├── __init__.py
│ │ ├── loop.py
│ │ └── templates/
│ │ ├── .gitkeep
│ │ ├── prepare.py
│ │ └── train_bayesian_vnet.py
│ ├── slurm.py
│ ├── sr-tests/
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_bayesian_uncertainty.py
│ │ ├── test_brain_generation.py
│ │ ├── test_croissant_metadata.py
│ │ ├── test_dataset_builder.py
│ │ ├── test_extract_patches.py
│ │ ├── test_kwyk_smoke.py
│ │ ├── test_raw_pytorch_api.py
│ │ ├── test_segmentation_estimator.py
│ │ ├── test_synthseg_brain.py
│ │ ├── test_zarr_conversion.py
│ │ └── test_zarr_pipeline.py
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── contract/
│ │ │ ├── __init__.py
│ │ │ └── test_cli.py
│ │ ├── gpu/
│ │ │ ├── __init__.py
│ │ │ ├── test_bayesian_e2e.py
│ │ │ ├── test_gan_e2e.py
│ │ │ ├── test_multi_gpu.py
│ │ │ └── test_predict_e2e.py
│ │ ├── integration/
│ │ │ ├── __init__.py
│ │ │ ├── test_datalad_commit.py
│ │ │ └── test_research_smoke.py
│ │ └── unit/
│ │ ├── __init__.py
│ │ ├── test_bayesian_layers.py
│ │ ├── test_bayesian_models.py
│ │ ├── test_class_weights.py
│ │ ├── test_croissant.py
│ │ ├── test_dataset.py
│ │ ├── test_dataset_builder.py
│ │ ├── test_datasets_openneuro.py
│ │ ├── test_estimator_generation.py
│ │ ├── test_estimator_segmentation.py
│ │ ├── test_experiment.py
│ │ ├── test_generative.py
│ │ ├── test_gpu.py
│ │ ├── test_io_weights.py
│ │ ├── test_io_zarr.py
│ │ ├── test_layers.py
│ │ ├── test_losses.py
│ │ ├── test_metrics.py
│ │ ├── test_model_interface.py
│ │ ├── test_model_registry.py
│ │ ├── test_models_segmentation.py
│ │ ├── test_prediction.py
│ │ ├── test_research_commit.py
│ │ ├── test_research_loop.py
│ │ ├── test_segformer3d.py
│ │ ├── test_slurm.py
│ │ ├── test_stride_patches.py
│ │ ├── test_synthseg.py
│ │ ├── test_training.py
│ │ ├── test_training_convergence.py
│ │ ├── test_transform_pipeline.py
│ │ ├── test_vwn_layers.py
│ │ ├── test_zarr_dataset.py
│ │ └── test_zarr_store.py
│ ├── training.py
│ ├── utils.py
│ └── validation.py
├── pyproject.toml
└── scripts/
├── kwyk_reproduction/
│ ├── 01_assemble_dataset.py
│ ├── 02_train_meshnet.py
│ ├── 03_train_bayesian.py
│ ├── 04_evaluate.py
│ ├── 05_compare_kwyk.py
│ ├── 06_block_size_sweep.py
│ ├── ARCHITECTURE.md
│ ├── README.md
│ ├── __init__.py
│ ├── build_kwyk_manifest.py
│ ├── config.yaml
│ ├── config_kwyk_smoke.yaml
│ ├── convert_zarr_shard.py
│ ├── experiments/
│ │ ├── 01_20260330_eval_deterministic/
│ │ │ ├── README.md
│ │ │ ├── eval_deterministic.py
│ │ │ ├── results_summary.md
│ │ │ └── run.sbatch
│ │ ├── 02_20260330_binary_bayesian/
│ │ │ ├── README.md
│ │ │ ├── config.yaml
│ │ │ ├── eval_binary.py
│ │ │ ├── eval_only.sbatch
│ │ │ └── run.sbatch
│ │ ├── 03_20260330_warmstart_diagnostic/
│ │ │ ├── README.md
│ │ │ ├── diagnose.py
│ │ │ ├── results_summary.md
│ │ │ └── run.sbatch
│ │ ├── 04_20260330_fixed_warmstart/
│ │ │ ├── README.md
│ │ │ ├── run.py
│ │ │ └── run.sbatch
│ │ ├── 05_20260330_kwyk_from_scratch/
│ │ │ ├── README.md
│ │ │ ├── results_summary.md
│ │ │ ├── run.py
│ │ │ └── run.sbatch
│ │ ├── 06_20260331_fullvol_augment/
│ │ │ ├── README.md
│ │ │ ├── config_256.yaml
│ │ │ ├── config_256_mp.yaml
│ │ │ ├── config_fullvol.yaml
│ │ │ ├── run_128.sbatch
│ │ │ ├── run_256.sbatch
│ │ │ ├── run_256_a100.sbatch
│ │ │ ├── run_256_gradckpt.sbatch
│ │ │ └── run_256_mp.sbatch
│ │ ├── 07_20260401_ddp_128/
│ │ │ ├── config.yaml
│ │ │ └── run.sbatch
│ │ ├── 08_20260401_ddp_128_full/
│ │ │ ├── config.yaml
│ │ │ └── run.sbatch
│ │ └── task-planner.md
│ ├── label_mappings/
│ │ ├── 115-class-mapping.csv
│ │ ├── 50-class-mapping.csv
│ │ └── 6-class-mapping.csv
│ ├── run.sh
│ ├── slurm_convert_zarr.sbatch
│ ├── slurm_kwyk_bayesian.sbatch
│ ├── slurm_kwyk_evaluate.sbatch
│ ├── slurm_kwyk_smoke.sbatch
│ ├── slurm_train.sbatch
│ ├── slurm_zarr_array.sbatch
│ ├── submit_kwyk_smoke.sh
│ └── utils.py
└── synthseg_evaluation/
├── 02_train.py
├── 03_evaluate.py
├── 04_compare.py
├── README.md
├── config.yaml
├── run.sh
└── slurm_train.sbatch
================================================
FILE CONTENTS
================================================
================================================
FILE: .autorc
================================================
{
"onlyPublishWithReleaseLabel": false,
"baseBranch": "master",
"prereleaseBranches": ["alpha"],
"author": "Nobrainer Bot <nobrainer@mit.edu>",
"noVersionPrefix": true,
"plugins": ["git-tag"]
}
================================================
FILE: .dockerignore
================================================
.git/
docker/
.idea/
================================================
FILE: .flake8
================================================
[flake8]
max-line-length = 100
exclude =
.git/
__pycache__/
build/
dist/
_version.py
versioneer.py
ignore =
E203
W503
================================================
FILE: .gitattributes
================================================
nobrainer/_version.py export-subst
================================================
FILE: .github/EC2_GPU_RUNNER.md
================================================
# EC2 GPU Runner Setup
This document describes how to configure the AWS EC2 instance used as a
self-hosted GitHub Actions runner for GPU integration tests.
The workflow (`guide-notebooks-ec2.yml`) uses
[machulav/ec2-github-runner](https://github.com/machulav/ec2-github-runner) to
start an ephemeral EC2 instance, run GPU tests, and terminate the instance
automatically.
## AMI preparation
Start from the **AWS Deep Learning Base AMI (Amazon Linux 2023)** or any
Amazon Linux 2023 AMI with NVIDIA drivers and CUDA pre-installed. The AMI must
be in the same region as the `AWS_REGION` variable configured in GitHub.
### 1. Launch an instance to build the AMI
```bash
aws ec2 run-instances \
--image-id ami-XXXXXXXX \
--instance-type g4dn.xlarge \
--key-name your-key-pair \
--security-group-ids sg-XXXXXXXX \
--subnet-id subnet-XXXXXXXX \
--block-device-mappings '[{"DeviceName":"/dev/xvda","Ebs":{"VolumeSize":100}}]' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=nobrainer-ami-builder}]'
```
### 2. SSH in as ec2-user and configure
```bash
ssh -i your-key.pem ec2-user@<public-ip>
```
All commands below run as `ec2-user`.
#### Install system dependencies
```bash
sudo dnf install -y jq git
```
#### Install uv
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
```
#### Create the pre-installed nobrainer venv
The CI workflow expects a venv at `~/nobrainer-env` with heavy dependencies
(torch, monai, pyro-ppl) already installed. This avoids re-downloading ~2 GB
of packages on every CI run.
```bash
uv venv --python 3.14 ~/nobrainer-env
# Install the heavy GPU dependencies into the base venv
uv pip install \
torch \
monai \
pyro-ppl \
pytorch-lightning \
pytest
```
#### Verify GPU access
```bash
source ~/nobrainer-env/bin/activate
python -c "
import torch
assert torch.cuda.is_available(), 'CUDA not available'
print(f'GPU: {torch.cuda.get_device_name(0)}')
print(f'CUDA: {torch.version.cuda}')
print(f'PyTorch: {torch.__version__}')
"
deactivate
```
### 3. Create the AMI
Stop the instance (or use `--no-reboot`), then:
```bash
aws ec2 create-image \
--instance-id i-XXXXXXXXX \
--name "nobrainer-pytorch-gpu-$(date +%Y%m%d)" \
--description "Amazon Linux 2023 + CUDA + PyTorch + uv for nobrainer GPU CI" \
--no-reboot
```
Note the resulting AMI ID — this goes into the `AWS_IMAGE_ID` variable.
### 4. Terminate the builder instance
```bash
aws ec2 terminate-instances --instance-id i-XXXXXXXXX
```
## GitHub configuration
### Secrets (Settings → Secrets → Actions)
| Name | Description |
|------|-------------|
| `AWS_KEY_ID` | IAM access key with EC2 RunInstances/TerminateInstances/DescribeInstances permissions |
| `AWS_KEY_SECRET` | Corresponding secret access key |
| `GH_TOKEN` | GitHub PAT with `repo` scope (used by machulav/ec2-github-runner to register the runner) |
### Variables (Settings → Variables → Actions)
| Name | Example | Description |
|------|---------|-------------|
| `AWS_REGION` | `us-east-1` | Region where the AMI lives |
| `AWS_IMAGE_ID` | `ami-0abc123def456` | The AMI created above |
| `AWS_INSTANCE_TYPE` | `g4dn.xlarge` | 1x T4 GPU (~$0.53/hr); `p3.2xlarge` for V100 |
| `AWS_SUBNET` | `subnet-0abc123` | Must have internet access for runner registration |
| `AWS_SECURITY_GROUP` | `sg-0abc123` | Allow outbound HTTPS (port 443) |
## IAM policy (minimum permissions)
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:RunInstances",
"ec2:TerminateInstances",
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:CreateTags"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "iam:PassRole",
"Resource": "*"
}
]
}
```
## Updating the base venv
When upgrading PyTorch or other dependencies, SSH into a running instance (or
launch the AMI), update `~/nobrainer-env`, and create a new AMI snapshot:
```bash
ssh -i your-key.pem ec2-user@<ip>
cd ~/nobrainer
uv pip install --upgrade torch monai pyro-ppl pytorch-lightning
# Then create a new AMI and update AWS_IMAGE_ID in GitHub variables
```
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Report a bug (e.g., something not working as described, missing/incorrect documentation).
title: ''
labels: 'bug'
assignees: ''
---
<!--
For the Bug Report,
Include this information:
-------------------------
Command-line output of `nobrainer info`.
What were you trying to do?
What did you expect will happen?
What actually happened?
Can you replicate the behavior? If yes, how?
List the steps you performed that revealed the bug to you.
Include any code samples. Enclose them in triple back-ticks (```)
Like this:
```
<code>
```
-->
================================================
FILE: .github/ISSUE_TEMPLATE/documentation.md
================================================
---
name: Documentation improvement
about: Request improvements to the documentation and tutorials.
title: ''
labels: 'documentation'
assignees: ''
---
<!--
For the Documentation request, please include the following:
------------------------
What would you like changed/added and why?
Do you have any suggestions for the new documents?
-->
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Propose a new feature or a change to an existing feature.
title: ''
labels: 'feature'
assignees: ''
---
<!--
For the Feature Request,
Include the following:
------------------------
What would you like changed/added and why?
What would be the benefit?
Does the change make something easier to use?
-->
================================================
FILE: .github/ISSUE_TEMPLATE/maintenance.md
================================================
---
name: Maintenance and delivery
about: Suggestions and requests regarding the infrastructure for development, testing, and delivery.
title: ''
labels: 'maintenance'
assignees: ''
---
================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: Not sure if you are using Nobrainer correctly, or other questions? This is the place.
title: ''
labels: 'question'
assignees: ''
---
<!--
For the Question,
Include the following:
------------------------
What are you trying to accomplish?
What have you tried?
-->
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
## Summary
<!--- What does your code do? -->
## Checklist
<!--- Please, let us know if you need help-->
- [ ] I have added tests to cover my changes
- [ ] I have updated documentation (if necessary)
## Acknowledgment
- [ ] I acknowledge that this contribution will be available under the Apache 2 license.
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [main, master]
pull_request:
branches: [main, master, alpha]
jobs:
unit-tests:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
python-version: ["3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Cache sample brain data
uses: actions/cache@v4
with:
path: /tmp/nobrainer-data
key: nobrainer-sample-data-v1
- name: Set up Python ${{ matrix.python-version }}
run: uv venv --python ${{ matrix.python-version }}
- name: Install dependencies
run: |
uv pip install \
".[bayesian,generative,zarr,dev]" \
monai \
pyro-ppl
- name: Test with pytest (CPU, skip GPU)
run: |
uv run pytest nobrainer/tests/unit/ -v \
-m "not gpu" \
--no-header
- name: Run sr-tests (somewhat realistic tests)
run: |
uv run pytest nobrainer/sr-tests/ -v \
-m "not gpu" \
--no-header \
--tb=short
- name: Research loop smoke test (5 min budget, no API key)
run: |
mkdir -p /tmp/research-smoke
cp nobrainer/research/templates/train_bayesian_vnet.py /tmp/research-smoke/train.py
cp nobrainer/research/templates/prepare.py /tmp/research-smoke/prepare.py
uv run nobrainer research run \
--working-dir /tmp/research-smoke \
--model-family meshnet \
--max-experiments 2 \
--budget-minutes 5 || true
test -f /tmp/research-smoke/run_summary.md && echo "run_summary.md exists" || echo "WARN: no run_summary.md"
image-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Test CPU Docker image build
run: |
docker build -t neuronets/nobrainer:ci-cpu -f docker/cpu.Dockerfile .
================================================
FILE: .github/workflows/guide-notebooks-ec2.yml
================================================
name: GPU Tests - EC2
run-name: ${{ github.ref_name }} - GPU Tests - EC2
on:
push:
branches: [main, master]
# PRs require approval label from a repo admin before this workflow runs.
# This prevents untrusted PR code from executing on the self-hosted GPU runner.
pull_request:
branches: [main, master, alpha]
types: [labeled, synchronize]
jobs:
start-runner:
name: Start self-hosted EC2 runner
runs-on: ubuntu-latest
# Only run on PRs if an admin added the 'gpu-test-approved' label
if: >-
github.event_name == 'push' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'gpu-test-approved'))
outputs:
label: ${{ steps.start-ec2-runner.outputs.label }}
ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }}
multi_gpu: ${{ steps.gpu-config.outputs.multi_gpu }}
steps:
- name: Parse GPU config labels
id: gpu-config
if: github.event_name == 'pull_request'
run: |
LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
# Priority: gpu-instance: (exact) > gpu-multi (multi-GPU) > gpu-family: (family default)
INSTANCE=$(echo "$LABELS" | jq -r '[.[] | select(startswith("gpu-instance:"))] | if length > 0 then .[0] | split(":")[1] else "" end')
# gpu-multi label → pick a multi-GPU instance for DDP/model-parallel tests
MULTI_GPU=$(echo "$LABELS" | jq -r '[.[] | select(. == "gpu-multi")] | if length > 0 then "true" else "" end')
if [ -z "$INSTANCE" ] && [ -n "$MULTI_GPU" ]; then
INSTANCE="g5.12xlarge" # 4x A10G GPUs
echo "gpu-multi label → selecting $INSTANCE (4 GPUs)"
fi
# gpu-family:<family> label → pick default instance from that family
# Supported families: g4dn, g5, g6, p3, p4d, p5
if [ -z "$INSTANCE" ]; then
FAMILY=$(echo "$LABELS" | jq -r '[.[] | select(startswith("gpu-family:"))] | if length > 0 then .[0] | split(":")[1] else "" end')
if [ -n "$FAMILY" ]; then
case "$FAMILY" in
g4dn) INSTANCE="g4dn.xlarge" ;; # 1x T4
g5) INSTANCE="g5.xlarge" ;; # 1x A10G
g6) INSTANCE="g6.xlarge" ;; # 1x L4
p3) INSTANCE="p3.2xlarge" ;; # 1x V100
p4d) INSTANCE="p4d.24xlarge" ;; # 8x A100
p5) INSTANCE="p5.48xlarge" ;; # 8x H100
*) echo "Unknown GPU family: $FAMILY"; INSTANCE="" ;;
esac
if [ -n "$INSTANCE" ]; then
echo "gpu-family:${FAMILY} → selecting $INSTANCE"
fi
fi
fi
# Default to spot pricing; gpu-ondemand:true overrides to on-demand
ONDEMAND=$(echo "$LABELS" | jq -r '[.[] | select(. == "gpu-ondemand:true")] | if length > 0 then "true" else "" end')
if [ -n "$ONDEMAND" ]; then
MARKET=""
else
MARKET="spot"
fi
echo "instance=${INSTANCE}" >> $GITHUB_OUTPUT
echo "market_type=${MARKET}" >> $GITHUB_OUTPUT
echo "multi_gpu=${MULTI_GPU}" >> $GITHUB_OUTPUT
echo "Parsed labels: instance=${INSTANCE:-default}, market=${MARKET:-spot}, multi_gpu=${MULTI_GPU:-false}"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_KEY_SECRET }}
aws-region: ${{ vars.AWS_REGION }}
- name: Start EC2 runner
id: start-ec2-runner
uses: machulav/ec2-github-runner@v2.5.2
with:
mode: start
github-token: ${{ secrets.GH_TOKEN }}
ec2-image-id: ${{ vars.AWS_IMAGE_ID }}
ec2-instance-type: ${{ steps.gpu-config.outputs.instance || vars.AWS_INSTANCE_TYPE }}
subnet-id: ${{ vars.AWS_SUBNET }}
security-group-id: ${{ vars.AWS_SECURITY_GROUP }}
market-type: ${{ steps.gpu-config.outputs.market_type || 'spot' }}
gpu-tests:
needs: start-runner
runs-on: ${{ needs.start-runner.outputs.label }}
env:
# The GitHub Actions runner runs as root on the EC2 instance, but
# the AMI was set up as ec2-user. Use absolute paths to ec2-user's
# home directory for the pre-installed venv and uv binary.
EC2_USER_HOME: /home/ec2-user
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Log GPU runner config
run: |
echo "Instance type: $(curl -s http://169.254.169.254/latest/meta-data/instance-type 2>/dev/null || echo 'unknown')"
echo "Availability zone: $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone 2>/dev/null || echo 'unknown')"
echo "Market type: $(curl -s http://169.254.169.254/latest/meta-data/instance-life-cycle 2>/dev/null || echo 'unknown')"
- name: Set up venv from pre-installed base
run: |
set -ex
BASE_VENV="${EC2_USER_HOME}/nobrainer-env"
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
if [ -d "$BASE_VENV" ]; then
echo "Found pre-installed base venv at $BASE_VENV"
# Copy the base venv so the AMI stays clean for next run
cp -a "$BASE_VENV" .venv
else
echo "No base venv found — creating from scratch"
uv venv --python 3.14
fi
# Install nobrainer from checkout on top of the base layer
uv pip install \
".[bayesian,generative,zarr,dev]" \
monai \
pyro-ppl \
matplotlib
- name: Verify GPU access
run: |
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
uv run python -c "
import torch
assert torch.cuda.is_available(), 'CUDA not available'
n = torch.cuda.device_count()
print(f'GPUs: {n}')
for i in range(n):
print(f' [{i}] {torch.cuda.get_device_name(i)}')
print(f'CUDA: {torch.version.cuda}')
print(f'PyTorch: {torch.__version__}')
"
- name: Run full test suite (including GPU)
run: |
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
uv run pytest nobrainer/tests/ nobrainer/sr-tests/ -v \
--no-header \
--tb=short
- name: Run multi-GPU tests (DDP + model parallel)
if: needs.start-runner.outputs.multi_gpu == 'true'
run: |
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
echo "=== Multi-GPU DDP and model-parallel tests ==="
uv run pytest nobrainer/tests/gpu/ -v \
--no-header \
--tb=short \
-k "multi_gpu or ddp or model_parallel"
stop-runner:
name: Stop self-hosted EC2 runner
needs:
- start-runner
- gpu-tests
runs-on: ubuntu-latest
if: ${{ always() && needs.start-runner.result == 'success' }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_KEY_SECRET }}
aws-region: ${{ vars.AWS_REGION }}
- name: Stop EC2 runner
uses: machulav/ec2-github-runner@v2.5.2
with:
mode: stop
github-token: ${{ secrets.GH_TOKEN }}
label: ${{ needs.start-runner.outputs.label }}
ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }}
================================================
FILE: .github/workflows/kwyk-reproduction-ec2.yml
================================================
name: KWYK Reproduction - EC2 GPU
run-name: ${{ github.ref_name }} - KWYK Reproduction
on:
workflow_dispatch:
inputs:
mode:
description: "Run mode"
required: true
default: "smoke-test"
type: choice
options:
- smoke-test
- small-train
- full
instance_type:
description: "EC2 instance type"
required: false
default: ""
type: string
on_demand:
description: "Use on-demand pricing (not spot)"
required: false
default: false
type: boolean
pull_request:
branches: [main, master, alpha]
types: [labeled, synchronize]
jobs:
start-runner:
name: Start self-hosted EC2 runner
runs-on: ubuntu-latest
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'pull_request' &&
contains(github.event.pull_request.labels.*.name, 'kwyk-gpu-test'))
outputs:
label: ${{ steps.start-ec2-runner.outputs.label }}
ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }}
steps:
- name: Determine instance config
id: gpu-config
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
INSTANCE="${{ inputs.instance_type }}"
if [ "${{ inputs.on_demand }}" = "true" ]; then
MARKET=""
else
MARKET="spot"
fi
else
# PR: parse labels
LABELS='${{ toJSON(github.event.pull_request.labels.*.name) }}'
INSTANCE=$(echo "$LABELS" | jq -r '[.[] | select(startswith("gpu-instance:"))] | if length > 0 then .[0] | split(":")[1] else "" end')
ONDEMAND=$(echo "$LABELS" | jq -r '[.[] | select(. == "gpu-ondemand:true")] | if length > 0 then "true" else "" end')
if [ -n "$ONDEMAND" ]; then MARKET=""; else MARKET="spot"; fi
fi
echo "instance=${INSTANCE}" >> $GITHUB_OUTPUT
echo "market_type=${MARKET}" >> $GITHUB_OUTPUT
echo "Config: instance=${INSTANCE:-default}, market=${MARKET:-spot}"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_KEY_SECRET }}
aws-region: ${{ vars.AWS_REGION }}
- name: Start EC2 runner
id: start-ec2-runner
uses: machulav/ec2-github-runner@v2.5.2
with:
mode: start
github-token: ${{ secrets.GH_TOKEN }}
ec2-image-id: ${{ vars.AWS_IMAGE_ID }}
ec2-instance-type: ${{ steps.gpu-config.outputs.instance || vars.AWS_INSTANCE_TYPE }}
subnet-id: ${{ vars.AWS_SUBNET }}
security-group-id: ${{ vars.AWS_SECURITY_GROUP }}
market-type: ${{ steps.gpu-config.outputs.market_type || 'spot' }}
kwyk-reproduction:
needs: start-runner
runs-on: ${{ needs.start-runner.outputs.label }}
timeout-minutes: 120
env:
EC2_USER_HOME: /home/ec2-user
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Log GPU runner config
run: |
echo "Instance type: $(curl -s http://169.254.169.254/latest/meta-data/instance-type 2>/dev/null || echo 'unknown')"
echo "Market type: $(curl -s http://169.254.169.254/latest/meta-data/instance-life-cycle 2>/dev/null || echo 'unknown')"
- name: Install git-annex
run: |
# Runner runs as root but EC2_USER_HOME points to ec2-user.
# Add both possible bin dirs to PATH.
export PATH="/root/.local/bin:${EC2_USER_HOME}/.local/bin:$PATH"
if ! command -v git-annex &>/dev/null; then
echo "Installing git-annex via uv..."
uv tool install git-annex
fi
git-annex version
# Persist PATH for subsequent steps
echo "/root/.local/bin" >> $GITHUB_PATH
- name: Set up venv from pre-installed base
run: |
set -ex
BASE_VENV="${EC2_USER_HOME}/nobrainer-env"
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
if [ -d "$BASE_VENV" ]; then
echo "Found pre-installed base venv at $BASE_VENV"
cp -a "$BASE_VENV" .venv
else
echo "No base venv found — creating from scratch"
uv venv --python 3.14
fi
uv pip install \
".[bayesian,generative,zarr,versioning,dev]" \
monai pyro-ppl datalad matplotlib pyyaml scipy nibabel
- name: Verify GPU access
run: |
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
uv run python -c "
import torch
assert torch.cuda.is_available(), 'CUDA not available'
print(f'GPU: {torch.cuda.get_device_name(0)}')
print(f'CUDA: {torch.version.cuda}')
print(f'PyTorch: {torch.__version__}')
print(f'Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB')
"
- name: Cache sample brain data
uses: actions/cache@v4
with:
path: /tmp/nobrainer-data
key: nobrainer-sample-data-v1
- name: Run kwyk sr-tests smoke test
run: |
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
uv run pytest nobrainer/sr-tests/test_kwyk_smoke.py -v \
--no-header \
--tb=short
- name: Determine run mode
id: mode
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "mode=${{ inputs.mode }}" >> $GITHUB_OUTPUT
else
echo "mode=smoke-test" >> $GITHUB_OUTPUT
fi
- name: Run kwyk reproduction scripts (smoke test)
if: steps.mode.outputs.mode == 'smoke-test'
run: |
set -ex
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
cd scripts/kwyk_reproduction
# Smoke test: skip DataLad (requires git-annex on AMI).
# The sr-tests already validated the pipeline with get_data().
# Here we verify the reproduction scripts parse configs correctly
# and that the training loop works end-to-end with sample data.
# Try DataLad first; fall back to sample brain data if it fails
uv run python 01_assemble_dataset.py \
--datasets ds000114 \
--output-csv manifest.csv \
--output-dir data \
--label-mapping binary \
|| {
echo "DataLad assembly failed, falling back to sample brain data"
uv run python -c "
import csv; from nobrainer.utils import get_data
src = get_data(); pairs = []
with open(src) as f:
r = csv.reader(f); next(r)
pairs = list(r)[:5]
splits = ['train','train','train','val','test']
with open('manifest.csv', 'w', newline='') as f:
w = csv.DictWriter(f, ['t1w_path','label_path','split']); w.writeheader()
for i,(t1,lbl) in enumerate(pairs):
w.writerow(dict(t1w_path=t1, label_path=lbl, split=splits[i]))
print('Manifest created with', len(pairs), 'volumes')
"
}
echo "=== Step 2: Train deterministic MeshNet (2 epochs) ==="
uv run python 02_train_meshnet.py \
--manifest manifest.csv \
--config config.yaml \
--output-dir checkpoints/meshnet \
--epochs 2
echo "=== Step 3a: MC dropout variant ==="
uv run python 03_train_bayesian.py \
--manifest manifest.csv \
--config config.yaml \
--variant bwn_multi \
--warmstart checkpoints/meshnet \
--output-dir checkpoints/bwn_multi \
--epochs 2
echo "=== Step 3b: Spike-and-slab variant (2 epochs) ==="
uv run python 03_train_bayesian.py \
--manifest manifest.csv \
--config config.yaml \
--variant bvwn_multi_prior \
--warmstart checkpoints/meshnet \
--output-dir checkpoints/bvwn_multi_prior \
--epochs 2
echo "=== Checking outputs ==="
ls -la checkpoints/meshnet/ checkpoints/bwn_multi/ checkpoints/bvwn_multi_prior/ 2>/dev/null || true
- name: Run kwyk reproduction scripts (small training)
if: steps.mode.outputs.mode == 'small-train'
run: |
set -ex
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
cd scripts/kwyk_reproduction
# Try DataLad first; fall back to sample brain data if it fails
uv run python 01_assemble_dataset.py \
--datasets ds000114 \
--output-csv manifest.csv \
--output-dir data \
--label-mapping binary \
|| {
echo "DataLad assembly failed, falling back to sample brain data"
uv run python -c "
import csv; from nobrainer.utils import get_data
src = get_data(); pairs = []
with open(src) as f:
r = csv.reader(f); next(r)
pairs = list(r)[:5]
splits = ['train','train','train','val','test']
with open('manifest.csv', 'w', newline='') as f:
w = csv.DictWriter(f, ['t1w_path','label_path','split']); w.writeheader()
for i,(t1,lbl) in enumerate(pairs):
w.writerow(dict(t1w_path=t1, label_path=lbl, split=splits[i]))
print('Manifest created with', len(pairs), 'volumes')
"
}
echo "=== Step 2: Train deterministic MeshNet (20 epochs) ==="
uv run python 02_train_meshnet.py \
--manifest manifest.csv \
--config config.yaml \
--output-dir checkpoints/meshnet \
--epochs 20
echo "=== Step 3a: MC dropout variant ==="
uv run python 03_train_bayesian.py \
--manifest manifest.csv \
--config config.yaml \
--variant bwn_multi \
--warmstart checkpoints/meshnet \
--output-dir checkpoints/bwn_multi \
--epochs 20
echo "=== Step 3b: Spike-and-slab variant (20 epochs) ==="
uv run python 03_train_bayesian.py \
--manifest manifest.csv \
--config config.yaml \
--variant bvwn_multi_prior \
--warmstart checkpoints/meshnet \
--output-dir checkpoints/bvwn_multi_prior \
--epochs 20
echo "=== Step 3c: Gaussian Bayesian variant (20 epochs) ==="
uv run python 03_train_bayesian.py \
--manifest manifest.csv \
--config config.yaml \
--variant bayesian_gaussian \
--warmstart checkpoints/meshnet \
--output-dir checkpoints/bayesian_gaussian \
--epochs 20
echo "=== Checking outputs ==="
ls -la checkpoints/*/ 2>/dev/null || true
- name: Run kwyk reproduction scripts (full)
if: steps.mode.outputs.mode == 'full'
timeout-minutes: 1440
run: |
set -ex
export PATH="${EC2_USER_HOME}/.local/bin:$PATH"
cd scripts/kwyk_reproduction
uv run python 01_assemble_dataset.py \
--datasets ds000114 ds000228 ds002609 ds001021 ds002105 \
--output-csv manifest.csv \
--output-dir data \
--label-mapping binary --conform
uv run python 02_train_meshnet.py \
--manifest manifest.csv \
--config config.yaml \
--output-dir checkpoints/meshnet \
--epochs 50
for variant in bwn_multi bvwn_multi_prior bayesian_gaussian; do
echo "=== Training $variant (50 epochs) ==="
uv run python 03_train_bayesian.py \
--manifest manifest.csv \
--config config.yaml \
--variant $variant \
--warmstart checkpoints/meshnet \
--output-dir checkpoints/$variant \
--epochs 50
done
for variant in meshnet bwn_multi bvwn_multi_prior bayesian_gaussian; do
echo "=== Evaluating $variant ==="
uv run python 04_evaluate.py \
--model checkpoints/$variant/model.pth \
--manifest manifest.csv \
--split test \
--n-samples 10 \
--output-dir results/$variant
done
uv run python 05_compare_kwyk.py \
--new-model checkpoints/bvwn_multi_prior/model.pth \
--kwyk-dir ../../kwyk \
--manifest manifest.csv \
--output-dir results/comparison || echo "WARN: kwyk comparison failed (container may not be available)"
uv run python 06_block_size_sweep.py \
--manifest manifest.csv \
--block-sizes 32 64 128 \
--output-dir results/sweep
- name: Upload artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: kwyk-reproduction-${{ steps.mode.outputs.mode }}
path: |
scripts/kwyk_reproduction/figures/
scripts/kwyk_reproduction/results/
scripts/kwyk_reproduction/checkpoints/*/croissant.json
retention-days: 30
if-no-files-found: warn
stop-runner:
name: Stop self-hosted EC2 runner
needs:
- start-runner
- kwyk-reproduction
runs-on: ubuntu-latest
if: ${{ always() && needs.start-runner.result == 'success' }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_KEY_SECRET }}
aws-region: ${{ vars.AWS_REGION }}
- name: Stop EC2 runner
uses: machulav/ec2-github-runner@v2.5.2
with:
mode: stop
github-token: ${{ secrets.GH_TOKEN }}
label: ${{ needs.start-runner.outputs.label }}
ec2-instance-id: ${{ needs.start-runner.outputs.ec2-instance-id }}
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish to PyPI on GitHub release
on:
release:
types: [published]
jobs:
pypi-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Build and publish
run: |
uv build
uv publish
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
================================================
FILE: .github/workflows/release.yml
================================================
name: Auto-release on PR merge
on:
pull_request:
branches: [master, alpha]
types: [closed]
env:
AUTO_VERSION: v11.2.1
jobs:
auto-release:
name: Create release
runs-on: ubuntu-latest
# Stable release: merged PR to master with 'release' label
# Alpha pre-release: merged PR to alpha (book validation runs as
# a separate check via validate-book.yml on every PR push)
if: >-
github.event.pull_request.merged == true &&
(
contains(github.event.pull_request.labels.*.name, 'release') ||
github.event.pull_request.base.ref == 'alpha'
)
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Unset header
run: git config --local --unset http.https://github.com/.extraheader
- name: Download auto
run: |
auto_download_url="$(curl -fsSL https://api.github.com/repos/intuit/auto/releases/tags/$AUTO_VERSION | jq -r '.assets[] | select(.name == "auto-linux.gz") | .browser_download_url')"
wget -O- "$auto_download_url" | gunzip > ~/auto
chmod a+x ~/auto
- name: Create release
run: ~/auto shipit -vv
env:
GH_TOKEN: ${{ secrets.AUTO_USER_TOKEN }}
================================================
FILE: .github/workflows/validate-book.yml
================================================
name: Validate nobrainer-book tutorials
on:
workflow_dispatch: # Manual trigger only — not part of CI checks
jobs:
validate-book:
name: Run nobrainer-book tutorials
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Set up Python
run: uv venv --python 3.14
- name: Install nobrainer and tutorial deps from PR branch
run: |
uv pip install \
".[bayesian,generative,zarr,dev]" \
monai \
pyro-ppl \
matplotlib \
nilearn
- name: Clone nobrainer-book (matching branch or alpha)
run: |
PR_BRANCH="${{ github.head_ref }}"
BOOK_REPO="https://github.com/neuronets/nobrainer-book.git"
# Try the PR's branch name first (for lockstep development),
# fall back to alpha
if git ls-remote --heads "$BOOK_REPO" "$PR_BRANCH" | grep -q .; then
echo "Using matching book branch: $PR_BRANCH"
git clone --branch "$PR_BRANCH" --depth 1 "$BOOK_REPO" /tmp/nobrainer-book
else
echo "No matching branch '$PR_BRANCH' on nobrainer-book, using alpha"
git clone --branch alpha --depth 1 "$BOOK_REPO" /tmp/nobrainer-book
fi
- name: Run book tutorials
run: |
for script in /tmp/nobrainer-book/docs/nobrainer-guides/scripts/0*.py /tmp/nobrainer-book/docs/nobrainer-guides/scripts/1[01]*.py; do
echo "=== Running $(basename $script) ==="
uv run python "$script" || {
echo "FAILED: $script"
exit 1
}
done
echo "All book tutorials passed"
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# pycharm
.idea/
# guide data
guide/data/
# Model artifacts
*.pth
brain_mask_extraction_model/
data/
# Model artifacts
*.pth
brain_mask_extraction_model/
================================================
FILE: .pre-commit-config.yaml
================================================
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
ci:
skip: [codespell]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-added-large-files
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 24.3.0
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
rev: 7.0.0
hooks:
- id: flake8
- repo: https://github.com/PyCQA/isort
rev: 5.13.2
hooks:
- id: isort
exclude: ^(nobrainer/_version\.py|versioneer\.py)$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
exclude: ^(nobrainer/_version\.py|versioneer\.py|pyproject\.toml|CHANGELOG\.md)$
================================================
FILE: .zenodo.json
================================================
{
"creators": [
{
"affiliation": "Stony Brook University",
"name": "Kaczmarzyk, Jakub",
"orcid": "0000-0002-5544-7577"
},
{
"affiliation": "NIMH",
"name": "McClure, Patrick"
},
{
"affiliation": "MIT",
"name": "Zulfikar, Wazeer"
},
{
"affiliation": "MIT",
"name": "Rana, Aakanksha",
"orcid": "0000-0002-8350-7602"
},
{
"affiliation": "MIT",
"name": "Rajaei, Hoda",
"orcid": "0000-0002-0754-5586"
},
{
"affiliation": "University of Washington",
"name": "Richie-Halford, Adam",
"orcid": "0000-0001-9276-9084"
},
{
"affiliation": "Department of Psychology, Stanford University",
"name": "Bansal, Shashank",
"orcid": "0000-0002-1252-8772"
},
{
"affiliation": "MIT",
"name": "Jarecka, Dorota",
"orcid": "0000-0001-8282-2988"
},
{
"affiliation": "NIMH",
"name": "Lee, John"
},
{
"affiliation": "MIT, HMS",
"name": "Ghosh, Satrajit",
"orcid": "0000-0002-5312-6729"
}
],
"keywords": [
"neuroimaging",
"deep learning",
"bayesian neural network"
],
"license": "Apache-2.0",
"upload_type": "software"
}
================================================
FILE: CHANGELOG.md
================================================
# 1.2.1 (Thu Apr 04 2024)
#### 🐛 Bug Fix
- Fix PGAN notebook [#319](https://github.com/neuronets/nobrainer/pull/319) ([@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- fix dependencies [#318](https://github.com/neuronets/nobrainer/pull/318) ([@satra](https://github.com/satra))
- Update setup.cfg to add cuda option [#309](https://github.com/neuronets/nobrainer/pull/309) ([@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#294](https://github.com/neuronets/nobrainer/pull/294) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra) [@hvgazula](https://github.com/hvgazula))
#### Authors: 3
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- H Gazula ([@hvgazula](https://github.com/hvgazula))
- Satrajit Ghosh ([@satra](https://github.com/satra))
---
# 1.2.0 (Fri Mar 22 2024)
#### 🚀 Enhancement
- Dev [#295](https://github.com/neuronets/nobrainer/pull/295) ([@hvgazula](https://github.com/hvgazula) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Update setup.cfg [#299](https://github.com/neuronets/nobrainer/pull/299) ([@satra](https://github.com/satra))
#### 🐛 Bug Fix
- Update release.yml ([@satra](https://github.com/satra))
- change hyphenation [#275](https://github.com/neuronets/nobrainer/pull/275) ([@satra](https://github.com/satra))
- update precommit checks [#275](https://github.com/neuronets/nobrainer/pull/275) ([@satra](https://github.com/satra))
- fix docker syntax [#275](https://github.com/neuronets/nobrainer/pull/275) ([@satra](https://github.com/satra))
- remove trained models [#275](https://github.com/neuronets/nobrainer/pull/275) ([@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#269](https://github.com/neuronets/nobrainer/pull/269) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
#### Authors: 3
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- H Gazula ([@hvgazula](https://github.com/hvgazula))
- Satrajit Ghosh ([@satra](https://github.com/satra))
---
# 1.1.1 (Sat Oct 07 2023)
#### 🐛 Bug Fix
- Small changes to support long, preemptable training runs [#267](https://github.com/neuronets/nobrainer/pull/267) ([@ohinds](https://github.com/ohinds) [@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#268](https://github.com/neuronets/nobrainer/pull/268) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
#### Authors: 3
- [@ohinds](https://github.com/ohinds)
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Satrajit Ghosh ([@satra](https://github.com/satra))
---
# 1.1.0 (Tue Sep 19 2023)
#### 🚀 Enhancement
- Changes required to support the warmstart guide notebook [#266](https://github.com/neuronets/nobrainer/pull/266) ([@ohinds](https://github.com/ohinds) [@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
#### 🐛 Bug Fix
- Fix some typos using codespell [#262](https://github.com/neuronets/nobrainer/pull/262) ([@yarikoptic](https://github.com/yarikoptic) [@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#263](https://github.com/neuronets/nobrainer/pull/263) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Remove unnecessary keepalive runner [#265](https://github.com/neuronets/nobrainer/pull/265) ([@ohinds](https://github.com/ohinds))
- Dynamically provision self-hosted runner [#264](https://github.com/neuronets/nobrainer/pull/264) ([@ohinds](https://github.com/ohinds))
#### Authors: 4
- [@ohinds](https://github.com/ohinds)
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Satrajit Ghosh ([@satra](https://github.com/satra))
- Yaroslav Halchenko ([@yarikoptic](https://github.com/yarikoptic))
---
# 1.0.0 (Thu Aug 31 2023)
#### 💥 Breaking Change
- `nobrainer` dataset API rework [#261](https://github.com/neuronets/nobrainer/pull/261) ([@ohinds](https://github.com/ohinds) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
#### 🐛 Bug Fix
- Fix Dockerfiles and add GitHub Actions job [#252](https://github.com/neuronets/nobrainer/pull/252) ([@kabilar](https://github.com/kabilar) [@satra](https://github.com/satra))
- Self-hosted runner weekly keepalive [#260](https://github.com/neuronets/nobrainer/pull/260) ([@ohinds](https://github.com/ohinds))
- [pre-commit.ci] pre-commit autoupdate [#253](https://github.com/neuronets/nobrainer/pull/253) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Processing model checkpointing [#256](https://github.com/neuronets/nobrainer/pull/256) ([@ohinds](https://github.com/ohinds) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Verify that the ec2 instance we started is where we are running. [#257](https://github.com/neuronets/nobrainer/pull/257) ([@ohinds](https://github.com/ohinds))
- Training from warm start with multiple GPUs [#251](https://github.com/neuronets/nobrainer/pull/251) ([@ohinds](https://github.com/ohinds) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
#### Authors: 4
- [@ohinds](https://github.com/ohinds)
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Kabilar Gunalan ([@kabilar](https://github.com/kabilar))
- Satrajit Ghosh ([@satra](https://github.com/satra))
---
# 0.5.0 (Wed Jul 19 2023)
#### 🚀 Enhancement
- Remove guide [#243](https://github.com/neuronets/nobrainer/pull/243) ([@ohinds](https://github.com/ohinds) [@satra](https://github.com/satra))
#### 🐛 Bug Fix
- [pre-commit.ci] pre-commit autoupdate [#247](https://github.com/neuronets/nobrainer/pull/247) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Fix #246: Transforms now return labels if passed [#250](https://github.com/neuronets/nobrainer/pull/250) ([@ohinds](https://github.com/ohinds) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- Check out the master branch of the book repo on CI [#249](https://github.com/neuronets/nobrainer/pull/249) ([@ohinds](https://github.com/ohinds))
- Nobrainer book guide examples run on AWS EC2 as a form of regression testing [#248](https://github.com/neuronets/nobrainer/pull/248) ([@ohinds](https://github.com/ohinds))
- [pre-commit.ci] pre-commit autoupdate [#236](https://github.com/neuronets/nobrainer/pull/236) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Multi-GPU support [#242](https://github.com/neuronets/nobrainer/pull/242) ([@ohinds](https://github.com/ohinds) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#234](https://github.com/neuronets/nobrainer/pull/234) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#232](https://github.com/neuronets/nobrainer/pull/232) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#231](https://github.com/neuronets/nobrainer/pull/231) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
#### ⚠️ Pushed to `master`
- Update setup.cfg ([@satra](https://github.com/satra))
- Update ci.yml ([@satra](https://github.com/satra))
- update python and tensorflow versions ([@satra](https://github.com/satra))
- [CI] update python and auto versions ([@satra](https://github.com/satra))
- replace special branch ([@satra](https://github.com/satra))
#### Authors: 3
- [@ohinds](https://github.com/ohinds)
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Satrajit Ghosh ([@satra](https://github.com/satra))
---
# 0.4.0 (Tue Oct 18 2022)
#### 🚀 Enhancement
- update actions [#230](https://github.com/neuronets/nobrainer/pull/230) ([@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#229](https://github.com/neuronets/nobrainer/pull/229) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
#### 🐛 Bug Fix
- Enh/api [#228](https://github.com/neuronets/nobrainer/pull/228) ([@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#227](https://github.com/neuronets/nobrainer/pull/227) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- Create unet_lstm.py [#207](https://github.com/neuronets/nobrainer/pull/207) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra) [@Hoda1394](https://github.com/Hoda1394))
- [pre-commit.ci] pre-commit autoupdate [#226](https://github.com/neuronets/nobrainer/pull/226) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#225](https://github.com/neuronets/nobrainer/pull/225) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#224](https://github.com/neuronets/nobrainer/pull/224) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#223](https://github.com/neuronets/nobrainer/pull/223) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [WIP] Updating and simplifying the end-user API of Nobrainer [#215](https://github.com/neuronets/nobrainer/pull/215) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@Hoda1394](https://github.com/Hoda1394) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#220](https://github.com/neuronets/nobrainer/pull/220) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#218](https://github.com/neuronets/nobrainer/pull/218) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- Hard-Coded Scaling is removed for ProgressiveGANs, GanTrainer and ProgressiveAE Training and Model files [#209](https://github.com/neuronets/nobrainer/pull/209) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- generation api [#216](https://github.com/neuronets/nobrainer/pull/216) ([@Hoda1394](https://github.com/Hoda1394) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [WIP] segmentation api [#213](https://github.com/neuronets/nobrainer/pull/213) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@Hoda1394](https://github.com/Hoda1394) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- file name corrected for bayesian_meshnet.py [#211](https://github.com/neuronets/nobrainer/pull/211) ([@Hoda1394](https://github.com/Hoda1394) [@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#212](https://github.com/neuronets/nobrainer/pull/212) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Adding vox2vox model [#205](https://github.com/neuronets/nobrainer/pull/205) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@Hoda1394](https://github.com/Hoda1394))
- added tensorflow-addons as install dependency [#206](https://github.com/neuronets/nobrainer/pull/206) ([@Hoda1394](https://github.com/Hoda1394))
- Composable Data Augmentations [#189](https://github.com/neuronets/nobrainer/pull/189) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
#### ⚠️ Pushed to `master`
- update dockerfiles ([@satra](https://github.com/satra))
#### Authors: 4
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Aakanksha Rana ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana))
- Hoda Rajaei ([@Hoda1394](https://github.com/Hoda1394))
- Satrajit Ghosh ([@satra](https://github.com/satra))
---
# 0.3.0 (Tue Jan 11 2022)
#### 🚀 Enhancement
- Update README.md [#203](https://github.com/neuronets/nobrainer/pull/203) ([@satra](https://github.com/satra))
#### 🐛 Bug Fix
- Progressive auto encoder [#196](https://github.com/neuronets/nobrainer/pull/196) (alice.bizeul@inf.ethz.ch [@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#197](https://github.com/neuronets/nobrainer/pull/197) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
#### ⚠️ Pushed to `master`
- Update README.md ([@satra](https://github.com/satra))
#### Authors: 3
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Alice Elsa Marie Bizeul (alice.bizeul@inf.ethz.ch)
- Satrajit Ghosh ([@satra](https://github.com/satra))
---
# 0.2.1 (Fri Dec 24 2021)
#### 🐛 Bug Fix
- fix: update docker files and citation pointer [#194](https://github.com/neuronets/nobrainer/pull/194) ([@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
#### Authors: 2
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Satrajit Ghosh ([@satra](https://github.com/satra))
---
# 0.2.0 (Fri Dec 24 2021)
#### 🚀 Enhancement
- Update README.md and move transforms [#187](https://github.com/neuronets/nobrainer/pull/187) ([@Hoda1394](https://github.com/Hoda1394) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
#### 🐛 Bug Fix
- Documentation update [#184](https://github.com/neuronets/nobrainer/pull/184) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Correction of G_dice and multi_class dice_calculation [#164](https://github.com/neuronets/nobrainer/pull/164) ([@kaczmarj](https://github.com/kaczmarj) [@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- DOC: Update link to CITATION file [#190](https://github.com/neuronets/nobrainer/pull/190) ([@arokem](https://github.com/arokem))
- Bayesian: 3D bayes_by_backprop_layer + Distributed Weight Consolidation [#185](https://github.com/neuronets/nobrainer/pull/185) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#188](https://github.com/neuronets/nobrainer/pull/188) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- Fix tf & tfp compatibility with python version [#186](https://github.com/neuronets/nobrainer/pull/186) ([@Hoda1394](https://github.com/Hoda1394) [@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#183](https://github.com/neuronets/nobrainer/pull/183) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- Added Clipping constraint and KLD loss with CD [#180](https://github.com/neuronets/nobrainer/pull/180) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- ENH: Allow multichannel input [#177](https://github.com/neuronets/nobrainer/pull/177) ([@richford](https://github.com/richford) [@satra](https://github.com/satra))
- Add citation [#181](https://github.com/neuronets/nobrainer/pull/181) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#182](https://github.com/neuronets/nobrainer/pull/182) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- adding brain siamese network models and test cases [#172](https://github.com/neuronets/nobrainer/pull/172) ([@dhritimandas](https://github.com/dhritimandas) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Added dcgan architecture with tests [#66](https://github.com/neuronets/nobrainer/pull/66) ([@wazeerzulfikar](https://github.com/wazeerzulfikar) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- BF: Add block_length param to dataset.interleave [#175](https://github.com/neuronets/nobrainer/pull/175) ([@richford](https://github.com/richford) [@satra](https://github.com/satra))
- Weightnorm feature for Variational layer [#166](https://github.com/neuronets/nobrainer/pull/166) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#179](https://github.com/neuronets/nobrainer/pull/179) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#178](https://github.com/neuronets/nobrainer/pull/178) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#173](https://github.com/neuronets/nobrainer/pull/173) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#170](https://github.com/neuronets/nobrainer/pull/170) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#165](https://github.com/neuronets/nobrainer/pull/165) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#163](https://github.com/neuronets/nobrainer/pull/163) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- fix: readme to remove qualified install [#162](https://github.com/neuronets/nobrainer/pull/162) ([@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- fix: remove unnecessary step in release action [#158](https://github.com/neuronets/nobrainer/pull/158) ([@satra](https://github.com/satra))
#### ⚠️ Pushed to `master`
- Update .zenodo.json ([@satra](https://github.com/satra))
#### Authors: 9
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Aakanksha Rana ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana))
- Adam Richie-Halford ([@richford](https://github.com/richford))
- Ariel Rokem ([@arokem](https://github.com/arokem))
- Dhritiman Das ([@dhritimandas](https://github.com/dhritimandas))
- Hoda Rajaei ([@Hoda1394](https://github.com/Hoda1394))
- Jakub Kaczmarzyk ([@kaczmarj](https://github.com/kaczmarj))
- Satrajit Ghosh ([@satra](https://github.com/satra))
- Wazeer Zulfikar ([@wazeerzulfikar](https://github.com/wazeerzulfikar))
---
# 0.1.1 (Tue Jun 22 2021)
#### 🐛 Bug Fix
- fix: replace key retrieval and normalizers [#157](https://github.com/neuronets/nobrainer/pull/157) ([@satra](https://github.com/satra))
- fix: separate auto release and publish to pypi [#156](https://github.com/neuronets/nobrainer/pull/156) ([@satra](https://github.com/satra))
- [pre-commit.ci] pre-commit autoupdate [#154](https://github.com/neuronets/nobrainer/pull/154) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
#### ⚠️ Pushed to `master`
- fix: add twine upload to release ([@satra](https://github.com/satra))
#### Authors: 2
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Satrajit Ghosh ([@satra](https://github.com/satra))
---
# 0.1.0 (Sat Jun 19 2021)
#### 🚀 Enhancement
- fix: change nobrainer models repo, readme, and guide notebooks [#150](https://github.com/neuronets/nobrainer/pull/150) ([@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- Enh/docker [#148](https://github.com/neuronets/nobrainer/pull/148) ([@satra](https://github.com/satra))
- fix: Update release.yml to handle branch protection [#147](https://github.com/neuronets/nobrainer/pull/147) ([@satra](https://github.com/satra))
#### 🐛 Bug Fix
- add zenodo file [#153](https://github.com/neuronets/nobrainer/pull/153) ([@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- enh: change standardize in dataset creation to a callable [#152](https://github.com/neuronets/nobrainer/pull/152) ([@satra](https://github.com/satra))
- ENH: Bayesian neural network architectures with training requisites [#126](https://github.com/neuronets/nobrainer/pull/126) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- fix: standardize not imported for dataset creation [#151](https://github.com/neuronets/nobrainer/pull/151) ([@satra](https://github.com/satra))
- add estimator prediction function for kwyk model [#149](https://github.com/neuronets/nobrainer/pull/149) ([@Hoda1394](https://github.com/Hoda1394) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]) [@satra](https://github.com/satra))
- Fix for the generate cli pytest and generation guide notebook [#146](https://github.com/neuronets/nobrainer/pull/146) ([@wazeerzulfikar](https://github.com/wazeerzulfikar))
- Created using Colaboratory [#144](https://github.com/neuronets/nobrainer/pull/144) ([@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- [pre-commit.ci] pre-commit autoupdate [#142](https://github.com/neuronets/nobrainer/pull/142) ([@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- FIX: keeping compatibility with official tensorflow docker images [#141](https://github.com/neuronets/nobrainer/pull/141) ([@satra](https://github.com/satra))
- Add bayesian prediction [#128](https://github.com/neuronets/nobrainer/pull/128) ([@Hoda1394](https://github.com/Hoda1394) [@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- ENH: Transform composition [#113](https://github.com/neuronets/nobrainer/pull/113) ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana) [@satra](https://github.com/satra) [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot]))
- fixes https://github.com/neuronets/nobrainer/issues/124 [#125](https://github.com/neuronets/nobrainer/pull/125) ([@shashankbansal6](https://github.com/shashankbansal6) [@satra](https://github.com/satra))
- fix: update dockerfiles to tensorflow 2.5.0 [#140](https://github.com/neuronets/nobrainer/pull/140) ([@satra](https://github.com/satra))
- ENH: Add progressive gan to nobrainer [#138](https://github.com/neuronets/nobrainer/pull/138) ([@wazeerzulfikar](https://github.com/wazeerzulfikar) [@satra](https://github.com/satra) [@djarecka](https://github.com/djarecka))
- add: release mechanism [#139](https://github.com/neuronets/nobrainer/pull/139) ([@satra](https://github.com/satra))
- Add progressiveGAN for 3D brain MR images [#114](https://github.com/neuronets/nobrainer/pull/114) ([@wazeerzulfikar](https://github.com/wazeerzulfikar))
- Enh/notebooks [#137](https://github.com/neuronets/nobrainer/pull/137) ([@satra](https://github.com/satra))
- add CI workflow badge [#133](https://github.com/neuronets/nobrainer/pull/133) ([@kaczmarj](https://github.com/kaczmarj))
- move CI to github actions [#131](https://github.com/neuronets/nobrainer/pull/131) ([@Hoda1394](https://github.com/Hoda1394))
- Enh/minor updates [#123](https://github.com/neuronets/nobrainer/pull/123) ([@kaczmarj](https://github.com/kaczmarj))
- export `LC_ALL` and `LANG` + use tensorflow 2.3.1 [#118](https://github.com/neuronets/nobrainer/pull/118) ([@kaczmarj](https://github.com/kaczmarj))
- use specific version of cloudpickle to fix import error [#108](https://github.com/neuronets/nobrainer/pull/108) ([@kaczmarj](https://github.com/kaczmarj))
- force reinstall of python dependencies in travis ci [#102](https://github.com/neuronets/nobrainer/pull/102) ([@kaczmarj](https://github.com/kaczmarj))
#### ⚠️ Pushed to `master`
- fix: use token for auto with repo access ([@satra](https://github.com/satra))
- Created using Colaboratory ([@satra](https://github.com/satra))
- Update release.yml ([@satra](https://github.com/satra))
#### Authors: 8
- [@Hoda1394](https://github.com/Hoda1394)
- [@pre-commit-ci[bot]](https://github.com/pre-commit-ci[bot])
- Aakanksha Rana ([@Aakanksha-Rana](https://github.com/Aakanksha-Rana))
- Dorota Jarecka ([@djarecka](https://github.com/djarecka))
- Jakub Kaczmarzyk ([@kaczmarj](https://github.com/kaczmarj))
- Satrajit Ghosh ([@satra](https://github.com/satra))
- Shashank Bansal ([@shashankbansal6](https://github.com/shashankbansal6))
- Wazeer Zulfikar ([@wazeerzulfikar](https://github.com/wazeerzulfikar))
================================================
FILE: CITATION
================================================
Please follow this DOI (https://doi.org/10.5281/zenodo.4995077) to find
the latest citation on Zenodo. The different citation formats are available
in the Share and Export sections of the page. On a desktop browser these
are on the bottom right of the page.
================================================
FILE: CLAUDE.md
================================================
# Nobrainer Development Guidelines
## Project Overview
Nobrainer is a PyTorch-based deep learning library for 3D brain MRI segmentation. It provides scikit-learn-style estimators (`Segmentation`, `Generation`), Bayesian models (VWN/FFG, Pyro-based), and a comprehensive data pipeline (MONAI transforms, Zarr3 stores, SynthSeg generation).
## Technology Stack
- **Python**: 3.12+; CI matrix 3.12/3.13/3.14
- **Package management**: `uv` throughout (never pip/conda/poetry)
- **ML framework**: PyTorch >= 2.0
- **Medical imaging**: MONAI >= 1.3 (transforms, losses, metrics, model wrappers)
- **Bayesian**: Pyro-ppl >= 1.9 (optional `[bayesian]` extra)
- **Data**: Zarr >= 3.0 (optional `[zarr]` extra), NIfTI via nibabel
- **Testing**: pytest; pre-commit (black, flake8, isort, codespell)
- **CI**: GitHub Actions; EC2 GPU runner for GPU tests
## Commands
```bash
# Install
uv pip install -e ".[all]"
# Test (CPU)
uv run pytest nobrainer/tests/unit/ -m "not gpu" --tb=short
# SR-tests (somewhat realistic, need sample brain data)
uv run pytest nobrainer/sr-tests/ -m "not gpu"
# Lint
uv run pre-commit run --all-files
```
## Code Conventions
- All models: `(B, C, D, H, W)` input → `(B, n_classes, D, H, W)` output
- Factory functions: `model_name(n_classes=1, in_channels=1, **kwargs) -> nn.Module`
- Bayesian models: `supports_mc = True` class attribute; `forward(x, **kwargs)` accepts `mc=True/False`
- Prediction: use `model_supports_mc(model)` to check, never `try/except TypeError`
- Labels: always squeeze channel dim + cast to `long` before `CrossEntropyLoss`
- Device selection: `nobrainer.gpu.get_device()` (CUDA > MPS > CPU)
- Data augmentation: `TrainableCompose` wraps MONAI Compose; `Augmentation()` wrapper auto-skips during predict
## Key Modules
| Module | Purpose |
|--------|---------|
| `models/` | MeshNet, SegFormer3D, UNet, SwinUNETR, SegResNet, Bayesian variants |
| `processing/` | Segmentation/Generation estimators, Dataset builder |
| `augmentation/` | SynthSeg generator, TrainableCompose, profiles |
| `datasets/` | OpenNeuro fetching, Zarr3 store management |
| `training.py` | `fit()` with DDP, AMP, validation, callbacks |
| `prediction.py` | Block-based predict, strided reassembly, MC uncertainty |
| `losses.py` | Dice, FocalLoss, DiceCE, ELBO, class weights |
| `gpu.py` | Device detection, auto batch size, multi-GPU scaling |
| `slurm.py` | SLURM preemption handler, checkpoint/resume |
| `experiment.py` | Local JSONL/CSV + optional W&B tracking |
## Development Workflow (Speckit Constitution)
When working on new features or significant changes, follow these principles:
### I. Specification-First
Every feature MUST begin with a written specification before implementation:
- Prioritized user stories with independently testable acceptance scenarios
- Functional requirements written as verifiable constraints (MUST/SHOULD)
- Measurable success criteria that are technology-agnostic
### II. Incremental Planning
Plans are built in ordered phases — no phase may be skipped:
- **Phase 0 — Research**: Resolve all unknowns before design
- **Phase 1 — Design**: Data model, interface contracts, quickstart documented
- **Phase 2 — Tasks**: Actionable task list organized by user story priority
Implementation MUST NOT begin until tasks exist.
### III. Independent User-Story Delivery
- Each P1 story MUST produce a viable MVP with standalone value
- Stories MUST NOT have hard runtime dependencies on lower-priority stories
- Tasks MUST be labeled with their owning story (`[US1]`, `[US2]`, etc.)
### IV. Constitution Compliance Gate
Every plan MUST include a Constitution Check evaluated before research and after design. Violations MUST be justified with a simpler alternative explicitly rejected.
### V. Simplicity & YAGNI
- Prefer the simplest architecture that satisfies current user stories
- Do not introduce abstractions for hypothetical future requirements
- Complexity MUST be justified against a concrete, present need
### VI. Git Commit Discipline
- Feature work on dedicated branches (`###-feature-name`)
- Planning artifacts committed after each speckit command
- Each completed task results in at least one commit
- Prefer new commits over amending
### VII. Technology Stack Standards
- Python: `uv` for all environment and package management
- Containers: Docker only
- No substitutions without justified amendment
## Quality Gates
| Gate | Condition |
|------|-----------|
| G1 | spec.md has ≥1 user story with acceptance scenarios |
| G2 | All NEEDS CLARIFICATION resolved before design |
| G3 | Constitution Check passes (or violations justified) |
| G4 | tasks.md exists and all tasks reference a user story |
| G5 | P1 story independently verified before P2 work |
| G6 | All planning artifacts committed to feature branch |
## Speckit Commands (if available)
```
/speckit.specify → spec.md
/speckit.clarify → spec.md (revised)
/speckit.plan → plan.md, research.md, data-model.md, quickstart.md
/speckit.tasks → tasks.md
/speckit.implement → code
/speckit.analyze → consistency report
```
If speckit is not installed, follow the principles above manually.
================================================
FILE: LICENSE
================================================
Copyright 2021 The Nobrainer Authors.
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.
================================================
FILE: MANIFEST.in
================================================
# This line includes versioneer.py in sdists, which is necessary for wheels
# built from sdists to have the version set in their metadata.
include versioneer.py
include CHANGELOG.md tox.ini
graft nobrainer
global-exclude *.py[cod]
include nobrainer/_version.py
================================================
FILE: README.md
================================================
# Nobrainer

_Nobrainer_ is a deep learning framework for 3D brain image processing built on
**PyTorch** and **MONAI**. It provides segmentation models (deterministic and
Bayesian), generative models, a MONAI-native data pipeline, block-based
prediction with uncertainty quantification, and CLI tools for inference and
automated hyperparameter search.
Pre-trained models for brain extraction, segmentation, and generation are
available in the [trained-models](https://github.com/neuronets/trained-models)
repository.
The _Nobrainer_ project is supported by NIH RF1MH121885 and is distributed
under the Apache 2.0 license.
## Models
### Segmentation
| Model | Backend | Application |
|-------|---------|-------------|
| [UNet](nobrainer/models/segmentation.py) | MONAI | segmentation |
| [VNet](nobrainer/models/segmentation.py) | MONAI | segmentation |
| [Attention U-Net](nobrainer/models/segmentation.py) | MONAI | segmentation |
| [UNETR](nobrainer/models/segmentation.py) | MONAI | segmentation |
| [MeshNet](nobrainer/models/meshnet.py) | PyTorch | segmentation |
| [HighResNet](nobrainer/models/highresnet.py) | PyTorch | segmentation |
### Bayesian (uncertainty quantification)
| Model | Backend | Application |
|-------|---------|-------------|
| [Bayesian VNet](nobrainer/models/bayesian/bayesian_vnet.py) | Pyro | segmentation + uncertainty |
| [Bayesian MeshNet](nobrainer/models/bayesian/bayesian_meshnet.py) | Pyro | segmentation + uncertainty |
### Generative
| Model | Backend | Application |
|-------|---------|-------------|
| [Progressive GAN](nobrainer/models/generative/progressivegan.py) | PyTorch Lightning | brain generation |
| [DCGAN](nobrainer/models/generative/dcgan.py) | PyTorch Lightning | brain generation |
### Other
| Model | Application |
|-------|-------------|
| [Autoencoder](nobrainer/models/autoencoder.py) | representation learning |
| [SimSiam](nobrainer/models/simsiam.py) | self-supervised learning |
### Custom layers
- `BernoulliDropout`, `ConcreteDropout`, `GaussianDropout` — stochastic regularization
- `BayesianConv3d`, `BayesianLinear` — Pyro-based weight uncertainty layers
- `MaxPool4D` — 4D max pooling via reshape
### Losses and metrics
**Losses**: Dice, Generalized Dice, Jaccard, Tversky, ELBO (Bayesian), Wasserstein, Gradient Penalty
**Metrics**: Dice, Jaccard, Hausdorff distance (all via MONAI)
## Installation
### pip / uv
```bash
uv venv --python 3.14
source .venv/bin/activate
uv pip install nobrainer
```
For Bayesian and generative model support:
```bash
uv pip install "nobrainer[bayesian,generative]" monai pyro-ppl
```
### Docker
GPU image (requires NVIDIA driver on host):
```bash
docker pull neuronets/nobrainer:latest-gpu-pt
docker run --gpus all --rm neuronets/nobrainer:latest-gpu-pt predict --help
```
CPU-only image:
```bash
docker pull neuronets/nobrainer:latest-cpu-pt
docker run --rm neuronets/nobrainer:latest-cpu-pt predict --help
```
## Quick start
### Tutorials
See the [Nobrainer Book](https://neuronets.dev/nobrainer-book/) for 11
progressive tutorials — from installation to contributing.
### sr-tests (somewhat realistic tests)
`nobrainer/sr-tests/` contains pytest integration tests that exercise the
real API with real brain data. They run in CI on every push:
```bash
pytest nobrainer/sr-tests/ -v -m "not gpu" --tb=short
```
### Simple API (3 lines)
```python
from nobrainer.processing import Segmentation, Dataset
ds = Dataset.from_files(filepaths, block_shape=(128, 128, 128), n_classes=2).batch(2)
result = Segmentation("unet").fit(ds, epochs=5).predict("brain.nii.gz")
```
Models are saved with [Croissant-ML](https://mlcommons.org/croissant/) metadata
for reproducibility:
```python
seg.save("my_model") # Creates model.pth + croissant.json
seg = Segmentation.load("my_model")
```
### Brain segmentation (CLI)
```bash
nobrainer predict \
--model unet_brainmask.pth \
--model-type unet \
--n-classes 2 \
input_T1w.nii.gz output_mask.nii.gz
```
### Brain segmentation (Python)
```python
import torch
import nobrainer
from nobrainer.prediction import predict
model = nobrainer.models.unet(n_classes=2)
model.load_state_dict(torch.load("unet_brainmask.pth"))
model.eval()
result = predict(
inputs="input_T1w.nii.gz",
model=model,
block_shape=(128, 128, 128),
device="cuda",
)
result.to_filename("output_mask.nii.gz")
```
### Bayesian inference with uncertainty maps
```python
from nobrainer.prediction import predict_with_uncertainty
model = nobrainer.models.bayesian_vnet(n_classes=2)
model.load_state_dict(torch.load("bayesian_vnet.pth"))
label, variance, entropy = predict_with_uncertainty(
inputs="input_T1w.nii.gz",
model=model,
n_samples=10,
block_shape=(128, 128, 128),
device="cuda",
)
label.to_filename("label.nii.gz")
variance.to_filename("variance.nii.gz")
entropy.to_filename("entropy.nii.gz")
```
### Brain generation
```bash
nobrainer generate \
--model progressivegan.ckpt \
--model-type progressivegan \
output_synthetic.nii.gz
```
### Zarr v3 data pipeline
```python
from nobrainer.io import nifti_to_zarr, zarr_to_nifti
# Convert NIfTI to sharded Zarr v3 with multi-resolution pyramid
nifti_to_zarr("brain_T1w.nii.gz", "brain.zarr", chunk_shape=(64, 64, 64), levels=3)
# Load Zarr stores directly in the training pipeline
from nobrainer.dataset import get_dataset
loader = get_dataset(
data=[{"image": "brain.zarr", "label": "label.zarr"}],
batch_size=2,
)
# Round-trip back to NIfTI
zarr_to_nifti("brain.zarr", "brain_roundtrip.nii.gz")
```
### Training a model
```python
import torch
from nobrainer.dataset import get_dataset
from nobrainer.losses import dice
data_files = [
{"image": f"sub-{i:03d}_T1w.nii.gz", "label": f"sub-{i:03d}_label.nii.gz"}
for i in range(1, 101)
]
loader = get_dataset(data=data_files, batch_size=2, augment=True, cache=True)
model = nobrainer.models.unet(n_classes=2).cuda()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
criterion = dice()
for epoch in range(50):
model.train()
for batch in loader:
images, labels = batch["image"].cuda(), batch["label"].cuda()
optimizer.zero_grad()
loss = criterion(model(images), labels)
loss.backward()
optimizer.step()
torch.save(model.state_dict(), "unet_trained.pth")
```
## Automated research (autoresearch)
Nobrainer includes an automated hyperparameter search loop that uses an LLM
to propose training modifications overnight:
```bash
nobrainer research run \
--working-dir ./research/bayesian_vnet \
--model-family bayesian_vnet \
--max-experiments 15 \
--budget-hours 8
```
Improved models are versioned via DataLad:
```bash
nobrainer research commit \
--run-dir ./research/bayesian_vnet \
--trained-models-path ~/trained-models \
--model-family bayesian_vnet
```
## GPU test dispatch (nobrainer-runner)
[nobrainer-runner](https://github.com/neuronets/nobrainer-runner) submits GPU
test suites to Slurm clusters or cloud instances (AWS Batch, GCP Batch):
```bash
nobrainer-runner submit --profile mycluster --gpus 1 "pytest tests/ -m gpu"
nobrainer-runner status $JOB_ID
nobrainer-runner results --format json $JOB_ID
```
## Package layout
- `nobrainer.models` — segmentation, Bayesian, and generative `torch.nn.Module` models
- `nobrainer.losses` — Dice, Jaccard, Tversky, ELBO, Wasserstein (MONAI-backed)
- `nobrainer.metrics` — Dice, Jaccard, Hausdorff (MONAI-backed)
- `nobrainer.dataset` — MONAI `CacheDataset` + `DataLoader` pipeline
- `nobrainer.prediction` — block-based `predict()` and `predict_with_uncertainty()`
- `nobrainer.io` — `convert_tfrecords()`, `convert_weights()` (TF → PyTorch migration)
- `nobrainer.layers` — dropout layers, Bayesian layers, MaxPool4D
- `nobrainer.research` — autoresearch loop and DataLad model versioning
- `nobrainer.cli` — Click CLI (`predict`, `generate`, `research`, `commit`, `info`)
## Development and releases
Nobrainer uses a two-branch release workflow:
| Branch | Purpose | PyPI version |
|--------|---------|--------------|
| `master` | Stable releases | `uv pip install nobrainer` |
| `alpha` | Pre-releases for testing | `uv pip install --pre nobrainer` |
**Alpha workflow**: Feature branches merge to `alpha`. Each merge triggers
book tutorial validation (using a matching branch on
[nobrainer-book](https://github.com/neuronets/nobrainer-book) if available,
otherwise the book's `alpha` branch) followed by an automatic pre-release
tag (e.g., `0.5.0-alpha.0`).
**Stable workflow**: When `alpha` is merged to `master` with the `release`
label, a stable version is tagged and published to PyPI.
**GPU CI**: PRs to `master` can request GPU testing on EC2 by adding the
`gpu-test-approved` label. Instance type and spot pricing are configurable
via `gpu-instance:<type>` and `gpu-spot:true` labels.
## Citation
If you use this package, please [cite](https://github.com/neuronets/nobrainer/blob/master/CITATION) it.
## Questions or issues
Please [submit a GitHub issue](https://github.com/neuronets/helpdesk/issues/new/choose).
================================================
FILE: conftest.py
================================================
"""Root conftest.py — auto-skip GPU tests when CUDA is unavailable."""
from __future__ import annotations
import pytest
import torch
def pytest_collection_modifyitems(config, items):
"""Skip tests marked with @pytest.mark.gpu when CUDA is not available."""
if torch.cuda.is_available():
return
skip_gpu = pytest.mark.skip(reason="CUDA not available — skipping GPU test")
for item in items:
if item.get_closest_marker("gpu"):
item.add_marker(skip_gpu)
================================================
FILE: docker/README.md
================================================
# Nobrainer in a container
The Dockerfiles in this directory can be used to create Docker images to use _Nobrainer_ on CPU or GPU.
## Build images
```bash
cd /code/nobrainer # Top-level nobrainer directory
docker build -t neuronets/nobrainer:master-cpu -f docker/cpu.Dockerfile .
docker build -t neuronets/nobrainer:master-gpu -f docker/gpu.Dockerfile .
```
# Convert Docker images to Singularity containers
Using Singularity version 3.x, Docker images can be converted to Singularity containers using the `singularity` command-line tool.
## Pulling from DockerHub
In most cases (e.g., working on a HPC cluster), the _Nobrainer_ singularity container can be created with:
```bash
singularity pull docker://neuronets/nobrainer:master-gpu
```
## Building from local Docker cache
If you built a _Nobrainer_ Docker images locally and would like to convert it to a Singularity container, you can do so with:
```bash
sudo singularity pull docker-daemon://neuronets/nobrainer:master-gpu
```
Please note the use of `sudo` here. This is necessary for interacting with the Docker daemon.
================================================
FILE: docker/cpu.Dockerfile
================================================
FROM python:3.14-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
&& rm -rf /var/lib/apt/lists/*
COPY [".", "/opt/nobrainer"]
RUN pip install --no-cache-dir uv \
&& uv pip install --system \
"torch" \
"/opt/nobrainer[bayesian,generative]" \
monai \
pyro-ppl \
--index-url https://download.pytorch.org/whl/cpu \
--extra-index-url https://pypi.org/simple \
&& rm -rf /root/.cache/uv
ENV LC_ALL=C.UTF-8 \
LANG=C.UTF-8
WORKDIR "/work"
LABEL maintainer="Satrajit Ghosh <satrajit.ghosh@gmail.com>"
LABEL org.opencontainers.image.title="nobrainer-cpu-pytorch"
LABEL org.opencontainers.image.description="nobrainer with PyTorch CPU-only support"
ENTRYPOINT ["nobrainer"]
================================================
FILE: docker/gpu.Dockerfile
================================================
FROM python:3.14-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
&& rm -rf /var/lib/apt/lists/*
COPY [".", "/opt/nobrainer"]
RUN pip install --no-cache-dir uv \
&& uv pip install --system \
torch \
"/opt/nobrainer[bayesian,generative,versioning]" \
monai \
pyro-ppl \
&& rm -rf /root/.cache/uv
ENV LC_ALL=C.UTF-8 \
LANG=C.UTF-8
WORKDIR "/work"
LABEL maintainer="Satrajit Ghosh <satrajit.ghosh@gmail.com>"
LABEL org.opencontainers.image.title="nobrainer-gpu-pytorch"
LABEL org.opencontainers.image.description="nobrainer with PyTorch GPU support (CUDA via host driver)"
ENTRYPOINT ["nobrainer"]
================================================
FILE: nobrainer/__init__.py
================================================
try:
from ._version import __version__ # noqa: F401
except (ImportError, ModuleNotFoundError):
try:
from . import _version # noqa: F401
__version__ = _version.get_versions()["version"]
except (ImportError, AttributeError):
__version__ = "0.0.0.dev0"
# Lazy imports: submodules are available via nobrainer.io, nobrainer.models, etc.
# but are not eagerly loaded to avoid requiring optional dependencies (monai,
# pyro-ppl, pytorch-lightning) at import time.
================================================
FILE: nobrainer/_version.py
================================================
# This file helps to compute a version number in source trees obtained from
# git-archive tarball (such as those provided by githubs download-from-tag
# feature). Distribution tarballs (built by setup.py sdist) and build
# directories (produced by setup.py build) will contain a much shorter file
# that just contains the computed version number.
# This file is released into the public domain. Generated by
# versioneer-0.21 (https://github.com/python-versioneer/python-versioneer)
"""Git implementation of _version.py."""
import errno
import os
import re
import subprocess
import sys
from typing import Callable, Dict
def get_keywords():
"""Get the keywords needed to look up the version information."""
# these strings will be replaced by git during git-archive.
# setup.py/versioneer.py will grep for the variable names, so they must
# each be defined on a line of their own. _version.py will just call
# get_keywords().
git_refnames = "$Format:%d$"
git_full = "$Format:%H$"
git_date = "$Format:%ci$"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
class VersioneerConfig:
"""Container for Versioneer configuration parameters."""
def get_config():
"""Create, populate and return the VersioneerConfig() object."""
# these strings are filled in when 'setup.py versioneer' creates
# _version.py
cfg = VersioneerConfig()
cfg.VCS = "git"
cfg.style = "pep440"
cfg.tag_prefix = ""
cfg.parentdir_prefix = ""
cfg.versionfile_source = "nobrainer/_version.py"
cfg.verbose = False
return cfg
class NotThisMethod(Exception):
"""Exception raised if a method is not valid for the current scenario."""
LONG_VERSION_PY: Dict[str, str] = {}
HANDLERS: Dict[str, Dict[str, Callable]] = {}
def register_vcs_handler(vcs, method): # decorator
"""Create decorator to mark a method as the handler of a VCS."""
def decorate(f):
"""Store f in HANDLERS[vcs][method]."""
if vcs not in HANDLERS:
HANDLERS[vcs] = {}
HANDLERS[vcs][method] = f
return f
return decorate
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None):
"""Call the given command(s)."""
assert isinstance(commands, list)
process = None
for command in commands:
try:
dispcmd = str([command] + args)
# remember shell=False, so use git.cmd on windows, not just git
process = subprocess.Popen(
[command] + args,
cwd=cwd,
env=env,
stdout=subprocess.PIPE,
stderr=(subprocess.PIPE if hide_stderr else None),
)
break
except OSError:
e = sys.exc_info()[1]
if e.errno == errno.ENOENT:
continue
if verbose:
print("unable to run %s" % dispcmd)
print(e)
return None, None
else:
if verbose:
print("unable to find command, tried %s" % (commands,))
return None, None
stdout = process.communicate()[0].strip().decode()
if process.returncode != 0:
if verbose:
print("unable to run %s (error)" % dispcmd)
print("stdout was %s" % stdout)
return None, process.returncode
return stdout, process.returncode
def versions_from_parentdir(parentdir_prefix, root, verbose):
"""Try to determine the version from the parent directory name.
Source tarballs conventionally unpack into a directory that includes both
the project name and a version string. We will also support searching up
two directory levels for an appropriately named parent directory
"""
rootdirs = []
for _ in range(3):
dirname = os.path.basename(root)
if dirname.startswith(parentdir_prefix):
return {
"version": dirname[len(parentdir_prefix) :],
"full-revisionid": None,
"dirty": False,
"error": None,
"date": None,
}
rootdirs.append(root)
root = os.path.dirname(root) # up a level
if verbose:
print(
"Tried directories %s but none started with prefix %s"
% (str(rootdirs), parentdir_prefix)
)
raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
@register_vcs_handler("git", "get_keywords")
def git_get_keywords(versionfile_abs):
"""Extract version information from the given file."""
# the code embedded in _version.py can just fetch the value of these
# keywords. When used from setup.py, we don't want to import _version.py,
# so we do it with a regexp instead. This function is not used from
# _version.py.
keywords = {}
try:
with open(versionfile_abs, "r") as fobj:
for line in fobj:
if line.strip().startswith("git_refnames ="):
mo = re.search(r'=\s*"(.*)"', line)
if mo:
keywords["refnames"] = mo.group(1)
if line.strip().startswith("git_full ="):
mo = re.search(r'=\s*"(.*)"', line)
if mo:
keywords["full"] = mo.group(1)
if line.strip().startswith("git_date ="):
mo = re.search(r'=\s*"(.*)"', line)
if mo:
keywords["date"] = mo.group(1)
except OSError:
pass
return keywords
@register_vcs_handler("git", "keywords")
def git_versions_from_keywords(keywords, tag_prefix, verbose):
"""Get version information from git keywords."""
if "refnames" not in keywords:
raise NotThisMethod("Short version file found")
date = keywords.get("date")
if date is not None:
# Use only the last line. Previous lines may contain GPG signature
# information.
date = date.splitlines()[-1]
# git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant
# datestamp. However we prefer "%ci" (which expands to an "ISO-8601
# -like" string, which we must then edit to make compliant), because
# it's been around since git-1.5.3, and it's too difficult to
# discover which version we're using, or to work around using an
# older one.
date = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
refnames = keywords["refnames"].strip()
if refnames.startswith("$Format"):
if verbose:
print("keywords are unexpanded, not using")
raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
refs = {r.strip() for r in refnames.strip("()").split(",")}
# starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
# just "foo-1.0". If we see a "tag: " prefix, prefer those.
TAG = "tag: "
tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)}
if not tags:
# Either we're using git < 1.8.3, or there really are no tags. We use
# a heuristic: assume all version tags have a digit. The old git %d
# expansion behaves like git log --decorate=short and strips out the
# refs/heads/ and refs/tags/ prefixes that would let us distinguish
# between branches and tags. By ignoring refnames without digits, we
# filter out many common branch names like "release" and
# "stabilization", as well as "HEAD" and "master".
tags = {r for r in refs if re.search(r"\d", r)}
if verbose:
print("discarding '%s', no digits" % ",".join(refs - tags))
if verbose:
print("likely tags: %s" % ",".join(sorted(tags)))
for ref in sorted(tags):
# sorting will prefer e.g. "2.0" over "2.0rc1"
if ref.startswith(tag_prefix):
r = ref[len(tag_prefix) :]
# Filter out refs that exactly match prefix or that don't start
# with a number once the prefix is stripped (mostly a concern
# when prefix is '')
if not re.match(r"\d", r):
continue
if verbose:
print("picking %s" % r)
return {
"version": r,
"full-revisionid": keywords["full"].strip(),
"dirty": False,
"error": None,
"date": date,
}
# no suitable tags, so version is "0+unknown", but full hex is still there
if verbose:
print("no suitable tags, using unknown + full revision id")
return {
"version": "0+unknown",
"full-revisionid": keywords["full"].strip(),
"dirty": False,
"error": "no suitable tags",
"date": None,
}
@register_vcs_handler("git", "pieces_from_vcs")
def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
"""Get version from 'git describe' in the root of the source tree.
This only gets called if the git-archive 'subst' keywords were *not*
expanded, and _version.py hasn't already been rewritten with a short
version string, meaning we're inside a checked out source tree.
"""
GITS = ["git"]
TAG_PREFIX_REGEX = "*"
if sys.platform == "win32":
GITS = ["git.cmd", "git.exe"]
TAG_PREFIX_REGEX = r"\*"
_, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True)
if rc != 0:
if verbose:
print("Directory %s not under git control" % root)
raise NotThisMethod("'git rev-parse --git-dir' returned error")
# if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
# if there isn't one, this yields HEX[-dirty] (no NUM)
describe_out, rc = runner(
GITS,
[
"describe",
"--tags",
"--dirty",
"--always",
"--long",
"--match",
"%s%s" % (tag_prefix, TAG_PREFIX_REGEX),
],
cwd=root,
)
# --long was added in git-1.5.5
if describe_out is None:
raise NotThisMethod("'git describe' failed")
describe_out = describe_out.strip()
full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root)
if full_out is None:
raise NotThisMethod("'git rev-parse' failed")
full_out = full_out.strip()
pieces = {}
pieces["long"] = full_out
pieces["short"] = full_out[:7] # maybe improved later
pieces["error"] = None
branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], cwd=root)
# --abbrev-ref was added in git-1.6.3
if rc != 0 or branch_name is None:
raise NotThisMethod("'git rev-parse --abbrev-ref' returned error")
branch_name = branch_name.strip()
if branch_name == "HEAD":
# If we aren't exactly on a branch, pick a branch which represents
# the current commit. If all else fails, we are on a branchless
# commit.
branches, rc = runner(GITS, ["branch", "--contains"], cwd=root)
# --contains was added in git-1.5.4
if rc != 0 or branches is None:
raise NotThisMethod("'git branch --contains' returned error")
branches = branches.split("\n")
# Remove the first line if we're running detached
if "(" in branches[0]:
branches.pop(0)
# Strip off the leading "* " from the list of branches.
branches = [branch[2:] for branch in branches]
if "master" in branches:
branch_name = "master"
elif not branches:
branch_name = None
else:
# Pick the first branch that is returned. Good or bad.
branch_name = branches[0]
pieces["branch"] = branch_name
# parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
# TAG might have hyphens.
git_describe = describe_out
# look for -dirty suffix
dirty = git_describe.endswith("-dirty")
pieces["dirty"] = dirty
if dirty:
git_describe = git_describe[: git_describe.rindex("-dirty")]
# now we have TAG-NUM-gHEX or HEX
if "-" in git_describe:
# TAG-NUM-gHEX
mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe)
if not mo:
# unparsable. Maybe git-describe is misbehaving?
pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out
return pieces
# tag
full_tag = mo.group(1)
if not full_tag.startswith(tag_prefix):
if verbose:
fmt = "tag '%s' doesn't start with prefix '%s'"
print(fmt % (full_tag, tag_prefix))
pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % (
full_tag,
tag_prefix,
)
return pieces
pieces["closest-tag"] = full_tag[len(tag_prefix) :]
# distance: number of commits since tag
pieces["distance"] = int(mo.group(2))
# commit: short hex revision ID
pieces["short"] = mo.group(3)
else:
# HEX: no tags
pieces["closest-tag"] = None
count_out, rc = runner(GITS, ["rev-list", "HEAD", "--count"], cwd=root)
pieces["distance"] = int(count_out) # total number of commits
# commit date: see ISO-8601 comment in git_versions_from_keywords()
date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip()
# Use only the last line. Previous lines may contain GPG signature
# information.
date = date.splitlines()[-1]
pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1)
return pieces
def plus_or_dot(pieces):
"""Return a + if we don't already have one, else return a ."""
if "+" in pieces.get("closest-tag", ""):
return "."
return "+"
def render_pep440(pieces):
"""Build up version string, with post-release "local version identifier".
Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
Exceptions:
1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += plus_or_dot(pieces)
rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
else:
# exception #1
rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
return rendered
def render_pep440_branch(pieces):
"""TAG[[.dev0]+DISTANCE.gHEX[.dirty]] .
The ".dev0" means not master branch. Note that .dev0 sorts backwards
(a feature branch will appear "older" than the master branch).
Exceptions:
1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
if pieces["branch"] != "master":
rendered += ".dev0"
rendered += plus_or_dot(pieces)
rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
else:
# exception #1
rendered = "0"
if pieces["branch"] != "master":
rendered += ".dev0"
rendered += "+untagged.%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
return rendered
def pep440_split_post(ver):
"""Split pep440 version string at the post-release segment.
Returns the release segments before the post-release and the
post-release version number (or -1 if no post-release segment is present).
"""
vc = str.split(ver, ".post")
return vc[0], int(vc[1] or 0) if len(vc) == 2 else None
def render_pep440_pre(pieces):
"""TAG[.postN.devDISTANCE] -- No -dirty.
Exceptions:
1: no tags. 0.post0.devDISTANCE
"""
if pieces["closest-tag"]:
if pieces["distance"]:
# update the post release segment
tag_version, post_version = pep440_split_post(pieces["closest-tag"])
rendered = tag_version
if post_version is not None:
rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"])
else:
rendered += ".post0.dev%d" % (pieces["distance"])
else:
# no commits, use the tag as the version
rendered = pieces["closest-tag"]
else:
# exception #1
rendered = "0.post0.dev%d" % pieces["distance"]
return rendered
def render_pep440_post(pieces):
"""TAG[.postDISTANCE[.dev0]+gHEX] .
The ".dev0" means dirty. Note that .dev0 sorts backwards
(a dirty tree will appear "older" than the corresponding clean one),
but you shouldn't be releasing software with -dirty anyways.
Exceptions:
1: no tags. 0.postDISTANCE[.dev0]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += ".post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
rendered += plus_or_dot(pieces)
rendered += "g%s" % pieces["short"]
else:
# exception #1
rendered = "0.post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
rendered += "+g%s" % pieces["short"]
return rendered
def render_pep440_post_branch(pieces):
"""TAG[.postDISTANCE[.dev0]+gHEX[.dirty]] .
The ".dev0" means not master branch.
Exceptions:
1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += ".post%d" % pieces["distance"]
if pieces["branch"] != "master":
rendered += ".dev0"
rendered += plus_or_dot(pieces)
rendered += "g%s" % pieces["short"]
if pieces["dirty"]:
rendered += ".dirty"
else:
# exception #1
rendered = "0.post%d" % pieces["distance"]
if pieces["branch"] != "master":
rendered += ".dev0"
rendered += "+g%s" % pieces["short"]
if pieces["dirty"]:
rendered += ".dirty"
return rendered
def render_pep440_old(pieces):
"""TAG[.postDISTANCE[.dev0]] .
The ".dev0" means dirty.
Exceptions:
1: no tags. 0.postDISTANCE[.dev0]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += ".post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
else:
# exception #1
rendered = "0.post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
return rendered
def render_git_describe(pieces):
"""TAG[-DISTANCE-gHEX][-dirty].
Like 'git describe --tags --dirty --always'.
Exceptions:
1: no tags. HEX[-dirty] (note: no 'g' prefix)
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"]:
rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
else:
# exception #1
rendered = pieces["short"]
if pieces["dirty"]:
rendered += "-dirty"
return rendered
def render_git_describe_long(pieces):
"""TAG-DISTANCE-gHEX[-dirty].
Like 'git describe --tags --dirty --always -long'.
The distance/hash is unconditional.
Exceptions:
1: no tags. HEX[-dirty] (note: no 'g' prefix)
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
else:
# exception #1
rendered = pieces["short"]
if pieces["dirty"]:
rendered += "-dirty"
return rendered
def render(pieces, style):
"""Render the given version pieces into the requested style."""
if pieces["error"]:
return {
"version": "unknown",
"full-revisionid": pieces.get("long"),
"dirty": None,
"error": pieces["error"],
"date": None,
}
if not style or style == "default":
style = "pep440" # the default
if style == "pep440":
rendered = render_pep440(pieces)
elif style == "pep440-branch":
rendered = render_pep440_branch(pieces)
elif style == "pep440-pre":
rendered = render_pep440_pre(pieces)
elif style == "pep440-post":
rendered = render_pep440_post(pieces)
elif style == "pep440-post-branch":
rendered = render_pep440_post_branch(pieces)
elif style == "pep440-old":
rendered = render_pep440_old(pieces)
elif style == "git-describe":
rendered = render_git_describe(pieces)
elif style == "git-describe-long":
rendered = render_git_describe_long(pieces)
else:
raise ValueError("unknown style '%s'" % style)
return {
"version": rendered,
"full-revisionid": pieces["long"],
"dirty": pieces["dirty"],
"error": None,
"date": pieces.get("date"),
}
def get_versions():
"""Get version information or return default if unable to do so."""
# I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
# __file__, we can work backwards from there to the root. Some
# py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
# case we can only use expanded keywords.
cfg = get_config()
verbose = cfg.verbose
try:
return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose)
except NotThisMethod:
pass
try:
root = os.path.realpath(__file__)
# versionfile_source is the relative path from the top of the source
# tree (where the .git directory might live) to this file. Invert
# this to find the root from __file__.
for _ in cfg.versionfile_source.split("/"):
root = os.path.dirname(root)
except NameError:
return {
"version": "0+unknown",
"full-revisionid": None,
"dirty": None,
"error": "unable to find root of source tree",
"date": None,
}
try:
pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
return render(pieces, cfg.style)
except NotThisMethod:
pass
try:
if cfg.parentdir_prefix:
return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
except NotThisMethod:
pass
return {
"version": "0+unknown",
"full-revisionid": None,
"dirty": None,
"error": "unable to compute version",
"date": None,
}
================================================
FILE: nobrainer/augmentation/__init__.py
================================================
"""Data augmentation: transform tagging, profiles, and SynthSeg generation."""
from .profiles import get_augmentation_profile
from .synthseg import SynthSegGenerator
from .transforms import Augmentation, TrainableCompose
__all__ = [
"Augmentation",
"SynthSegGenerator",
"TrainableCompose",
"get_augmentation_profile",
]
================================================
FILE: nobrainer/augmentation/profiles.py
================================================
"""Predefined augmentation profiles for brain imaging.
Each profile returns a list of MONAI dictionary transforms wrapped with
:class:`~nobrainer.augmentation.transforms.Augmentation` so they are
automatically skipped during inference.
Profiles: ``"none"``, ``"light"``, ``"standard"``, ``"heavy"``.
Usage::
from nobrainer.augmentation.profiles import get_augmentation_profile
transforms = get_augmentation_profile("standard", keys=["image", "label"])
"""
from __future__ import annotations
from .transforms import Augmentation
def get_augmentation_profile(
name: str,
keys: list[str] | None = None,
) -> list:
"""Return a list of augmentation transforms for the given profile.
All returned transforms are wrapped with :class:`Augmentation` so
:class:`TrainableCompose` will skip them during inference.
Parameters
----------
name : str
Profile name: ``"none"``, ``"light"``, ``"standard"``, ``"heavy"``.
keys : list of str or None
MONAI dictionary keys (default ``["image", "label"]``).
Returns
-------
list
List of ``Augmentation``-wrapped MONAI transforms.
"""
from monai.transforms import RandAffined, RandFlipd, RandGaussianNoised
if keys is None:
keys = ["image", "label"]
img_keys = [k for k in keys if k == "image"]
has_label = "label" in keys
modes = ["bilinear", "nearest"] if has_label else ["bilinear"]
if name == "none":
return []
if name == "light":
return [
Augmentation(RandFlipd(keys=keys, prob=0.5, spatial_axis=0)),
Augmentation(RandFlipd(keys=keys, prob=0.5, spatial_axis=1)),
Augmentation(RandFlipd(keys=keys, prob=0.5, spatial_axis=2)),
]
if name == "standard":
return [
Augmentation(
RandAffined(
keys=keys,
prob=0.5,
rotate_range=(0.15, 0.15, 0.15),
scale_range=(0.1, 0.1, 0.1),
mode=modes,
padding_mode="border",
)
),
Augmentation(RandFlipd(keys=keys, prob=0.5, spatial_axis=0)),
Augmentation(RandFlipd(keys=keys, prob=0.5, spatial_axis=1)),
Augmentation(RandFlipd(keys=keys, prob=0.5, spatial_axis=2)),
Augmentation(
RandGaussianNoised(keys=img_keys, prob=0.2, mean=0.0, std=0.1)
),
]
if name == "heavy":
return [
Augmentation(
RandAffined(
keys=keys,
prob=0.8,
rotate_range=(0.3, 0.3, 0.3),
scale_range=(0.2, 0.2, 0.2),
mode=modes,
padding_mode="border",
)
),
Augmentation(RandFlipd(keys=keys, prob=0.5, spatial_axis=0)),
Augmentation(RandFlipd(keys=keys, prob=0.5, spatial_axis=1)),
Augmentation(RandFlipd(keys=keys, prob=0.5, spatial_axis=2)),
Augmentation(
RandGaussianNoised(keys=img_keys, prob=0.5, mean=0.0, std=0.15)
),
]
available = "none, light, standard, heavy"
raise ValueError(f"Unknown augmentation profile '{name}'. Available: {available}")
================================================
FILE: nobrainer/augmentation/synthseg.py
================================================
"""SynthSeg-style synthetic brain data generator.
Enhanced implementation following Billot et al. (2023) with:
- GMM tissue class grouping (labels grouped by tissue type)
- Spatial augmentation (elastic deformation, rotation, scaling, flipping)
- Resolution randomization (downsample + upsample)
- Configurable intensity priors
Reference: Billot et al., "SynthSeg: Segmentation of brain MRI scans
of any contrast and resolution without retraining", Medical Image Analysis, 2023.
"""
from __future__ import annotations
from pathlib import Path
import nibabel as nib
import numpy as np
import torch
import torch.utils.data
class SynthSegGenerator(torch.utils.data.Dataset):
"""SynthSeg-style synthetic brain data generator.
Generates synthetic brain images from label maps with domain
randomization for contrast-agnostic training.
Parameters
----------
label_maps : list of str or Path
Paths to NIfTI label-map files (e.g., FreeSurfer aparc+aseg).
n_samples_per_map : int
Number of synthetic samples per label map.
generation_classes : dict or None
Tissue class grouping: ``{"WM": [2, 41], ...}``.
Labels in the same class share one intensity distribution.
None = use default FreeSurfer tissue classes.
intensity_prior : tuple of float
``(min, max)`` bounds for sampling per-class mean intensities.
std_prior : tuple of float
``(min, max)`` bounds for sampling per-class std.
noise_std : float
Additive Gaussian noise std.
bias_field_std : float
Bias field magnitude (std of polynomial coefficients).
elastic_std : float
Elastic deformation magnitude (0 = disabled).
rotation_range : float
Max rotation in degrees per axis (0 = disabled).
scaling_bounds : float
Max scaling fraction (e.g., 0.2 = ±20%).
flipping : bool
Enable random left-right flipping with label remapping.
randomize_resolution : bool
Simulate variable acquisition resolution.
resolution_range : tuple of float
``(min_mm, max_mm)`` per-axis resolution range.
"""
def __init__(
self,
label_maps: list[str | Path],
n_samples_per_map: int = 10,
generation_classes: dict[str, list[int]] | None = None,
intensity_prior: tuple[float, float] = (0.0, 250.0),
std_prior: tuple[float, float] = (0.0, 35.0),
noise_std: float = 0.1,
bias_field_std: float = 0.7,
elastic_std: float = 4.0,
rotation_range: float = 15.0,
scaling_bounds: float = 0.2,
flipping: bool = True,
randomize_resolution: bool = True,
resolution_range: tuple[float, float] = (1.0, 3.0),
seed: int | None = None,
) -> None:
self.label_maps = [Path(p) for p in label_maps]
self._seed = seed
self.n_samples_per_map = n_samples_per_map
self.intensity_prior = intensity_prior
self.std_prior = std_prior
self.noise_std = noise_std
self.bias_field_std = bias_field_std
self.elastic_std = elastic_std
self.rotation_range = rotation_range
self.scaling_bounds = scaling_bounds
self.flipping = flipping
self.randomize_resolution = randomize_resolution
self.resolution_range = resolution_range
# Load tissue class mapping
if generation_classes is None:
from nobrainer.data.tissue_classes import FREESURFER_TISSUE_CLASSES
self.generation_classes = FREESURFER_TISSUE_CLASSES
else:
self.generation_classes = generation_classes
# Build reverse lookup: label_id → class_name
self._label_to_class: dict[int, str] = {}
for cls_name, label_ids in self.generation_classes.items():
for lid in label_ids:
self._label_to_class[lid] = cls_name
def __len__(self) -> int:
return len(self.label_maps) * self.n_samples_per_map
def _get_rng(self, idx: int) -> np.random.Generator:
"""Get a seeded RNG for reproducibility, or unseeded if no seed."""
if self._seed is not None:
return np.random.default_rng(self._seed + idx)
return np.random.default_rng()
def __getitem__(self, idx: int) -> dict[str, torch.Tensor]:
map_idx = idx // self.n_samples_per_map
label_path = self.label_maps[map_idx]
# Load label map
label_data = np.asarray(nib.load(label_path).dataobj, dtype=np.int32)
# 1. GMM intensity generation (per tissue class)
image = self._generate_intensities(label_data)
# 2. Spatial augmentation (elastic + affine + flip)
if self.elastic_std > 0 or self.rotation_range > 0 or self.flipping:
image, label_data = self._spatial_augmentation(image, label_data)
# 3. Resolution randomization
if self.randomize_resolution:
image = self._randomize_resolution(image)
# 4. Bias field
if self.bias_field_std > 0:
image = self._add_bias_field(image)
# 5. Gaussian noise
if self.noise_std > 0:
image = image + np.random.normal(0, self.noise_std, image.shape).astype(
np.float32
)
# Convert to tensors with channel dim [1, D, H, W]
image_t = torch.from_numpy(image).float().unsqueeze(0)
label_t = torch.from_numpy(label_data).long().unsqueeze(0)
return {"image": image_t, "label": label_t}
# ------------------------------------------------------------------
# GMM intensity generation
# ------------------------------------------------------------------
def _generate_intensities(self, label_data: np.ndarray) -> np.ndarray:
"""Generate image by sampling GMM intensities per tissue class."""
rng = np.random.default_rng()
unique_labels = np.unique(label_data)
# Sample one (mean, std) per tissue class
class_params: dict[str, tuple[float, float]] = {}
for cls_name in self.generation_classes:
mean = rng.uniform(*self.intensity_prior)
std = rng.uniform(*self.std_prior)
class_params[cls_name] = (mean, std)
# Fill each label region from its class distribution
image = np.zeros_like(label_data, dtype=np.float32)
for lab in unique_labels:
mask = label_data == lab
n_vox = int(mask.sum())
if n_vox == 0:
continue
cls_name = self._label_to_class.get(lab)
if cls_name is not None and cls_name in class_params:
mean, std = class_params[cls_name]
else:
# Unknown label: sample fresh random params
mean = rng.uniform(*self.intensity_prior)
std = rng.uniform(*self.std_prior)
image[mask] = rng.normal(mean, max(std, 1e-6), size=n_vox).astype(
np.float32
)
return image
# ------------------------------------------------------------------
# Spatial augmentation
# ------------------------------------------------------------------
def _spatial_augmentation(
self, image: np.ndarray, label: np.ndarray
) -> tuple[np.ndarray, np.ndarray]:
"""Apply elastic deformation, affine transform, and flipping."""
from scipy.ndimage import map_coordinates
D, H, W = image.shape
# Build coordinate grid
coords = np.mgrid[:D, :H, :W].astype(np.float32) # (3, D, H, W)
# Elastic deformation: smooth random displacement field
if self.elastic_std > 0:
# Sample on coarse grid, smooth, then resize
coarse_shape = (max(4, D // 8), max(4, H // 8), max(4, W // 8))
rng = np.random.default_rng()
for axis in range(3):
displacement = rng.normal(0, self.elastic_std, coarse_shape).astype(
np.float32
)
# Smooth
from scipy.ndimage import gaussian_filter, zoom
displacement = gaussian_filter(displacement, sigma=2.0)
# Resize to full volume
zoom_factors = (
D / coarse_shape[0],
H / coarse_shape[1],
W / coarse_shape[2],
)
displacement = zoom(displacement, zoom_factors, order=1)
# Crop/pad to exact shape if needed
displacement = displacement[:D, :H, :W]
coords[axis] += displacement
# Affine: rotation + scaling
if self.rotation_range > 0 or self.scaling_bounds > 0:
center = np.array([D / 2, H / 2, W / 2])
coords_centered = coords.reshape(3, -1) - center[:, None]
# Build rotation matrix (Euler angles)
rng = np.random.default_rng()
angles = rng.uniform(-self.rotation_range, self.rotation_range, size=3)
angles_rad = np.deg2rad(angles)
Rx = _rot_x(angles_rad[0])
Ry = _rot_y(angles_rad[1])
Rz = _rot_z(angles_rad[2])
R = Rz @ Ry @ Rx
# Scaling
if self.scaling_bounds > 0:
scale = rng.uniform(
1 - self.scaling_bounds, 1 + self.scaling_bounds, size=3
)
S = np.diag(scale)
R = R @ S
coords_centered = R @ coords_centered
coords = (coords_centered + center[:, None]).reshape(3, D, H, W)
# Apply spatial transform
image_out = map_coordinates(image, coords, order=3, mode="nearest")
label_out = map_coordinates(
label.astype(np.float32), coords, order=0, mode="nearest"
).astype(np.int32)
# Flipping
if self.flipping and np.random.random() > 0.5:
image_out = np.flip(image_out, axis=2).copy() # flip W axis (L/R)
label_out = np.flip(label_out, axis=2).copy()
label_out = self._remap_lr_labels(label_out)
return image_out.astype(np.float32), label_out
@staticmethod
def _remap_lr_labels(label: np.ndarray) -> np.ndarray:
"""Swap left/right FreeSurfer labels after L/R flip."""
from nobrainer.data.tissue_classes import FREESURFER_LR_PAIRS
result = label.copy()
for left, right in FREESURFER_LR_PAIRS:
left_mask = label == left
right_mask = label == right
result[left_mask] = right
result[right_mask] = left
return result
# ------------------------------------------------------------------
# Resolution randomization
# ------------------------------------------------------------------
def _randomize_resolution(self, image: np.ndarray) -> np.ndarray:
"""Simulate variable MRI acquisition resolution."""
from scipy.ndimage import gaussian_filter, zoom
rng = np.random.default_rng()
target_res = rng.uniform(*self.resolution_range, size=3)
# Downsample with anti-aliasing
sigmas = [max(0, (r - 1) / 2) for r in target_res]
blurred = gaussian_filter(image, sigma=sigmas)
# Downsample then upsample
down_factors = [1.0 / r for r in target_res]
downsampled = zoom(blurred, down_factors, order=1)
up_factors = [image.shape[i] / downsampled.shape[i] for i in range(3)]
upsampled = zoom(downsampled, up_factors, order=1)
# Ensure exact shape match
D, H, W = image.shape
return upsampled[:D, :H, :W].astype(np.float32)
# ------------------------------------------------------------------
# Bias field
# ------------------------------------------------------------------
def _add_bias_field(self, image: np.ndarray) -> np.ndarray:
"""Apply smooth multiplicative bias field."""
D, H, W = image.shape
order = 3
coords_d = np.linspace(-1, 1, D)
coords_h = np.linspace(-1, 1, H)
coords_w = np.linspace(-1, 1, W)
rng = np.random.default_rng()
coeffs = rng.normal(0, self.bias_field_std, (order + 1, order + 1, order + 1))
bias = np.zeros_like(image)
for i in range(order + 1):
for j in range(order + 1):
for k in range(order + 1):
term = coeffs[i, j, k]
term = term * np.power(coords_d, i)[:, None, None]
term = term * np.power(coords_h, j)[None, :, None]
term = term * np.power(coords_w, k)[None, None, :]
bias += term
bias = np.exp(bias)
return (image * bias).astype(np.float32)
# ------------------------------------------------------------------
# Rotation matrix helpers
# ------------------------------------------------------------------
def _rot_x(angle: float) -> np.ndarray:
c, s = np.cos(angle), np.sin(angle)
return np.array([[1, 0, 0], [0, c, -s], [0, s, c]])
def _rot_y(angle: float) -> np.ndarray:
c, s = np.cos(angle), np.sin(angle)
return np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]])
def _rot_z(angle: float) -> np.ndarray:
c, s = np.cos(angle), np.sin(angle)
return np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
================================================
FILE: nobrainer/augmentation/transforms.py
================================================
"""Augmentation tagging for MONAI transform pipelines.
Extends MONAI's ``Compose`` so individual transforms can be tagged as
**augmentation** (train-only) or **preprocessing** (always runs). During
inference/prediction, augmentation-tagged transforms are automatically
skipped.
Usage::
from nobrainer.augmentation.transforms import Augmentation, TrainableCompose
from monai.transforms import RandAffined, RandGaussianNoised, LoadImaged
pipeline = TrainableCompose([
LoadImaged(keys=["image", "label"]), # preprocessing
Augmentation(RandAffined(keys=["image", "label"], ...)), # train-only
Augmentation(RandGaussianNoised(keys=["image"], ...)), # train-only
])
# Training: all transforms run
result = pipeline(data, mode="train")
# Predict: augmentation transforms are skipped
result = pipeline(data, mode="predict")
"""
from __future__ import annotations
from typing import Any
from monai.transforms import Compose
class Augmentation:
"""Wrapper that tags a MONAI transform as train-only (augmentation).
When used inside a :class:`TrainableCompose`, this transform is
automatically skipped when ``mode="predict"``.
Parameters
----------
transform : callable
Any MONAI dictionary transform.
"""
is_augmentation = True
def __init__(self, transform: Any) -> None:
self.transform = transform
def __call__(self, data: Any) -> Any:
return self.transform(data)
def __repr__(self) -> str:
return f"Augmentation({self.transform!r})"
class TrainableCompose(Compose):
"""MONAI Compose that skips augmentation-tagged transforms in predict mode.
Behaves identically to ``monai.transforms.Compose`` in train mode.
In predict mode, any transform wrapped with :class:`Augmentation`
(or having ``is_augmentation = True``) is skipped.
Parameters
----------
transforms : list
List of MONAI transforms, optionally wrapped with :class:`Augmentation`.
mode : str
Default mode: ``"train"`` or ``"predict"``. Can be overridden
per-call via ``__call__(data, mode=...)``.
"""
def __init__(self, transforms: list, mode: str = "train") -> None:
super().__init__(transforms)
self._mode = mode
@property
def mode(self) -> str:
return self._mode
@mode.setter
def mode(self, value: str) -> None:
if value not in ("train", "predict"):
raise ValueError(f"mode must be 'train' or 'predict', got '{value}'")
self._mode = value
def __call__(self, data: Any, mode: str | None = None, **kwargs) -> Any:
"""Apply transforms, skipping augmentation in predict mode.
Extra keyword arguments (e.g., ``end``, ``threading``) are passed
through to MONAI's ``Compose.__call__`` for CacheDataset compat.
"""
active_mode = mode or self._mode
if active_mode == "train":
# All transforms run — pass through MONAI kwargs
return super().__call__(data, **kwargs)
# Predict mode: skip augmentation transforms
result = data
for t in self.transforms:
if getattr(t, "is_augmentation", False):
continue
result = t(result)
return result
================================================
FILE: nobrainer/cli/__init__.py
================================================
================================================
FILE: nobrainer/cli/main.py
================================================
"""Main command-line interface for nobrainer."""
from __future__ import annotations
import datetime
import os
import platform
import sys
import click
import nibabel as nib
import numpy as np
import torch
from .. import __version__
from ..prediction import predict as _predict
from ..training import get_device
_option_kwds = {"show_default": True}
class JSONParamType(click.ParamType):
name = "json"
def convert(self, value, param, ctx):
try:
import json
return json.loads(value)
except Exception:
self.fail(f"{value} is not valid JSON", param, ctx)
@click.group()
@click.version_option(__version__, message="%(prog)s version %(version)s")
def cli():
"""A framework for developing neural network models for 3D image processing."""
return
@cli.command()
@click.argument("infile")
@click.argument("outfile")
@click.option(
"-m",
"--model",
type=click.Path(exists=True),
required=True,
help="Path to PyTorch model file (.pth) or model name.",
**_option_kwds,
)
@click.option(
"--model-type",
default="unet",
help=(
"Model architecture: unet, vnet, attention_unet, unetr, meshnet, "
"highresnet, bayesian_vnet, bayesian_meshnet."
),
**_option_kwds,
)
@click.option(
"--n-classes",
type=int,
default=1,
help="Number of output classes.",
**_option_kwds,
)
@click.option(
"--in-channels",
type=int,
default=1,
help="Number of input channels.",
**_option_kwds,
)
@click.option(
"-b",
"--block-shape",
default=(128, 128, 128),
type=int,
nargs=3,
help="Shape of sub-volumes on which to predict.",
**_option_kwds,
)
@click.option(
"--batch-size",
type=int,
default=4,
help="Number of blocks to process per forward pass.",
**_option_kwds,
)
@click.option(
"--n-samples",
type=int,
default=1,
help="Monte-Carlo samples for Bayesian uncertainty estimation (>1 enables MC-Dropout).",
**_option_kwds,
)
@click.option(
"--device",
default="auto",
help='Compute device: "auto", "cpu", "cuda", "cuda:0", …',
**_option_kwds,
)
@click.option(
"-v", "--verbose", is_flag=True, help="Print progress messages.", **_option_kwds
)
def predict(
*,
infile,
outfile,
model,
model_type,
n_classes,
in_channels,
block_shape,
batch_size,
n_samples,
device,
verbose,
):
"""Predict labels from a NIfTI volume using a trained PyTorch model.
The predictions are saved to OUTFILE.
"""
if os.path.exists(outfile):
raise FileExistsError(f"Output file already exists: {outfile}")
# Resolve device
if device == "auto":
_device = get_device()
else:
_device = torch.device(device)
if verbose:
click.echo(f"Using device: {_device}")
# Load model architecture + weights
from ..models import get as _get_model
try:
factory = _get_model(model_type)
pt_model = factory(n_classes=n_classes, in_channels=in_channels)
state = torch.load(model, map_location=_device, weights_only=True)
pt_model.load_state_dict(state, strict=False)
except Exception as exc:
click.echo(click.style(f"ERROR: could not load model: {exc}", fg="red"))
raise SystemExit(1) from exc
if verbose:
click.echo("Running prediction ...")
if n_samples > 1:
from ..prediction import predict_with_uncertainty
try:
label_img, var_img, entropy_img = predict_with_uncertainty(
infile,
pt_model,
n_samples=n_samples,
block_shape=block_shape,
batch_size=batch_size,
device=_device,
)
nib.save(label_img, outfile)
nib.save(var_img, outfile.replace(".nii", "_var.nii"))
nib.save(entropy_img, outfile.replace(".nii", "_entropy.nii"))
except NotImplementedError:
click.echo(
click.style(
"predict_with_uncertainty not yet implemented; "
"falling back to deterministic predict()",
fg="yellow",
)
)
out_img = _predict(
infile,
pt_model,
block_shape=block_shape,
batch_size=batch_size,
device=_device,
)
nib.save(out_img, outfile)
else:
out_img = _predict(
infile,
pt_model,
block_shape=block_shape,
batch_size=batch_size,
device=_device,
)
nib.save(out_img, outfile)
if verbose:
click.echo(click.style(f"Output saved to {outfile}", fg="green"))
@cli.command()
@click.option(
"-i",
"--input",
"input_paths",
multiple=True,
type=click.Path(exists=True),
required=True,
help="TFRecord file(s) to convert.",
**_option_kwds,
)
@click.option(
"-o",
"--output-dir",
required=True,
type=click.Path(),
help="Output directory for NIfTI or HDF5 files.",
**_option_kwds,
)
@click.option(
"--format",
"output_format",
default="nifti",
type=click.Choice(["nifti", "hdf5"]),
help="Output format.",
**_option_kwds,
)
@click.option(
"-v", "--verbose", is_flag=True, help="Print progress messages.", **_option_kwds
)
def convert_tfrecords(*, input_paths, output_dir, output_format, verbose):
"""Convert TFRecord files to NIfTI or HDF5 (no TensorFlow required)."""
from ..io import convert_tfrecords as _convert
if verbose:
click.echo(f"Converting {len(input_paths)} TFRecord file(s) …")
out_paths = _convert(
tfrecord_paths=list(input_paths),
output_dir=output_dir,
output_format=output_format,
)
if verbose:
for p in out_paths:
click.echo(f" → {p}")
click.echo(click.style(f"Done. {len(out_paths)} files written.", fg="green"))
@cli.command()
@click.argument("output", type=click.Path())
@click.option(
"-i",
"--images",
multiple=True,
type=click.Path(exists=True),
required=True,
help="Image NIfTI files.",
**_option_kwds,
)
@click.option(
"-l",
"--labels",
multiple=True,
type=click.Path(exists=True),
required=True,
help="Label NIfTI files (same order as --images).",
**_option_kwds,
)
@click.option(
"--chunk-shape",
default="32,32,32",
help="Chunk shape (comma-separated).",
**_option_kwds,
)
@click.option("--no-conform", is_flag=True, help="Disable auto-conforming.")
@click.option("-v", "--verbose", is_flag=True, help="Print progress.")
def convert_to_zarr(*, output, images, labels, chunk_shape, no_conform, verbose):
"""Convert NIfTI image+label pairs to a sharded Zarr3 store."""
from ..datasets.zarr_store import create_zarr_store
if len(images) != len(labels):
click.echo(
click.style(
f"Error: {len(images)} images but {len(labels)} labels.", fg="red"
)
)
sys.exit(1)
pairs = list(zip(images, labels))
chunks = tuple(int(x) for x in chunk_shape.split(","))
if verbose:
click.echo(f"Converting {len(pairs)} pairs → {output}")
store_path = create_zarr_store(
pairs,
output,
chunk_shape=chunks,
conform=not no_conform,
)
click.echo(click.style(f"Zarr store created: {store_path}", fg="green"))
@cli.command()
def merge():
"""Merge multiple models trained with variational weights."""
click.echo("Not implemented yet.")
sys.exit(-2)
@cli.command()
@click.argument("outfile")
@click.option(
"-m",
"--model",
type=click.Path(exists=True),
required=True,
help="Path to model checkpoint (.ckpt) or weights (.pth).",
**_option_kwds,
)
@click.option(
"--model-type",
default="progressivegan",
type=click.Choice(["progressivegan", "dcgan"]),
help="Generative model architecture.",
**_option_kwds,
)
@click.option(
"--latent-size",
type=int,
default=512,
help="Latent vector dimension.",
**_option_kwds,
)
@click.option(
"--n-samples",
type=int,
default=1,
help="Number of images to generate.",
**_option_kwds,
)
@click.option(
"--device",
default="auto",
help='Compute device: "auto", "cpu", "cuda", …',
**_option_kwds,
)
@click.option(
"-v", "--verbose", is_flag=True, help="Print progress messages.", **_option_kwds
)
def generate(
*,
outfile,
model,
model_type,
latent_size,
n_samples,
device,
verbose,
):
"""Generate brain volumes from a trained GAN model.
Saves OUTFILE (NIfTI) for each generated sample. When ``--n-samples > 1``
the file stem is suffixed with ``_0``, ``_1``, … before the extension.
"""
import os
if device == "auto":
_device = get_device()
else:
_device = torch.device(device)
if verbose:
click.echo(f"Using device: {_device}")
from ..models import get as _get_model
try:
factory = _get_model(model_type)
pt_model = factory(latent_size=latent_size)
# Support both .ckpt (Lightning) and .pth (state dict)
if model.endswith(".ckpt"):
model_cls = type(pt_model)
pt_model = model_cls.load_from_checkpoint(model, map_location=_device)
else:
state = torch.load(model, map_location=_device, weights_only=True)
pt_model.load_state_dict(state, strict=False)
except Exception as exc:
click.echo(click.style(f"ERROR: could not load model: {exc}", fg="red"))
raise SystemExit(1) from exc
pt_model = pt_model.to(_device)
pt_model.eval()
if verbose:
click.echo(f"Generating {n_samples} sample(s) …")
stem, ext = os.path.splitext(outfile)
if ext == ".gz":
stem, ext2 = os.path.splitext(stem)
ext = ext2 + ext
with torch.no_grad():
for i in range(n_samples):
z = torch.randn(1, latent_size, device=_device)
out = pt_model.generator(z) # (1, 1, D, H, W)
arr = out.squeeze().cpu().numpy()
img = nib.Nifti1Image(arr.astype(np.float32), np.eye(4))
path = f"{stem}_{i}{ext}" if n_samples > 1 else outfile
nib.save(img, path)
if verbose:
click.echo(f" Saved {path}")
if verbose:
click.echo(click.style("Done.", fg="green"))
@cli.command()
@click.option(
"--working-dir",
required=True,
type=click.Path(),
help="Directory with train script and data_manifest.json.",
**_option_kwds,
)
@click.option(
"--model-family",
default="bayesian_vnet",
help="Model family to use for training.",
**_option_kwds,
)
@click.option(
"--max-experiments",
type=int,
default=10,
help="Maximum number of experiments.",
**_option_kwds,
)
@click.option(
"--budget-hours",
type=float,
default=8.0,
help="Wall-clock budget in hours.",
**_option_kwds,
)
@click.option(
"--budget-minutes",
type=float,
default=None,
help="Wall-clock budget in minutes (overrides --budget-hours).",
**_option_kwds,
)
@click.option(
"-v",
"--verbose",
is_flag=True,
help="Print per-experiment progress.",
**_option_kwds,
)
def research(
*,
working_dir,
model_family,
max_experiments,
budget_hours,
budget_minutes,
verbose,
):
"""Run the autoresearch experiment loop.
Proposes hyperparameter configs (via Anthropic API or random grid),
runs training experiments, and keeps improvements.
Writes ``run_summary.md`` in WORKING_DIR on completion.
"""
from ..research.loop import run_loop
if verbose:
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
budget_seconds = None
if budget_minutes is not None:
budget_seconds = budget_minutes * 60
results = run_loop(
working_dir=working_dir,
model_family=model_family,
max_experiments=max_experiments,
budget_hours=budget_hours,
budget_seconds=budget_seconds,
)
# Progress table
click.echo(
f"\n{'run_id':>6} {'val_dice':>10} {'outcome':<12} {'failure_reason'}"
)
click.echo("-" * 55)
for r in results:
dice_str = f"{r.val_dice:.4f}" if r.val_dice is not None else "—"
click.echo(
f"{r.run_id:>6} {dice_str:>10} {r.outcome:<12} {r.failure_reason or '—'}"
)
summary_path = click.format_filename(f"{working_dir}/run_summary.md")
click.echo(click.style(f"\nSummary written to {summary_path}", fg="green"))
@cli.command()
@click.option(
"--model-path",
required=True,
type=click.Path(exists=True),
help="Path to best_model.pth file.",
**_option_kwds,
)
@click.option(
"--config-path",
required=True,
type=click.Path(exists=True),
help="Path to best_config.json file.",
**_option_kwds,
)
@click.option(
"--trained-models-path",
required=True,
type=click.Path(),
help="Root of the DataLad-managed trained_models dataset.",
**_option_kwds,
)
@click.option(
"--model-family",
default="bayesian_vnet",
help="Model family name (used as subdirectory).",
**_option_kwds,
)
@click.option(
"--val-dice",
type=float,
required=True,
help="Validation Dice score of the best model.",
**_option_kwds,
)
@click.option(
"--source-run-id",
default="",
help="Run ID string for traceability.",
**_option_kwds,
)
def commit(
*,
model_path,
config_path,
trained_models_path,
model_family,
val_dice,
source_run_id,
):
"""Version the best model with DataLad and push to OSF.
Copies model weights and config into the trained_models DataLad dataset,
generates a model card, saves with DataLad, and pushes to OSF.
"""
from ..research.loop import commit_best_model
try:
result = commit_best_model(
best_model_path=model_path,
best_config_path=config_path,
trained_models_path=trained_models_path,
model_family=model_family,
val_dice=val_dice,
source_run_id=source_run_id,
)
except ImportError as exc:
click.echo(click.style(f"ERROR: {exc}", fg="red"))
raise SystemExit(1) from exc
click.echo(f"Model versioned at: {result['path']}")
click.echo(f"DataLad commit: {result['datalad_commit']}")
if result.get("osf_url"):
click.echo(click.style(f"OSF URL: {result['osf_url']}", fg="green"))
else:
click.echo(click.style("OSF push skipped (no remote configured)", fg="yellow"))
@cli.command()
def save():
"""Save a model to PyTorch format."""
click.echo("Not implemented yet.")
sys.exit(-2)
@cli.command()
def evaluate():
"""Evaluate a model's predictions against known labels."""
click.echo("Not implemented yet.")
sys.exit(-2)
@cli.command()
def info():
"""Return information about this system."""
uname = platform.uname()
cuda_available = torch.cuda.is_available()
cuda_devices = torch.cuda.device_count() if cuda_available else 0
s = f"""\
Python:
Version: {platform.python_version()}
Implementation: {platform.python_implementation()}
64-bit: {sys.maxsize > 2**32}
Packages:
Nobrainer: {__version__}
Nibabel: {nib.__version__}
Numpy: {np.__version__}
PyTorch: {torch.__version__}
CUDA available: {cuda_available}
CUDA devices: {cuda_devices}
System:
OSType: {uname.system}
Release: {uname.release}
Version: {uname.version}
Architecture: {uname.machine}
Timestamp: {datetime.datetime.utcnow().strftime('%Y/%m/%d %T')}"""
click.echo(s)
# For debugging only.
if __name__ == "__main__":
cli()
================================================
FILE: nobrainer/cli/tests/__init__.py
================================================
================================================
FILE: nobrainer/cli/tests/main_test.py
================================================
"""Tests for `nobrainer.cli.main`."""
import csv
from pathlib import Path
from click.testing import CliRunner
import nibabel as nib
import numpy as np
import pytest
from .. import main as climain
from ...io import read_csv
from ...models.meshnet import meshnet
from ...models.progressivegan import progressivegan
from ...utils import get_data
def test_convert_nonscalar_labels(tmp_path):
runner = CliRunner()
with runner.isolated_filesystem():
csvpath = get_data(tmp_path)
tfrecords_template = Path("data/shard-{shard:03d}.tfrecords")
tfrecords_template.parent.mkdir(exist_ok=True)
args = """\
convert --csv={} --tfrecords-template={} --volume-shape 256 256 256
--examples-per-shard=2 --to-ras --no-verify-volumes
""".format(
csvpath, tfrecords_template
)
result = runner.invoke(climain.cli, args.split())
assert result.exit_code == 0
assert Path("data/shard-000.tfrecords").is_file()
assert Path("data/shard-001.tfrecords").is_file()
assert Path("data/shard-002.tfrecords").is_file()
assert Path("data/shard-003.tfrecords").is_file()
assert Path("data/shard-004.tfrecords").is_file()
assert not Path("data/shard-005.tfrecords").is_file()
def test_convert_scalar_int_labels(tmp_path):
runner = CliRunner()
with runner.isolated_filesystem():
csvpath = get_data(str(tmp_path))
# Make labels scalars.
data = [(x, 0) for (x, _) in read_csv(csvpath)]
csvpath = tmp_path.with_suffix(".new.csv")
with open(csvpath, "w", newline="") as myfile:
wr = csv.writer(myfile, quoting=csv.QUOTE_ALL)
wr.writerows(data)
tfrecords_template = Path("data/shard-{shard:03d}.tfrecords")
tfrecords_template.parent.mkdir(exist_ok=True)
args = """\
convert --csv={} --tfrecords-template={} --volume-shape 256 256 256
--examples-per-shard=2 --to-ras --no-verify-volumes
""".format(
csvpath, tfrecords_template
)
result = runner.invoke(climain.cli, args.split())
assert result.exit_code == 0
assert Path("data/shard-000.tfrecords").is_file()
assert Path("data/shard-001.tfrecords").is_file()
assert Path("data/shard-002.tfrecords").is_file()
assert Path("data/shard-003.tfrecords").is_file()
assert Path("data/shard-004.tfrecords").is_file()
assert not Path("data/shard-005.tfrecords").is_file()
def test_convert_scalar_float_labels(tmp_path):
runner = CliRunner()
with runner.isolated_filesystem():
csvpath = get_data(str(tmp_path))
# Make labels scalars.
data = [(x, 1.0) for (x, _) in read_csv(csvpath)]
csvpath = tmp_path.with_suffix(".new.csv")
with open(csvpath, "w", newline="") as myfile:
wr = csv.writer(myfile, quoting=csv.QUOTE_ALL)
wr.writerows(data)
tfrecords_template = Path("data/shard-{shard:03d}.tfrecords")
tfrecords_template.parent.mkdir(exist_ok=True)
args = """\
convert --csv={} --tfrecords-template={} --volume-shape 256 256 256
--examples-per-shard=2 --to-ras --no-verify-volumes
""".format(
csvpath, tfrecords_template
)
result = runner.invoke(climain.cli, args.split())
assert result.exit_code == 0
assert Path("data/shard-000.tfrecords").is_file()
assert Path("data/shard-001.tfrecords").is_file()
assert Path("data/shard-002.tfrecords").is_file()
assert Path("data/shard-003.tfrecords").is_file()
assert Path("data/shard-004.tfrecords").is_file()
assert not Path("data/shard-005.tfrecords").is_file()
def test_convert_multi_resolution(tmp_path):
runner = CliRunner()
with runner.isolated_filesystem():
csvpath = get_data(str(tmp_path))
# Make labels scalars.
data = [(x, 1.0) for (x, _) in read_csv(csvpath)]
csvpath = tmp_path.with_suffix(".new.csv")
with open(csvpath, "w", newline="") as myfile:
wr = csv.writer(myfile, quoting=csv.QUOTE_ALL)
wr.writerows(data)
tfrecords_template = Path("data/shard-{shard:03d}.tfrecords")
tfrecords_template.parent.mkdir(exist_ok=True)
args = """\
convert --csv={} --tfrecords-template={} --volume-shape 256 256 256 --start-resolution 64
--examples-per-shard=2 --no-verify-volumes --multi-resolution
""".format(
csvpath, tfrecords_template
)
result = runner.invoke(climain.cli, args.split())
assert result.exit_code == 0
resolutions = [64, 128, 256]
for res in resolutions:
assert Path("data/shard-000-res-{:03d}.tfrecords".format(res)).is_file()
assert Path("data/shard-001-res-{:03d}.tfrecords".format(res)).is_file()
assert Path("data/shard-002-res-{:03d}.tfrecords".format(res)).is_file()
assert Path("data/shard-003-res-{:03d}.tfrecords".format(res)).is_file()
assert Path("data/shard-004-res-{:03d}.tfrecords".format(res)).is_file()
assert not Path("data/shard-005-res-{:03d}.tfrecords".format(res)).is_file()
@pytest.mark.xfail
def test_merge():
assert False
def test_predict():
runner = CliRunner()
with runner.isolated_filesystem():
model = meshnet(1, (10, 10, 10, 1))
model_path = "model.h5"
model.save(model_path)
img_path = "features.nii.gz"
nib.Nifti1Image(np.random.randn(20, 20, 20), np.eye(4)).to_filename(img_path)
out_path = "predictions.nii.gz"
args = """\
predict --model={} --block-shape 10 10 10 --resize-features-to 20 20 20
--largest-label --rotate-and-predict {} {}
""".format(
model_path, img_path, out_path
)
result = runner.invoke(climain.cli, args.split())
assert result.exit_code == 0
assert Path("predictions.nii.gz").is_file()
assert nib.load(out_path).shape == (20, 20, 20)
def test_generate():
runner = CliRunner()
with runner.isolated_filesystem():
generator, _ = progressivegan(
latent_size=256, g_fmap_base=1024, d_fmap_base=1024
)
resolutions = [8, 16]
Path("models").mkdir(exist_ok=True)
for res in resolutions:
generator.add_resolution()
generator([np.random.random((1, 256)), 1.0]) # to build the model by a call
model_path = "models/generator_res_{}".format(res)
generator.save(model_path)
assert Path(model_path).is_dir()
out_path = "generated.nii.gz"
args = """\
generate --model {} --multi-resolution --latent-size 256 {}
""".format(
"models", out_path
)
result = runner.invoke(climain.cli, args.split())
assert result.exit_code == 0
for res in resolutions:
assert Path("generated_res_{}.nii.gz".format(res)).is_file()
assert nib.load("generated_res_{}.nii.gz".format(res)).shape == (
res,
res,
res,
)
@pytest.mark.xfail
def test_save():
assert False
@pytest.mark.xfail
def test_evaluate():
assert False
def test_info():
runner = CliRunner()
result = runner.invoke(climain.cli, ["info"])
assert result.exit_code == 0
assert "Python" in result.output
assert "System" in result.output
assert "Timestamp" in result.output
================================================
FILE: nobrainer/dataset.py
================================================
"""PyTorch dataset utilities backed by MONAI."""
from __future__ import annotations
from pathlib import Path
from typing import Any, Callable
from monai.data import CacheDataset, DataLoader
from monai.transforms import (
EnsureChannelFirstd,
LoadImaged,
NormalizeIntensityd,
Orientationd,
Spacingd,
)
import numpy as np
import torch
def get_dataset(
image_paths: list[str | Path],
label_paths: list[str | Path] | None = None,
block_shape: tuple[int, int, int] | None = None,
batch_size: int = 1,
num_workers: int = 0,
augment: bool = False,
binarize_labels: bool | set | Callable = False,
target_spacing: tuple[float, float, float] = (1.0, 1.0, 1.0),
cache_rate: float = 1.0,
**kwargs: Any,
) -> DataLoader:
"""Build a MONAI-backed :class:`torch.utils.data.DataLoader`.
Applies the following transform chain:
``LoadImaged → EnsureChannelFirstd → Orientationd("RAS")
→ Spacingd(*target_spacing) → NormalizeIntensityd``
→ (if augment) ``RandAffined, RandFlipd, RandGaussianNoised``
Parameters
----------
image_paths : list
Paths to input NIfTI volumes.
label_paths : list or None
Paths to corresponding label NIfTI volumes. ``None`` for
inference-only datasets.
block_shape : tuple or None
If provided, spatial patch size ``(D, H, W)`` extracted by MONAI's
``RandSpatialCropd``. ``None`` loads full volumes.
batch_size : int
Number of samples per mini-batch.
num_workers : int
Number of DataLoader worker processes.
augment : bool
Whether to apply random spatial and intensity augmentations.
target_spacing : tuple of float
Voxel spacing (mm) to resample volumes to.
cache_rate : float
Fraction of dataset to cache in memory (1.0 = all).
**kwargs
Additional keyword arguments forwarded to :class:`DataLoader`.
Returns
-------
DataLoader
PyTorch DataLoader that yields batches of ``{"image": tensor}``
(or ``{"image": tensor, "label": tensor}`` when labels are given).
"""
if label_paths is not None and len(image_paths) != len(label_paths):
raise ValueError(
f"len(image_paths)={len(image_paths)} != len(label_paths)={len(label_paths)}"
)
has_labels = label_paths is not None
# Build data dicts
if has_labels:
data = [
{"image": str(img), "label": str(lbl)}
for img, lbl in zip(image_paths, label_paths)
]
keys = ["image", "label"]
else:
data = [{"image": str(img)} for img in image_paths]
keys = ["image"]
# Core transforms — use NibabelReader to support .mgz and other formats
transforms: list[Any] = [
LoadImaged(keys=keys, image_only=False, reader="NibabelReader"),
EnsureChannelFirstd(keys=keys),
Orientationd(keys=keys, axcodes="RAS"),
Spacingd(
keys=keys,
pixdim=target_spacing,
mode=["bilinear", "nearest"] if has_labels else ["bilinear"],
),
NormalizeIntensityd(keys=["image"], nonzero=True, channel_wise=True),
]
# Optional label binarization (e.g., FreeSurfer parcellation → brain mask)
if binarize_labels and has_labels:
from monai.transforms import Lambdad
if callable(binarize_labels) and binarize_labels is not True:
transforms.append(Lambdad(keys=["label"], func=binarize_labels))
elif isinstance(binarize_labels, set):
label_set = binarize_labels
def _remap(x):
import torch
mask = torch.zeros_like(x)
for val in label_set:
mask = mask | (x == val)
return mask.float()
transforms.append(Lambdad(keys=["label"], func=_remap))
else:
transforms.append(Lambdad(keys=["label"], func=lambda x: (x > 0).float()))
# Optional augmentation — supports bool or profile name
if augment:
from nobrainer.augmentation.profiles import get_augmentation_profile
profile_name = augment if isinstance(augment, str) else "standard"
aug_transforms = get_augmentation_profile(profile_name, keys=keys)
transforms += aug_transforms
if block_shape is not None:
from monai.transforms import RandSpatialCropd
transforms.append(
RandSpatialCropd(keys=keys, roi_size=block_shape, random_size=False)
)
# Use TrainableCompose so augmentation can be skipped during predict
from nobrainer.augmentation.transforms import TrainableCompose
compose = TrainableCompose(transforms)
dataset = CacheDataset(
data=data,
transform=compose,
cache_rate=cache_rate,
num_workers=max(0, num_workers),
)
return DataLoader(
dataset,
batch_size=batch_size,
shuffle=True,
num_workers=num_workers,
pin_memory=torch.cuda.is_available(),
**kwargs,
)
# ---------------------------------------------------------------------------
# Zarr v3 dataset (requires [zarr] extras)
# ---------------------------------------------------------------------------
class ZarrDataset(torch.utils.data.Dataset):
"""PyTorch Dataset backed by Zarr v3 stores.
Each item in *data_list* is a dict with ``"image"`` (and optionally
``"label"``) keys pointing to ``.zarr`` store paths.
"""
def __init__(
self,
data_list: list[dict[str, str]],
transform: Any | None = None,
zarr_level: int = 0,
):
self.data = data_list
self.transform = transform
self.level = zarr_level
def __len__(self) -> int:
return len(self.data)
def __getitem__(self, idx: int) -> dict:
import zarr
item = self.data[idx]
store = zarr.open_group(str(item["image"]), mode="r")
img_arr = np.asarray(store[str(self.level)]).astype(np.float32)
result: dict[str, Any] = {"image": img_arr[None]} # add channel dim
if "label" in item:
lbl_store = zarr.open_group(str(item["label"]), mode="r")
lbl_arr = np.asarray(lbl_store[str(self.level)]).astype(np.float32)
result["label"] = lbl_arr[None]
if self.transform is not None:
result = self.transform(result)
# Convert to tensors if still numpy
for k, v in result.items():
if isinstance(v, np.ndarray):
result[k] = torch.from_numpy(v)
return result
def _is_zarr_path(path: str | Path) -> bool:
"""Check if a path looks like a Zarr store."""
return str(path).rstrip("/").endswith(".zarr")
def _get_zarr_dataset(
data: list[dict[str, str]],
batch_size: int,
num_workers: int,
augment: bool,
zarr_level: int,
**kwargs: Any,
) -> DataLoader:
"""Build a DataLoader from Zarr v3 stores."""
transform = None
if augment:
import monai.transforms as mt
transform = mt.Compose(
[
mt.RandAffined(
keys=list(data[0].keys()),
prob=0.5,
rotate_range=(0.1, 0.1, 0.1),
),
mt.RandFlipd(keys=list(data[0].keys()), prob=0.5),
]
)
dataset = ZarrDataset(data, transform=transform, zarr_level=zarr_level)
return DataLoader(
dataset,
batch_size=batch_size,
shuffle=True,
num_workers=num_workers,
pin_memory=torch.cuda.is_available(),
**kwargs,
)
__all__ = ["get_dataset", "ZarrDataset"]
================================================
FILE: nobrainer/datasets/__init__.py
================================================
"""Dataset fetching utilities for various neuroimaging sources.
Each submodule provides functions to install and fetch data from a
specific source. All require the ``[versioning]`` optional extra
(``datalad``, ``git-annex``) unless noted otherwise.
Available sources
-----------------
- :mod:`nobrainer.datasets.openneuro` — OpenNeuro raw + derivatives
"""
from __future__ import annotations
def _check_datalad():
"""Import datalad.api, raising a clear error if not available."""
try:
import datalad.api as dl
return dl
except ImportError:
raise ImportError(
"DataLad is required for dataset fetching. "
"Install with: pip install 'nobrainer[versioning]'\n"
"Also install git-annex: uv tool install git-annex"
) from None
__all__ = ["openneuro"]
================================================
FILE: nobrainer/datasets/openneuro.py
================================================
"""Fetch datasets from OpenNeuro and OpenNeuro Derivatives via DataLad.
Requires the ``[versioning]`` extra (``datalad >= 0.19``) and the
``git-annex`` PyPI package (``uv tool install git-annex`` or
``pip install git-annex``).
Examples
--------
Fetch fmriprep derivatives and get T1w + aparc+aseg pairs::
from nobrainer.datasets.openneuro import (
install_derivatives,
find_subject_pairs,
write_manifest,
)
ds_path = install_derivatives("ds000114", "/tmp/data")
pairs = find_subject_pairs(ds_path)
write_manifest(pairs, "manifest.csv")
Fetch a raw OpenNeuro dataset::
from nobrainer.datasets.openneuro import install_dataset
ds_path = install_dataset("ds000114", "/tmp/data")
Fetch specific files without auto-discovery::
from nobrainer.datasets.openneuro import (
install_derivatives,
glob_dataset,
fetch_files,
)
ds_path = install_derivatives("ds000114", "/tmp/data")
bold_files = glob_dataset(ds_path, "sub-*/func/*_bold.nii.gz")
fetched = fetch_files(ds_path, bold_files[:5])
"""
from __future__ import annotations
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
_OPENNEURO_GH = "https://github.com/OpenNeuroDatasets"
_OPENNEURO_DERIV_GH = "https://github.com/OpenNeuroDerivatives"
def _dl():
"""Lazy import of datalad.api."""
from nobrainer.datasets import _check_datalad
return _check_datalad()
# ---------------------------------------------------------------------------
# Install (lightweight clone, no bulk download)
# ---------------------------------------------------------------------------
def install_dataset(
dataset_id: str,
path: str | Path,
) -> Path:
"""Clone an OpenNeuro dataset (metadata only, no file content).
Parameters
----------
dataset_id : str
OpenNeuro accession (e.g. ``"ds000114"``).
path : str or Path
Base directory. The dataset is cloned into
``<path>/<dataset_id>``.
Returns
-------
Path
Absolute path to the installed dataset directory.
"""
dl = _dl()
dest = Path(path) / dataset_id
if dest.exists():
logger.info("Dataset %s already at %s", dataset_id, dest)
return dest.resolve()
source = f"{_OPENNEURO_GH}/{dataset_id}.git"
logger.info("Installing %s from %s", dataset_id, source)
dl.install(source=source, path=str(dest))
return dest.resolve()
def install_derivatives(
dataset_id: str,
path: str | Path,
derivative: str = "fmriprep",
) -> Path:
"""Clone an OpenNeuro Derivatives dataset (metadata only).
Parameters
----------
dataset_id : str
OpenNeuro accession (e.g. ``"ds000114"``).
path : str or Path
Base directory. Cloned into ``<path>/<dataset_id>-<derivative>``.
derivative : str
Pipeline name (default ``"fmriprep"``). Common values:
``"fmriprep"``, ``"mriqc"``, ``"freesurfer"``.
Returns
-------
Path
Absolute path to the installed derivative directory.
"""
dl = _dl()
dest = Path(path) / f"{dataset_id}-{derivative}"
if dest.exists():
logger.info("Derivative %s-%s already at %s", dataset_id, derivative, dest)
return dest.resolve()
source = f"{_OPENNEURO_DERIV_GH}/{dataset_id}-{derivative}.git"
logger.info("Installing %s-%s from %s", dataset_id, derivative, source)
dl.install(source=source, path=str(dest))
return dest.resolve()
# ---------------------------------------------------------------------------
# File discovery and download
# ---------------------------------------------------------------------------
def glob_dataset(
dataset_dir: str | Path,
pattern: str,
) -> list[Path]:
"""Glob a DataLad dataset directory (metadata only, no download).
Works on the git tree — returned paths may be git-annex symlinks
whose content hasn't been fetched yet.
Parameters
----------
dataset_dir : str or Path
Root of the DataLad dataset.
pattern : str
Glob pattern (e.g. ``"sub-*/anat/*_T1w.nii.gz"``).
Returns
-------
list of Path
Sorted matching paths.
"""
return sorted(Path(dataset_dir).glob(pattern))
def fetch_files(
dataset_dir: str | Path,
paths: list[str | Path],
) -> list[Path]:
"""Download specific files from a DataLad dataset.
Parameters
----------
dataset_dir : str or Path
Root of the DataLad dataset.
paths : list of str or Path
Files to download (absolute or relative to *dataset_dir*).
Returns
-------
list of Path
Paths whose content was successfully downloaded.
"""
dl = _dl()
dataset_dir = Path(dataset_dir)
try:
dl.get([str(p) for p in paths], dataset=str(dataset_dir))
except Exception as exc:
logger.warning("datalad get failed: %s", exc)
return [p for p in (Path(x) for x in paths) if _file_ok(p)]
# ---------------------------------------------------------------------------
# Paired file discovery (structural MRI)
# ---------------------------------------------------------------------------
def _extract_subject_id(path: Path) -> str:
"""Extract ``sub-XX`` from a BIDS-style path.
Checks directory components first (``sub-01/anat/...``), then
parses the filename (``sub-01_desc-preproc_T1w.nii.gz``).
"""
# Check directory parts (e.g. .../sub-01/anat/...)
for part in path.parts[:-1]: # skip filename
if part.startswith("sub-"):
return part
# Parse from filename
name = path.name
if name.startswith("sub-"):
return name.split("_")[0]
return name
def _file_ok(p: Path) -> bool:
"""True if *p* is a real file with nonzero size."""
try:
return p.stat().st_size > 0
except OSError:
return False
def find_subject_pairs(
dataset_dir: str | Path,
feature_pattern: str | None = None,
label_pattern: str | None = None,
native_space: bool = True,
download: bool = True,
) -> list[dict[str, str]]:
"""Discover and optionally download paired (feature, label) files.
The default patterns find native-space preprocessed T1w images and
aparc+aseg parcellations from fmriprep derivatives.
Strategy:
1. Glob the dataset tree (git metadata only) to find label files.
2. For each label, find the matching feature file for the same
subject.
3. Download each pair via ``datalad get``.
4. Verify both files are accessible before including them.
Parameters
----------
dataset_dir : str or Path
Root of a DataLad dataset (typically an fmriprep derivative).
feature_pattern : str or None
Glob for feature files. When *None*, discovers the best
native-space T1w pattern automatically.
label_pattern : str or None
Glob for label files. When *None*, tries
``*desc-aparcaseg_dseg.nii.gz`` then ``*desc-aseg_dseg.nii.gz``.
native_space : bool
Prefer native-space files (no ``space-`` token). Default True.
download : bool
If True (default), download each pair via ``datalad get``.
Returns
-------
list of dict
Each dict: ``{"subject_id", "t1w_path", "label_path"}``.
"""
dataset_dir = Path(dataset_dir)
pairs: list[dict[str, str]] = []
# --- Discover label files ---
if label_pattern is not None:
label_files = glob_dataset(dataset_dir, label_pattern)
else:
label_files = []
for pat in [
"sub-*/anat/*desc-aparcaseg_dseg.nii.gz",
"sub-*/anat/*desc-aseg_dseg.nii.gz",
]:
label_files = glob_dataset(dataset_dir, pat)
if label_files:
logger.info("Found %d labels matching %s", len(label_files), pat)
break
if not label_files:
logger.warning("No label files found in %s", dataset_dir)
return pairs
# --- Match each label to a feature file ---
for label_path in label_files:
sub_id = _extract_subject_id(label_path)
anat_dir = label_path.parent
if feature_pattern is not None:
feat_candidates = sorted(anat_dir.glob(feature_pattern))
else:
feat_candidates = [
p
for p in anat_dir.glob(f"{sub_id}*desc-preproc_T1w.nii.gz")
if (not native_space) or ("space-" not in p.name)
]
if not feat_candidates:
feat_candidates = sorted(anat_dir.glob(f"{sub_id}*_T1w.nii.gz"))[:1]
if not feat_candidates:
logger.warning("No feature file for %s", sub_id)
continue
feat_path = feat_candidates[0]
if download:
logger.info("Downloading pair for %s", sub_id)
fetch_files(dataset_dir, [feat_path, label_path])
feat_ok = _file_ok(feat_path) if download else True
label_ok = _file_ok(label_path) if download else True
if feat_ok and label_ok:
pairs.append(
{
"subject_id": sub_id,
"t1w_path": str(feat_path),
"label_path": str(label_path),
}
)
else:
logger.warning("Skipping %s: files not accessible", sub_id)
logger.info("Found %d paired subjects in %s", len(pairs), dataset_dir.name)
return pairs
# ---------------------------------------------------------------------------
# Manifest writing
# ---------------------------------------------------------------------------
def write_manifest(
pairs: list[dict[str, str]],
output_path: str | Path,
split_ratios: tuple[int, int, int] = (80, 10, 10),
seed: int = 42,
) -> Path:
"""Write a manifest CSV with train/val/test split.
Parameters
----------
pairs : list of dict
Each dict has ``"subject_id"``, ``"t1w_path"``, ``"label_path"``.
Optionally ``"dataset_id"``.
output_path : str or Path
Destination CSV.
split_ratios : tuple of int
(train, val, test) percentages.
seed : int
Random seed for reproducible splits.
Returns
-------
Path
Written CSV path.
"""
import csv
import numpy as np
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
rng = np.random.default_rng(seed)
indices = rng.permutation(len(pairs))
total = sum(split_ratios)
n_train = int(len(pairs) * split_ratios[0] / total)
n_val = int(len(pairs) * split_ratios[1] / total)
for i, idx in enumerate(indices):
if i < n_train:
pairs[idx]["split"] = "train"
elif i < n_train + n_val:
pairs[idx]["split"] = "val"
else:
pairs[idx]["split"] = "test"
fieldnames = ["subject_id", "dataset_id", "t1w_path", "label_path", "split"]
if not any("dataset_id" in p for p in pairs):
fieldnames = [f for f in fieldnames if f != "dataset_id"]
with open(output_path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
writer.writeheader()
writer.writerows(pairs)
counts = {
s: sum(1 for p in pairs if p.get("split") == s)
for s in ("train", "val", "test")
}
logger.info(
"Manifest: %s — %d subjects (train=%d, val=%d, test=%d)",
output_path,
len(pairs),
counts["train"],
counts["val"],
counts["test"],
)
return output_path
================================================
FILE: nobrainer/datasets/zarr_store.py
================================================
"""Multi-subject Zarr3 dataset store with sharding.
Converts NIfTI collections into a single sharded Zarr3 store where
subjects are stacked along a 4th dimension: ``images[N, D, H, W]``
and ``labels[N, D, H, W]``. This layout enables efficient partial I/O
for training: reading one subject's patch is a single seek into one
shard file.
Requires the ``[zarr]`` optional extra (``zarr >= 3.0``).
"""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Any
import numpy as np
logger = logging.getLogger(__name__)
def _conform_volume(img, target_shape, target_voxel_size=(1.0, 1.0, 1.0)):
"""Conform a nibabel image to target shape and voxel size."""
from nibabel.processing import conform
return conform(img, out_shape=target_shape, voxel_size=target_voxel_size)
def _infer_target_shape(
image_paths: list[str | Path],
max_scan: int = 50,
) -> tuple[tuple[int, int, int], tuple[float, float, float]]:
"""Infer target shape and voxel size from input volumes.
Uses the median shape and modal voxel size across a sample of volumes.
"""
import nibabel as nib
shapes = []
voxel_sizes = []
for p in image_paths[:max_scan]:
img = nib.load(p)
shapes.append(img.shape[:3])
voxel_sizes.append(tuple(np.abs(img.header.get_zooms()[:3])))
# Median shape (rounded to nearest integer)
median_shape = tuple(int(np.median([s[i] for s in shapes])) for i in range(3))
# Modal voxel size (most common, or median if all different)
from collections import Counter
vox_counts = Counter(voxel_sizes)
if vox_counts:
modal_voxel = vox_counts.most_common(1)[0][0]
else:
modal_voxel = (1.0, 1.0, 1.0)
return median_shape, modal_voxel
def create_zarr_store(
image_label_pairs: list[tuple[str, str]],
output_path: str | Path,
subject_ids: list[str] | None = None,
chunk_shape: tuple[int, int, int] = (32, 32, 32),
shard_shape: tuple[int, int, int] | None = None,
compressor: str = "blosc",
conform: bool = True,
target_shape: tuple[int, int, int] | None = None,
target_voxel_size: tuple[float, float, float] | None = None,
) -> Path:
"""Convert NIfTI pairs into a single sharded Zarr3 store.
When ``conform=True`` (default), volumes are conformed to a uniform
shape so they can be stacked into 4D arrays ``images[N, D, H, W]``
and ``labels[N, D, H, W]``. The target shape is inferred from the
data (median shape) unless explicitly provided.
Parameters
----------
image_label_pairs : list of (str, str)
List of ``(image_path, label_path)`` tuples.
output_path : str or Path
Output Zarr store directory.
subject_ids : list of str or None
Subject identifiers. If None, auto-generated as ``sub-000``, etc.
chunk_shape : tuple of int
Spatial chunk dimensions (default 32³).
shard_shape : tuple of int or None
Shard dimensions. None = auto (full array or large multiple).
compressor : str
Compression codec name (default ``"blosc"``).
conform : bool
Auto-conform volumes to uniform shape (default True).
target_shape : tuple of int or None
Target spatial shape. None = infer from data.
target_voxel_size : tuple of float or None
Target voxel size. None = infer from data.
Returns
-------
Path
Path to the created Zarr store.
"""
import nibabel as nib
import zarr
output_path = Path(output_path)
n_subjects = len(image_label_pairs)
if subject_ids is None:
subject_ids = [f"sub-{i:03d}" for i in range(n_subjects)]
if len(subject_ids) != n_subjects:
raise ValueError(
f"subject_ids length ({len(subject_ids)}) != pairs ({n_subjects})"
)
image_paths = [p[0] for p in image_label_pairs]
# Infer or validate target shape
if conform:
if target_shape is None or target_voxel_size is None:
inferred_shape, inferred_voxel = _infer_target_shape(image_paths)
if target_shape is None:
target_shape = inferred_shape
if target_voxel_size is None:
target_voxel_size = inferred_voxel
logger.info(
"Inferred target: shape=%s, voxel_size=%s",
target_shape,
target_voxel_size,
)
else:
# Check all shapes are the same
first_img = nib.load(image_paths[0])
target_shape = first_img.shape[:3]
for p in image_paths[1:]:
img = nib.load(p)
if img.shape[:3] != target_shape:
raise ValueError(
f"Non-uniform shapes detected ({img.shape[:3]} vs {target_shape}). "
"Use conform=True to auto-conform, or ensure all volumes match."
)
D, H, W = target_shape
full_chunk = (1, *chunk_shape) # one subject per chunk along axis 0
# Shard shape: group subjects into shards for balanced write parallelism
# and read efficiency. Default: ~50 subjects per shard → manageable
# file count while allowing parallel writes across shards.
subjects_per_shard = 50
if shard_shape is not None:
full_shard = shard_shape
else:
full_shard = (min(subjects_per_shard, n_subjects), D, H, W)
# Create store
store = zarr.open_group(str(output_path), mode="w")
# Create sharded 4D arrays
n_shards = int(np.ceil(n_subjects / full_shard[0]))
images_arr = store.create_array(
"images",
shape=(n_subjects, D, H, W),
chunks=full_chunk,
shards=full_shard,
dtype=np.float32,
)
labels_arr = store.create_array(
"labels",
shape=(n_subjects, D, H, W),
chunks=full_chunk,
shards=full_shard,
dtype=np.int32,
)
logger.info(
"Created sharded Zarr3: shape=%s, chunks=%s, shards=%s (%d shard files)",
(n_subjects, D, H, W),
full_chunk,
full_shard,
n_shards,
)
# Write volumes — parallel across shards.
# Each shard is independent, so we can write to different shards
# concurrently. Within a shard, writes are sequential.
import concurrent.futures
import os
n_workers = min(os.cpu_count() or 1, n_shards, 8)
def _write_shard_group(shard_idx):
"""Load and write all subjects belonging to one shard."""
start = shard_idx * full_shard[0]
end = min(start + full_shard[0], n_subjects)
for i in range(start, end):
img_path, lbl_path = image_label_pairs[i]
img = nib.load(img_path)
lbl = nib.load(lbl_path)
if conform:
img = _conform_volume(img, target_shape, target_voxel_size)
lbl = _conform_volume(lbl, target_shape, target_voxel_size)
images_arr[i] = np.asarray(img.dataobj, dtype=np.float32)[:D, :H, :W]
labels_arr[i] = np.asarray(lbl.dataobj, dtype=np.int32)[:D, :H, :W]
return end - start
logger.info(
"Writing %d volumes across %d shards with %d workers...",
n_subjects,
n_shards,
n_workers,
)
with concurrent.futures.ThreadPoolExecutor(max_workers=n_workers) as pool:
futures = [pool.submit(_write_shard_group, s) for s in range(n_shards)]
done = 0
for future in concurrent.futures.as_completed(futures):
done += future.result()
logger.info("Stored %d/%d volumes", done, n_subjects)
# Store metadata
store.attrs["n_subjects"] = n_subjects
store.attrs["subject_ids"] = subject_ids
store.attrs["volume_shape"] = list(target_shape)
store.attrs["chunk_shape"] = list(chunk_shape)
store.attrs["layout"] = "stacked"
store.attrs["image_dtype"] = "float32"
store.attrs["label_dtype"] = "int32"
if conform:
store.attrs["conformed"] = True
store.attrs["target_shape"] = [int(x) for x in target_shape]
store.attrs["target_voxel_size"] = [float(x) for x in target_voxel_size]
else:
store.attrs["conformed"] = False
logger.info(
"Zarr store created: %s (%d subjects, shape=%s)",
output_path,
n_subjects,
target_shape,
)
return output_path.resolve()
def store_info(store_path: str | Path) -> dict[str, Any]:
"""Return store metadata without reading voxel data.
Parameters
----------
store_path : str or Path
Path to a Zarr store.
Returns
-------
dict
Store metadata including n_subjects, volume_shape, subject_ids, etc.
"""
import zarr
store = zarr.open_group(str(store_path), mode="r")
return dict(store.attrs)
def create_partition(
store_path: str | Path,
ratios: tuple[int, int, int] = (80, 10, 10),
seed: int = 42,
output_path: str | Path | None = None,
) -> Path:
"""Generate a partition index JSON file.
Parameters
----------
store_path : str or Path
Path to the Zarr store.
ratios : tuple of int
(train, val, test) percentages.
seed : int
Random seed for reproducibility.
output_path : str or Path or None
Output JSON path. None = ``<store_path>_partition.json``.
Returns
-------
Path
Path to the written partition JSON file.
"""
info = store_info(store_path)
subject_ids = info["subject_ids"]
n = len(subject_ids)
rng = np.random.default_rng(seed)
indices = rng.permutation(n)
total = sum(ratios)
n_train = int(n * ratios[0] / total)
n_val = int(n * ratios[1] / total)
train_ids = [subject_ids[i] for i in indices[:n_train]]
val_ids = [subject_ids[i] for i in indices[n_train : n_train + n_val]]
test_ids = [subject_ids[i] for i in indices[n_train + n_val :]]
partition = {
"seed": seed,
"ratios": list(ratios),
"n_subjects": n,
"store_path": str(store_path),
"partitions": {
"train": train_ids,
"val": val_ids,
"test": test_ids,
},
}
if output_path is None:
output_path = Path(str(store_path) + "_partition.json")
output_path = Path(output_path)
with open(output_path, "w") as f:
json.dump(partition, f, indent=2)
logger.info(
"Partition created: %s (train=%d, val=%d, test=%d)",
output_path,
len(train_ids),
len(val_ids),
len(test_ids),
)
return output_path
def load_partition(partition_path: str | Path) -> dict[str, list[str]]:
"""Load a partition index and return ``{split: [subject_ids]}``.
Parameters
----------
partition_path : str or Path
Path to a partition JSON file.
Returns
-------
dict
``{"train": [...], "val": [...], "test": [...]}``.
"""
with open(partition_path) as f:
data = json.load(f)
return data["partitions"]
================================================
FILE: nobrainer/distributed_learning/dwc.py
================================================
import numpy as np
# Distributed weight consolidation for Bayesian Deep Neural Networks
# Implemented according to the:
# McClure, Patrick, et al. Distributed weight consolidation: a brain segmentation case study.
# Advances in neural information processing systems 31 (2018): 4093.
def distributed_weight_consolidation(model_weights, model_priors):
# model_weights is a list of weights of client-models; models = [model1, model2, model3...]
# model_priors is a list of priors of client models sames as models
num_layers = int(len(model_weights[0]) / 2.0)
num_datasets = np.shape(model_weights)[0]
consolidated_model = model_weights[0]
mean_idx = [i for i in range(0, len(model_weights[0])) if i % 2 == 0]
std_idx = [i for i in range(0, len(model_weights[0])) if i % 2 != 0]
ep = 1e-5
for i in range(num_layers):
num_1 = 0
num_2 = 0
den_1 = 0
den_2 = 0
for m in range(num_datasets):
model = model_weights[m]
prior = model_priors[m]
mu_s = model[mean_idx[i]]
mu_o = prior[mean_idx[i]]
sig_s = model[std_idx[i]]
sig_o = prior[std_idx[i]]
d1 = np.power(sig_s, 2) + ep
d2 = np.power(sig_o, 2) + ep
num_1 = num_1 + (mu_s / d1)
num_2 = num_2 + (mu_o / d2)
den_1 = den_1 + (1.0 / d1)
den_2 = den_2 + (1.0 / d2)
consolidated_model[mean_idx[i]] = (num_1 - num_2) / (den_1 - den_2)
consolidated_model[std_idx[i]] = 1 / (den_1 - den_2)
return consolidated_model
================================================
FILE: nobrainer/experiment.py
================================================
"""Experiment tracking: local file logger + optional Weights & Biases.
Provides a unified interface for logging training metrics. The local
logger always works (writes JSON lines + CSV to the output directory).
W&B integration is optional and auto-detected.
Usage::
from nobrainer.experiment import ExperimentTracker
# Local-only (writes to output_dir/metrics.jsonl + metrics.csv)
tracker = ExperimentTracker(output_dir="checkpoints/bvwn", config={...})
# With W&B (if wandb is installed and WANDB_API_KEY is set)
tracker = ExperimentTracker(
output_dir="checkpoints/bvwn",
config={"lr": 1e-4, "filters": 96},
project="kwyk-reproduction",
tags=["bvwn_multi_prior", "50-class"],
)
for epoch in range(epochs):
tracker.log({"epoch": epoch, "train_loss": loss, "val_dice": dice})
tracker.finish()
"""
from __future__ import annotations
import csv
import json
import logging
import os
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
class _LocalLogger:
"""Write metrics to JSON lines + CSV in the output directory."""
def __init__(self, output_dir: Path) -> None:
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.jsonl_path = self.output_dir / "metrics.jsonl"
self.csv_path = self.output_dir / "metrics.csv"
self._csv_writer = None
self._csv_file = None
self._fieldnames: list[str] | None = None
def log(self, metrics: dict[str, Any]) -> None:
# JSON lines (append)
with open(self.jsonl_path, "a") as f:
f.write(json.dumps(metrics, default=str) + "\n")
# CSV (create header on first call, append rows)
if self._csv_writer is None:
self._fieldnames = list(metrics.keys())
self._csv_file = open(self.csv_path, "w", newline="")
self._csv_writer = csv.DictWriter(
self._csv_file, fieldnames=self._fieldnames, extrasaction="ignore"
)
self._csv_writer.writeheader()
self._csv_writer.writerow(metrics)
self._csv_file.flush()
def log_config(self, config: dict[str, Any]) -> None:
with open(self.output_dir / "config.json", "w") as f:
json.dump(config, f, indent=2, default=str)
def finish(self) -> None:
if self._csv_file is not None:
self._csv_file.close()
self._csv_file = None
self._csv_writer = None
class _WandbLogger:
"""Log metrics to Weights & Biases."""
def __init__(
self,
config: dict[str, Any],
project: str | None,
name: str | None,
tags: list[str] | None,
) -> None:
import wandb
self._wandb = wandb
self._run = wandb.init(
project=project or "nobrainer",
name=name,
config=config,
tags=tags,
reinit=True,
)
def log(self, metrics: dict[str, Any]) -> None:
self._wandb.log(metrics)
def log_config(self, config: dict[str, Any]) -> None:
self._run.config.update(config, allow_val_change=True)
def finish(self) -> None:
self._wandb.finish()
class ExperimentTracker:
"""Unified experiment tracker with local + optional W&B backends.
The local backend always runs, writing ``metrics.jsonl``,
``metrics.csv``, and ``config.json`` to *output_dir*. W&B is
activated when:
1. ``wandb`` is installed, AND
2. ``WANDB_API_KEY`` is set or ``use_wandb=True`` is passed.
Parameters
----------
output_dir : str or Path
Directory for local metric files.
config : dict, optional
Hyperparameters / configuration to log.
project : str, optional
W&B project name (default ``"nobrainer"``).
name : str, optional
W&B run name.
tags : list of str, optional
W&B run tags.
use_wandb : bool or None
Force W&B on/off. None = auto-detect (use if installed + key set).
"""
def __init__(
self,
output_dir: str | Path,
config: dict[str, Any] | None = None,
project: str | None = None,
name: str | None = None,
tags: list[str] | None = None,
use_wandb: bool | None = None,
) -> None:
self._backends: list[Any] = []
# Local logger (always active)
local = _LocalLogger(Path(output_dir))
self._backends.append(local)
# Save config locally
if config:
local.log_config(config)
# W&B (optional)
if use_wandb is None:
use_wandb = (
os.environ.get("WANDB_API_KEY") is not None
or os.environ.get("WANDB_MODE") == "offline"
)
if use_wandb:
try:
wb = _WandbLogger(
config=config or {},
project=project,
name=name,
tags=tags,
)
self._backends.append(wb)
logger.info("W&B tracking enabled (project=%s)", project)
except Exception as exc:
logger.warning("W&B init failed: %s — using local only", exc)
backend_names = [type(b).__name__ for b in self._backends]
logger.info("Experiment tracking: %s", ", ".join(backend_names))
def log(self, metrics: dict[str, Any]) -> None:
"""Log a dict of metrics to all backends."""
for backend in self._backends:
backend.log(metrics)
def log_config(self, config: dict[str, Any]) -> None:
"""Log/update configuration to all backends."""
for backend in self._backends:
backend.log_config(config)
def finish(self) -> None:
"""Finalize all backends (flush files, end W&B run)."""
for backend in self._backends:
backend.finish()
def callback(self, **extra_fields) -> callable:
"""Return a training callback that logs epoch metrics.
The returned callable has signature ``(epoch, logs, model)`` —
matching the callback protocol in :func:`nobrainer.training.fit`
and :class:`Segmentation.fit`.
Parameters
----------
**extra_fields
Extra key-value pairs included in every log entry (e.g.,
``variant="bvwn_multi_prior"``).
Example::
tracker = ExperimentTracker("checkpoints/bvwn", config={...})
seg.fit(ds, epochs=50, callbacks=[tracker.callback(variant="ssd")])
tracker.finish()
"""
def _cb(epoch: int, logs: dict, model: Any) -> None:
self.log({"epoch": epoch, **logs, **extra_fields})
return _cb
================================================
FILE: nobrainer/gpu.py
================================================
"""GPU utilities: device detection, memory profiling, batch size optimization.
Examples
--------
Auto-select the best batch size for a model and block shape::
from nobrainer.gpu import auto_batch_size, gpu_info
info = gpu_info()
print(info)
# [{'name': 'Tesla T4', 'memory_gb': 15.1, 'id': 0}, ...]
batch_size = auto_batch_size(
model=my_model,
block_shape=(32, 32, 32),
n_classes=2,
target_memory_fraction=0.85,
)
print(f"Optimal batch size: {batch_size}")
Scale batch size for multi-GPU::
from nobrainer.gpu import scale_for_multi_gpu
effective_batch, per_gpu_batch, n_gpus = scale_for_multi_gpu(
base_batch_size=32,
block_shape=(32, 32, 32),
)
# On 4x T4: effective=128, per_gpu=32, n_gpus=4
"""
from __future__ import annotations
import logging
from typing import Any
import torch
import torch.nn as nn
logger = logging.getLogger(__name__)
def get_device() -> torch.device:
"""Select the best available device: CUDA > MPS > CPU."""
if torch.cuda.is_available():
return torch.device("cuda")
if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
return torch.device("mps")
return torch.device("cpu")
def gpu_count() -> int:
"""Return the number of CUDA GPUs available (0 if none)."""
if torch.cuda.is_available():
return torch.cuda.device_count()
return 0
def gpu_info() -> list[dict[str, Any]]:
"""Return a list of dicts with GPU name, memory, and id.
Returns an empty list if no CUDA GPUs are available.
"""
info = []
if not torch.cuda.is_available():
return info
for i in range(torch.cuda.device_count()):
props = torch.cuda.get_device_properties(i)
info.append(
{
"id": i,
"name": props.name,
"memory_gb": round(props.total_memory / 1e9, 1),
"compute_capability": f"{props.major}.{props.minor}",
}
)
return info
def _estimate_memory_per_sample(
model: nn.Module,
block_shape: tuple[int, int, int],
n_classes: int = 2,
in_channels: int = 1,
dtype: torch.dtype = torch.float32,
forward_kwargs: dict | None = None,
) -> float:
"""Estimate GPU memory (bytes) for one training sample.
Runs a forward + backward pass with batch_size=1 and measures the
peak allocated memory. The model is moved to GPU temporarily.
Parameters
----------
model : nn.Module
Model to profile.
block_shape : tuple of int
Spatial dimensions of one input patch.
n_classes : int
Number of output classes.
in_channels : int
Number of input channels.
dtype : torch.dtype
Input data type.
Returns
-------
float
Estimated bytes per sample (forward + backward + optimizer overhead).
"""
if forward_kwargs is None:
forward_kwargs = {}
if not torch.cuda.is_available():
raise RuntimeError("CUDA required for memory estimation")
device = torch.device("cuda")
model = model.to(device)
model.train()
torch.cuda.reset_peak_memory_stats(device)
torch.cuda.empty_cache()
baseline = torch.cuda.memory_allocated(device)
x = torch.randn(1, in_channels, *block_shape, device=device, dtype=dtype)
labels = torch.randint(0, n_classes, (1, *block_shape), device=device)
# Pass forward_kwargs if model accepts them (e.g. mc_vwn, mc_dropout)
try:
out = model(x, **forward_kwargs)
except TypeError:
out = model(x)
loss = nn.CrossEntropyLoss()(out, labels)
loss.backward()
peak = torch.cuda.max_memory_allocated(device) - baseline
# Clean up
model.zero_grad(set_to_none=True)
del x, labels, out, loss
torch.cuda.empty_cache()
model.cpu()
return float(peak)
def auto_batch_size(
model: nn.Module,
block_shape: tuple[int, int, int],
n_classes: int = 2,
in_channels: int = 1,
target_memory_fraction: float = 0.85,
gpu_id: int = 0,
min_batch: int = 1,
max_batch: int = 512,
forward_kwargs: dict | None = None,
) -> int:
"""Estimate the largest batch size that fits in GPU memory.
Profiles one sample, then scales to fill ``target_memory_fraction``
of the GPU.
Parameters
----------
model : nn.Module
Model to profile (will be temporarily moved to GPU).
block_shape : tuple of int
Spatial dimensions ``(D, H, W)`` of one input patch.
n_classes : int
Number of output classes.
in_channels : int
Number of input channels.
target_memory_fracti
gitextract_7t995rdz/
├── .autorc
├── .dockerignore
├── .flake8
├── .gitattributes
├── .github/
│ ├── EC2_GPU_RUNNER.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── documentation.md
│ │ ├── feature_request.md
│ │ ├── maintenance.md
│ │ └── question.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ ├── ci.yml
│ ├── guide-notebooks-ec2.yml
│ ├── kwyk-reproduction-ec2.yml
│ ├── publish.yml
│ ├── release.yml
│ └── validate-book.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .zenodo.json
├── CHANGELOG.md
├── CITATION
├── CLAUDE.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── conftest.py
├── docker/
│ ├── README.md
│ ├── cpu.Dockerfile
│ └── gpu.Dockerfile
├── nobrainer/
│ ├── __init__.py
│ ├── _version.py
│ ├── augmentation/
│ │ ├── __init__.py
│ │ ├── profiles.py
│ │ ├── synthseg.py
│ │ └── transforms.py
│ ├── cli/
│ │ ├── __init__.py
│ │ ├── main.py
│ │ └── tests/
│ │ ├── __init__.py
│ │ └── main_test.py
│ ├── dataset.py
│ ├── datasets/
│ │ ├── __init__.py
│ │ ├── openneuro.py
│ │ └── zarr_store.py
│ ├── distributed_learning/
│ │ └── dwc.py
│ ├── experiment.py
│ ├── gpu.py
│ ├── io.py
│ ├── layers/
│ │ ├── InstanceNorm.py
│ │ ├── __init__.py
│ │ ├── bernoulli_dropout.py
│ │ ├── concrete_dropout.py
│ │ ├── gaussian_dropout.py
│ │ ├── maxpool4d.py
│ │ ├── padding.py
│ │ └── tests/
│ │ └── __init__.py
│ ├── losses.py
│ ├── metrics.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── _constants.py
│ │ ├── _utils.py
│ │ ├── autoencoder.py
│ │ ├── bayesian/
│ │ │ ├── __init__.py
│ │ │ ├── bayesian_meshnet.py
│ │ │ ├── bayesian_vnet.py
│ │ │ ├── kwyk_meshnet.py
│ │ │ ├── layers.py
│ │ │ ├── utils.py
│ │ │ ├── vwn_layers.py
│ │ │ └── warmstart.py
│ │ ├── generative/
│ │ │ ├── __init__.py
│ │ │ ├── dcgan.py
│ │ │ └── progressivegan.py
│ │ ├── highresnet.py
│ │ ├── meshnet.py
│ │ ├── segformer3d.py
│ │ ├── segmentation.py
│ │ ├── simsiam.py
│ │ └── tests/
│ │ └── __init__.py
│ ├── prediction.py
│ ├── processing/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── croissant.py
│ │ ├── dataset.py
│ │ ├── generation.py
│ │ └── segmentation.py
│ ├── research/
│ │ ├── __init__.py
│ │ ├── loop.py
│ │ └── templates/
│ │ ├── .gitkeep
│ │ ├── prepare.py
│ │ └── train_bayesian_vnet.py
│ ├── slurm.py
│ ├── sr-tests/
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── test_bayesian_uncertainty.py
│ │ ├── test_brain_generation.py
│ │ ├── test_croissant_metadata.py
│ │ ├── test_dataset_builder.py
│ │ ├── test_extract_patches.py
│ │ ├── test_kwyk_smoke.py
│ │ ├── test_raw_pytorch_api.py
│ │ ├── test_segmentation_estimator.py
│ │ ├── test_synthseg_brain.py
│ │ ├── test_zarr_conversion.py
│ │ └── test_zarr_pipeline.py
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── contract/
│ │ │ ├── __init__.py
│ │ │ └── test_cli.py
│ │ ├── gpu/
│ │ │ ├── __init__.py
│ │ │ ├── test_bayesian_e2e.py
│ │ │ ├── test_gan_e2e.py
│ │ │ ├── test_multi_gpu.py
│ │ │ └── test_predict_e2e.py
│ │ ├── integration/
│ │ │ ├── __init__.py
│ │ │ ├── test_datalad_commit.py
│ │ │ └── test_research_smoke.py
│ │ └── unit/
│ │ ├── __init__.py
│ │ ├── test_bayesian_layers.py
│ │ ├── test_bayesian_models.py
│ │ ├── test_class_weights.py
│ │ ├── test_croissant.py
│ │ ├── test_dataset.py
│ │ ├── test_dataset_builder.py
│ │ ├── test_datasets_openneuro.py
│ │ ├── test_estimator_generation.py
│ │ ├── test_estimator_segmentation.py
│ │ ├── test_experiment.py
│ │ ├── test_generative.py
│ │ ├── test_gpu.py
│ │ ├── test_io_weights.py
│ │ ├── test_io_zarr.py
│ │ ├── test_layers.py
│ │ ├── test_losses.py
│ │ ├── test_metrics.py
│ │ ├── test_model_interface.py
│ │ ├── test_model_registry.py
│ │ ├── test_models_segmentation.py
│ │ ├── test_prediction.py
│ │ ├── test_research_commit.py
│ │ ├── test_research_loop.py
│ │ ├── test_segformer3d.py
│ │ ├── test_slurm.py
│ │ ├── test_stride_patches.py
│ │ ├── test_synthseg.py
│ │ ├── test_training.py
│ │ ├── test_training_convergence.py
│ │ ├── test_transform_pipeline.py
│ │ ├── test_vwn_layers.py
│ │ ├── test_zarr_dataset.py
│ │ └── test_zarr_store.py
│ ├── training.py
│ ├── utils.py
│ └── validation.py
├── pyproject.toml
└── scripts/
├── kwyk_reproduction/
│ ├── 01_assemble_dataset.py
│ ├── 02_train_meshnet.py
│ ├── 03_train_bayesian.py
│ ├── 04_evaluate.py
│ ├── 05_compare_kwyk.py
│ ├── 06_block_size_sweep.py
│ ├── ARCHITECTURE.md
│ ├── README.md
│ ├── __init__.py
│ ├── build_kwyk_manifest.py
│ ├── config.yaml
│ ├── config_kwyk_smoke.yaml
│ ├── convert_zarr_shard.py
│ ├── experiments/
│ │ ├── 01_20260330_eval_deterministic/
│ │ │ ├── README.md
│ │ │ ├── eval_deterministic.py
│ │ │ ├── results_summary.md
│ │ │ └── run.sbatch
│ │ ├── 02_20260330_binary_bayesian/
│ │ │ ├── README.md
│ │ │ ├── config.yaml
│ │ │ ├── eval_binary.py
│ │ │ ├── eval_only.sbatch
│ │ │ └── run.sbatch
│ │ ├── 03_20260330_warmstart_diagnostic/
│ │ │ ├── README.md
│ │ │ ├── diagnose.py
│ │ │ ├── results_summary.md
│ │ │ └── run.sbatch
│ │ ├── 04_20260330_fixed_warmstart/
│ │ │ ├── README.md
│ │ │ ├── run.py
│ │ │ └── run.sbatch
│ │ ├── 05_20260330_kwyk_from_scratch/
│ │ │ ├── README.md
│ │ │ ├── results_summary.md
│ │ │ ├── run.py
│ │ │ └── run.sbatch
│ │ ├── 06_20260331_fullvol_augment/
│ │ │ ├── README.md
│ │ │ ├── config_256.yaml
│ │ │ ├── config_256_mp.yaml
│ │ │ ├── config_fullvol.yaml
│ │ │ ├── run_128.sbatch
│ │ │ ├── run_256.sbatch
│ │ │ ├── run_256_a100.sbatch
│ │ │ ├── run_256_gradckpt.sbatch
│ │ │ └── run_256_mp.sbatch
│ │ ├── 07_20260401_ddp_128/
│ │ │ ├── config.yaml
│ │ │ └── run.sbatch
│ │ ├── 08_20260401_ddp_128_full/
│ │ │ ├── config.yaml
│ │ │ └── run.sbatch
│ │ └── task-planner.md
│ ├── label_mappings/
│ │ ├── 115-class-mapping.csv
│ │ ├── 50-class-mapping.csv
│ │ └── 6-class-mapping.csv
│ ├── run.sh
│ ├── slurm_convert_zarr.sbatch
│ ├── slurm_kwyk_bayesian.sbatch
│ ├── slurm_kwyk_evaluate.sbatch
│ ├── slurm_kwyk_smoke.sbatch
│ ├── slurm_train.sbatch
│ ├── slurm_zarr_array.sbatch
│ ├── submit_kwyk_smoke.sh
│ └── utils.py
└── synthseg_evaluation/
├── 02_train.py
├── 03_evaluate.py
├── 04_compare.py
├── README.md
├── config.yaml
├── run.sh
└── slurm_train.sbatch
SYMBOL INDEX (1108 symbols across 122 files)
FILE: conftest.py
function pytest_collection_modifyitems (line 9) | def pytest_collection_modifyitems(config, items):
FILE: nobrainer/_version.py
function get_keywords (line 20) | def get_keywords():
class VersioneerConfig (line 33) | class VersioneerConfig:
function get_config (line 37) | def get_config():
class NotThisMethod (line 51) | class NotThisMethod(Exception):
function register_vcs_handler (line 59) | def register_vcs_handler(vcs, method): # decorator
function run_command (line 72) | def run_command(commands, args, cwd=None, verbose=False, hide_stderr=Fal...
function versions_from_parentdir (line 109) | def versions_from_parentdir(parentdir_prefix, root, verbose):
function git_get_keywords (line 140) | def git_get_keywords(versionfile_abs):
function git_versions_from_keywords (line 168) | def git_versions_from_keywords(keywords, tag_prefix, verbose):
function git_pieces_from_vcs (line 239) | def git_pieces_from_vcs(tag_prefix, root, verbose, runner=run_command):
function plus_or_dot (line 374) | def plus_or_dot(pieces):
function render_pep440 (line 381) | def render_pep440(pieces):
function render_pep440_branch (line 405) | def render_pep440_branch(pieces):
function pep440_split_post (line 434) | def pep440_split_post(ver):
function render_pep440_pre (line 444) | def render_pep440_pre(pieces):
function render_pep440_post (line 468) | def render_pep440_post(pieces):
function render_pep440_post_branch (line 495) | def render_pep440_post_branch(pieces):
function render_pep440_old (line 524) | def render_pep440_old(pieces):
function render_git_describe (line 546) | def render_git_describe(pieces):
function render_git_describe_long (line 566) | def render_git_describe_long(pieces):
function render (line 586) | def render(pieces, style):
function get_versions (line 628) | def get_versions():
FILE: nobrainer/augmentation/profiles.py
function get_augmentation_profile (line 21) | def get_augmentation_profile(
FILE: nobrainer/augmentation/synthseg.py
class SynthSegGenerator (line 23) | class SynthSegGenerator(torch.utils.data.Dataset):
method __init__ (line 61) | def __init__(
method __len__ (line 106) | def __len__(self) -> int:
method _get_rng (line 109) | def _get_rng(self, idx: int) -> np.random.Generator:
method __getitem__ (line 115) | def __getitem__(self, idx: int) -> dict[str, torch.Tensor]:
method _generate_intensities (line 153) | def _generate_intensities(self, label_data: np.ndarray) -> np.ndarray:
method _spatial_augmentation (line 191) | def _spatial_augmentation(
method _remap_lr_labels (line 266) | def _remap_lr_labels(label: np.ndarray) -> np.ndarray:
method _randomize_resolution (line 282) | def _randomize_resolution(self, image: np.ndarray) -> np.ndarray:
method _add_bias_field (line 307) | def _add_bias_field(self, image: np.ndarray) -> np.ndarray:
function _rot_x (line 338) | def _rot_x(angle: float) -> np.ndarray:
function _rot_y (line 343) | def _rot_y(angle: float) -> np.ndarray:
function _rot_z (line 348) | def _rot_z(angle: float) -> np.ndarray:
FILE: nobrainer/augmentation/transforms.py
class Augmentation (line 33) | class Augmentation:
method __init__ (line 47) | def __init__(self, transform: Any) -> None:
method __call__ (line 50) | def __call__(self, data: Any) -> Any:
method __repr__ (line 53) | def __repr__(self) -> str:
class TrainableCompose (line 57) | class TrainableCompose(Compose):
method __init__ (line 73) | def __init__(self, transforms: list, mode: str = "train") -> None:
method mode (line 78) | def mode(self) -> str:
method mode (line 82) | def mode(self, value: str) -> None:
method __call__ (line 87) | def __call__(self, data: Any, mode: str | None = None, **kwargs) -> Any:
FILE: nobrainer/cli/main.py
class JSONParamType (line 22) | class JSONParamType(click.ParamType):
method convert (line 25) | def convert(self, value, param, ctx):
function cli (line 36) | def cli():
function predict (line 107) | def predict(
function convert_tfrecords (line 227) | def convert_tfrecords(*, input_paths, output_dir, output_format, verbose):
function convert_to_zarr (line 274) | def convert_to_zarr(*, output, images, labels, chunk_shape, no_conform, ...
function merge (line 302) | def merge():
function generate (line 348) | def generate(
function research (line 457) | def research(
function commit (line 546) | def commit(
function save (line 584) | def save():
function evaluate (line 591) | def evaluate():
function info (line 598) | def info():
FILE: nobrainer/cli/tests/main_test.py
function test_convert_nonscalar_labels (line 18) | def test_convert_nonscalar_labels(tmp_path):
function test_convert_scalar_int_labels (line 40) | def test_convert_scalar_int_labels(tmp_path):
function test_convert_scalar_float_labels (line 68) | def test_convert_scalar_float_labels(tmp_path):
function test_convert_multi_resolution (line 96) | def test_convert_multi_resolution(tmp_path):
function test_merge (line 128) | def test_merge():
function test_predict (line 132) | def test_predict():
function test_generate (line 156) | def test_generate():
function test_save (line 190) | def test_save():
function test_evaluate (line 195) | def test_evaluate():
function test_info (line 199) | def test_info():
FILE: nobrainer/dataset.py
function get_dataset (line 20) | def get_dataset(
class ZarrDataset (line 163) | class ZarrDataset(torch.utils.data.Dataset):
method __init__ (line 170) | def __init__(
method __len__ (line 180) | def __len__(self) -> int:
method __getitem__ (line 183) | def __getitem__(self, idx: int) -> dict:
function _is_zarr_path (line 208) | def _is_zarr_path(path: str | Path) -> bool:
function _get_zarr_dataset (line 213) | def _get_zarr_dataset(
FILE: nobrainer/datasets/__init__.py
function _check_datalad (line 15) | def _check_datalad():
FILE: nobrainer/datasets/openneuro.py
function _dl (line 50) | def _dl():
function install_dataset (line 62) | def install_dataset(
function install_derivatives (line 93) | def install_derivatives(
function glob_dataset (line 132) | def glob_dataset(
function fetch_files (line 156) | def fetch_files(
function _extract_subject_id (line 190) | def _extract_subject_id(path: Path) -> str:
function _file_ok (line 207) | def _file_ok(p: Path) -> bool:
function find_subject_pairs (line 215) | def find_subject_pairs(
function write_manifest (line 325) | def write_manifest(
FILE: nobrainer/datasets/zarr_store.py
function _conform_volume (line 24) | def _conform_volume(img, target_shape, target_voxel_size=(1.0, 1.0, 1.0)):
function _infer_target_shape (line 31) | def _infer_target_shape(
function create_zarr_store (line 63) | def create_zarr_store(
function store_info (line 247) | def store_info(store_path: str | Path) -> dict[str, Any]:
function create_partition (line 266) | def create_partition(
function load_partition (line 334) | def load_partition(partition_path: str | Path) -> dict[str, list[str]]:
FILE: nobrainer/distributed_learning/dwc.py
function distributed_weight_consolidation (line 9) | def distributed_weight_consolidation(model_weights, model_priors):
FILE: nobrainer/experiment.py
class _LocalLogger (line 40) | class _LocalLogger:
method __init__ (line 43) | def __init__(self, output_dir: Path) -> None:
method log (line 52) | def log(self, metrics: dict[str, Any]) -> None:
method log_config (line 68) | def log_config(self, config: dict[str, Any]) -> None:
method finish (line 72) | def finish(self) -> None:
class _WandbLogger (line 79) | class _WandbLogger:
method __init__ (line 82) | def __init__(
method log (line 100) | def log(self, metrics: dict[str, Any]) -> None:
method log_config (line 103) | def log_config(self, config: dict[str, Any]) -> None:
method finish (line 106) | def finish(self) -> None:
class ExperimentTracker (line 110) | class ExperimentTracker:
method __init__ (line 136) | def __init__(
method log (line 177) | def log(self, metrics: dict[str, Any]) -> None:
method log_config (line 182) | def log_config(self, config: dict[str, Any]) -> None:
method finish (line 187) | def finish(self) -> None:
method callback (line 192) | def callback(self, **extra_fields) -> callable:
FILE: nobrainer/gpu.py
function get_device (line 43) | def get_device() -> torch.device:
function gpu_count (line 52) | def gpu_count() -> int:
function gpu_info (line 59) | def gpu_info() -> list[dict[str, Any]]:
function _estimate_memory_per_sample (line 80) | def _estimate_memory_per_sample(
function auto_batch_size (line 148) | def auto_batch_size(
function scale_for_multi_gpu (line 223) | def scale_for_multi_gpu(
FILE: nobrainer/io.py
function read_csv (line 22) | def read_csv(
function read_mapping (line 33) | def read_mapping(
function _compute_sha256 (line 46) | def _compute_sha256(path: str | Path) -> str:
function _parse_tfrecord_file (line 54) | def _parse_tfrecord_file(path: str | Path):
function convert_tfrecords (line 71) | def convert_tfrecords(
function _keras_conv3d_to_pytorch (line 173) | def _keras_conv3d_to_pytorch(w: np.ndarray) -> np.ndarray:
function convert_weights (line 180) | def convert_weights(
function _map_name (line 249) | def _map_name(
function nifti_to_zarr (line 275) | def nifti_to_zarr(
function zarr_to_nifti (line 357) | def zarr_to_nifti(
FILE: nobrainer/layers/InstanceNorm.py
class InstanceNormalization (line 6) | class InstanceNormalization(GroupNormalization):
method __init__ (line 37) | def __init__(self, **kwargs):
FILE: nobrainer/layers/bernoulli_dropout.py
class BernoulliDropout (line 7) | class BernoulliDropout(nn.Module):
method __init__ (line 36) | def __init__(
method forward (line 55) | def forward(self, x: torch.Tensor) -> torch.Tensor:
method extra_repr (line 66) | def extra_repr(self) -> str:
FILE: nobrainer/layers/concrete_dropout.py
class ConcreteDropout (line 9) | class ConcreteDropout(nn.Module):
method __init__ (line 38) | def __init__(
method p_post (line 64) | def p_post(self) -> torch.Tensor:
method forward (line 68) | def forward(self, x: torch.Tensor) -> torch.Tensor:
method _apply_concrete (line 77) | def _apply_concrete(self, x: torch.Tensor) -> torch.Tensor:
method _kl_divergence (line 94) | def _kl_divergence(self) -> torch.Tensor:
method extra_repr (line 103) | def extra_repr(self) -> str:
FILE: nobrainer/layers/gaussian_dropout.py
class GaussianDropout (line 9) | class GaussianDropout(nn.Module):
method __init__ (line 34) | def __init__(
method forward (line 57) | def forward(self, x: torch.Tensor) -> torch.Tensor:
method extra_repr (line 63) | def extra_repr(self) -> str:
FILE: nobrainer/layers/maxpool4d.py
class MaxPool4D (line 12) | class MaxPool4D(nn.Module):
method __init__ (line 33) | def __init__(
method forward (line 44) | def forward(self, x: torch.Tensor) -> torch.Tensor:
method extra_repr (line 59) | def extra_repr(self) -> str:
FILE: nobrainer/layers/padding.py
class ZeroPadding3DChannels (line 8) | class ZeroPadding3DChannels(nn.Module):
method __init__ (line 20) | def __init__(self, padding: int) -> None:
method forward (line 24) | def forward(self, x: torch.Tensor) -> torch.Tensor:
method extra_repr (line 28) | def extra_repr(self) -> str:
FILE: nobrainer/losses.py
function dice (line 13) | def dice(
function generalized_dice (line 46) | def generalized_dice(
function jaccard (line 63) | def jaccard(
function tversky (line 86) | def tversky(
function elbo (line 120) | def elbo(
function wasserstein (line 154) | def wasserstein(y_true: torch.Tensor, y_pred: torch.Tensor) -> torch.Ten...
function gradient_penalty (line 172) | def gradient_penalty(
function compute_class_weights (line 219) | def compute_class_weights(
function weighted_cross_entropy (line 289) | def weighted_cross_entropy(
class HammingLoss (line 308) | class HammingLoss(torch.nn.Module):
method __init__ (line 323) | def __init__(self, from_logits: bool = True) -> None:
method forward (line 327) | def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.T...
function hamming (line 347) | def hamming(from_logits: bool = True) -> HammingLoss:
class DiceCELoss (line 352) | class DiceCELoss(torch.nn.Module):
method __init__ (line 373) | def __init__(
method forward (line 389) | def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.T...
class FocalLoss (line 409) | class FocalLoss(torch.nn.Module):
method __init__ (line 423) | def __init__(
method forward (line 435) | def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.T...
function focal (line 453) | def focal(gamma: float = 2.0, alpha: torch.Tensor | None = None) -> Foca...
function get (line 477) | def get(name: str):
FILE: nobrainer/metrics.py
function dice_metric (line 13) | def dice_metric(
function generalized_dice_metric (line 34) | def generalized_dice_metric(
function jaccard_metric (line 52) | def jaccard_metric(
function tversky_metric (line 65) | def tversky_metric(
function hausdorff_metric (line 82) | def hausdorff_metric(
function hamming_metric (line 112) | def hamming_metric(reduction: str = "mean") -> "HammingMetric":
class HammingMetric (line 122) | class HammingMetric:
method __init__ (line 125) | def __init__(self, reduction: str = "mean") -> None:
method __call__ (line 128) | def __call__(
function get (line 158) | def get(name: str):
FILE: nobrainer/models/__init__.py
function get (line 53) | def get(name: str):
function available_models (line 86) | def available_models() -> list[str]:
function list_available_models (line 90) | def list_available_models() -> None:
FILE: nobrainer/models/_utils.py
function unpack_batch (line 12) | def unpack_batch(
function load_input (line 54) | def load_input(
function model_supports_mc (line 82) | def model_supports_mc(model: torch.nn.Module) -> bool:
FILE: nobrainer/models/autoencoder.py
class Autoencoder (line 15) | class Autoencoder(nn.Module):
method __init__ (line 34) | def __init__(
method encode (line 97) | def encode(self, x: torch.Tensor) -> torch.Tensor:
method decode (line 101) | def decode(self, z: torch.Tensor) -> torch.Tensor:
method forward (line 105) | def forward(self, x: torch.Tensor) -> torch.Tensor:
function autoencoder (line 109) | def autoencoder(
FILE: nobrainer/models/bayesian/bayesian_meshnet.py
class _BayesConvBNActDrop (line 26) | class _BayesConvBNActDrop(PyroModule):
method __init__ (line 29) | def __init__(
method forward (line 55) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class BayesianMeshNet (line 59) | class BayesianMeshNet(PyroModule):
method __init__ (line 92) | def __init__(
method forward (line 146) | def forward(self, x: torch.Tensor, **kwargs) -> torch.Tensor:
function bayesian_meshnet (line 153) | def bayesian_meshnet(
FILE: nobrainer/models/bayesian/bayesian_vnet.py
class _BayesResBlock (line 24) | class _BayesResBlock(PyroModule):
method __init__ (line 27) | def __init__(self, channels: int, prior_type: str = "standard_normal")...
method forward (line 38) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class _EncoderBlock (line 44) | class _EncoderBlock(PyroModule):
method __init__ (line 47) | def __init__(
method forward (line 59) | def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
class _DecoderBlock (line 65) | class _DecoderBlock(PyroModule):
method __init__ (line 68) | def __init__(
method forward (line 83) | def forward(self, x: torch.Tensor, skip: torch.Tensor) -> torch.Tensor:
class BayesianVNet (line 94) | class BayesianVNet(PyroModule):
method __init__ (line 118) | def __init__(
method forward (line 166) | def forward(self, x: torch.Tensor, **kwargs) -> torch.Tensor:
function bayesian_vnet (line 185) | def bayesian_vnet(
FILE: nobrainer/models/bayesian/kwyk_meshnet.py
class _VWNLayerBernoulli (line 31) | class _VWNLayerBernoulli(nn.Module):
method __init__ (line 34) | def __init__(
method forward (line 54) | def forward(
class _VWNLayerConcrete (line 67) | class _VWNLayerConcrete(nn.Module):
method __init__ (line 70) | def __init__(
method forward (line 95) | def forward(
class KWYKMeshNet (line 107) | class KWYKMeshNet(nn.Module):
method __init__ (line 136) | def __init__(
method forward (line 181) | def forward(
method kl_divergence (line 218) | def kl_divergence(self) -> torch.Tensor:
method concrete_regularization (line 226) | def concrete_regularization(self) -> torch.Tensor:
function kwyk_meshnet (line 235) | def kwyk_meshnet(
FILE: nobrainer/models/bayesian/layers.py
function _kl_normal_normal (line 36) | def _kl_normal_normal(
function _kl_spike_and_slab (line 50) | def _kl_spike_and_slab(
class BayesianConv3d (line 100) | class BayesianConv3d(PyroModule):
method __init__ (line 124) | def __init__(
method weight_sigma (line 196) | def weight_sigma(self) -> torch.Tensor:
method forward (line 199) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class BayesianLinear (line 239) | class BayesianLinear(PyroModule):
method __init__ (line 258) | def __init__(
method weight_sigma (line 312) | def weight_sigma(self) -> torch.Tensor:
method forward (line 315) | def forward(self, x: torch.Tensor) -> torch.Tensor:
FILE: nobrainer/models/bayesian/utils.py
function accumulate_kl (line 11) | def accumulate_kl(model: torch.nn.Module) -> torch.Tensor:
FILE: nobrainer/models/bayesian/vwn_layers.py
class FFGConv3d (line 34) | class FFGConv3d(nn.Module):
method __init__ (line 74) | def __init__(
method kernel_m (line 120) | def kernel_m(self) -> torch.Tensor:
method weight_sigma (line 126) | def weight_sigma(self) -> torch.Tensor:
method forward (line 130) | def forward(self, x: torch.Tensor, mc: bool = True) -> torch.Tensor:
class ConcreteDropout3d (line 166) | class ConcreteDropout3d(nn.Module):
method __init__ (line 185) | def __init__(
method p (line 200) | def p(self) -> torch.Tensor:
method forward (line 204) | def forward(self, x: torch.Tensor, mc: bool = True) -> torch.Tensor:
method kl_divergence (line 234) | def kl_divergence(self) -> torch.Tensor:
method regularization (line 244) | def regularization(self) -> torch.Tensor:
FILE: nobrainer/models/bayesian/warmstart.py
function warmstart_bayesian_from_deterministic (line 17) | def warmstart_bayesian_from_deterministic(
function _transfer_by_name (line 60) | def _transfer_by_name(
function _transfer_by_position (line 84) | def _transfer_by_position(
function _transfer_pair (line 164) | def _transfer_pair(
function warmstart_kwyk_from_deterministic (line 206) | def warmstart_kwyk_from_deterministic(
FILE: nobrainer/models/generative/dcgan.py
class _GenBlock (line 25) | class _GenBlock(nn.Module):
method __init__ (line 28) | def __init__(
method forward (line 45) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class _DiscBlock (line 49) | class _DiscBlock(nn.Module):
method __init__ (line 52) | def __init__(
method forward (line 70) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class _DCGenerator (line 79) | class _DCGenerator(nn.Module):
method __init__ (line 82) | def __init__(self, latent_size: int = 128, n_filters: int = 64) -> None:
method forward (line 101) | def forward(self, z: torch.Tensor) -> torch.Tensor:
class _DCDiscriminator (line 110) | class _DCDiscriminator(nn.Module):
method __init__ (line 113) | def __init__(self, n_filters: int = 64) -> None:
method forward (line 130) | def forward(self, img: torch.Tensor) -> torch.Tensor:
class DCGAN (line 139) | class DCGAN(pl.LightningModule):
method __init__ (line 157) | def __init__(
method _sample_z (line 177) | def _sample_z(self, n: int) -> torch.Tensor:
method training_step (line 180) | def training_step(self, batch: Any, batch_idx: int) -> None:
method configure_optimizers (line 234) | def configure_optimizers(self):
function dcgan (line 244) | def dcgan(
FILE: nobrainer/models/generative/progressivegan.py
function _pixel_norm (line 27) | def _pixel_norm(x: torch.Tensor, eps: float = 1e-8) -> torch.Tensor:
class _ConvBlock (line 32) | class _ConvBlock(nn.Module):
method __init__ (line 33) | def __init__(self, in_ch: int, out_ch: int, use_pixel_norm: bool = Tru...
method forward (line 38) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class _ToRGB (line 45) | class _ToRGB(nn.Module):
method __init__ (line 46) | def __init__(self, in_ch: int) -> None:
method forward (line 50) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class _FromRGB (line 54) | class _FromRGB(nn.Module):
method __init__ (line 55) | def __init__(self, out_ch: int) -> None:
method forward (line 59) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class _Generator (line 68) | class _Generator(nn.Module):
method __init__ (line 71) | def __init__(
method forward (line 104) | def forward(self, z: torch.Tensor) -> torch.Tensor:
class _Discriminator (line 134) | class _Discriminator(nn.Module):
method __init__ (line 137) | def __init__(
method forward (line 170) | def forward(self, img: torch.Tensor) -> torch.Tensor:
class ProgressiveGAN (line 197) | class ProgressiveGAN(pl.LightningModule):
method __init__ (line 220) | def __init__(
method _gradient_penalty (line 253) | def _gradient_penalty(self, real: torch.Tensor, fake: torch.Tensor) ->...
method _sample_z (line 269) | def _sample_z(self, n: int) -> torch.Tensor:
method training_step (line 276) | def training_step(self, batch: Any, batch_idx: int) -> None:
method on_train_batch_end (line 303) | def on_train_batch_end(self, *args: Any, **kwargs: Any) -> None:
method configure_optimizers (line 314) | def configure_optimizers(self):
function progressivegan (line 324) | def progressivegan(
FILE: nobrainer/models/highresnet.py
class _ResBlock (line 17) | class _ResBlock(nn.Module):
method __init__ (line 20) | def __init__(
method forward (line 41) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class _ZeroPadChannels (line 45) | class _ZeroPadChannels(nn.Module):
method __init__ (line 48) | def __init__(self, extra_channels: int) -> None:
method forward (line 52) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class HighResNet (line 56) | class HighResNet(nn.Module):
method __init__ (line 79) | def __init__(
method forward (line 120) | def forward(self, x: torch.Tensor) -> torch.Tensor:
function highresnet (line 137) | def highresnet(
FILE: nobrainer/models/meshnet.py
class _ConvBNActDrop (line 20) | class _ConvBNActDrop(nn.Module):
method __init__ (line 21) | def __init__(
method forward (line 45) | def forward(self, x: torch.Tensor) -> torch.Tensor:
class MeshNet (line 49) | class MeshNet(nn.Module):
method __init__ (line 71) | def __init__(
method forward (line 97) | def forward(self, x: torch.Tensor) -> torch.Tensor:
function meshnet (line 101) | def meshnet(
FILE: nobrainer/models/segformer3d.py
class PatchEmbedding3d (line 23) | class PatchEmbedding3d(nn.Module):
method __init__ (line 40) | def __init__(
method forward (line 58) | def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, int, int, int]:
class EfficientSelfAttention3d (line 67) | class EfficientSelfAttention3d(nn.Module):
method __init__ (line 74) | def __init__(
method forward (line 100) | def forward(
class DWConv3d (line 132) | class DWConv3d(nn.Module):
method __init__ (line 135) | def __init__(self, dim: int = 64) -> None:
method forward (line 140) | def forward(self, x: torch.Tensor, D: int, H: int, W: int) -> torch.Te...
class MixFFN3d (line 147) | class MixFFN3d(nn.Module):
method __init__ (line 150) | def __init__(
method forward (line 161) | def forward(self, x: torch.Tensor, D: int, H: int, W: int) -> torch.Te...
class TransformerBlock3d (line 171) | class TransformerBlock3d(nn.Module):
method __init__ (line 174) | def __init__(
method forward (line 193) | def forward(self, x: torch.Tensor, D: int, H: int, W: int) -> torch.Te...
class MixTransformerEncoder3d (line 204) | class MixTransformerEncoder3d(nn.Module):
method __init__ (line 211) | def __init__(
method forward (line 254) | def forward(self, x: torch.Tensor) -> list[torch.Tensor]:
class SegFormerDecoderHead (line 279) | class SegFormerDecoderHead(nn.Module):
method __init__ (line 286) | def __init__(
method forward (line 307) | def forward(self, features: list[torch.Tensor]) -> torch.Tensor:
class SegFormer3D (line 341) | class SegFormer3D(nn.Module):
method __init__ (line 369) | def __init__(
method forward (line 400) | def forward(self, x: torch.Tensor) -> torch.Tensor:
function segformer3d (line 420) | def segformer3d(
FILE: nobrainer/models/segmentation.py
function unet (line 13) | def unet(
function vnet (line 59) | def vnet(
function attention_unet (line 89) | def attention_unet(
function unetr (line 123) | def unetr(
function swin_unetr (line 172) | def swin_unetr(
function segresnet (line 216) | def segresnet(
FILE: nobrainer/models/simsiam.py
class SimSiam (line 17) | class SimSiam(nn.Module):
method __init__ (line 50) | def __init__(
method _encode (line 83) | def _encode(self, x: torch.Tensor) -> torch.Tensor:
method forward (line 95) | def forward(
method loss (line 119) | def loss(
function simsiam (line 134) | def simsiam(
FILE: nobrainer/prediction.py
function _forward (line 16) | def _forward(model: nn.Module, tensor: torch.Tensor, mc: bool | None = N...
function _pad_to_multiple (line 25) | def _pad_to_multiple(
function _extract_blocks (line 36) | def _extract_blocks(arr: np.ndarray, block_shape: tuple[int, int, int]) ...
function _stitch_blocks (line 48) | def _stitch_blocks(
function strided_patch_positions (line 82) | def strided_patch_positions(
function reassemble_predictions (line 135) | def reassemble_predictions(
function _predict_strided (line 181) | def _predict_strided(
function predict (line 233) | def predict(
function predict_with_uncertainty (line 367) | def predict_with_uncertainty(
FILE: nobrainer/processing/base.py
class BaseEstimator (line 13) | class BaseEstimator:
method __init__ (line 25) | def __init__(
method model (line 34) | def model(self) -> nn.Module:
method save (line 39) | def save(self, save_dir: str | Path) -> None:
method load (line 49) | def load(cls, model_dir: str | Path, multi_gpu: bool = True) -> "BaseE...
method _build_model (line 69) | def _build_model(self) -> nn.Module:
method _restore_from_provenance (line 73) | def _restore_from_provenance(self, prov: dict) -> None:
FILE: nobrainer/processing/croissant.py
function _sha256 (line 12) | def _sha256(path: str | Path) -> str:
function _dataset_checksums (line 21) | def _dataset_checksums(dataset: Any) -> list[dict]:
function write_model_croissant (line 33) | def write_model_croissant(
function write_checkpoint_croissant (line 106) | def write_checkpoint_croissant(
function write_dataset_croissant (line 169) | def write_dataset_croissant(
function validate_croissant (line 206) | def validate_croissant(path: str | Path) -> bool:
FILE: nobrainer/processing/dataset.py
function _load_label_mapping (line 24) | def _load_label_mapping(name_or_path: str) -> Callable:
class _LabelRemap (line 72) | class _LabelRemap:
method __init__ (line 75) | def __init__(self, lookup: dict[int, int]):
method __call__ (line 78) | def __call__(self, x):
class Dataset (line 85) | class Dataset:
method __init__ (line 100) | def __init__(
method from_files (line 121) | def from_files(
method from_zarr (line 164) | def from_zarr(
method batch (line 230) | def batch(self, batch_size: int) -> "Dataset":
method binarize (line 236) | def binarize(self, labels: str | set[int] | Callable | None = None) ->...
method shuffle (line 279) | def shuffle(self, buffer_size: int = 100) -> "Dataset":
method augment (line 285) | def augment(self, profile: str | bool = True) -> "Dataset":
method mix (line 306) | def mix(
method streaming (line 343) | def streaming(self, patches_per_volume: int = 10) -> "Dataset":
method normalize (line 371) | def normalize(self, fn: Callable | None = None) -> "Dataset":
method split (line 377) | def split(self, eval_size: float = 0.1) -> tuple["Dataset", "Dataset"]:
method dataloader (line 396) | def dataloader(self) -> DataLoader:
method batch_size (line 474) | def batch_size(self) -> int:
method block_shape (line 478) | def block_shape(self) -> tuple | None:
method to_croissant (line 481) | def to_croissant(self, output_path: str | Path) -> Path:
function extract_patches (line 488) | def extract_patches(
class PatchDataset (line 570) | class PatchDataset(torch.utils.data.Dataset):
method __init__ (line 611) | def __init__(
method __len__ (line 640) | def __len__(self) -> int:
method __getitem__ (line 643) | def __getitem__(self, idx: int) -> dict[str, torch.Tensor]:
method _apply_binarize (line 672) | def _apply_binarize(self, lbl: np.ndarray) -> np.ndarray:
method _parse_zarr_path (line 689) | def _parse_zarr_path(path: str) -> tuple[str, str, int] | None:
method _get_shape (line 706) | def _get_shape(path: str) -> tuple[int, ...]:
method _get_zarr_store (line 727) | def _get_zarr_store(self, store_path: str):
method _read_region_cached (line 735) | def _read_region_cached(self, path: str, slc: tuple[slice, ...]) -> np...
method _read_region (line 747) | def _read_region(path: str, slc: tuple[slice, ...]) -> np.ndarray:
class MixedDataset (line 770) | class MixedDataset(torch.utils.data.Dataset):
method __init__ (line 786) | def __init__(
method __len__ (line 799) | def __len__(self) -> int:
method __getitem__ (line 802) | def __getitem__(self, idx: int) -> dict:
FILE: nobrainer/processing/generation.py
class Generation (line 16) | class Generation(BaseEstimator):
method __init__ (line 27) | def __init__(
method fit (line 38) | def fit(
method generate (line 78) | def generate(
method save (line 102) | def save(self, save_dir: str | Path) -> None:
method _build_model (line 111) | def _build_model(self) -> nn.Module:
method _restore_from_provenance (line 116) | def _restore_from_provenance(self, prov: dict) -> None:
FILE: nobrainer/processing/segmentation.py
class Segmentation (line 18) | class Segmentation(BaseEstimator):
method __init__ (line 36) | def __init__(
method fit (line 53) | def fit(
method predict (line 163) | def predict(
method evaluate (line 196) | def evaluate(
method _build_model (line 226) | def _build_model(self) -> nn.Module:
method _restore_from_provenance (line 233) | def _restore_from_provenance(self, prov: dict) -> None:
FILE: nobrainer/research/loop.py
class ExperimentResult (line 35) | class ExperimentResult:
function run_loop (line 47) | def run_loop(
function _propose_config (line 181) | def _propose_config(
function _propose_via_llm (line 199) | def _propose_via_llm(
function _propose_random (line 237) | def _propose_random(current: dict[str, Any]) -> dict[str, Any]:
function _parse_config_comment (line 249) | def _parse_config_comment(path: Path) -> dict[str, Any]:
function _patch_config (line 261) | def _patch_config(path: Path, config: dict[str, Any]) -> None:
function _read_val_dice (line 277) | def _read_val_dice(path: Path) -> float | None:
function _has_nan (line 288) | def _has_nan(text: str) -> bool:
function _classify_failure (line 292) | def _classify_failure(stderr: str) -> str:
function _write_summary (line 301) | def _write_summary(
function commit_best_model (line 332) | def commit_best_model(
FILE: nobrainer/research/templates/prepare.py
function prepare (line 47) | def prepare(*, data_dir: str, val_fraction: float, seed: int, output: st...
FILE: nobrainer/research/templates/train_bayesian_vnet.py
function main (line 31) | def main() -> None:
FILE: nobrainer/slurm.py
class SlurmPreemptionHandler (line 39) | class SlurmPreemptionHandler:
method __init__ (line 56) | def __init__(self, sig: signal.Signals = signal.SIGUSR1) -> None:
method _handle (line 66) | def _handle(self, signum: int, frame: Any) -> None:
method is_slurm_job (line 73) | def is_slurm_job() -> bool:
method slurm_info (line 78) | def slurm_info() -> dict[str, str]:
function save_checkpoint (line 92) | def save_checkpoint(
function load_checkpoint (line 148) | def load_checkpoint(
FILE: nobrainer/sr-tests/conftest.py
function sample_data (line 10) | def sample_data():
function train_eval_split (line 17) | def train_eval_split(sample_data):
FILE: nobrainer/sr-tests/test_bayesian_uncertainty.py
class TestBayesianUncertainty (line 19) | class TestBayesianUncertainty:
method test_bayesian_predict_returns_tuple (line 22) | def test_bayesian_predict_returns_tuple(self, train_eval_split, tmp_pa...
method test_variance_nonzero (line 50) | def test_variance_nonzero(self, train_eval_split, tmp_path):
FILE: nobrainer/sr-tests/test_brain_generation.py
class TestBrainGeneration (line 14) | class TestBrainGeneration:
method test_generate_returns_nifti_images (line 17) | def test_generate_returns_nifti_images(self, sample_data):
FILE: nobrainer/sr-tests/test_croissant_metadata.py
class TestCroissantMetadata (line 9) | class TestCroissantMetadata:
method test_segmentation_save_croissant_fields (line 12) | def test_segmentation_save_croissant_fields(self, train_eval_split, tm...
method test_dataset_to_croissant (line 54) | def test_dataset_to_croissant(self, train_eval_split, tmp_path):
FILE: nobrainer/sr-tests/test_dataset_builder.py
class TestDatasetBuilder (line 6) | class TestDatasetBuilder:
method test_from_files_batch_binarize_augment (line 9) | def test_from_files_batch_binarize_augment(self, train_eval_split):
method test_split_sizes (line 32) | def test_split_sizes(self, train_eval_split):
method test_streaming_mode_produces_patches (line 46) | def test_streaming_mode_produces_patches(self, train_eval_split):
FILE: nobrainer/sr-tests/test_extract_patches.py
class TestExtractPatches (line 10) | class TestExtractPatches:
method volume_and_label (line 14) | def volume_and_label(self, train_eval_split):
method test_binarize_true (line 22) | def test_binarize_true(self, volume_and_label):
method test_binarize_set (line 36) | def test_binarize_set(self, volume_and_label):
method test_binarize_callable (line 47) | def test_binarize_callable(self, volume_and_label):
method test_patch_shapes (line 62) | def test_patch_shapes(self, volume_and_label):
FILE: nobrainer/sr-tests/test_kwyk_smoke.py
function _build_dataset (line 37) | def _build_dataset(sample_data):
function _plot_learning_curve (line 49) | def _plot_learning_curve(losses, output_path):
class TestKwykSmoke (line 68) | class TestKwykSmoke:
method test_deterministic_meshnet_train (line 71) | def test_deterministic_meshnet_train(self, sample_data, tmp_path):
method test_bayesian_warmstart_train (line 96) | def test_bayesian_warmstart_train(self, sample_data, tmp_path):
method test_predict_output (line 179) | def test_predict_output(self, sample_data, tmp_path):
FILE: nobrainer/sr-tests/test_raw_pytorch_api.py
class TestRawPyTorchAPI (line 11) | class TestRawPyTorchAPI:
method test_raw_train_predict_cycle (line 14) | def test_raw_train_predict_cycle(self, train_eval_split, tmp_path):
FILE: nobrainer/sr-tests/test_segmentation_estimator.py
class TestSegmentationEstimator (line 10) | class TestSegmentationEstimator:
method test_fit_predict_returns_nifti (line 13) | def test_fit_predict_returns_nifti(self, train_eval_split, tmp_path):
method test_save_creates_croissant (line 38) | def test_save_creates_croissant(self, train_eval_split, tmp_path):
method test_load_roundtrip (line 66) | def test_load_roundtrip(self, train_eval_split, tmp_path):
FILE: nobrainer/sr-tests/test_synthseg_brain.py
class TestSynthSegBrain (line 13) | class TestSynthSegBrain:
method test_generate_from_sample_data (line 16) | def test_generate_from_sample_data(self, sample_data):
method test_two_samples_differ (line 44) | def test_two_samples_differ(self, sample_data):
method test_label_structure_preserved (line 62) | def test_label_structure_preserved(self, sample_data):
FILE: nobrainer/sr-tests/test_zarr_conversion.py
function _mgz_to_nifti (line 14) | def _mgz_to_nifti(mgz_path: str, output_dir: Path) -> Path:
class TestZarrConversion (line 22) | class TestZarrConversion:
method test_nifti_to_zarr (line 25) | def test_nifti_to_zarr(self, train_eval_split, tmp_path):
method test_zarr_to_nifti_roundtrip (line 43) | def test_zarr_to_nifti_roundtrip(self, train_eval_split, tmp_path):
method test_multi_resolution_pyramid (line 64) | def test_multi_resolution_pyramid(self, train_eval_split, tmp_path):
FILE: nobrainer/sr-tests/test_zarr_pipeline.py
class TestZarrPipeline (line 14) | class TestZarrPipeline:
method test_zarr_store_from_sample_data (line 17) | def test_zarr_store_from_sample_data(self, sample_data, tmp_path):
method test_zarr_store_roundtrip (line 60) | def test_zarr_store_roundtrip(self, sample_data, tmp_path):
FILE: nobrainer/tests/contract/test_cli.py
function _help (line 13) | def _help(cmd: list[str]) -> str:
class TestPredictCommand (line 26) | class TestPredictCommand:
method test_predict_help_exits_zero (line 27) | def test_predict_help_exits_zero(self):
method test_predict_has_model_option (line 30) | def test_predict_has_model_option(self):
method test_predict_has_model_type_option (line 34) | def test_predict_has_model_type_option(self):
method test_predict_has_n_classes_option (line 38) | def test_predict_has_n_classes_option(self):
method test_predict_has_device_option (line 42) | def test_predict_has_device_option(self):
method test_predict_has_n_samples_option (line 46) | def test_predict_has_n_samples_option(self):
class TestGenerateCommand (line 51) | class TestGenerateCommand:
method test_generate_help_exits_zero (line 52) | def test_generate_help_exits_zero(self):
method test_generate_has_model_option (line 55) | def test_generate_has_model_option(self):
method test_generate_has_model_type_option (line 59) | def test_generate_has_model_type_option(self):
method test_generate_has_n_samples_option (line 63) | def test_generate_has_n_samples_option(self):
method test_generate_has_latent_size_option (line 67) | def test_generate_has_latent_size_option(self):
class TestConvertTfrecordsCommand (line 72) | class TestConvertTfrecordsCommand:
method test_convert_tfrecords_help_exits_zero (line 73) | def test_convert_tfrecords_help_exits_zero(self):
method test_convert_tfrecords_has_input_option (line 76) | def test_convert_tfrecords_has_input_option(self):
method test_convert_tfrecords_has_output_dir_option (line 80) | def test_convert_tfrecords_has_output_dir_option(self):
class TestResearchCommand (line 85) | class TestResearchCommand:
method test_research_help_exits_zero (line 86) | def test_research_help_exits_zero(self):
method test_research_has_working_dir_option (line 89) | def test_research_has_working_dir_option(self):
method test_research_has_max_experiments_option (line 93) | def test_research_has_max_experiments_option(self):
method test_research_has_budget_hours_option (line 97) | def test_research_has_budget_hours_option(self):
class TestCommitCommand (line 102) | class TestCommitCommand:
method test_commit_help_exits_zero (line 103) | def test_commit_help_exits_zero(self):
method test_commit_has_model_path_option (line 106) | def test_commit_has_model_path_option(self):
method test_commit_has_config_path_option (line 110) | def test_commit_has_config_path_option(self):
method test_commit_has_val_dice_option (line 114) | def test_commit_has_val_dice_option(self):
class TestInfoCommand (line 119) | class TestInfoCommand:
method test_info_help_exits_zero (line 120) | def test_info_help_exits_zero(self):
FILE: nobrainer/tests/gpu/test_bayesian_e2e.py
function _make_sphere_volume (line 20) | def _make_sphere_volume(shape=(64, 64, 64), radius=20):
class TestBayesianEndToEnd (line 34) | class TestBayesianEndToEnd:
method test_bayesian_vnet_overfit_with_uncertainty (line 35) | def test_bayesian_vnet_overfit_with_uncertainty(self):
FILE: nobrainer/tests/gpu/test_gan_e2e.py
function _make_loader (line 19) | def _make_loader(n_samples=64, spatial=4, batch_size=4):
class TestProgressiveGANEndToEnd (line 26) | class TestProgressiveGANEndToEnd:
method test_extended_training_no_nan (line 27) | def test_extended_training_no_nan(self):
method test_generated_output_shape (line 64) | def test_generated_output_shape(self):
FILE: nobrainer/tests/gpu/test_multi_gpu.py
class TestMultiGPU (line 27) | class TestMultiGPU:
method test_ddp_fit_loss_decreases (line 28) | def test_ddp_fit_loss_decreases(self):
method test_multi_gpu_predict_matches_single (line 56) | def test_multi_gpu_predict_matches_single(self):
method test_ddp_speedup (line 82) | def test_ddp_speedup(self):
FILE: nobrainer/tests/gpu/test_predict_e2e.py
function _make_sphere_volume (line 23) | def _make_sphere_volume(shape=(64, 64, 64), radius=20):
class TestPredictEndToEnd (line 37) | class TestPredictEndToEnd:
method test_unet_overfit_dice_above_threshold (line 38) | def test_unet_overfit_dice_above_threshold(self):
method test_predict_output_is_nifti_on_gpu (line 82) | def test_predict_output_is_nifti_on_gpu(self):
FILE: nobrainer/tests/integration/test_datalad_commit.py
function trained_models_dataset (line 22) | def trained_models_dataset(tmp_path):
function model_files (line 33) | def model_files(tmp_path):
class TestCommitBestModelIntegration (line 44) | class TestCommitBestModelIntegration:
method test_files_committed_to_datalad (line 45) | def test_files_committed_to_datalad(self, trained_models_dataset, mode...
method test_datalad_dataset_is_clean_after_commit (line 64) | def test_datalad_dataset_is_clean_after_commit(
method test_git_log_contains_commit_message (line 85) | def test_git_log_contains_commit_message(self, trained_models_dataset,...
method test_directory_structure_follows_convention (line 109) | def test_directory_structure_follows_convention(
method test_model_card_contains_required_metadata (line 131) | def test_model_card_contains_required_metadata(
method test_osf_push_skipped_gracefully_when_no_remote (line 153) | def test_osf_push_skipped_gracefully_when_no_remote(
FILE: nobrainer/tests/integration/test_research_smoke.py
class TestResearchSmoke (line 12) | class TestResearchSmoke:
method test_research_loop_completes_with_budget_seconds (line 13) | def test_research_loop_completes_with_budget_seconds(self, tmp_path):
FILE: nobrainer/tests/unit/test_bayesian_layers.py
class TestBayesianConv3d (line 17) | class TestBayesianConv3d:
method setup_method (line 18) | def setup_method(self):
method _forward (line 21) | def _forward(self, layer, x):
method test_output_shape (line 26) | def test_output_shape(self):
method test_kl_populated_after_forward (line 32) | def test_kl_populated_after_forward(self):
method test_kl_positive (line 39) | def test_kl_positive(self):
method test_kl_varies_across_samples (line 45) | def test_kl_varies_across_samples(self):
method test_prior_laplace (line 56) | def test_prior_laplace(self):
method test_prior_spike_and_slab (line 62) | def test_prior_spike_and_slab(self):
method test_no_bias (line 74) | def test_no_bias(self):
method test_weight_sigma_positive (line 82) | def test_weight_sigma_positive(self):
class TestBayesianLinear (line 92) | class TestBayesianLinear:
method setup_method (line 93) | def setup_method(self):
method _forward (line 96) | def _forward(self, layer, x):
method test_output_shape (line 100) | def test_output_shape(self):
method test_kl_populated (line 106) | def test_kl_populated(self):
method test_no_bias (line 112) | def test_no_bias(self):
method test_prior_laplace (line 119) | def test_prior_laplace(self):
method test_prior_spike_and_slab (line 125) | def test_prior_spike_and_slab(self):
class TestAccumulateKl (line 139) | class TestAccumulateKl:
method setup_method (line 140) | def setup_method(self):
method test_single_layer (line 143) | def test_single_layer(self):
method test_multiple_layers (line 151) | def test_multiple_layers(self):
method test_non_bayesian_model_returns_zero (line 171) | def test_non_bayesian_model_returns_zero(self):
FILE: nobrainer/tests/unit/test_bayesian_models.py
function _run (line 22) | def _run(model, x):
class TestBayesianVNet (line 33) | class TestBayesianVNet:
method setup_method (line 34) | def setup_method(self):
method test_default_construction (line 37) | def test_default_construction(self):
method test_output_shape_single_class (line 41) | def test_output_shape_single_class(self):
method test_output_shape_multi_class (line 47) | def test_output_shape_multi_class(self):
method test_kl_accumulated (line 53) | def test_kl_accumulated(self):
method test_factory_function (line 60) | def test_factory_function(self):
method test_laplace_prior (line 64) | def test_laplace_prior(self):
method test_kl_weight_attribute (line 72) | def test_kl_weight_attribute(self):
class TestBayesianMeshNet (line 82) | class TestBayesianMeshNet:
method setup_method (line 83) | def setup_method(self):
method test_default_construction (line 86) | def test_default_construction(self):
method test_output_shape_single_class (line 90) | def test_output_shape_single_class(self):
method test_output_shape_multi_class (line 96) | def test_output_shape_multi_class(self):
method test_kl_accumulated (line 102) | def test_kl_accumulated(self):
method test_invalid_receptive_field (line 108) | def test_invalid_receptive_field(self):
method test_all_dilation_schedules (line 112) | def test_all_dilation_schedules(self):
method test_factory_function (line 121) | def test_factory_function(self):
method test_kl_weight_attribute (line 125) | def test_kl_weight_attribute(self):
FILE: nobrainer/tests/unit/test_class_weights.py
class TestComputeClassWeights (line 11) | class TestComputeClassWeights:
method test_uniform_distribution (line 12) | def test_uniform_distribution(self, tmp_path):
method test_imbalanced_gives_higher_weight_to_rare (line 25) | def test_imbalanced_gives_higher_weight_to_rare(self, tmp_path):
method test_median_frequency_method (line 36) | def test_median_frequency_method(self, tmp_path):
method test_max_samples (line 52) | def test_max_samples(self, tmp_path):
class TestWeightedCrossEntropy (line 68) | class TestWeightedCrossEntropy:
method test_with_weights (line 69) | def test_with_weights(self):
method test_without_weights (line 78) | def test_without_weights(self):
class TestDiceCELoss (line 86) | class TestDiceCELoss:
method test_3d_segmentation (line 87) | def test_3d_segmentation(self):
method test_with_class_weights (line 95) | def test_with_class_weights(self):
method test_loss_registry (line 103) | def test_loss_registry(self):
FILE: nobrainer/tests/unit/test_croissant.py
function _make_nifti (line 24) | def _make_nifti(shape=(16, 16, 16), tmpdir: Path | None = None) -> str:
function _make_fake_estimator (line 33) | def _make_fake_estimator(model_name="unet"):
function _make_fake_dataset (line 46) | def _make_fake_dataset(tmp_path, n=2):
class TestWriteModelCroissant (line 67) | class TestWriteModelCroissant:
method test_creates_valid_jsonld (line 68) | def test_creates_valid_jsonld(self, tmp_path):
method test_required_provenance_fields (line 86) | def test_required_provenance_fields(self, tmp_path):
method test_provenance_model_architecture (line 105) | def test_provenance_model_architecture(self, tmp_path):
method test_sha256_checksums_for_source_datasets (line 112) | def test_sha256_checksums_for_source_datasets(self, tmp_path):
class TestSHA256 (line 125) | class TestSHA256:
method test_checksum_computed (line 126) | def test_checksum_computed(self, tmp_path):
method test_deterministic (line 133) | def test_deterministic(self, tmp_path):
class TestValidateCroissant (line 144) | class TestValidateCroissant:
method test_returns_true_on_valid (line 145) | def test_returns_true_on_valid(self, tmp_path):
class TestWriteDatasetCroissant (line 158) | class TestWriteDatasetCroissant:
method test_writes_dataset_metadata (line 159) | def test_writes_dataset_metadata(self, tmp_path):
method test_dataset_info_present (line 169) | def test_dataset_info_present(self, tmp_path):
method test_distribution_has_sha256 (line 178) | def test_distribution_has_sha256(self, tmp_path):
FILE: nobrainer/tests/unit/test_dataset.py
function _make_nifti (line 17) | def _make_nifti(shape=(16, 16, 16), tmpdir: Path | None = None) -> str:
class TestGetDataset (line 33) | class TestGetDataset:
method test_batch_shape_image_only (line 34) | def test_batch_shape_image_only(self, tmp_path):
method test_batch_shape_with_labels (line 49) | def test_batch_shape_with_labels(self, tmp_path):
method test_mismatch_raises (line 64) | def test_mismatch_raises(self, tmp_path):
method test_augment_flag (line 75) | def test_augment_flag(self, tmp_path):
method test_returns_dataloader (line 88) | def test_returns_dataloader(self, tmp_path):
FILE: nobrainer/tests/unit/test_dataset_builder.py
function _make_nifti (line 19) | def _make_nifti(shape=(16, 16, 16), tmpdir: Path | None = None) -> str:
function _make_file_pairs (line 28) | def _make_file_pairs(n, shape, tmpdir):
class TestFromFiles (line 43) | class TestFromFiles:
method test_tuple_format (line 44) | def test_tuple_format(self, tmp_path):
method test_dict_format (line 51) | def test_dict_format(self, tmp_path):
method test_volume_shape_detected (line 58) | def test_volume_shape_detected(self, tmp_path):
class TestFluentChaining (line 65) | class TestFluentChaining:
method test_batch_returns_self (line 66) | def test_batch_returns_self(self, tmp_path):
method test_shuffle_returns_self (line 72) | def test_shuffle_returns_self(self, tmp_path):
method test_augment_returns_self (line 78) | def test_augment_returns_self(self, tmp_path):
method test_chaining (line 84) | def test_chaining(self, tmp_path):
class TestSplit (line 95) | class TestSplit:
method test_split_sizes (line 96) | def test_split_sizes(self, tmp_path):
method test_split_returns_datasets (line 104) | def test_split_returns_datasets(self, tmp_path):
class TestDataloader (line 112) | class TestDataloader:
method test_returns_dataloader (line 113) | def test_returns_dataloader(self, tmp_path):
method test_batch_produces_data (line 120) | def test_batch_produces_data(self, tmp_path):
class TestMetadataProperties (line 130) | class TestMetadataProperties:
method test_batch_size (line 131) | def test_batch_size(self, tmp_path):
method test_block_shape (line 136) | def test_block_shape(self, tmp_path):
method test_volume_shape (line 141) | def test_volume_shape(self, tmp_path):
method test_n_classes (line 146) | def test_n_classes(self, tmp_path):
class TestToCroissant (line 152) | class TestToCroissant:
method test_writes_valid_jsonld (line 153) | def test_writes_valid_jsonld(self, tmp_path):
method test_has_dataset_info (line 164) | def test_has_dataset_info(self, tmp_path):
FILE: nobrainer/tests/unit/test_datasets_openneuro.py
class TestWriteManifest (line 11) | class TestWriteManifest:
method test_creates_csv (line 14) | def test_creates_csv(self, tmp_path):
method test_split_ratios (line 36) | def test_split_ratios(self, tmp_path):
method test_dataset_id_column (line 56) | def test_dataset_id_column(self, tmp_path):
class TestGlobDataset (line 77) | class TestGlobDataset:
method test_finds_files (line 80) | def test_finds_files(self, tmp_path):
method test_no_matches (line 91) | def test_no_matches(self, tmp_path):
class TestExtractSubjectId (line 98) | class TestExtractSubjectId:
method test_from_bids_path (line 99) | def test_from_bids_path(self, tmp_path):
method test_from_filename (line 105) | def test_from_filename(self):
class TestFileOk (line 112) | class TestFileOk:
method test_real_file (line 113) | def test_real_file(self, tmp_path):
method test_empty_file (line 120) | def test_empty_file(self, tmp_path):
method test_missing_file (line 127) | def test_missing_file(self, tmp_path):
class TestImportGuard (line 133) | class TestImportGuard:
method test_install_without_datalad (line 136) | def test_install_without_datalad(self):
FILE: nobrainer/tests/unit/test_estimator_generation.py
class _FakeDataset (line 27) | class _FakeDataset:
method __init__ (line 30) | def __init__(self, loader):
method dataloader (line 35) | def dataloader(self):
function _make_fake_dataset (line 39) | def _make_fake_dataset(n=4, spatial=SPATIAL, batch_size=2):
class TestGenerationFit (line 51) | class TestGenerationFit:
method test_fit_returns_self (line 52) | def test_fit_returns_self(self):
method test_model_created_after_fit (line 64) | def test_model_created_after_fit(self):
class TestGenerationGenerate (line 76) | class TestGenerationGenerate:
method test_generate_returns_list_of_nifti (line 77) | def test_generate_returns_list_of_nifti(self):
class TestGenerationSave (line 94) | class TestGenerationSave:
method test_save_creates_croissant (line 95) | def test_save_creates_croissant(self, tmp_path):
FILE: nobrainer/tests/unit/test_estimator_segmentation.py
function _make_nifti (line 24) | def _make_nifti(shape=(16, 16, 16), tmpdir: Path | None = None) -> str:
function _make_tiny_loader (line 33) | def _make_tiny_loader(n=4, spatial=SPATIAL, n_classes=N_CLASSES, batch_s...
class _FakeDataset (line 41) | class _FakeDataset:
method __init__ (line 44) | def __init__(self, loader, block_shape, volume_shape, n_classes):
method block_shape (line 51) | def block_shape(self):
method dataloader (line 55) | def dataloader(self):
function _make_fake_dataset (line 59) | def _make_fake_dataset(n=4, spatial=SPATIAL, n_classes=N_CLASSES, batch_...
class TestSegmentationFit (line 75) | class TestSegmentationFit:
method test_fit_returns_self (line 76) | def test_fit_returns_self(self):
method test_model_created_after_fit (line 87) | def test_model_created_after_fit(self):
class TestSegmentationPredict (line 99) | class TestSegmentationPredict:
method test_predict_returns_nifti (line 100) | def test_predict_returns_nifti(self, tmp_path):
class TestSegmentationSaveLoad (line 117) | class TestSegmentationSaveLoad:
method test_save_creates_files (line 118) | def test_save_creates_files(self, tmp_path):
method test_croissant_provenance_fields (line 132) | def test_croissant_provenance_fields(self, tmp_path):
method test_load_roundtrip (line 151) | def test_load_roundtrip(self, tmp_path):
FILE: nobrainer/tests/unit/test_experiment.py
class TestExperimentTracker (line 10) | class TestExperimentTracker:
method test_local_logging (line 11) | def test_local_logging(self, tmp_path):
method test_callback (line 33) | def test_callback(self, tmp_path):
method test_no_wandb_by_default (line 49) | def test_no_wandb_by_default(self, tmp_path):
FILE: nobrainer/tests/unit/test_generative.py
function _tiny_loader (line 16) | def _tiny_loader(batch_size: int = 2, spatial: int = 4) -> DataLoader:
class TestProgressiveGAN (line 27) | class TestProgressiveGAN:
method test_construction (line 28) | def test_construction(self):
method test_factory_function (line 34) | def test_factory_function(self):
method test_generator_output_shape (line 40) | def test_generator_output_shape(self):
method test_discriminator_output_shape (line 51) | def test_discriminator_output_shape(self):
method test_training_step_losses_finite (line 60) | def test_training_step_losses_finite(self):
method test_alpha_schedule (line 81) | def test_alpha_schedule(self):
class TestDCGAN (line 99) | class TestDCGAN:
method test_construction (line 100) | def test_construction(self):
method test_factory_function (line 104) | def test_factory_function(self):
method test_generator_output_shape (line 108) | def test_generator_output_shape(self):
method test_discriminator_output_shape (line 115) | def test_discriminator_output_shape(self):
method test_training_step_losses_finite (line 121) | def test_training_step_losses_finite(self):
method test_configure_optimizers (line 135) | def test_configure_optimizers(self):
FILE: nobrainer/tests/unit/test_gpu.py
class TestGetDevice (line 10) | class TestGetDevice:
method test_returns_torch_device (line 11) | def test_returns_torch_device(self):
method test_device_type_known (line 15) | def test_device_type_known(self):
class TestGpuCount (line 20) | class TestGpuCount:
method test_returns_int (line 21) | def test_returns_int(self):
class TestGpuInfo (line 27) | class TestGpuInfo:
method test_returns_list (line 28) | def test_returns_list(self):
class TestScaleForMultiGpu (line 37) | class TestScaleForMultiGpu:
method test_no_gpu_returns_base (line 38) | def test_no_gpu_returns_base(self):
method test_simple_division (line 46) | def test_simple_division(self):
FILE: nobrainer/tests/unit/test_io_weights.py
class _SimplePT (line 17) | class _SimplePT(nn.Module):
method __init__ (line 20) | def __init__(self):
method forward (line 25) | def forward(self, x):
function _write_synthetic_h5 (line 29) | def _write_synthetic_h5(path: str, model: nn.Module) -> None:
class TestConvertWeights (line 48) | class TestConvertWeights:
method test_returns_dict (line 49) | def test_returns_dict(self, tmp_path):
method test_output_pth_written (line 58) | def test_output_pth_written(self, tmp_path):
method test_state_dict_keys_preserved (line 69) | def test_state_dict_keys_preserved(self, tmp_path):
FILE: nobrainer/tests/unit/test_io_zarr.py
function _make_nifti (line 14) | def _make_nifti(tmp_path, shape=(32, 32, 32)):
class TestNiftiToZarr (line 24) | class TestNiftiToZarr:
method test_creates_valid_store (line 25) | def test_creates_valid_store(self, tmp_path):
method test_provenance_stored (line 33) | def test_provenance_stored(self, tmp_path):
method test_multi_resolution_pyramid (line 44) | def test_multi_resolution_pyramid(self, tmp_path):
class TestZarrToNifti (line 57) | class TestZarrToNifti:
method test_round_trip_shape (line 58) | def test_round_trip_shape(self, tmp_path):
method test_round_trip_data (line 66) | def test_round_trip_data(self, tmp_path):
method test_round_trip_level1 (line 78) | def test_round_trip_level1(self, tmp_path):
FILE: nobrainer/tests/unit/test_layers.py
function x3d (line 22) | def x3d():
function x4d (line 27) | def x4d():
class TestBernoulliDropout (line 36) | class TestBernoulliDropout:
method test_forward_shape (line 37) | def test_forward_shape(self, x3d):
method test_passthrough_eval_scale (line 43) | def test_passthrough_eval_scale(self, x3d):
method test_passthrough_eval_noscale (line 52) | def test_passthrough_eval_noscale(self, x3d):
method test_gradient_flow (line 62) | def test_gradient_flow(self, x3d):
method test_invalid_rate (line 71) | def test_invalid_rate(self):
method test_mc_applies_in_eval (line 75) | def test_mc_applies_in_eval(self, x3d):
class TestConcreteDropout (line 90) | class TestConcreteDropout:
method test_forward_shape (line 91) | def test_forward_shape(self, x3d):
method test_kl_positive (line 98) | def test_kl_positive(self, x3d):
method test_gradient_flow (line 105) | def test_gradient_flow(self, x3d):
method test_p_post_clipped (line 116) | def test_p_post_clipped(self, x3d):
method test_passthrough_eval (line 122) | def test_passthrough_eval(self, x3d):
class TestGaussianDropout (line 137) | class TestGaussianDropout:
method test_forward_shape (line 138) | def test_forward_shape(self, x3d):
method test_passthrough_eval (line 144) | def test_passthrough_eval(self, x3d):
method test_gradient_flow (line 150) | def test_gradient_flow(self, x3d):
method test_mc_in_eval (line 158) | def test_mc_in_eval(self, x3d):
method test_invalid_rate (line 167) | def test_invalid_rate(self):
class TestMaxPool4D (line 177) | class TestMaxPool4D:
method test_forward_shape (line 178) | def test_forward_shape(self, x4d):
method test_wrong_ndim (line 184) | def test_wrong_ndim(self):
method test_pool_v (line 190) | def test_pool_v(self):
method test_gradient_flow (line 196) | def test_gradient_flow(self, x4d):
FILE: nobrainer/tests/unit/test_losses.py
function _binary_pair (line 13) | def _binary_pair(batch=2, spatial=16):
function _multiclass_pair (line 20) | def _multiclass_pair(batch=2, n_classes=3, spatial=8):
class TestDiceLoss (line 36) | class TestDiceLoss:
method test_returns_scalar (line 37) | def test_returns_scalar(self):
method test_non_negative (line 43) | def test_non_negative(self):
method test_perfect_prediction_near_zero (line 49) | def test_perfect_prediction_near_zero(self):
class TestGeneralizedDiceLoss (line 61) | class TestGeneralizedDiceLoss:
method test_returns_scalar (line 62) | def test_returns_scalar(self):
method test_non_negative (line 68) | def test_non_negative(self):
class TestJaccardLoss (line 80) | class TestJaccardLoss:
method test_returns_scalar (line 81) | def test_returns_scalar(self):
method test_non_negative (line 87) | def test_non_negative(self):
class TestTverskyLoss (line 99) | class TestTverskyLoss:
method test_returns_scalar (line 100) | def test_returns_scalar(self):
method test_non_negative (line 106) | def test_non_negative(self):
class TestStubs (line 118) | class TestStubs:
method test_elbo_returns_tensor (line 119) | def test_elbo_returns_tensor(self):
method test_wasserstein_returns_tensor (line 129) | def test_wasserstein_returns_tensor(self):
class TestGet (line 144) | class TestGet:
method test_known_loss (line 145) | def test_known_loss(self):
method test_unknown_raises (line 149) | def test_unknown_raises(self):
FILE: nobrainer/tests/unit/test_metrics.py
function _onehot_pair (line 13) | def _onehot_pair(batch=2, n_classes=2, spatial=8):
class TestDiceMetric (line 31) | class TestDiceMetric:
method test_instantiation (line 32) | def test_instantiation(self):
method test_perfect_score (line 36) | def test_perfect_score(self):
method test_output_scalar (line 44) | def test_output_scalar(self):
class TestJaccardMetric (line 57) | class TestJaccardMetric:
method test_instantiation (line 58) | def test_instantiation(self):
method test_perfect_score (line 62) | def test_perfect_score(self):
class TestHausdorffMetric (line 76) | class TestHausdorffMetric:
method test_instantiation (line 77) | def test_instantiation(self):
method test_perfect_score_zero (line 81) | def test_perfect_score_zero(self):
class TestGet (line 95) | class TestGet:
method test_known_metric (line 96) | def test_known_metric(self):
method test_unknown_raises (line 100) | def test_unknown_raises(self):
FILE: nobrainer/tests/unit/test_model_interface.py
class TestUnifiedForward (line 11) | class TestUnifiedForward:
method test_meshnet (line 14) | def test_meshnet(self):
method test_unet (line 20) | def test_unet(self):
method test_segformer3d (line 26) | def test_segformer3d(self):
class TestMcSupport (line 35) | class TestMcSupport:
method test_kwyk_meshnet_supports_mc (line 38) | def test_kwyk_meshnet_supports_mc(self):
method test_bayesian_meshnet_supports_mc (line 48) | def test_bayesian_meshnet_supports_mc(self):
method test_regular_model_no_mc (line 60) | def test_regular_model_no_mc(self):
method test_forward_helper_uses_explicit_check (line 64) | def test_forward_helper_uses_explicit_check(self):
FILE: nobrainer/tests/unit/test_model_registry.py
class TestSwinUNETR (line 10) | class TestSwinUNETR:
method test_instantiate (line 11) | def test_instantiate(self):
method test_output_shape (line 15) | def test_output_shape(self):
class TestSegResNet (line 25) | class TestSegResNet:
method test_instantiate (line 26) | def test_instantiate(self):
method test_output_shape (line 30) | def test_output_shape(self):
class TestRegistryAccess (line 37) | class TestRegistryAccess:
method test_swin_unetr_in_registry (line 38) | def test_swin_unetr_in_registry(self):
method test_segresnet_in_registry (line 43) | def test_segresnet_in_registry(self):
FILE: nobrainer/tests/unit/test_models_segmentation.py
function _grad_check (line 18) | def _grad_check(model: torch.nn.Module, inp: torch.Tensor) -> bool:
class TestUNet (line 35) | class TestUNet:
method test_output_shape_binary (line 36) | def test_output_shape_binary(self):
method test_output_shape_multiclass (line 41) | def test_output_shape_multiclass(self):
method test_gradient_flow (line 46) | def test_gradient_flow(self):
method test_get_registry (line 51) | def test_get_registry(self):
class TestVNet (line 61) | class TestVNet:
method test_output_shape (line 62) | def test_output_shape(self):
method test_gradient_flow (line 68) | def test_gradient_flow(self):
class TestUNETR (line 79) | class TestUNETR:
method test_output_shape (line 80) | def test_output_shape(self):
class TestAttentionUNet (line 96) | class TestAttentionUNet:
method test_output_shape (line 97) | def test_output_shape(self):
method test_gradient_flow (line 106) | def test_gradient_flow(self):
class TestMeshNet (line 121) | class TestMeshNet:
method test_output_shape_binary (line 122) | def test_output_shape_binary(self):
method test_output_shape_multiclass (line 127) | def test_output_shape_multiclass(self):
method test_receptive_field_37 (line 132) | def test_receptive_field_37(self):
method test_receptive_field_129 (line 137) | def test_receptive_field_129(self):
method test_invalid_rf (line 142) | def test_invalid_rf(self):
method test_gradient_flow (line 146) | def test_gradient_flow(self):
class TestHighResNet (line 157) | class TestHighResNet:
method test_output_shape_binary (line 158) | def test_output_shape_binary(self):
method test_output_shape_multiclass (line 163) | def test_output_shape_multiclass(self):
method test_gradient_flow (line 168) | def test_gradient_flow(self):
class TestAutoencoder (line 179) | class TestAutoencoder:
method test_output_shape (line 181) | def test_output_shape(self):
method test_encode_shape (line 187) | def test_encode_shape(self):
method test_gradient_flow (line 193) | def test_gradient_flow(self):
class TestSimSiam (line 204) | class TestSimSiam:
method test_forward_shapes (line 206) | def test_forward_shapes(self):
method test_loss_negative_range (line 214) | def test_loss_negative_range(self):
method test_gradient_flow (line 223) | def test_gradient_flow(self):
FILE: nobrainer/tests/unit/test_prediction.py
class _IdentityModel (line 19) | class _IdentityModel(nn.Module):
method __init__ (line 22) | def __init__(self):
method forward (line 26) | def forward(self, x):
class _MultiClassModel (line 30) | class _MultiClassModel(nn.Module):
method __init__ (line 33) | def __init__(self):
method forward (line 37) | def forward(self, x):
function _make_nifti (line 41) | def _make_nifti(shape=(32, 32, 32), tmp_path=None) -> str:
class TestPredict (line 56) | class TestPredict:
method test_returns_nifti (line 57) | def test_returns_nifti(self, tmp_path):
method test_output_shape_matches_input (line 63) | def test_output_shape_matches_input(self, tmp_path):
method test_ndarray_input (line 69) | def test_ndarray_input(self):
method test_nifti_image_input (line 75) | def test_nifti_image_input(self):
method test_affine_preserved (line 82) | def test_affine_preserved(self, tmp_path):
method test_return_probabilities (line 89) | def test_return_probabilities(self):
method test_non_block_aligned_input (line 98) | def test_non_block_aligned_input(self):
class TestPredictWithUncertainty (line 111) | class TestPredictWithUncertainty:
method test_returns_three_niftis (line 112) | def test_returns_three_niftis(self):
method test_output_shapes_match_input (line 122) | def test_output_shapes_match_input(self):
method test_variance_nonnegative (line 132) | def test_variance_nonnegative(self):
method test_entropy_nonnegative (line 140) | def test_entropy_nonnegative(self):
FILE: nobrainer/tests/unit/test_research_commit.py
function _make_model_files (line 12) | def _make_model_files(tmp_path: Path) -> tuple[Path, Path]:
class TestCommitBestModel (line 21) | class TestCommitBestModel:
method test_directory_structure_created (line 22) | def test_directory_structure_created(self, tmp_path):
method test_model_card_contains_required_fields (line 52) | def test_model_card_contains_required_fields(self, tmp_path):
method test_model_version_dict_fields (line 80) | def test_model_version_dict_fields(self, tmp_path):
method test_datalad_commit_message_in_result (line 108) | def test_datalad_commit_message_in_result(self, tmp_path):
method test_result_contains_osf_url_key (line 132) | def test_result_contains_osf_url_key(self, tmp_path):
method test_datalad_not_installed_raises_import_error (line 154) | def test_datalad_not_installed_raises_import_error(self, tmp_path):
FILE: nobrainer/tests/unit/test_research_loop.py
function _write_train_script (line 27) | def _write_train_script(path: Path, config: dict | None = None) -> None:
function _write_val_dice (line 35) | def _write_val_dice(path: Path, val_dice: float) -> None:
class TestHelpers (line 44) | class TestHelpers:
method test_parse_config_comment (line 45) | def test_parse_config_comment(self, tmp_path):
method test_parse_config_comment_missing (line 51) | def test_parse_config_comment_missing(self, tmp_path):
method test_patch_config (line 57) | def test_patch_config(self, tmp_path):
method test_patch_config_adds_when_missing (line 66) | def test_patch_config_adds_when_missing(self, tmp_path):
method test_read_val_dice_valid (line 73) | def test_read_val_dice_valid(self, tmp_path):
method test_read_val_dice_missing (line 77) | def test_read_val_dice_missing(self, tmp_path):
method test_has_nan (line 80) | def test_has_nan(self):
method test_classify_failure_oom (line 84) | def test_classify_failure_oom(self):
method test_classify_failure_nan (line 87) | def test_classify_failure_nan(self):
method test_classify_failure_generic (line 90) | def test_classify_failure_generic(self):
method test_write_summary (line 93) | def test_write_summary(self, tmp_path):
class TestRunLoop (line 109) | class TestRunLoop:
method test_keep_improved_experiment (line 110) | def test_keep_improved_experiment(self, tmp_path):
method test_revert_on_degraded (line 139) | def test_revert_on_degraded(self, tmp_path):
method test_failure_handling_reverts (line 175) | def test_failure_handling_reverts(self, tmp_path):
method test_run_summary_written (line 198) | def test_run_summary_written(self, tmp_path):
method test_missing_train_script_raises (line 217) | def test_missing_train_script_raises(self, tmp_path):
method test_budget_seconds_terminates_quickly (line 221) | def test_budget_seconds_terminates_quickly(self, tmp_path):
FILE: nobrainer/tests/unit/test_segformer3d.py
class TestSegFormer3DShapes (line 11) | class TestSegFormer3DShapes:
method test_output_shape_32 (line 12) | def test_output_shape_32(self):
method test_output_shape_64 (line 20) | def test_output_shape_64(self):
method test_batch_size_2 (line 28) | def test_batch_size_2(self):
class TestSegFormer3DParams (line 37) | class TestSegFormer3DParams:
method test_default_param_count (line 38) | def test_default_param_count(self):
method test_tiny_param_count (line 44) | def test_tiny_param_count(self):
method test_base_param_count (line 50) | def test_base_param_count(self):
class TestSegFormer3DRegistry (line 57) | class TestSegFormer3DRegistry:
method test_accessible_via_get (line 58) | def test_accessible_via_get(self):
method test_in_available_models (line 63) | def test_in_available_models(self):
method test_factory_defaults (line 68) | def test_factory_defaults(self):
FILE: nobrainer/tests/unit/test_slurm.py
class TestSlurmPreemptionHandler (line 10) | class TestSlurmPreemptionHandler:
method test_initial_state (line 11) | def test_initial_state(self):
method test_is_slurm_job (line 15) | def test_is_slurm_job(self):
class TestCheckpoint (line 20) | class TestCheckpoint:
method test_save_and_load (line 21) | def test_save_and_load(self, tmp_path):
method test_load_no_checkpoint (line 39) | def test_load_no_checkpoint(self, tmp_path):
method test_model_weights_restored (line 45) | def test_model_weights_restored(self, tmp_path):
FILE: nobrainer/tests/unit/test_stride_patches.py
class TestStridedPatchPositions (line 10) | class TestStridedPatchPositions:
method test_non_overlapping_count (line 11) | def test_non_overlapping_count(self):
method test_overlapping_more_patches (line 16) | def test_overlapping_more_patches(self):
method test_patch_shapes_valid (line 22) | def test_patch_shapes_valid(self):
method test_stride_equals_block_default (line 33) | def test_stride_equals_block_default(self):
class TestReassemblePredictions (line 39) | class TestReassemblePredictions:
method test_non_overlapping_perfect_reconstruction (line 40) | def test_non_overlapping_perfect_reconstruction(self):
method test_overlapping_average (line 60) | def test_overlapping_average(self):
method test_output_shape (line 81) | def test_output_shape(self):
FILE: nobrainer/tests/unit/test_synthseg.py
function _make_label_map (line 13) | def _make_label_map(tmp_path: Path, shape=(32, 32, 32)) -> str:
class TestTissueClasses (line 27) | class TestTissueClasses:
method test_all_50class_labels_covered (line 28) | def test_all_50class_labels_covered(self):
method test_no_label_in_multiple_classes (line 41) | def test_no_label_in_multiple_classes(self):
class TestGMMGrouping (line 53) | class TestGMMGrouping:
method test_within_class_same_distribution (line 54) | def test_within_class_same_distribution(self, tmp_path):
method test_different_classes_differ (line 83) | def test_different_classes_differ(self, tmp_path):
method test_two_runs_produce_different_intensities (line 108) | def test_two_runs_produce_different_intensities(self, tmp_path):
class TestSpatialAugmentation (line 125) | class TestSpatialAugmentation:
method test_elastic_changes_geometry (line 126) | def test_elastic_changes_geometry(self, tmp_path):
method test_label_nearest_neighbor (line 151) | def test_label_nearest_neighbor(self, tmp_path):
method test_flipping_swaps_lr (line 179) | def test_flipping_swaps_lr(self, tmp_path):
class TestResolutionRandomization (line 210) | class TestResolutionRandomization:
method test_blurs_image (line 211) | def test_blurs_image(self, tmp_path):
class TestOutputFormat (line 248) | class TestOutputFormat:
method test_returns_dict_with_correct_keys (line 249) | def test_returns_dict_with_correct_keys(self, tmp_path):
method test_correct_length (line 262) | def test_correct_length(self, tmp_path):
class TestMixedDataset (line 270) | class TestMixedDataset:
method test_mix_ratio (line 271) | def test_mix_ratio(self, tmp_path):
method test_dataset_mix_method (line 297) | def test_dataset_mix_method(self, tmp_path):
FILE: nobrainer/tests/unit/test_training.py
function _make_loader (line 12) | def _make_loader(n=8, spatial=8, n_classes=2, batch_size=2):
function _make_model (line 20) | def _make_model(n_classes=2):
class TestFit (line 29) | class TestFit:
method test_returns_correct_keys (line 30) | def test_returns_correct_keys(self):
method test_loss_decreases (line 44) | def test_loss_decreases(self):
method test_checkpoint_created (line 60) | def test_checkpoint_created(self, tmp_path):
method test_checkpoint_croissant_content (line 75) | def test_checkpoint_croissant_content(self, tmp_path):
method test_epochs_completed (line 96) | def test_epochs_completed(self):
method test_dict_batch_format (line 108) | def test_dict_batch_format(self):
FILE: nobrainer/tests/unit/test_training_convergence.py
function _run_epochs (line 26) | def _run_epochs(model: torch.nn.Module, seed: int = 42) -> list[float]:
class TestTrainingConvergence (line 45) | class TestTrainingConvergence:
method test_unet_loss_decreases (line 48) | def test_unet_loss_decreases(self):
method test_vnet_loss_decreases (line 54) | def test_vnet_loss_decreases(self):
method test_attention_unet_loss_decreases (line 60) | def test_attention_unet_loss_decreases(self):
method test_meshnet_loss_decreases (line 66) | def test_meshnet_loss_decreases(self):
method test_highresnet_loss_decreases (line 72) | def test_highresnet_loss_decreases(self):
FILE: nobrainer/tests/unit/test_transform_pipeline.py
function _identity (line 8) | def _identity(data):
function _augment (line 14) | def _augment(data):
class TestAugmentation (line 20) | class TestAugmentation:
method test_wraps_transform (line 21) | def test_wraps_transform(self):
method test_repr (line 27) | def test_repr(self):
class TestTrainableCompose (line 32) | class TestTrainableCompose:
method test_train_mode_runs_all (line 33) | def test_train_mode_runs_all(self):
method test_predict_mode_skips_augmentation (line 39) | def test_predict_mode_skips_augmentation(self):
method test_default_mode_is_train (line 45) | def test_default_mode_is_train(self):
method test_mode_setter (line 50) | def test_mode_setter(self):
method test_multiple_augmentations_skipped (line 56) | def test_multiple_augmentations_skipped(self):
method test_train_mode_runs_multiple_augmentations (line 69) | def test_train_mode_runs_multiple_augmentations(self):
method test_empty_pipeline (line 82) | def test_empty_pipeline(self):
class TestAugmentationProfiles (line 88) | class TestAugmentationProfiles:
method test_none_returns_empty (line 89) | def test_none_returns_empty(self):
method test_standard_returns_augmentations (line 95) | def test_standard_returns_augmentations(self):
method test_all_profiles_valid (line 102) | def test_all_profiles_valid(self):
method test_unknown_profile_raises (line 109) | def test_unknown_profile_raises(self):
FILE: nobrainer/tests/unit/test_vwn_layers.py
class TestFFGConv3d (line 10) | class TestFFGConv3d:
method test_output_shape (line 11) | def test_output_shape(self):
method test_deterministic_mode (line 17) | def test_deterministic_mode(self):
method test_stochastic_mode_varies (line 25) | def test_stochastic_mode_varies(self):
method test_kl_populated_after_mc (line 33) | def test_kl_populated_after_mc(self):
method test_kernel_m_shape (line 39) | def test_kernel_m_shape(self):
method test_no_bias (line 43) | def test_no_bias(self):
method test_sigma_positive (line 50) | def test_sigma_positive(self):
class TestConcreteDropout3d (line 55) | class TestConcreteDropout3d:
method test_output_shape (line 56) | def test_output_shape(self):
method test_deterministic_scales (line 62) | def test_deterministic_scales(self):
method test_p_in_range (line 71) | def test_p_in_range(self):
method test_regularization_positive (line 76) | def test_regularization_positive(self):
class TestKWYKMeshNet (line 82) | class TestKWYKMeshNet:
method test_bernoulli_variant (line 83) | def test_bernoulli_variant(self):
method test_concrete_variant (line 96) | def test_concrete_variant(self):
method test_kl_divergence (line 109) | def test_kl_divergence(self):
method test_concrete_regularization (line 118) | def test_concrete_regularization(self):
method test_deterministic_forward (line 130) | def test_deterministic_forward(self):
method test_factory_function (line 140) | def test_factory_function(self):
FILE: nobrainer/tests/unit/test_zarr_dataset.py
function _make_zarr_pair (line 16) | def _make_zarr_pair(tmp_path, shape=(32, 32, 32)):
class TestIsZarrPath (line 32) | class TestIsZarrPath:
method test_zarr_extension (line 33) | def test_zarr_extension(self):
method test_non_zarr (line 37) | def test_non_zarr(self):
class TestZarrDataset (line 42) | class TestZarrDataset:
method test_returns_dict_with_image (line 43) | def test_returns_dict_with_image(self, tmp_path):
method test_image_shape_has_channel (line 50) | def test_image_shape_has_channel(self, tmp_path):
method test_returns_label_when_provided (line 57) | def test_returns_label_when_provided(self, tmp_path):
method test_batch_from_dataloader (line 64) | def test_batch_from_dataloader(self, tmp_path):
method test_multi_resolution_level (line 73) | def test_multi_resolution_level(self, tmp_path):
FILE: nobrainer/tests/unit/test_zarr_store.py
function _make_nifti_pair (line 12) | def _make_nifti_pair(tmp_path, idx, shape=(32, 32, 32)):
class TestCreateZarrStore (line 25) | class TestCreateZarrStore:
method test_creates_store (line 26) | def test_creates_store(self, tmp_path):
method test_stacked_4d_layout (line 37) | def test_stacked_4d_layout(self, tmp_path):
method test_metadata_stored (line 55) | def test_metadata_stored(self, tmp_path):
method test_round_trip_fidelity (line 72) | def test_round_trip_fidelity(self, tmp_path):
method test_partial_io (line 86) | def test_partial_io(self, tmp_path):
method test_auto_conform (line 99) | def test_auto_conform(self, tmp_path):
method test_non_uniform_without_conform_raises (line 123) | def test_non_uniform_without_conform_raises(self, tmp_path):
class TestPartition (line 134) | class TestPartition:
method test_create_partition (line 135) | def test_create_partition(self, tmp_path):
method test_load_partition (line 149) | def test_load_partition(self, tmp_path):
method test_different_seeds_produce_different_splits (line 167) | def test_different_seeds_produce_different_splits(self, tmp_path):
FILE: nobrainer/training.py
function get_device (line 16) | def get_device() -> torch.device:
function _run_validation (line 27) | def _run_validation(
function _apply_gradient_checkpointing (line 96) | def _apply_gradient_checkpointing(model: nn.Module) -> None:
function _apply_model_parallel (line 127) | def _apply_model_parallel(model: nn.Module, gpus: int) -> nn.Module:
function fit (line 207) | def fit(
function _ddp_worker (line 415) | def _ddp_worker(
function _fit_ddp (line 580) | def _fit_ddp(
FILE: nobrainer/utils.py
function _sha256 (line 16) | def _sha256(path: str) -> str:
function _download_if_needed (line 25) | def _download_if_needed(url: str, dest: str, expected_hash: str) -> None:
function get_data (line 38) | def get_data(cache_dir=_cache_dir):
class StreamingStats (line 138) | class StreamingStats:
method __init__ (line 165) | def __init__(self):
method update (line 170) | def update(self, value):
method mean (line 194) | def mean(self):
method var (line 198) | def var(self):
method std (line 202) | def std(self):
method entropy (line 206) | def entropy(self):
function get_num_parallel (line 214) | def get_num_parallel():
FILE: nobrainer/validation.py
function validate_from_filepath (line 16) | def validate_from_filepath(
function get_dice_for_images (line 81) | def get_dice_for_images(pred, gt, n_classes):
function validate_from_filepaths (line 101) | def validate_from_filepaths(
FILE: scripts/kwyk_reproduction/01_assemble_dataset.py
function main (line 26) | def main():
FILE: scripts/kwyk_reproduction/02_train_meshnet.py
function parse_args (line 25) | def parse_args() -> argparse.Namespace:
function load_manifest (line 63) | def load_manifest(manifest_path: str, split: str) -> list[tuple[str, str]]:
function evaluate_val_dice (line 74) | def evaluate_val_dice(
function plot_learning_curve (line 139) | def plot_learning_curve(
function main (line 175) | def main() -> None:
FILE: scripts/kwyk_reproduction/03_train_bayesian.py
class ELBOLoss (line 50) | class ELBOLoss(nn.Module):
method __init__ (line 64) | def __init__(
method forward (line 76) | def forward(self, pred: torch.Tensor, target: torch.Tensor) -> torch.T...
function parse_args (line 88) | def parse_args() -> argparse.Namespace:
function load_manifest (line 141) | def load_manifest(manifest_path: str, split: str) -> list[tuple[str, str]]:
function evaluate_mc_dice (line 155) | def evaluate_mc_dice(
function plot_learning_curve (line 240) | def plot_learning_curve(
function train_bayesian (line 307) | def train_bayesian(
function main (line 506) | def main() -> None:
FILE: scripts/kwyk_reproduction/04_evaluate.py
function parse_args (line 32) | def parse_args() -> argparse.Namespace:
function load_manifest (line 43) | def load_manifest(manifest_path: str, split: str) -> list[tuple[str, str]]:
function per_class_dice (line 52) | def per_class_dice(
function compute_entropy (line 93) | def compute_entropy(prob_map: np.ndarray) -> np.ndarray:
function plot_prediction_overlay (line 102) | def plot_prediction_overlay(
function plot_per_class_dice (line 150) | def plot_per_class_dice(
function main (line 175) | def main() -> None:
FILE: scripts/kwyk_reproduction/05_compare_kwyk.py
function parse_args (line 31) | def parse_args() -> argparse.Namespace:
function load_manifest (line 69) | def load_manifest(manifest_path: str, split: str) -> list[tuple[str, str]]:
function run_kwyk_prediction (line 83) | def run_kwyk_prediction(
function compute_spatial_correlation (line 143) | def compute_spatial_correlation(map1: np.ndarray, map2: np.ndarray) -> f...
function plot_dice_scatter (line 178) | def plot_dice_scatter(
function main (line 211) | def main() -> None:
FILE: scripts/kwyk_reproduction/06_block_size_sweep.py
function parse_args (line 28) | def parse_args() -> argparse.Namespace:
function load_manifest (line 67) | def load_manifest(manifest_path: str, split: str) -> list[tuple[str, str]]:
function train_and_evaluate (line 81) | def train_and_evaluate(
function plot_block_size_comparison (line 224) | def plot_block_size_comparison(
function main (line 274) | def main() -> None:
FILE: scripts/kwyk_reproduction/build_kwyk_manifest.py
function main (line 21) | def main():
FILE: scripts/kwyk_reproduction/convert_zarr_shard.py
function main (line 23) | def main():
FILE: scripts/kwyk_reproduction/experiments/01_20260330_eval_deterministic/eval_deterministic.py
function per_class_dice (line 23) | def per_class_dice(pred: np.ndarray, gt: np.ndarray, n_classes: int) -> ...
function predict_volume (line 35) | def predict_volume(model, img_path, block_shape, mc=False):
function main (line 64) | def main():
FILE: scripts/kwyk_reproduction/experiments/02_20260330_binary_bayesian/eval_binary.py
function predict_volume (line 23) | def predict_volume(model, img_path, block_shape, mc=False):
function main (line 55) | def main():
FILE: scripts/kwyk_reproduction/experiments/03_20260330_warmstart_diagnostic/diagnose.py
function predict_volume_simple (line 23) | def predict_volume_simple(model, img_path, block_shape, mc=False):
function main (line 55) | def main():
FILE: scripts/kwyk_reproduction/experiments/04_20260330_fixed_warmstart/run.py
function fixed_warmstart_kwyk (line 24) | def fixed_warmstart_kwyk(kwyk_model, det_weights_path):
function predict_volume (line 79) | def predict_volume(model, img_path, block_shape, mc=False):
function per_class_dice (line 111) | def per_class_dice(pred, gt, n_classes):
function main (line 122) | def main():
FILE: scripts/kwyk_reproduction/experiments/05_20260330_kwyk_from_scratch/run.py
function predict_volume (line 25) | def predict_volume(model, img_path, block_shape, mc=False):
function per_class_dice (line 50) | def per_class_dice(pred, gt, n_classes):
function binary_dice (line 61) | def binary_dice(pred, gt):
function train_kwyk (line 69) | def train_kwyk(name, n_classes, label_mapping, mc_train, epochs=50):
function main (line 189) | def main():
FILE: scripts/kwyk_reproduction/utils.py
function load_config (line 15) | def load_config(path: str | Path) -> dict[str, Any]:
function setup_logging (line 35) | def setup_logging(name: str) -> logging.Logger:
function save_figure (line 61) | def save_figure(fig: Any, path: str | Path) -> None:
function compute_dice (line 76) | def compute_dice(pred: np.ndarray, label: np.ndarray) -> float:
function apply_label_mapping (line 100) | def apply_label_mapping(
class SlurmPreemptionHandler (line 146) | class SlurmPreemptionHandler:
method __init__ (line 165) | def __init__(self, sig: int = signal.SIGUSR1) -> None:
method _handle (line 171) | def _handle(self, signum: int, frame: Any) -> None:
function save_training_checkpoint (line 178) | def save_training_checkpoint(
function load_training_checkpoint (line 235) | def load_training_checkpoint(
FILE: scripts/synthseg_evaluation/02_train.py
function parse_args (line 23) | def parse_args():
function load_manifest (line 34) | def load_manifest(path, split):
function main (line 43) | def main():
FILE: scripts/synthseg_evaluation/03_evaluate.py
function per_class_dice (line 25) | def per_class_dice(pred, gt, n_classes):
function main (line 37) | def main():
FILE: scripts/synthseg_evaluation/04_compare.py
function main (line 21) | def main():
Condensed preview — 220 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (978K chars).
[
{
"path": ".autorc",
"chars": 218,
"preview": "{\n \"onlyPublishWithReleaseLabel\": false,\n \"baseBranch\": \"master\",\n \"prereleaseBranches\": [\"alpha\"],\n \"author"
},
{
"path": ".dockerignore",
"chars": 21,
"preview": ".git/\ndocker/\n.idea/\n"
},
{
"path": ".flake8",
"chars": 150,
"preview": "[flake8]\nmax-line-length = 100\nexclude =\n .git/\n __pycache__/\n build/\n dist/\n _version.py\n versioneer."
},
{
"path": ".gitattributes",
"chars": 35,
"preview": "nobrainer/_version.py export-subst\n"
},
{
"path": ".github/EC2_GPU_RUNNER.md",
"chars": 4202,
"preview": "# EC2 GPU Runner Setup\n\nThis document describes how to configure the AWS EC2 instance used as a\nself-hosted GitHub Actio"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 567,
"preview": "---\nname: Bug report\nabout: Report a bug (e.g., something not working as described, missing/incorrect documentation).\nti"
},
{
"path": ".github/ISSUE_TEMPLATE/documentation.md",
"chars": 342,
"preview": "---\nname: Documentation improvement\nabout: Request improvements to the documentation and tutorials.\ntitle: ''\nlabels: 'd"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 336,
"preview": "---\nname: Feature request\nabout: Propose a new feature or a change to an existing feature.\ntitle: ''\nlabels: 'feature'\na"
},
{
"path": ".github/ISSUE_TEMPLATE/maintenance.md",
"chars": 187,
"preview": "---\nname: Maintenance and delivery\nabout: Suggestions and requests regarding the infrastructure for development, testing"
},
{
"path": ".github/ISSUE_TEMPLATE/question.md",
"chars": 291,
"preview": "---\nname: Question\nabout: Not sure if you are using Nobrainer correctly, or other questions? This is the place.\ntitle: '"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 638,
"preview": "## Types of changes\n<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->\n- "
},
{
"path": ".github/workflows/ci.yml",
"chars": 2170,
"preview": "name: CI\n\non:\n push:\n branches: [main, master]\n pull_request:\n branches: [main, master, alpha]\n\njobs:\n unit-tes"
},
{
"path": ".github/workflows/guide-notebooks-ec2.yml",
"chars": 7668,
"preview": "name: GPU Tests - EC2\nrun-name: ${{ github.ref_name }} - GPU Tests - EC2\non:\n push:\n branches: [main, master]\n # PR"
},
{
"path": ".github/workflows/kwyk-reproduction-ec2.yml",
"chars": 14281,
"preview": "name: KWYK Reproduction - EC2 GPU\nrun-name: ${{ github.ref_name }} - KWYK Reproduction\n\non:\n workflow_dispatch:\n inp"
},
{
"path": ".github/workflows/publish.yml",
"chars": 391,
"preview": "name: Publish to PyPI on GitHub release\n\non:\n release:\n types: [published]\n\njobs:\n pypi-release:\n runs-on: ubunt"
},
{
"path": ".github/workflows/release.yml",
"chars": 1269,
"preview": "name: Auto-release on PR merge\n\non:\n pull_request:\n branches: [master, alpha]\n types: [closed]\n\nenv:\n AUTO_VERSI"
},
{
"path": ".github/workflows/validate-book.yml",
"chars": 1747,
"preview": "name: Validate nobrainer-book tutorials\n\non:\n workflow_dispatch: # Manual trigger only — not part of CI checks\n\njobs:\n"
},
{
"path": ".gitignore",
"chars": 2193,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": ".pre-commit-config.yaml",
"chars": 853,
"preview": "# See https://pre-commit.com for more information\n# See https://pre-commit.com/hooks.html for more hooks\nci:\n skip: ["
},
{
"path": ".zenodo.json",
"chars": 1257,
"preview": "{\n \"creators\": [\n {\n \"affiliation\": \"Stony Brook University\",\n \"name\": \"Kaczmarzyk, Jakub\",\n \"orcid\":"
},
{
"path": "CHANGELOG.md",
"chars": 24703,
"preview": "# 1.2.1 (Thu Apr 04 2024)\n\n#### 🐛 Bug Fix\n\n- Fix PGAN notebook [#319](https://github.com/neuronets/nobrainer/pull/319) ("
},
{
"path": "CITATION",
"chars": 258,
"preview": "Please follow this DOI (https://doi.org/10.5281/zenodo.4995077) to find\nthe latest citation on Zenodo. The different cit"
},
{
"path": "CLAUDE.md",
"chars": 5160,
"preview": "# Nobrainer Development Guidelines\n\n## Project Overview\n\nNobrainer is a PyTorch-based deep learning library for 3D brain"
},
{
"path": "LICENSE",
"chars": 562,
"preview": "Copyright 2021 The Nobrainer Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use t"
},
{
"path": "MANIFEST.in",
"chars": 263,
"preview": "# This line includes versioneer.py in sdists, which is necessary for wheels\n# built from sdists to have the version set "
},
{
"path": "README.md",
"chars": 9179,
"preview": "# Nobrainer\n\n\n\n_Nobrainer_ is "
},
{
"path": "conftest.py",
"chars": 499,
"preview": "\"\"\"Root conftest.py — auto-skip GPU tests when CUDA is unavailable.\"\"\"\n\nfrom __future__ import annotations\n\nimport pytes"
},
{
"path": "docker/README.md",
"chars": 1091,
"preview": "# Nobrainer in a container\n\nThe Dockerfiles in this directory can be used to create Docker images to use _Nobrainer_ on "
},
{
"path": "docker/cpu.Dockerfile",
"chars": 799,
"preview": "FROM python:3.14-slim\nENV DEBIAN_FRONTEND=noninteractive\nRUN apt-get update && apt-get install -y --no-install-recommend"
},
{
"path": "docker/gpu.Dockerfile",
"chars": 715,
"preview": "FROM python:3.14-slim\nENV DEBIAN_FRONTEND=noninteractive\nRUN apt-get update && apt-get install -y --no-install-recommend"
},
{
"path": "nobrainer/__init__.py",
"chars": 497,
"preview": "try:\n from ._version import __version__ # noqa: F401\nexcept (ImportError, ModuleNotFoundError):\n try:\n fro"
},
{
"path": "nobrainer/_version.py",
"chars": 23139,
"preview": "# This file helps to compute a version number in source trees obtained from\n# git-archive tarball (such as those provide"
},
{
"path": "nobrainer/augmentation/__init__.py",
"chars": 338,
"preview": "\"\"\"Data augmentation: transform tagging, profiles, and SynthSeg generation.\"\"\"\n\nfrom .profiles import get_augmentation_p"
},
{
"path": "nobrainer/augmentation/profiles.py",
"chars": 3331,
"preview": "\"\"\"Predefined augmentation profiles for brain imaging.\n\nEach profile returns a list of MONAI dictionary transforms wrapp"
},
{
"path": "nobrainer/augmentation/synthseg.py",
"chars": 13346,
"preview": "\"\"\"SynthSeg-style synthetic brain data generator.\n\nEnhanced implementation following Billot et al. (2023) with:\n- GMM ti"
},
{
"path": "nobrainer/augmentation/transforms.py",
"chars": 3332,
"preview": "\"\"\"Augmentation tagging for MONAI transform pipelines.\n\nExtends MONAI's ``Compose`` so individual transforms can be tagg"
},
{
"path": "nobrainer/cli/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/cli/main.py",
"chars": 15992,
"preview": "\"\"\"Main command-line interface for nobrainer.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport os\nimport p"
},
{
"path": "nobrainer/cli/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/cli/tests/main_test.py",
"chars": 7540,
"preview": "\"\"\"Tests for `nobrainer.cli.main`.\"\"\"\n\nimport csv\nfrom pathlib import Path\n\nfrom click.testing import CliRunner\nimport n"
},
{
"path": "nobrainer/dataset.py",
"chars": 7691,
"preview": "\"\"\"PyTorch dataset utilities backed by MONAI.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfrom typi"
},
{
"path": "nobrainer/datasets/__init__.py",
"chars": 835,
"preview": "\"\"\"Dataset fetching utilities for various neuroimaging sources.\n\nEach submodule provides functions to install and fetch "
},
{
"path": "nobrainer/datasets/openneuro.py",
"chars": 11632,
"preview": "\"\"\"Fetch datasets from OpenNeuro and OpenNeuro Derivatives via DataLad.\n\nRequires the ``[versioning]`` extra (``datalad "
},
{
"path": "nobrainer/datasets/zarr_store.py",
"chars": 11033,
"preview": "\"\"\"Multi-subject Zarr3 dataset store with sharding.\n\nConverts NIfTI collections into a single sharded Zarr3 store where\n"
},
{
"path": "nobrainer/distributed_learning/dwc.py",
"chars": 1594,
"preview": "import numpy as np\n\n# Distributed weight consolidation for Bayesian Deep Neural Networks\n# Implemented according to the:"
},
{
"path": "nobrainer/experiment.py",
"chars": 6804,
"preview": "\"\"\"Experiment tracking: local file logger + optional Weights & Biases.\n\nProvides a unified interface for logging trainin"
},
{
"path": "nobrainer/gpu.py",
"chars": 7833,
"preview": "\"\"\"GPU utilities: device detection, memory profiling, batch size optimization.\n\nExamples\n--------\nAuto-select the best b"
},
{
"path": "nobrainer/io.py",
"chars": 13520,
"preview": "\"\"\"Input/output utilities for nobrainer (PyTorch, no TensorFlow).\"\"\"\n\nfrom __future__ import annotations\n\nimport csv\nimp"
},
{
"path": "nobrainer/layers/InstanceNorm.py",
"chars": 1825,
"preview": "import logging\n\nfrom ..layers.groupnorm import GroupNormalization\n\n\nclass InstanceNormalization(GroupNormalization):\n "
},
{
"path": "nobrainer/layers/__init__.py",
"chars": 347,
"preview": "from .bernoulli_dropout import BernoulliDropout\nfrom .concrete_dropout import ConcreteDropout\nfrom .gaussian_dropout imp"
},
{
"path": "nobrainer/layers/bernoulli_dropout.py",
"chars": 2461,
"preview": "\"\"\"Bernoulli dropout layer for PyTorch.\"\"\"\n\nimport torch\nimport torch.nn as nn\n\n\nclass BernoulliDropout(nn.Module):\n "
},
{
"path": "nobrainer/layers/concrete_dropout.py",
"chars": 3631,
"preview": "\"\"\"Concrete Dropout layer for PyTorch.\"\"\"\n\nimport math\n\nimport torch\nimport torch.nn as nn\n\n\nclass ConcreteDropout(nn.Mo"
},
{
"path": "nobrainer/layers/gaussian_dropout.py",
"chars": 2125,
"preview": "\"\"\"Gaussian dropout layer for PyTorch.\"\"\"\n\nimport math\n\nimport torch\nimport torch.nn as nn\n\n\nclass GaussianDropout(nn.Mo"
},
{
"path": "nobrainer/layers/maxpool4d.py",
"chars": 2051,
"preview": "\"\"\"MaxPool4D layer for PyTorch.\n\nImplements 4-D max-pooling (N, C, V, D, H, W) by treating the volume\ndimension V as a b"
},
{
"path": "nobrainer/layers/padding.py",
"chars": 871,
"preview": "\"\"\"Custom padding layers for nobrainer (PyTorch).\"\"\"\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F"
},
{
"path": "nobrainer/layers/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/losses.py",
"chars": 14357,
"preview": "\"\"\"Loss functions for 3-D semantic segmentation (PyTorch / MONAI).\"\"\"\n\nfrom __future__ import annotations\n\nfrom monai.lo"
},
{
"path": "nobrainer/metrics.py",
"chars": 4799,
"preview": "\"\"\"Evaluation metrics for 3-D semantic segmentation (PyTorch / MONAI).\"\"\"\n\nfrom __future__ import annotations\n\nfrom mona"
},
{
"path": "nobrainer/models/__init__.py",
"chars": 2378,
"preview": "\"\"\"Nobrainer model registry (PyTorch).\"\"\"\n\nfrom pprint import pprint\n\nfrom .autoencoder import autoencoder\nfrom .highres"
},
{
"path": "nobrainer/models/_constants.py",
"chars": 333,
"preview": "\"\"\"Shared constants for nobrainer models.\"\"\"\n\nfrom __future__ import annotations\n\n# Dilation schedules indexed by recept"
},
{
"path": "nobrainer/models/_utils.py",
"chars": 2818,
"preview": "\"\"\"Shared utilities for nobrainer models and training.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\n"
},
{
"path": "nobrainer/models/autoencoder.py",
"chars": 3875,
"preview": "\"\"\"Symmetric 3-D autoencoder (PyTorch).\n\nEncodes a 3-D volume into a flat latent vector and reconstructs it via\ntranspos"
},
{
"path": "nobrainer/models/bayesian/__init__.py",
"chars": 1065,
"preview": "\"\"\"Bayesian model sub-package.\n\nTwo flavours of Bayesian convolution are provided:\n\n* **Bayes-by-backprop** (``BayesianC"
},
{
"path": "nobrainer/models/bayesian/bayesian_meshnet.py",
"chars": 5712,
"preview": "\"\"\"Bayesian MeshNet: dilated-convolution segmentation with weight uncertainty.\n\nReplaces every ``nn.Conv3d`` in the 7-la"
},
{
"path": "nobrainer/models/bayesian/bayesian_vnet.py",
"chars": 6847,
"preview": "\"\"\"Bayesian V-Net: encoder-decoder segmentation with weight uncertainty.\n\nReplaces the standard ``nn.Conv3d`` convolutio"
},
{
"path": "nobrainer/models/bayesian/kwyk_meshnet.py",
"chars": 8124,
"preview": "\"\"\"KWYK MeshNet variants — matching McClure et al. (2019) architecture.\n\nAll three kwyk models use Fully Factorized Gaus"
},
{
"path": "nobrainer/models/bayesian/layers.py",
"chars": 11549,
"preview": "\"\"\"Bayesian convolutional and linear layers as Pyro modules.\n\nBoth ``BayesianConv3d`` and ``BayesianLinear`` implement w"
},
{
"path": "nobrainer/models/bayesian/utils.py",
"chars": 1040,
"preview": "\"\"\"Utility functions for Bayesian models.\"\"\"\n\nfrom __future__ import annotations\n\nimport torch\n\nfrom .layers import Baye"
},
{
"path": "nobrainer/models/bayesian/vwn_layers.py",
"chars": 8322,
"preview": "\"\"\"Fully Factorized Gaussian (FFG) layers with local reparameterization.\n\nThese layers implement the convolution used in"
},
{
"path": "nobrainer/models/bayesian/warmstart.py",
"chars": 9789,
"preview": "\"\"\"Warm-start a Bayesian model from a trained deterministic model.\"\"\"\n\nfrom __future__ import annotations\n\nimport loggin"
},
{
"path": "nobrainer/models/generative/__init__.py",
"chars": 229,
"preview": "\"\"\"Generative model sub-package (Phase 5 — US3).\"\"\"\n\nfrom .dcgan import DCGAN, dcgan\nfrom .progressivegan import Progres"
},
{
"path": "nobrainer/models/generative/dcgan.py",
"chars": 7782,
"preview": "\"\"\"DCGAN implemented as a PyTorch Lightning module.\n\nStandard alternating generator/discriminator training using BCE los"
},
{
"path": "nobrainer/models/generative/progressivegan.py",
"chars": 11730,
"preview": "\"\"\"ProgressiveGAN implemented as a PyTorch Lightning module.\n\nGrows the generator and discriminator from 4³ to the targe"
},
{
"path": "nobrainer/models/highresnet.py",
"chars": 4702,
"preview": "\"\"\"HighResNet 3-D segmentation model (PyTorch).\n\nReference\n---------\nLi W. et al., \"On the Compactness, Efficiency, and "
},
{
"path": "nobrainer/models/meshnet.py",
"chars": 3461,
"preview": "\"\"\"MeshNet 3-D segmentation model (PyTorch).\n\nReference\n---------\nFedorov A. et al., \"End-to-end learning of brain tissu"
},
{
"path": "nobrainer/models/segformer3d.py",
"chars": 14351,
"preview": "\"\"\"SegFormer3D: Efficient Transformer for 3D Medical Image Segmentation.\n\nPort of SegFormer3D (Perera et al., CVPR 2024 "
},
{
"path": "nobrainer/models/segmentation.py",
"chars": 7053,
"preview": "\"\"\"MONAI-backed segmentation model factory functions.\n\nAll models expect input of shape ``(N, C_in, D, H, W)`` and produ"
},
{
"path": "nobrainer/models/simsiam.py",
"chars": 4718,
"preview": "\"\"\"SimSiam self-supervised learning model for 3-D brain volumes (PyTorch).\n\nReference\n---------\nChen X. & He K., \"Explor"
},
{
"path": "nobrainer/models/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/prediction.py",
"chars": 16852,
"preview": "\"\"\"Block-based prediction utilities (PyTorch, no TensorFlow).\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib impor"
},
{
"path": "nobrainer/processing/__init__.py",
"chars": 648,
"preview": "\"\"\"Scikit-learn-style estimator API for nobrainer.\n\nProvides high-level ``Segmentation``, ``Generation``, and ``Dataset`"
},
{
"path": "nobrainer/processing/base.py",
"chars": 2417,
"preview": "\"\"\"Base estimator with Croissant-ML metadata persistence.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfrom pathl"
},
{
"path": "nobrainer/processing/croissant.py",
"chars": 7251,
"preview": "\"\"\"Croissant-ML JSON-LD metadata helpers for nobrainer estimators.\"\"\"\n\nfrom __future__ import annotations\n\nimport dateti"
},
{
"path": "nobrainer/processing/dataset.py",
"chars": 28397,
"preview": "\"\"\"Fluent Dataset builder for nobrainer estimators.\"\"\"\n\nfrom __future__ import annotations\n\nimport copy\nfrom pathlib imp"
},
{
"path": "nobrainer/processing/generation.py",
"chars": 3638,
"preview": "\"\"\"Generation estimator — scikit-learn-style API for GANs.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import P"
},
{
"path": "nobrainer/processing/segmentation.py",
"chars": 8438,
"preview": "\"\"\"Segmentation estimator — scikit-learn-style API.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfro"
},
{
"path": "nobrainer/research/__init__.py",
"chars": 158,
"preview": "\"\"\"Autoresearch sub-package for nobrainer (Phase 7 — US5/US6).\"\"\"\n\nfrom .loop import commit_best_model, run_loop\n\n__all_"
},
{
"path": "nobrainer/research/loop.py",
"chars": 13632,
"preview": "\"\"\"Autoresearch loop for nobrainer.\n\nProposes hyperparameter diffs via the Anthropic API, applies them to a\ntraining scr"
},
{
"path": "nobrainer/research/templates/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/research/templates/prepare.py",
"chars": 1990,
"preview": "\"\"\"Standard data preparation script for autoresearch.\n\nUsage\n-----\n python prepare.py --data-dir /path/to/nifti --val"
},
{
"path": "nobrainer/research/templates/train_bayesian_vnet.py",
"chars": 5073,
"preview": "\"\"\"Bayesian VNet training script for autoresearch.\n\nThe autoresearch loop patches the ``# CONFIG:`` comment line below t"
},
{
"path": "nobrainer/slurm.py",
"chars": 5718,
"preview": "\"\"\"SLURM utilities for preemptible training with checkpoint/resume.\n\nProvides signal handling for SLURM preemption and c"
},
{
"path": "nobrainer/sr-tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/sr-tests/conftest.py",
"chars": 461,
"preview": "\"\"\"Shared fixtures for somewhat-realistic tests.\"\"\"\n\nimport pytest\n\nfrom nobrainer.io import read_csv\nfrom nobrainer.uti"
},
{
"path": "nobrainer/sr-tests/test_bayesian_uncertainty.py",
"chars": 2310,
"preview": "\"\"\"Tests for Bayesian segmentation with uncertainty quantification.\n\nThese tests train a BayesianVNet and run MC predict"
},
{
"path": "nobrainer/sr-tests/test_brain_generation.py",
"chars": 1688,
"preview": "\"\"\"Tests for brain generation with Progressive GAN.\"\"\"\n\nimport nibabel as nib\nimport numpy as np\nimport pytest\nimport to"
},
{
"path": "nobrainer/sr-tests/test_croissant_metadata.py",
"chars": 2184,
"preview": "\"\"\"Tests for Croissant-ML metadata generation.\"\"\"\n\nimport json\nfrom pathlib import Path\n\nfrom nobrainer.processing impor"
},
{
"path": "nobrainer/sr-tests/test_dataset_builder.py",
"chars": 2070,
"preview": "\"\"\"Tests for the fluent Dataset builder with real brain data.\"\"\"\n\nfrom nobrainer.processing import Dataset\n\n\nclass TestD"
},
{
"path": "nobrainer/sr-tests/test_extract_patches.py",
"chars": 2669,
"preview": "\"\"\"Tests for extract_patches() with various binarization modes.\"\"\"\n\nimport nibabel as nib\nimport numpy as np\nimport pyte"
},
{
"path": "nobrainer/sr-tests/test_kwyk_smoke.py",
"chars": 8214,
"preview": "\"\"\"Smoke tests for the kwyk reproduction pipeline.\n\nTests train a tiny MeshNet and Bayesian MeshNet for 1 epoch each to "
},
{
"path": "nobrainer/sr-tests/test_raw_pytorch_api.py",
"chars": 1822,
"preview": "\"\"\"Tests for the raw PyTorch API without the estimator layer.\"\"\"\n\nimport nibabel as nib\nimport torch\n\nimport nobrainer.m"
},
{
"path": "nobrainer/sr-tests/test_segmentation_estimator.py",
"chars": 2724,
"preview": "\"\"\"Tests for the Segmentation estimator with real brain data.\"\"\"\n\nimport json\n\nimport nibabel as nib\n\nfrom nobrainer.pro"
},
{
"path": "nobrainer/sr-tests/test_synthseg_brain.py",
"chars": 2614,
"preview": "\"\"\"SR-test: SynthSeg generation from real aparc+aseg label maps.\n\nTests that the enhanced SynthSeg generator produces re"
},
{
"path": "nobrainer/sr-tests/test_zarr_conversion.py",
"chars": 3215,
"preview": "\"\"\"Tests for NIfTI-to-Zarr and Zarr-to-NIfTI conversion.\"\"\"\n\nfrom pathlib import Path\n\nimport nibabel as nib\nimport nump"
},
{
"path": "nobrainer/sr-tests/test_zarr_pipeline.py",
"chars": 2354,
"preview": "\"\"\"SR-test: end-to-end Zarr pipeline with real brain data.\n\nConverts sample brain data to Zarr, creates partition, build"
},
{
"path": "nobrainer/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/tests/contract/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/tests/contract/test_cli.py",
"chars": 3407,
"preview": "\"\"\"CLI contract tests for nobrainer commands.\n\nVerifies that all CLI commands advertised in contracts/nobrainer-pytorch-"
},
{
"path": "nobrainer/tests/gpu/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/tests/gpu/test_bayesian_e2e.py",
"chars": 3193,
"preview": "\"\"\"GPU end-to-end test: Bayesian VNet with uncertainty quantification.\n\nT045 — US2 acceptance scenario: predict_with_unc"
},
{
"path": "nobrainer/tests/gpu/test_gan_e2e.py",
"chars": 3277,
"preview": "\"\"\"GPU end-to-end test: ProgressiveGAN training.\n\nT054 — US3 acceptance scenario: ProgressiveGAN completes extended trai"
},
{
"path": "nobrainer/tests/gpu/test_multi_gpu.py",
"chars": 3413,
"preview": "\"\"\"GPU integration test: multi-GPU training and inference.\n\nT035 — US4: requires 2+ GPUs. Tests DDP training speedup and"
},
{
"path": "nobrainer/tests/gpu/test_predict_e2e.py",
"chars": 3392,
"preview": "\"\"\"GPU end-to-end test: train a UNet on synthetic data, then verify predict()\nproduces high Dice on the same data (overf"
},
{
"path": "nobrainer/tests/integration/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/tests/integration/test_datalad_commit.py",
"chars": 6072,
"preview": "\"\"\"Integration test for commit_best_model with a real DataLad dataset.\n\nRequirements: datalad>=0.19 and git-annex must b"
},
{
"path": "nobrainer/tests/integration/test_research_smoke.py",
"chars": 1378,
"preview": "\"\"\"Integration test for autoresearch loop with budget-minutes constraint.\n\nT014: Run the full research loop with a 60-se"
},
{
"path": "nobrainer/tests/unit/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "nobrainer/tests/unit/test_bayesian_layers.py",
"chars": 5842,
"preview": "\"\"\"Unit tests for BayesianConv3d, BayesianLinear, and accumulate_kl.\"\"\"\n\nfrom __future__ import annotations\n\nimport pyro"
},
{
"path": "nobrainer/tests/unit/test_bayesian_models.py",
"chars": 4101,
"preview": "\"\"\"Unit tests for BayesianVNet and BayesianMeshNet.\"\"\"\n\nfrom __future__ import annotations\n\nimport pyro\nimport pytest\nim"
},
{
"path": "nobrainer/tests/unit/test_class_weights.py",
"chars": 3589,
"preview": "\"\"\"Unit tests for class weight computation and weighted losses.\"\"\"\n\nfrom __future__ import annotations\n\nimport numpy as "
},
{
"path": "nobrainer/tests/unit/test_croissant.py",
"chars": 6510,
"preview": "\"\"\"Unit tests for nobrainer.processing.croissant helpers (T024).\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfro"
},
{
"path": "nobrainer/tests/unit/test_dataset.py",
"chars": 3292,
"preview": "\"\"\"Unit tests for nobrainer.dataset.get_dataset().\"\"\"\n\nfrom pathlib import Path\nimport tempfile\n\nimport nibabel as nib\ni"
},
{
"path": "nobrainer/tests/unit/test_dataset_builder.py",
"chars": 6832,
"preview": "\"\"\"Unit tests for nobrainer.processing.dataset.Dataset fluent builder (T013).\"\"\"\n\nfrom __future__ import annotations\n\nim"
},
{
"path": "nobrainer/tests/unit/test_datasets_openneuro.py",
"chars": 4245,
"preview": "\"\"\"Unit tests for nobrainer.datasets.openneuro.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfrom un"
},
{
"path": "nobrainer/tests/unit/test_estimator_generation.py",
"chars": 3305,
"preview": "\"\"\"Unit tests for nobrainer.processing.generation.Generation estimator (T029).\"\"\"\n\nfrom __future__ import annotations\n\ni"
},
{
"path": "nobrainer/tests/unit/test_estimator_segmentation.py",
"chars": 5850,
"preview": "\"\"\"Unit tests for nobrainer.processing.segmentation.Segmentation estimator (T023).\"\"\"\n\nfrom __future__ import annotation"
},
{
"path": "nobrainer/tests/unit/test_experiment.py",
"chars": 1760,
"preview": "\"\"\"Unit tests for nobrainer.experiment tracking.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\n\nfrom nobrainer.exp"
},
{
"path": "nobrainer/tests/unit/test_generative.py",
"chars": 4488,
"preview": "\"\"\"Unit tests for ProgressiveGAN and DCGAN (CPU smoke tests).\"\"\"\n\nfrom __future__ import annotations\n\nimport pytorch_lig"
},
{
"path": "nobrainer/tests/unit/test_gpu.py",
"chars": 1296,
"preview": "\"\"\"Unit tests for nobrainer.gpu utilities.\"\"\"\n\nfrom __future__ import annotations\n\nimport torch\n\nfrom nobrainer.gpu impo"
},
{
"path": "nobrainer/tests/unit/test_io_weights.py",
"chars": 2752,
"preview": "\"\"\"Unit tests for convert_weights() in nobrainer.io.\"\"\"\n\nfrom pathlib import Path\n\nimport h5py\nimport numpy as np\nimport"
},
{
"path": "nobrainer/tests/unit/test_io_zarr.py",
"chars": 3540,
"preview": "\"\"\"Unit tests for NIfTI <-> Zarr v3 conversion.\"\"\"\n\nfrom __future__ import annotations\n\nimport nibabel as nib\nimport num"
},
{
"path": "nobrainer/tests/unit/test_layers.py",
"chars": 6358,
"preview": "\"\"\"Unit tests for nobrainer.layers (PyTorch implementations).\"\"\"\n\nimport pytest\nimport torch\n\nfrom nobrainer.layers impo"
},
{
"path": "nobrainer/tests/unit/test_losses.py",
"chars": 4974,
"preview": "\"\"\"Unit tests for nobrainer.losses (MONAI-backed).\"\"\"\n\nimport pytest\nimport torch\n\nimport nobrainer.losses as losses_mod"
},
{
"path": "nobrainer/tests/unit/test_metrics.py",
"chars": 3388,
"preview": "\"\"\"Unit tests for nobrainer.metrics (MONAI-backed).\"\"\"\n\nimport pytest\nimport torch\n\nimport nobrainer.metrics as metrics_"
},
{
"path": "nobrainer/tests/unit/test_model_interface.py",
"chars": 2296,
"preview": "\"\"\"Unit tests for unified model forward interface.\"\"\"\n\nfrom __future__ import annotations\n\nimport torch\n\nfrom nobrainer."
},
{
"path": "nobrainer/tests/unit/test_model_registry.py",
"chars": 1352,
"preview": "\"\"\"Unit tests for SwinUNETR and SegResNet model registration.\"\"\"\n\nfrom __future__ import annotations\n\nimport torch\n\nfrom"
},
{
"path": "nobrainer/tests/unit/test_models_segmentation.py",
"chars": 7567,
"preview": "\"\"\"Unit tests for nobrainer segmentation models (PyTorch).\"\"\"\n\nimport pytest\nimport torch\n\nfrom nobrainer.models import "
},
{
"path": "nobrainer/tests/unit/test_prediction.py",
"chars": 5174,
"preview": "\"\"\"Unit tests for predict() and predict_with_uncertainty().\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import "
},
{
"path": "nobrainer/tests/unit/test_research_commit.py",
"chars": 6258,
"preview": "\"\"\"Unit tests for commit_best_model in nobrainer.research.loop.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfrom"
},
{
"path": "nobrainer/tests/unit/test_research_loop.py",
"chars": 8305,
"preview": "\"\"\"Unit tests for the autoresearch run_loop.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nfrom pathlib import Pat"
},
{
"path": "nobrainer/tests/unit/test_segformer3d.py",
"chars": 2418,
"preview": "\"\"\"Unit tests for SegFormer3D model.\"\"\"\n\nfrom __future__ import annotations\n\nimport torch\n\nfrom nobrainer.models import "
},
{
"path": "nobrainer/tests/unit/test_slurm.py",
"chars": 1795,
"preview": "\"\"\"Unit tests for nobrainer.slurm utilities.\"\"\"\n\nfrom __future__ import annotations\n\nimport torch\n\nfrom nobrainer.slurm "
},
{
"path": "nobrainer/tests/unit/test_stride_patches.py",
"chars": 3277,
"preview": "\"\"\"Unit tests for strided patch extraction and reassembly.\"\"\"\n\nfrom __future__ import annotations\n\nimport numpy as np\n\nf"
},
{
"path": "nobrainer/tests/unit/test_synthseg.py",
"chars": 11164,
"preview": "\"\"\"Unit tests for enhanced SynthSeg generator.\"\"\"\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\n\nimport "
},
{
"path": "nobrainer/tests/unit/test_training.py",
"chars": 3921,
"preview": "\"\"\"Unit tests for nobrainer.training.fit().\"\"\"\n\nfrom __future__ import annotations\n\nimport torch\nimport torch.nn as nn\nf"
},
{
"path": "nobrainer/tests/unit/test_training_convergence.py",
"chars": 2674,
"preview": "\"\"\"CPU training-convergence smoke tests (US1 acceptance scenario 3).\n\nVerifies that each core segmentation model's train"
},
{
"path": "nobrainer/tests/unit/test_transform_pipeline.py",
"chars": 3855,
"preview": "\"\"\"Unit tests for TrainableCompose and Augmentation tagging.\"\"\"\n\nfrom __future__ import annotations\n\nfrom nobrainer.augm"
},
{
"path": "nobrainer/tests/unit/test_vwn_layers.py",
"chars": 4608,
"preview": "\"\"\"Unit tests for VWN layers and KWYKMeshNet.\"\"\"\n\nfrom __future__ import annotations\n\nimport torch\n\nfrom nobrainer.model"
},
{
"path": "nobrainer/tests/unit/test_zarr_dataset.py",
"chars": 3153,
"preview": "\"\"\"Unit tests for ZarrDataset and get_dataset() Zarr routing.\"\"\"\n\nfrom __future__ import annotations\n\nimport nibabel as "
},
{
"path": "nobrainer/tests/unit/test_zarr_store.py",
"chars": 6740,
"preview": "\"\"\"Unit tests for nobrainer.datasets.zarr_store.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\n\nimport nibabel as "
},
{
"path": "nobrainer/training.py",
"chars": 20503,
"preview": "\"\"\"Training utilities with optional multi-GPU DDP support.\"\"\"\n\nfrom __future__ import annotations\n\nimport logging\nfrom p"
},
{
"path": "nobrainer/utils.py",
"chars": 7296,
"preview": "\"\"\"Utilities for Nobrainer.\"\"\"\n\nfrom collections import namedtuple\nimport csv\nimport hashlib\nimport os\nimport tempfile\ni"
},
{
"path": "nobrainer/validation.py",
"chars": 6593,
"preview": "#!/usr/bin/env python3\n\nfrom pathlib import Path\n\nimport nibabel as nib\nimport numpy as np\n\nfrom .io import read_mapping"
},
{
"path": "pyproject.toml",
"chars": 3133,
"preview": "[build-system]\nrequires = [\"hatchling\", \"hatch-vcs\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"nobrainer\"\ndyn"
},
{
"path": "scripts/kwyk_reproduction/01_assemble_dataset.py",
"chars": 2769,
"preview": "#!/usr/bin/env python\n\"\"\"Assemble training dataset from OpenNeuro fmriprep derivatives.\n\nUses :mod:`nobrainer.datasets.o"
},
{
"path": "scripts/kwyk_reproduction/02_train_meshnet.py",
"chars": 13231,
"preview": "#!/usr/bin/env python\n\"\"\"Train a deterministic MeshNet for brain extraction / parcellation.\n\nUsage:\n python 02_train_"
},
{
"path": "scripts/kwyk_reproduction/03_train_bayesian.py",
"chars": 29957,
"preview": "#!/usr/bin/env python\n\"\"\"Train a Bayesian MeshNet with optional warm-start from deterministic weights.\n\nSupports the thr"
},
{
"path": "scripts/kwyk_reproduction/04_evaluate.py",
"chars": 13031,
"preview": "#!/usr/bin/env python\n\"\"\"Evaluate a trained segmentation model on test volumes.\n\nComputes per-class Dice for each volume"
},
{
"path": "scripts/kwyk_reproduction/05_compare_kwyk.py",
"chars": 11605,
"preview": "#!/usr/bin/env python\n\"\"\"Compare new model predictions against original kwyk container.\n\nUsage:\n python 05_compare_kw"
},
{
"path": "scripts/kwyk_reproduction/06_block_size_sweep.py",
"chars": 11344,
"preview": "#!/usr/bin/env python\n\"\"\"Sweep over block sizes to compare segmentation performance.\n\nUsage:\n python 06_block_size_sw"
},
{
"path": "scripts/kwyk_reproduction/ARCHITECTURE.md",
"chars": 5564,
"preview": "# KWYK Architecture Verification\n\nThis document records how the original kwyk model architecture was verified\nagainst th"
},
{
"path": "scripts/kwyk_reproduction/README.md",
"chars": 10003,
"preview": "# KWYK Brain Extraction Reproduction\n\nReproduce the kwyk brain extraction study (McClure et al., Frontiers in\nNeuroinfor"
},
{
"path": "scripts/kwyk_reproduction/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "scripts/kwyk_reproduction/build_kwyk_manifest.py",
"chars": 3415,
"preview": "#!/usr/bin/env python\n\"\"\"Build a manifest CSV from the original KWYK dataset (PAC brain volumes).\n\nThe KWYK dataset cont"
},
{
"path": "scripts/kwyk_reproduction/config.yaml",
"chars": 3541,
"preview": "# KWYK Brain Segmentation Reproduction - Default Configuration\n# Based on: McClure et al., Frontiers in Neuroinformatics"
},
{
"path": "scripts/kwyk_reproduction/config_kwyk_smoke.yaml",
"chars": 2023,
"preview": "# KWYK PAC Dataset Smoke Test — 50-class parcellation, 100 subjects\n# Based on: McClure et al., Frontiers in Neuroinform"
},
{
"path": "scripts/kwyk_reproduction/convert_zarr_shard.py",
"chars": 3949,
"preview": "#!/usr/bin/env python\n\"\"\"Convert one shard of NIfTI volumes to a pre-created Zarr3 store.\n\nUsage:\n python convert_zar"
},
{
"path": "scripts/kwyk_reproduction/experiments/01_20260330_eval_deterministic/README.md",
"chars": 984,
"preview": "# Experiment 01: Evaluate Bayesian models in deterministic mode\n\n## Rationale\n\nAll 3 Bayesian variants show zero Dice du"
},
{
"path": "scripts/kwyk_reproduction/experiments/01_20260330_eval_deterministic/eval_deterministic.py",
"chars": 4432,
"preview": "#!/usr/bin/env python\n\"\"\"Evaluate Bayesian models in deterministic mode (mc=False).\n\nQuick diagnostic: do the weights co"
},
{
"path": "scripts/kwyk_reproduction/experiments/01_20260330_eval_deterministic/results_summary.md",
"chars": 943,
"preview": "# Experiment 01 Results\n\n## Finding\n\n**Both deterministic and MC modes produce zero Dice for all 3 Bayesian variants.**\n"
},
{
"path": "scripts/kwyk_reproduction/experiments/01_20260330_eval_deterministic/run.sbatch",
"chars": 566,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp01-det\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:1\n#SBATCH --cpus-per-tas"
},
{
"path": "scripts/kwyk_reproduction/experiments/02_20260330_binary_bayesian/README.md",
"chars": 743,
"preview": "# Experiment 02: Binary (2-class) Bayesian training\n\n## Rationale\n\n50-class parcellation is a hard problem. Binary brain"
},
{
"path": "scripts/kwyk_reproduction/experiments/02_20260330_binary_bayesian/config.yaml",
"chars": 315,
"preview": "filters: 96\nreceptive_field: 37\ndropout_rate: 0.25\nsigma_init: 0.0001\n\nblock_shape: [32, 32, 32]\nlr: 0.0001\nbatch_size: "
},
{
"path": "scripts/kwyk_reproduction/experiments/02_20260330_binary_bayesian/eval_binary.py",
"chars": 3684,
"preview": "#!/usr/bin/env python\n\"\"\"Evaluate binary Bayesian model in both mc=True and mc=False modes.\"\"\"\n\nfrom __future__ import a"
},
{
"path": "scripts/kwyk_reproduction/experiments/02_20260330_binary_bayesian/eval_only.sbatch",
"chars": 515,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp02-eval\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:1\n#SBATCH --cpus-per-ta"
},
{
"path": "scripts/kwyk_reproduction/experiments/02_20260330_binary_bayesian/run.sbatch",
"chars": 1358,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp02-bin\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:1\n#SBATCH --cpus-per-tas"
},
{
"path": "scripts/kwyk_reproduction/experiments/03_20260330_warmstart_diagnostic/README.md",
"chars": 781,
"preview": "# Experiment 03: Warm-start transfer diagnostic\n\n## Rationale\n\nExperiment 01 showed that Bayesian model weights have zer"
},
{
"path": "scripts/kwyk_reproduction/experiments/03_20260330_warmstart_diagnostic/diagnose.py",
"chars": 6410,
"preview": "#!/usr/bin/env python\n\"\"\"Diagnose warm-start transfer from MeshNet to KWYKMeshNet.\"\"\"\n\nfrom __future__ import annotation"
},
{
"path": "scripts/kwyk_reproduction/experiments/03_20260330_warmstart_diagnostic/results_summary.md",
"chars": 1393,
"preview": "# Experiment 03 Results\n\n## Key Finding: Warm-start transfer bug — sorted key ordering mismatch\n\n**MeshNet Dice (trained"
},
{
"path": "scripts/kwyk_reproduction/experiments/03_20260330_warmstart_diagnostic/run.sbatch",
"chars": 515,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp03-ws\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:1\n#SBATCH --cpus-per-task"
},
{
"path": "scripts/kwyk_reproduction/experiments/04_20260330_fixed_warmstart/README.md",
"chars": 715,
"preview": "# Experiment 04: Fix warm-start and verify Bayesian learning\n\n## Rationale\n\nExperiment 03 found that `warmstart_kwyk_fro"
},
{
"path": "scripts/kwyk_reproduction/experiments/04_20260330_fixed_warmstart/run.py",
"chars": 8833,
"preview": "#!/usr/bin/env python\n\"\"\"Fix warm-start, verify transfer, train Bayesian, evaluate.\"\"\"\n\nfrom __future__ import annotatio"
},
{
"path": "scripts/kwyk_reproduction/experiments/04_20260330_fixed_warmstart/run.sbatch",
"chars": 521,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp04-fix\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:1\n#SBATCH --cpus-per-tas"
},
{
"path": "scripts/kwyk_reproduction/experiments/05_20260330_kwyk_from_scratch/README.md",
"chars": 734,
"preview": "# Experiment 05: KWYKMeshNet from scratch (no warm-start)\n\n## Rationale\n\nExperiments 03-04 showed warm-start doesn't tra"
},
{
"path": "scripts/kwyk_reproduction/experiments/05_20260330_kwyk_from_scratch/results_summary.md",
"chars": 1724,
"preview": "# Experiment 05 Results\n\n## Key Finding: mc=False during training is REQUIRED for KWYKMeshNet to learn\n\n| Condition | mc"
},
{
"path": "scripts/kwyk_reproduction/experiments/05_20260330_kwyk_from_scratch/run.py",
"chars": 6521,
"preview": "#!/usr/bin/env python\n\"\"\"Train KWYKMeshNet from scratch: mc=True vs mc=False, 50-class vs binary.\"\"\"\n\nfrom __future__ im"
},
{
"path": "scripts/kwyk_reproduction/experiments/05_20260330_kwyk_from_scratch/run.sbatch",
"chars": 515,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp05-scratch\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:1\n#SBATCH --cpus-per"
},
{
"path": "scripts/kwyk_reproduction/experiments/06_20260331_fullvol_augment/README.md",
"chars": 918,
"preview": "# Experiment 06: Full-volume (256³) training with augmentation\n\n## Rationale\n\nCurrent training uses 32³ patches — the mo"
},
{
"path": "scripts/kwyk_reproduction/experiments/06_20260331_fullvol_augment/config_256.yaml",
"chars": 623,
"preview": "# Full 256³ volume training — requires H200 or multi-GPU\n\nfilters: 96\nreceptive_field: 37\ndropout_rate: 0.25\nsigma_init:"
},
{
"path": "scripts/kwyk_reproduction/experiments/06_20260331_fullvol_augment/config_256_mp.yaml",
"chars": 551,
"preview": "# Full 256³ volume — model parallel across 2 GPUs\n\nfilters: 96\nreceptive_field: 37\ndropout_rate: 0.25\nsigma_init: 0.0001"
},
{
"path": "scripts/kwyk_reproduction/experiments/06_20260331_fullvol_augment/config_fullvol.yaml",
"chars": 596,
"preview": "# Full-volume training with augmentation — 50-class parcellation\n# Uses 128³ blocks (fits on L40S) or 256³ on larger GPU"
},
{
"path": "scripts/kwyk_reproduction/experiments/06_20260331_fullvol_augment/run_128.sbatch",
"chars": 1808,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp06-128\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:1\n#SBATCH --cpus-per-tas"
},
{
"path": "scripts/kwyk_reproduction/experiments/06_20260331_fullvol_augment/run_256.sbatch",
"chars": 1778,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp06-256\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:h200:1\n#SBATCH --cpus-pe"
},
{
"path": "scripts/kwyk_reproduction/experiments/06_20260331_fullvol_augment/run_256_a100.sbatch",
"chars": 1916,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp06-256\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:h200:1\n#SBATCH --cpus-pe"
},
{
"path": "scripts/kwyk_reproduction/experiments/06_20260331_fullvol_augment/run_256_gradckpt.sbatch",
"chars": 2722,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp06-gc\n#SBATCH --partition=pi_satra\n#SBATCH --gres=gpu:a100:1\n#SBATCH --cpus-per-task=8"
},
{
"path": "scripts/kwyk_reproduction/experiments/06_20260331_fullvol_augment/run_256_mp.sbatch",
"chars": 1994,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp06-mp\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:2\n#SBATCH --cpus-per-task"
},
{
"path": "scripts/kwyk_reproduction/experiments/07_20260401_ddp_128/config.yaml",
"chars": 299,
"preview": "# DDP test: 128³ patches on 2× L40S\n\nfilters: 96\nreceptive_field: 37\ndropout_rate: 0.25\n\nblock_shape: [128, 128, 128]\nlr"
},
{
"path": "scripts/kwyk_reproduction/experiments/07_20260401_ddp_128/run.sbatch",
"chars": 1086,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp07-ddp\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:2\n#SBATCH --cpus-per-tas"
},
{
"path": "scripts/kwyk_reproduction/experiments/08_20260401_ddp_128_full/config.yaml",
"chars": 309,
"preview": "# DDP 128³ on full dataset (11,479 subjects)\n\nfilters: 96\nreceptive_field: 37\ndropout_rate: 0.25\n\nblock_shape: [128, 128"
},
{
"path": "scripts/kwyk_reproduction/experiments/08_20260401_ddp_128_full/run.sbatch",
"chars": 1264,
"preview": "#!/bin/bash\n#SBATCH --job-name=exp08-full\n#SBATCH --partition=mit_preemptable\n#SBATCH --gres=gpu:2\n#SBATCH --cpus-per-ta"
}
]
// ... and 20 more files (download for full content)
About this extraction
This page contains the full source code of the neuronets/nobrainer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 220 files (898.7 KB), approximately 241.8k tokens, and a symbol index with 1108 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.