XDS plugin 1.1.0: mask module/chip edges, pass saturation through; recommend Durin
Build Packages / Create release (push) Successful in 16s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 3m20s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 7m11s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 9m57s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 12m39s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 19m41s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 20m21s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 20m36s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 21m50s
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 23m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m30s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 28m22s
Build Packages / Generate python client (push) Successful in 44s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 18m58s
Build Packages / Build documentation (push) Successful in 1m25s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 19m3s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m9s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 17m14s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 18m37s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 14m23s
Build Packages / Unit tests (push) Failing after 1h17m53s

Plugin:
- Module-edge (bit 30) and chip-edge (bit 31) pixels are marked -2 (untrusted). Chip-edge pixels
  are split from a larger pixel with integer division and read low at low counts; feeding them to
  XDS as real pixels was what made the three plugins disagree in CI.
- A saturated pixel in a signed image (INTx_MAX) is passed through with its value, so XDS counts
  it as an overload; only the error marker (INTx_MIN / UINTx_MAX) becomes -1.
- Release version 1.1.0 set in CMake (library name and start message). VERSION_* in plugin.h stay
  0: XDS refuses a plugin whose info-array major version is not 0.
- Built and packaged on Linux only; the untested macOS archive is dropped.
- The XDS linking permission is removed from LICENSE; the plugin is GPL-3.0.

CI:
- XDS.INP beam centre corrected to 1091/1137 (ORGY was 7.7 px off, which put IDXREF on a knife
  edge where one plugin's run fell into a false minimum).
- Each XDS case must reach CORRECT, IDXREF spot SD must be < 2 px, and all file layouts read by
  the same plugin must give identical SPOT.XDS.

Docs: recommend Durin (PSI or Global Phasing build); mask-bit table rewritten from measurement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-08 10:56:34 +02:00
co-authored by Claude Opus 5.5
parent 1a970eef91
commit 2629f3821c
15 changed files with 135 additions and 166 deletions
+45 -49
View File
@@ -105,25 +105,18 @@ jobs:
find /tmp/rgx -name 'libcufft*' > /tmp/rgx.cufft
if [ -s /tmp/rgx.cufft ]; then echo "a libcufft is shipped in the tarball"; exit 1; fi
echo "self-contained: static cuFFT, nothing to ship beside it"
- name: Verify the XDS plugin carries no FFTW
- name: Verify the XDS plugin exports
shell: bash
run: |
set -euo pipefail
# The plugin may be linked with XDS only while it holds no third-party GPL code (LICENSE,
# additional permission 2), so check both what the linker was given and what came out.
ninja -C build -t commands jfjoch_xds_plugin > /tmp/xdsp.cmds
grep -e '-o xds-plugin/libjfjoch_xds_plugin' /tmp/xdsp.cmds > /tmp/xdsp.link
if grep -qi fftw /tmp/xdsp.link; then cat /tmp/xdsp.link; echo "FFTW is on the plugin's link line"; exit 1; fi
rm -rf /tmp/xdsp && mkdir -p /tmp/xdsp
tar xf build/jfjoch_xds_plugin-*.tar.gz -C /tmp/xdsp
so=$(find /tmp/xdsp -name 'libjfjoch_xds_plugin.so.*' -type f)
nm "$so" > /tmp/xdsp.nm
if grep -qi fftw /tmp/xdsp.nm; then echo "FFTW code in the plugin"; exit 1; fi
# only the four plugin_* entry points are exported
nm -D --defined-only "$so" | awk '{print $3}' > /tmp/xdsp.exports
if grep -v '^plugin_' /tmp/xdsp.exports; then echo "the plugin exports more than plugin_*"; exit 1; fi
ls /tmp/xdsp/share/doc/jfjoch_xds_plugin/licenses
echo "no FFTW, exports plugin_* only"
echo "exports plugin_* only"
- name: Upload tgz to release
if: github.event_name == 'workflow_dispatch' && (github.event.inputs.create_release == 'true' || github.event.inputs.create_release == true)
shell: bash
@@ -255,9 +248,9 @@ jobs:
run: |
cmake --build build-macos --target jfjoch_portable_test -j "$(sysctl -n hw.ncpu)"
cd build-macos/tests && ./jfjoch_portable_test "[portable]"
# One build, three artifacts: the viewer .dmg, and rugnux and the XDS plugin as .tar.gz each
# (CPACK_PROJECT_CONFIG_FILE in CMakeLists.txt gives each generator its components).
- name: Build viewer DMG, rugnux and XDS plugin tgz
# One build, two artifacts: the viewer .dmg and rugnux as .tar.gz (CPACK_PROJECT_CONFIG_FILE
# in CMakeLists.txt gives each generator its components). The XDS plugin is Linux-only.
- name: Build viewer DMG and rugnux tgz
shell: bash
run: |
cmake --build build-macos -j "$(sysctl -n hw.ncpu)"
@@ -275,8 +268,8 @@ jobs:
if grep -q "/Users/" /tmp/jfjv.libs; then grep "/Users/" /tmp/jfjv.libs; echo "absolute path into the build machine"; exit 1; fi
# the (ad-hoc) signature must survive everything installed into the bundle
codesign --verify --deep --strict "$app"
# rugnux and the plugin are in archives of their own, not in the app
find "$app" -name rugnux -o -name 'libjfjoch_xds_plugin*' > /tmp/jfjv.extra
# rugnux is in an archive of its own, not in the app
find "$app" -name rugnux > /tmp/jfjv.extra
if [ -s /tmp/jfjv.extra ]; then cat /tmp/jfjv.extra; echo "the bundle carries another program"; exit 1; fi
echo "self-contained and validly signed"
- name: Verify rugnux is self-contained
@@ -293,22 +286,6 @@ jobs:
"$bin" > /tmp/rgxm.out 2>&1 || true
if ! grep -qi "usage" /tmp/rgxm.out; then cat /tmp/rgxm.out; echo "rugnux did not start"; exit 1; fi
echo "self-contained and runs"
- name: Verify the XDS plugin carries no FFTW
shell: bash
run: |
set -euo pipefail
# As in build:portable:linux - the linker's input and the library that came out.
if grep -qi fftw build-macos/xds-plugin/CMakeFiles/jfjoch_xds_plugin.dir/link.txt; then echo "FFTW is on the plugin's link line"; exit 1; fi
rm -rf /tmp/xdspm && mkdir -p /tmp/xdspm
tar xf build-macos/jfjoch_xds_plugin-*.tar.gz -C /tmp/xdspm
lib=$(find /tmp/xdspm -name 'libjfjoch_xds_plugin*.dylib' -type f)
nm "$lib" > /tmp/xdspm.nm
if grep -qi fftw /tmp/xdspm.nm; then echo "FFTW code in the plugin"; exit 1; fi
nm -gU "$lib" | awk '{print $3}' > /tmp/xdspm.exports
if grep -v '^_plugin_' /tmp/xdspm.exports; then echo "the plugin exports more than plugin_*"; exit 1; fi
otool -L "$lib" | tail -n +2 > /tmp/xdspm.libs
if grep -v -e "/usr/lib/" -e "/System/Library/" -e "libjfjoch_xds_plugin" /tmp/xdspm.libs; then echo "links a non-system library"; exit 1; fi
echo "no FFTW, exports plugin_* only, system libraries only"
# Notarization - needs an Apple Developer ID, which the project does not have yet. Once it
# does, the app has to be signed with it (hardened runtime, secure timestamp) BEFORE cpack
# packs it, e.g. by passing to macdeployqt, through qt_generate_deploy_app_script's
@@ -327,13 +304,13 @@ jobs:
run: |
set -euo pipefail
shopt -s nullglob
# CMakeLists.txt names them jfjoch-viewer-<version>-macos-arm64.dmg,
# rugnux-<version>-macos-arm64-cpu.tar.gz and jfjoch_xds_plugin-<version>-macos-arm64.tar.gz;
# the archives are renamed to .tgz before upload, as the Linux ones are.
# CMakeLists.txt names them jfjoch-viewer-<version>-macos-arm64.dmg and
# rugnux-<version>-macos-arm64-cpu.tar.gz; the archive is renamed to .tgz before upload,
# as the Linux ones are.
dmgs=(build-macos/jfjoch-viewer-*.dmg)
tars=(build-macos/rugnux-*.tar.gz build-macos/jfjoch_xds_plugin-*.tar.gz)
if [ ${#dmgs[@]} -ne 1 ] || [ ${#tars[@]} -ne 2 ]; then
echo "Expected one DMG and two archives in build-macos/"
tars=(build-macos/rugnux-*.tar.gz)
if [ ${#dmgs[@]} -ne 1 ] || [ ${#tars[@]} -ne 1 ]; then
echo "Expected one DMG and one archive in build-macos/"
exit 1
fi
python3 gitea_upload_file.py "${dmgs[0]}"
@@ -687,30 +664,49 @@ jobs:
# Each case clears its directory first. The runner reuses the workspace volume, so a
# leftover .h5/.LP from an earlier run could let XDS "pass" on files this case never
# wrote -- which is what the first case of two of these jobs used to risk.
xds_case() { # <dir> <jfjoch_hdf5_test flags...>
local dir=$1; shift
# Indexing must succeed, not just run: a first IDXREF refinement above 2 px is a false
# minimum (Durin fell into one at 6 px while XDS.INP started 7 px off the beam centre). And
# SPOT.XDS, which is deterministic, is kept per layout: a plugin must find the same spots in
# every layout, since they all hold the same pixels.
xds_case() { # <dir> <layout> <jfjoch_hdf5_test flags...>
local dir=$1 layout=$2; shift 2
cd "tests/$dir"
rm -f ./*.h5 ./*.LP ./*.HKL
rm -f ./*.h5 ./*.LP ./*.HKL ./SPOT.XDS "./SPOT.XDS.$layout"
"$HDF5_TEST" "$DATA" "$@"
/opt/xds/xds_par
test -f IDXREF.LP
test -f CORRECT.LP
sd=$(grep -m1 'STANDARD DEVIATION OF SPOT' IDXREF.LP | awk '{print $NF}')
echo "IDXREF spot position SD: $sd px"
awk -v sd="$sd" 'BEGIN { exit !(sd < 2.0) }' || { echo "IDXREF converged to a false minimum"; return 1; }
cp SPOT.XDS "SPOT.XDS.$layout"
}
durin_legacy() { xds_case xds_durin -n25 -f10; }
durin_vds() { xds_case xds_durin -n25 -f10 -V; }
durin_single() { xds_case xds_durin -n25 -S; }
jfjoch_legacy() { xds_case xds -n25 -f10; }
jfjoch_vds() { xds_case xds -n25 -f10 -V; }
jfjoch_single() { xds_case xds -n25 -S; }
same_spots() { # <dir> <layouts...>: every layout gave the same SPOT.XDS
local dir=$1 first=$2; shift 2
for l in "$@"; do
[ "$(md5sum < "tests/$dir/SPOT.XDS.$first")" = "$(md5sum < "tests/$dir/SPOT.XDS.$l")" ] \
|| { echo "$dir: SPOT.XDS of $l differs from $first"; return 1; }
done
}
durin_legacy() { xds_case xds_durin legacy -n25 -f10; }
durin_vds() { xds_case xds_durin vds -n25 -f10 -V; }
durin_single() { xds_case xds_durin single -n25 -S; }
jfjoch_legacy() { xds_case xds legacy -n25 -f10; }
jfjoch_vds() { xds_case xds vds -n25 -f10 -V; }
jfjoch_single() { xds_case xds single -n25 -S; }
# Neggia cannot read a VDS master, so it gets the two layouts it supports.
neggia_legacy() { xds_case xds_neggia -n25 -f10; }
neggia_single() { xds_case xds_neggia -n25 -S; }
neggia_legacy() { xds_case xds_neggia legacy -n25 -f10; }
neggia_single() { xds_case xds_neggia single -n25 -S; }
durin_layouts() { same_spots xds_durin legacy vds single; }
jfjoch_layouts() { same_spots xds legacy vds single; }
neggia_layouts() { same_spots xds_neggia legacy single; }
fail=0
summary=""
for t in dials_legacy dials_vds dials_single dials_tilted \
durin_legacy durin_vds durin_single \
jfjoch_legacy jfjoch_vds jfjoch_single \
neggia_legacy neggia_single; do
neggia_legacy neggia_single \
durin_layouts jfjoch_layouts neggia_layouts; do
echo "::group::$t"
# Run the subshell as a command of its own and test $? afterwards. A subshell used
# directly as an `if` condition has its `set -e` ignored, so the case would carry on
+4 -7
View File
@@ -44,9 +44,9 @@ Key CMake options:
- `JFJOCH_VIEWER_BUILD` (default OFF) — builds the Qt6 `jfjoch_viewer` desktop app in addition to
the server stack.
- `JFJOCH_VIEWER_ONLY` (default OFF on Linux, **forced ON on Windows/macOS**) — builds only
`jfjoch_viewer`, `rugnux`, the XDS plugin (not on Windows) and the libraries they link; skips
`jfjoch_viewer`, `rugnux`, the XDS plugin (Linux only) and the libraries they link; skips
receiver, FPGA, detector control, tests and the frontend. `cpack` then writes one archive per
program (Linux: three `.tar.gz`; macOS: the viewer `.dmg` plus rugnux and plugin `.tar.gz`).
program (Linux: three `.tar.gz`; macOS: the viewer `.dmg` plus the rugnux `.tar.gz`).
- `SLS9` (default OFF) — build against slsDetectorPackage 9.2.0 instead of 8.0.2.
- `JFJOCH_INSTALL_DRIVER_SOURCE` (default OFF) — install the PCIe driver source for DKMS/RPM.
@@ -255,11 +255,8 @@ hand-edit it. `CMakeLists.txt` installs `LICENSE`, `THIRD_PARTY_NOTICES.md` and
into `share/doc/jfjoch` for every package component, so nothing further is needed to reach the built
product.
**The XDS plugin is the one product under a different grant.** `LICENSE` gives it an additional
permission to be linked with XDS (closed source), valid only while the plugin contains no third-party
GPL code: never link FFTW, or anything that pulls it in, into `xds-plugin/`. It ships its own short
notices (`xds-plugin/THIRD_PARTY_NOTICES.md` + `JFJOCH_XDS_PLUGIN_LICENSES` in its CMakeLists) —
update both when the plugin's link line changes.
**The XDS plugin ships its own notices** (`xds-plugin/THIRD_PARTY_NOTICES.md` +
`JFJOCH_XDS_PLUGIN_LICENSES` in its CMakeLists); update both when its link line changes.
A **build-time dependency** (`FetchContent`/`ExternalProject`, statically linked) is handled the
same way with one difference: there is no in-tree copy, so `COLLECT.sh` reads its licence out of the
+8 -8
View File
@@ -61,8 +61,8 @@ SET(JFJOCH_VIEWER_BUILD OFF CACHE BOOL "Compile Jungfraujoch viewer")
# The portable products come out of two build modes; every artifact holds exactly one program:
#
# JFJOCH_VIEWER_ONLY jfjoch_viewer .tar.gz (Linux) / installer (Windows) / .dmg (macOS)
# and also rugnux (.tar.gz; .zip on Windows) and, on Linux and macOS,
# the XDS plugin (.tar.gz)
# and also rugnux (.tar.gz; .zip on Windows) and, on Linux only, the
# XDS plugin (.tar.gz)
# JFJOCH_RUGNUX_ONLY rugnux .tar.gz (Linux, incl. cross-built aarch64) / .zip (Windows)
#
# The viewer build compiles everything rugnux needs anyway, so one build packs all three; the
@@ -588,7 +588,7 @@ ELSEIF (JFJOCH_VIEWER_ONLY)
ADD_SUBDIRECTORY(viewer)
ADD_SUBDIRECTORY(tools) # builds only the portable analysis tools (rugnux/extract_hkl)
ADD_SUBDIRECTORY(tests) # jfjoch_portable_test only
IF (NOT WIN32) # there is no native XDS for Windows
IF (NOT WIN32 AND NOT APPLE) # no native XDS for Windows; the macOS plugin is not tested
ADD_SUBDIRECTORY(xds-plugin)
ENDIF()
ELSE()
@@ -679,8 +679,8 @@ SET(CPACK_PACKAGE_NAME "jfjoch")
# Select the components to package based on build mode
if (JFJOCH_RUGNUX_ONLY)
set(CPACK_COMPONENTS_ALL rugnux)
elseif (JFJOCH_VIEWER_ONLY AND WIN32)
set(CPACK_COMPONENTS_ALL viewer rugnux) # there is no native XDS for Windows, so no plugin
elseif (JFJOCH_VIEWER_ONLY AND (WIN32 OR APPLE))
set(CPACK_COMPONENTS_ALL viewer rugnux) # the XDS plugin is built on Linux only
elseif (JFJOCH_VIEWER_ONLY)
set(CPACK_COMPONENTS_ALL viewer rugnux xds_plugin)
else()
@@ -724,10 +724,10 @@ if (WIN32)
else()
set(CPACK_ARCHIVE_RUGNUX_FILE_NAME "rugnux-${JFJOCH_VERSION}-${_jfjoch_tgz_os}-${CMAKE_SYSTEM_PROCESSOR}-${_jfjoch_tgz_variant}")
endif()
set(CPACK_ARCHIVE_XDS_PLUGIN_FILE_NAME "jfjoch_xds_plugin-${JFJOCH_VERSION}-${_jfjoch_tgz_os}-${CMAKE_SYSTEM_PROCESSOR}")
set(CPACK_ARCHIVE_XDS_PLUGIN_FILE_NAME "jfjoch_xds_plugin-${JFJOCH_VERSION}-linux-${CMAKE_SYSTEM_PROCESSOR}")
# macOS and Windows pack the viewer as a .dmg / NSIS installer and the other programs of the same
# build (rugnux; on macOS also the XDS plugin) as archives, one each. CPack reads
# build (rugnux) as an archive. CPack reads
# CPACK_PROJECT_CONFIG_FILE once per generator, which is where each generator gets its components.
file(WRITE ${CMAKE_BINARY_DIR}/CPackProjectConfig.cmake [=[
if (CPACK_GENERATOR STREQUAL "TGZ" OR CPACK_GENERATOR STREQUAL "ZIP")
@@ -744,7 +744,7 @@ endif()
if (APPLE AND NOT JFJOCH_RUGNUX_ONLY)
# .dmg containing jfjoch_viewer.app (Qt runtime already deployed into the bundle). Named like the
# Windows and Linux artifacts; CMAKE_SYSTEM_PROCESSOR is arm64 on Apple Silicon.
# plus rugnux and the XDS plugin as .tar.gz, see CPackProjectConfig.cmake above
# plus rugnux as .tar.gz, see CPackProjectConfig.cmake above
set(CPACK_GENERATOR "DragNDrop;TGZ")
set(CPACK_PROJECT_CONFIG_FILE ${CMAKE_BINARY_DIR}/CPackProjectConfig.cmake)
set(CPACK_PACKAGE_FILE_NAME "jfjoch-viewer-${JFJOCH_VERSION}-macos-${CMAKE_SYSTEM_PROCESSOR}")
+2 -11
View File
@@ -689,18 +689,9 @@ license of third-party code.
distributed with the CUDA Toolkit) are treated as System Libraries for the purposes of this
License, so a work linked with them may be conveyed without their Corresponding Source.
2. XDS plugin. The Jungfraujoch XDS plugin (the jfjoch_xds_plugin library, built from xds-plugin/
and the Jungfraujoch code it links) exists to be loaded by XDS, which is not free software. You
may link the XDS plugin with XDS, and convey the XDS plugin for that use, without XDS becoming
subject to the terms of this License. The XDS plugin itself remains under this License. This
permission covers the XDS plugin only, not any other part of Jungfraujoch, and only as long as
the plugin contains no third-party code licensed under the GNU GPL (for this reason it is never
linked with FFTW). If you modify the XDS plugin, you may extend this permission to your version,
but you are not obliged to; if you do not, delete it from your version.
3. FPGA simulation. Jungfraujoch software (GPL-3.0) may be linked with Jungfraujoch high-level
2. FPGA simulation. Jungfraujoch software (GPL-3.0) may be linked with Jungfraujoch high-level
synthesis code (CERN-OHL-S-2.0) to simulate the FPGA design on a CPU.
4. OpenAPI definition. Client code generated from the OpenAPI definition file (jfjoch_api.yaml), or
3. OpenAPI definition. Client code generated from the OpenAPI definition file (jfjoch_api.yaml), or
code that uses that file solely to interact with the Jungfraujoch API, may be distributed under
terms of your choosing, without being subject to the requirements of the GNU GPL.
-5
View File
@@ -112,11 +112,6 @@ served frontend, so the shipped web UI carries its own attribution.
sphere manifold for the crystal refinement, following its source. Both files name the origin at the
top and are covered by `licenses/ceres-solver.txt`.
* **FFTW** is GPL-2.0-or-later — compatible with, and absorbed by, this project's GPL-3.0 license.
It is never linked into the XDS plugin, which `LICENSE` allows to be linked with the closed-source
XDS only while it holds no third-party GPL code.
* **XDS plugin**: ships as an archive of its own, with only the notices of what it links (HDF5,
zlib-ng, Zstandard, LZ4, Bitshuffle, Bitshuffle h-perf) — see
[`xds-plugin/THIRD_PARTY_NOTICES.md`](xds-plugin/THIRD_PARTY_NOTICES.md).
* **Apache-2.0** components: where upstream ships a `NOTICE` file, it is reproduced in the
corresponding `licenses/` text.
* **Qt (LGPL-3.0)** and **NVIDIA CUDA (EULA)** carry redistribution conditions beyond a copyright
+2 -11
View File
@@ -691,18 +691,9 @@ license of third-party code.
distributed with the CUDA Toolkit) are treated as System Libraries for the purposes of this
License, so a work linked with them may be conveyed without their Corresponding Source.
2. XDS plugin. The Jungfraujoch XDS plugin (the jfjoch_xds_plugin library, built from xds-plugin/
and the Jungfraujoch code it links) exists to be loaded by XDS, which is not free software. You
may link the XDS plugin with XDS, and convey the XDS plugin for that use, without XDS becoming
subject to the terms of this License. The XDS plugin itself remains under this License. This
permission covers the XDS plugin only, not any other part of Jungfraujoch, and only as long as
the plugin contains no third-party code licensed under the GNU GPL (for this reason it is never
linked with FFTW). If you modify the XDS plugin, you may extend this permission to your version,
but you are not obliged to; if you do not, delete it from your version.
3. FPGA simulation. Jungfraujoch software (GPL-3.0) may be linked with Jungfraujoch high-level
2. FPGA simulation. Jungfraujoch software (GPL-3.0) may be linked with Jungfraujoch high-level
synthesis code (CERN-OHL-S-2.0) to simulate the FPGA design on a CPU.
4. OpenAPI definition. Client code generated from the OpenAPI definition file (jfjoch_api.yaml), or
3. OpenAPI definition. Client code generated from the OpenAPI definition file (jfjoch_api.yaml), or
code that uses that file solely to interact with the Jungfraujoch API, may be distributed under
terms of your choosing, without being subject to the requirements of the GNU GPL.
+4 -9
View File
@@ -23,7 +23,6 @@ result see [Deployment](DEPLOYMENT.md); for the package-repository URLs see
| `rugnux-<version>-macos-arm64-cpu.tgz` | Gitea release page | The same for macOS on Apple Silicon, CPU only |
| `jfjoch-writer` `.rpm` / `.deb` | Gitea release page | The writer alone, for a file-writing machine without the rest of the stack |
| `jfjoch_xds_plugin-<version>-linux-x86_64.tgz` | Gitea release page | XDS HDF5 read plugin (`lib/libjfjoch_xds_plugin.so.<n>`, built on RHEL 8) and its license notices; see [Integration with MX software](SOFTWARE_INTEGRATION.md) |
| `jfjoch_xds_plugin-<version>-macos-arm64.tgz` | Gitea release page | The same for macOS on Apple Silicon (`lib/libjfjoch_xds_plugin.<n>.dylib`) |
| `jfjoch-client` | [PyPI](https://pypi.org/project/jfjoch-client/) and the Gitea PyPI index | Generated Python OpenAPI client |
| Documentation | [Read the Docs](https://jungfraujoch.readthedocs.io) and the `gitea-pages` branch | This documentation set |
@@ -42,7 +41,7 @@ baseline the compiler defaults to). The released binaries are compiled to a fixe
| --- | --- | --- |
| Linux (all packages, and the portable `.tgz`) | `-march=x86-64-v3 -flto=auto` | AVX2 + FMA + BMI2 — Intel Haswell (2013) / AMD Zen (2017) and newer |
| Windows installer | `/arch:AVX` | AVX — Intel Sandy Bridge (2011) / AMD Bulldozer and newer |
| macOS `.dmg`, `rugnux` and XDS plugin `.tgz` | none (the compiler's default Apple Silicon target) | Any Apple Silicon Mac — M1 and newer |
| macOS `.dmg` and `rugnux` `.tgz` | none (the compiler's default Apple Silicon target) | Any Apple Silicon Mac — M1 and newer |
The Windows floor is lower because MSVC has no spelling for the `x86-64-v2` level; `/arch:AVX` is
the nearest one and implies SSE4.1/4.2, which is what actually matters — without it Eigen has no
@@ -161,8 +160,8 @@ To build the viewer yourself on Windows, see
## macOS
The macOS artefacts are the `jfjoch_viewer` disk image and the `rugnux` and XDS plugin `.tgz`; as on
Windows, the rest of Jungfraujoch is Linux-only. Both are **CPU-only** — macOS has no CUDA, so indexing uses the
The macOS artefacts are the `jfjoch_viewer` disk image and the `rugnux` `.tgz`; as on Windows, the
rest of Jungfraujoch, including the XDS plugin, is Linux-only. Both are **CPU-only** — macOS has no CUDA, so indexing uses the
FFTW indexer and the whole pipeline runs on the CPU — and both are built for **Apple Silicon only**.
They need **macOS 13 (Ventura) or newer**.
@@ -183,7 +182,7 @@ and choose *Open*. Alternatively, clear the download flag in Terminal:
xattr -dr com.apple.quarantine /Applications/jfjoch_viewer.app
```
The same applies to the `rugnux` binary and the XDS plugin from their `.tgz` — see
The same applies to the `rugnux` binary from its `.tgz` — see
[Installing Rugnux](RUGNUX_INSTALL.md#from-the-release-archive).
The toolchain of the released macOS artefacts is Xcode (Apple Clang) and Qt 6.11 for macOS,
@@ -198,7 +197,3 @@ license texts of the bundled dependencies, each package under a directory of its
so that no two packages claim the same path and they can be upgraded independently. The macOS
viewer is the exception: its notices are inside the app, in `jfjoch_viewer.app/Contents/Resources`. See
[Third-party software notices](THIRD_PARTY_NOTICES.md).
The XDS plugin archive carries only what applies to the plugin, in `share/doc/jfjoch_xds_plugin`: the
project license, whose additional permission 2 allows the plugin to be linked with XDS, and the
license texts of the libraries it links (HDF5, zlib, Zstandard, LZ4, Bitshuffle).
+37 -26
View File
@@ -7,7 +7,7 @@ program specifically needs another.**
| | `NXmxLegacy` | `NXmxVDS` (default) | `NXmxIntegrated` |
|---|---|---|---|
| Jungfraujoch XDS plugin | yes | yes | yes |
| Durin (Global Phasing) | yes | yes | yes |
| Durin (PSI or Global Phasing build) | yes | yes | yes |
| Durin (Diamond, original) | yes | known bugs | known bugs |
| Neggia | yes | **no** — no virtual-dataset support | not tested |
| DIALS / xia2 | **only one data file** | yes | yes |
@@ -22,22 +22,29 @@ single data file — see the DIALS section below.
XDS reads HDF5 through a plugin, named in `XDS.INP`:
```
LIB="/opt/xds/lib/libjfjoch_xds_plugin.so.1.0.1"
LIB=/opt/xds/durin-plugin.so
```
**Use the Jungfraujoch plugin.** It is released for Linux (x86_64, built on RHEL 8) and macOS (Apple
Silicon, `libjfjoch_xds_plugin.<version>.dylib`) as a `.tgz` on the Gitea release page, and also
ships inside the [`jfjoch_viewer`](JFJOCH_VIEWER.md) RPM/APT packages. Extract the archive into a
directory of its own; it holds the library under `lib/` and its license notices under `share/doc/`.
The three numbers are the plugin version and change over time. The plugin is GPL-3.0, with an
additional permission to be linked with XDS (see [License](LICENSE.md)).
**We recommend the Durin plugin**, from either of two sources:
The alternatives, in order of preference:
* the PSI build — [gitea.psi.ch/mx/durin/releases](https://gitea.psi.ch/mx/durin/releases);
* the Global Phasing fork by Clemens Vonrhein, distributed with autoPROC —
[github.com/CV-GPhL/durin](https://github.com/CV-GPhL/durin).
* **Durin, Global Phasing build** — [github.com/CV-GPhL/durin](https://github.com/CV-GPhL/durin).
Prefer it over the original from Diamond Light Source, which has known bugs with non-DECTRIS files
(virtual datasets and the single-file layout). It is the only third-party plugin that reads
**signed** Jungfraujoch images correctly.
Prefer either over the original Durin from Diamond Light Source, which has known bugs with
non-DECTRIS files (virtual datasets and the single-file layout). Durin is the only third-party plugin
that reads **signed** Jungfraujoch images correctly. Be aware that it does not act on every mask bit
— in particular not on the user mask, the beam-stop shadow or module edges (see
[Which pixels are masked](#which-pixels-are-masked)); where those matter, exclude the regions in
`XDS.INP` (`UNTRUSTED_RECTANGLE=` and friends).
The other options:
* **Jungfraujoch XDS plugin** — released for Linux (x86_64, built on RHEL 8) as
`jfjoch_xds_plugin-<version>-linux-x86_64.tgz` on the Gitea release page, and also shipped inside
the [`jfjoch_viewer`](JFJOCH_VIEWER.md) RPM/APT packages. Extract the archive into a directory of
its own; it holds the library (`lib/libjfjoch_xds_plugin.so.<version>`) and its license notices
(`share/doc/`). It acts on every mask bit Jungfraujoch writes.
* **Neggia** — [github.com/dectris/neggia](https://github.com/dectris/neggia/). No virtual-dataset
support, so it cannot read the default layout. **It also mis-reads signed 16-bit images**: it
dispatches on the pixel size in bytes and always casts to an unsigned type, so a count of `-2`
@@ -60,21 +67,25 @@ There is no header field that changes this: if XDS is the target, consider colle
### Which pixels are masked
The plugins do not all act on the same mask bits, so XDS and DIALS do not mask the same pixels:
The plugins do not all act on the same mask bits, so XDS and DIALS do not mask the same pixels.
Measured with the Durin and Neggia builds used in Jungfraujoch CI:
| mask bit | meaning | jfjoch plugin | Durin / Neggia | DIALS |
|---|---|---|---|---|
| 0 | module gap | yes | yes | yes |
| 1, 4 | error, noisy | yes | yes | yes |
| 8, 9 | user mask, beam stop | yes | **no** | yes |
| 30 | module edge | yes | **no** | yes |
| 31 | chip gap | **no** | **no** | yes |
| mask bit | meaning | jfjoch plugin | Durin | Neggia | DIALS |
|---|---|---|---|---|---|
| 0 | module gap | yes | yes | yes | yes |
| 1–4 | error, noisy | yes | yes | yes | yes |
| 8, 9, 10 | user mask, beam stop, defective | yes | **no** | **no** | yes |
| 30 | module edge | yes | **no** | **no** | yes |
| 31 | chip edge | yes | yes | **no** | yes |
DIALS masks a pixel whenever *any* `pixel_mask` bit is set; Durin and Neggia look only at
bits 0–4. On a JUNGFRAU with the default edge masking this is a difference of order 2%
of the detector. Bits 30 and 31 mark pixels that are larger than normal rather than bad, which is
why the Jungfraujoch plugin passes chip-gap pixels through — but be aware that a dataset processed
by XDS and by DIALS will not have used exactly the same pixels.
DIALS masks a pixel whenever *any* `pixel_mask` bit is set. Bits 30 and 31 mark pixels that are not
defective but do not measure like the rest: a module-edge pixel is larger and reads high, and a
chip-edge pixel is split from a larger one and at low counts reads low. On a JUNGFRAU with the default
edge masking they are of order 2% of the detector, so a dataset processed through Neggia or Durin
will not have used the same pixels as one processed through the Jungfraujoch plugin or DIALS.
A saturated pixel is passed to XDS with its value by all three plugins, so XDS flags it against
`OVERLOAD`; only the error marker becomes an untrusted (negative) pixel.
## DIALS
-5
View File
@@ -117,11 +117,6 @@ served frontend, so the shipped web UI carries its own attribution.
sphere manifold for the crystal refinement, following its source. Both files name the origin at the
top and are covered by `licenses/ceres-solver.txt`.
* **FFTW** is GPL-2.0-or-later — compatible with, and absorbed by, this project's GPL-3.0 license.
It is never linked into the XDS plugin, which `LICENSE` allows to be linked with the closed-source
XDS only while it holds no third-party GPL code.
* **XDS plugin**: ships as an archive of its own, with only the notices of what it links (HDF5,
zlib-ng, Zstandard, LZ4, Bitshuffle, Bitshuffle h-perf) — see
[`xds-plugin/THIRD_PARTY_NOTICES.md`](https://gitea.psi.ch/mx/jungfraujoch/src/branch/main/xds-plugin/THIRD_PARTY_NOTICES.md).
* **Apache-2.0** components: where upstream ships a `NOTICE` file, it is reproduced in the
corresponding `licenses/` text.
* **Qt (LGPL-3.0)** and **NVIDIA CUDA (EULA)** carry redistribution conditions beyond a copyright
+2 -2
View File
@@ -7,8 +7,8 @@ MAXIMUM_NUMBER_OF_JOBS=1
MAXIMUM_NUMBER_OF_PROCESSORS=1
! for this experiment:
ORGX= 1097
ORGY= 1130
ORGX= 1091
ORGY= 1137
DETECTOR_DISTANCE= 75
OSCILLATION_RANGE= 0.088
X-RAY_WAVELENGTH= 1.0
+2 -2
View File
@@ -7,8 +7,8 @@ MAXIMUM_NUMBER_OF_JOBS=1
MAXIMUM_NUMBER_OF_PROCESSORS=1
! for this experiment:
ORGX= 1097
ORGY= 1130
ORGX= 1091
ORGY= 1137
DETECTOR_DISTANCE= 75
OSCILLATION_RANGE= 0.088
X-RAY_WAVELENGTH= 1.0
+2 -2
View File
@@ -7,8 +7,8 @@ MAXIMUM_NUMBER_OF_JOBS=1
MAXIMUM_NUMBER_OF_PROCESSORS=1
! for this experiment:
ORGX= 1097
ORGY= 1130
ORGX= 1091
ORGY= 1137
DETECTOR_DISTANCE= 75
OSCILLATION_RANGE= 0.088
X-RAY_WAVELENGTH= 1.0
+7 -13
View File
@@ -1,8 +1,10 @@
# The plugin's own release number. Not the VERSION_* in plugin.h: those fill the info array, where XDS
# requires the major version to be 0 and refuses the plugin otherwise.
SET(JFJOCH_XDS_PLUGIN_VERSION 1.1.0)
ADD_LIBRARY(jfjoch_xds_plugin SHARED plugin.cpp plugin.h)
TARGET_COMPILE_DEFINITIONS(jfjoch_xds_plugin PRIVATE JFJOCH_XDS_PLUGIN_VERSION="${JFJOCH_XDS_PLUGIN_VERSION}")
# XDS is not free software, so the plugin is conveyed under the additional permission in LICENSE,
# which holds only while the plugin carries no third-party GPL code -- never link FFTW (or anything
# that pulls it in, such as JFJochImageAnalysis) here. CI checks the built library for it.
TARGET_LINK_LIBRARIES(jfjoch_xds_plugin JFJochHDF5Wrappers JFJochVersion Compression hdf5-static)
# The licence texts (licenses/<name>.txt) of the third-party code the plugin links: HDF5 and the zlib
@@ -13,22 +15,14 @@ set_target_properties(jfjoch_xds_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden
C_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
VERSION 1.0.1
VERSION ${JFJOCH_XDS_PLUGIN_VERSION}
)
# Export the four plugin_* entry points and nothing else, so the statically linked HDF5 cannot clash
# with another copy in the process.
if(APPLE)
target_link_options(jfjoch_xds_plugin PRIVATE "LINKER:-exported_symbol,_plugin_*")
elseif(UNIX)
target_link_options(jfjoch_xds_plugin PRIVATE
"LINKER:--exclude-libs,ALL"
)
endif()
target_link_options(jfjoch_xds_plugin PRIVATE "LINKER:--exclude-libs,ALL")
IF (JFJOCH_PORTABLE_ONLY)
# An archive of its own, with only the notices that apply to it: the plugin is the one product
# under the XDS permission, and the full manifest would list code (FFTW) it does not contain.
INSTALL(TARGETS jfjoch_xds_plugin LIBRARY DESTINATION lib COMPONENT xds_plugin)
LIST(TRANSFORM JFJOCH_XDS_PLUGIN_LICENSES REPLACE "(.+)" "${CMAKE_SOURCE_DIR}/licenses/\\1.txt")
INSTALL(FILES ${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/THIRD_PARTY_NOTICES.md
+2 -3
View File
@@ -1,8 +1,7 @@
# Jungfraujoch XDS plugin - third-party software notices
The Jungfraujoch XDS plugin is licensed under **GPL-3.0** with an additional permission to be linked
with XDS (see `LICENSE`, "Additional permissions", item 2). It statically links the third-party
components below, none of which is under the GPL; their verbatim license texts are in `licenses/`.
The Jungfraujoch XDS plugin is licensed under **GPL-3.0** (see `LICENSE`). It statically links the
third-party components below; their verbatim license texts are in `licenses/`.
| Component | Version | Copyright | License (SPDX) | License text |
|---|---|---|---|---|
+18 -13
View File
@@ -8,6 +8,7 @@
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <limits>
#include <memory>
#include <mutex>
#include <shared_mutex>
@@ -42,9 +43,7 @@ namespace {
std::vector<uint8_t> one_byte_mask;
template<class T>
void ConvertAndMaskTyped(const std::vector<uint8_t> &in_8bit,
int marker_value,
int *out) {
void ConvertAndMaskTyped(const std::vector<uint8_t> &in_8bit, int *out) {
auto in = reinterpret_cast<const T *>(in_8bit.data());
size_t size = in_8bit.size() / sizeof(T);
@@ -53,10 +52,13 @@ namespace {
out[i] = -1;
else if (!one_byte_mask.empty() && (one_byte_mask.at(i) == 2))
out[i] = -2;
else if (marker_value != 0 && in[i] == static_cast<T>(marker_value))
out[i] = -1;
// The error marker: INTx_MIN in a signed image (caught as negative), UINTx_MAX in an
// unsigned one. INTx_MAX in a signed image is a saturated pixel and is passed through,
// so XDS sees it above OVERLOAD and flags the reflection as overloaded.
else if (std::is_signed_v<T> && in[i] < 0)
out[i] = -1;
else if (!std::is_signed_v<T> && in[i] == std::numeric_limits<T>::max())
out[i] = -1;
else if (in[i] > INT32_MAX)
out[i] = INT32_MAX;
else
@@ -124,8 +126,11 @@ namespace {
{image_size_y, image_size_x}
);
// Module edge (bit 30) and chip edge (bit 31) pixels are not defective, but they do not measure
// like the rest: a module edge pixel is larger and reads high, a chip edge pixel is split from
// a larger one with integer division and at low counts reads low.
for (int i = 0; i < mask_tmp.size(); i++) {
if (mask_tmp[i] & (1 << 30))
if (mask_tmp[i] & ((1u << 30) | (1u << 31)))
one_byte_mask[i] = 2;
else if (mask_tmp[i] & 0xFFFF)
one_byte_mask[i] = 1;
@@ -136,19 +141,19 @@ namespace {
switch (pixel_byte_depth) {
case 1:
if (pixel_signed)
return ConvertAndMaskTyped<int8_t>(in_8bit, INT8_MAX, out_buffer);
return ConvertAndMaskTyped<int8_t>(in_8bit, out_buffer);
else
return ConvertAndMaskTyped<uint8_t>(in_8bit, UINT8_MAX, out_buffer);
return ConvertAndMaskTyped<uint8_t>(in_8bit, out_buffer);
case 2:
if (pixel_signed)
return ConvertAndMaskTyped<int16_t>(in_8bit, INT16_MAX, out_buffer);
return ConvertAndMaskTyped<int16_t>(in_8bit, out_buffer);
else
return ConvertAndMaskTyped<uint16_t>(in_8bit, UINT16_MAX, out_buffer);
return ConvertAndMaskTyped<uint16_t>(in_8bit, out_buffer);
case 4:
if (pixel_signed)
return ConvertAndMaskTyped<int32_t>(in_8bit, INT32_MAX, out_buffer);
return ConvertAndMaskTyped<int32_t>(in_8bit, out_buffer);
else
return ConvertAndMaskTyped<uint32_t>(in_8bit, UINT32_MAX, out_buffer);
return ConvertAndMaskTyped<uint32_t>(in_8bit, out_buffer);
default:
throw PluginError(-1, "Unsupported conversion to int");
}
@@ -171,7 +176,7 @@ void plugin_open(const char *filename, int info[1024], int *error_flag) {
std::cout << "********** Jungfraujoch XDS plugin **********" << std::endl;
std::cout << "Jungfraujoch version " << jfjoch_version() << std::endl;
std::cout << "Plugin version " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_PATCH << std::endl << std::endl;
std::cout << "Plugin version " << JFJOCH_XDS_PLUGIN_VERSION << std::endl << std::endl;
std::cout << "Copyright (C) 2024-2026 Paul Scherrer Institute" << std::endl;
std::cout << "This program comes with ABSOLUTELY NO WARRANTY" << std::endl;
std::cout << "This is free software, and you are welcome to redistribute it" << std::endl;