Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9d0dc7a
move back to dev version
m-reuter May 22, 2026
9cbc3db
add dependabot for workflow action updates
m-reuter May 23, 2026
ed9dcbd
Bump actions/setup-python from 5 to 6
dependabot[bot] May 23, 2026
eecc5e5
Bump actions/upload-artifact from 4 to 7
dependabot[bot] May 23, 2026
e61fb91
Bump astral-sh/setup-uv from 5 to 7
dependabot[bot] May 23, 2026
b2dc55b
Bump actions/checkout from 4 to 6
dependabot[bot] May 23, 2026
c4f06f9
Bump actions/download-artifact from 4 to 8
dependabot[bot] May 23, 2026
518f62c
Merge pull request #822 from Deep-MI/dependabot/github_actions/action…
m-reuter May 23, 2026
43d4550
Merge pull request #823 from Deep-MI/dependabot/github_actions/action…
m-reuter May 23, 2026
d7e0480
Merge pull request #824 from Deep-MI/dependabot/github_actions/astral…
m-reuter May 23, 2026
710d725
Merge pull request #825 from Deep-MI/dependabot/github_actions/action…
m-reuter May 23, 2026
30497c6
Merge pull request #826 from Deep-MI/dependabot/github_actions/action…
m-reuter May 23, 2026
d1b32f0
push neuroreg version to guard getuser in docker for lta write
m-reuter May 27, 2026
bb23b79
Fix surfaceRAS header check
ClePol May 21, 2026
e49bc9d
remove randomness from lapy.fem.eigs call
m-reuter May 26, 2026
732f71b
Speed up aparc smoothing mode filter
ClePol May 15, 2026
8160aae
copy labels to avoid silent shadow
m-reuter May 26, 2026
3ef52a3
Merge pull request #827 from Deep-MI/surf
m-reuter May 27, 2026
21407ba
CC Visualization fix duplicate point handling (#829)
ClePol May 27, 2026
328a1e2
avoid unexpected number of contour points when levelpath endpoints an…
ClePol May 28, 2026
7076ddb
Merge pull request #830 from ClePol/codex/cc-contour-cardinality
m-reuter May 29, 2026
12bb9f9
Update version from 2.6.0-dev to 2.5.3
m-reuter May 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
4 changes: 2 additions & 2 deletions .github/workflows/code-style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: 'src'
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
with:
python-version: '3.10'
- name: Run Ruff
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ jobs:
- name: Get repository name.
run: echo "FASTSURFER_DIR=$GITHUB_WORKSPACE" >> $GITHUB_ENV
- name: Checkout repository
uses: actions/checkout@v6.0.2
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up python environment
uses: actions/setup-python@v6.2.0
uses: actions/setup-python@v6
with:
python-version: '3.10'
- name: install dependencies
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/doc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ jobs:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
path: src
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
with:
python-version: '3.12'
- name: Build doc
Expand All @@ -33,7 +33,7 @@ jobs:
run: |
uv run --project src --extra doc sphinx-build -WT --keep-going -j auto src/doc doc-build
- name: Upload documentation
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: doc
path: |
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/quicktest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,15 @@ jobs:
- name: Checkout repository
# DOCKER_IMAGE is build-cached => we want to build, check this repository out to build the docker image
if: steps.parse.outputs.CONTINUE == 'true' && steps.parse.outputs.DOCKER_IMAGE == 'build-cached'
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Checkout repository
# DOCKER_IMAGE starts with pr/*** => we want to build, check the PR out to build the docker image
if: steps.parse.outputs.CONTINUE == 'true' && startsWith(steps.parse.outputs.DOCKER_IMAGE, 'pr/')
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: ${{ steps.parse.outputs.DOCKER_IMAGE }}
- name: Setup uv and Python 3.11
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v7
with:
python-version: '3.11'
# python 3.11 is needed to read pyproject.toml (tomllib)
Expand Down Expand Up @@ -195,7 +195,7 @@ jobs:
extra-args: "--3T"
steps:
- name: Check out the repository that is to be tested
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Run FastSurfer for ${{ matrix.subject-id }} with the previously created docker container
uses: Deep-MI/FastSurfer/.github/actions/run-fastsurfer@dev
with:
Expand All @@ -221,7 +221,7 @@ jobs:
if: always() && needs.build-docker.outputs.continue == 'true'
steps:
- name: Retrieve test JUnit files
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
pattern: fastsurfer-${{ github.sha }}-junit-*
merge-multiple: 'true'
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/unittest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ jobs:
pytest-flags: [""]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Setup Python 3.10
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: '3.10'
architecture: 'x64'
Expand Down Expand Up @@ -71,7 +71,7 @@ jobs:
python -m pytest "${flags[@]}" ${{ matrix.pytest-flags }} test/${{ matrix.tests }}
echo "::endgroup::"
- name: Upload the JUnit XML file as an artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: fastsurfer-${{ github.sha }}-${{ matrix.tests }}-junit
path: /tmp/fastsurfer-unittest-${{ matrix.tests }}.junit.xml
Expand Down
5 changes: 5 additions & 0 deletions CorpusCallosum/shape/contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,11 @@ def fill_thickness_values(self) -> None:
closest_indices = known_idx[np.argsort(distances)[:2]]
closest_distances = np.sort(distances)[:2]

zero_distance = closest_distances <= 1e-10
if np.any(zero_distance):
thickness[j] = np.mean(thickness[closest_indices[zero_distance]])
continue

# Calculate weights based on inverse distance
weights = 1.0 / closest_distances
weights = weights / np.sum(weights)
Expand Down
45 changes: 44 additions & 1 deletion CorpusCallosum/shape/thickness.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def insert_point_with_thickness(
point: np.ndarray,
thickness_value: float,
return_index: Literal[True],
) -> tuple[np.ndarray, np.ndarray, int] | list[np.ndarray, np.ndarray]:
) -> tuple[np.ndarray, np.ndarray, int]:
...


Expand Down Expand Up @@ -148,6 +148,49 @@ def insert_point_with_thickness(
insertion_index : int
The index, where the point was inserted (only if return_index is True).
"""
existing_distances = np.linalg.norm(contour_in_as_space[:, :2] - point[:2], axis=1)
existing_matches = np.where(existing_distances <= 1e-10)[0]
if len(existing_matches) > 0:
point_idx = int(existing_matches[0])
if np.isnan(contour_thickness[point_idx]):
contour_thickness[point_idx] = thickness_value

# Keep the contour cardinality stable across slices, but avoid inserting
# an exact duplicate vertex. Exact duplicates create zero-length edges in
# visualization/mesh code, while skipping the point breaks the equal
# point-count invariant expected by CCMesh.from_contours().
before_idx = (point_idx - 1) % len(contour_in_as_space)
after_idx = (point_idx + 1) % len(contour_in_as_space)
before_vector = contour_in_as_space[before_idx, :2] - contour_in_as_space[point_idx, :2]
after_vector = contour_in_as_space[after_idx, :2] - contour_in_as_space[point_idx, :2]
before_length = np.linalg.norm(before_vector)
after_length = np.linalg.norm(after_vector)

if after_length >= before_length and after_length > 0:
insert_idx = point_idx + 1
direction = after_vector / after_length
edge_length = after_length
elif before_length > 0:
insert_idx = point_idx
direction = before_vector / before_length
edge_length = before_length
else:
insert_idx = point_idx + 1
direction = np.zeros_like(point[:2])
edge_length = 0.0

if edge_length > 0:
step = min(max(edge_length * 1e-6, 1e-8), edge_length * 0.5)
point = contour_in_as_space[point_idx, :2] + direction * step

contour_in_as_space = np.insert(contour_in_as_space, insert_idx, point, axis=0)
contour_thickness = np.insert(contour_thickness, insert_idx, thickness_value)

if return_index:
return contour_in_as_space, contour_thickness, insert_idx
else:
return contour_in_as_space, contour_thickness

# Find closest edge for the point
edge_idx = find_closest_edge(point, contour_in_as_space)

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = 'setuptools.build_meta'

[project]
name = 'fastsurfer'
version = '2.5.2'
version = '2.5.3'
description = 'A fast and accurate deep-learning based neuroimaging pipeline'
readme = 'README.md'
license = {file = 'LICENSE'}
Expand Down Expand Up @@ -38,7 +38,7 @@ dependencies = [
'matplotlib>=3.7.1',
'meshpy>=2025.1.1', # needed for FastSurfer-CC
'monai>=1.4.0', # needed for FastSurfer-CC
'neuroreg>=0.6.1', # needed for registration / etiv
'neuroreg>=0.6.2', # needed for registration / etiv
'nibabel>=5.4.0', # needed to fix a bug in nibabel
'numpy>=1.25',
'packaging',
Expand Down
2 changes: 1 addition & 1 deletion recon_surf/recon-surf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ for hemi in lh rh ; do

# Check if the surfaceRAS was correctly set and exit otherwise (sanity check in case nibabel changes their default header behaviour)
{
cmd="mris_info $outmesh | tr -s ' ' | grep -q 'vertex locs : surfaceRAS'"
cmd="mris_info $outmesh | awk '\$1 == \"vertex\" && \$2 == \"locs\" && \$3 == \":\" && \$4 == \"surfaceRAS\" { found = 1 } END { exit !found }'"
echo "echo \"$cmd\""
echo "$timecmd $cmd"
} | tee -a "$CMDF"
Expand Down
15 changes: 5 additions & 10 deletions recon_surf/smooth_aparc.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def mode_filter(
# for num rings exponentiate adjM and add adjM from step before
# we currently do this outside of mode_filter
# new labels will be the same as old almost everywhere
labels_new = labels
labels_new = labels.copy()
# find vertices to fill
# if fillonlylabels empty, fill all
if not fillonlylabel:
Expand All @@ -204,8 +204,6 @@ def mode_filter(
# create sparse matrix with labels at neighbors
nlabels = sparse.csr_matrix((labels[JJ], (II, JJ)))
# print("nlabels: {}".format(nlabels))
from scipy.stats import mode

if not isinstance(nlabels, sparse.csr_matrix):
raise ValueError("Matrix must be CSR format.")
# novote = [-1,0,fillonlylabel]
Expand All @@ -227,19 +225,16 @@ def mode_filter(
rr = np.isin(nlabels.data, novote)
nlabels.data[rr] = 0
nlabels.eliminate_zeros()
# run over all rows and compute mode (maybe vectorize later)
# Run over all rows and compute mode. The labels are non-negative at
# this point; bincount().argmax() matches scipy.stats.mode's smallest-value
# tie behavior without the heavy per-row SciPy dispatch.
rempty = 0
for row in rows:
rvals = nlabels.data[nlabels.indptr[row] : nlabels.indptr[row + 1]]
if rvals.size == 0:
rempty += 1
continue
# print(str(rvals))
mvals = mode(rvals, keepdims=True)[0]
# print(str(mvals))
if mvals.size != 0:
# print(str(row)+' '+str(ids[row])+' '+str(mvals[0]))
labels_new[ids[row]] = mvals[0]
labels_new[ids[row]] = np.bincount(rvals).argmax()
if rempty > 0:
# should not happen
print("WARNING: row empty: " + str(rempty))
Expand Down
2 changes: 1 addition & 1 deletion recon_surf/spherically_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def get_flipped_area(tria):
return area

fem = Solver(tria, lump=False, use_cholmod=use_cholmod)
evals, evecs = fem.eigs(k=4)
evals, evecs = fem.eigs(k=4, rng=0)

if debug:
data = dict()
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ multipledispatch==1.0.0
narwhals==2.21.0
networkx==3.6.1
neurolit==0.6.1
neuroreg==0.6.1
neuroreg==0.6.2
nibabel==5.4.2
numpy==2.4.4
packaging==26.2
Expand Down
Loading