Skip to content

Commit 9168e26

Browse files
committed
Lots of changes to quicktest
1 parent 9ee2849 commit 9168e26

32 files changed

+986
-860
lines changed

.github/actions/run-fastsurfer/action.yml

+41-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# The build artifact includes the image in docker-image.tar and the name of the image in image_name.env
55

66
name: 'Run Fastsurfer'
7+
author: 'David Kuegler'
78
description: 'Runs FastSurfer'
89

910
inputs:
@@ -30,21 +31,24 @@ inputs:
3031
license:
3132
required: true
3233
description: "The FreeSurfer license"
34+
cleanup:
35+
type: string
36+
default: 'false'
37+
description: "Whether all docker images and containers should be cleaned"
3338

3439
runs:
3540
using: "composite"
3641
steps:
37-
- name: Load the docker image
38-
id: load-docker
39-
uses: ./.github/actions/load-docker
40-
with:
41-
docker-image: ${{ inputs.docker-image }}
4242
- name: Set up FreeSurfer License and download T1 image
43+
# This is a failfast step and thus should be run first
4344
shell: bash
45+
id: prep
46+
continue-on-error: false
4447
env:
4548
IMAGE_HREF: ${{ inputs.image-href }}
4649
EXTRA_ARGS: ${{ inputs.extra-args }}
4750
run: |
51+
# Preparation code
4852
echo "::group::Check arguments and prepare directories"
4953
DATA_DIR=$(dirname $(pwd))/data
5054
echo "DATA_DIR=$DATA_DIR" >> $GITHUB_ENV # DATA_DIR is used in the next 2 steps as well
@@ -58,15 +62,32 @@ runs:
5862
echo "Could not find subject folder for ${{ inputs.subject-id }}!"
5963
exit 1
6064
fi
65+
# Check if the docker needs to be loaded (fail if not)
66+
if [[ -z "$(docker images -q "${{ inputs.docker-image }}")" ]] ; then di="0" ; else di="1" ; fi
67+
echo "IMAGE_LOADED=$di" >> $GITHUB_OUTPUT
6168
echo "::endgroup::"
69+
- name: Load the docker image
70+
if: steps.prep.outputs.IMAGE_LOADED == 0
71+
id: load-docker
72+
uses: ./.github/actions/load-docker
73+
with:
74+
docker-image: ${{ inputs.docker-image }}
75+
- name: Reuse loaded image
76+
if: steps.prep.outputs.IMAGE_LOADD != 0
77+
id: load-docker
78+
shell: bash
79+
run: |
80+
# populate image-name
81+
echo "image-name=${{ inputs.docker-image }}" > $GITHUB_OUTPUT
6282
- name: FastSurfer Segmentation pipeline
6383
shell: bash
6484
if: ${{ ! contains( inputs.extra-args, '--surf_only' ) }}
6585
env:
6686
IMAGE_NAME: ${{ steps.load-docker.outputs.image-name }}
6787
run: |
88+
# Segmentation call
6889
echo "::group::FastSurfer Segmentation"
69-
docker run --rm -t -v "$DATA_DIR:/data" -u $(id -u):$(id -g) --env TQDM_DISABLE=1 "$IMAGE_NAME" \
90+
docker run -t -v "$DATA_DIR:/data" -u $(id -u):$(id -g) --env TQDM_DISABLE=1 --name seg_container "$IMAGE_NAME" \
7091
--fs_license /data/.fs_license --t1 /data/T1${{ inputs.file-extension }} --sid "${{ inputs.subject-id }}" \
7192
--sd /data --threads $(nproc) --seg_only ${{ inputs.extra-args }}
7293
echo "::endgroup::"
@@ -76,15 +97,27 @@ runs:
7697
env:
7798
IMAGE_NAME: ${{ steps.load-docker.outputs.image-name }}
7899
run: |
100+
# Surface pipeline call
79101
echo "::group::FastSurfer Surface reconstruction"
80-
docker run --rm -t -v "$DATA_DIR:/data" -u $(id -u):$(id -g) --env TQDM_DISABLE=1 "$IMAGE_NAME" \
102+
docker run -t -v "$DATA_DIR:/data" -u $(id -u):$(id -g) --env TQDM_DISABLE=1 --name surf_container "$IMAGE_NAME" \
81103
--fs_license /data/.fs_license --t1 /data/T1${{ inputs.file-extension }} --sid "${{ inputs.subject-id }}" \
82104
--sd /data --parallel --threads 1 --surf_only ${{ inputs.extra-args }}
83105
echo "::endgroup::"
106+
- name: Docker image and container cleanup
107+
if: inputs.cleanup == 'true'
108+
shell: bash
109+
run: |
110+
# Cleanup code
111+
echo "::group::Docker image and container cleanup"
112+
if [[ "${{ steps.prep.outputs.IMAGE_LOADED }}" == 0 ]] ; then docker image rm "${{ steps.load-docker.outputs.image-name }}" ; fi
113+
if [[ "${{ contains( inputs.extra-args, '--surf_only' ) }}" == "0" ]] ; then docker container rm seg_container ; fi
114+
if [[ "${{ contains( inputs.extra-args, '--seg_only' ) }}" == "0" ]] ; then docker container rm surf_container ; fi
115+
echo "::endgroup::"
84116
- name: Create archive of Processed subject-data
85117
shell: bash
86118
run: |
87-
echo "::group::Save ${{ inputs.subject-id }} as artifact"
119+
# Archive data code
120+
echo "::group::Save ${{ inputs.subject-id }} as an artifact"
88121
tar cfz "$DATA_DIR/${{ inputs.subject-id }}.tar.gz" -C "$DATA_DIR" "${{ inputs.subject-id }}"
89122
- name: Save processed data
90123
uses: actions/upload-artifact@v4

.github/actions/run-tests/action.yml

+15-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ inputs:
2323
required: true
2424
type: string
2525
description: "The url to the reference file to download for comparison"
26+
junit-file:
27+
default: /tmp/quicktest.junit.xml
28+
type: string
29+
description: "The path where the JUnit-XML file should be saved"
2630

2731
runs:
2832
using: "composite"
@@ -67,6 +71,15 @@ runs:
6771
shell: bash
6872
run: |
6973
echo "::group::Run tests"
70-
python -m pytest test/quicktest
74+
flags=("--junit-xml=${{ inputs.junit-file }}")
75+
if [[ "$ACTION_RUNNER_DEBUG" == "true" ]]
76+
then
77+
flags+=(-vv --log_cli_level=DEBUG)
78+
fi
79+
python -m pytest "${flags[@]}" test/quicktest
7180
echo "::endgroup::"
72-
81+
- name: Upload the the JUnit XML file as an artifact
82+
uses: actions/upload-artifact@v4
83+
with:
84+
name: fastsurfer-${{ github.sha }}-junit-${{ inputs.subject-id }}
85+
path: ${{ inputs.junit-file }}

.github/workflows/quicktest.yaml

+41-29
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ on:
2828
workflow_dispatch:
2929
inputs:
3030
docker-image:
31-
description: 'Which docker image should be used to run this test (build-cached => build from git)'
31+
description: 'Which docker image should be used to run this test (build-cached => build from git, also pr/<#no>)'
3232
default: build-cached
3333
type: string
3434
freesurfer-build-image:
35-
description: 'FreeSurfer build image to build with (default="": deep MI/fastsurfer-build:freesurferXXX; extract version from Docker/install_fs_pruned.sh)'
35+
description: 'FreeSurfer build image to build with ("" (default) => deepmi/fastsurfer-build:freesurferXXX; extract version from Docker/install_fs_pruned.sh)'
3636
type: string
3737

3838
permissions: read-all
@@ -117,16 +117,23 @@ jobs:
117117
# this may also need adaptations in the test script at the bottom
118118
echo "EXTRA_ARGS=" >> $GITHUB_OUTPUT
119119
- name: Checkout repository
120-
if: ${{ steps.parse.outputs.CONTINUE == 'true' && steps.parse.outputs.DOCKER_IMAGE == 'build-cached' }}
120+
if: steps.parse.outputs.CONTINUE == 'true' && steps.parse.outputs.DOCKER_IMAGE == 'build-cached'
121+
uses: actions/checkout@v4
122+
- name: Checkout repository
123+
if: steps.parse.outputs.CONTINUE == 'true' && (steps.parse.outputs.DOCKER_IMAGE == 'build-cached' || startsWith(steps.parse.outputs.DOCKER_IMAGE, 'pr/'))
121124
uses: actions/checkout@v4
125+
with:
126+
ref: ${{ steps.parse.outputs.DOCKER_IMAGE }}
122127
- name: Get the FreeSurfer version
123-
if: ${{ steps.parse.outputs.CONTINUE == 'true' && steps.parse.outputs.DOCKER_IMAGE == 'build-cached' }}
128+
if: steps.parse.outputs.CONTINUE == 'true' && (steps.parse.outputs.DOCKER_IMAGE == 'build-cached' || startsWith(steps.parse.outputs.DOCKER_IMAGE, 'pr/'))
124129
shell: bash
125130
id: parse-version
126131
run: |
127132
# get the FreeSurfer version from install_fs_pruned.sh
128133
{
129-
eval "$(grep "^fslink=" ./Docker/install_fs_pruned.sh)"
134+
fslink="$(grep "^fslink=" ./Docker/install_fs_pruned.sh)"
135+
fslink="${fslink:7}"
136+
if [[ "${fslink:0:1}" == '"' ]] || [[ "${fslink:0:1}" == "'" ]] ; then fslink="${fslink:1:-1}" ; fi
130137
fs_version="$(basename "$(dirname "$fslink")")"
131138
fs_version_short="${fs_version//\./}"
132139
echo "FS_VERSION=$fs_version"
@@ -146,8 +153,8 @@ jobs:
146153
# currently, this image has to be updated and used to circumvent storage limitations in github actions
147154
# and it is also faster to use this prebuilt, reduced-size freesurfer distribution
148155
freesurfer-build-image: "${{ steps.parse-version.outputs.FS_BUILD_IMAGE }}"
149-
fastsurfer:
150-
name: 'Run FastSurfer on sample images'
156+
fastsurfer-test:
157+
name: 'Run FastSurfer on sample images and perform tests'
151158
needs: build-docker
152159
runs-on: ubuntu-latest
153160
timeout-minutes: 180
@@ -161,43 +168,48 @@ jobs:
161168
- subject-id: 1.0mm
162169
file-extension: ".mgz"
163170
image-key: QUICKTEST_IMAGE_HREF_1mm
171+
case-key: QUICKTEST_TARGET_HREF_1mm
164172
- subject-id: 0.8mm
165173
file-extension: ".nii.gz"
166174
image-key: QUICKTEST_IMAGE_HREF_08mm
175+
case-key: QUICKTEST_TARGET_HREF_08mm
167176
steps:
168-
- uses: actions/checkout@v4
169-
with:
170-
sparse-checkout: .github/actions
177+
- name: Check out the repository that is to be tested
178+
uses: actions/checkout@v4
171179
- name: Run FastSurfer for ${{ matrix.subject-id }} with the previously created docker container
172-
uses: Deep-MI/FastSurfer/.github/actions/run-fastsurfer@dev
180+
# uses: Deep-MI/FastSurfer/.github/actions/run-fastsurfer@dev
181+
uses: ./.github/actions/run-fastsurfer@dev
173182
with:
174183
subject-id: ${{ matrix.subject-id }}
175184
file-extension: ${{ matrix.file-extension }}
176185
image-href: ${{ secrets[matrix.image-key] }}
177186
license: ${{ secrets.QUICKTEST_LICENSE }}
178187
docker-image: ${{ needs.build-docker.outputs.docker-image }}
179188
extra-args: ${{ needs.build-docker.outputs.extra-args }}
180-
tests:
181-
name: 'Download data and perform tests'
182-
needs: fastsurfer
183-
runs-on: ubuntu-latest
184-
timeout-minutes: 60
185-
strategy:
186-
# the following matrix strategy will result in one run per subject as matrix is "one-dimensional".
187-
# Additional parameters under "include" are then added as additional (dependent) information
188-
matrix:
189-
subject-id: [0.8mm, 1.0mm]
190-
include:
191-
- subject-id: 0.8mm
192-
case-key: QUICKTEST_TARGET_HREF_08mm
193-
- subject-id: 1.0mm
194-
case-key: QUICKTEST_TARGET_HREF_1mm
195-
steps:
196-
- uses: actions/checkout@v4
189+
cleanup: 'true'
197190
- name: Run tests
198-
uses: Deep-MI/FastSurfer/.github/actions/run-tests@dev
191+
# uses: Deep-MI/FastSurfer/.github/actions/run-tests@dev
192+
uses: ./.github/actions/run-tests
199193
with:
200194
subject-id: ${{ matrix.subject-id }}
201195
subjects-dir: ${{ env.SUBJECTS_DIR }}
202196
reference-dir: ${{ env.REFERENCE_DIR }}
203197
case-href: ${{ secrets[matrix.case-key] }}
198+
junit-file: /tmp/fastsurfer-quicktest-${{ matrix.subject-id }}.junit.xml
199+
annotation:
200+
name: Annotate test results as checks
201+
runs-on: ubuntu-latest
202+
needs: [fastsurfer-test, build-docker]
203+
if: always() && needs.build-docker.outputs.continue == 'true'
204+
steps:
205+
- name: Retrieve test JUnit files
206+
uses: actions/download-artifact@v4
207+
with:
208+
pattern: fastsurfer-${{ github.sha }}-junit-*
209+
merge-multiple: 'true'
210+
path: /tmp
211+
- name: Write the results into the check
212+
uses: mikepenz/action-junit-report@v5
213+
with:
214+
report_paths: /tmp/fastsurfer-quicktest-*.junit.xml
215+
check_name: Annotate the test results as checks

CerebNet/inference.py

+10-24
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,7 @@ def _calc_segstats(
277277
"""
278278

279279
def _get_ids_startswith(_label_map: dict[int, str], prefix: str) -> list[int]:
280-
return [
281-
id
282-
for id, name in _label_map.items()
283-
if name.startswith(prefix) and not name.endswith("Medullare")
284-
]
280+
return [id for id, name in _label_map.items() if name.startswith(prefix) and not name.endswith("Medullare")]
285281

286282
freesurfer_id2cereb_name = self.cereb_name2fs_id.__reversed__()
287283
freesurfer_id2name = self.freesurfer_name2id.__reversed__()
@@ -291,10 +287,7 @@ def _get_ids_startswith(_label_map: dict[int, str], prefix: str) -> list[int]:
291287
47: ("Right", "Right-Cerebellum-Cortex"),
292288
632: ("Vermis", "Cbm_Vermis"),
293289
}
294-
merge_map = {
295-
id: _get_ids_startswith(label_map, prefix=prefix)
296-
for id, (prefix, _) in meta_labels.items()
297-
}
290+
merge_map = {id: _get_ids_startswith(label_map, prefix=prefix) for id, (prefix, _) in meta_labels.items()}
298291

299292
# calculate PVE
300293
from FastSurferCNN.segstats import pv_calc
@@ -375,22 +368,16 @@ def _get_subject_dataset(
375368
from FastSurferCNN.utils.parser_defaults import ALL_FLAGS
376369

377370
raise ValueError(
378-
f"Cannot resolve the intended filename "
379-
f"{subject.get_attribute('cereb_statsfile')} for the "
380-
f"cereb_statsfile, maybe specify an absolute path via "
381-
f"{ALL_FLAGS['cereb_statsfile'](dict)['flag']}."
371+
f"Cannot resolve the intended filename {subject.get_attribute('cereb_statsfile')} for the "
372+
f"cereb_statsfile, maybe specify an absolute path via {ALL_FLAGS['cereb_statsfile'](dict)['flag']}."
382373
)
383-
if not subject.has_attribute(
384-
"norm_name"
385-
) or not subject.fileexists_by_attribute("norm_name"):
374+
if not subject.has_attribute("norm_name") or not subject.fileexists_by_attribute("norm_name"):
386375
from FastSurferCNN.utils.parser_defaults import ALL_FLAGS
387376

388377
raise ValueError(
389-
f"Cannot resolve the file name "
390-
f"{subject.get_attribute('norm_name')} for the bias field "
391-
f"corrected image, maybe specify an absolute path via "
392-
f"{ALL_FLAGS['norm_name'](dict)['flag']} or the file does not "
393-
f"exist."
378+
f"Cannot resolve the file name {subject.get_attribute('norm_name')} for the bias field corrected "
379+
f"image, maybe specify an absolute path via {ALL_FLAGS['norm_name'](dict)['flag']} or the file "
380+
f"does not exist."
394381
)
395382

396383
norm_file = subject.filename_by_attribute("norm_name")
@@ -510,9 +497,8 @@ def run(self, subject_dirs: SubjectList):
510497
)
511498

512499
logger.info(
513-
f"Subject {idx + 1}/{len(subject_dirs)} with id "
514-
f"'{subject.id}' processed in {pred_time - start_time :.2f} "
515-
f"sec."
500+
f"Subject {idx + 1}/{len(subject_dirs)} with id '{subject.id}' processed in "
501+
f"{pred_time - start_time :.2f} sec."
516502
)
517503
except Exception as e:
518504
logger.exception(e)

0 commit comments

Comments
 (0)