Create a brain model from completed FreeSurfer results. FSQC generates the surface files, and this repository's script converts, combines, and smooths them into an STL file. You can then import that file into 3D modeling software or prepare it for printing.
The workflow is:
- Process a T1-weighted MRI with FreeSurfer and check the reconstruction.
- Run FSQC with
--shapeto generate BrainPrint VTK surfaces. - Run
3Dprintprep.pyor its container once per subject to create a combined STL. - Inspect and repair the mesh, choose its orientation and size, and prepare it in your printer's slicing software.
- You need completed FreeSurfer subject directories, not just the original MRI files. A T1 NIfTI or T1 DICOM series can be used for the earlier FreeSurfer processing step. See the FreeSurfer documentation for installation and reconstruction instructions.
- FreeSurfer must be available to the environment running FSQC's shape analysis, with a valid license.
- Choose how to run FSQC and how to run the STL conversion. These are two separate tools: the
3dprintprepcontainer converts existing surfaces; it does not run FreeSurfer reconstruction or FSQC. - The conversion writes intermediate STL files into the input
surfacesdirectory. You need write access there as well as to the final output directory.
FSQC can run as an installed command, a Python package, or a container. Whichever method you choose, include --shape (or shape=True in Python).
The examples below use a FreeSurfer subjects directory called fs_subjects, a subject called sub-001, and an output directory called fsqc_out. Replace these with your own paths and subject IDs. The subjects directory is the parent of the individual subject directories:
fs_subjects/
└── sub-001/
├── mri/
├── surf/
└── stats/
Use an isolated environment, such as a Python virtual environment or Conda/Mamba environment. For example:
python3 -m venv /path/to/fsqc-env
source /path/to/fsqc-env/bin/activate
python -m pip install fsqcSet up FreeSurfer in that shell using your installation's setup instructions or your cluster's module. Then run:
run_fsqc \
--subjects_dir ./fs_subjects \
--subjects sub-001 \
--output_dir ./fsqc_out \
--shapeList several subject IDs after --subjects to process several subjects, or omit that option to let FSQC select subjects from the subjects directory. For this printing workflow, selecting subjects explicitly makes the next step easier to follow.
If you prefer to call FSQC from Python in the same configured environment:
import fsqc
fsqc.run_fsqc(
subjects_dir="./fs_subjects",
subjects=["sub-001"],
output_dir="./fsqc_out",
shape=True,
)You can create your own FSQC image using the upstream container instructions. An example using the upstream Docker image as the base is:
apptainer build fsqc.sif docker://deepmi/fsqcdocker:2.1.7That creates an FSQC image, but does not add FreeSurfer. For --shape, you must also provide a compatible Linux FreeSurfer installation, its environment, and its license inside the container. Use the course script's bind-and-environment pattern as an example, adapting every host path. This build command does not establish how the instructor's shared .sif was built or which version it contains.
FSQC also provides a Docker image and Docker instructions. The upstream image does not include FreeSurfer, and its documentation notes the resulting limitation for --shape. For this workflow, use a container setup that supplies a compatible Linux FreeSurfer installation and license, or use the installed FSQC or HiPerGator method above. Running the unmodified FSQC Docker image alone is insufficient for generating these shape surfaces.
After shape analysis, check for:
fsqc_out/
└── brainprint/
└── sub-001/
└── surfaces/
├── lh.pial.vtk
├── rh.pial.vtk
└── aseg.final.*.vtk
The conversion needs both pial surfaces and the specific segmentation surfaces used by 3Dprintprep.py. Pass this subject's brainprint/sub-001/surfaces directory to the converter. FSQC's separate surface screenshots are not the VTK input for this step.
Choose one of the following methods. Each invocation converts one subject. Repeat it for each subject, using a different output filename.
Download or clone this repository. In an isolated Python environment, install the conversion dependencies. These pins match the repository's current Dockerfile:
python -m pip install numpy numpy-stl pymeshlab==2025.7.post1 vtk==9.7.0You can use the same environment as FSQC if compatible, or keep separate environments for the two tools. Local installs may also need system libraries for PyMeshLab; the Dockerfile lists the libraries used by the container.
From the directory containing 3Dprintprep.py, run:
python 3Dprintprep.py \
./fsqc_out/brainprint/sub-001/surfaces \
./fsqc_out/sub-001.stlRun this from the directory containing fsqc_out:
docker run --rm \
-v "$PWD/fsqc_out/brainprint/sub-001/surfaces:/in" \
-v "$PWD/fsqc_out:/out" \
jjtanner/3dprintprep:latest \
/in /out/sub-001.stlOn Linux, you can add --user "$(id -u):$(id -g)" to keep generated files owned by your user. The mounted input directory must remain writable because the converter saves intermediate files there.
To build the converter from the code in your checkout, run this from the repository root, then use 3dprintprep:local instead of the published image name:
docker build -t 3dprintprep:local ./DockerBuild the image once, then run it against an existing FSQC output directory:
apptainer build 3dprintprep.sif docker://jjtanner/3dprintprep:latest
apptainer run \
-B "$PWD/fsqc_out/brainprint/sub-001/surfaces:/in" \
-B "$PWD/fsqc_out:/out" \
3dprintprep.sif \
/in /out/sub-001.stlIf your system uses Singularity, replace apptainer with singularity.
Published latest images and existing .sif files may contain a different script version from your checkout. Record the versions you use; rebuilding or replacing a .sif is a separate step from updating repository code.
The script converts VTK files to STL, combines and smooths the left/right pial surfaces, combines and smooths the selected non-cortical structures, and merges the smoothed meshes into the final brain STL.
Intermediate files stay in the input surfaces directory, including cortex.stl, cortex_smoothed.stl, non-cortex.stl, and non-cortex_smoothed.stl. The combined brain is saved to the output filename you supplied. Merging meshes does not guarantee a watertight, printable solid; inspect the result before printing. The developer of this script has not had issues with printing different brains, however.
Import the combined STL into your preferred 3D modeling or mesh-repair software. Check for missing structures, unwanted shells, disconnected pieces, and mesh errors. Repair the model as needed, confirm its size, and choose an orientation before slicing it for your printer.
One approach used for this workflow is to import the STL into 3D Builder on Windows 10 or 11, fix the errors reported on import, and use Settle to rest the model on the medulla and temporal lobes. Use this approach if you already have that software available; another mesh-repair tool is fine. Then review supports and print settings in your slicer.
- No
brainprint/<subject>/surfacesdirectory: check the FSQC log, subject ID, completed FreeSurfer results, and--shapeoption. Also check that FreeSurfer and the license are visible inside the FSQC environment. A top-levelbrainprintdirectory alone does not prove every subject finished. - Missing pial or
aseg.final.*files: the converter expects the filenames listed in the current script. Confirm that shape analysis generated the complete set of surfaces. - FreeSurfer/license errors inside the course container: check
module load freesurfer/7.4.1, the$FREESURFER_HOMEbind,-B /apps, and theFS_LICENSEsetting. - Permission errors: both the input surfaces directory and output directory must be writable. Copy course scripts to your own directory before editing them.
- Python package or smoothing errors: compare your environment with the current Dockerfile, or use a matching conversion container. An older shared image may behave differently from the current script.
- Job fails or runs out of time/memory: read the Slurm log before resubmitting and adjust resources as needed. An STL file's existence is not a substitute for visually checking it.