recipes/relion/meta.yaml lists ghostscript under requirements: host: but not under requirements: run::
host:
- libtiff
- fftw
- ghostscript # <-- here
- libpng
...
run:
- openmpi
- llvm-openmp # [osx]
- libgomp # [linux]
- tbb-devel # [x86_64]
- pytorch
# <-- not here
ghostscript is a runtime tool dependency, not a library RELION links against — RELION shells out to the gs binary — so a host-only entry means gs is absent in the installed environment. Verified on relion=5.1.0 (build h4615e1f_0, linux-aarch64): gs is not on PATH.
This is not cosmetic. It turns a successful analysis into a non-zero exit.
The failure chain
relion_postprocess finishes its actual work, writes its output maps and postprocess.star, then assembles a PDF log via joinMultipleEPSIntoSinglePDF (src/CPlot2D.cpp:29, RELION 5.1.0):
std::string command = "gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dSAFER ... -sOutputFile=";
...
if (system(command.c_str()))
{
std::cerr << " ERROR in executing: " << command << "\n";
have_error_in_gs = true;
}
...
if (!have_at_least_one || have_error_in_gs)
{
std::cerr << " + Will make an empty PDF-file in " << fn_pdf << "\n";
touch(fn_pdf); // <-- fatal when dirname(fn_pdf) does not exist
}
The fallback is meant to degrade gracefully — a missing gs should cost you the PDF and nothing else. But fn_pdf is dirname(--o)/logfile.pdf, and RELION's own documented usage passes a bare output prefix, so that directory does not exist. touch then throws and the process dies:
sh: 1: gs: not found
ERROR in executing: gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -dSAFER -dDEVICEWIDTHPOINTS=800 -dDEVICEHEIGHTPOINTS=800 -sOutputFile=pp/logfile.pdf @pp/logfile.pdf.lst > /dev/null
+ Will make an empty PDF-file in pp/logfile.pdf
Filename::touch ERROR: Cannot open file: pp/logfile.pdf
Non-zero exit, with correct scientific output already on disk. In any pipeline that checks exit codes — which is all of them — this reads as a failed job.
Reproduction
docker run --rm -it quay.io/biocontainers/relion:5.1.0--h4615e1f_0 bash
# ... obtain two unfiltered half-maps + a mask (e.g. any RELION Refine3D output) ...
relion_postprocess --mask mask.mrc --i run_half1_class001_unfil.mrc \
--o pp --angpix 0.968 --auto_bfac --autob_lowres 10
echo "exit=$?" # non-zero
command -v gs # nothing
The bare prefix --o pp is the trigger, and it is the form RELION's own tutorial and GUI use.
Workaround for anyone who lands here first: pass an absolute --o whose parent exists, e.g. --o /tmp/postprocess. The empty-PDF fallback then lands somewhere writable and the run exits 0. The two gs lines in the log are expected noise in that case, which matters if you grep logs for ERROR.
Fix
Add ghostscript to run: (keeping it in host: is harmless) and bump build: number. It's available for every platform this recipe targets — conda-forge ghostscript 10.07.1 has linux-64, linux-aarch64 (hfae3067_0), osx-64 and osx-arm64 builds, and the recipe declares additional-platforms: [linux-aarch64, osx-arm64], so nothing is left behind.
Worth a look at whether other RELION entry points hit the same path — joinMultipleEPSIntoSinglePDF is in the shared CPlot2D unit, so anything that emits a PDF log is exposed, not just relion_postprocess.
Happy to open the PR if that's easier than doing it inline.
Found while building a reproducible arm64 cryo-EM example (a relion_postprocess run reproducing an archived EMPIAR-10581 result byte-for-byte on Graviton4 — all 221 FSC shells identical, so the science in the image is fine; only the packaging bit us).
recipes/relion/meta.yamllistsghostscriptunderrequirements: host:but not underrequirements: run::ghostscriptis a runtime tool dependency, not a library RELION links against — RELION shells out to thegsbinary — so a host-only entry meansgsis absent in the installed environment. Verified onrelion=5.1.0(buildh4615e1f_0, linux-aarch64):gsis not onPATH.This is not cosmetic. It turns a successful analysis into a non-zero exit.
The failure chain
relion_postprocessfinishes its actual work, writes its output maps andpostprocess.star, then assembles a PDF log viajoinMultipleEPSIntoSinglePDF(src/CPlot2D.cpp:29, RELION 5.1.0):The fallback is meant to degrade gracefully — a missing
gsshould cost you the PDF and nothing else. Butfn_pdfisdirname(--o)/logfile.pdf, and RELION's own documented usage passes a bare output prefix, so that directory does not exist.touchthen throws and the process dies:Non-zero exit, with correct scientific output already on disk. In any pipeline that checks exit codes — which is all of them — this reads as a failed job.
Reproduction
The bare prefix
--o ppis the trigger, and it is the form RELION's own tutorial and GUI use.Workaround for anyone who lands here first: pass an absolute
--owhose parent exists, e.g.--o /tmp/postprocess. The empty-PDF fallback then lands somewhere writable and the run exits 0. The twogslines in the log are expected noise in that case, which matters if you grep logs forERROR.Fix
Add
ghostscripttorun:(keeping it inhost:is harmless) and bumpbuild: number. It's available for every platform this recipe targets — conda-forgeghostscript10.07.1 haslinux-64,linux-aarch64(hfae3067_0),osx-64andosx-arm64builds, and the recipe declaresadditional-platforms: [linux-aarch64, osx-arm64], so nothing is left behind.Worth a look at whether other RELION entry points hit the same path —
joinMultipleEPSIntoSinglePDFis in the sharedCPlot2Dunit, so anything that emits a PDF log is exposed, not justrelion_postprocess.Happy to open the PR if that's easier than doing it inline.
Found while building a reproducible arm64 cryo-EM example (a
relion_postprocessrun reproducing an archived EMPIAR-10581 result byte-for-byte on Graviton4 — all 221 FSC shells identical, so the science in the image is fine; only the packaging bit us).