aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas White <taw@physics.org>2023-09-18 13:05:22 +0200
committerThomas White <taw@physics.org>2023-09-18 13:05:22 +0200
commit38b4e5fec7fc9d1cf554afa42b4209f14bc3444f (patch)
tree0f2a3697f1ac2b9667b4b1009a666aac24e2d952
parentb91c1cdbdbd75b3c23f90faf98340e398f583406 (diff)
parentb4e92e6b8851fdb45bd55a3b02fae9f5fa216b1a (diff)
Merge branch 'millepede'
Fixes: https://gitlab.desy.de/thomas.white/crystfel/-/issues/3 Fixes: https://gitlab.desy.de/thomas.white/crystfel/-/issues/29
-rw-r--r--.gitlab-ci.yml10
-rw-r--r--doc/examples/cspad-cxiformat.geom41
-rw-r--r--doc/examples/cspad-single.geom41
-rw-r--r--doc/examples/lcls-dec.geom5
-rw-r--r--doc/examples/lcls-june-r0013-r0128.geom5
-rw-r--r--doc/examples/lcls-xpp-estimate.geom2
-rw-r--r--doc/examples/simple.geom3
-rw-r--r--doc/man/adjust_detector.1.md100
-rw-r--r--doc/man/indexamajig.1675
-rw-r--r--doc/man/indexamajig.1.md927
-rw-r--r--libcrystfel/meson.build4
-rw-r--r--libcrystfel/src/crystfel-mille.c311
-rw-r--r--libcrystfel/src/crystfel-mille.h60
-rw-r--r--libcrystfel/src/datatemplate.c1258
-rw-r--r--libcrystfel/src/datatemplate.h44
-rw-r--r--libcrystfel/src/datatemplate_priv.h40
-rw-r--r--libcrystfel/src/detgeom.c163
-rw-r--r--libcrystfel/src/detgeom.h42
-rw-r--r--libcrystfel/src/geometry.c197
-rw-r--r--libcrystfel/src/geometry.h35
-rw-r--r--libcrystfel/src/index.c18
-rw-r--r--libcrystfel/src/index.h8
-rw-r--r--libcrystfel/src/predict-refine.c484
-rw-r--r--libcrystfel/src/predict-refine.h51
-rw-r--r--libcrystfel/src/utils.c80
-rw-r--r--libcrystfel/src/utils.h6
-rw-r--r--meson.build37
-rwxr-xr-xscripts/detector-shift188
-rw-r--r--src/adjust_detector.c245
-rw-r--r--src/align_detector.c512
-rw-r--r--src/im-sandbox.c13
-rw-r--r--src/indexamajig.c11
-rw-r--r--src/process_image.c7
-rw-r--r--src/process_image.h9
-rw-r--r--subprojects/cjson.wrap10
-rw-r--r--subprojects/millepede.wrap6
-rw-r--r--subprojects/packagefiles/millepede/meson.build42
-rw-r--r--tests/ev_enum1.geom2
-rw-r--r--tests/ev_enum2.geom2
-rw-r--r--tests/ev_enum3.geom2
-rwxr-xr-xtests/geom_roundtrip645
-rw-r--r--tests/gradient_check.c187
-rw-r--r--tests/gradient_check_utils.c184
-rw-r--r--tests/gradient_check_utils.h32
-rw-r--r--tests/meson.build63
-rwxr-xr-xtests/partialator_merge_check_12
-rwxr-xr-xtests/partialator_merge_check_22
-rwxr-xr-xtests/partialator_merge_check_32
-rwxr-xr-xtests/plot_gradients28
-rw-r--r--tests/prediction_gradient_check.c540
-rwxr-xr-xtests/process_hkl_check_12
-rwxr-xr-xtests/process_hkl_check_22
-rwxr-xr-xtests/process_hkl_check_32
-rwxr-xr-xtests/process_hkl_check_42
-rw-r--r--tests/wavelength_geom1.geom2
-rw-r--r--tests/wavelength_geom10.geom2
-rw-r--r--tests/wavelength_geom11.geom2
-rw-r--r--tests/wavelength_geom12.geom2
-rw-r--r--tests/wavelength_geom2.geom2
-rw-r--r--tests/wavelength_geom3.geom2
-rw-r--r--tests/wavelength_geom4.geom2
-rw-r--r--tests/wavelength_geom5.geom2
-rw-r--r--tests/wavelength_geom6.geom2
-rw-r--r--tests/wavelength_geom7.geom2
-rw-r--r--tests/wavelength_geom8.geom2
-rw-r--r--tests/wavelength_geom9.geom2
66 files changed, 5194 insertions, 2219 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6ce7606b..87fb9f6f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -10,8 +10,8 @@ build-meson:
- dnf group install -y 'Development Tools'
- dnf install -y hdf5-devel gsl-devel flex bison gcc-c++
- dnf install -y gtk3-devel cairo-devel pango-devel gdk-pixbuf2-devel fftw-devel libpng-devel diffutils
- - dnf install -y meson
- - meson build && ninja -C build
+ - dnf install -y meson pandoc
+ - meson setup build && ninja -C build
- ninja -C build test
artifacts:
when: always
@@ -24,10 +24,10 @@ build-meson-nohdf5:
image: fedora:37
script:
- dnf group install -y 'Development Tools'
- - dnf install -y gsl-devel flex bison gcc-c++
+ - dnf install -y gsl-devel flex bison gcc-c++ gcc-gfortran
- dnf install -y gtk3-devel cairo-devel pango-devel gdk-pixbuf2-devel fftw-devel libpng-devel diffutils
- dnf install -y meson
- - meson build -Dhdf5=disabled && ninja -C build
+ - meson setup build -Dhdf5=disabled && ninja -C build
- ninja -C build test
artifacts:
when: always
@@ -105,7 +105,7 @@ build-native-macos:
- export PATH="$(brew --prefix)/opt/bison/bin:$(brew --prefix)/opt/flex/bin:$PATH"
- export LDFLAGS="-L$(brew --prefix)/opt/bison/lib -L$(brew --prefix)/opt/flex/lib -L$(brew --prefix)/opt/argp-standalone/lib -largp $LDFLAGS"
- export CFLAGS="-I$(brew --prefix)/opt/flex/include -I$(brew --prefix)/opt/argp-standalone/include/ $CFLAGS"
- - meson build
+ - meson setup build
- ninja -C build
- ninja -C build test
artifacts:
diff --git a/doc/examples/cspad-cxiformat.geom b/doc/examples/cspad-cxiformat.geom
index 6ca27e69..bdbcd9b7 100644
--- a/doc/examples/cspad-cxiformat.geom
+++ b/doc/examples/cspad-cxiformat.geom
@@ -658,3 +658,44 @@ q3a15/fs = +0.004918x +0.999989y
q3a15/ss = -0.999989x +0.004918y
q3a15/corner_x = 555.732
q3a15/corner_y = -263.253
+
+
+group_a0 = q0a0,q0a1
+group_a1 = q0a2,q0a3
+group_a2 = q0a4,q0a5
+group_a3 = q0a6,q0a7
+group_a4 = q0a8,q0a9
+group_a5 = q0a10,q0a11
+group_a6 = q0a12,q0a13
+group_a7 = q0a14,q0a15
+group_a8 = q1a0,q1a1
+group_a9 = q1a2,q1a3
+group_a10 = q1a4,q1a5
+group_a11 = q1a6,q1a7
+group_a12 = q1a8,q1a9
+group_a13 = q1a10,q1a11
+group_a14 = q1a12,q1a13
+group_a15 = q1a14,q1a15
+group_a16 = q2a0,q2a1
+group_a17 = q2a2,q2a3
+group_a18 = q2a4,q2a5
+group_a19 = q2a6,q2a7
+group_a20 = q2a8,q2a9
+group_a21 = q2a10,q2a11
+group_a22 = q2a12,q2a13
+group_a23 = q2a14,q2a15
+group_a24 = q3a0,q3a1
+group_a25 = q3a2,q3a3
+group_a26 = q3a4,q3a5
+group_a27 = q3a6,q3a7
+group_a28 = q3a8,q3a9
+group_a29 = q3a10,q3a11
+group_a30 = q3a12,q3a13
+group_a31 = q3a14,q3a15
+
+group_q0 = a0,a1,a2,a3,a4,a5,a6,a7
+group_q1 = a8,a9,a10,a11,a12,a13,a14,a15
+group_q2 = a16,a17,a18,a19,a20,a21,a22,a23
+group_q3 = a24,a25,a26,a27,a28,a29,a30,a31
+
+group_all = q0,q1,q2,q3
diff --git a/doc/examples/cspad-single.geom b/doc/examples/cspad-single.geom
index 5ed5fb52..bd1086d5 100644
--- a/doc/examples/cspad-single.geom
+++ b/doc/examples/cspad-single.geom
@@ -656,3 +656,44 @@ q3a15/fs = -0.0010058292x +0.9999995232y
q3a15/ss = -0.9999995232x -0.0010058292y
q3a15/corner_x = 575.475
q3a15/corner_y = -221.866
+
+
+group_a0 = q0a0,q0a1
+group_a1 = q0a2,q0a3
+group_a2 = q0a4,q0a5
+group_a3 = q0a6,q0a7
+group_a4 = q0a8,q0a9
+group_a5 = q0a10,q0a11
+group_a6 = q0a12,q0a13
+group_a7 = q0a14,q0a15
+group_a8 = q1a0,q1a1
+group_a9 = q1a2,q1a3
+group_a10 = q1a4,q1a5
+group_a11 = q1a6,q1a7
+group_a12 = q1a8,q1a9
+group_a13 = q1a10,q1a11
+group_a14 = q1a12,q1a13
+group_a15 = q1a14,q1a15
+group_a16 = q2a0,q2a1
+group_a17 = q2a2,q2a3
+group_a18 = q2a4,q2a5
+group_a19 = q2a6,q2a7
+group_a20 = q2a8,q2a9
+group_a21 = q2a10,q2a11
+group_a22 = q2a12,q2a13
+group_a23 = q2a14,q2a15
+group_a24 = q3a0,q3a1
+group_a25 = q3a2,q3a3
+group_a26 = q3a4,q3a5
+group_a27 = q3a6,q3a7
+group_a28 = q3a8,q3a9
+group_a29 = q3a10,q3a11
+group_a30 = q3a12,q3a13
+group_a31 = q3a14,q3a15
+
+group_q0 = a0,a1,a2,a3,a4,a5,a6,a7
+group_q1 = a8,a9,a10,a11,a12,a13,a14,a15
+group_q2 = a16,a17,a18,a19,a20,a21,a22,a23
+group_q3 = a24,a25,a26,a27,a28,a29,a30,a31
+
+group_all = q0,q1,q2,q3
diff --git a/doc/examples/lcls-dec.geom b/doc/examples/lcls-dec.geom
index 3ecf4b03..c0250faf 100644
--- a/doc/examples/lcls-dec.geom
+++ b/doc/examples/lcls-dec.geom
@@ -1,6 +1,7 @@
mask = /processing/hitfinder/masks
mask_good = 0x07
mask_bad = 0x00
+clen = 67.8e-3
; These default values will be used unless overridden by the per-panel values
adu_per_eV = 0.0835
@@ -13,7 +14,7 @@ upper/corner_x = -491.90
upper/corner_y = 71.30
upper/fs = x
upper/ss = y
-upper/clen = 67.8e-3
+upper/coffset = 0.0
upper/res = 13333.3 ; 75 micron pixel size
upper/badrow_direction = y
@@ -25,7 +26,7 @@ lower/corner_x = -492.00
lower/corner_y = -779.70
lower/fs = x
lower/ss = y
-lower/clen = 70.8e-3
+lower/coffset = 3e-3
lower/res = 13333.3 ; 75 micron pixel size
lower/badrow_direction = y
diff --git a/doc/examples/lcls-june-r0013-r0128.geom b/doc/examples/lcls-june-r0013-r0128.geom
index 64790daa..c466666b 100644
--- a/doc/examples/lcls-june-r0013-r0128.geom
+++ b/doc/examples/lcls-june-r0013-r0128.geom
@@ -1,6 +1,7 @@
mask = /processing/hitfinder/masks
mask_good = 0x27
mask_bad = 0x00
+clen = 64.78e-3
; These default values will be used unless overridden by the per-panel values
adu_per_eV = 0.01045
@@ -14,7 +15,7 @@ adu_per_eV = 0.01045
0/corner_y = 53.00
0/fs = x
0/ss = y
-0/clen = 64.78e-3
+0/coffset = 0.0
0/res = 13333.3 ; 75 micron pixel size
0/badrow_direction = y
0/rigid_group = 0
@@ -28,7 +29,7 @@ adu_per_eV = 0.01045
1/corner_y = -901.00
1/fs = x
1/ss = y
-1/clen = 67.73e-3
+1/coffset = 2.95e-3
1/res = 13333.3 ; 75 micron pixel size
1/badrow_direction = y
1/rigid_group = 1
diff --git a/doc/examples/lcls-xpp-estimate.geom b/doc/examples/lcls-xpp-estimate.geom
index d34dc3b3..6cbb63e3 100644
--- a/doc/examples/lcls-xpp-estimate.geom
+++ b/doc/examples/lcls-xpp-estimate.geom
@@ -1,3 +1,4 @@
+clen = 0.08 ; 8 cm camera length
0/min_fs = 0
0/max_fs = 1455
0/min_ss = 0
@@ -6,7 +7,6 @@
0/corner_y = -727.50
0/fs = x
0/ss = y
-0/clen = 0.08 ; 8 cm camera length
0/res = 9090 ; 110 micron pixel size
0/badrow_direction = x
0/adu_per_eV = 1.0
diff --git a/doc/examples/simple.geom b/doc/examples/simple.geom
index 55dbd1cd..548e3344 100644
--- a/doc/examples/simple.geom
+++ b/doc/examples/simple.geom
@@ -1,4 +1,5 @@
adu_per_eV = 1.0
+clen = 50.0e-3
; Upper panel
0/min_fs = 0
@@ -9,7 +10,6 @@ adu_per_eV = 1.0
0/corner_y = 10.00
0/fs = x
0/ss = y
-0/clen = 50.0e-3
0/res = 13333.3 ; 75 micron pixel size
; Lower panel
@@ -21,5 +21,4 @@ adu_per_eV = 1.0
1/corner_y = -522.00
1/fs = x
1/ss = y
-1/clen = 50.0e-3
1/res = 13333.3 ; 75 micron pixel size
diff --git a/doc/man/adjust_detector.1.md b/doc/man/adjust_detector.1.md
new file mode 100644
index 00000000..aec4bc3b
--- /dev/null
+++ b/doc/man/adjust_detector.1.md
@@ -0,0 +1,100 @@
+% adjust_detector(1)
+
+NAME
+====
+
+adjust_detector - move detector panels
+
+
+SYNOPSIS
+========
+
+adjust_detector -g _input.geom_ -o _output.geom_ -p _group_ [_movement_]
+
+
+DESCRIPTION
+===========
+
+**adjust_detector** moves a panel (or group of panels) in a CrystFEL detector
+geometry file, and writes an updated geometry file.
+
+To rotate a panel, use one of **--rotx**, **--roty** or **--rotz**. The
+rotation will be in degrees, clockwise when looking along the specified axis.
+The center of rotation will be the centroid of the corners of the panel, or the
+centroid of all the panel centers in the specified group.
+
+To translate a panel or group, use one of **--shiftx**, **--shifty** or
+**--shiftz**. The translation will be along the positive direction of the
+specified axis. The units are pixels, unless you additionally specify
+**--mm**.
+
+You can use multiple movements together, but the results are unspecified if you
+combine a rotation with any other movement (including another rotation).
+
+
+OPTIONS
+=======
+
+**-g** _input.geom_
+: Specify the input geometry filename.
+
+**-o** _output.geom_
+: Specify the output geometry filename.
+: Note that the geometry file will be re-written, meaning that any formatting
+: and comments will be lost.
+
+**-p** _group_
+: Specify the panel, or panel group, to move.
+
+**--mm**
+: Interpret panel shifts as millimetres, not pixels.
+
+**--shiftx=***n*, **--shifty=***n*, **--shiftz=***n*
+: Shift the chosen panel (or group) by _n_ along the given direction, in units
+: of pixels (unless **--mm** is used). When moving a group of panels, the
+: pixel size of the *first* panel will be used.
+
+**--rotx=***n*, **--roty=***n*, **--rotz=***n*
+: Shift the chosen panel (or group) by _n_ degrees clockwise about the given
+: axis. The center of rotation will be the centroid of the corners of the
+: panel, or the centroid of all the panel centers in the specified group.
+
+
+AUTHOR
+======
+
+This page was written by Thomas White.
+
+
+REPORTING BUGS
+==============
+
+Report bugs to <taw@physics.org>, or visit <http://www.desy.de/~twhite/crystfel>.
+
+
+COPYRIGHT AND DISCLAIMER
+========================
+
+Copyright © 2023 Deutsches Elektronen-Synchrotron DESY, a research centre of
+the Helmholtz Association.
+
+adjust_detector, and this manual, are part of CrystFEL.
+
+CrystFEL is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+CrystFEL is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+
+
+SEE ALSO
+========
+
+**crystfel**(7), **indexamajig**(1), **align_detector**(1),
+**crystfel_geometry**(5)
diff --git a/doc/man/indexamajig.1 b/doc/man/indexamajig.1
deleted file mode 100644
index 0f35d4d8..00000000
--- a/doc/man/indexamajig.1
+++ /dev/null
@@ -1,675 +0,0 @@
-.\"
-.\" indexamajig man page
-.\"
-.\" Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY,
-.\" a research centre of the Helmholtz Association.
-.\"
-.\" Part of CrystFEL - crystallography with a FEL
-.\"
-
-.TH INDEXAMAJIG 1
-.SH NAME
-indexamajig \- bulk indexing and data reduction program
-.SH SYNOPSIS
-.PP
-.BR indexamajig
-\fB-i\fR \fIfilename\fR \fB-o\fR \fIoutput.stream\fR \fB-g\fR \fIdetector.geom\fR \fB--peaks=\fR\fImethod\fR \fB--indexing=\fR\fImethod\fR
-[\fBoptions\fR] \fB...\fR
-.PP
-\fBindexamajig --help\fR
-
-.SH DESCRIPTION
-
-\fBindexamajig\fR takes a list of diffraction snapshots from crystals in random orientations and attempts to find peaks, index and integrate each one. The input is a list of diffraction image files and some auxiliary files and parameters. The output is a long text file ('stream') containing the results from each image in turn.
-
-For minimal basic use, you need to provide the list of diffraction patterns, the method which will be used to index, a file describing the geometry of the detector, and a file which contains the unit cell which will be used for the indexing. Here is what the minimal use might look like on the command line:
-
-.IP \fBindexamajig\fR
-.PD
--i mypatterns.lst -g mygeometry.geom --indexing=mosflm,dirax --peaks=hdf5 -o test.stream -p mycell.pdb
-
-.PP
-More typical use includes all the above, but might also include extra parameters to modify the behaviour. For example, you'll probably want to run more than one indexing job at a time (-j <n>).
-
-See \fBman crystfel_geometry\fR for information about how to create a file describing the detector geometry and beam characteristics.
-
-.SH DIFFRACTION PATTERN LIST
-
-Indexamajig requires an input file with a list of diffraction patterns ("events") to process. In its simplest form, this is just a text files containing a list of image filenames. The image files might be in some folder a long way from the current directory, so you might want to specify a full pathname to be added in front of each filename. The geometry file includes a description of the data layout within the files. Indexamajig uses this description to determine the number of diffraction patterns stored in each file, and tries to process them all. You can also specify explicity which event(s) you would like to process by putting a string describing the event after the file name(s) in this list.
-
-
-.SH PEAK DETECTION
-
-You can control the peak detection on the command line. First, you can choose the peak detection method using \fB--peaks=\fR\fImethod\fR. \fB--peaks=hdf5\fR or \fB--peaks=cxi\fR will take the peak locations from the input file. See the documentation for \fBpeak_list\fR and \fBpeak_list_type\fR in crystfel_geometry(5) for details.
-
-If you use \fB--peaks=zaef\fR, indexamajig will use a simple gradient search after Zaefferer (2000). You can control the overall threshold and minimum squared gradient for finding a peak using \fB--threshold\fR and \fB--min-squared-gradient\fR. The threshold has arbitrary units matching the pixel values in the data, and the minimum gradient has the equivalent squared units. Peaks will be rejected if the 'foot point' is further away from the 'summit' of the peak by more than the inner integration radius (see below). They will also be rejected if the peak is closer than twice the inner integration radius from another peak.
-
-If you instead use \fB--peaks=peakfinder8\fR, indexamajig will use the "peakfinder8" peak finding algorithm describerd in Barty et al. (2014). Pixels above a radius-dependent intensity threshold are considered as candidate peaks (although the user sets an absolute minimum threshold for candidate peaks). Peaks are then only accepted if their signal to noise level over the local background is sufficiently high. Peaks can include multiple pixels and the user can reject a peak if it includes too many or too few. The distance of a peak from the center of the detector can also be used as a filtering criterion. Note that the peakfinder8 will not report more than 2048 peaks for each panel: any additional peak is ignored.
-
-If you instead use \fB--peaks=peakfinder9\fR, indexamajig will use the "peakfinder9" peak finding algorithm described in the master thesis "Real-time image analysis and data compression in high throughput X-ray diffraction experiments" by Gevorkov. Other than peakFinder8, peakFinder9 uses local background estimation based on border pixels in a specified radius (\fB--local-bg-radius\fR). For being fast and precise, a hierarchy of conditions is used. First condition is only useful for speed consideration, it demands that a pixel that is the biggest pixel in a peak must be larger than every border pixel by a constant value (\fB--min-peak-over-neighbour\fR). Second condition ensures, that the pixel passing the previous condition is the highest pixel in the peak. It assumes, that peaks rise monotonically towards the biggest pixel. Third condition ensures, that the biggest pixel in the peak is significantly over the noise level (\fB--min-snr-biggest-pix\fR) by computing the local statistics from the border pixels in a specified radius. Fourth condition sums up all pixels belonging to the peak (\fB--min-snr-peak-pix\fR) and demands that the whole peak must be significantly over the noise level (\fB--min-snr\fR). Only if all conditions are passed, the peak is accepted.
-
-You can suppress peak detection altogether for a panel in the geometry file by specifying the "no_index" value for the panel as non-zero.
-
-
-.SH INDEXING METHODS
-
-You can choose between a variety of indexing methods. You can choose more than one method, in which case each method will be tried in turn until one of them reports that the pattern has been successfully indexed. Choose from:
-
-.IP \fBdirax\fR
-.PD
-Invoke DirAx. See Duisenberg, J. Applied Crystallography 25 (1992) p92, https://doi.org/10.1107/S0021889891010634.
-
-.IP \fBmosflm\fR
-.PD
-Invoke Mosflm. See Powell, Acta Crystallographica D55 (1999) p1690, https://doi.org/10.1107/S0907444999009506.
-
-.IP \fBasdf\fR
-.PD
-This is a implementation of the \fBdirax\fR algorithm, with some very small changes such as using a 1D Fourier transform for finding the lattice repeats. This algorithm is implemented natively within CrystFEL meaning that no external software is required.
-
-.IP \fBfelix\fR
-.PD
-Invoke Felix, which will use your cell parameters to find multiple crystals in each pattern.
-.sp
-The Felix indexer has been developed by Soeren Schmidt <ssch@fysik.dtu.dk>. To use this option, 'Felix' must be in your shell's search path. This can be a link to the latest version of Felix. If you see the Felix version information when you run \fBFelix\fR on the command line, things are set up correctly.
-
-.IP \fBxds\fR
-.PD
-Invoke XDS, and use its REFIDX procedure to attempt to index the pattern.
-
-.IP \fBtaketwo\fR
-.PD
-Use the TakeTwo algorithm. See Ginn et al., Acta Crystallographica D72 (2016), p956, https://doi.org/10.1107/S2059798316010706.
-
-.IP \fBxgandalf\fR
-.PD
-Invoke XGANDALF - eXtended GrAdieNt Descent Algorithm for Lattice Finding. See Gevorkov et al., Acta Crystallographica A75 (2019) p694, https://doi.org/10.1107/S2053273319010593.
-
-.IP \fBpinkIndexer\fR
-.PD
-Invoke pinkIndexer. See Gevorkov et al., Acta Crystallographica A76 (2020) p121, https://doi.org/10.1107/S2053273319015559.
-
-.PP
-Most of the indexing methods require some extra software to be installed, either at the time of compiling CrystFEL or afterwards. CrystFEL is distributed with a script (\fBscripts/install-indexers\fR) which can help you to quickly install all the required programs.
-
-.PP
-You can add one or more of the following to the above indexing methods, to control what information should be provided to them. Note that indexamajig performs a series of checks on the indexing results, including checking that the result is consistent with the target unit cell parameters. To get completely "raw" indexing, you need to disable these checks (see below) \fBand\fR not provide prior information.
-
-.IP \fB-latt\fR
-.PD
-Provide the Bravais lattice type (e.g. the knowledge that the lattice is tetragonal primitive), as prior information to the indexing engine.
-
-.IP \fB-nolatt\fR
-.PD
-The opposite of \fB-latt\fR: do not provide Bravais lattice type information to the indexing engine.
-
-.IP \fB-cell\fR
-.PD
-Provide your unit cell parameters as prior information to the indexing engine.
-
-.IP \fB-nocell\fR
-.PD
-The opposite of \fB-cell\fR: do not provide unit cell parameters as prior information to the indexing engine.
-
-.PP
-Example: \fB--indexing=mosflm-cell-latt\fR means to use Mosflm for indexing, and provide it with unit cell parameters and Bravais lattice type information.
-
-.PP
-If you don't specify any indexing methods, \fBindexamajig\fR will try to automatically determine which indexing methods are available. You can also specify indexing method \fBnone\fR, in which case no indexing will be done. This is useful if you just want to check that the peak detection is working properly.
-
-.PP
-Usually, you do not need to explicitly specify anything more than the indexing method itself (e.g. \fBmosflm\fR or \fBasdf\fR). The default behaviour for all indexing methods is to make the maximum possible use of prior information such as the lattice type and cell parameters. If you do not provide this information, for example if you do not give any unit cell file or if the unit cell file does not contain cell parameters (only lattice type information), the indexing methods you give will be modified accordingly. If you only specify the indexing methods themselves, in most cases \fBindexamajig\fR will do what you want and intuitively expect! However, the options are available if you need finer control.
-
-The indexing results from the indexing engine will be put through a number of refinement and checking stages. See the options \fB--no-check-cell, --no-multi, --no-retry\fR and \fB--no-refine\fR below for more details.
-
-.SH PEAK INTEGRATION
-If the pattern could be successfully indexed, peaks will be predicted in the pattern and their intensities measured. You have a choice of integration methods, and you specify the method using \fB--integration\fR. Choose from:
-
-.IP \fBrings\fR
-.PD
-Use three concentric rings to determine the peak, buffer and background estimation regions. The radius of the smallest circle sets the peak region. The radius of the middle and outer circles describe an annulus from which the background will be estimated. You can set the radii of the rings using \fB--int-radius\fR (see below). The default behaviour with \fBrings\fR is \fBnot\fR to center the peak boxes first. Use \fBrings-cen\fR if you want to use centering.
-
-.IP \fBprof2d\fR
-.PD
-Integrate the peaks using 2D profile fitting with a planar background, close to the method described by Rossmann (1979) J. Appl. Cryst. 12 p225. The default behaviour with \fBprof2d\fR is to center the peak first - use \fBprof2d-nocen\fR to skip this step.
-
-.PP
-You can add one or more of the following to the above integration methods:
-
-.IP \fB-cen\fR
-.PD
-Center the peak boxes iteratively on the actual peak locations. The opposite is \fB-nocen\fR, which is the default.
-
-.IP \fB-sat\fR
-.PD
-Normally, reflections which contain one or more pixels above max_adu (defined in the detector geometry file) will not be integrated and written to the stream. Using this option skips this check, and allows saturated reflections to be passed to the later merging stages. The opposite is \fB-nosat\fR, which is the default for all integration methods. However, note that the saturation check will only be done if max_adu is set in the geometry file. Usually, it's better to exclude saturated reflections at the merging stage. See the documentation for max_adu in crystfel_geometry(5).
-
-.IP \fB-grad\fR
-.PD
-Fit the background around the reflection using gradients in two dimensions. This was the default until version 0.6.1. Without the option (or with its opposite, \fB-nograd\fR, which is the default), the background will be considered to have the same value across the entire integration box, which gives better results in most cases.
-
-.SH BASIC OPTIONS
-.PD 0
-.IP "\fB-i\fR \fIfilename\fR"
-.IP \fB--input=\fR\fIfilename\fR
-.PD
-Read the list of images to process from \fIfilename\fR. \fB--input=-\fR means to read from stdin. There is no default.
-
-.PD 0
-.IP "\fB-o\fR \fIfilename\fR"
-.IP \fB--output=\fR\fIfilename\fR
-.PD
-Write the output data stream to \fIfilename\fR.
-
-.PD 0
-.IP "\fB-g\fR \fIfilename\fR"
-.IP \fB--geometry=\fR\fIfilename\fR
-.PD
-Read the detector geometry description from \fIfilename\fR. See \fBman crystfel_geometry\fR for more information.
-
-.PD 0
-.IP \fB--zmq-input=\fIaddress\fR
-.PD
-Receive data over ZeroMQ from \fIaddress\fR. The options \fB--input\fR and \fB--zmq-input\fR are mutually exclusive - you must specify exactly one of them. Example: \fB--zmq-input=tcp://127.0.0.1:5002\fR.
-.IP
-If you use this option, you should also use either \fB--zmq-subscribe\fR to add a ZeroMQ subscription, or \fB--zmq-request\fR to define how to request data.
-
-.PD 0
-.IP \fB--zmq-subscribe=\fItag\fR
-.PD
-Subscribe to ZeroMQ message type \fItag\fR. You can use this option multiple times to add multiple subscriptions. This option and \fB--zmq-request\fR are mutually exclusive.
-
-.PD 0
-.IP \fB--zmq-request=\fImsg\fR
-.PD
-Request new data over ZeroMQ by sending string \fImsg\fR. This will cause indexamajig's ZeroMQ socket to use REQ mode instead of SUB. This option and \fB--zmq-subscribe\fR are mutually exclusive.
-
-.PD 0
-.IP \fB--asapo-endpoint=\fIendpoint\fR
-.PD
-Receive data via the specified ASAP::O endpoint. This option and \fB--zmq-input\fR are mutually exclusive.
-
-.PD 0
-.IP \fB--asapo-token=\fItoken\fR
-.IP \fB--asapo-beamtime=\fIbeamtime\fR
-.IP \fB--asapo-source=\fIsource\fR
-.IP \fB--asapo-group=\fIgroup\fR
-.IP \fB--asapo-stream=\fIstream\fR
-.PD
-Authentication token, beamtime, data source, consumer group and stream, respectively, for ASAP::O data.
-
-.PD 0
-.IP \fB--asapo-output-stream=\fIstream\fR
-.PD
-Send an output stream via ASAP::O. For non-hits, a small placeholder will be sent.
-
-.PD 0
-.IP \fB--asapo-wait-for-stream
-.PD
-If the ASAP::O stream does not exist, wait for it to be appear. Without this option, indexamajig will exit immediately if the stream is not found.
-
-.PD 0
-.IP \fB--data-format=\fIformat\fR
-.PD
-Specify the data format for data received over ZeroMQ or ASAP::O. Possible values in this version are \fBmsgpack\fR, \fBhdf5\fR and \fBseedee\fR.
-
-.PD 0
-.IP \fB--basename\fR
-.PD
-Remove the directory parts of the filenames taken from the input file. If \fB--prefix\fR or \fB-x\fR is also given, the directory parts of the filename will be removed \fIbefore\fR adding the prefix.
-
-.PD 0
-.IP "\fB-x\fR \fIprefix\fR"
-.IP \fB--prefix=\fR\fIprefix\fR
-.PD
-Prefix the filenames from the input file with \fIprefix\fR. If \fB--basename\fR is also given, the filenames will be prefixed \fIafter\fR removing the directory parts of the filenames.
-
-.PD 0
-.IP "\fB-j\fR \fIn\fR"
-.PD
-Run \fIn\fR analyses in parallel. Default: 1. See also \fB--max-indexer-threads\fR.
-.IP
-Tip: use \fB-j `nproc`\fR (note the backticks) to use as many processes as there are available CPUs.
-
-.PD 0
-.IP \fB--cpu-pin
-.PD
-Pin worker processes to CPUs. Usually this is not needed or desirable, but in some cases it dramatically improves performance.
-
-.PD 0
-.IP \fB--no-check-prefix\fR
-.PD
-Don't attempt to correct the prefix (see \fB--prefix\fR) if it doesn't look correct.
-
-.PD 0
-.IP \fB--highres=\fIn\fR
-.PD
-Mark all pixels on the detector higher than \fIn\fR Angstroms as bad. This might be useful when you have noisy patterns and don't expect any signal above a certain resolution.
-
-.PD 0
-.IP \fB--profile
-.PD
-Display timing data for performance monitoring.
-
-.PD 0
-.IP \fB--temp-dir=\fIpath\fR
-.PD
-Put the temporary folder under \fIpath\fR.
-
-.PD 0
-.IP \fB--wait-for-file=\fIn\fR
-.PD
-Wait at most \fIn\fR seconds for each image file in the input list to be created before trying to process it. This is useful for some automated processing pipelines. It obviously only really works for single-frame files. If a file exists but is not readable when this option is set non-zero, a second attempt will be made after ten seconds. This is to allow for incompletely written files. A value of -1 means to wait forever. The default value is \fB--wait-for-file=0\fR.
-
-.IP \fB--no-image-data\fR
-.PD
-Do not load the actual image data (or bad pixel masks), only the metadata. This allows you to check if patterns can be indexed, without high data bandwidth requirements. Obviously, any feature requiring the image data, especially peak search procedures and integration, cannot be used in this case. Therefore, you'll need to get the peaks from somewhere else (see \fB--peaks=msgpack\fR or \fB--peaks=hdf5\fR).
-
-
-.SH PEAK SEARCH OPTIONS
-.PD 0
-.IP \fB--peaks=\fR\fImethod\fR
-.PD
-Find peaks in the images using \fImethod\fR. See the second titled \fBPEAK DETECTION\fB (above) for more information.
-
-.PD 0
-.IP \fB--peak-radius=\fR\fIinner,middle,outer\fR
-.PD
-Set the inner, middle and outer radii for three-ring integration during the peak search. See the section about \fBPEAK INTEGRATION\fR, above, for details of how to determine
-these. The default is to use the same values as for \fB--int-radius\fR.
-
-.PD 0
-.IP \fB--min-peaks=\fIn\fR
-.PD
-Do not try to index frames with fewer than \fIn\fR peaks. These frames will still be described in the output stream. To exclude them, use \fB--no-non-hits-in-stream\fR. The default is \fB--min-peaks=0\fR, which means that \fIall\fR frames will be considered hits, even if they have no peaks at all.
-
-.PD 0
-.IP \fB--median-filter=\fR\fIn\fR
-.PD
-Apply a median filter with box "radius" \fIn\fR to the image. The median of the values from a \fI(n+1)\fRx\fI(n+1)\fR square centered on the pixel will be subtracted from each pixel. This might help with peak detection if the background is high and/or noisy. The \fIunfiltered\fR image will be used for the final integration of the peaks. If you also use \fB--noise-filter\fR, the median filter will be applied first.
-
-.PD 0
-.IP \fB--filter-noise\fR
-.PD
-Apply a noise filter to the image with checks 3x3 squares of pixels and sets all of them to zero if any of the nine pixels have a negative value. This filter may help with peak detection under certain circumstances. The \fIunfiltered\fR image will be used for the final integration of the peaks, because the filter is destroys a lot of information from the pattern. If you also use \fB--median-filter\fR, the median filter will be applied first.
-
-.PD 0
-.IP \fB--threshold=\fR\fIthres\fR
-.PD
-Set the overall threshold for peak detection using \fB--peaks=zaef\fR or \fB--peaks=peakfinder8\fR to \fIthres\fR, which has the same units as the detector data. The default is \fB--threshold=800\fR.
-
-.PD 0
-.IP \fB--min-squared-gradient=\fR\fIgrad\fR
-.PD
-Set the square of the gradient threshold for peak detection using \fB--peaks=zaef\fR to \fIgrad\fR, which has units of "squared detector units per pixel". The default is \fB--min-squared-gradient=100000\fR. \fB--min-sq-gradient\fR and \fB--min-gradient\fR are synonyms for this option, however the latter should not be used to avoid confusion.
-
-.PD 0
-.IP \fB--min-snr=\fR\fIsnr\fR
-.PD
-Set the minimum I/sigma(I) for peak detection when using \fB--peaks=zaef\fR, \fB--peaks=peakfinder8\fR or \fB--peaks=peakfinder9\fR. The default is \fB--min-snr=5\fR.
-
-.PD 0
-.IP \fB--min-snr-biggest-pix=<n>\fR
-.PD
-(peakFinder9 only) min snr of the biggest pixel in the peak, given as a factor of the standard deviation. Default is 7.0.
-
-.PD 0
-.IP \fB--min-snr-peak-pix=<n>\fR
-.PD
-(peakFinder9 only) min snr of a peak pixel, given as a factor of the standard deviation. Should be smaller or equal to sig_fac_biggest_pix. Default is 6.0.
-
-.PD 0
-.IP \fB--min-sig=<n>\fR
-.PD
-(peakFinder9 only) minimum standard deviation of the background. Prevents finding of peaks in erroneous or highly shadowed unmasked regions. Default is 11.0.
-
-.PD 0
-.IP \fB--min-peak-over-neighbour=<n>\fR
-.PD
-(peakFinder9 only) just for speed. Biggest pixel must be n higher than the pixels in window_radius distance to be a candidate for the biggest pixel in a peak. Should be chosen as a small positive number, a few times smaller than the weakest expected peak. The default is -INFINITY, which turns off the speedup and searches with maximum precision.
-
-.PD 0
-.IP \fB--min-pix-count=\fR\fIcnt\fR
-.PD
-Accepts peaks only if they include more than \fR\fIcnt\fR pixels, when using \fB--peaks=peakfinder8\fR. The default is \fB--min-pix-count=2\fR.
-
-.PD 0
-.IP \fB--max-pix-count=\fR\fIcnt\fR
-.PD
-Accepts peaks only if they include less than \fR\fIcnt\fR pixels, when using \fB--peaks=peakfinder8\fR. The default is \fB--max-pix-count=200\fR.
-
-.PD 0
-.IP \fB--local-bg-radius=\fR\fIr\fR
-.PD
-Radius (in pixels) used for the estimation of the local background when using \fB--peaks=peakfinder8 or --peaks=peakfinder9\fR. The default is \fB--local-bg-radius=3\fR.
-
-.PD 0
-.IP \fB--min-res=\fR\fIpx\fR
-.PD
-Only accept peaks if they lay at more than \fR\fIpx\fR pixels from the center of the detector when using \fB--peaks=peakfinder8\fR. The default is \fB--min-res=0\fR.
-
-.PD 0
-.IP \fB--max-res=\fR\fIpx\fR
-.PD
-Only accept peaks if they lay at less than \fR\fIpx\fR pixels from the center of the detector when using \fB--peaks=peakfinder8\fR. The default is \fB--max-res=1200\fR.
-
-.PD 0
-.IP \fB--no-use-saturated\fR
-.PD
-Normally, peaks which contain one or more pixels above max_adu (defined in the detector geometry file) will be used for indexing (but not used in the final integration - see the section on peak integration above). Using this option causes saturated peaks to be ignored completely. The opposite is \fB--use-saturated\fR, which is the default.
-
-.PD 0
-.IP \fB--no-revalidate\fR
-.PD
-When using \fB--peaks=hdf5\fR, \fB--peaks=cxi\fR or \fB--peaks=msgpack\fR, the peaks will be put through some of the same checks as if you were using \fB--peaks=zaef\fR. These checks reject peaks which are too close to panel edges, are saturated (unless you use \fB--use-saturated\fR), have other nearby peaks (closer than twice the inner integration radius, see \fB--int-radius\fR), or have any part in a bad region. Using this option skips this validation step, and uses the peaks directly.
-
-.PD 0
-.IP \fB--no-half-pixel-shift\fR
-.PD
-CrystFEL considers all peak locations to be distances from the corner of the detector panel, in pixel units, consistent with its description of detector geometry (see 'man crystfel_geometry'). The software which generates the image data files, including Cheetah, may instead consider the peak locations to be pixel indices in the data array. Therefore, the peak coordinates from \fB--peaks=cxi\fR or \fB--peaks=hdf5\fR will by default have 0.5 added to them. This option \fBdisables\fR this half-pixel offset.
-
-.PD 0
-.IP \fB--check-hdf5-snr\fR
-.PD
-With this option with \fB--peaks=hdf5\fR (or \fBcxi\fR or \fBmsgpack\fR), the peaks will additionally be checked to see that they satisfy the minimum SNR specified with \fB--min-snr\fR.
-
-.PD 0
-.IP \fB--peakfinder8-fast\fR
-.PD
-(peakfinder8 only) Increase speed by pre-calculating some coordinate tables.
-
-.SH INDEXING OPTIONS
-.PD 0
-.IP \fB--indexing=\fR\fImethod\fR
-.PD
-Index the patterns using \fImethod\fR. See the section titled \fBINDEXING METHODS\fR (above) for more information. The default is to automatically detect which indexing methods to use.
-
-.PD 0
-.IP "\fB-p\fR \fIunitcell.cell\fR"
-.IP "\fB-p\fR \fIunitcell.pdb\fR"
-.IP \fB--pdb=\fR\fIunitcell.pdb\fR
-.PD
-Specify the name of the file containing unit cell information, in PDB or CrystFEL format.
-
-.PD 0
-.IP \fB--tolerance=\fR\fItol\fR
-.PD
-Set the tolerances for unit cell comparison. \fItol\fR takes the form \fIa\fR,\fIb\fR,\fIc\fR,\fIang\fR. \fIa\fR, \fIb\fR and \fIc\fR are the tolerances, in percent, for the respective \fIreciprocal\fR space axes, and \fIang\fR is the tolerance in degrees for the reciprocal space angles. If the unit cell is centered, the tolerances are applied to the corresponding primitive unit cell.
-.PD
-The default is \fB--tolerance=5,5,5,1.5\fR.
-
-.PD 0
-.IP \fB--no-check-cell
-.PD
-Do not check the cell parameters against the reference unit cell (given with \fB-p\fR). If you've used older versions of CrystFEL, this replaces putting "-raw" in the indexing method.
-
-.PD 0
-.IP \fB--no-check-peaks
-.PD
-Do not check that most of the peaks can be accounted for by the indexing solution. The thresholds for a successful result are influenced by option \fB--multi\fR.
-
-.PD 0
-.IP \fB--multi
-.PD
-Enable the "subtract and retry" method, where after a successful indexing attempt the spots accounted for by the indexing solution are removed before trying to index again in the hope of finding a second lattice. This doesn't have anything to do with the multi-lattice indexing algorithms such as Felix.
-.IP
-This option also adjusts the thresholds for identifying successful indexing results (see \fB--no-check-peaks\fR).
-
-.PD 0
-.IP \fB--no-retry
-.PD
-Disable retry indexing. After an unsuccessful indexing attempt, indexamajig would normally remove the 10% weakest peaks and try again. This option disables that, which makes things much faster but decreases the indexing success rate.
-
-.PD 0
-.IP \fB--no-refine
-.PD
-Skip the prediction refinement step. Usually this will decrease the quality of the results and allow false solutions to get through, but occasionally it might be necessary.
-
-.PD 0
-.IP \fB--wavelength-estimate=\fIm\fR
-.IP \fB--camera-length-estimate=\fIm\fR
-.PD
-Some indexing algorithms need to know the camera length or the wavelength of the incident radiation in advance, e.g. to prepare an internal look-up table. However, if these values are taken from image headers, then they are not available at start-up. In this case, you will be prompted to add one of these options to give approximate values (in metres). A warning will be generated if the actual value differs from this value by more than 10%.
-
-.PD 0
-.IP \fB--max-indexer-threads=\fIn\fR
-.PD
-Some indexing algorithms (e.g. pinkIndexer) can use multiple threads for faster calculations. This is in addition to the frame-based parallelism already available in indexamajig (see \fB-j\fR). This option sets the maximum number of threads that each indexing engine is allowed to use. Default: 1.
-
-.PD 0
-.IP \fB--taketwo-member-threshold=\fIn\fR
-.IP \fB--taketwo-len-tolerance=\fIn\fR
-.IP \fB--taketwo-angle-tolerance=\fIn\fR
-.IP \fB--taketwo-trace-tolerance=\fIn\fR
-.PD
-These set low-level parameters for the TakeTwo indexing algorithm. Respectively, the minimum number of vectors in the network before the pattern is considered indexed, the length and angle tolerances (in reciprocal Angstroms and degrees, respectively) and the rotation matrix angle tolerance (in degrees) for considering rotation matrices as equal.
-.IP
-The defaults are: \fB--taketwo-member-threshold=20\fR, \fB--taketwo-len-tolernace=0.001\fR, \fB--taketwo-angle-tolerance=0.6\fR and \fB--taketwo-trace-tolerance=3\fR.
-
-.PD 0
-.IP \fB--felix-domega=\fIn\fR
-.IP \fB--felix-fraction-max-visits=\fIn\fR
-.IP \fB--felix-max-internal-angle=\fIn\fR
-.IP \fB--felix-max-uniqueness=\fIn\fR
-.IP \fB--felix-min-completeness=\fIn\fR
-.IP \fB--felix-min-visits=\fIn\fR
-.IP \fB--felix-num-voxels=\fIn\fR
-.IP \fB--felix-sigma=\fIn\fR
-.IP \fB--felix-tthrange-max=\fIn\fR
-.IP \fB--felix-tthrange-min=\fIn\fR
-.PD 0
-These set low-level parameters for the Felix indexing algorithm.
-
-.PD 0
-.IP \fB--xgandalf-sampling-pitch=\fIn\fR
-.IP \fB--xgandalf-grad-desc-iterations=\fIn\fR
-.IP \fB--xgandalf-tolerance=\fIn\fR
-.IP \fB--xgandalf-no-deviation-from-provided-cell\fR
-.IP \fB--xgandalf-max-lattice-vector-length=\fIn\fR
-.IP \fB--xgandalf-min-lattice-vector-length=\fIn\fR
-.IP \fB--xgandalf-max-peaks=\fIn\fR
-.IP \fB--xgandalf-fast-execution\fR
-.PD
-These set low-level parameters for the XGANDALF indexing algorithm.
-.IP
-\fB--xgandalf-sampling-pitch\fR selects how dense the reciprocal space is sampled. [0-4]: extremelyLoose to extremelyDense. [5-7]: standardWithSeondaryMillerIndices to extremelyDenseWithSeondaryMillerIndices. Default is 6 (denseWithSeondaryMillerIndices).
-.IP
-\fB--xgandalf-grad-desc-iterations\fR selects how many gradient descent iterations are performed. [0-5]: veryFew to extremelyMany. Default is 4 (manyMany).
-.IP
-\fB--xgandalf-tolerance\fR relative tolerance of the lattice vectors. Default is 0.02.
-.IP
-\fB--xgandalf-no-deviation-from-provided-cell\fR if a prior unit cell was provided, and this flag is set, the found unit cell will have exactly the same size as the provided one.
-.IP
-\fB--xgandalf-min-lattice-vector-length\fR and \fB--xgandalf-min-lattice-vector-length\fR minimum and maximum possible lattice vector lengths (unit is A). Used for fitting without prior lattice as starting point for gradient descent, so the final minimum lattice vector length can be smaller/highier as min/max. Note: This is valid for the uncentered cell, i.e. the P-cell! Default is 30A and 250A respectively.
-.IP
-\fB--xgandalf-max-peaks\fR maximum number of peaks used for indexing. For refinement all peaks are used. Peaks are selected by increasing radius. Limits the maximum execution time for patterns with a huge amount of peaks - either real ones or false positives. Default is 250.
-.IP
-\fB--xgandalf-fast-execution\fR Shortcut to set --xgandalf-sampling-pitch=2 --xgandalf-grad-desc-iterations=3
-
-.PD 0
-.IP \fB--pinkIndexer-considered-peaks-count=\fIn\fR
-.IP \fB--pinkIndexer-angle-resolution=\fIn\fR
-.IP \fB--pinkIndexer-refinement-type=\fIn\fR
-.IP \fB--pinkIndexer-tolerance=\fIn\fR
-.IP \fB--pinkIndexer-reflection-radius=\fIn\fR
-.IP \fB--pinkIndexer-max-resolution-for-indexing=\fIn\fR
-.IP \fB--pinkIndexer-max-refinement-disbalance=\fIn\fR
-
-.PD
-These set low-level parameters for the PinkIndexer indexing algorithm.
-.IP
-\fB--pinkIndexer-considered-peaks-count\fR selects how many peaks are considered for indexing. [0-4] (veryFew to manyMany). Default is 4 (manyMany).
-.IP
-\fB--pinkIndexer-angle-resolution\fR selects how dense the orientation angles of the sample lattice are sampled. [0-4] (extremelyLoose to extremelyDense). Default is 2 (normal).
-.IP
-\fB--pinkIndexer-refinement-type\fR selects the refinement type. 0 = none, 1 = fixedLatticeParameters, 2 = variableLatticeParameters, 3 = firstFixedThenVariableLatticeParameters, 4 = firstFixedThenVariableLatticeParametersMultiSeed, 5 = firstFixedThenVariableLatticeParametersCenterAdjustmentMultiSeed.
-.IP
-\fB--pinkIndexer-tolerance\fR selects the tolerance of the pinkIndexer (relative tolerance of the lattice vectors). Default is 0.06. For bad geometrys or cell parameters use a high tolerance. For a well known geometry and cell use a small tolerance. Only important for refinement and indexed/not indexed identificaton. Too small tolerance will lead to refining to only a fraction of the peaks and possibly discarding of correctly indexed images. Too high tolerance will lead to bad fitting in presence of multiples or noise and can mark wrongly-indexed patterns as indexed.
-.IP
-\fB--pinkIndexer-reflection-radius\fR sets radius of the reflections in reciprocal space in 1/A. Default is 2%% of a* (which works quiet well for X-rays). Should be chosen much bigger for electrons (~0.002).
-.IP
-\fB--pinkIndexer-max-resolution-for-indexing\fR sets the maximum resolution in 1/A used for indexing. Peaks at high resolution don't add much information, but they add a lot of computation time. Default is infinity. Does not influence the refinement.
-.IP
-\fB--pinkIndexer-max-refinement-disbalance\fR Indexing solutions are dismissed if the refinement refined very well to one side of the detector and very badly to the other side. Allowed values range from 0 (no disbalance) to 2 (extreme disbalance), default 0.4. Disbalance after refinement usually appears for bad geometries or bad prior unit cell parameters.
-
-.PD 0
-.IP \fB--asdf-fast
-.PD
-This enables a faster mode of operation for \fIasdf\fR indexing, which is around 3 times faster but only about 7% less successful.
-
-.SH INTEGRATION OPTIONS
-.PD 0
-.IP \fB--integration=\fR\fImethod\fR
-.PD
-Integrate the reflections using \fImethod\fR. See the section titled \fBPEAK INTEGRATION\fR (above) for more information. The default is \fB--integration=rings-nocen\fR.
-
-.PD 0
-.IP \fB--fix-profile-radius=\fIn\fR
-.IP \fB--fix-divergence=\fIn\fR
-.PD
-Fix the beam and crystal paramters to the given values. The profile radius is given in m^-1 and the divergence in radians (full angle). The default is to set the divergence to zero, and then to automatically determine the profile radius.
-.IP
-You do not have to use all three of these options together. For example, if the automatic profile radius determination is not working well for your data set, you could fix that alone and continue using the default values for the other parameters (which might be automatically determined in future versions of CrystFEL, but are not currently).
-
-.PD 0
-.IP \fB--int-radius=\fR\fIinner,middle,outer\fR
-.PD
-Set the inner, middle and outer radii for three-ring integration. See the
-section about \fBPEAK INTEGRATION\fR, above, for details of how to determine
-these. The defaults are probably not appropriate for your situation.
-.PD
-The default is \fB--int-radius=4,5,7\fR.
-
-.PD 0
-.IP \fB--int-diag=\fIcondition\fR
-.PD
-Show detailed information about reflection integration when \fIcondition\fR is met. The \fIcondition\fR can be \fBall\fR, \fBnone\fR, a set of Miller indices separated by commas, \fBrandom\fR, \fBimplausible\fR or \fBnegative\fR. \fBrandom\fR means to show information about a random 1% of the peaks. \fBnegative\fR means to show peaks with intensities which are negative by more than 3 sigma. \fBimplausible\fR means to show peaks with intensities which are negative by more than 5 sigma. \fBstrong\fR means to show peaks with intensities which are positive by more than 3 sigma The default is \fB--int-diag=none\fR.
-
-.PD 0
-.IP \fB--push-res=\fIn\fR
-.PD
-Integrate \fIn\fR nm^-1 higher than the apparent resolution limit of each individual crystal. \fIn\fR can be negative to integrate \fIlower\fR than the apparent resolution limit. The default is \fB--push-res=infinity\fR, which means that no cutoff is applied. Note that you can also apply this cutoff at the merging stage using \fBprocess_hkl/partialator --push-res\fR, which is usually better: reflections which are thrown away at the integration stage cannot be brought back later. However, applying a resolution cutoff during integration will make the stream file significantly smaller and faster to merge.
-
-.PD 0
-.IP \fB--overpredict\fR
-.PD
-Over-predict reflections. This is needed to provide a buffer zone when using post-refinement, but makes it difficult to judge the accuracy of the predictions because there are so many reflections. It will also reduce the quality of the merged data if you merge without partiality estimation.
-
-.PD 0
-.IP \fB--cell-parameters-only\fR
-.PD
-Do not predict reflections at all. Use this option if you're not at all interested in the integrated reflection intensities or even the positions of the reflections. You will still get unit cell parameters, and the process will be much faster, especially for large unit cells.
-
-.SH OUTPUT OPTIONS
-
-.PD 0
-.IP \fB--no-non-hits-in-stream\fR
-.PD
-Completely exclude 'non-hit' frames in the stream. When this option is given, frames with fewer than the number of peaks given to \fB--min-peaks\fR will not have chunks written to the stream at all. Note that the default value for \fB--min-peaks\fR is zero, which means \fIall\fR frames will be written to the stream, even if they have no peaks at all.
-
-.PD 0
-.IP \fB--copy-header=\fR\fIheader\fR
-.PD
-Copy the information from \fR\fIheader\fR in the image file into the output stream. For HDF5 files, \fIheader\fR is interpreted as a path within the file. This option is sometimes useful to allow data to be separated after indexing according to some condition such the presence of an optical pump pulse. You can give this option as many times as you need to copy multiple bits of information.
-.IP
-The old option \fB--copy-hdf5-field\fR is a synonym for this option.
-
-.PD 0
-.IP \fB--no-peaks-in-stream\fR
-.PD
-Do not record peak search results in the stream. You won't be able to check that the peak detection was any good, but the stream will be around 30% smaller.
-
-.PD 0
-.IP \fB--no-refls-in-stream\fR
-.PD
-Do not record integrated reflections in the stream. The resulting output won't be usable for merging, but will be a lot smaller. This option might be useful if you're only interested in things like unit cell parameters and orientations.
-
-.PD 0
-.IP \fB--serial-offset=\fIn\fR
-.PD
-Start the serial numbers in the stream at \fIn\fR instead of 1. Use this if you are splitting an indexing job up into several smaller ones, so that the streams can be concatenated into a single one with consistent numbering. This is important if you use \fBwhirligig\fR.
-
-.PD 0
-.IP \fB--harvest-file=\fIfn\fR
-.PD
-Write a list of parameters to \fIfn\fR, in JSON format. This is intended to be used for harvesting data into a database system. This option has no effect if --serial-offset is set to a number larger than 1, to avoid the file being overwritten multiple times in a batch system.
-
-
-.SH HISTORICAL OPTIONS
-
-.PD 0
-.IP \fB--no-sat-corr\fR
-.PD
-This option is here for historical purposes only, to disable a correction which is done if certain extra information is included in the image data file.
-
-.SH IDENTIFYING SINGLE PATTERNS IN THE INPUT FILE
-
-By default indexamajig processes all diffraction patterns ("events") in each of the data files listed in the input list. It is however, possible, to only process single events in a multi-event file, by adding in the list an event description string after the data filename. The event description always includes a first section with alphanumeric strings separated by forward slashes ("/") and a second section with integer numbers also separated by forward slashes. The two sections are in turn separated by a double forward slash ('//'). Any of the two sections can be empty, but the double forward slash separator must always be present. Indexamajig matches the strings and the numbers in the event description with the event placeholders ('%') present respectively in the 'data' and 'dim' properties defined in the geometry file, and tries to retrieve the full HDF path to the event data and the the its location in a multi-dimensional data space. Consider the following examples:
-
-\fBExample 1:\fR The 'data' and 'dim' properties have been defined like this in the geometry file:
-
-.br
-data = /data/%/rawdata
-.br
-dim0 = ss
-.br
-dim1 = fs
-
-The event list contains the following line:
-.br
-
-filename.h5 event1//
-.br
-
-This identifies an event in the 2-dimensional data block located at /data/event1/rawdata in the HDF5 file called filename.h5.
-
-\fBExample 2:\fR The 'data' and 'dim' properties have been defined like this in the geometry file:
-
-.br
-data = /data/rawdata
-.br
-dim0 = %
-.br
-dim1 = ss
-.br
-dim2 = fs
-
-The event list contains the following line:
-.br
-
-filename.h5 //3
-.br
-
-This identifies an event in the 3-dimensional data block located at /data/rawdata in the HDF5 file called filename.h5, specifically the 2-dimensional data slice defined by the value 3 of the first axis of the data space.
-
-Indexamajig tries to match the alphanumerical strings to the placeholders in the 'dim' property defined in the geometry file. The first string is matched to the first placeholder, the second to
-the second placeholder, and so on. A similar strategy is followed to match integer numbers to the placeholders in the 'dim' property defined in the geometry file.
-For a full explanation of how the internal layout of the data file can be described in the geometry file, please see \fBman crystfel_geometry\fR.
-
-You can use \fBlist_events\fR to prepare a list of each event in one or more input files. Note that you only need to do this if you need to perform some sorting or filtering on this list. If you want to process every event in a file, simply specify the filename in the input file.
-
-.SH AUTHOR
-This page was written by Thomas White, Yaroslav Gevorkov and Valerio Mariani.
-
-.SH REPORTING BUGS
-Report bugs to <taw@physics.org>, or visit <http://www.desy.de/~twhite/crystfel>.
-
-.SH COPYRIGHT AND DISCLAIMER
-Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY, a research centre of the Helmholtz Association.
-.P
-indexamajig, and this manual, are part of CrystFEL.
-.P
-CrystFEL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
-.P
-CrystFEL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-.P
-You should have received a copy of the GNU General Public License along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
-
-.SH SEE ALSO
-.BR crystfel (7),
-.BR crystfel_geometry (5),
-.BR cell_explorer (1),
-.BR process_hkl (1),
-.BR partialator (1),
-.BR list_events (1),
-.BR whirligig (1)
diff --git a/doc/man/indexamajig.1.md b/doc/man/indexamajig.1.md
new file mode 100644
index 00000000..229be44a
--- /dev/null
+++ b/doc/man/indexamajig.1.md
@@ -0,0 +1,927 @@
+% indexamajig(1)
+
+NAME
+====
+
+indexamajig - bulk indexing and data reduction program
+
+
+SYNOPSIS
+========
+
+indexamajig
+-i _filename_ -o _output.stream_ -g _detector.geom_ --peaks=_method_ --indexing=_method_ ...
+
+
+DESCRIPTION
+===========
+
+**indexamajig** takes a list of diffraction snapshots from crystals in random
+orientations and attempts to find peaks, index and integrate each one. The
+input is a list of diffraction image files and some auxiliary files and
+parameters. The output is a long text file ('stream') containing the results
+from each image in turn.
+
+For minimal basic use, you need to provide the list of diffraction patterns,
+the method which will be used to index, a file describing the geometry of the
+detector, and a file which contains the unit cell which will be used for the
+indexing. Here is what the minimal use might look like on the command line:
+
+ indexamajig
+ -i mypatterns.lst
+ -g mygeometry.geom
+ --indexing=xgandalf
+ --peaks=hdf5
+ -p mycell.pdb
+ -o test.stream
+
+More typical use includes all the above, but might also include extra
+parameters to modify the behaviour. For example, you'll probably want to run
+more than one indexing job at a time (-j <n>).
+
+See **man crystfel_geometry** for information about how to create a file
+describing the detector geometry and beam characteristics.
+
+
+DIFFRACTION PATTERN LIST
+========================
+
+Indexamajig requires an input file with a list of diffraction patterns to
+process. In its simplest form, this is just a text files containing a list of
+image filenames. The image files might be in some folder a long way from the
+current directory, so you might want to specify a full pathname to be added in
+front of each filename. The geometry file includes a description of the data
+layout within the files. Indexamajig uses this description to determine the
+number of diffraction patterns stored in each file, and tries to process them
+all. You can also specify explicity which framess you would like to process
+by putting a string describing the frames after the file name(s) in this list.
+
+
+PEAK DETECTION
+==============
+
+You can control the peak detection on the command line. First, you can choose
+the peak detection method using **--peaks=method**. **--peaks=hdf5** or
+**--peaks=cxi** will take the peak locations from the input file. See the
+documentation for peak_list and peak_list_type in **crystfel_geometry**(5) for
+details.
+
+If you use **--peaks=zaef**, indexamajig will use a simple gradient search
+after Zaefferer (2000). You can control the overall threshold and minimum
+squared gradient for finding a peak using **--threshold** and
+**--min-squared-gradient**. The threshold has arbitrary units matching the
+pixel values in the data, and the minimum gradient has the equivalent squared
+units. Peaks will be rejected if the 'foot point' is further away from the
+'summit' of the peak by more than the inner integration radius (see below).
+They will also be rejected if the peak is closer than twice the inner
+integration radius from another peak.
+
+If you instead use **--peaks=peakfinder8**, indexamajig will use the
+"peakfinder8" peak finding algorithm describerd in Barty et al. (2014). Pixels
+above a radius-dependent intensity threshold are considered as candidate peaks
+(although the user sets an absolute minimum threshold for candidate peaks).
+Peaks are then only accepted if their signal to noise level over the local
+background is sufficiently high. Peaks can include multiple pixels and the user
+can reject a peak if it includes too many or too few. The distance of a peak
+from the center of the detector can also be used as a filtering criterion. Note
+that the peakfinder8 will not report more than 2048 peaks for each panel: any
+additional peak is ignored.
+
+If you instead use **--peaks=peakfinder9**, indexamajig will use the
+"peakfinder9" peak finding algorithm described in the master thesis "Real-time
+image analysis and data compression in high throughput X-ray diffraction
+experiments" by Gevorkov. Other than peakFinder8, peakFinder9 uses local
+background estimation based on border pixels in a specified radius
+(**--local-bg-radius**). For being fast and precise, a hierarchy of
+conditions is used. First condition is only useful for speed consideration, it
+demands that a pixel that is the biggest pixel in a peak must be larger than
+every border pixel by a constant value (**--min-peak-over-neighbour**).
+Second condition ensures, that the pixel passing the previous condition is the
+highest pixel in the peak. It assumes, that peaks rise monotonically towards
+the biggest pixel. Third condition ensures, that the biggest pixel in the peak
+is significantly over the noise level (**--min-snr-biggest-pix**) by
+computing the local statistics from the border pixels in a specified radius.
+Fourth condition sums up all pixels belonging to the peak
+(**--min-snr-peak-pix**) and demands that the whole peak must be
+significantly over the noise level (**--min-snr**). Only if all conditions
+are passed, the peak is accepted.
+
+
+INDEXING METHODS
+================
+
+You can choose between a variety of indexing methods. You can choose more than
+one method, in which case each method will be tried in turn until one of them
+reports that the pattern has been successfully indexed. Choose from:
+
+**dirax**
+: Invoke DirAx. See Duisenberg, J. Applied Crystallography 25 (1992) p92,
+: https://doi.org/10.1107/S0021889891010634.
+
+**mosflm**
+: Invoke Mosflm. See Powell, Acta Crystallographica D55 (1999) p1690,
+: https://doi.org/10.1107/S0907444999009506.
+
+**asdf**
+: This is a implementation of the dirax algorithm, with some very small changes
+: such as using a 1D Fourier transform for finding the lattice repeats. This
+: algorithm is implemented natively within CrystFEL meaning that no external
+: software is required.
+
+**felix**
+: Invoke Felix, which will use your cell parameters to find multiple crystals in
+: each pattern.
+: The Felix indexer has been developed by Soeren Schmidt <ssch@fysik.dtu.dk>. To
+: use this option, 'Felix' must be in your shell's search path. This can be a
+: link to the latest version of Felix. If you see the Felix version information
+: when you run Felix on the command line, things are set up correctly.
+
+**xds**
+: Invoke XDS, and use its REFIDX procedure to attempt to index the pattern.
+
+**taketwo**
+: Use the TakeTwo algorithm. See Ginn et al., Acta Crystallographica D72
+(2016), p956, https://doi.org/10.1107/S2059798316010706.
+
+**xgandalf**
+: Use the eXtended GrAdieNt Descent Algorithm for Lattice Finding.
+: See Gevorkov et al., Acta Crystallographica A75 (2019) p694,
+: https://doi.org/10.1107/S2053273319010593.
+
+**pinkIndexer**
+: Use the pinkIndexer algorithm. See Gevorkov et al., Acta Crystallographica
+: A76 (2020) p121, https://doi.org/10.1107/S2053273319015559.
+
+**file**
+: See **Re-playing old indexing and injecting external results** below.
+
+Most of the indexing methods require some extra software to be installed,
+either at the time of compiling CrystFEL or afterwards. CrystFEL is
+distributed with a script (scripts/install-indexers) which can help you to
+quickly install all the required programs.
+
+If you don't specify any indexing methods, indexamajig will try to
+automatically determine which indexing methods are available. You can also
+specify indexing method none, in which case no indexing will be done. This is
+useful if you just want to check that the peak detection is working properly.
+
+
+### Prior unit cell information
+
+You can add one or more of the following to the above indexing methods, to
+control what information should be provided to them. Note that indexamajig
+performs a series of checks on the indexing results, including checking that
+the result is consistent with the target unit cell parameters. To get
+completely "raw" indexing, you need to disable these checks (see below) and not
+provide prior information.
+
+**-latt**
+: Provide the Bravais lattice type (e.g. the knowledge that the lattice is
+: tetragonal primitive), as prior information to the indexing engine.
+
+**-nolatt**
+: The opposite of -latt: do not provide Bravais lattice type information to the
+: indexing engine.
+
+**-cell**
+: Provide your unit cell parameters as prior information to the indexing
+: engine.
+
+**-nocell**
+: The opposite of -cell: do not provide unit cell parameters as prior information
+: to the indexing engine.
+
+Example: **--indexing=mosflm-cell-latt** means to use Mosflm for indexing, and
+provide it with unit cell parameters and Bravais lattice type information.
+
+Usually, you do not need to explicitly specify anything more than the indexing
+method itself (e.g. mosflm or asdf). The default behaviour for all indexing
+methods is to make the maximum possible use of prior information such as the
+lattice type and cell parameters. If you do not provide this information, for
+example if you do not give any unit cell file or if the unit cell file does not
+contain cell parameters (only lattice type information), the indexing methods
+you give will be modified accordingly. If you only specify the indexing
+methods themselves, in most cases indexamajig will do what you want and
+intuitively expect! However, the options are available if you need finer
+control.
+
+
+### Post-indexing stages
+
+The indexing results from the indexing engine will be put through a number of
+refinement and checking stages. See the options **--no-check-cell**,
+**--no-multi**, **--no-retry** and **--no-refine** below for more details.
+
+
+### Re-playing old indexing and injecting external results
+
+With **--indexing=file**, indexamajig will read indexing results from a text
+file. Use **--fromfile-input-file** to specify the filename. Each line of the
+file represents one diffraction pattern in one image, and should be formatted
+as follows:
+
+ filename frameID asx asy asz bsx bsy bsz csx csy csz xshift yshift latt_cen
+
+To describe multiple overlapping diffraction patterns in one frame, simply
+use the same _filename_ and _frameID_ in multiple lines.
+
+The lattice type and centering information are contained in _latt\_cen_.
+This should consist of two characters, e.g. **tI** or **aP**. The first letter
+represents the lattice type (**a**, **m**, **o**, **t**, **c**, **h**, **r**
+for, respectively, triclinic, monoclinic, orthorhombic, tetragonal, cubic,
+hexagonal or rhombohedral). The centering symbol (**P**, **A**, **B**, **C**,
+**I**, **F**, **H** or **R**) follows.
+
+The vector components _asx_,_asy_,... are the reciprocal lattice basis vectors
+in reciprocal nanometres. The _xshift_ and _yshift_ values are the offsets of
+the detector position in millimetres. Usually, these offsets should be zero.
+
+This method can be used to inject indexing results from an external indexing
+program. It can also be used to re-play previous indexing results, which is
+useful for changing integration parameters while using slower indexing methods
+(such as **pinkIndexer**). To generate an indexing result from a previous
+**indexamajig** run, use the script **stream2sol** in the CrystFEL **scripts**
+folder.
+
+
+REFLECTION INTEGRATION
+======================
+
+If the pattern could be successfully indexed, peaks will be predicted in the
+pattern and their intensities measured. You have a choice of integration
+methods, and you specify the method using **--integration**. Choose from:
+
+**rings**
+: Use three concentric rings to determine the peak, buffer and background
+: estimation regions. The radius of the smallest circle sets the peak region.
+: The radius of the middle and outer circles describe an annulus from which the
+: background will be estimated. You can set the radii of the rings using
+: **--int-radius** (see below). The default behaviour with rings is not to
+: center the peak boxes first. Use rings-cen if you want to use centering.
+
+**prof2d**
+: Integrate the peaks using 2D profile fitting with a planar background, close to
+: the method described by Rossmann (1979) J. Appl. Cryst. 12 p225. The default
+: behaviour with prof2d is to center the peak first - use prof2d-nocen to skip
+: this step.
+
+You can add one or more of the following to the above integration methods:
+
+**-cen**
+: Center the peak boxes iteratively on the actual peak locations. The opposite
+: is -nocen, which is the default.
+
+**-sat**
+: Normally, reflections which contain one or more pixels above max_adu (defined
+: in the detector geometry file) will not be integrated and written to the
+: stream. Using this option skips this check, and allows saturated reflections
+: to be passed to the later merging stages. The opposite is -nosat, which is the
+: default for all integration methods. However, note that the saturation check
+: will only be done if max_adu is set in the geometry file. Usually, it's better
+: to exclude saturated reflections at the merging stage. See the documentation
+: for max_adu in crystfel_geometry(5).
+
+**-grad**
+: Fit the background around the reflection using gradients in two dimensions.
+: This was the default until version 0.6.1. Without the option (or with its
+: opposite, -nograd, which is the default), the background will be considered to
+: have the same value across the entire integration box, which gives better
+: results in most cases.
+
+
+BASIC OPTIONS
+-------------
+
+**-i filename**, **--input=filename**
+: Read the list of images to process from filename. **--input=-** means to
+: read from stdin. There is no default.
+
+**-o filename**, **--output=filename**
+: Write the output data stream to filename.
+
+**-g filename**, **--geometry=filename**
+: Read the detector geometry description from _filename_. See **man
+: crystfel_geometry** for more information.
+
+: **--zmq-input=address**
+: Receive data over ZeroMQ from address. The options **--input** and
+: **--zmq-input** are mutually exclusive - you must specify exactly one of
+: them. Example: **--zmq-input=tcp://127.0.0.1:5002**.
+: If you use this option, you should also use either **--zmq-subscribe** to add
+: a ZeroMQ subscription, or **--zmq-request** to define how to request data.
+
+**--zmq-subscribe=tag**
+: Subscribe to ZeroMQ message type tag. You can use this option multiple times
+: to add multiple subscriptions. This option and **--zmq-request** are mutually
+: exclusive.
+
+**--zmq-request=msg**
+: Request new data over ZeroMQ by sending string msg. This will cause
+: indexamajig's ZeroMQ socket to use REQ mode instead of SUB. This option and
+: **--zmq-subscribe** are mutually exclusive.
+
+**--asapo-endpoint=endpoint**
+: Receive data via the specified ASAP::O endpoint. This option and **--zmq-input**
+: are mutually exclusive.
+
+**--asapo-token=token**
+: Authentication token for ASAP::O data.
+
+**--asapo-beamtime=beamtime**
+: Beamtime ID for ASAP::O data.
+
+**--asapo-source=source**
+: Data source for ASAP::O data.
+
+**--asapo-group=group**
+: Consumer group name for ASAP::O data. Concurrent **indexamajig** processes
+: working on the same data should use the same value for this, to have the
+: data shared between them.
+
+**--asapo-stream=stream**
+: Stream name for ASAP::O data.
+
+**--asapo-output-stream=stream**
+: Send an output stream via ASAP::O. For non-hits, a small placeholder will be
+: sent.
+
+**--asapo-wait-for-stream**
+: If the ASAP::O stream does not exist, wait for it to be appear. Without this
+: option, indexamajig will exit immediately if the stream is not found.
+
+**--data-format=format**
+: Specify the data format for data received over ZeroMQ or ASAP::O. Possible
+: values in this version are msgpack, hdf5 and seedee.
+
+**--basename**
+: Remove the directory parts of the filenames taken from the input file. If
+: **--prefix** or -x is also given, the directory parts of the filename will be
+: removed before adding the prefix.
+
+**-x prefix**, **--prefix=prefix**
+: Prefix the filenames from the input file with prefix. If **--basename** is also
+: given, the filenames will be prefixed after removing the directory parts of the
+: filenames.
+
+**-j n**
+: Run n analyses in parallel. Default: 1. See also **--max-indexer-threads.**
+: Tip: use -j `nproc` (note the backticks) to use as many processes as there
+: are available CPUs.
+
+**--cpu-pin**
+: Pin worker processes to CPUs. Usually this is not needed or desirable, but in
+: some cases it dramatically improves performance.
+
+**--no-check-prefix**
+: Don't attempt to correct the prefix (see **--prefix**) if it doesn't look correct.
+
+**--highres=n**
+: Mark all pixels on the detector higher than n Angstroms as bad. This might be
+: useful when you have noisy patterns and don't expect any signal above a certain
+: resolution.
+
+**--profile**
+: Display timing data for performance monitoring.
+
+**--temp-dir=path**
+: Put the temporary folder under path.
+
+**--wait-for-file=n**
+: Wait at most n seconds for each image file in the input list to be created
+: before trying to process it. This is useful for some automated processing
+: pipelines. It obviously only really works for single-frame files. If a file
+: exists but is not readable when this option is set non-zero, a second attempt
+: will be made after ten seconds. This is to allow for incompletely written
+: files. A value of -1 means to wait forever. The default value is
+: **--wait-for-file=0**.
+
+**--no-image-data**
+: Do not load the actual image data (or bad pixel masks), only the metadata.
+: This allows you to check if patterns can be indexed, without high data
+: bandwidth requirements. Obviously, any feature requiring the image data,
+: especially peak search procedures and integration, cannot be used in this case.
+: Therefore, you'll need to get the peaks from somewhere else (see
+: **--peaks=msgpack** or **--peaks=hdf5**).
+
+
+PEAK SEARCH OPTIONS
+-------------------
+
+**--peaks=method**
+: Find peaks in the images using method. See the second titled PEAK DETECTION
+: (above) for more information.
+
+**--peak-radius=inner,middle,outer**
+: Set the inner, middle and outer radii for three-ring integration during the
+: peak search. See the section about PEAK INTEGRATION, above, for details of how
+: to determine
+: these. The default is to use the same values as for **--int-radius**.
+
+**--min-peaks=n**
+: Do not try to index frames with fewer than n peaks. These frames will still be
+: described in the output stream. To exclude them, use **--no-non-hits-in-stream**.
+: The default is **--min-peaks=0**, which means that all frames will be considered
+: hits, even if they have no peaks at all.
+
+**--median-filter=n**
+: Apply a median filter with box "radius" n to the image. The median of the
+: values from a (n+1)x(n+1) square centered on the pixel will be subtracted from
+: each pixel. This might help with peak detection if the background is high
+: and/or noisy. The unfiltered image will be used for the final integration of
+: the peaks. If you also use **--noise-filter**, the median filter will be applied
+: first.
+
+**--filter-noise**
+: Apply a noise filter to the image with checks 3x3 squares of pixels and sets
+: all of them to zero if any of the nine pixels have a negative value. This
+: filter may help with peak detection under certain circumstances. The
+: unfiltered image will be used for the final integration of the peaks, because
+: the filter is destroys a lot of information from the pattern. If you also use
+: **--median-filter**, the median filter will be applied first.
+
+**--threshold=thres**
+: Set the overall threshold for peak detection using **--peaks=zaef** or
+: **--peaks=peakfinder8** to thres, which has the same units as the detector data.
+: The default is **--threshold=800**.
+
+**--min-squared-gradient=grad**
+: Set the square of the gradient threshold for peak detection using **--peaks=zaef**
+: to grad, which has units of "squared detector units per pixel". The default is
+: **--min-squared-gradient=100000**. **--min-sq-gradient** and **--min-gradient** are
+: synonyms for this option, however the latter should not be used to avoid
+: confusion.
+
+**--min-snr=snr**
+: Set the minimum I/sigma(I) for peak detection when using **--peaks=zaef**,
+: **--peaks=peakfinder8** or **--peaks=peakfinder9.** The default is **--min-snr=5**.
+
+**--min-snr-biggest-pix=<n>**
+: (peakFinder9 only) min snr of the biggest pixel in the peak, given as a factor
+: of the standard deviation. Default is 7.0.
+
+**--min-snr-peak-pix=<n>**
+: (peakFinder9 only) min snr of a peak pixel, given as a factor of the standard
+: deviation. Should be smaller or equal to sig_fac_biggest_pix. Default is 6.0.
+
+**--min-sig=<n>**
+: (peakFinder9 only) minimum standard deviation of the background. Prevents
+: finding of peaks in erroneous or highly shadowed unmasked regions. Default is
+: 11.0.
+
+**--min-peak-over-neighbour=<n>**
+: (peakFinder9 only) just for speed. Biggest pixel must be n higher than the
+: pixels in window_radius distance to be a candidate for the biggest pixel in a
+: peak. Should be chosen as a small positive number, a few times smaller than the
+: weakest expected peak. The default is -INFINITY, which turns off the speedup
+: and searches with maximum precision.
+
+**--min-pix-count=cnt**
+: Accepts peaks only if they include more than cnt pixels, when using
+: --peaks=peakfinder8. The default is **--min-pix-count=2**.
+
+**--max-pix-count=cnt**
+: Accepts peaks only if they include less than cnt pixels, when using
+: **--peaks=peakfinder8**. The default is **--max-pix-count=200**.
+
+**--local-bg-radius=r**
+: Radius (in pixels) used for the estimation of the local background when using
+: **--peaks=peakfinder8** or **--peaks=peakfinder9**. The default is
+: **--local-bg-radius=3**.
+
+**--min-res=px**
+: Only accept peaks if they lay at more than px pixels from the center of the
+: detector when using **--peaks=peakfinder8**. The default is **--min-res=0**.
+
+**--max-res=px**
+: Only accept peaks if they lay at less than px pixels from the center of the
+: detector when using **--peaks=peakfinder8**. The default is **--max-res=1200**.
+
+**--no-use-saturated**
+: Normally, peaks which contain one or more pixels above max_adu (defined in the
+: detector geometry file) will be used for indexing (but not used in the final
+: integration - see the section on peak integration above). Using this option
+: causes saturated peaks to be ignored completely. The opposite is
+: **--use-saturated**, which is the default.
+
+**--no-revalidate**
+: When using **--peaks=hdf5**, **--peaks=cxi** or **--peaks=msgpack**, the peaks will be put
+: through some of the same checks as if you were using **--peaks=zaef**. These
+: checks reject peaks which are too close to panel edges, are saturated (unless
+: you use **--use-saturated**), have other nearby peaks (closer than twice the inner
+: integration radius, see **--int-radius**), or have any part in a bad region. Using
+: this option skips this validation step, and uses the peaks directly.
+
+**--no-half-pixel-shift**
+: CrystFEL considers all peak locations to be distances from the corner of the
+: detector panel, in pixel units, consistent with its description of detector
+: geometry (see 'man crystfel_geometry'). The software which generates the image
+: data files, including Cheetah, may instead consider the peak locations to be
+: pixel indices in the data array. Therefore, the peak coordinates from
+: **--peaks=cxi** or **--peaks=hdf5** will by default have 0.5 added to them. This
+: option disables this half-pixel offset.
+
+**--check-hdf5-snr**
+: With this option with **--peaks=hdf5** (or cxi or msgpack), the peaks will
+: additionally be checked to see that they satisfy the minimum SNR specified with
+: **--min-snr**.
+
+**--peakfinder8-fast**
+: (peakfinder8 only) Increase speed by pre-calculating some coordinate tables.
+
+
+INDEXING OPTIONS
+----------------
+
+**--indexing=method**
+: Index the patterns using method. See the section titled INDEXING METHODS
+: (above) for more information. The default is to automatically detect which
+: indexing methods to use.
+
+**-p unitcell.cell**, **-p unitcell.pdb**, **--pdb=unitcell.pdb**
+: Specify the name of the file containing unit cell information, in PDB or
+: CrystFEL format.
+
+**--tolerance=tol**
+: Set the tolerances for unit cell comparison. tol takes the form a,b,c,ang. a,
+: b and c are the tolerances, in percent, for the respective reciprocal space
+: axes, and ang is the tolerance in degrees for the reciprocal space angles. If
+: the unit cell is centered, the tolerances are applied to the corresponding
+: primitive unit cell. The default is **--tolerance=5,5,5,1.5**.
+
+**--no-check-cell**
+: Do not check the cell parameters against the reference unit cell (given with
+: -p). If you've used older versions of CrystFEL, this replaces putting "-raw"
+: in the indexing method.
+
+**--no-check-peaks**
+: Do not check that most of the peaks can be accounted for by the indexing
+: solution. The thresholds for a successful result are influenced by option
+: --multi.
+
+**--multi**
+: Enable the "subtract and retry" method, where after a successful indexing
+: attempt the spots accounted for by the indexing solution are removed before
+: trying to index again in the hope of finding a second lattice. This doesn't
+: have anything to do with the multi-lattice indexing algorithms such as Felix.
+: This option also adjusts the thresholds for identifying successful indexing
+: results (see **--no-check-peaks**).
+
+**--no-retry**
+: Disable retry indexing. After an unsuccessful indexing attempt, indexamajig
+: would normally remove the 10% weakest peaks and try again. This option
+: disables that, which makes things much faster but decreases the indexing
+: success rate.
+
+**--no-refine**
+: Skip the prediction refinement step. Usually this will decrease the quality of
+: the results and allow false solutions to get through, but occasionally it might
+: be necessary.
+
+**--mille**
+: Write detector calibration data in Millepede-II format.
+
+**--wavelength-estimate=m** **--camera-length-estimate=m**
+: Some indexing algorithms need to know the camera length or the wavelength of
+: the incident radiation in advance, e.g. to prepare an internal look-up table.
+: However, if these values are taken from image headers, then they are not
+: available at start-up. In this case, you will be prompted to add one of these
+: options to give approximate values (in metres). A warning will be generated if
+: the actual value differs from this value by more than 10%.
+
+**--max-indexer-threads=n**
+: Some indexing algorithms (e.g. pinkIndexer) can use multiple threads for faster
+: calculations. This is in addition to the frame-based parallelism already
+: available in indexamajig (see -j). This option sets the maximum number of
+: threads that each indexing engine is allowed to use. Default: 1.
+
+**--taketwo-member-threshold=n**
+: Minimum number of vectors in the network before the pattern is considered
+: indexed. Default 20.
+
+**--taketwo-len-tolerance=n**
+: The length tolerance in reciprocal Angstroms. Default 0.001.
+
+**--taketwo-angle-tolerance=n**
+: The angle tolerance in degrees. Default 0.6.
+
+**--taketwo-trace-tolerance=n**
+: The rotation matrix trace tolerance in degrees. Default 3.
+
+**--felix-domega=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--felix-fraction-max-visits=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--felix-max-internal-angle=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--felix-max-uniqueness=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--felix-min-completeness=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--felix-min-visits=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--felix-num-voxels=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--felix-sigma=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--felix-tthrange-max=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--felix-tthrange-min=n**
+: Low-level parameter for the Felix indexing algorithm.
+
+**--xgandalf-sampling-pitch=n**
+: Selects how dense the reciprocal space is sampled. [0-4]: extremelyLoose to
+: extremelyDense. [5-7]: standardWithSeondaryMillerIndices to
+: extremelyDenseWithSeondaryMillerIndices. Default is 6
+: (denseWithSeondaryMillerIndices).
+
+**--xgandalf-grad-desc-iterations**
+: Selects how many gradient descent iterations are performed. [0-5]: veryFew to
+: extremelyMany. Default is 4 (manyMany).
+
+**--xgandalf-tolerance**
+: relative tolerance of the lattice vectors. Default is 0.02.
+
+**--xgandalf-no-deviation-from-provided-cell**
+: If a prior unit cell was provided, and this flag is set, the found unit cell
+: will have exactly the same size as the provided one.
+
+**--xgandalf-min-lattice-vector-length** **--xgandalf-min-lattice-vector-length**
+: Minimum and maximum possible lattice vector lengths (unit is A). Used for
+: fitting without prior lattice as starting point for gradient descent, so the
+: final minimum lattice vector length can be smaller/highier as min/max. Note:
+: This is valid for the uncentered cell, i.e. the P-cell! Default is 30A and 250A
+: respectively.
+
+**--xgandalf-max-peaks**
+: Maximum number of peaks used for indexing. For refinement all peaks are used.
+: Peaks are selected by increasing radius. Limits the maximum execution time for
+: Patterns with a huge amount of peaks - either real ones or false positives.
+: Default is 250.
+
+**--xgandalf-fast-execution**
+: Shortcut to set **--xgandalf-sampling-pitch=2 --xgandalf-grad-desc-iterations=3**.
+
+**--pinkIndexer-considered-peaks-count**
+: Selects how many peaks are considered for indexing. [0-4] (veryFew to
+: manyMany). Default is 4 (manyMany).
+
+**--pinkIndexer-angle-resolution**
+: Selects how dense the orientation angles of the sample lattice are sampled.
+: [0-4] (extremelyLoose to extremelyDense). Default is 2 (normal).
+
+**--pinkIndexer-refinement-type**
+: Selects the refinement type. 0 = none, 1 = fixedLatticeParameters, 2 =
+: variableLatticeParameters, 3 = firstFixedThenVariableLatticeParameters, 4 =
+: firstFixedThenVariableLatticeParametersMultiSeed, 5 =
+: firstFixedThenVariableLatticeParametersCenterAdjustmentMultiSeed.
+
+**--pinkIndexer-tolerance**
+: Selects the tolerance of the pinkIndexer (relative tolerance of the lattice
+: vectors). Default is 0.06. For bad geometrys or cell parameters use a high
+: tolerance. For a well known geometry and cell use a small tolerance. Only
+: important for refinement and indexed/not indexed identificaton. Too small
+: tolerance will lead to refining to only a fraction of the peaks and possibly
+: discarding of correctly indexed images. Too high tolerance will lead to bad
+: Fitting in presence of multiples or noise and can mark wrongly-indexed patterns
+: as indexed.
+
+**--pinkIndexer-reflection-radius**
+: Sets radius of the reflections in reciprocal space in 1/A. Default is 2%% of a*
+: (which works quiet well for X-rays). Should be chosen much bigger for electrons
+: (~0.002).
+
+**--pinkIndexer-max-resolution-for-indexing**
+: Sets the maximum resolution in 1/A used for indexing. Peaks at high resolution
+: Don't add much information, but they add a lot of computation time. Default is
+: infinity. Does not influence the refinement.
+
+**--pinkIndexer-max-refinement-disbalance**
+: Indexing solutions are dismissed if the refinement refined very well to one
+: side of the detector and very badly to the other side. Allowed values range
+: from 0 (no disbalance) to 2 (extreme disbalance), default 0.4. Disbalance after
+: Refinement usually appears for bad geometries or bad prior unit cell
+: parameters.
+
+**--asdf-fast**
+: This enables a faster mode of operation for asdf indexing, which is around 3
+: times faster but only about 7% less successful.
+
+
+INTEGRATION OPTIONS
+-------------------
+
+**--integration=method**
+: Integrate the reflections using method. See the section titled PEAK
+: INTEGRATION (above) for more information. The default is
+: **--integration=rings-nocen**.
+
+**--fix-profile-radius=n**, **--fix-divergence=n**
+: Fix the beam and crystal paramters to the given values. The profile radius is
+: given in m^-1 and the divergence in radians (full angle). The default is to
+: set the divergence to zero, and then to automatically determine the profile
+: radius.
+: You do not have to use all three of these options together. For example, if
+: the automatic profile radius determination is not working well for your data
+: set, you could fix that alone and continue using the default values for the
+: other parameters (which might be automatically determined in future versions of
+: CrystFEL, but are not currently).
+
+**--int-radius=inner,middle,outer**
+: Set the inner, middle and outer radii for three-ring integration. See the
+: section about PEAK INTEGRATION, above, for details of how to determine
+: these. The defaults are probably not appropriate for your situation.
+: The default is **--int-radius=4,5,7**.
+
+**--int-diag=condition**
+: Show detailed information about reflection integration when condition is met.
+: The condition can be all, none, a set of Miller indices separated by commas,
+: random, implausible or negative. random means to show information about a
+: random 1% of the peaks. negative means to show peaks with intensities which
+: are negative by more than 3 sigma. implausible means to show peaks with
+: intensities which are negative by more than 5 sigma. strong means to show
+: peaks with intensities which are positive by more than 3 sigma The default is
+: **--int-diag=none**.
+
+**--push-res=n**
+: Integrate n nm^-1 higher than the apparent resolution limit of each individual
+: crystal. n can be negative to integrate lower than the apparent resolution
+: limit. The default is --push-res=infinity, which means that no cutoff is
+: applied. Note that you can also apply this cutoff at the merging stage using
+: **process_hkl/partialator --push-res**, which is usually better: reflections
+: which are thrown away at the integration stage cannot be brought back later.
+: However, applying a resolution cutoff during integration will make the stream
+: file significantly smaller and faster to merge.
+
+**--overpredict**
+: Over-predict reflections. This is needed to provide a buffer zone when using
+: post-refinement, but makes it difficult to judge the accuracy of the
+: predictions because there are so many reflections. It will also reduce the
+: quality of the merged data if you merge without partiality estimation.
+
+**--cell-parameters-only**
+: Do not predict reflections at all. Use this option if you're not at all
+: interested in the integrated reflection intensities or even the positions of
+: the reflections. You will still get unit cell parameters, and the process will
+: be much faster, especially for large unit cells.
+
+OUTPUT OPTIONS
+--------------
+
+**--no-non-hits-in-stream**
+: Completely exclude 'non-hit' frames in the stream. When this option is given,
+: frames with fewer than the number of peaks given to **--min-peaks** will not
+: have chunks written to the stream at all. Note that the default value for
+: **--min-peaks** is zero, which means all frames will be written to the stream,
+: even if they have no peaks at all.
+
+**--copy-header=header**
+: Copy the information from header in the image file into the output stream. For
+: HDF5 files, header is interpreted as a path within the file. This option is
+: sometimes useful to allow data to be separated after indexing according to some
+: condition such the presence of an optical pump pulse. You can give this option
+: as many times as you need to copy multiple bits of information.
+: The old option **--copy-hdf5-field** is a synonym for this option.
+
+**--no-peaks-in-stream**
+: Do not record peak search results in the stream. You won't be able to check
+: that the peak detection was any good, but the stream will be around 30%
+: smaller.
+
+**--no-refls-in-stream**
+: Do not record integrated reflections in the stream. The resulting output won't
+: be usable for merging, but will be a lot smaller. This option might be useful
+: if you're only interested in things like unit cell parameters and orientations.
+
+**--serial-offset=n**
+: Start the serial numbers in the stream at n instead of 1. Use this if you are
+: splitting an indexing job up into several smaller ones, so that the streams can
+: be concatenated into a single one with consistent numbering. This is important
+: if you use **whirligig**.
+
+**--harvest-file=fn**
+: Write a list of parameters to fn, in JSON format. This is intended to be used
+: for harvesting data into a database system. This option has no effect if
+: --serial-offset is set to a number larger than 1, to avoid the file being
+: overwritten multiple times in a batch system.
+
+
+HISTORICAL OPTIONS
+------------------
+
+**--no-sat-corr**
+: This option is here for historical purposes only, to disable a correction which
+: is done if certain extra information is included in the image data file.
+
+
+IDENTIFYING SINGLE PATTERNS IN THE INPUT FILE
+=============================================
+
+By default indexamajig processes all diffraction patterns ("events") in each of
+the data files listed in the input list. It is however, possible, to only
+process single events in a multi-event file, by adding in the list an event
+description string after the data filename. The event description always
+includes a first section with alphanumeric strings separated by forward slashes
+("/") and a second section with integer numbers also separated by forward
+slashes. The two sections are in turn separated by a double forward slash
+('//'). Any of the two sections can be empty, but the double forward slash
+separator must always be present. Indexamajig matches the strings and the
+numbers in the event description with the event placeholders ('%') present
+respectively in the 'data' and 'dim' properties defined in the geometry file,
+and tries to retrieve the full HDF path to the event data and the the its
+location in a multi-dimensional data space. Consider the following examples:
+
+### Example 1
+
+The 'data' and 'dim' properties have been defined like this in the geometry file:
+
+ data = /data/%/rawdata
+ dim0 = ss
+ dim1 = fs
+
+The event list contains the following line:
+
+ filename.h5 event1//
+
+This identifies an event in the 2-dimensional data block located at
+/data/event1/rawdata in the HDF5 file called filename.h5.
+
+### Example 2
+
+The 'data' and 'dim' properties have been defined like this in the geometry
+file:
+
+ data = /data/rawdata
+ dim0 = %
+ dim1 = ss
+ dim2 = fs
+
+The event list contains the following line:
+
+ filename.h5 //3
+
+This identifies an event in the 3-dimensional data block located at
+/data/rawdata in the HDF5 file called filename.h5, specifically the
+2-dimensional data slice defined by the value 3 of the first axis of the data
+space.
+
+Indexamajig tries to match the alphanumerical strings to the placeholders in
+the 'dim' property defined in the geometry file. The first string is matched to
+the first placeholder, the second to the second placeholder, and so on. A
+similar strategy is followed to match integer numbers to the placeholders in
+the 'dim' property defined in the geometry file. For a full explanation of how
+the internal layout of the data file can be described in the geometry file,
+please see **man crystfel_geometry**.
+
+You can use **list_events** to prepare a list of each event in one or more
+input files. Note that you only need to do this if you need to perform some
+sorting or filtering on this list. If you want to process every event in a
+file, simply specify the filename in the input file.
+
+
+AUTHOR
+=======
+
+This page was written by Thomas White, Yaroslav Gevorkov and Valerio Mariani.
+
+
+REPORTING BUGS
+==============
+
+Report bugs to <taw@physics.org>, or visit <http://www.desy.de/~twhite/crystfel>.
+
+
+COPYRIGHT AND DISCLAIMER
+========================
+
+Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY, a research centre
+of the Helmholtz Association.
+
+indexamajig, and this manual, are part of CrystFEL.
+
+CrystFEL is free software: you can redistribute it and/or modify it under the
+terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+CrystFEL is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+
+
+SEE ALSO
+========
+
+**crystfel**(7), **crystfel_geometry**(5), **cell_explorer**(1),
+**process_hkl**(1), **partialator**(1), **list_events**(1), **whirligig**(1)
diff --git a/libcrystfel/meson.build b/libcrystfel/meson.build
index 8897aaae..497abef6 100644
--- a/libcrystfel/meson.build
+++ b/libcrystfel/meson.build
@@ -138,6 +138,7 @@ libcrystfel_sources = ['src/image.c',
'src/detgeom.c',
'src/fom.c',
'src/profile.c',
+ 'src/crystfel-mille.c',
'src/image-cbf.c',
'src/image-hdf5.c',
'src/image-msgpack.c',
@@ -197,7 +198,8 @@ install_headers(['src/reflist.h',
'src/datatemplate.h',
'src/colscale.h',
'src/detgeom.h',
- 'src/fom.h'],
+ 'src/fom.h',
+ 'src/crystfel-mille.h'],
subdir: 'crystfel')
# API documentation (Doxygen)
diff --git a/libcrystfel/src/crystfel-mille.c b/libcrystfel/src/crystfel-mille.c
new file mode 100644
index 00000000..6a80e323
--- /dev/null
+++ b/libcrystfel/src/crystfel-mille.c
@@ -0,0 +1,311 @@
+/*
+ * crystfel-mille.c
+ *
+ * Interface to Millepede geometry refinement
+ *
+ * Copyright © 2023 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2023 Thomas White <taw@physics.org>
+ *
+ * This file is part of CrystFEL.
+ *
+ * CrystFEL is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CrystFEL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <libcrystfel-config.h>
+
+#include <stdlib.h>
+#include <assert.h>
+
+#include "image.h"
+#include "geometry.h"
+#include "cell-utils.h"
+#include "predict-refine.h"
+#include "profile.h"
+
+int mille_label(int group_serial, enum gparam param)
+{
+ switch ( param ) {
+ case GPARAM_DET_TX : return group_serial+1; /* x-shift */
+ case GPARAM_DET_TY : return group_serial+2; /* y-shift */
+ case GPARAM_DET_TZ : return group_serial+3; /* z-shift */
+ case GPARAM_DET_RX : return group_serial+4; /* Rotation around x */
+ case GPARAM_DET_RY : return group_serial+5; /* Rotation around y */
+ case GPARAM_DET_RZ : return group_serial+6; /* Rotation around z */
+ default : abort();
+ }
+}
+
+
+/* Opposite of mille_label(), for decoding labels later */
+enum gparam mille_unlabel(int n)
+{
+ switch ( n ) {
+ case 1 : return GPARAM_DET_TX;
+ case 2 : return GPARAM_DET_TY;
+ case 3 : return GPARAM_DET_TZ;
+ case 4 : return GPARAM_DET_RX;
+ case 5 : return GPARAM_DET_RY;
+ case 6 : return GPARAM_DET_RZ;
+ default : abort();
+ }
+}
+
+
+struct mille
+{
+ float *float_arr;
+ int *int_arr;
+ int max_entries;
+ int n;
+ FILE *fh;
+};
+
+typedef struct mille Mille;
+
+
+static void mille_add_measurement(Mille *m,
+ int NLC, float *derLc,
+ int NGL, float *derGl, int *labels,
+ float rMeas, float sigma)
+{
+ int space_required;
+ int i;
+
+ if ( m == NULL ) return;
+
+ /* Allocate extra space if necessary */
+ space_required = m->n + NLC + NGL + 2;
+ if ( space_required > m->max_entries ) {
+
+ float *new_float_arr;
+ int *new_int_arr;
+ int new_max_entries;
+
+ if ( m->max_entries == 0 ) {
+ new_max_entries = 256;
+ } else {
+ new_max_entries = m->max_entries;
+ }
+
+ while ( new_max_entries < space_required ) {
+ new_max_entries *= 2;
+ }
+
+ new_float_arr = realloc(m->float_arr, new_max_entries*sizeof(float));
+ new_int_arr = realloc(m->int_arr, new_max_entries*sizeof(int));
+ if ( (new_float_arr == NULL) || (new_int_arr == NULL) ) return;
+
+ m->float_arr = new_float_arr;
+ m->int_arr = new_int_arr;
+ m->max_entries = new_max_entries;
+ }
+
+ /* The measurement */
+ m->float_arr[m->n] = rMeas;
+ m->int_arr[m->n] = 0;
+ m->n++;
+
+ /* Local gradients */
+ for ( i=0; i<NLC; i++ ) {
+ if ( derLc[i] != 0.0 ) {
+ m->float_arr[m->n] = derLc[i];
+ m->int_arr[m->n] = i+1;
+ m->n++;
+ }
+ }
+
+ /* The measurement error */
+ m->float_arr[m->n] = sigma;
+ m->int_arr[m->n] = 0;
+ m->n++;
+
+ /* Global gradients */
+ for ( i=0; i<NGL; i++ ) {
+ if ( (derGl[i] != 0.0) && (labels[i] > 0) ) {
+ m->float_arr[m->n] = derGl[i];
+ m->int_arr[m->n] = labels[i];
+ m->n++;
+ }
+ }
+}
+
+
+void write_mille(Mille *mille, int n, UnitCell *cell,
+ struct reflpeak *rps, struct image *image,
+ gsl_matrix **Minvs)
+{
+ int i;
+
+ /* No groups -> no refinement */
+ if ( image->detgeom->top_group == NULL ) return;
+
+ /* Local parameters */
+ const enum gparam rvl[] =
+ {
+ GPARAM_ASX,
+ GPARAM_ASY,
+ GPARAM_ASZ,
+ GPARAM_BSX,
+ GPARAM_BSY,
+ GPARAM_BSZ,
+ GPARAM_CSX,
+ GPARAM_CSY,
+ GPARAM_CSZ,
+ };
+ const int nl = 9;
+
+ /* Global parameters */
+ const enum gparam rvg[] =
+ {
+ GPARAM_DET_TX,
+ GPARAM_DET_TY,
+ GPARAM_DET_TZ,
+ GPARAM_DET_RX,
+ GPARAM_DET_RY,
+ GPARAM_DET_RZ,
+ };
+ const int ng = 6;
+ const int max_hierarchy_levels = 8;
+
+ for ( i=0; i<n; i++ ) {
+
+ float local_gradients_fs[nl];
+ float local_gradients_ss[nl];
+ float local_gradients_r[nl];
+ float global_gradients_fs[ng*max_hierarchy_levels];
+ float global_gradients_ss[ng*max_hierarchy_levels];
+ int labels[ng*max_hierarchy_levels];
+ int j, levels;
+ const struct detgeom_panel_group *group;
+
+ /* Local gradients */
+ for ( j=0; j<nl; j++ ) {
+ fs_ss_gradient(rvl[j], rps[i].refl, cell,
+ &image->detgeom->panels[rps[i].peak->pn],
+ Minvs[rps[i].peak->pn], 0, 0, 0,
+ &local_gradients_fs[j],
+ &local_gradients_ss[j]);
+ local_gradients_r[j] = EXC_WEIGHT * r_gradient(rvl[j], rps[i].refl,
+ cell, image->lambda);
+ }
+
+ /* Global gradients for each hierarchy level, starting at the
+ * individual panel and working up to the top level */
+ j = 0;
+ levels = 0;
+ group = image->detgeom->panels[rps[i].peak->pn].group;
+ while ( group != NULL ) {
+
+ double cx, cy, cz;
+ int g;
+
+ detgeom_group_center(group, &cx, &cy, &cz);
+
+ for ( g=0; g<ng; g++ ) {
+ fs_ss_gradient(rvg[g], rps[i].refl, cell,
+ &image->detgeom->panels[rps[i].peak->pn],
+ Minvs[rps[i].peak->pn], 0, 0, 0,
+ &global_gradients_fs[j],
+ &global_gradients_ss[j]);
+ labels[j] = mille_label(group->serial, rvg[g]);
+ j++;
+ }
+
+ levels++;
+ group = group->parent;
+
+ if ( levels >= max_hierarchy_levels ) {
+ ERROR("Too many nested hierarchy levels for refinement.\n");
+ break;
+ }
+ }
+
+ /* Add fs measurement */
+ mille_add_measurement(mille,
+ nl, local_gradients_fs,
+ j, global_gradients_fs, labels,
+ fs_dev(&rps[i], image->detgeom), 0.22);
+
+ /* Add ss measurement */
+ mille_add_measurement(mille,
+ nl, local_gradients_ss,
+ j, global_gradients_ss, labels,
+ ss_dev(&rps[i], image->detgeom), 0.22);
+
+ /* Add excitation error "measurement" (local-only) */
+ mille_add_measurement(mille, nl, local_gradients_r,
+ 0, NULL, NULL, r_dev(&rps[i])*EXC_WEIGHT, 1.0);
+ }
+}
+
+
+Mille *crystfel_mille_new(const char *outFileName)
+{
+ Mille *m;
+
+ m = malloc(sizeof(Mille));
+ if ( m == NULL ) return NULL;
+
+ m->max_entries = 0;
+ m->n = 0;
+ m->float_arr = NULL;
+ m->int_arr = NULL;
+
+ m->fh = fopen(outFileName, "wb");
+ if ( m->fh == NULL ) {
+ ERROR("Failed to open Mille file '%s'\n", outFileName);
+ free(m);
+ return NULL;
+ }
+
+
+ return m;
+}
+
+
+void crystfel_mille_free(Mille *m)
+{
+ if ( m == NULL ) return;
+ fclose(m->fh);
+ free(m->float_arr);
+ free(m->int_arr);
+ free(m);
+}
+
+
+void crystfel_mille_delete_last_record(Mille *m)
+{
+ m->n = 0;
+}
+
+
+void crystfel_mille_write_record(Mille *m)
+{
+ float nf = 0.0;
+ int ni = 0;
+ int nw = (m->n * 2)+2;
+
+ fwrite(&nw, sizeof(int), 1, m->fh);
+
+ fwrite(&nf, sizeof(float), 1, m->fh);
+ fwrite(m->float_arr, sizeof(float), m->n, m->fh);
+
+ fwrite(&ni, sizeof(int), 1, m->fh);
+ fwrite(m->int_arr, sizeof(int), m->n, m->fh);
+ m->n = 0;
+}
diff --git a/libcrystfel/src/crystfel-mille.h b/libcrystfel/src/crystfel-mille.h
new file mode 100644
index 00000000..a4b83815
--- /dev/null
+++ b/libcrystfel/src/crystfel-mille.h
@@ -0,0 +1,60 @@
+/*
+ * crystfel-mille.h
+ *
+ * Interface to Millepede geometry refinement
+ *
+ * Copyright © 2023 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2023 Thomas White <taw@physics.org>
+ *
+ * This file is part of CrystFEL.
+ *
+ * CrystFEL is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CrystFEL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef CRYSTFEL_MILLE_H
+#define CRYSTFEL_MILLE_H
+
+#include <gsl/gsl_matrix.h>
+
+typedef struct mille Mille;
+
+#include "cell.h"
+#include "image.h"
+#include "predict-refine.h"
+
+/**
+ * \file crystfel-mille.h
+ * Detector geometry refinement using Millepede
+ */
+
+extern Mille *crystfel_mille_new(const char *outFileName);
+
+extern void crystfel_mille_free(Mille *m);
+
+extern int mille_label(int group_serial, enum gparam param);
+extern enum gparam mille_unlabel(int n);
+
+extern void write_mille(Mille *mille, int n, UnitCell *cell,
+ struct reflpeak *rps, struct image *image,
+ gsl_matrix **Minvs);
+
+extern void crystfel_mille_delete_last_record(Mille *m);
+
+extern void crystfel_mille_write_record(Mille *m);
+
+#endif /* CRYSTFEL_MILLE_H */
diff --git a/libcrystfel/src/datatemplate.c b/libcrystfel/src/datatemplate.c
index e97ae3df..36c0a422 100644
--- a/libcrystfel/src/datatemplate.c
+++ b/libcrystfel/src/datatemplate.c
@@ -45,16 +45,94 @@
* \file datatemplate.h
*/
-struct rg_definition {
- char *name;
- char *pns;
-};
+static struct panel_group_template *find_group(const DataTemplate *dt, const char *name)
+{
+ int i;
+
+ for ( i=0; i<dt->n_groups; i++ ) {
+ if ( strcmp(dt->groups[i]->name, name) == 0 ) {
+ return dt->groups[i];
+ }
+ }
+
+ return NULL;
+}
+
+
+static struct panel_group_template *add_group(const char *name, DataTemplate *dt)
+{
+ struct panel_group_template *gt;
+
+ if ( find_group(dt, name) != NULL ) {
+ ERROR("Duplicate panel group '%s'\n", name);
+ return NULL;
+ }
+
+ if ( dt->n_groups >= MAX_PANEL_GROUPS ) {
+ ERROR("Too many panel groups\n");
+ return NULL;
+ }
+
+ gt = malloc(sizeof(struct panel_group_template));
+ if ( gt == NULL ) return NULL;
+
+ gt->name = strdup(name);
+ gt->n_children = 0;
+
+ if ( gt->name == NULL ) {
+ free(gt);
+ return NULL;
+ }
+
+ dt->groups[dt->n_groups++] = gt;
+
+ return gt;
+}
+
+
+static int parse_group(const char *name, DataTemplate *dt, const char *val)
+{
+ struct panel_group_template *gt;
+ int n_members;
+ char **members;
+ int i;
+ int fail = 0;
+
+ gt = add_group(name, dt);
+ if ( gt == NULL ) {
+ ERROR("Failed to add group\n");
+ return 1;
+ }
+
+ n_members = assplode(val, ",", &members, ASSPLODE_NONE);
+ if ( n_members == 0 ) {
+ ERROR("Panel group '%s' has no members\n", name);
+ fail = 1;
+ }
+
+ if ( n_members > MAX_PANEL_GROUP_CHILDREN ) {
+ ERROR("Panel group '%s' has too many members\n", name);
+ fail = 1;
+ } else {
+
+ for ( i=0; i<n_members; i++ ) {
+ gt->children[i] = find_group(dt, members[i]);
+ if ( gt->children[i] == NULL ) {
+ ERROR("Unknown panel group '%s'\n", members[i]);
+ fail = 1;
+ }
+ }
+
+ gt->n_children = n_members;
+
+ }
+ for ( i=0; i<n_members; i++ ) free(members[i]);
+ free(members);
+
+ return fail;
+}
-struct rgc_definition {
- char *name;
- char *rgs;
-};
static struct panel_template *new_panel(DataTemplate *det,
@@ -75,7 +153,6 @@ static struct panel_template *new_panel(DataTemplate *det,
new->name = strdup(name);
/* Copy strings */
- new->cnz_from = safe_strdup(defaults->cnz_from);
new->data = safe_strdup(defaults->data);
new->satmap = safe_strdup(defaults->satmap);
new->satmap_file = safe_strdup(defaults->satmap_file);
@@ -84,6 +161,9 @@ static struct panel_template *new_panel(DataTemplate *det,
new->masks[i].filename = safe_strdup(defaults->masks[i].filename);
}
+ /* Create a new group just for this panel */
+ add_group(name, det);
+
return new;
}
@@ -143,150 +223,6 @@ static struct dt_badregion *find_bad_region_by_name(DataTemplate *det,
}
-static struct rigid_group *find_or_add_rg(DataTemplate *det,
- const char *name)
-{
- int i;
- struct rigid_group **new;
- struct rigid_group *rg;
-
- for ( i=0; i<det->n_rigid_groups; i++ ) {
-
- if ( strcmp(det->rigid_groups[i]->name, name) == 0 ) {
- return det->rigid_groups[i];
- }
-
- }
-
- new = realloc(det->rigid_groups,
- (1+det->n_rigid_groups)*sizeof(struct rigid_group *));
- if ( new == NULL ) return NULL;
-
- det->rigid_groups = new;
-
- rg = malloc(sizeof(struct rigid_group));
- if ( rg == NULL ) return NULL;
-
- det->rigid_groups[det->n_rigid_groups++] = rg;
-
- rg->name = strdup(name);
- rg->panel_numbers = NULL;
- rg->n_panels = 0;
-
- return rg;
-}
-
-
-static struct rg_collection *find_or_add_rg_coll(DataTemplate *det,
- const char *name)
-{
- int i;
- struct rg_collection **new;
- struct rg_collection *rgc;
-
- for ( i=0; i<det->n_rg_collections; i++ ) {
- if ( strcmp(det->rigid_group_collections[i]->name, name) == 0 )
- {
- return det->rigid_group_collections[i];
- }
- }
-
- new = realloc(det->rigid_group_collections,
- (1+det->n_rg_collections)*sizeof(struct rg_collection *));
- if ( new == NULL ) return NULL;
-
- det->rigid_group_collections = new;
-
- rgc = malloc(sizeof(struct rg_collection));
- if ( rgc == NULL ) return NULL;
-
- det->rigid_group_collections[det->n_rg_collections++] = rgc;
-
- rgc->name = strdup(name);
- rgc->rigid_groups = NULL;
- rgc->n_rigid_groups = 0;
-
- return rgc;
-}
-
-
-static void add_to_rigid_group(struct rigid_group *rg, int panel_number)
-{
- int *pn;
-
- pn = realloc(rg->panel_numbers, (1+rg->n_panels)*sizeof(int));
- if ( pn == NULL ) {
- ERROR("Couldn't add panel to rigid group.\n");
- return;
- }
-
- rg->panel_numbers = pn;
- rg->panel_numbers[rg->n_panels++] = panel_number;
-}
-
-
-static void add_to_rigid_group_coll(struct rg_collection *rgc,
- struct rigid_group *rg)
-{
- struct rigid_group **r;
-
- r = realloc(rgc->rigid_groups, (1+rgc->n_rigid_groups)*
- sizeof(struct rigid_group *));
- if ( r == NULL ) {
- ERROR("Couldn't add rigid group to collection.\n");
- return;
- }
-
- rgc->rigid_groups = r;
- rgc->rigid_groups[rgc->n_rigid_groups++] = rg;
-}
-
-
-/* Free all rigid groups in detector */
-static void free_all_rigid_groups(DataTemplate *det)
-{
- int i;
-
- if ( det->rigid_groups == NULL ) return;
- for ( i=0; i<det->n_rigid_groups; i++ ) {
- free(det->rigid_groups[i]->name);
- free(det->rigid_groups[i]->panel_numbers);
- free(det->rigid_groups[i]);
- }
- free(det->rigid_groups);
-}
-
-
-/* Free all rigid groups in detector */
-static void free_all_rigid_group_collections(DataTemplate *det)
-{
- int i;
-
- if ( det->rigid_group_collections == NULL ) return;
- for ( i=0; i<det->n_rg_collections; i++ ) {
- free(det->rigid_group_collections[i]->name);
- free(det->rigid_group_collections[i]->rigid_groups);
- free(det->rigid_group_collections[i]);
- }
- free(det->rigid_group_collections);
-}
-
-
-static struct rigid_group *find_rigid_group_by_name(DataTemplate *det,
- char *name)
-{
- int i;
-
- for ( i=0; i<det->n_rigid_groups; i++ ) {
- if ( strcmp(det->rigid_groups[i]->name, name) == 0 ) {
- return det->rigid_groups[i];
- }
- }
-
- return NULL;
-}
-
-
static int atob(const char *a)
{
if ( strcasecmp(a, "true") == 0 ) return 1;
@@ -490,7 +426,8 @@ static int add_flag_value(struct panel_template *p,
static int parse_mask(struct panel_template *panel,
const char *key_orig,
- const char *val)
+ const char *val,
+ int def)
{
int n;
char *key;
@@ -551,13 +488,15 @@ static int parse_mask(struct panel_template *panel,
return 1;
}
+ panel->masks[n].mask_default = def;
free(key);
return 0;
}
static int parse_field_for_panel(struct panel_template *panel, const char *key,
- const char *val, DataTemplate *det)
+ const char *val, DataTemplate *det,
+ int def)
{
int reject = 0;
@@ -576,16 +515,19 @@ static int parse_field_for_panel(struct panel_template *panel, const char *key,
} else if ( strcmp(key, "adu_per_eV") == 0 ) {
panel->adu_scale = atof(val);
panel->adu_scale_unit = ADU_PER_EV;
+ panel->adu_scale_default = def;
} else if ( strcmp(key, "adu_per_photon") == 0 ) {
panel->adu_scale = atof(val);
panel->adu_scale_unit = ADU_PER_PHOTON;
+ panel->adu_scale_default = def;
} else if ( strcmp(key, "clen") == 0 ) {
- /* Gets expanded when image is loaded */
- panel->cnz_from = strdup(val);
+ ERROR("'clen' is a top-level property in this version of CrystFEL.\n");
+ reject = 1;
} else if ( strcmp(key, "data") == 0 ) {
free(panel->data);
panel->data = strdup(val);
+ panel->data_default = def;
} else if ( strcmp(key, "mask_edge_pixels") == 0 ) {
if ( convert_int(val, &panel->mask_edge_pixels) ) {
@@ -593,30 +535,36 @@ static int parse_field_for_panel(struct panel_template *panel, const char *key,
panel->name, val);
reject = 1;
}
+ panel->mask_edge_pixels_default = def;
} else if ( strcmp(key, "mask_bad") == 0 ) {
- parse_field_for_panel(panel, "mask0_badbits", val, det);
+ parse_field_for_panel(panel, "mask0_badbits", val, det, def);
} else if ( strcmp(key, "mask_good") == 0 ) {
- parse_field_for_panel(panel, "mask0_goodbits", val, det);
+ parse_field_for_panel(panel, "mask0_goodbits", val, det, def);
} else if ( strcmp(key, "mask") == 0 ) {
- parse_field_for_panel(panel, "mask0_data", val, det);
+ parse_field_for_panel(panel, "mask0_data", val, det, def);
} else if ( strcmp(key, "mask_file") == 0 ) {
- parse_field_for_panel(panel, "mask0_file", val, det);
+ parse_field_for_panel(panel, "mask0_file", val, det, def);
} else if ( strncmp(key, "mask", 4) == 0 ) {
- reject = parse_mask(panel, key, val);
+ reject = parse_mask(panel, key, val, def);
} else if ( strcmp(key, "saturation_map") == 0 ) {
panel->satmap = strdup(val);
+ panel->satmap_default = def;
} else if ( strcmp(key, "saturation_map_file") == 0 ) {
panel->satmap_file = strdup(val);
+ panel->satmap_file_default = def;
} else if ( strcmp(key, "coffset") == 0) {
panel->cnz_offset = atof(val);
+ panel->cnz_offset_default = def;
} else if ( strcmp(key, "res") == 0 ) {
panel->pixel_pitch = 1.0/atof(val);
+ panel->pixel_pitch_default = def;
} else if ( strcmp(key, "max_adu") == 0 ) {
panel->max_adu = atof(val);
+ panel->max_adu_default = def;
ERROR("WARNING: It's usually better not to set max_adu "
"in the geometry file. Use --max-adu during "
"merging instead.\n");
@@ -625,14 +573,17 @@ static int parse_field_for_panel(struct panel_template *panel, const char *key,
if ( add_flag_value(panel, atof(val), FLAG_EQUAL) ) {
reject = -1;
}
+ panel->flag_values_default = def;
} else if ( strcmp(key, "flag_lessthan") == 0 ) {
if ( add_flag_value(panel, atof(val), FLAG_LESSTHAN) ) {
reject = -1;
}
+ panel->flag_values_default = def;
} else if ( strcmp(key, "flag_morethan") == 0 ) {
if ( add_flag_value(panel, atof(val), FLAG_MORETHAN) ) {
reject = -1;
}
+ panel->flag_values_default = def;
} else if ( strcmp(key, "badrow_direction") == 0 ) {
ERROR("WARNING 'badrow_direction' is ignored in this version.\n");
@@ -851,10 +802,6 @@ static int parse_peak_layout(const char *val,
static int parse_toplevel(DataTemplate *dt,
const char *key,
const char *val,
- struct rg_definition ***rg_defl,
- struct rgc_definition ***rgc_defl,
- int *n_rg_defs,
- int *n_rgc_defs,
struct panel_template *defaults,
int *defaults_updated)
{
@@ -864,6 +811,9 @@ static int parse_toplevel(DataTemplate *dt,
} else if ( strcmp(key, "detector_shift_y") == 0 ) {
dt->shift_y_from = strdup(val);
+ } else if ( strcmp(key, "clen") == 0 ) {
+ dt->cnz_from = strdup(val);
+
} else if ( strcmp(key, "photon_energy") == 0 ) {
return parse_photon_energy(val,
&dt->wavelength_from,
@@ -895,37 +845,22 @@ static int parse_toplevel(DataTemplate *dt,
ERROR("Invalid value for bandwidth\n");
}
- } else if (strncmp(key, "rigid_group", 11) == 0
- && strncmp(key, "rigid_group_collection", 22) != 0 ) {
-
- struct rg_definition **new;
-
- new = realloc(*rg_defl,
- ((*n_rg_defs)+1)*sizeof(struct rg_definition*));
- *rg_defl = new;
+ } else if ( strncmp(key, "rigid_group", 11) == 0 ) {
- (*rg_defl)[*n_rg_defs] = malloc(sizeof(struct rg_definition));
- (*rg_defl)[*n_rg_defs]->name = strdup(key+12);
- (*rg_defl)[*n_rg_defs]->pns = strdup(val);
- *n_rg_defs = *n_rg_defs+1;
+ /* Rigid group lines are ignored in this version */
- } else if ( strncmp(key, "rigid_group_collection", 22) == 0 ) {
+ } else if ( strncmp(key, "group_", 6) == 0 ) {
- struct rgc_definition **new;
-
- new = realloc(*rgc_defl, ((*n_rgc_defs)+1)*
- sizeof(struct rgc_definition*));
- *rgc_defl = new;
-
- (*rgc_defl)[*n_rgc_defs] =
- malloc(sizeof(struct rgc_definition));
- (*rgc_defl)[*n_rgc_defs]->name = strdup(key+23);
- (*rgc_defl)[*n_rgc_defs]->rgs = strdup(val);
- *n_rgc_defs = *n_rgc_defs+1;
+ if ( parse_group(key+6, dt, val) ) {
+ return 1;
+ }
} else {
- if ( parse_field_for_panel(defaults, key, val, dt) == 0 ) {
+ /* If there are any panels, the value in 'defaults' gets marked
+ * as "not default". This will cause it to be written out for
+ * each subsequent panel. */
+ if ( parse_field_for_panel(defaults, key, val, dt, (dt->n_panels==0)) == 0 ) {
*defaults_updated = 1;
} else {
return 1;
@@ -1038,18 +973,38 @@ static int try_guess_panel(struct dt_badregion *bad, DataTemplate *dt)
}
+static void show_group(const struct panel_group_template *gt, int level)
+{
+ int i;
+
+ for ( i=0; i<level; i++ ) STATUS(" ");
+
+ if ( gt == NULL ) {
+ STATUS("!!!\n");
+ return;
+ }
+
+ STATUS("%s\n", gt->name);
+
+ for ( i=0; i<gt->n_children; i++ ) {
+ show_group(gt->children[i], level+1);
+ }
+}
+
+
+void data_template_show_hierarchy(const DataTemplate *dtempl)
+{
+ STATUS("Hierarchy:\n");
+ show_group(find_group(dtempl, "all"), 0);
+}
+
+
DataTemplate *data_template_new_from_string(const char *string_in)
{
DataTemplate *dt;
- char **bits;
int done = 0;
int i;
- int rgi, rgci;
int reject = 0;
- struct rg_definition **rg_defl = NULL;
- struct rgc_definition **rgc_defl = NULL;
- int n_rg_definitions = 0;
- int n_rgc_definitions = 0;
char *string;
char *string_orig;
size_t len;
@@ -1063,15 +1018,13 @@ DataTemplate *data_template_new_from_string(const char *string_in)
dt->panels = NULL;
dt->n_bad = 0;
dt->bad = NULL;
- dt->n_rigid_groups = 0;
- dt->rigid_groups = NULL;
- dt->n_rg_collections = 0;
- dt->rigid_group_collections = NULL;
dt->bandwidth = 0.00000001;
dt->peak_list = NULL;
dt->shift_x_from = NULL;
dt->shift_y_from = NULL;
+ dt->cnz_from = NULL;
dt->n_headers_to_copy = 0;
+ dt->n_groups = 0;
/* The default defaults... */
defaults.orig_min_fs = -1;
@@ -1080,11 +1033,13 @@ DataTemplate *data_template_new_from_string(const char *string_in)
defaults.orig_max_ss = -1;
defaults.cnx = NAN;
defaults.cny = NAN;
- defaults.cnz_from = NULL;
defaults.cnz_offset = 0.0;
+ defaults.cnz_offset_default = 1;
defaults.pixel_pitch = -1.0;
+ defaults.pixel_pitch_default = 1;
defaults.bad = 0;
defaults.mask_edge_pixels = 0;
+ defaults.mask_edge_pixels_default = 1;
defaults.fsx = NAN;
defaults.fsy = NAN;
defaults.fsz = NAN;
@@ -1093,22 +1048,30 @@ DataTemplate *data_template_new_from_string(const char *string_in)
defaults.ssz = NAN;
defaults.adu_scale = NAN;
defaults.adu_scale_unit = ADU_PER_PHOTON;
+ defaults.adu_scale_default = 1;
for ( i=0; i<MAX_FLAG_VALUES; i++ ) defaults.flag_values[i] = 0;
for ( i=0; i<MAX_FLAG_VALUES; i++ ) defaults.flag_types[i] = FLAG_NOTHING;
+ defaults.flag_values_default = 1;
for ( i=0; i<MAX_MASKS; i++ ) {
defaults.masks[i].data_location = NULL;
defaults.masks[i].filename = NULL;
defaults.masks[i].good_bits = 0;
defaults.masks[i].bad_bits = 0;
+ defaults.masks[i].mask_default = 1;
}
defaults.max_adu = +INFINITY;
+ defaults.max_adu_default = 1;
defaults.satmap = NULL;
+ defaults.satmap_default = 1;
defaults.satmap_file = NULL;
+ defaults.satmap_file_default = 1;
defaults.data = strdup("/data/data");
+ defaults.data_default = 1;
defaults.name = NULL;
defaults.dims[0] = DIM_SS;
defaults.dims[1] = DIM_FS;
for ( i=2; i<MAX_DIMS; i++ ) defaults.dims[i] = DIM_UNDEFINED;
+ for ( i=0; i<MAX_DIMS; i++ ) defaults.dims_default[i] = 1;
string = strdup(string_in);
if ( string == NULL ) return NULL;
@@ -1184,10 +1147,6 @@ DataTemplate *data_template_new_from_string(const char *string_in)
/* Top-level option */
if ( parse_toplevel(dt, line, val,
- &rg_defl,
- &rgc_defl,
- &n_rg_definitions,
- &n_rgc_definitions,
&defaults,
&have_unused_defaults) )
{
@@ -1218,11 +1177,9 @@ DataTemplate *data_template_new_from_string(const char *string_in)
}
if ( panel != NULL ) {
- if ( parse_field_for_panel(panel, key, val,
- dt) ) reject = 1;
+ if ( parse_field_for_panel(panel, key, val, dt, 0) ) reject = 1;
} else {
- if ( parse_field_bad(badregion, key,
- val) ) reject = 1;
+ if ( parse_field_bad(badregion, key, val) ) reject = 1;
}
free(line);
@@ -1254,6 +1211,11 @@ DataTemplate *data_template_new_from_string(const char *string_in)
reject = 1;
}
+ if ( dt->cnz_from == NULL ) {
+ ERROR("Geometry file must specify the camera length\n");
+ reject = 1;
+ }
+
for ( i=0; i<dt->n_panels; i++ ) {
int j;
@@ -1316,11 +1278,6 @@ DataTemplate *data_template_new_from_string(const char *string_in)
" panel %s\n", dt->panels[i].name);
reject = 1;
}
- if ( p->cnz_from == NULL ) {
- ERROR("Please specify the camera length for panel %s\n",
- dt->panels[i].name);
- reject = 1;
- }
if ( p->pixel_pitch < 0 ) {
ERROR("Please specify the pixel size for"
" panel %s\n", dt->panels[i].name);
@@ -1420,73 +1377,17 @@ DataTemplate *data_template_new_from_string(const char *string_in)
}
}
- free(defaults.cnz_from);
free(defaults.data);
for ( i=0; i<MAX_MASKS; i++ ) {
free(defaults.masks[i].data_location);
free(defaults.masks[i].filename);
}
- for ( rgi=0; rgi<n_rg_definitions; rgi++) {
-
- int pi, n1;
- struct rigid_group *rigidgroup = NULL;
-
- rigidgroup = find_or_add_rg(dt, rg_defl[rgi]->name);
-
- n1 = assplode(rg_defl[rgi]->pns, ",", &bits, ASSPLODE_NONE);
-
- for ( pi=0; pi<n1; pi++ ) {
-
- int panel_number;
- if ( data_template_panel_name_to_number(dt,
- bits[pi],
- &panel_number) )
- {
- ERROR("Cannot add panel to rigid group\n");
- ERROR("Panel not found: %s\n", bits[pi]);
- return NULL;
- }
- add_to_rigid_group(rigidgroup, panel_number);
- free(bits[pi]);
-
- }
- free(bits);
- free(rg_defl[rgi]->name);
- free(rg_defl[rgi]->pns);
- free(rg_defl[rgi]);
- }
- free(rg_defl);
-
- for ( rgci=0; rgci<n_rgc_definitions; rgci++ ) {
-
- int n2;
- struct rg_collection *rgcollection = NULL;
-
- rgcollection = find_or_add_rg_coll(dt, rgc_defl[rgci]->name);
-
- n2 = assplode(rgc_defl[rgci]->rgs, ",", &bits, ASSPLODE_NONE);
-
- for ( rgi=0; rgi<n2; rgi++ ) {
-
- struct rigid_group *r;
-
- r = find_rigid_group_by_name(dt, bits[rgi]);
- if ( r == NULL ) {
- ERROR("Cannot add rigid group to collection\n");
- ERROR("Rigid group not found: %s\n", bits[rgi]);
- return NULL;
- }
- add_to_rigid_group_coll(rgcollection, r);
- free(bits[rgi]);
- }
- free(bits);
- free(rgc_defl[rgci]->name);
- free(rgc_defl[rgci]->rgs);
- free(rgc_defl[rgci]);
-
+ /* If this is a single-panel detector, there should only be one group
+ * called "all" which points to the panel */
+ if ( (dt->n_panels == 1) && (dt->n_groups == 1) ) {
+ parse_group("all", dt, dt->groups[0]->name);
}
- free(rgc_defl);
free(string_orig);
@@ -1519,9 +1420,6 @@ void data_template_free(DataTemplate *dt)
if ( dt == NULL ) return;
- free_all_rigid_groups(dt);
- free_all_rigid_group_collections(dt);
-
for ( i=0; i<dt->n_panels; i++ ) {
int j;
@@ -1530,7 +1428,6 @@ void data_template_free(DataTemplate *dt)
free(dt->panels[i].data);
free(dt->panels[i].satmap);
free(dt->panels[i].satmap_file);
- free(dt->panels[i].cnz_from);
for ( j=0; j<MAX_MASKS; j++ ) {
free(dt->panels[i].masks[j].filename);
@@ -1542,8 +1439,14 @@ void data_template_free(DataTemplate *dt)
free(dt->headers_to_copy[i]);
}
+ for ( i=0; i<dt->n_groups; i++ ) {
+ free(dt->groups[i]->name);
+ free(dt->groups[i]);
+ }
+
free(dt->wavelength_from);
free(dt->peak_list);
+ free(dt->cnz_from);
free(dt->panels);
free(dt->bad);
@@ -1872,87 +1775,23 @@ static int im_get_length(struct image *image, const char *from,
}
-static int safe_strcmp(const char *a, const char *b)
-{
- if ( (a==NULL) && (b==NULL) ) return 0;
- if ( (a!=NULL) && (b!=NULL) ) return strcmp(a, b);
- return 1;
-}
-
-
-static int all_panels_reference_same_clen(const DataTemplate *dtempl)
-{
- int i;
- char *first_val = NULL;
- char *first_units = NULL;
- int fail = 0;
-
- for ( i=0; i<dtempl->n_panels; i++ ) {
- struct panel_template *p = &dtempl->panels[i];
- char *val;
- char *units;
- if ( separate_value_and_units(p->cnz_from, &val, &units) ) {
- /* Parse error */
- return 0;
- }
- if ( i == 0 ) {
- first_val = val;
- first_units = units;
- } else {
- if ( safe_strcmp(val, first_val) != 0 ) fail = 1;
- if ( safe_strcmp(units, first_units) != 0 ) fail = 1;
- free(val);
- free(units);
- }
- }
-
- free(first_val);
- free(first_units);
- return fail;
-}
-
-
-static int all_coffsets_small(const DataTemplate *dtempl)
+static int all_panels_same_coffset(const DataTemplate *dtempl)
{
int i;
-
- for ( i=0; i<dtempl->n_panels; i++ ) {
- struct panel_template *p = &dtempl->panels[i];
- if ( p->cnz_offset > 10.0*p->pixel_pitch ) return 0;
- }
-
- return 1;
-}
-
-
-static int all_panels_same_clen(const DataTemplate *dtempl)
-{
- int i;
- double *zvals;
- double total = 0.0;
+ double total;
double mean;
- zvals = malloc(sizeof(double)*dtempl->n_panels);
- if ( zvals == NULL ) return 0;
-
+ total = 0.0;
for ( i=0; i<dtempl->n_panels; i++ ) {
- struct panel_template *p = &dtempl->panels[i];
- if ( im_get_length(NULL, p->cnz_from, 1e-3, &zvals[i]) ) {
- /* Can't get length because it used a header reference */
- free(zvals);
- return 0;
- }
- total += zvals[i];
+ total += dtempl->panels[i].cnz_offset;
}
-
mean = total/dtempl->n_panels;
+
for ( i=0; i<dtempl->n_panels; i++ ) {
struct panel_template *p = &dtempl->panels[i];
- if ( fabs(zvals[i] - mean) > 10.0*p->pixel_pitch ) return 0;
+ if ( fabs(dtempl->panels[i].cnz_offset - mean) > 10.0*p->pixel_pitch ) return 0;
}
- free(zvals);
-
return 1;
}
@@ -1974,8 +1813,92 @@ static int all_panels_perpendicular_to_beam(const DataTemplate *dtempl)
static int detector_flat(const DataTemplate *dtempl)
{
return all_panels_perpendicular_to_beam(dtempl)
- && ( (all_panels_reference_same_clen(dtempl) && all_coffsets_small(dtempl))
- || all_panels_same_clen(dtempl) );
+ && all_panels_same_coffset(dtempl);
+}
+
+
+static void add_dg_point(const struct detgeom_panel *p,
+ int fs, int ss,
+ double *tx, double *ty, double *tz)
+{
+ *tx += (p->cnx + fs*p->fsx + ss*p->ssx) * p->pixel_pitch;
+ *ty += (p->cny + fs*p->fsy + ss*p->ssy) * p->pixel_pitch;
+ *tz += (p->cnz + fs*p->fsz + ss*p->ssz) * p->pixel_pitch;
+}
+
+
+static struct detgeom_panel_group *walk_group(const DataTemplate *dtempl,
+ struct panel_group_template *gt,
+ struct detgeom *detgeom,
+ int serial, int c_mul)
+{
+ struct detgeom_panel_group *gr;
+
+ if ( gt == NULL ) return NULL;
+
+ gr = malloc(sizeof(struct detgeom_panel_group));
+ if ( gr == NULL ) return NULL;
+
+ gr->name = strdup(gt->name);
+ gr->n_children = gt->n_children;
+
+ if ( gr->n_children == 0 ) {
+
+ /* Leaf node */
+ gr->children = NULL;
+ gr->panel = detgeom_find_panel(detgeom, gr->name);
+ if ( gr->panel == NULL ) {
+ ERROR("Couldn't find panel %s for leaf group\n", gr->name);
+ return NULL;
+ }
+ gr->panel->group = gr;
+
+ /* Calculate and make a note of the panel center */
+ double tx = 0.0;
+ double ty = 0.0;
+ double tz = 0.0;
+
+ add_dg_point(gr->panel, 0, 0, &tx, &ty, &tz);
+ add_dg_point(gr->panel, gr->panel->w, 0, &tx, &ty, &tz);
+ add_dg_point(gr->panel, 0, gr->panel->h, &tx, &ty, &tz);
+ add_dg_point(gr->panel, gr->panel->w, gr->panel->h, &tx, &ty, &tz);
+
+ gr->cx = tx / 4.0;
+ gr->cy = ty / 4.0;
+ gr->cz = tz / 4.0;
+
+ } else {
+
+ int i;
+ double tx = 0.0;
+ double ty = 0.0;
+ double tz = 0.0;
+
+ gr->panel = NULL;
+ gr->children = malloc(gt->n_children*sizeof(struct detgeom_panel_group *));
+ if ( gr->children == NULL ) {
+ free(gr);
+ return NULL;
+ }
+
+ for ( i=0; i<gt->n_children; i++ ) {
+ gr->children[i] = walk_group(dtempl, gt->children[i], detgeom,
+ serial + c_mul*(i+1), c_mul*100);
+ if ( gr->children[i] == NULL ) return NULL;
+ gr->children[i]->parent = gr;
+ tx += gr->children[i]->cx;
+ ty += gr->children[i]->cy;
+ tz += gr->children[i]->cz;
+ }
+
+ gr->cx = tx / gt->n_children;
+ gr->cy = ty / gt->n_children;
+ gr->cz = tz / gt->n_children;
+
+ }
+
+ gr->serial = serial;
+ return gr;
}
@@ -1985,6 +1908,7 @@ struct detgeom *create_detgeom(struct image *image,
{
struct detgeom *detgeom;
int i;
+ double clen;
if ( dtempl == NULL ) {
ERROR("NULL data template!\n");
@@ -1994,6 +1918,8 @@ struct detgeom *create_detgeom(struct image *image,
detgeom = malloc(sizeof(struct detgeom));
if ( detgeom == NULL ) return NULL;
+ detgeom->top_group = NULL;
+
detgeom->panels = malloc(dtempl->n_panels*sizeof(struct detgeom_panel));
if ( detgeom->panels == NULL ) {
free(detgeom);
@@ -2013,6 +1939,17 @@ struct detgeom *create_detgeom(struct image *image,
}
}
+ if ( im_get_length(image, dtempl->cnz_from, 1e-3, &clen) )
+ {
+ if ( two_d_only ) {
+ clen = NAN;
+ } else {
+ ERROR("Failed to read length from '%s'\n", dtempl->cnz_from);
+ return NULL;
+ }
+ }
+
+
for ( i=0; i<dtempl->n_panels; i++ ) {
struct detgeom_panel *p = &detgeom->panels[i];
@@ -2027,20 +1964,8 @@ struct detgeom *create_detgeom(struct image *image,
p->cnx = tmpl->cnx;
p->cny = tmpl->cny;
- if ( im_get_length(image, tmpl->cnz_from, 1e-3, &p->cnz) )
- {
- if ( two_d_only ) {
- p->cnz = NAN;
- } else {
- ERROR("Failed to read length from '%s'\n", tmpl->cnz_from);
- return NULL;
- }
- }
-
- /* Apply offset (in m) and then convert cnz from
- * m to pixels */
- p->cnz += tmpl->cnz_offset;
- p->cnz /= p->pixel_pitch;
+ /* Apply offset (in m) and then convert cnz from m to pixels */
+ p->cnz = (clen + tmpl->cnz_offset) / p->pixel_pitch;
/* Apply overall shift (already in m) */
if ( dtempl->shift_x_from != NULL ) {
@@ -2103,6 +2028,14 @@ struct detgeom *create_detgeom(struct image *image,
}
+ detgeom->top_group = walk_group(dtempl, find_group(dtempl, "all"), detgeom, 0, 100);
+ if ( detgeom->top_group != NULL ) {
+ detgeom->top_group->parent = NULL;
+ } else {
+ ERROR("Warning: Top-level panel group ('all') not found. "
+ "Geometry refinement will not be possible.\n");
+ }
+
return detgeom;
}
@@ -2145,3 +2078,598 @@ double data_template_get_clen_if_possible(const DataTemplate *dt)
detgeom_free(dg);
return clen;
}
+
+
+static int translate_group_contents(DataTemplate *dtempl,
+ const struct panel_group_template *group,
+ double x, double y, double z,
+ int is_metres)
+{
+ int i;
+
+ if ( group->n_children == 0 ) {
+
+ struct panel_template *p = find_panel_by_name(dtempl, group->name);
+ if ( p == NULL ) return 1;
+
+ if ( is_metres ) {
+ p->cnx += x/p->pixel_pitch;
+ p->cny += y/p->pixel_pitch;
+ p->cnz_offset += z;
+ } else {
+ p->cnx += x;
+ p->cny += y;
+ p->cnz_offset += z*p->pixel_pitch;
+ }
+
+ } else {
+ for ( i=0; i<group->n_children; i++ ) {
+ translate_group_contents(dtempl, group->children[i],
+ x, y, z, is_metres);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Alters dtempl by shifting the named panel group by x,y,z in the CrystFEL
+ * coordinate system. x,y,z are in pixels, and all panels in the group must
+ * have the same pixel size (but, this will not be checked).
+ *
+ * \returns zero for success, non-zero on error
+ */
+int data_template_translate_group_px(DataTemplate *dtempl, const char *group_name,
+ double x, double y, double z)
+{
+ const struct panel_group_template *group = find_group(dtempl, group_name);
+ if ( group == NULL ) return 1;
+ return translate_group_contents(dtempl, group, x, y, z, 0);
+}
+
+
+/**
+ * Alters dtempl by shifting the named panel group by x,y,z in the CrystFEL
+ * coordinate system. x,y,z are in metres.
+ *
+ * \returns zero for success, non-zero on error
+ */
+int data_template_translate_group_m(DataTemplate *dtempl, const char *group_name,
+ double x, double y, double z)
+{
+ const struct panel_group_template *group = find_group(dtempl, group_name);
+ if ( group == NULL ) return 1;
+ return translate_group_contents(dtempl, group, x, y, z, 1);
+}
+
+
+static void add_point(const struct panel_template *p,
+ int fs, int ss,
+ double *tx, double *ty, double *tz)
+{
+ *tx += (p->cnx + fs*p->fsx + ss*p->ssx) * p->pixel_pitch;
+ *ty += (p->cny + fs*p->fsy + ss*p->ssy) * p->pixel_pitch;
+ *tz += p->cnz_offset + (fs*p->fsz + ss*p->ssz) * p->pixel_pitch;
+}
+
+
+static int group_center(DataTemplate *dtempl,
+ const struct panel_group_template *group,
+ double *cx, double *cy, double *cz)
+{
+ if ( group->n_children == 0 ) {
+
+ const struct panel_template *p = find_panel_by_name(dtempl, group->name);
+ if ( p == NULL ) return 1;
+
+ double tx = 0.0;
+ double ty = 0.0;
+ double tz = 0.0;
+
+ add_point(p, 0, 0, &tx, &ty, &tz);
+ add_point(p, PANEL_WIDTH(p), 0, &tx, &ty, &tz);
+ add_point(p, 0, PANEL_HEIGHT(p), &tx, &ty, &tz);
+ add_point(p, PANEL_WIDTH(p), PANEL_HEIGHT(p), &tx, &ty, &tz);
+
+ *cx = tx / 4.0;
+ *cy = ty / 4.0;
+ *cz = tz / 4.0;
+
+ return 0;
+
+ } else {
+
+ int i;
+ double tx = 0.0;
+ double ty = 0.0;
+ double tz = 0.0;
+
+ for ( i=0; i<group->n_children; i++ ) {
+ double gcx, gcy, gcz;
+ group_center(dtempl, group->children[i], &gcx, &gcy, &gcz);
+ tx += gcx;
+ ty += gcy;
+ tz += gcz;
+ }
+
+ *cx = tx / group->n_children;
+ *cy = ty / group->n_children;
+ *cz = tz / group->n_children;
+
+ return 0;
+
+ }
+}
+
+
+static int rotate_all_panels(DataTemplate *dtempl,
+ struct panel_group_template *group,
+ char axis, double ang,
+ double cx, double cy, double cz)
+{
+ if ( group->n_children == 0 ) {
+
+ double cnz_px;
+ struct panel_template *p = find_panel_by_name(dtempl, group->name);
+ if ( p == NULL ) return 1;
+
+ cx /= p->pixel_pitch;
+ cy /= p->pixel_pitch;
+ cz /= p->pixel_pitch;
+ cnz_px = p->cnz_offset / p->pixel_pitch;
+
+ switch ( axis )
+ {
+ case 'x':
+ rotate2d(&p->cny, &cnz_px, cy, cz, ang);
+ rotate2d(&p->fsy, &p->fsz, 0, 0, ang);
+ rotate2d(&p->ssy, &p->ssz, 0, 0, ang);
+ p->cnz_offset = cnz_px * p->pixel_pitch;
+ break;
+
+ case 'y':
+ rotate2d(&cnz_px, &p->cnx, cz, cx, ang);
+ rotate2d(&p->fsz, &p->fsx, 0, 0, ang);
+ rotate2d(&p->ssz, &p->ssx, 0, 0, ang);
+ p->cnz_offset = cnz_px * p->pixel_pitch;
+ break;
+
+ case 'z':
+ rotate2d(&p->cnx, &p->cny, cx, cy, ang);
+ rotate2d(&p->fsx, &p->fsy, 0, 0, ang);
+ rotate2d(&p->ssx, &p->ssy, 0, 0, ang);
+ break;
+
+ default:
+ ERROR("Invalid rotation axis '%c'\n", axis);
+ return 1;
+ }
+
+ return 0;
+
+ } else {
+
+ int i;
+
+ for ( i=0; i<group->n_children; i++ ) {
+ rotate_all_panels(dtempl, group->children[i],
+ axis, ang, cx, cy, cz);
+ }
+
+ return 0;
+
+ }
+}
+
+/**
+ * Alters dtempl by rotating the named panel group by ang (degrees) about its
+ * center.
+ *
+ * \returns zero for success, non-zero on error
+ */
+int data_template_rotate_group(DataTemplate *dtempl, const char *group_name,
+ double ang, char axis)
+{
+ struct panel_group_template *group;
+ double cx, cy, cz;
+
+ group = find_group(dtempl, group_name);
+ if ( group == NULL ) return 1;
+
+ if ( group_center(dtempl, group, &cx, &cy, &cz) ) return 1;
+
+ return rotate_all_panels(dtempl, group, axis, ang, cx, cy, cz);
+}
+
+
+static const char *str_dim(int dim)
+{
+ switch ( dim ) {
+ case DIM_FS: return "fs";
+ case DIM_SS: return "ss";
+ case DIM_PLACEHOLDER: return "%";
+ default: return NULL;
+ }
+}
+
+
+int data_template_write_to_file(const DataTemplate *dtempl, const char *filename)
+{
+ FILE *fh;
+ int i;
+
+ fh = fopen(filename, "w");
+ if ( fh == NULL ) return 1;
+
+ /* Basic top-level parameters */
+ switch ( dtempl->wavelength_unit ) {
+
+ case WAVELENGTH_M:
+ fprintf(fh, "wavelength = %s m\n", dtempl->wavelength_from);
+ break;
+
+ case WAVELENGTH_A:
+ fprintf(fh, "wavelength = %s A\n", dtempl->wavelength_from);
+ break;
+
+ case WAVELENGTH_ELECTRON_KV:
+ fprintf(fh, "electron_voltage = %s kV\n", dtempl->wavelength_from);
+ break;
+
+ case WAVELENGTH_ELECTRON_V:
+ fprintf(fh, "electron_voltage = %s V\n", dtempl->wavelength_from);
+ break;
+
+ case WAVELENGTH_PHOTON_KEV:
+ fprintf(fh, "photon_energy = %s keV\n", dtempl->wavelength_from);
+ break;
+
+ case WAVELENGTH_PHOTON_EV:
+ fprintf(fh, "photon_energy = %s eV\n", dtempl->wavelength_from);
+ break;
+
+ default:
+ ERROR("Unknown wavelength unit (%i)\n", dtempl->wavelength_unit);
+ return 1;
+
+ }
+
+ fprintf(fh, "clen = %s\n", dtempl->cnz_from);
+
+ if ( dtempl->peak_list != NULL ) {
+ fprintf(fh, "peak_list = %s\n", dtempl->peak_list);
+ }
+ switch ( dtempl->peak_list_type ) {
+ case PEAK_LIST_AUTO:
+ break;
+
+ case PEAK_LIST_CXI:
+ fprintf(fh, "peak_list_type = cxi\n");
+ break;
+
+ case PEAK_LIST_LIST3:
+ fprintf(fh, "peak_list_type = list3\n");
+ break;
+
+ default:
+ ERROR("Unknown peak list type (%i)\n", dtempl->peak_list_type);
+ return 1;
+ }
+
+ fprintf(fh, "bandwidth = %e\n", dtempl->bandwidth);
+
+ if ( dtempl->shift_x_from != NULL ) {
+ fprintf(fh, "detector_shift_x = %s\n", dtempl->shift_x_from);
+ }
+ if ( dtempl->shift_y_from != NULL ) {
+ fprintf(fh, "detector_shift_y = %s\n", dtempl->shift_y_from);
+ }
+
+ /* Other top-levels */
+ int cnz_offset_done = 0;
+ int mask_done[MAX_MASKS] = {0};
+ int satmap_done = 0;
+ int satmap_file_done = 0;
+ int mask_edge_pixels_done = 0;
+ int pixel_pitch_done = 0;
+ int adu_scale_done = 0;
+ int max_adu_done = 0;
+ int flag_values_done = 0;
+ int data_done = 0;
+ int dims_done[MAX_DIMS] = {0};
+ for ( i=0; i<dtempl->n_panels; i++ ) {
+
+ const struct panel_template *p = &dtempl->panels[i];
+ int j;
+
+ if ( p->cnz_offset_default && !cnz_offset_done ) {
+ fprintf(fh, "coffset = %f\n", p->cnz_offset);
+ cnz_offset_done = 1;
+ }
+
+ for ( j=0; j<MAX_MASKS; j++ ) {
+ if ( p->masks[j].data_location == NULL ) continue;
+ if ( !p->masks[j].mask_default ) continue;
+ if ( mask_done[j] ) continue;
+ fprintf(fh, "mask%i_data = %s\n",
+ j, p->masks[j].data_location);
+ if ( p->masks[j].filename != NULL ) {
+ fprintf(fh, "mask%i_filename = %s\n",
+ j, p->masks[j].filename);
+ }
+ fprintf(fh, "mask%i_goodbits = 0x%x\n",
+ j, p->masks[j].good_bits);
+ fprintf(fh, "mask%i_badbits = 0x%x\n",
+ j, p->masks[j].bad_bits);
+ mask_done[j] = 1;
+ }
+
+ if ( p->satmap_default && !satmap_done && (p->satmap != NULL) ) {
+ fprintf(fh, "saturation_map = %s\n", p->satmap);
+ satmap_done = 1;
+ }
+
+ if ( p->satmap_file_default && !satmap_file_done && (p->satmap_file != NULL) ) {
+ fprintf(fh, "saturation_map_file = %s\n", p->satmap);
+ satmap_file_done = 1;
+ }
+
+ if ( p->mask_edge_pixels_default && !mask_edge_pixels_done && (p->mask_edge_pixels != 0) ) {
+ fprintf(fh, "mask_edge_pixels = %i\n", p->mask_edge_pixels);
+ mask_edge_pixels_done = 1;
+ }
+
+ if ( p->pixel_pitch_default && !pixel_pitch_done ) {
+ fprintf(fh, "res = %f\n", 1.0/p->pixel_pitch);
+ pixel_pitch_done = 1;
+ }
+
+ if ( p->max_adu_default && !max_adu_done && !isinf(p->max_adu) ) {
+ fprintf(fh, "max_adu = %f\n", p->max_adu);
+ max_adu_done = 1;
+ }
+
+ if ( p->data_default && !data_done ) {
+ fprintf(fh, "data = %s\n", p->data);
+ data_done = 1;
+ }
+
+ if ( p->flag_values_default && !flag_values_done ) {
+ for ( j=0; j<MAX_FLAG_VALUES; j++ ) {
+ switch ( p->flag_types[j] ) {
+ case FLAG_NOTHING :
+ break;
+
+ case FLAG_EQUAL:
+ fprintf(fh, "flag_equal = %i\n",
+ p->flag_values[j]);
+ break;
+
+ case FLAG_MORETHAN:
+ fprintf(fh, "flag_morethan = %i\n",
+ p->flag_values[j]);
+ break;
+
+ case FLAG_LESSTHAN:
+ fprintf(fh, "flag_lessthan = %i\n",
+ p->flag_values[j]);
+ break;
+ }
+ }
+ flag_values_done = 1;
+ }
+
+ if ( p->adu_scale_default && !adu_scale_done ) {
+ switch ( p->adu_scale_unit ) {
+
+ case ADU_PER_EV:
+ fprintf(fh, "adu_per_eV = %f\n", p->adu_scale);
+ break;
+
+ case ADU_PER_PHOTON:
+ fprintf(fh, "adu_per_photon = %f\n", p->adu_scale);
+ break;
+ }
+ adu_scale_done = 1;
+ }
+
+ for ( j=0; j<MAX_DIMS; j++ ) {
+ if ( p->dims_default[j] && !dims_done[j] && p->dims[j] != DIM_UNDEFINED ) {
+ if ( p->dims[j] < 0 ) {
+ fprintf(fh, "dim%i = %s\n", j, str_dim(p->dims[j]));
+ } else {
+ fprintf(fh, "dim%i = %i\n", j, p->dims[j]);
+ }
+ dims_done[j] = 1;
+ }
+ }
+ }
+
+ fprintf(fh, "\n");
+
+ /* Bad regions */
+ for ( i=0; i<dtempl->n_bad; i++ ) {
+ const struct dt_badregion *bad = &dtempl->bad[i];
+ if ( bad->is_fsss ) {
+ fprintf(fh, "bad_%s/panel = %s\n", bad->name, bad->panel_name);
+ fprintf(fh, "bad_%s/min_fs = %i\n", bad->name, bad->min_fs);
+ fprintf(fh, "bad_%s/max_fs = %i\n", bad->name, bad->max_fs);
+ fprintf(fh, "bad_%s/min_ss = %i\n", bad->name, bad->min_ss);
+ fprintf(fh, "bad_%s/max_ss = %i\n", bad->name, bad->max_ss);
+ } else {
+ fprintf(fh, "bad_%s/min_x = %f\n", bad->name, bad->min_x);
+ fprintf(fh, "bad_%s/max_x = %f\n", bad->name, bad->max_x);
+ fprintf(fh, "bad_%s/min_y = %f\n", bad->name, bad->min_y);
+ fprintf(fh, "bad_%s/max_y = %f\n", bad->name, bad->max_y);
+ }
+ fprintf(fh, "\n");
+ }
+
+ /* Panels */
+ for ( i=0; i<dtempl->n_panels; i++ ) {
+
+ int j;
+ const struct panel_template *p = &dtempl->panels[i];
+
+ fprintf(fh, "%s/min_fs = %i\n", p->name, p->orig_min_fs);
+ fprintf(fh, "%s/max_fs = %i\n", p->name, p->orig_max_fs);
+ fprintf(fh, "%s/min_ss = %i\n", p->name, p->orig_min_ss);
+ fprintf(fh, "%s/max_ss = %i\n", p->name, p->orig_max_ss);
+ fprintf(fh, "%s/corner_x = %f\n", p->name, p->cnx);
+ fprintf(fh, "%s/corner_y = %f\n", p->name, p->cny);
+ fprintf(fh, "%s/fs = %fx %+fy %+fz\n", p->name,
+ p->fsx, p->fsy, p->fsz);
+ fprintf(fh, "%s/ss = %fx %+fy %+fz\n", p->name,
+ p->ssx, p->ssy, p->ssz);
+
+ if ( !p->cnz_offset_default ) {
+ fprintf(fh, "%s/coffset = %f\n", p->name, p->cnz_offset);
+ }
+
+ for ( j=0; j<MAX_MASKS; j++ ) {
+ if ( p->masks[j].data_location == NULL ) continue;
+ if ( p->masks[j].mask_default ) continue;
+ fprintf(fh, "%s/mask%i_data = %s\n",
+ p->name, j, p->masks[j].data_location);
+ if ( p->masks[j].filename != NULL ) {
+ fprintf(fh, "%smask%i_filename = %s\n",
+ p->name, j, p->masks[j].filename);
+ }
+ fprintf(fh, "%s/mask%i_goodbits = 0x%x\n",
+ p->name, j, p->masks[j].good_bits);
+ fprintf(fh, "%s/mask%i_badbits = 0x%x\n",
+ p->name, j, p->masks[j].bad_bits);
+ }
+
+ if ( !p->satmap_default && (p->satmap != NULL) ) {
+ fprintf(fh, "%s/saturation_map = %s\n", p->name, p->satmap);
+ }
+
+ if ( !p->satmap_file_default && (p->satmap_file != NULL) ) {
+ fprintf(fh, "%s/saturation_map_file = %s\n", p->name, p->satmap_file);
+ }
+
+ if ( !p->mask_edge_pixels_default && (p->mask_edge_pixels != 0) ) {
+ fprintf(fh, "%s/mask_edge_pixels = %i\n", p->name, p->mask_edge_pixels);
+ }
+
+ if ( !p->pixel_pitch_default ) {
+ fprintf(fh, "%s/res = %f\n", p->name, 1.0/p->pixel_pitch);
+ }
+
+ if ( !p->adu_scale_default ) {
+ switch ( p->adu_scale_unit ) {
+
+ case ADU_PER_EV:
+ fprintf(fh, "%s/adu_per_eV = %f\n", p->name, p->adu_scale);
+ break;
+
+ case ADU_PER_PHOTON:
+ fprintf(fh, "%s/adu_per_photon = %f\n", p->name, p->adu_scale);
+ break;
+ }
+ }
+
+ if ( !p->max_adu_default ) {
+ fprintf(fh, "%s/max_adu = %f\n", p->name, p->max_adu);
+ }
+
+ if ( !p->flag_values_default ) {
+ for ( j=0; j<MAX_FLAG_VALUES; j++ ) {
+ switch ( p->flag_types[j] ) {
+ case FLAG_NOTHING :
+ break;
+
+ case FLAG_EQUAL:
+ fprintf(fh, "%s/flag_equal = %i\n",
+ p->name, p->flag_values[j]);
+ break;
+
+ case FLAG_MORETHAN:
+ fprintf(fh, "%s/flag_morethan = %i\n",
+ p->name, p->flag_values[j]);
+ break;
+
+ case FLAG_LESSTHAN:
+ fprintf(fh, "%s/flag_lessthan = %i\n",
+ p->name, p->flag_values[j]);
+ break;
+ }
+ }
+ }
+
+ if ( !p->data_default ) {
+ fprintf(fh, "%s/data = %s\n", p->name, p->data);
+ }
+
+ for ( j=0; j<MAX_DIMS; j++ ) {
+ if ( !p->dims_default[j] && (p->dims[j] != DIM_UNDEFINED) ) {
+ if ( p->dims[j] < 0 ) {
+ fprintf(fh, "%s/dim%i = %s\n", p->name, j, str_dim(p->dims[j]));
+ } else {
+ fprintf(fh, "%s/dim%i = %i\n", p->name, j, p->dims[j]);
+ }
+ dims_done[j] = 1;
+ }
+ }
+
+ if ( p->bad ) {
+ fprintf(fh, "%s/no_index = 1\n", p->name);
+ }
+
+ fprintf(fh, "\n");
+ }
+
+ /* Groups */
+ for ( i=0; i<dtempl->n_groups; i++ ) {
+ int j;
+ if ( dtempl->groups[i]->n_children == 0 ) continue;
+ fprintf(fh, "group_%s = ", dtempl->groups[i]->name);
+ for ( j=0; j<dtempl->groups[i]->n_children; j++ ) {
+ if ( j > 0 ) fprintf(fh, ",");
+ fprintf(fh, "%s", dtempl->groups[i]->children[j]->name);
+ }
+ fprintf(fh, "\n");
+ }
+
+ fclose(fh);
+ return 0;
+}
+
+
+static void add_group_info(struct dg_group_info *ginfo, int *ppos,
+ struct panel_group_template *group,
+ int serial, int level, int c_mul)
+{
+ int j;
+ int i = *ppos;
+ (*ppos)++;
+
+ ginfo[i].name = group->name;
+ ginfo[i].serial = serial;
+ ginfo[i].hierarchy_level = level;
+
+ for ( j=0; j<group->n_children; j++ ) {
+ add_group_info(ginfo, ppos, group->children[j],
+ serial+c_mul*(j+1), level+1, c_mul*100);
+ }
+}
+
+
+struct dg_group_info *data_template_group_info(const DataTemplate *dtempl, int *n)
+{
+ struct dg_group_info *ginfo;
+ int i;
+ struct panel_group_template *group;
+
+ ginfo = malloc(sizeof(struct dg_group_info)*dtempl->n_groups);
+ if ( ginfo == NULL ) return NULL;
+
+ group = find_group(dtempl, "all");
+ i = 0;
+ add_group_info(ginfo, &i, group, 0, 0, 100);
+
+ *n = dtempl->n_groups;
+ return ginfo;
+}
diff --git a/libcrystfel/src/datatemplate.h b/libcrystfel/src/datatemplate.h
index fea39ccd..07f2387d 100644
--- a/libcrystfel/src/datatemplate.h
+++ b/libcrystfel/src/datatemplate.h
@@ -43,25 +43,17 @@
typedef struct _datatemplate DataTemplate;
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-struct rigid_group
+struct dg_group_info
{
- char *name;
- int *panel_numbers;
- int n_panels;
+ const char *name;
+ int serial;
+ int hierarchy_level;
};
-struct rg_collection
-{
- char *name;
- struct rigid_group **rigid_groups;
- int n_rigid_groups;
-};
-
+#ifdef __cplusplus
+extern "C" {
+#endif
extern DataTemplate *data_template_new_from_file(const char *filename);
extern DataTemplate *data_template_new_from_string(const char *string_in);
@@ -91,15 +83,31 @@ extern void data_template_add_copy_header(DataTemplate *dt,
extern int data_template_get_slab_extents(const DataTemplate *dt, int *pw, int *ph);
-extern struct rg_collection *data_template_get_rigid_groups(const DataTemplate *dtempl,
- const char *collection_name);
-
extern double data_template_get_wavelength_if_possible(const DataTemplate *dt);
extern double data_template_get_clen_if_possible(const DataTemplate *dt);
extern struct detgeom *data_template_get_2d_detgeom_if_possible(const DataTemplate *dt);
+extern void data_template_show_hierarchy(const DataTemplate *dtempl);
+
+extern int data_template_translate_group_px(DataTemplate *dtempl,
+ const char *group_name,
+ double x, double y, double z);
+
+extern int data_template_translate_group_m(DataTemplate *dtempl,
+ const char *group_name,
+ double x, double y, double z);
+
+extern int data_template_rotate_group(DataTemplate *dtempl,
+ const char *group_name,
+ double ang, char axis);
+
+extern int data_template_write_to_file(const DataTemplate *dtempl,
+ const char *filename);
+
+extern struct dg_group_info *data_template_group_info(const DataTemplate *dtempl, int *n);
+
#ifdef __cplusplus
}
#endif
diff --git a/libcrystfel/src/datatemplate_priv.h b/libcrystfel/src/datatemplate_priv.h
index 657d5094..ab40ac2f 100644
--- a/libcrystfel/src/datatemplate_priv.h
+++ b/libcrystfel/src/datatemplate_priv.h
@@ -40,6 +40,12 @@
/* Maximum number of placeholders expected in path structure */
#define MAX_PATH_PARTS (16)
+/* Maximum number of panel groups */
+#define MAX_PANEL_GROUPS (256)
+
+/* Maximum number of panel groups that can derive from one panel */
+#define MAX_PANEL_GROUP_CHILDREN (64)
+
enum adu_per_unit
{
ADU_PER_PHOTON,
@@ -102,6 +108,9 @@ struct mask_template
/** Bit mask for good pixels
* (pixel cannot be good unless all of these are set) */
unsigned int good_bits;
+
+ /** If non-zero, this mask came from the top level */
+ int mask_default;
};
@@ -119,46 +128,53 @@ struct panel_template
double cny;
/**@}*/
- /** Location to get cnz from, e.g. from HDF5 file */
- char *cnz_from;
-
/** The offset to be applied from clen */
double cnz_offset;
+ int cnz_offset_default;
/** Mask definitions */
struct mask_template masks[MAX_MASKS];
/** Location of per-pixel saturation map */
char *satmap;
+ int satmap_default;
/** Filename for saturation map */
char *satmap_file;
+ int satmap_file_default;
/** Mark entire panel as bad if set */
int bad;
/** Mark this number of edge rows as bad */
int mask_edge_pixels;
+ int mask_edge_pixels_default;
/** Pixel size in metres */
double pixel_pitch;
+ int pixel_pitch_default;
/** Number of detector intensity units per photon, or eV */
double adu_scale;
enum adu_per_unit adu_scale_unit;
+ int adu_scale_default;
/** Treat pixel as unreliable if higher than this */
double max_adu;
+ int max_adu_default;
/** Pixels with exactly this value will be marked as bad */
enum flag_value_type flag_types[MAX_FLAG_VALUES];
signed int flag_values[MAX_FLAG_VALUES];
+ int flag_values_default;
/** Location of data in file (possibly with placeholders) */
char *data;
+ int data_default;
/** Dimensions (see definitions for DIM_FS etc above) */
signed int dims[MAX_DIMS];
+ int dims_default[MAX_DIMS];
/** \name Transformation matrix from pixel coordinates to lab frame */
/*@{*/
@@ -205,6 +221,14 @@ struct dt_badregion
};
+struct panel_group_template
+{
+ char *name;
+ int n_children;
+ struct panel_group_template *children[MAX_PANEL_GROUP_CHILDREN];
+};
+
+
struct _datatemplate
{
struct panel_template *panels;
@@ -218,11 +242,8 @@ struct _datatemplate
double bandwidth;
- struct rigid_group **rigid_groups;
- int n_rigid_groups;
-
- struct rg_collection **rigid_group_collections;
- int n_rg_collections;
+ struct panel_group_template *groups[MAX_PANEL_GROUPS];
+ int n_groups;
char *peak_list;
enum peak_layout peak_list_type;
@@ -231,6 +252,9 @@ struct _datatemplate
char *shift_x_from;
char *shift_y_from;
+ /** Location to get detector z from, e.g. from HDF5 file */
+ char *cnz_from;
+
char *headers_to_copy[MAX_COPY_HEADERS];
int n_headers_to_copy;
};
diff --git a/libcrystfel/src/detgeom.c b/libcrystfel/src/detgeom.c
index 58e52f01..b4835317 100644
--- a/libcrystfel/src/detgeom.c
+++ b/libcrystfel/src/detgeom.c
@@ -29,6 +29,9 @@
#include <libcrystfel-config.h>
#include <math.h>
#include <stdlib.h>
+#include <gsl/gsl_matrix.h>
+#include <gsl/gsl_blas.h>
+#include <gsl/gsl_linalg.h>
#include "detgeom.h"
#include "utils.h"
@@ -61,6 +64,22 @@ void detgeom_transform_coords(struct detgeom_panel *p,
}
+static void free_group(struct detgeom_panel_group *g)
+{
+ int i;
+
+ if ( g == NULL ) return;
+
+ for ( i=0; i<g->n_children; i++ ) {
+ free_group(g->children[i]);
+ }
+
+ free(g->name);
+ free(g->children);
+ free(g);
+}
+
+
void detgeom_free(struct detgeom *detgeom)
{
int i;
@@ -71,6 +90,7 @@ void detgeom_free(struct detgeom *detgeom)
free(detgeom->panels[i].name);
}
+ free_group(detgeom->top_group);
free(detgeom->panels);
free(detgeom);
}
@@ -174,3 +194,146 @@ double detgeom_mean_camera_length(struct detgeom *dg)
return mean;
}
+
+
+struct detgeom_panel *detgeom_find_panel(struct detgeom *dg, const char *name)
+{
+ int i;
+ for ( i=0; i<dg->n_panels; i++ ) {
+ if ( strcmp(dg->panels[i].name, name) == 0 ) {
+ return &dg->panels[i];
+ }
+ }
+ return NULL;
+}
+
+
+static void detgeom_show_group(const struct detgeom_panel_group *group, int level)
+{
+ int i;
+
+ for ( i=0; i<level; i++ ) STATUS(" ");
+
+ if ( group == NULL ) {
+ STATUS("!!!\n");
+ return;
+ }
+
+ STATUS("%s (serial %i)\n", group->name, group->serial);
+
+ for ( i=0; i<group->n_children; i++ ) {
+ detgeom_show_group(group->children[i], level+1);
+ }
+}
+
+
+void detgeom_show_hierarchy(const struct detgeom *dg)
+{
+ detgeom_show_group(dg->top_group, 0);
+}
+
+
+void detgeom_translate_detector_m(struct detgeom *dg, double x, double y, double z)
+{
+ int i;
+ for ( i=0; i<dg->n_panels; i++ ) {
+ struct detgeom_panel *p = &dg->panels[i];
+ p->cnx += x / p->pixel_pitch;
+ p->cny += y / p->pixel_pitch;
+ p->cnz += z / p->pixel_pitch;
+ }
+}
+
+
+gsl_matrix **make_panel_minvs(struct detgeom *dg)
+{
+ int i;
+ gsl_matrix **Minvs;
+
+ Minvs = malloc(dg->n_panels * sizeof(gsl_matrix *));
+ if ( Minvs == NULL ) return NULL;
+
+ for ( i=0; i<dg->n_panels; i++ ) {
+
+ struct detgeom_panel *p = &dg->panels[i];
+ gsl_matrix *M = gsl_matrix_calloc(3, 3);
+
+ gsl_matrix_set(M, 0, 0, p->pixel_pitch*p->cnx);
+ gsl_matrix_set(M, 0, 1, p->pixel_pitch*p->fsx);
+ gsl_matrix_set(M, 0, 2, p->pixel_pitch*p->ssx);
+ gsl_matrix_set(M, 1, 0, p->pixel_pitch*p->cny);
+ gsl_matrix_set(M, 1, 1, p->pixel_pitch*p->fsy);
+ gsl_matrix_set(M, 1, 2, p->pixel_pitch*p->ssy);
+ gsl_matrix_set(M, 2, 0, p->pixel_pitch*p->cnz);
+ gsl_matrix_set(M, 2, 1, p->pixel_pitch*p->fsz);
+ gsl_matrix_set(M, 2, 2, p->pixel_pitch*p->ssz);
+
+ Minvs[i] = matrix_invert(M);
+ if ( Minvs[i] == NULL ) {
+ ERROR("Failed to calculate inverse panel matrix for %s\n",
+ p->name);
+ return NULL;
+ }
+
+ }
+
+ return Minvs;
+}
+
+
+static void add_point(const struct detgeom_panel *p,
+ int fs, int ss,
+ double *tx, double *ty, double *tz)
+{
+ *tx += (p->cnx + fs*p->fsx + ss*p->ssx) * p->pixel_pitch;
+ *ty += (p->cny + fs*p->fsy + ss*p->ssy) * p->pixel_pitch;
+ *tz += (p->cnz + fs*p->fsz + ss*p->ssz) * p->pixel_pitch;
+}
+
+
+int detgeom_group_center(const struct detgeom_panel_group *grp,
+ double *x, double *y, double *z)
+{
+ if ( grp->n_children == 0 ) {
+
+ const struct detgeom_panel *p = grp->panel;
+ if ( p == NULL ) return 1;
+
+ double tx = 0.0;
+ double ty = 0.0;
+ double tz = 0.0;
+
+ add_point(p, 0, 0, &tx, &ty, &tz);
+ add_point(p, p->w, 0, &tx, &ty, &tz);
+ add_point(p, 0, p->h, &tx, &ty, &tz);
+ add_point(p, p->w, p->h, &tx, &ty, &tz);
+
+ *x = tx / 4.0;
+ *y = ty / 4.0;
+ *z = tz / 4.0;
+
+ return 0;
+
+ } else {
+
+ int i;
+ double tx = 0.0;
+ double ty = 0.0;
+ double tz = 0.0;
+
+ for ( i=0; i<grp->n_children; i++ ) {
+ double gcx, gcy, gcz;
+ detgeom_group_center(grp->children[i], &gcx, &gcy, &gcz);
+ tx += gcx;
+ ty += gcy;
+ tz += gcz;
+ }
+
+ *x = tx / grp->n_children;
+ *y = ty / grp->n_children;
+ *z = tz / grp->n_children;
+
+ return 0;
+
+ }
+}
diff --git a/libcrystfel/src/detgeom.h b/libcrystfel/src/detgeom.h
index 7f291a7a..53877b2e 100644
--- a/libcrystfel/src/detgeom.h
+++ b/libcrystfel/src/detgeom.h
@@ -42,6 +42,7 @@ extern "C" {
* Detector geometry structure and related functions.
*/
+#include <gsl/gsl_matrix.h>
/**
* Represents one panel of a detector
@@ -83,15 +84,43 @@ struct detgeom_panel
int w;
int h;
/*@}*/
+
+ /** \name Leaf group containing this panel (only) */
+ const struct detgeom_panel_group *group;
+};
+
+
+struct detgeom_panel_group
+{
+ char *name;
+ int n_children;
+
+ struct detgeom_panel_group *parent;
+ int serial;
+
+ /* Center of panel group, in lab coordinate system (metres)
+ * This will be the rotation center. */
+ double cx;
+ double cy;
+ double cz;
+
+ /* If n_children > 0, here are the child groups */
+ struct detgeom_panel_group **children;
+
+ /* If n_children == 0, this is a leaf node, so: */
+ struct detgeom_panel *panel;
};
struct detgeom
{
struct detgeom_panel *panels;
- int n_panels;
+ int n_panels;
+
+ struct detgeom_panel_group *top_group;
};
+
extern void detgeom_transform_coords(struct detgeom_panel *p,
double fs, double ss,
double wavelength,
@@ -107,6 +136,17 @@ extern void show_panel(struct detgeom_panel *p);
extern double detgeom_mean_camera_length(struct detgeom *dg);
+extern struct detgeom_panel *detgeom_find_panel(struct detgeom *dg, const char *name);
+
+extern void detgeom_show_hierarchy(const struct detgeom *dg);
+
+extern void detgeom_translate_detector_m(struct detgeom *dg, double x, double y, double z);
+
+extern int detgeom_group_center(const struct detgeom_panel_group *grp,
+ double *x, double *y, double *z);
+
+extern gsl_matrix **make_panel_minvs(struct detgeom *dg);
+
#ifdef __cplusplus
}
#endif
diff --git a/libcrystfel/src/geometry.c b/libcrystfel/src/geometry.c
index e454b73d..674fe4d0 100644
--- a/libcrystfel/src/geometry.c
+++ b/libcrystfel/src/geometry.c
@@ -53,17 +53,11 @@ static int locate_peak_on_panel(double x, double y, double z, double k,
double det_shift_x, double det_shift_y,
double *pfs, double *pss)
{
- double ctt, tta, phi;
gsl_vector *v;
gsl_vector *t;
gsl_matrix *M;
double fs, ss, one_over_mu;
- /* Calculate 2theta (scattering angle) and azimuth (phi) */
- tta = atan2(sqrt(x*x+y*y), k+z);
- ctt = cos(tta);
- phi = atan2(y, x);
-
/* Set up matrix equation */
M = gsl_matrix_alloc(3, 3);
v = gsl_vector_alloc(3);
@@ -73,9 +67,9 @@ static int locate_peak_on_panel(double x, double y, double z, double k,
return 0;
}
- gsl_vector_set(t, 0, sin(tta)*cos(phi));
- gsl_vector_set(t, 1, sin(tta)*sin(phi));
- gsl_vector_set(t, 2, ctt);
+ gsl_vector_set(t, 0, x);
+ gsl_vector_set(t, 1, y);
+ gsl_vector_set(t, 2, k+z);
gsl_matrix_set(M, 0, 0, p->cnx+(det_shift_x/p->pixel_pitch));
gsl_matrix_set(M, 0, 1, p->fsx);
@@ -429,69 +423,6 @@ static Reflection *check_reflection(struct image *image, Crystal *cryst,
}
-double r_gradient(UnitCell *cell, int k, Reflection *refl, struct image *image)
-{
- double asx, asy, asz;
- double bsx, bsy, bsz;
- double csx, csy, csz;
- double xl, yl, zl;
- signed int hs, ks, ls;
- double tl, phi, azi;
-
- get_symmetric_indices(refl, &hs, &ks, &ls);
-
- cell_get_reciprocal(cell, &asx, &asy, &asz,
- &bsx, &bsy, &bsz,
- &csx, &csy, &csz);
- xl = hs*asx + ks*bsx + ls*csx;
- yl = hs*asy + ks*bsy + ls*csy;
- zl = hs*asz + ks*bsz + ls*csz;
-
- tl = sqrt(xl*xl + yl*yl);
- phi = angle_between_2d(tl, zl+1.0/image->lambda, 0.0, 1.0); /* 2theta */
- azi = atan2(yl, xl); /* azimuth */
-
- switch ( k ) {
-
- case GPARAM_ASX :
- return - hs * sin(phi) * cos(azi);
-
- case GPARAM_BSX :
- return - ks * sin(phi) * cos(azi);
-
- case GPARAM_CSX :
- return - ls * sin(phi) * cos(azi);
-
- case GPARAM_ASY :
- return - hs * sin(phi) * sin(azi);
-
- case GPARAM_BSY :
- return - ks * sin(phi) * sin(azi);
-
- case GPARAM_CSY :
- return - ls * sin(phi) * sin(azi);
-
- case GPARAM_ASZ :
- return - hs * cos(phi);
-
- case GPARAM_BSZ :
- return - ks * cos(phi);
-
- case GPARAM_CSZ :
- return - ls * cos(phi);
-
- case GPARAM_DETX :
- case GPARAM_DETY :
- case GPARAM_CLEN :
- return 0.0;
-
- }
-
- ERROR("No r gradient defined for parameter %i\n", k);
- abort();
-}
-
-
/**
* \param cryst: A \ref Crystal
* \param max_res: Maximum resolution to predict to (m^-1)
@@ -1084,125 +1015,3 @@ void polarisation_correction(RefList *list, UnitCell *cell,
set_esd_intensity(refl, sigma / pol);
}
}
-
-
-/* Returns dx_h/dP, where P = any parameter */
-double x_gradient(int param, Reflection *refl, UnitCell *cell,
- struct detgeom_panel *p)
-{
- signed int h, k, l;
- double xl, zl, kpred;
- double asx, asy, asz, bsx, bsy, bsz, csx, csy, csz;
-
- get_indices(refl, &h, &k, &l);
- kpred = get_kpred(refl);
- cell_get_reciprocal(cell, &asx, &asy, &asz,
- &bsx, &bsy, &bsz,
- &csx, &csy, &csz);
- xl = h*asx + k*bsx + l*csx;
- zl = h*asz + k*bsz + l*csz;
-
- switch ( param ) {
-
- case GPARAM_ASX :
- return h * p->cnz * p->pixel_pitch / (kpred + zl);
-
- case GPARAM_BSX :
- return k * p->cnz * p->pixel_pitch / (kpred + zl);
-
- case GPARAM_CSX :
- return l * p->cnz * p->pixel_pitch / (kpred + zl);
-
- case GPARAM_ASY :
- return 0.0;
-
- case GPARAM_BSY :
- return 0.0;
-
- case GPARAM_CSY :
- return 0.0;
-
- case GPARAM_ASZ :
- return -h * xl * p->cnz * p->pixel_pitch / (kpred*kpred + 2.0*kpred*zl + zl*zl);
-
- case GPARAM_BSZ :
- return -k * xl * p->cnz * p->pixel_pitch / (kpred*kpred + 2.0*kpred*zl + zl*zl);
-
- case GPARAM_CSZ :
- return -l * xl * p->cnz * p->pixel_pitch / (kpred*kpred + 2.0*kpred*zl + zl*zl);
-
- case GPARAM_DETX :
- return -1;
-
- case GPARAM_DETY :
- return 0;
-
- case GPARAM_CLEN :
- return xl / (kpred+zl);
-
- }
-
- ERROR("Positional gradient requested for parameter %i?\n", param);
- abort();
-}
-
-
-/* Returns dy_h/dP, where P = any parameter */
-double y_gradient(int param, Reflection *refl, UnitCell *cell,
- struct detgeom_panel *p)
-{
- signed int h, k, l;
- double yl, zl, kpred;
- double asx, asy, asz, bsx, bsy, bsz, csx, csy, csz;
-
- get_indices(refl, &h, &k, &l);
- kpred = get_kpred(refl);
- cell_get_reciprocal(cell, &asx, &asy, &asz,
- &bsx, &bsy, &bsz,
- &csx, &csy, &csz);
- yl = h*asy + k*bsy + l*csy;
- zl = h*asz + k*bsz + l*csz;
-
- switch ( param ) {
-
- case GPARAM_ASX :
- return 0.0;
-
- case GPARAM_BSX :
- return 0.0;
-
- case GPARAM_CSX :
- return 0.0;
-
- case GPARAM_ASY :
- return h * p->cnz * p->pixel_pitch / (kpred + zl);
-
- case GPARAM_BSY :
- return k * p->cnz * p->pixel_pitch / (kpred + zl);
-
- case GPARAM_CSY :
- return l * p->cnz * p->pixel_pitch / (kpred + zl);
-
- case GPARAM_ASZ :
- return -h * yl * p->cnz * p->pixel_pitch / (kpred*kpred + 2.0*kpred*zl + zl*zl);
-
- case GPARAM_BSZ :
- return -k * yl * p->cnz * p->pixel_pitch / (kpred*kpred + 2.0*kpred*zl + zl*zl);
-
- case GPARAM_CSZ :
- return -l * yl * p->cnz * p->pixel_pitch / (kpred*kpred + 2.0*kpred*zl + zl*zl);
-
- case GPARAM_DETX :
- return 0;
-
- case GPARAM_DETY :
- return -1;
-
- case GPARAM_CLEN :
- return yl / (kpred+zl);
-
- }
-
- ERROR("Positional gradient requested for parameter %i?\n", param);
- abort();
-}
diff --git a/libcrystfel/src/geometry.h b/libcrystfel/src/geometry.h
index 19c6a23a..d05c8832 100644
--- a/libcrystfel/src/geometry.h
+++ b/libcrystfel/src/geometry.h
@@ -8,7 +8,7 @@
* Copyright © 2012 Richard Kirian
*
* Authors:
- * 2010-2020 Thomas White <taw@physics.org>
+ * 2010-2021 Thomas White <taw@physics.org>
* 2012 Richard Kirian
*
* This file is part of CrystFEL.
@@ -62,32 +62,6 @@ typedef enum {
} PartialityModel;
-/** Enumeration of parameters which may want to be refined */
-enum gparam {
- GPARAM_ASX,
- GPARAM_ASY,
- GPARAM_ASZ,
- GPARAM_BSX,
- GPARAM_BSY,
- GPARAM_BSZ,
- GPARAM_CSX,
- GPARAM_CSY,
- GPARAM_CSZ,
- GPARAM_R,
- GPARAM_DIV,
- GPARAM_DETX,
- GPARAM_DETY,
- GPARAM_CLEN,
- GPARAM_OSF, /* Linear scale factor */
- GPARAM_BFAC, /* D-W scale factor */
- GPARAM_ANG1, /* Out of plane rotation angles of crystal */
- GPARAM_ANG2,
- GPARAM_WAVELENGTH,
-
- GPARAM_EOL /* End of list */
-};
-
-
/**
* This structure represents the polarisation of the incident radiation
*/
@@ -107,8 +81,6 @@ extern RefList *predict_to_res(Crystal *cryst, double max_res);
extern void calculate_partialities(Crystal *cryst, PartialityModel pmodel);
-extern double r_gradient(UnitCell *cell, int k, Reflection *refl,
- struct image *image);
extern void update_predictions(Crystal *cryst);
extern struct polarisation parse_polarisation(const char *text);
extern void polarisation_correction(RefList *list, UnitCell *cell,
@@ -117,11 +89,6 @@ extern void polarisation_correction(RefList *list, UnitCell *cell,
extern double sphere_fraction(double rlow, double rhigh, double pr);
extern double gaussian_fraction(double rlow, double rhigh, double pr);
-extern double x_gradient(int param, Reflection *refl, UnitCell *cell,
- struct detgeom_panel *p);
-extern double y_gradient(int param, Reflection *refl, UnitCell *cell,
- struct detgeom_panel *p);
-
#ifdef __cplusplus
}
#endif
diff --git a/libcrystfel/src/index.c b/libcrystfel/src/index.c
index 02b01757..e3512b4a 100644
--- a/libcrystfel/src/index.c
+++ b/libcrystfel/src/index.c
@@ -591,7 +591,8 @@ static float real_time()
/* Return non-zero for "success" */
static int try_indexer(struct image *image, IndexingMethod indm,
- IndexingPrivate *ipriv, void *mpriv, char *last_task)
+ IndexingPrivate *ipriv, void *mpriv, char *last_task,
+ Mille *mille)
{
int i, r;
int n_bad = 0;
@@ -719,7 +720,7 @@ static int try_indexer(struct image *image, IndexingMethod indm,
{
int r;
profile_start("refine");
- r = refine_prediction(image, cr);
+ r = refine_prediction(image, cr, mille);
profile_end("refine");
if ( r ) {
crystal_set_user_flag(cr, 1);
@@ -920,19 +921,26 @@ static int finished_retry(IndexingMethod indm, IndexingFlags flags,
void index_pattern(struct image *image, IndexingPrivate *ipriv)
{
- index_pattern_3(image, ipriv, NULL, NULL);
+ index_pattern_4(image, ipriv, NULL, NULL, NULL);
}
void index_pattern_2(struct image *image, IndexingPrivate *ipriv, int *ping)
{
- index_pattern_3(image, ipriv, ping, NULL);
+ index_pattern_4(image, ipriv, ping, NULL, NULL);
}
void index_pattern_3(struct image *image, IndexingPrivate *ipriv, int *ping,
char *last_task)
{
+ index_pattern_4(image, ipriv, ping, last_task, NULL);
+}
+
+
+void index_pattern_4(struct image *image, IndexingPrivate *ipriv, int *ping,
+ char *last_task, Mille *mille)
+{
int n = 0;
ImageFeatureList *orig;
@@ -982,7 +990,7 @@ void index_pattern_3(struct image *image, IndexingPrivate *ipriv, int *ping,
r = try_indexer(image, ipriv->methods[n],
ipriv, ipriv->engine_private[n],
- last_task);
+ last_task, mille);
success += r;
ntry++;
done = finished_retry(ipriv->methods[n], ipriv->flags,
diff --git a/libcrystfel/src/index.h b/libcrystfel/src/index.h
index 94018904..fa371276 100644
--- a/libcrystfel/src/index.h
+++ b/libcrystfel/src/index.h
@@ -3,13 +3,13 @@
*
* Perform indexing (somehow)
*
- * Copyright © 2012-2021 Deutsches Elektronen-Synchrotron DESY,
+ * Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY,
* a research centre of the Helmholtz Association.
* Copyright © 2012 Richard Kirian
* Copyright © 2012 Lorenzo Galli
*
* Authors:
- * 2010-2021 Thomas White <taw@physics.org>
+ * 2010-2023 Thomas White <taw@physics.org>
* 2010 Richard Kirian
* 2012 Lorenzo Galli
* 2015 Kenneth Beyerlein <kenneth.beyerlein@desy.de>
@@ -210,6 +210,7 @@ extern char *base_indexer_str(IndexingMethod indm);
#include "cell.h"
#include "image.h"
#include "datatemplate.h"
+#include "predict-refine.h"
extern struct argp felix_argp;
extern struct argp pinkIndexer_argp;
@@ -251,6 +252,9 @@ extern void index_pattern_2(struct image *image, IndexingPrivate *ipriv,
extern void index_pattern_3(struct image *image, IndexingPrivate *ipriv,
int *ping, char *last_task);
+extern void index_pattern_4(struct image *image, IndexingPrivate *ipriv,
+ int *ping, char *last_task, Mille *mille);
+
extern void cleanup_indexing(IndexingPrivate *ipriv);
#ifdef __cplusplus
diff --git a/libcrystfel/src/predict-refine.c b/libcrystfel/src/predict-refine.c
index 19c689cd..43b54092 100644
--- a/libcrystfel/src/predict-refine.c
+++ b/libcrystfel/src/predict-refine.c
@@ -3,11 +3,11 @@
*
* Prediction refinement
*
- * Copyright © 2012-2021 Deutsches Elektronen-Synchrotron DESY,
+ * Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY,
* a research centre of the Helmholtz Association.
*
* Authors:
- * 2010-2020 Thomas White <taw@physics.org>
+ * 2010-2023 Thomas White <taw@physics.org>
* 2016 Valerio Mariani
*
* This file is part of CrystFEL.
@@ -33,91 +33,325 @@
#include <assert.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_vector.h>
+#include <gsl/gsl_linalg.h>
#include "image.h"
#include "geometry.h"
#include "cell-utils.h"
+#include "predict-refine.h"
+#include "profile.h"
+#include "crystfel-mille.h"
/** \file predict-refine.h */
+double r_dev(struct reflpeak *rp)
+{
+ /* Excitation error term */
+ return get_exerr(rp->refl);
+}
-/* Maximum number of iterations of NLSq to do for each image per macrocycle. */
-#define MAX_CYCLES (10)
-/* Weighting of excitation error term (m^-1) compared to position term (m) */
-#define EXC_WEIGHT (4e-20)
+double fs_dev(struct reflpeak *rp, struct detgeom *det)
+{
+ double fsh, ssh;
+ get_detector_pos(rp->refl, &fsh, &ssh);
+ return fsh - rp->peak->fs;
+}
+
-/* Parameters to refine */
-static const enum gparam rv[] =
+double ss_dev(struct reflpeak *rp, struct detgeom *det)
{
- GPARAM_ASX,
- GPARAM_ASY,
- GPARAM_ASZ,
- GPARAM_BSX,
- GPARAM_BSY,
- GPARAM_BSZ,
- GPARAM_CSX,
- GPARAM_CSY,
- GPARAM_CSZ,
- GPARAM_DETX,
- GPARAM_DETY,
-};
-
-static const int num_params = 11;
-
-struct reflpeak {
- Reflection *refl;
- struct imagefeature *peak;
- double Ih; /* normalised */
- struct detgeom_panel *panel; /* panel the reflection appears on
- * (we assume this never changes) */
-};
-
-
-static void twod_mapping(double fs, double ss, double *px, double *py,
- struct detgeom_panel *p, double dx, double dy)
+ double fsh, ssh;
+ get_detector_pos(rp->refl, &fsh, &ssh);
+ return ssh - rp->peak->ss;
+}
+
+
+double r_gradient(int param, Reflection *refl, UnitCell *cell, double wavelength)
{
- double xs, ys;
+ double asx, asy, asz;
+ double bsx, bsy, bsz;
+ double csx, csy, csz;
+ double xl, yl, zl;
+ signed int hs, ks, ls;
+ double tl, phi, azi;
+
+ get_symmetric_indices(refl, &hs, &ks, &ls);
+
+ cell_get_reciprocal(cell, &asx, &asy, &asz,
+ &bsx, &bsy, &bsz,
+ &csx, &csy, &csz);
+ xl = hs*asx + ks*bsx + ls*csx;
+ yl = hs*asy + ks*bsy + ls*csy;
+ zl = hs*asz + ks*bsz + ls*csz;
+
+ tl = sqrt(xl*xl + yl*yl);
+ phi = angle_between_2d(tl, zl+1.0/wavelength, 0.0, 1.0); /* 2theta */
+ azi = atan2(yl, xl); /* azimuth */
+
+ switch ( param ) {
+
+ case GPARAM_ASX :
+ return - hs * sin(phi) * cos(azi);
+
+ case GPARAM_BSX :
+ return - ks * sin(phi) * cos(azi);
+
+ case GPARAM_CSX :
+ return - ls * sin(phi) * cos(azi);
+
+ case GPARAM_ASY :
+ return - hs * sin(phi) * sin(azi);
- xs = fs*p->fsx + ss*p->ssx; /* pixels */
- ys = fs*p->fsy + ss*p->ssy; /* pixels */
+ case GPARAM_BSY :
+ return - ks * sin(phi) * sin(azi);
- *px = (xs + p->cnx) * p->pixel_pitch + dx; /* metres */
- *py = (ys + p->cny) * p->pixel_pitch + dy; /* metres */
+ case GPARAM_CSY :
+ return - ls * sin(phi) * sin(azi);
+
+ case GPARAM_ASZ :
+ return - hs * cos(phi);
+
+ case GPARAM_BSZ :
+ return - ks * cos(phi);
+
+ case GPARAM_CSZ :
+ return - ls * cos(phi);
+
+ /* Detector movements don't affect excitation error */
+ case GPARAM_DET_TX :
+ case GPARAM_DET_TY :
+ case GPARAM_DET_TZ :
+ case GPARAM_DET_RX :
+ case GPARAM_DET_RY :
+ case GPARAM_DET_RZ :
+ return 0.0;
+
+ }
+
+ ERROR("No r gradient defined for parameter %i\n", param);
+ abort();
}
-static double r_dev(struct reflpeak *rp)
+/* Spot position gradients for diffraction physics (anything that changes the
+ * diffracted ray direction) */
+int fs_ss_gradient_physics(int param, Reflection *refl, UnitCell *cell,
+ struct detgeom_panel *p, gsl_matrix *Minv,
+ double fs, double ss, double mu,
+ float *fsg, float *ssg)
{
- /* Excitation error term */
- return get_exerr(rp->refl);
+ signed int h, k, l;
+ gsl_vector *dRdp;
+ gsl_vector *v;
+
+ get_indices(refl, &h, &k, &l);
+
+ dRdp = gsl_vector_calloc(3);
+
+ switch ( param ) {
+
+ case GPARAM_ASX :
+ gsl_vector_set(dRdp, 0, h);
+ break;
+
+ case GPARAM_BSX :
+ gsl_vector_set(dRdp, 0, k);
+ break;
+
+ case GPARAM_CSX :
+ gsl_vector_set(dRdp, 0, l);
+ break;
+
+ case GPARAM_ASY :
+ gsl_vector_set(dRdp, 1, h);
+ break;
+
+ case GPARAM_BSY :
+ gsl_vector_set(dRdp, 1, k);
+ break;
+
+ case GPARAM_CSY :
+ gsl_vector_set(dRdp, 1, l);
+ break;
+
+ case GPARAM_ASZ :
+ gsl_vector_set(dRdp, 2, h);
+ break;
+
+ case GPARAM_BSZ :
+ gsl_vector_set(dRdp, 2, k);
+ break;
+
+ case GPARAM_CSZ :
+ gsl_vector_set(dRdp, 2, l);
+ break;
+
+ default :
+ ERROR("Invalid physics gradient %i\n", param);
+ return 1;
+ }
+
+ v = gsl_vector_calloc(3);
+ gsl_blas_dgemv(CblasNoTrans, 1.0, Minv, dRdp, 0.0, v);
+
+ *fsg = mu*(gsl_vector_get(v, 1) - fs*gsl_vector_get(v, 0));
+ *ssg = mu*(gsl_vector_get(v, 2) - ss*gsl_vector_get(v, 0));
+
+ gsl_vector_free(v);
+ gsl_vector_free(dRdp);
+
+ return 0;
}
-static double x_dev(struct reflpeak *rp, struct detgeom *det,
- double dx, double dy)
+/* Spot position gradients for panel motions (translation or rotation) */
+int fs_ss_gradient_panel(int param, Reflection *refl, UnitCell *cell,
+ struct detgeom_panel *p, gsl_matrix *Minv,
+ double fs, double ss, double mu,
+ gsl_vector *t, double cx, double cy, double cz,
+ float *fsg, float *ssg)
{
- /* Peak position term */
- double xpk, ypk, xh, yh;
- double fsh, ssh;
- twod_mapping(rp->peak->fs, rp->peak->ss, &xpk, &ypk, rp->panel, dx, dy);
- get_detector_pos(rp->refl, &fsh, &ssh);
- twod_mapping(fsh, ssh, &xh, &yh, rp->panel, dx, dy);
- return xh-xpk;
+ gsl_vector *v;
+ gsl_matrix *gM; /* M^-1 * dM/dx * M^-1 */
+ gsl_matrix *dMdp = gsl_matrix_calloc(3, 3);
+
+ switch ( param ) {
+
+ case GPARAM_DET_TX :
+ gsl_matrix_set(dMdp, 0, 0, 1.0);
+ break;
+
+ case GPARAM_DET_TY :
+ gsl_matrix_set(dMdp, 1, 0, 1.0);
+ break;
+
+ case GPARAM_DET_TZ :
+ gsl_matrix_set(dMdp, 2, 0, 1.0);
+ break;
+
+ case GPARAM_DET_RX :
+ gsl_matrix_set(dMdp, 1, 0, cz-p->pixel_pitch*p->cnz);
+ gsl_matrix_set(dMdp, 2, 0, p->pixel_pitch*p->cny-cy);
+ gsl_matrix_set(dMdp, 1, 1, -p->pixel_pitch*p->fsz);
+ gsl_matrix_set(dMdp, 2, 1, p->pixel_pitch*p->fsy);
+ gsl_matrix_set(dMdp, 1, 2, -p->pixel_pitch*p->ssz);
+ gsl_matrix_set(dMdp, 2, 2, p->pixel_pitch*p->ssy);
+ break;
+
+ case GPARAM_DET_RY :
+ gsl_matrix_set(dMdp, 0, 0, p->pixel_pitch*p->cnz-cz);
+ gsl_matrix_set(dMdp, 2, 0, cx-p->pixel_pitch*p->cnx);
+ gsl_matrix_set(dMdp, 0, 1, p->pixel_pitch*p->fsz);
+ gsl_matrix_set(dMdp, 2, 1, -p->pixel_pitch*p->fsx);
+ gsl_matrix_set(dMdp, 0, 2, p->pixel_pitch*p->ssz);
+ gsl_matrix_set(dMdp, 2, 2, -p->pixel_pitch*p->ssx);
+ break;
+
+ case GPARAM_DET_RZ :
+ gsl_matrix_set(dMdp, 0, 0, cy-p->pixel_pitch*p->cny);
+ gsl_matrix_set(dMdp, 1, 0, p->pixel_pitch*p->cnx-cx);
+ gsl_matrix_set(dMdp, 0, 1, -p->pixel_pitch*p->fsy);
+ gsl_matrix_set(dMdp, 1, 1, p->pixel_pitch*p->fsx);
+ gsl_matrix_set(dMdp, 0, 2, -p->pixel_pitch*p->ssy);
+ gsl_matrix_set(dMdp, 1, 2, p->pixel_pitch*p->ssx);
+ break;
+
+ default:
+ ERROR("Invalid panel gradient %i\n", param);
+ return 1;
+
+ }
+
+ gM = matrix_mult3(Minv, dMdp, Minv);
+ gsl_matrix_free(dMdp);
+
+ v = gsl_vector_calloc(3);
+ gsl_blas_dgemv(CblasNoTrans, -1.0, gM, t, 0.0, v);
+ gsl_vector_free(t);
+ gsl_matrix_free(gM);
+
+ *fsg = mu*(gsl_vector_get(v, 1) - fs*gsl_vector_get(v, 0));
+ *ssg = mu*(gsl_vector_get(v, 2) - ss*gsl_vector_get(v, 0));
+
+ gsl_vector_free(v);
+
+ return 0;
}
-static double y_dev(struct reflpeak *rp, struct detgeom *det,
- double dx, double dy)
+/* Returns the gradient of fs_dev and ss_dev w.r.t. any parameter.
+ * cx,cy,cz are the rotation axis coordinates (only 2 in use at any time)
+ * in metres (not pixels) */
+int fs_ss_gradient(int param, Reflection *refl, UnitCell *cell,
+ struct detgeom_panel *p, gsl_matrix *Minv,
+ double cx, double cy, double cz,
+ float *fsg, float *ssg)
{
- /* Peak position term */
- double xpk, ypk, xh, yh;
- double fsh, ssh;
- twod_mapping(rp->peak->fs, rp->peak->ss, &xpk, &ypk, rp->panel, dx, dy);
- get_detector_pos(rp->refl, &fsh, &ssh);
- twod_mapping(fsh, ssh, &xh, &yh, rp->panel, dx, dy);
- return yh-ypk;
+ signed int h, k, l;
+ double xl, yl, zl, kpred;
+ double asx, asy, asz, bsx, bsy, bsz, csx, csy, csz;
+ gsl_vector *t;
+ gsl_vector *v;
+ gsl_matrix *M;
+ double mu;
+ double fs, ss;
+
+ get_indices(refl, &h, &k, &l);
+ kpred = get_kpred(refl);
+ cell_get_reciprocal(cell, &asx, &asy, &asz,
+ &bsx, &bsy, &bsz,
+ &csx, &csy, &csz);
+ xl = h*asx + k*bsx + l*csx;
+ yl = h*asy + k*bsy + l*csy;
+ zl = h*asz + k*bsz + l*csz;
+
+ /* Set up matrix equation */
+ M = gsl_matrix_alloc(3, 3);
+ v = gsl_vector_alloc(3);
+ t = gsl_vector_alloc(3);
+ if ( (M==NULL) || (v==NULL) || (t==NULL) ) {
+ ERROR("Failed to allocate vectors for gradient calculation\n");
+ return 1;
+ }
+
+ gsl_vector_set(t, 0, xl);
+ gsl_vector_set(t, 1, yl);
+ gsl_vector_set(t, 2, kpred+zl);
+
+ gsl_matrix_set(M, 0, 0, (p->cnx)*p->pixel_pitch);
+ gsl_matrix_set(M, 0, 1, (p->fsx)*p->pixel_pitch);
+ gsl_matrix_set(M, 0, 2, (p->ssx)*p->pixel_pitch);
+ gsl_matrix_set(M, 1, 0, (p->cny)*p->pixel_pitch);
+ gsl_matrix_set(M, 1, 1, (p->fsy)*p->pixel_pitch);
+ gsl_matrix_set(M, 1, 2, (p->ssy)*p->pixel_pitch);
+ gsl_matrix_set(M, 2, 0, (p->cnz)*p->pixel_pitch);
+ gsl_matrix_set(M, 2, 1, (p->fsz)*p->pixel_pitch);
+ gsl_matrix_set(M, 2, 2, (p->ssz)*p->pixel_pitch);
+
+ if ( gsl_linalg_HH_solve(M, t, v) ) {
+ ERROR("Failed to solve gradient equation\n");
+ return 1;
+ }
+ gsl_matrix_free(M);
+
+ mu = 1.0 / gsl_vector_get(v, 0);
+ fs = mu*gsl_vector_get(v, 1);
+ ss = mu*gsl_vector_get(v, 2);
+ gsl_vector_free(v);
+
+ if ( param <= GPARAM_CSZ ) {
+ gsl_vector_free(t);
+ return fs_ss_gradient_physics(param, refl, cell, p,
+ Minv, fs, ss, mu,
+ fsg, ssg);
+ } else {
+ return fs_ss_gradient_panel(param, refl, cell, p,
+ Minv, fs, ss, mu, t,
+ cx, cy, cz,
+ fsg, ssg);
+ }
}
@@ -244,7 +478,6 @@ static int pair_peaks(struct image *image, Crystal *cr,
rps[n].refl = refl;
rps[n].peak = f;
- rps[n].panel = &image->detgeom->panels[f->pn];
n++;
}
@@ -349,8 +582,7 @@ int refine_radius(Crystal *cr, struct image *image)
static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
- struct image *image,
- double *total_x, double *total_y, double *total_z)
+ struct image *image, gsl_matrix **Minvs)
{
int i;
gsl_matrix *M;
@@ -359,6 +591,18 @@ static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
double asx, asy, asz;
double bsx, bsy, bsz;
double csx, csy, csz;
+ const enum gparam rv[] = {
+ GPARAM_ASX,
+ GPARAM_ASY,
+ GPARAM_ASZ,
+ GPARAM_BSX,
+ GPARAM_BSY,
+ GPARAM_BSZ,
+ GPARAM_CSX,
+ GPARAM_CSY,
+ GPARAM_CSZ,
+ };
+ const int num_params = 9;
/* Number of parameters to refine */
M = gsl_matrix_calloc(num_params, num_params);
@@ -367,17 +611,21 @@ static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
for ( i=0; i<n; i++ ) {
int k;
- double gradients[num_params];
+ float fs_gradients[num_params];
+ float ss_gradients[num_params];
+ float r_gradients[num_params];
double w;
- /* Excitation error terms */
- w = EXC_WEIGHT * rps[i].Ih;
-
+ /* Calculate all gradients for this parameter */
for ( k=0; k<num_params; k++ ) {
- gradients[k] = r_gradient(cell, rv[k], rps[i].refl,
- image);
+ int pn = rps[i].peak->pn;
+ r_gradients[k] = r_gradient(rv[k], rps[i].refl, cell, image->lambda);
+ fs_ss_gradient(rv[k], rps[i].refl, cell, &image->detgeom->panels[pn],
+ Minvs[pn], 0, 0, 0, &fs_gradients[k], &ss_gradients[k]);
}
+ /* Excitation error terms */
+ w = EXC_WEIGHT * rps[i].Ih;
for ( k=0; k<num_params; k++ ) {
int g;
@@ -390,7 +638,7 @@ static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
/* Matrix is symmetric */
if ( g > k ) continue;
- M_c = w * gradients[g] * gradients[k];
+ M_c = w * r_gradients[g] * r_gradients[k];
M_curr = gsl_matrix_get(M, k, g);
gsl_matrix_set(M, k, g, M_curr + M_c);
gsl_matrix_set(M, g, k, M_curr + M_c);
@@ -398,18 +646,13 @@ static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
}
v_c = w * r_dev(&rps[i]);
- v_c *= -gradients[k];
+ v_c *= -r_gradients[k];
v_curr = gsl_vector_get(v, k);
gsl_vector_set(v, k, v_curr + v_c);
}
- /* Positional x terms */
- for ( k=0; k<num_params; k++ ) {
- gradients[k] = x_gradient(rv[k], rps[i].refl, cell,
- rps[i].panel);
- }
-
+ /* Positional fs terms */
for ( k=0; k<num_params; k++ ) {
int g;
@@ -422,26 +665,21 @@ static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
/* Matrix is symmetric */
if ( g > k ) continue;
- M_c = gradients[g] * gradients[k];
+ M_c = fs_gradients[g] * fs_gradients[k];
M_curr = gsl_matrix_get(M, k, g);
gsl_matrix_set(M, k, g, M_curr + M_c);
gsl_matrix_set(M, g, k, M_curr + M_c);
}
- v_c = x_dev(&rps[i], image->detgeom, *total_x, *total_y);
- v_c *= -gradients[k];
+ v_c = fs_dev(&rps[i], image->detgeom);
+ v_c *= -fs_gradients[k];
v_curr = gsl_vector_get(v, k);
gsl_vector_set(v, k, v_curr + v_c);
}
- /* Positional y terms */
- for ( k=0; k<num_params; k++ ) {
- gradients[k] = y_gradient(rv[k], rps[i].refl, cell,
- rps[i].panel);
- }
-
+ /* Positional ss terms */
for ( k=0; k<num_params; k++ ) {
int g;
@@ -454,15 +692,15 @@ static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
/* Matrix is symmetric */
if ( g > k ) continue;
- M_c = gradients[g] * gradients[k];
+ M_c = ss_gradients[g] * ss_gradients[k];
M_curr = gsl_matrix_get(M, k, g);
gsl_matrix_set(M, k, g, M_curr + M_c);
gsl_matrix_set(M, g, k, M_curr + M_c);
}
- v_c = y_dev(&rps[i], image->detgeom, *total_x, *total_y);
- v_c *= -gradients[k];
+ v_c = ss_dev(&rps[i], image->detgeom);
+ v_c *= -ss_gradients[k];
v_curr = gsl_vector_get(v, k);
gsl_vector_set(v, k, v_curr + v_c);
@@ -472,14 +710,8 @@ static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
int k;
for ( k=0; k<num_params; k++ ) {
- double M_curr;
- M_curr = gsl_matrix_get(M, k, k);
- if ( (rv[k] == GPARAM_DETX) || (rv[k] == GPARAM_DETY) ) {
- M_curr += 10.0;
- } else {
- M_curr += 1e-18;
- }
- gsl_matrix_set(M, k, k, M_curr);
+ double M_curr = gsl_matrix_get(M, k, k);
+ gsl_matrix_set(M, k, k, M_curr+1e-7);
}
//show_matrix_eqn(M, v);
@@ -513,9 +745,6 @@ static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
csx += gsl_vector_get(shifts, 6);
csy += gsl_vector_get(shifts, 7);
csz += gsl_vector_get(shifts, 8);
- *total_x += gsl_vector_get(shifts, 9);
- *total_y += gsl_vector_get(shifts, 10);
- *total_z += 0.0;
cell_set_reciprocal(cell, asx, asy, asz, bsx, bsy, bsz, csx, csy, csz);
@@ -527,8 +756,7 @@ static int iterate(struct reflpeak *rps, int n, UnitCell *cell,
}
-static double pred_residual(struct reflpeak *rps, int n, struct detgeom *det,
- double dx, double dy)
+static double pred_residual(struct reflpeak *rps, int n, struct detgeom *det)
{
int i;
double res = 0.0;
@@ -542,13 +770,13 @@ static double pred_residual(struct reflpeak *rps, int n, struct detgeom *det,
r = 0.0;
for ( i=0; i<n; i++ ) {
- r += pow(x_dev(&rps[i], det, dx, dy), 2.0);
+ r += pow(det->panels[rps[i].peak->pn].pixel_pitch*fs_dev(&rps[i], det), 2.0);
}
res += r;
r = 0.0;
for ( i=0; i<n; i++ ) {
- r += pow(y_dev(&rps[i], det, dx, dy), 2.0);
+ r += pow(det->panels[rps[i].peak->pn].pixel_pitch*ss_dev(&rps[i], det), 2.0);
}
res += r;
@@ -569,18 +797,15 @@ static void free_rps_noreflist(struct reflpeak *rps, int n)
}
-int refine_prediction(struct image *image, Crystal *cr)
+int refine_prediction(struct image *image, Crystal *cr, Mille *mille)
{
int n;
int i;
struct reflpeak *rps;
double max_I;
RefList *reflist;
- double total_x = 0.0;
- double total_y = 0.0;
- double total_z = 0.0;
- double orig_shift_x, orig_shift_y;
char tmp[256];
+ gsl_matrix **Minvs;
rps = malloc(image_feature_count(image->features)
* sizeof(struct reflpeak));
@@ -595,9 +820,7 @@ int refine_prediction(struct image *image, Crystal *cr)
}
crystal_set_reflections(cr, reflist);
- crystal_get_det_shift(cr, &total_x, &total_y);
- orig_shift_x = total_x;
- orig_shift_y = total_y;
+ Minvs = make_panel_minvs(image->detgeom);
/* Normalise the intensities to max 1 */
max_I = -INFINITY;
@@ -620,29 +843,36 @@ int refine_prediction(struct image *image, Crystal *cr)
}
//STATUS("Initial residual = %e\n",
- // pred_residual(rps, n, image->detgeom, total_x, total_y));
+ // pred_residual(rps, n, image->detgeom));
- /* Refine */
- for ( i=0; i<MAX_CYCLES; i++ ) {
+ /* Refine (max 10 cycles) */
+ for ( i=0; i<10; i++ ) {
update_predictions(cr);
- if ( iterate(rps, n, crystal_get_cell(cr), image,
- &total_x, &total_y, &total_z) )
+ if ( iterate(rps, n, crystal_get_cell(cr), image, Minvs) )
{
crystal_set_reflections(cr, NULL);
return 1;
}
- crystal_set_det_shift(cr, total_x, total_y);
//STATUS("Residual after %i = %e\n", i,
- // pred_residual(rps, n, image->detgeom, total_x, total_y));
+ // pred_residual(rps, n, image->detgeom));
}
//STATUS("Final residual = %e\n",
- // pred_residual(rps, n, image->detgeom, total_x, total_y));
+ // pred_residual(rps, n, image->detgeom));
snprintf(tmp, 255, "predict_refine/final_residual = %e",
- pred_residual(rps, n, image->detgeom, total_x, total_y));
+ pred_residual(rps, n, image->detgeom));
crystal_add_notes(cr, tmp);
- crystal_set_det_shift(cr, total_x, total_y);
+ if ( mille != NULL ) {
+ profile_start("mille-calc");
+ write_mille(mille, n, crystal_get_cell(cr), rps, image, Minvs);
+ profile_end("mille-calc");
+ }
+
+ for ( i=0; i<image->detgeom->n_panels; i++ ) {
+ gsl_matrix_free(Minvs[i]);
+ }
+ free(Minvs);
crystal_set_reflections(cr, NULL);
reflist_free(reflist);
@@ -650,9 +880,17 @@ int refine_prediction(struct image *image, Crystal *cr)
n = pair_peaks(image, cr, NULL, rps);
free_rps_noreflist(rps, n);
if ( n < 10 ) {
- crystal_set_det_shift(cr, orig_shift_x, orig_shift_y);
+ if ( mille != NULL ) {
+ crystfel_mille_delete_last_record(mille);
+ }
return 1;
}
+ if ( mille != NULL ) {
+ profile_start("mille-write");
+ crystfel_mille_write_record(mille);
+ profile_end("mille-write");
+ }
+
return 0;
}
diff --git a/libcrystfel/src/predict-refine.h b/libcrystfel/src/predict-refine.h
index 5607e356..1e72a048 100644
--- a/libcrystfel/src/predict-refine.h
+++ b/libcrystfel/src/predict-refine.h
@@ -29,17 +29,64 @@
#ifndef PREDICT_REFINE_H
#define PREDICT_REFINE_H
+#include <gsl/gsl_matrix.h>
+
+struct reflpeak;
+
+/** Enumeration of parameters which may want to be refined */
+enum gparam {
+ GPARAM_ASX,
+ GPARAM_ASY,
+ GPARAM_ASZ,
+ GPARAM_BSX,
+ GPARAM_BSY,
+ GPARAM_BSZ,
+ GPARAM_CSX,
+ GPARAM_CSY,
+ GPARAM_CSZ,
+ GPARAM_DET_TX,
+ GPARAM_DET_TY,
+ GPARAM_DET_TZ,
+ GPARAM_DET_RX, /* Detector panel (group) rotation about +x */
+ GPARAM_DET_RY, /* Detector panel (group) rotation about +y */
+ GPARAM_DET_RZ, /* Detector panel (group) rotation about +z */
+};
+
+
+/* Weighting of excitation error term (m^-1) compared to position term (pixels) */
+#define EXC_WEIGHT (0.5e-7)
+
+
#include "crystal.h"
+#include "crystfel-mille.h"
-struct image;
+struct reflpeak {
+ Reflection *refl;
+ struct imagefeature *peak;
+ double Ih; /* normalised */
+};
/**
* \file predict-refine.h
* Prediction refinement: refinement of indexing solutions before integration.
*/
-extern int refine_prediction(struct image *image, Crystal *cr);
+extern int refine_prediction(struct image *image, Crystal *cr, Mille *mille);
+
extern int refine_radius(Crystal *cr, struct image *image);
+extern double r_dev(struct reflpeak *rp);
+
+extern double fs_dev(struct reflpeak *rp, struct detgeom *det);
+
+extern double ss_dev(struct reflpeak *rp, struct detgeom *det);
+
+extern double r_gradient(int param, Reflection *refl, UnitCell *cell,
+ double wavelength);
+
+extern int fs_ss_gradient(int param, Reflection *refl, UnitCell *cell,
+ struct detgeom_panel *p, gsl_matrix *panel_Minv,
+ double cx, double cy, double cz,
+ float *fsg, float *ssg);
#endif /* PREDICT_REFINE_H */
diff --git a/libcrystfel/src/utils.c b/libcrystfel/src/utils.c
index b69a816d..10e4c38e 100644
--- a/libcrystfel/src/utils.c
+++ b/libcrystfel/src/utils.c
@@ -97,6 +97,77 @@ void show_matrix(gsl_matrix *M)
}
+void show_vector(gsl_vector *v)
+{
+ int i;
+
+ for ( i=0; i<v->size; i++ ) {
+ STATUS("[ ");
+ STATUS("%+9.3e ", gsl_vector_get(v, i));
+ STATUS("]\n");
+ }
+}
+
+
+gsl_matrix *matrix_mult(gsl_matrix *A, gsl_matrix *B)
+{
+ gsl_matrix *r = gsl_matrix_calloc(A->size1, A->size2);
+ gsl_blas_dgemm(CblasNoTrans, CblasNoTrans, 1.0, A, B, 0.0, r);
+ return r;
+}
+
+
+gsl_matrix *matrix_mult3(gsl_matrix *A, gsl_matrix *B, gsl_matrix *C)
+{
+ gsl_matrix *tmp = matrix_mult(B, C);
+ gsl_matrix *r = matrix_mult(A, tmp);
+ gsl_matrix_free(tmp);
+ return r;
+}
+
+
+gsl_matrix *matrix_invert(gsl_matrix *m)
+{
+ gsl_permutation *perm;
+ gsl_matrix *inv;
+ int s;
+
+ perm = gsl_permutation_alloc(m->size1);
+ if ( perm == NULL ) {
+ ERROR("Couldn't allocate permutation\n");
+ gsl_matrix_free(m);
+ return NULL;
+ }
+
+ inv = gsl_matrix_alloc(m->size1, m->size2);
+ if ( inv == NULL ) {
+ ERROR("Couldn't allocate inverse\n");
+ gsl_matrix_free(m);
+ gsl_permutation_free(perm);
+ return NULL;
+ }
+
+ if ( gsl_linalg_LU_decomp(m, perm, &s) ) {
+ ERROR("Couldn't decompose matrix\n");
+ gsl_matrix_free(m);
+ gsl_permutation_free(perm);
+ return NULL;
+ }
+
+ if ( gsl_linalg_LU_invert(m, perm, inv) ) {
+ ERROR("Couldn't invert cell matrix:\n");
+ gsl_matrix_free(m);
+ gsl_permutation_free(perm);
+ return NULL;
+ }
+
+ gsl_permutation_free(perm);
+ gsl_matrix_free(m);
+
+ return inv;
+}
+
+
static int check_eigen(gsl_vector *e_val, int verbose)
{
int i;
@@ -399,6 +470,15 @@ void progress_bar(int val, int total, const char *text)
}
+void rotate2d(double *x, double *y, double cx, double cy, double ang)
+{
+ double nx, ny;
+ nx = cx + (*x-cx)*cos(ang) - (*y-cy)*sin(ang);
+ ny = cy + (*x-cx)*sin(ang) + (*y-cy)*cos(ang);
+ *x = nx; *y = ny;
+}
+
+
double random_flat(gsl_rng *rng, double max)
{
return max * gsl_rng_uniform(rng);
diff --git a/libcrystfel/src/utils.h b/libcrystfel/src/utils.h
index 9683039e..67940ad4 100644
--- a/libcrystfel/src/utils.h
+++ b/libcrystfel/src/utils.h
@@ -74,8 +74,12 @@ extern "C" {
extern void show_matrix_eqn(gsl_matrix *M, gsl_vector *v);
extern void show_matrix(gsl_matrix *M);
+extern void show_vector(gsl_vector *M);
extern gsl_vector *solve_svd(gsl_vector *v, gsl_matrix *M, int *n_filt,
int verbose);
+extern gsl_matrix *matrix_mult2(gsl_matrix *A, gsl_matrix *B);
+extern gsl_matrix *matrix_mult3(gsl_matrix *A, gsl_matrix *B, gsl_matrix *C);
+extern gsl_matrix *matrix_invert(gsl_matrix *m);
extern size_t notrail(char *s);
extern int convert_int(const char *str, int *pval);
@@ -164,6 +168,8 @@ static inline int within_tolerance(double a, double b, double percent)
return 0;
}
+extern void rotate2d(double *x, double *y, double cx, double cy, double ang);
+
/* ----------------------------- Useful macros ------------------------------ */
diff --git a/meson.build b/meson.build
index c07a326a..27fa4af0 100644
--- a/meson.build
+++ b/meson.build
@@ -155,6 +155,24 @@ executable('whirligig',
install: true,
install_rpath: crystfel_rpath)
+# align_detector
+executable('align_detector',
+ ['src/align_detector.c', versionc],
+ dependencies: [mdep, libcrystfeldep],
+ install: true,
+ install_rpath: '$ORIGIN/../lib64/:$ORIGIN/../lib')
+
+# Get 'pede' from the subproject (needed for align_detector)
+millepede_proj = subproject('millepede')
+pede_exe = millepede_proj.get_variable('pede')
+
+# adjust_detector
+adjust_detector = executable('adjust_detector',
+ ['src/adjust_detector.c', versionc],
+ dependencies: [mdep, libcrystfeldep],
+ install: true,
+ install_rpath: '$ORIGIN/../lib64/:$ORIGIN/../lib')
+
# indexamajig
indexamajig_sources = ['src/indexamajig.c', 'src/im-sandbox.c',
'src/process_image.c',
@@ -260,6 +278,24 @@ subdir('tests')
# ************************ Manual pages ************************
+
+pandoc = find_program('pandoc', required: false)
+
+pandoc_pages = ['indexamajig.1.md',
+ 'adjust_detector.1.md']
+
+if pandoc.found()
+ foreach page : pandoc_pages
+ custom_target(page,
+ input: join_paths('doc/man', page),
+ output: '@BASENAME@',
+ command: [pandoc, '@INPUT@','-o', '@OUTPUT@',
+ '-s', '-f', 'markdown-smart', '-t', 'man'],
+ install: true,
+ install_dir: join_paths(get_option('mandir'), 'man1'))
+ endforeach
+endif
+
install_man(['doc/man/ambigator.1',
'doc/man/cell_explorer.1',
'doc/man/cell_tool.1',
@@ -268,7 +304,6 @@ install_man(['doc/man/ambigator.1',
'doc/man/crystfel.7',
'doc/man/crystfel_geometry.5',
'doc/man/get_hkl.1',
- 'doc/man/indexamajig.1',
'doc/man/list_events.1',
'doc/man/list_events.1',
'doc/man/make_pixelmap.1',
diff --git a/scripts/detector-shift b/scripts/detector-shift
index b16d788b..083188f6 100755
--- a/scripts/detector-shift
+++ b/scripts/detector-shift
@@ -1,174 +1,14 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-#
-# Determine mean detector shift based on prediction refinement results
-#
-# Copyright © 2015-2020 Deutsches Elektronen-Synchrotron DESY,
-# a research centre of the Helmholtz Association.
-#
-# Author:
-# 2015-2018 Thomas White <taw@physics.org>
-# 2016 Mamoru Suzuki <mamoru.suzuki@protein.osaka-u.ac.jp>
-# 2018 Chun Hong Yoon
-#
-
-import sys
-import os
-import re
-import numpy as np
-import matplotlib.pyplot as plt
-
-infiles = []
-
-if sys.argv[1] == "--":
- have_geom = 0
- infiles += sys.argv[2:]
-else:
- if len(sys.argv) > 2:
- infiles += [sys.argv[1]]
- geom = sys.argv[2]
- have_geom = 1
- else:
- have_geom = 0
- infiles += sys.argv[1:]
-
-# Determine the mean shifts
-x_shifts = []
-y_shifts = []
-z_shifts = []
-
-prog1 = re.compile("^predict_refine/det_shift\sx\s=\s([0-9\.\-]+)\sy\s=\s([0-9\.\-]+)\smm$")
-prog2 = re.compile("^predict_refine/clen_shift\s=\s([0-9\.\-]+)\smm$")
-
-for file in infiles:
-
- print("Reading "+file)
- if file == "-":
- f = sys.stdin
- else:
- f = open(file, 'r')
-
- while True:
-
- fline = f.readline()
- if not fline:
- break
-
- match = prog1.match(fline)
- if match:
- xshift = float(match.group(1))
- yshift = float(match.group(2))
- x_shifts.append(xshift)
- y_shifts.append(yshift)
-
- match = prog2.match(fline)
- if match:
- zshift = float(match.group(1))
- z_shifts.append(zshift)
-
- f.close()
-
-mean_x = sum(x_shifts) / len(x_shifts)
-mean_y = sum(y_shifts) / len(y_shifts)
-print('Mean shifts: dx = {:.2} mm, dy = {:.2} mm'.format(mean_x,mean_y))
-print('Shifts will be applied to geometry file when you close the graph window')
-print('Click anywhere on the graph to override the detector shift')
-
-def plotNewCentre(x, y):
- circle1 = plt.Circle((x,y),.1,color='r',fill=False)
- fig.gca().add_artist(circle1)
- plt.plot(x, y, 'b8', color='m')
- plt.grid(True)
-
-def onclick(event):
- print('New shifts: dx = {:.2} mm, dy = {:.2} mm'.format(event.xdata, event.ydata))
- print('Shifts will be applied to geometry file when you close the graph window')
- mean_x = event.xdata
- mean_y = event.ydata
- plotNewCentre(mean_x, mean_y)
-
-nbins = 200
-H, xedges, yedges = np.histogram2d(x_shifts,y_shifts,bins=nbins)
-H = np.rot90(H)
-H = np.flipud(H)
-Hmasked = np.ma.masked_where(H==0,H)
-
-# Plot 2D histogram using pcolor
-plt.ion()
-fig2 = plt.figure()
-cid = fig2.canvas.mpl_connect('button_press_event', onclick)
-plt.pcolormesh(xedges,yedges,Hmasked)
-plt.title('Detector shifts according to prediction refinement')
-plt.xlabel('x shift / mm')
-plt.ylabel('y shift / mm')
-plt.plot(0, 0, 'bH', color='c')
-fig = plt.gcf()
-cbar = plt.colorbar()
-cbar.ax.set_ylabel('Counts')
-plotNewCentre(mean_x, mean_y)
-plt.show(block=True)
-
-# Apply shifts to geometry
-if have_geom:
-
- out = os.path.splitext(geom)[0]+'-predrefine.geom'
- print('Applying corrections to {}, output filename {}'.format(geom,out))
- g = open(geom, 'r')
- h = open(out, 'w')
- panel_resolutions = {}
-
- prog1 = re.compile("^\s*res\s+=\s+([0-9\.]+)\s")
- prog2 = re.compile("^\s*(.*)\/res\s+=\s+([0-9\.]+)\s")
- prog3 = re.compile("^\s*(.*)\/corner_x\s+=\s+([0-9\.\-]+)\s")
- prog4 = re.compile("^\s*(.*)\/corner_y\s+=\s+([0-9\.\-]+)\s")
- default_res = 0
- while True:
-
- fline = g.readline()
- if not fline:
- break
-
- match = prog1.match(fline)
- if match:
- default_res = float(match.group(1))
- h.write(fline)
- continue
-
- match = prog2.match(fline)
- if match:
- panel = match.group(1)
- panel_res = float(match.group(2))
- default_res = panel_res
- panel_resolutions[panel] = panel_res
- h.write(fline)
- continue
-
- match = prog3.match(fline)
- if match:
- panel = match.group(1)
- panel_cnx = float(match.group(2))
- if panel in panel_resolutions:
- res = panel_resolutions[panel]
- else:
- res = default_res
- print('Using default resolution ({} px/m) for panel {}'.format(res, panel))
- h.write('%s/corner_x = %f\n' % (panel,panel_cnx+(mean_x*res*1e-3)))
- continue
-
- match = prog4.match(fline)
- if match:
- panel = match.group(1)
- panel_cny = float(match.group(2))
- if panel in panel_resolutions:
- res = panel_resolutions[panel]
- else:
- res = default_res
- print('Using default resolution ({} px/m) for panel {}'.format(res, panel))
- h.write('%s/corner_y = %f\n' % (panel,panel_cny+(mean_y*res*1e-3)))
- continue
-
- h.write(fline)
-
- g.close()
- h.close()
-
+#!/bin/sh
+
+echo -------------------------------------------------------------
+echo
+echo In this CrystFEL version, all detector geometry refinement is
+echo done using the new program align_detector.
+echo
+echo To update the detector center based on the indexing results,
+echo add --mille to the indexamajig arguments, then run the following:
+echo
+echo align_detector -i old.geom -o new.geom --level=0 mille-data-*.bin
+echo
+echo -------------------------------------------------------------
+exit 1
diff --git a/src/adjust_detector.c b/src/adjust_detector.c
new file mode 100644
index 00000000..c2b47a02
--- /dev/null
+++ b/src/adjust_detector.c
@@ -0,0 +1,245 @@
+/*
+ * adjust_detector.c
+ *
+ * Move detector panels
+ *
+ * Copyright © 2023 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2023 Thomas White <taw@physics.org>
+ *
+ * This file is part of CrystFEL.
+ *
+ * CrystFEL is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CrystFEL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <datatemplate.h>
+#include <utils.h>
+
+#include "version.h"
+
+
+static void show_syntax(const char *s)
+{
+ printf("Syntax: %s [options] -g <input.geom> -o <output.geom> [...]\n", s);
+}
+
+
+static void show_help(const char *s)
+{
+ show_syntax(s);
+ printf("\nMove detector panels.\n"
+ "\n"
+ " -g, --geometry=file Input geometry file\n"
+ " -o, --output=file Output geometry file\n"
+ " -p, --panel=p Panel (or group) to move\n"
+ " --mm Interpret shifts as mm, not px\n"
+ "\n"
+ " --rotx Rotation around x-axis (deg)\n"
+ " --roty Rotation around y-axis (deg)\n"
+ " --rotz Rotation around z-axis (deg)\n"
+ " --shiftx Shift in x direction (px, see above)\n"
+ " --shifty Shift in y direction (px, see above)\n"
+ " --shiftz Shift in z direction (px, see above)\n"
+ "\n"
+ " -h, --help Display this help message\n"
+ " --version Print version number and exit\n");
+}
+
+
+static double parse_double(const char *str, char complain)
+{
+ double v;
+ char *rval;
+
+ errno = 0;
+ v = strtod(optarg, &rval);
+ if ( *rval != '\0' ) {
+ ERROR("Invalid value for -%c.\n", complain);
+ exit(1);
+ }
+
+ return v;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int c;
+ DataTemplate *dtempl;
+ char *in_geom = NULL;
+ char *out_geom = NULL;
+ double x_shift = 0.0;
+ double y_shift = 0.0;
+ double z_shift = 0.0;
+ double x_rot = 0.0;
+ double y_rot = 0.0;
+ double z_rot = 0.0;
+ int mm = 0;
+ char *group = strdup("all");
+ int r;
+
+ /* Long options */
+ const struct option longopts[] = {
+
+ {"help", 0, NULL, 'h'},
+ {"verbose", 0, NULL, 'v'},
+
+ {"version", 0, NULL, 'V'},
+ {"input", 1, NULL, 'g'},
+ {"output", 1, NULL, 'o'},
+ {"panel", 1, NULL, 'p'},
+ {"mm", 0, NULL, 3},
+
+ {"shiftx", 1, NULL, 11},
+ {"shifty", 1, NULL, 12},
+ {"shiftz", 1, NULL, 13},
+ {"rotx", 1, NULL, 14},
+ {"roty", 1, NULL, 15},
+ {"rotz", 1, NULL, 16},
+
+ {0, 0, NULL, 0}
+ };
+
+ /* Short options */
+ while ((c = getopt_long(argc, argv, "hVo:g:i:l:p:",
+ longopts, NULL)) != -1)
+ {
+
+ switch (c) {
+
+ case 'h' :
+ show_help(argv[0]);
+ return 0;
+
+ case 'V' :
+ printf("CrystFEL: %s\n", crystfel_version_string());
+ printf("%s\n", crystfel_licence_string());
+ return 0;
+
+ case 'g' :
+ case 'i' :
+ in_geom = strdup(optarg);
+ break;
+
+ case 'o' :
+ out_geom = strdup(optarg);
+ break;
+
+ case 'p' :
+ free(group);
+ group = strdup(optarg);
+ break;
+
+ case 3 :
+ mm = 1;
+ break;
+
+ case 11 :
+ x_shift = parse_double(optarg, 'x');
+ break;
+
+ case 12 :
+ y_shift = parse_double(optarg, 'y');
+ break;
+
+ case 13 :
+ z_shift = parse_double(optarg, 'z');
+ break;
+
+ case 14 :
+ x_rot = parse_double(optarg, 'a');
+ break;
+
+ case 15 :
+ y_rot = parse_double(optarg, 'b');
+ break;
+
+ case 16 :
+ z_rot = parse_double(optarg, 'c');
+ break;
+
+ case 0 :
+ break;
+
+ case '?' :
+ break;
+
+ default :
+ ERROR("Unhandled option '%c'\n", c);
+ break;
+
+ }
+
+ }
+
+ if ( (in_geom == NULL) || (out_geom == NULL) ) {
+ show_syntax(argv[0]);
+ return 1;
+ }
+
+ dtempl = data_template_new_from_file(in_geom);
+ if ( dtempl == NULL ) return 1;
+
+ if ( mm ) {
+ r = data_template_translate_group_m(dtempl, group,
+ x_shift * 1e-3,
+ y_shift * 1e-3,
+ z_shift * 1e-3);
+ } else {
+ r = data_template_translate_group_px(dtempl, group,
+ x_shift, y_shift, z_shift);
+ }
+
+ if ( r ) {
+ ERROR("Failed to translate group.\n");
+ return 1;
+ }
+
+ if ( data_template_rotate_group(dtempl, group, deg2rad(x_rot), 'x') ) {
+ ERROR("Failed to rotate group around x.\n");
+ return 1;
+ }
+
+ if ( data_template_rotate_group(dtempl, group, deg2rad(y_rot), 'y') ) {
+ ERROR("Failed to rotate group around y.\n");
+ return 1;
+ }
+
+ if ( data_template_rotate_group(dtempl, group, deg2rad(z_rot), 'z') ) {
+ ERROR("Failed to rotate group around z.\n");
+ return 1;
+ }
+
+ if ( data_template_write_to_file(dtempl, out_geom) ) {
+ ERROR("Failed to save geometry file.\n");
+ return 1;
+ }
+
+ data_template_free(dtempl);
+
+ return 0;
+}
diff --git a/src/align_detector.c b/src/align_detector.c
new file mode 100644
index 00000000..c4059f2c
--- /dev/null
+++ b/src/align_detector.c
@@ -0,0 +1,512 @@
+/*
+ * align_detector.c
+ *
+ * Align detector using Millepede
+ *
+ * Copyright © 2023 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2023 Thomas White <taw@physics.org>
+ *
+ * This file is part of CrystFEL.
+ *
+ * CrystFEL is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CrystFEL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <datatemplate.h>
+#include <utils.h>
+#include <predict-refine.h>
+#include <crystfel-mille.h>
+
+#include "version.h"
+
+
+static void show_syntax(const char *s)
+{
+ printf("Syntax: %s [options] -g <input.geom> -o <output.geom> <mille-0.dat> [...]\n", s);
+}
+
+
+static void show_help(const char *s)
+{
+ show_syntax(s);
+ printf("\nRefine detector geometry using Millepede.\n"
+ "\n"
+ " -g, --geometry=file Input geometry file\n"
+ " -o, --output=file Output geometry file\n"
+ " -l, --level=n Alignment hierarchy level\n"
+ " --out-of-plane Also refine out of x/y plane\n"
+ "\n"
+ " -h, --help Display this help message\n"
+ " --version Print version number and exit\n");
+}
+
+
+static const char *str_param(enum gparam param)
+{
+ switch ( param ) {
+ case GPARAM_DET_TX : return "x-translation";
+ case GPARAM_DET_TY : return "y-translation";
+ case GPARAM_DET_TZ : return "z-translation";
+ case GPARAM_DET_RX : return "x-rotation";
+ case GPARAM_DET_RY : return "y-rotation";
+ case GPARAM_DET_RZ : return "z-rotation";
+ default : return "(unknown)";
+ }
+}
+
+
+static const char *group_serial_to_name(int serial,
+ struct dg_group_info *groups,
+ int n_groups)
+{
+ int i;
+
+ for ( i=0; i<n_groups; i++ ) {
+ if ( groups[i].serial == serial ) return groups[i].name;
+ }
+
+ return NULL;
+}
+
+
+static struct dg_group_info *find_group(struct dg_group_info *groups,
+ int n_groups, const char *name)
+{
+ int i;
+
+ for ( i=0; i<n_groups; i++ ) {
+ if ( strcmp(name, groups[i].name) == 0 ) return &groups[i];
+ }
+
+ return NULL;
+}
+
+static int ipow(int base, int ex)
+{
+ int i;
+ int v = 1;
+ for ( i=0; i<ex; i++ ) {
+ v *= base;
+ }
+ return v;
+}
+
+
+static int is_child(struct dg_group_info *parent, struct dg_group_info *child)
+{
+ int parent_serial;
+
+ if ( 1+parent->hierarchy_level != child->hierarchy_level ) return 0;
+
+ parent_serial = child->serial % ipow(100, child->hierarchy_level);
+ if ( parent->serial != parent_serial ) return 0;
+
+ return 1;
+}
+
+
+static void write_zero_sum(FILE *fh, struct dg_group_info *g,
+ struct dg_group_info *groups, int n_groups,
+ enum gparam p)
+{
+ int i;
+ int found = 0;
+
+ for ( i=0; i<n_groups; i++ ) {
+ if ( is_child(g, &groups[i]) ) {
+ if ( !found ) {
+ fprintf(fh, "Constraint 0\n");
+ found = 1;
+ }
+ fprintf(fh, "%i 1\n", mille_label(groups[i].serial, p));
+ }
+ }
+
+ if ( found ) {
+ fprintf(fh, "\n");
+ }
+}
+
+
+static int make_zero_sum(FILE *fh, struct dg_group_info *groups, int n_groups,
+ const char *group_name, int level, int out_of_plane)
+{
+ int i;
+ struct dg_group_info *g = find_group(groups, n_groups, group_name);
+
+ if ( g == NULL ) {
+ ERROR("Couldn't find group '%s'\n", group_name);
+ return 1;
+ }
+
+ /* Millepede doesn't like excessive constraints */
+ if ( g->hierarchy_level >= level ) return 0;
+
+ fprintf(fh, "! Hierarchy constraints for group %s\n", group_name);
+ write_zero_sum(fh, g, groups, n_groups, GPARAM_DET_TX);
+ write_zero_sum(fh, g, groups, n_groups, GPARAM_DET_TY);
+ if ( out_of_plane ) {
+ write_zero_sum(fh, g, groups, n_groups, GPARAM_DET_TZ);
+ write_zero_sum(fh, g, groups, n_groups, GPARAM_DET_RX);
+ write_zero_sum(fh, g, groups, n_groups, GPARAM_DET_RY);
+ }
+ write_zero_sum(fh, g, groups, n_groups, GPARAM_DET_RZ);
+ fprintf(fh, "\n");
+
+ for ( i=0; i<n_groups; i++ ) {
+ if ( is_child(g, &groups[i]) ) {
+ if ( make_zero_sum(fh, groups, n_groups, groups[i].name,
+ level, out_of_plane) ) return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static void print_time_warning()
+{
+ ERROR("\n\n");
+ ERROR("WARNING: The modification times of the Mille data files differ "
+ "by more than one minute.\n");
+ ERROR("This suggests that they might have come from different runs of "
+ "indexamajig.\n");
+ ERROR("If this is not the case, then there is no problem and you can "
+ "ignore this warning.\n");
+ ERROR("However, if Mille files have been mixed up between indexamajig "
+ "runs, then the detector alignment results will be wrong.\n");
+ ERROR("\n\n");
+}
+
+
+static int different(time_t a, time_t b)
+{
+ if ( a-b > 60 ) return 1;
+ if ( b-a > 60 ) return 1;
+ return 0;
+}
+
+
+int main(int argc, char *argv[])
+{
+ int c;
+ char *in_geom = NULL;
+ char *out_geom = NULL;
+ int level = 0;
+ char *rval;
+ int i;
+ FILE *fh;
+ DataTemplate *dtempl;
+ struct dg_group_info *groups;
+ int n_groups;
+ int r;
+ char line[256];
+ time_t first_mtime = 0;
+ int warn_times = 0;
+ int out_of_plane = 0;
+
+ /* Long options */
+ const struct option longopts[] = {
+
+ {"help", 0, NULL, 'h'},
+ {"verbose", 0, NULL, 'v'},
+
+ {"version", 0, NULL, 'V'},
+ {"input", 1, NULL, 'g'},
+ {"output", 1, NULL, 'o'},
+ {"level", 1, NULL, 'l'},
+ {"out-of-plane", 0, &out_of_plane, 1},
+
+ {0, 0, NULL, 0}
+ };
+
+ /* Short options */
+ while ((c = getopt_long(argc, argv, "hVo:g:i:l:",
+ longopts, NULL)) != -1)
+ {
+
+ switch (c) {
+
+ case 'h' :
+ show_help(argv[0]);
+ return 0;
+
+ case 'V' :
+ printf("CrystFEL: %s\n", crystfel_version_string());
+ printf("%s\n", crystfel_licence_string());
+ return 0;
+
+ case 'g' :
+ case 'i' :
+ in_geom = strdup(optarg);
+ break;
+
+ case 'o' :
+ out_geom = strdup(optarg);
+ break;
+
+ case 'l' :
+ errno = 0;
+ level = strtol(optarg, &rval, 10);
+ if ( *rval != '\0' ) {
+ ERROR("Invalid value for --level.\n");
+ return 1;
+ }
+
+ case 0 :
+ break;
+
+ case '?' :
+ break;
+
+ default :
+ ERROR("Unhandled option '%c'\n", c);
+ break;
+
+ }
+
+ }
+
+ if ( (in_geom == NULL) || (out_geom == NULL) || (argc == optind) ) {
+ show_syntax(argv[0]);
+ return 1;
+ }
+
+ fh = fopen("millepede.txt", "w");
+ if ( fh == NULL ) {
+ ERROR("Couldn't open Millepede steering file\n");
+ return 1;
+ }
+
+ for ( i=optind; i<argc; i++ ) {
+
+ struct stat statbuf;
+
+ r = stat(argv[i], &statbuf);
+ if ( r != 0 ) {
+ ERROR("File '%s' not found\n", argv[i]);
+ return 1;
+ }
+
+ if ( i == optind ) {
+ first_mtime = statbuf.st_mtime;
+ } else {
+ if ( different(statbuf.st_mtime, first_mtime) ) {
+ warn_times = 1;
+ }
+ }
+
+ fprintf(fh, "%s\n", argv[i]);
+ }
+
+ if ( warn_times ) print_time_warning();
+
+ dtempl = data_template_new_from_file(in_geom);
+ groups = data_template_group_info(dtempl, &n_groups);
+
+ fprintf(fh, "\nParameter\n");
+
+ /* Top level */
+ fprintf(fh, "%i 0 0\n", mille_label(0, GPARAM_DET_TX));
+ fprintf(fh, "%i 0 0\n", mille_label(0, GPARAM_DET_TY));
+ fprintf(fh, "%i 0 %i\n", mille_label(0, GPARAM_DET_TZ), out_of_plane ? 0 : -1);
+ fprintf(fh, "%i 0 %i\n", mille_label(0, GPARAM_DET_RX), out_of_plane ? 0 : -1);
+ fprintf(fh, "%i 0 %i\n", mille_label(0, GPARAM_DET_RY), out_of_plane ? 0 : -1);
+ fprintf(fh, "%i 0 -1\n", mille_label(0, GPARAM_DET_RZ));
+
+ for ( i=0; i<n_groups; i++ ) {
+ int f_inplane = (groups[i].hierarchy_level > level) ? -1 : 0;
+ int f_outplane = out_of_plane ? f_inplane : -1;
+ if ( groups[i].hierarchy_level == 0 ) continue;
+ fprintf(fh, "%i 0 %i\n", mille_label(groups[i].serial, GPARAM_DET_TX), f_inplane);
+ fprintf(fh, "%i 0 %i\n", mille_label(groups[i].serial, GPARAM_DET_TY), f_inplane);
+ fprintf(fh, "%i 0 %i\n", mille_label(groups[i].serial, GPARAM_DET_TZ), f_outplane);
+ fprintf(fh, "%i 0 %i\n", mille_label(groups[i].serial, GPARAM_DET_RX), f_outplane);
+ fprintf(fh, "%i 0 %i\n", mille_label(groups[i].serial, GPARAM_DET_RY), f_outplane);
+ fprintf(fh, "%i 0 %i\n", mille_label(groups[i].serial, GPARAM_DET_RZ), f_inplane);
+ }
+ fprintf(fh, "\n");
+
+ /* All corrections must sum to zero at each level of hierarchy */
+ if ( make_zero_sum(fh, groups, n_groups, "all", level, out_of_plane) ) return 1;
+
+ fprintf(fh, "method inversion 5 0.1\n");
+ fprintf(fh, "end\n");
+ fclose(fh);
+
+ unlink("millepede.res");
+
+ r = system("pede millepede.txt");
+ if ( r == -1 ) {
+ ERROR("Failed to run Millepde: %s\n", strerror(errno));
+ return 1;
+ }
+ if ( !WIFEXITED(r) ) {
+ ERROR("Millepede exited abnormally.\n");
+ return 1;
+ }
+ if ( WEXITSTATUS(r) != 0 ) {
+ ERROR("Millepede returned an error status (%i)\n", WEXITSTATUS(r));
+ return 1;
+ }
+
+ STATUS("Millepede succeeded.\n\n");
+
+ fh = fopen("millepede.res", "r");
+ if ( fh == NULL ) {
+ ERROR("Failed to open millepede.res\n");
+ return 1;
+ }
+
+ if ( fgets(line, 256, fh) != line ) {
+ ERROR("Failed to read first line of millepede.res\n");
+ return 1;
+ }
+ if ( strncmp(line, " Parameter ", 11) != 0 ) {
+ ERROR("First line of millepede.res is not as expected.\n");
+ return 1;
+ }
+
+ int last_group_serial = -1;
+ do {
+
+ char **bits;
+ int i, n;
+
+ rval = fgets(line, 256, fh);
+ if ( rval != line ) continue;
+
+ chomp(line);
+ notrail(line);
+ n = assplode(line, " ", &bits, ASSPLODE_NONE);
+ if ( (n != 3) && (n != 5) ) {
+ ERROR("Didn't understand this line from Millepede: (%i) %s", n, line);
+ return 1;
+ }
+
+ if ( n == 5 ) {
+
+ int code;
+ double shift;
+ int p;
+ const char *group_name;
+ int group_serial;
+
+ if ( convert_int(bits[0], &code) ) {
+ ERROR("Didn't understand '%s'\n", bits[0]);
+ return 1;
+ }
+ if ( convert_float(bits[1], &shift) ) {
+ ERROR("Didn't understand '%s'\n", bits[1]);
+ return 1;
+ }
+
+ p = mille_unlabel(code % 100);
+ group_serial = code - (code % 100);
+ group_name = group_serial_to_name(group_serial,
+ groups,
+ n_groups);
+
+ if ( last_group_serial != group_serial ) {
+ STATUS("Group %s:\n", group_name);
+ last_group_serial = group_serial;
+ }
+
+ switch ( p ) {
+ case GPARAM_DET_TX:
+ case GPARAM_DET_TY:
+ case GPARAM_DET_TZ:
+ STATUS(" %14s %+f mm\n", str_param(p), 1e3*shift);
+ break;
+
+ case GPARAM_DET_RX:
+ case GPARAM_DET_RY:
+ case GPARAM_DET_RZ:
+ STATUS(" %14s %+f deg\n", str_param(p), rad2deg(shift));
+ break;
+ }
+
+ if ( group_name == NULL ) {
+ ERROR("Invalid group serial number %i\n", code);
+ return 1;
+ }
+
+ switch ( p ) {
+
+ case GPARAM_DET_TX:
+ data_template_translate_group_m(dtempl, group_name,
+ -shift, 0, 0);
+ break;
+
+ case GPARAM_DET_TY:
+ data_template_translate_group_m(dtempl, group_name,
+ 0, -shift, 0);
+ break;
+
+ case GPARAM_DET_TZ:
+ data_template_translate_group_m(dtempl, group_name,
+ 0, 0, -shift);
+ break;
+
+ case GPARAM_DET_RX:
+ data_template_rotate_group(dtempl, group_name,
+ -shift, 'x');
+ break;
+
+ case GPARAM_DET_RY:
+ data_template_rotate_group(dtempl, group_name,
+ -shift, 'y');
+ break;
+
+ case GPARAM_DET_RZ:
+ data_template_rotate_group(dtempl, group_name,
+ -shift, 'z');
+ break;
+
+ default:
+ ERROR("Invalid parameter %i\n", p);
+ return 1;
+ }
+
+ }
+
+ for ( i=0; i<n; i++ ) free(bits[i]);
+ free(bits);
+
+ } while ( rval == line );
+
+ fclose(fh);
+
+ data_template_write_to_file(dtempl, out_geom);
+
+ if ( warn_times ) print_time_warning();
+
+ return 0;
+}
diff --git a/src/im-sandbox.c b/src/im-sandbox.c
index a91c6a87..27e33b8f 100644
--- a/src/im-sandbox.c
+++ b/src/im-sandbox.c
@@ -69,6 +69,7 @@
#include "im-zmq.h"
#include "profile.h"
#include "im-asapo.h"
+#include "predict-refine.h"
struct sandbox
@@ -340,6 +341,7 @@ static int run_work(const struct index_args *iargs, Stream *st,
int allDone = 0;
struct im_zmq *zmqstuff = NULL;
struct im_asapo *asapostuff = NULL;
+ Mille *mille;
if ( sb->profile ) {
profile_init();
@@ -363,6 +365,13 @@ static int run_work(const struct index_args *iargs, Stream *st,
}
}
+ mille = NULL;
+ if ( iargs->mille ) {
+ char tmp[64];
+ snprintf(tmp, 63, "mille-data-%i.bin", cookie);
+ mille = crystfel_mille_new(tmp);
+ }
+
while ( !allDone ) {
struct pattern_args pargs;
@@ -519,7 +528,7 @@ static int run_work(const struct index_args *iargs, Stream *st,
profile_start("process-image");
process_image(iargs, &pargs, st, cookie, tmpdir, ser,
sb->shared, sb->shared->last_task[cookie],
- asapostuff);
+ asapostuff, mille);
profile_end("process-image");
}
@@ -536,6 +545,8 @@ static int run_work(const struct index_args *iargs, Stream *st,
free(pargs.event);
}
+ crystfel_mille_free(mille);
+
/* These are both no-ops if argument is NULL */
im_zmq_shutdown(zmqstuff);
im_asapo_shutdown(asapostuff);
diff --git a/src/indexamajig.c b/src/indexamajig.c
index 57e1e5fb..7f5504cd 100644
--- a/src/indexamajig.c
+++ b/src/indexamajig.c
@@ -3,13 +3,13 @@
*
* Index patterns, output hkl+intensity etc.
*
- * Copyright © 2012-2022 Deutsches Elektronen-Synchrotron DESY,
+ * Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY,
* a research centre of the Helmholtz Association.
* Copyright © 2012 Richard Kirian
* Copyright © 2012 Lorenzo Galli
*
* Authors:
- * 2010-2022 Thomas White <taw@physics.org>
+ * 2010-2023 Thomas White <taw@physics.org>
* 2011 Richard Kirian
* 2012 Lorenzo Galli
* 2012 Chunhong Yoon
@@ -722,6 +722,10 @@ static error_t parse_arg(int key, char *arg, struct argp_state *state)
}
break;
+ case 416 :
+ args->iargs.mille = 1;
+ break;
+
/* ---------- Integration ---------- */
case 501 :
@@ -966,6 +970,7 @@ int main(int argc, char *argv[])
args.iargs.clen_estimate = NAN;
args.iargs.n_threads = 1;
args.iargs.data_format = DATA_SOURCE_TYPE_UNKNOWN;
+ args.iargs.mille = 0;
argp_program_version_hook = show_version;
@@ -1085,6 +1090,8 @@ int main(int argc, char *argv[])
"Maximum number of threads allowed for indexing engines."},
{"camera-length-estimate", 415, "metres", 0,
"Estimate of the camera length, in metres."},
+ {"mille", 416, NULL, 0,
+ "Generate data for detector geometry refinement using Millepede"},
{NULL, 0, 0, OPTION_DOC, "Integration options:", 5},
{"integration", 501, "method", OPTION_NO_USAGE, "Integration method"},
diff --git a/src/process_image.c b/src/process_image.c
index 8acd3e86..4763646a 100644
--- a/src/process_image.c
+++ b/src/process_image.c
@@ -178,7 +178,8 @@ static struct image *file_wait_open_read(const char *filename,
void process_image(const struct index_args *iargs, struct pattern_args *pargs,
Stream *st, int cookie, const char *tmpdir,
int serial, struct sb_shm *sb_shared,
- char *last_task, struct im_asapo *asapostuff)
+ char *last_task, struct im_asapo *asapostuff,
+ Mille *mille)
{
struct image *image;
int i;
@@ -414,8 +415,8 @@ void process_image(const struct index_args *iargs, struct pattern_args *pargs,
/* Index the pattern */
set_last_task(last_task, "indexing");
profile_start("index");
- index_pattern_3(image, iargs->ipriv, &sb_shared->pings[cookie],
- last_task);
+ index_pattern_4(image, iargs->ipriv, &sb_shared->pings[cookie],
+ last_task, mille);
profile_end("index");
r = chdir(rn);
diff --git a/src/process_image.h b/src/process_image.h
index cca2f7d2..eabd425a 100644
--- a/src/process_image.h
+++ b/src/process_image.h
@@ -3,11 +3,11 @@
*
* The processing pipeline for one image
*
- * Copyright © 2012-2022 Deutsches Elektronen-Synchrotron DESY,
+ * Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY,
* a research centre of the Helmholtz Association.
*
* Authors:
- * 2010-2022 Thomas White <taw@physics.org>
+ * 2010-2023 Thomas White <taw@physics.org>
* 2014-2017 Valerio Mariani <valerio.mariani@desy.de>
* 2017-2018 Yaroslav Gevorkov <yaroslav.gevorkov@desy.de>
*
@@ -46,6 +46,7 @@ struct index_args;
#include "peaks.h"
#include "image.h"
#include "im-asapo.h"
+#include "predict-refine.h"
/* Information about the indexing process which is common to all patterns */
@@ -73,6 +74,7 @@ struct index_args
float wavelength_estimate;
float clen_estimate;
int n_threads;
+ int mille;
/* Integration */
IntegrationMethod int_meth;
@@ -115,7 +117,8 @@ extern void process_image(const struct index_args *iargs,
struct pattern_args *pargs, Stream *st,
int cookie, const char *tmpdir, int serial,
struct sb_shm *sb_shared, char *last_task,
- struct im_asapo *asapostuff);
+ struct im_asapo *asapostuff,
+ Mille *mille);
#endif /* PROCESS_IMAGE_H */
diff --git a/subprojects/cjson.wrap b/subprojects/cjson.wrap
index dc10279a..9b11edcb 100644
--- a/subprojects/cjson.wrap
+++ b/subprojects/cjson.wrap
@@ -3,10 +3,12 @@ directory = cJSON-1.7.15
source_url = https://github.com/DaveGamble/cJSON/archive/refs/tags/v1.7.15.tar.gz
source_filename = v1.7.15.tar.gz
source_hash = 5308fd4bd90cef7aa060558514de6a1a4a0819974a26e6ed13973c5f624c24b2
-patch_filename = cjson_1.7.15-2_patch.zip
-patch_url = https://wrapdb.mesonbuild.com/v2/cjson_1.7.15-2/get_patch
-patch_hash = d83b4bc0ca94e392c62c8c6c7839392f382d66a84974f5e10611074836ef1777
+patch_filename = cjson_1.7.15-6_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/cjson_1.7.15-6/get_patch
+patch_hash = 277fe1e7a85aa79016b1d2ae6781d69f3dd3841e8ebec25e8a7e96e0af4fce02
+source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/cjson_1.7.15-6/v1.7.15.tar.gz
+wrapdb_version = 1.7.15-6
[provide]
libcjson = libcjson_dep
-
+libcjson_utils = libcjson_utils_dep
diff --git a/subprojects/millepede.wrap b/subprojects/millepede.wrap
new file mode 100644
index 00000000..dc124637
--- /dev/null
+++ b/subprojects/millepede.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+directory = millepede
+url = https://gitlab.desy.de/claus.kleinwort/millepede-ii.git
+revision = main
+depth = 1
+patch_directory = millepede
diff --git a/subprojects/packagefiles/millepede/meson.build b/subprojects/packagefiles/millepede/meson.build
new file mode 100644
index 00000000..eab03d24
--- /dev/null
+++ b/subprojects/packagefiles/millepede/meson.build
@@ -0,0 +1,42 @@
+# Meson file for Millepede-II
+project('millepede', ['c', 'fortran'],
+ version: '2.0.0',
+ license: 'LGPL2+',
+ default_options: ['buildtype=debugoptimized'])
+
+pede_c_args = []
+
+zlibdep = dependency('zlib', required: false)
+if zlibdep.found()
+ pede_c_args += '-DUSE_ZLIB'
+endif
+
+pede = executable('pede',
+ ['mpdef.f90',
+ 'mpdalc.f90',
+ 'mpmod.f90',
+ 'mpmon.f90',
+ 'mpbits.f90',
+ 'mpqldec.f90',
+ 'mptest1.f90',
+ 'mptest2.f90',
+ 'mille.f90',
+ 'mpnum.f90',
+ 'mptext.f90',
+ 'mphistab.f90',
+ 'minresDataModule.f90',
+ 'minresModule.f90',
+ 'minresqlpDataModule.f90',
+ 'minresqlpBlasModule.f90',
+ 'minresqlpModule.f90',
+ 'randoms.f90',
+ 'vertpr.f90',
+ 'linesrch.f90',
+ 'Dbandmatrix.f90',
+ 'pede.f90',
+ 'readc.c'],
+ fortran_args: ['-DREAD_C_FILES', '-cpp'],
+ c_args: pede_c_args,
+ build_by_default: true,
+ dependencies: [zlibdep],
+ install: true)
diff --git a/tests/ev_enum1.geom b/tests/ev_enum1.geom
index 9189727f..0afc500c 100644
--- a/tests/ev_enum1.geom
+++ b/tests/ev_enum1.geom
@@ -1,4 +1,5 @@
photon_energy = 10000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/dim0 = ss
diff --git a/tests/ev_enum2.geom b/tests/ev_enum2.geom
index ce0aeacc..c27fb49c 100644
--- a/tests/ev_enum2.geom
+++ b/tests/ev_enum2.geom
@@ -1,4 +1,5 @@
photon_energy = 10000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/dim0 = %
diff --git a/tests/ev_enum3.geom b/tests/ev_enum3.geom
index 3a83848e..2e08f383 100644
--- a/tests/ev_enum3.geom
+++ b/tests/ev_enum3.geom
@@ -1,4 +1,5 @@
photon_energy = 10000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/dim0 = ss
diff --git a/tests/geom_roundtrip b/tests/geom_roundtrip
new file mode 100755
index 00000000..6d205f06
--- /dev/null
+++ b/tests/geom_roundtrip
@@ -0,0 +1,645 @@
+#!/bin/sh
+
+ADJUST_DETECTOR=$1
+
+cat > roundtrip.geom << EOF
+photon_energy = /LCLS/photon_energy_eV eV
+clen = /LCLS/detector_1/EncoderValue
+bandwidth = 1.000000e-08
+coffset = 0.573224
+mask0_data = /entry_1/data_1/mask
+mask0_goodbits = 0x0
+mask0_badbits = 0xffff
+res = 9090.910000
+data = /entry_1/data_1/data
+adu_per_eV = 0.003380
+dim0 = %
+dim1 = ss
+dim2 = fs
+
+q0a0/min_fs = 0
+q0a0/max_fs = 193
+q0a0/min_ss = 0
+q0a0/max_ss = 184
+q0a0/corner_x = 443.819000
+q0a0/corner_y = -49.871900
+q0a0/fs = 0.004806x +0.999989y +0.000000z
+q0a0/ss = -0.999989x +0.004806y +0.000000z
+
+q0a1/min_fs = 194
+q0a1/max_fs = 387
+q0a1/min_ss = 0
+q0a1/max_ss = 184
+q0a1/corner_x = 444.766000
+q0a1/corner_y = 147.126000
+q0a1/fs = 0.004806x +0.999989y +0.000000z
+q0a1/ss = -0.999989x +0.004806y +0.000000z
+
+q0a2/min_fs = 0
+q0a2/max_fs = 193
+q0a2/min_ss = 185
+q0a2/max_ss = 369
+q0a2/corner_x = 239.800000
+q0a2/corner_y = -49.350400
+q0a2/fs = 0.003265x +0.999995y +0.000000z
+q0a2/ss = -0.999995x +0.003265y +0.000000z
+
+q0a3/min_fs = 194
+q0a3/max_fs = 387
+q0a3/min_ss = 185
+q0a3/max_ss = 369
+q0a3/corner_x = 240.444000
+q0a3/corner_y = 147.649000
+q0a3/fs = 0.003265x +0.999995y +0.000000z
+q0a3/ss = -0.999995x +0.003265y +0.000000z
+
+q0a4/min_fs = 0
+q0a4/max_fs = 193
+q0a4/min_ss = 370
+q0a4/max_ss = 554
+q0a4/corner_x = 872.219000
+q0a4/corner_y = 342.054000
+q0a4/fs = -0.999997x +0.002424y +0.000000z
+q0a4/ss = -0.002424x -0.999997y +0.000000z
+
+q0a5/min_fs = 194
+q0a5/max_fs = 387
+q0a5/min_ss = 370
+q0a5/max_ss = 554
+q0a5/corner_x = 675.220000
+q0a5/corner_y = 342.532000
+q0a5/fs = -0.999997x +0.002424y +0.000000z
+q0a5/ss = -0.002424x -0.999997y +0.000000z
+
+q0a6/min_fs = 0
+q0a6/max_fs = 193
+q0a6/min_ss = 555
+q0a6/max_ss = 739
+q0a6/corner_x = 871.381000
+q0a6/corner_y = 135.836000
+q0a6/fs = -0.999997x +0.002685y +0.000000z
+q0a6/ss = -0.002685x -0.999997y +0.000000z
+
+q0a7/min_fs = 194
+q0a7/max_fs = 387
+q0a7/min_ss = 555
+q0a7/max_ss = 739
+q0a7/corner_x = 674.382000
+q0a7/corner_y = 136.365000
+q0a7/fs = -0.999997x +0.002685y +0.000000z
+q0a7/ss = -0.002685x -0.999997y +0.000000z
+
+q0a8/min_fs = 0
+q0a8/max_fs = 193
+q0a8/min_ss = 740
+q0a8/max_ss = 924
+q0a8/corner_x = 480.758000
+q0a8/corner_y = 769.640000
+q0a8/fs = -0.000078x -0.999999y +0.000000z
+q0a8/ss = 0.999999x -0.000078y +0.000000z
+
+q0a9/min_fs = 194
+q0a9/max_fs = 387
+q0a9/min_ss = 740
+q0a9/max_ss = 924
+q0a9/corner_x = 480.743000
+q0a9/corner_y = 572.640000
+q0a9/fs = -0.000078x -0.999999y +0.000000z
+q0a9/ss = 0.999999x -0.000078y +0.000000z
+
+q0a10/min_fs = 0
+q0a10/max_fs = 193
+q0a10/min_ss = 925
+q0a10/max_ss = 1109
+q0a10/corner_x = 689.447000
+q0a10/corner_y = 770.295000
+q0a10/fs = 0.001551x -0.999999y +0.000000z
+q0a10/ss = 0.999999x +0.001551y +0.000000z
+
+q0a11/min_fs = 194
+q0a11/max_fs = 387
+q0a11/min_ss = 925
+q0a11/max_ss = 1109
+q0a11/corner_x = 689.752000
+q0a11/corner_y = 573.296000
+q0a11/fs = 0.001551x -0.999999y +0.000000z
+q0a11/ss = 0.999999x +0.001551y +0.000000z
+
+q0a12/min_fs = 0
+q0a12/max_fs = 193
+q0a12/min_ss = 1110
+q0a12/max_ss = 1294
+q0a12/corner_x = 445.672000
+q0a12/corner_y = 751.701000
+q0a12/fs = -0.999998x -0.002161y +0.000000z
+q0a12/ss = 0.002161x -0.999998y +0.000000z
+
+q0a13/min_fs = 194
+q0a13/max_fs = 387
+q0a13/min_ss = 1110
+q0a13/max_ss = 1294
+q0a13/corner_x = 248.672000
+q0a13/corner_y = 751.276000
+q0a13/fs = -0.999998x -0.002161y +0.000000z
+q0a13/ss = 0.002161x -0.999998y +0.000000z
+
+q0a14/min_fs = 0
+q0a14/max_fs = 193
+q0a14/min_ss = 1295
+q0a14/max_ss = 1479
+q0a14/corner_x = 445.151000
+q0a14/corner_y = 541.081000
+q0a14/fs = -0.999999x -0.000074y +0.000000z
+q0a14/ss = 0.000074x -0.999999y +0.000000z
+
+q0a15/min_fs = 194
+q0a15/max_fs = 387
+q0a15/min_ss = 1295
+q0a15/max_ss = 1479
+q0a15/corner_x = 248.151000
+q0a15/corner_y = 541.066000
+q0a15/fs = -0.999999x -0.000074y +0.000000z
+q0a15/ss = 0.000074x -0.999999y +0.000000z
+
+q1a0/min_fs = 388
+q1a0/max_fs = 581
+q1a0/min_ss = 0
+q1a0/max_ss = 184
+q1a0/corner_x = 28.477600
+q1a0/corner_y = 436.830000
+q1a0/fs = -0.999990x -0.004167y +0.000000z
+q1a0/ss = 0.004167x -0.999990y +0.000000z
+
+q1a1/min_fs = 582
+q1a1/max_fs = 775
+q1a1/min_ss = 0
+q1a1/max_ss = 184
+q1a1/corner_x = -168.520000
+q1a1/corner_y = 436.009000
+q1a1/fs = -0.999990x -0.004167y +0.000000z
+q1a1/ss = 0.004167x -0.999990y +0.000000z
+
+q1a2/min_fs = 388
+q1a2/max_fs = 581
+q1a2/min_ss = 185
+q1a2/max_ss = 369
+q1a2/corner_x = 29.355900
+q1a2/corner_y = 226.978000
+q1a2/fs = -1.000001x +0.000385y +0.000000z
+q1a2/ss = -0.000385x -1.000001y +0.000000z
+
+q1a3/min_fs = 582
+q1a3/max_fs = 775
+q1a3/min_ss = 185
+q1a3/max_ss = 369
+q1a3/corner_x = -167.644000
+q1a3/corner_y = 227.054000
+q1a3/fs = -1.000001x +0.000385y +0.000000z
+q1a3/ss = -0.000385x -1.000001y +0.000000z
+
+q1a4/min_fs = 388
+q1a4/max_fs = 581
+q1a4/min_ss = 370
+q1a4/max_ss = 554
+q1a4/corner_x = -364.144000
+q1a4/corner_y = 859.163000
+q1a4/fs = 0.000539x -1.000000y +0.000000z
+q1a4/ss = 1.000000x +0.000539y +0.000000z
+
+q1a5/min_fs = 582
+q1a5/max_fs = 775
+q1a5/min_ss = 370
+q1a5/max_ss = 554
+q1a5/corner_x = -364.038000
+q1a5/corner_y = 662.163000
+q1a5/fs = 0.000539x -1.000000y +0.000000z
+q1a5/ss = 1.000000x +0.000539y +0.000000z
+
+q1a6/min_fs = 388
+q1a6/max_fs = 581
+q1a6/min_ss = 555
+q1a6/max_ss = 739
+q1a6/corner_x = -156.511000
+q1a6/corner_y = 857.902000
+q1a6/fs = -0.000337x -1.000000y +0.000000z
+q1a6/ss = 1.000000x -0.000337y +0.000000z
+
+q1a7/min_fs = 582
+q1a7/max_fs = 775
+q1a7/min_ss = 555
+q1a7/max_ss = 739
+q1a7/corner_x = -156.577000
+q1a7/corner_y = 660.902000
+q1a7/fs = -0.000337x -1.000000y +0.000000z
+q1a7/ss = 1.000000x -0.000337y +0.000000z
+
+q1a8/min_fs = 388
+q1a8/max_fs = 581
+q1a8/min_ss = 740
+q1a8/max_ss = 924
+q1a8/corner_x = -786.718000
+q1a8/corner_y = 463.506000
+q1a8/fs = 0.999996x +0.002303y +0.000000z
+q1a8/ss = -0.002303x +0.999996y +0.000000z
+
+q1a9/min_fs = 582
+q1a9/max_fs = 775
+q1a9/min_ss = 740
+q1a9/max_ss = 924
+q1a9/corner_x = -589.719000
+q1a9/corner_y = 463.959000
+q1a9/fs = 0.999996x +0.002303y +0.000000z
+q1a9/ss = -0.002303x +0.999996y +0.000000z
+
+q1a10/min_fs = 388
+q1a10/max_fs = 581
+q1a10/min_ss = 925
+q1a10/max_ss = 1109
+q1a10/corner_x = -787.022000
+q1a10/corner_y = 668.135000
+q1a10/fs = 0.999997x +0.001741y +0.000000z
+q1a10/ss = -0.001741x +0.999997y +0.000000z
+
+q1a11/min_fs = 582
+q1a11/max_fs = 775
+q1a11/min_ss = 925
+q1a11/max_ss = 1109
+q1a11/corner_x = -590.022000
+q1a11/corner_y = 668.478000
+q1a11/fs = 0.999997x +0.001741y +0.000000z
+q1a11/ss = -0.001741x +0.999997y +0.000000z
+
+q1a12/min_fs = 388
+q1a12/max_fs = 581
+q1a12/min_ss = 1110
+q1a12/max_ss = 1294
+q1a12/corner_x = -761.085000
+q1a12/corner_y = 428.541000
+q1a12/fs = -0.000201x -0.999999y +0.000000z
+q1a12/ss = 0.999999x -0.000201y +0.000000z
+
+q1a13/min_fs = 582
+q1a13/max_fs = 775
+q1a13/min_ss = 1110
+q1a13/max_ss = 1294
+q1a13/corner_x = -761.125000
+q1a13/corner_y = 231.541000
+q1a13/fs = -0.000201x -0.999999y +0.000000z
+q1a13/ss = 0.999999x -0.000201y +0.000000z
+
+q1a14/min_fs = 388
+q1a14/max_fs = 581
+q1a14/min_ss = 1295
+q1a14/max_ss = 1479
+q1a14/corner_x = -559.624000
+q1a14/corner_y = 428.347000
+q1a14/fs = 0.003097x -0.999995y +0.000000z
+q1a14/ss = 0.999995x +0.003097y +0.000000z
+
+q1a15/min_fs = 582
+q1a15/max_fs = 775
+q1a15/min_ss = 1295
+q1a15/max_ss = 1479
+q1a15/corner_x = -559.014000
+q1a15/corner_y = 231.348000
+q1a15/fs = 0.003097x -0.999995y +0.000000z
+q1a15/ss = 0.999995x +0.003097y +0.000000z
+
+q2a0/min_fs = 776
+q2a0/max_fs = 969
+q2a0/min_ss = 0
+q2a0/max_ss = 184
+q2a0/corner_x = -442.346000
+q2a0/corner_y = 20.338200
+q2a0/fs = -0.004086x -0.999991y +0.000000z
+q2a0/ss = 0.999991x -0.004086y +0.000000z
+
+q2a1/min_fs = 970
+q2a1/max_fs = 1163
+q2a1/min_ss = 0
+q2a1/max_ss = 184
+q2a1/corner_x = -443.151000
+q2a1/corner_y = -176.660000
+q2a1/fs = -0.004086x -0.999991y +0.000000z
+q2a1/ss = 0.999991x -0.004086y +0.000000z
+
+q2a2/min_fs = 776
+q2a2/max_fs = 969
+q2a2/min_ss = 185
+q2a2/max_ss = 369
+q2a2/corner_x = -235.519000
+q2a2/corner_y = 19.231200
+q2a2/fs = 0.000302x -1.000000y +0.000000z
+q2a2/ss = 1.000000x +0.000302y +0.000000z
+
+q2a3/min_fs = 970
+q2a3/max_fs = 1163
+q2a3/min_ss = 185
+q2a3/max_ss = 369
+q2a3/corner_x = -235.459000
+q2a3/corner_y = -177.769000
+q2a3/fs = 0.000302x -1.000000y +0.000000z
+q2a3/ss = 1.000000x +0.000302y +0.000000z
+
+q2a4/min_fs = 776
+q2a4/max_fs = 969
+q2a4/min_ss = 370
+q2a4/max_ss = 554
+q2a4/corner_x = -863.817000
+q2a4/corner_y = -370.344000
+q2a4/fs = 0.999997x -0.002037y +0.000000z
+q2a4/ss = 0.002037x +0.999997y +0.000000z
+
+q2a5/min_fs = 970
+q2a5/max_fs = 1163
+q2a5/min_ss = 370
+q2a5/max_ss = 554
+q2a5/corner_x = -666.817000
+q2a5/corner_y = -370.746000
+q2a5/fs = 0.999997x -0.002037y +0.000000z
+q2a5/ss = 0.002037x +0.999997y +0.000000z
+
+q2a6/min_fs = 776
+q2a6/max_fs = 969
+q2a6/min_ss = 555
+q2a6/max_ss = 739
+q2a6/corner_x = -863.549000
+q2a6/corner_y = -165.126000
+q2a6/fs = 1.000000x -0.001155y +0.000000z
+q2a6/ss = 0.001155x +1.000000y +0.000000z
+
+q2a7/min_fs = 970
+q2a7/max_fs = 1163
+q2a7/min_ss = 555
+q2a7/max_ss = 739
+q2a7/corner_x = -666.549000
+q2a7/corner_y = -165.353000
+q2a7/fs = 1.000000x -0.001155y +0.000000z
+q2a7/ss = 0.001155x +1.000000y +0.000000z
+
+q2a8/min_fs = 776
+q2a8/max_fs = 969
+q2a8/min_ss = 740
+q2a8/max_ss = 924
+q2a8/corner_x = -473.620000
+q2a8/corner_y = -793.473000
+q2a8/fs = 0.002076x +0.999998y +0.000000z
+q2a8/ss = -0.999998x +0.002076y +0.000000z
+
+q2a9/min_fs = 970
+q2a9/max_fs = 1163
+q2a9/min_ss = 740
+q2a9/max_ss = 924
+q2a9/corner_x = -473.211000
+q2a9/corner_y = -596.474000
+q2a9/fs = 0.002076x +0.999998y +0.000000z
+q2a9/ss = -0.999998x +0.002076y +0.000000z
+
+q2a10/min_fs = 776
+q2a10/max_fs = 969
+q2a10/min_ss = 925
+q2a10/max_ss = 1109
+q2a10/corner_x = -676.809000
+q2a10/corner_y = -792.653000
+q2a10/fs = 0.004134x +0.999991y +0.000000z
+q2a10/ss = -0.999991x +0.004134y +0.000000z
+
+q2a11/min_fs = 970
+q2a11/max_fs = 1163
+q2a11/min_ss = 925
+q2a11/max_ss = 1109
+q2a11/corner_x = -675.995000
+q2a11/corner_y = -595.655000
+q2a11/fs = 0.004134x +0.999991y +0.000000z
+q2a11/ss = -0.999991x +0.004134y +0.000000z
+
+q2a12/min_fs = 776
+q2a12/max_fs = 969
+q2a12/min_ss = 1110
+q2a12/max_ss = 1294
+q2a12/corner_x = -442.034000
+q2a12/corner_y = -769.447000
+q2a12/fs = 0.999981x -0.006417y +0.000000z
+q2a12/ss = 0.006417x +0.999981y +0.000000z
+
+q2a13/min_fs = 970
+q2a13/max_fs = 1163
+q2a13/min_ss = 1110
+q2a13/max_ss = 1294
+q2a13/corner_x = -245.038000
+q2a13/corner_y = -770.711000
+q2a13/fs = 0.999981x -0.006417y +0.000000z
+q2a13/ss = 0.006417x +0.999981y +0.000000z
+
+q2a14/min_fs = 776
+q2a14/max_fs = 969
+q2a14/min_ss = 1295
+q2a14/max_ss = 1479
+q2a14/corner_x = -441.283000
+q2a14/corner_y = -566.627000
+q2a14/fs = 0.999996x -0.002727y +0.000000z
+q2a14/ss = 0.002727x +0.999996y +0.000000z
+
+q2a15/min_fs = 970
+q2a15/max_fs = 1163
+q2a15/min_ss = 1295
+q2a15/max_ss = 1479
+q2a15/corner_x = -244.283000
+q2a15/corner_y = -567.164000
+q2a15/fs = 0.999996x -0.002727y +0.000000z
+q2a15/ss = 0.002727x +0.999996y +0.000000z
+
+q3a0/min_fs = 1164
+q3a0/max_fs = 1357
+q3a0/min_ss = 0
+q3a0/max_ss = 184
+q3a0/corner_x = -33.350700
+q3a0/corner_y = -458.693000
+q3a0/fs = 0.999988x -0.004965y +0.000000z
+q3a0/ss = 0.004965x +0.999988y +0.000000z
+
+q3a1/min_fs = 1358
+q3a1/max_fs = 1551
+q3a1/min_ss = 0
+q3a1/max_ss = 184
+q3a1/corner_x = 163.647000
+q3a1/corner_y = -459.671000
+q3a1/fs = 0.999988x -0.004965y +0.000000z
+q3a1/ss = 0.004965x +0.999988y +0.000000z
+
+q3a2/min_fs = 1164
+q3a2/max_fs = 1357
+q3a2/min_ss = 185
+q3a2/max_ss = 369
+q3a2/corner_x = -31.831600
+q3a2/corner_y = -254.931000
+q3a2/fs = 0.999998x -0.002316y +0.000000z
+q3a2/ss = 0.002316x +0.999998y +0.000000z
+
+q3a3/min_fs = 1358
+q3a3/max_fs = 1551
+q3a3/min_ss = 185
+q3a3/max_ss = 369
+q3a3/corner_x = 165.168000
+q3a3/corner_y = -255.388000
+q3a3/fs = 0.999998x -0.002316y +0.000000z
+q3a3/ss = 0.002316x +0.999998y +0.000000z
+
+q3a4/min_fs = 1164
+q3a4/max_fs = 1357
+q3a4/min_ss = 370
+q3a4/max_ss = 554
+q3a4/corner_x = 359.553000
+q3a4/corner_y = -886.512000
+q3a4/fs = 0.002474x +0.999997y +0.000000z
+q3a4/ss = -0.999997x +0.002474y +0.000000z
+
+q3a5/min_fs = 1358
+q3a5/max_fs = 1551
+q3a5/min_ss = 370
+q3a5/max_ss = 554
+q3a5/corner_x = 360.040000
+q3a5/corner_y = -689.512000
+q3a5/fs = 0.002474x +0.999997y +0.000000z
+q3a5/ss = -0.999997x +0.002474y +0.000000z
+
+q3a6/min_fs = 1164
+q3a6/max_fs = 1357
+q3a6/min_ss = 555
+q3a6/max_ss = 739
+q3a6/corner_x = 154.142000
+q3a6/corner_y = -884.763000
+q3a6/fs = 0.000059x +1.000000y +0.000000z
+q3a6/ss = -1.000000x +0.000059y +0.000000z
+
+q3a7/min_fs = 1358
+q3a7/max_fs = 1551
+q3a7/min_ss = 555
+q3a7/max_ss = 739
+q3a7/corner_x = 154.154000
+q3a7/corner_y = -687.763000
+q3a7/fs = 0.000059x +1.000000y +0.000000z
+q3a7/ss = -1.000000x +0.000059y +0.000000z
+
+q3a8/min_fs = 1164
+q3a8/max_fs = 1357
+q3a8/min_ss = 740
+q3a8/max_ss = 924
+q3a8/corner_x = 784.877000
+q3a8/corner_y = -492.935000
+q3a8/fs = -0.999993x +0.004040y +0.000000z
+q3a8/ss = -0.004040x -0.999993y +0.000000z
+
+q3a9/min_fs = 1358
+q3a9/max_fs = 1551
+q3a9/min_ss = 740
+q3a9/max_ss = 924
+q3a9/corner_x = 587.878000
+q3a9/corner_y = -492.139000
+q3a9/fs = -0.999993x +0.004040y +0.000000z
+q3a9/ss = -0.004040x -0.999993y +0.000000z
+
+q3a10/min_fs = 1164
+q3a10/max_fs = 1357
+q3a10/min_ss = 925
+q3a10/max_ss = 1109
+q3a10/corner_x = 784.254000
+q3a10/corner_y = -699.590000
+q3a10/fs = -0.999971x +0.007529y +0.000000z
+q3a10/ss = -0.007529x -0.999971y +0.000000z
+
+q3a11/min_fs = 1358
+q3a11/max_fs = 1551
+q3a11/min_ss = 925
+q3a11/max_ss = 1109
+q3a11/corner_x = 587.260000
+q3a11/corner_y = -698.107000
+q3a11/fs = -0.999971x +0.007529y +0.000000z
+q3a11/ss = -0.007529x -0.999971y +0.000000z
+
+q3a12/min_fs = 1164
+q3a12/max_fs = 1357
+q3a12/min_ss = 1110
+q3a12/max_ss = 1294
+q3a12/corner_x = 769.176000
+q3a12/corner_y = -460.510000
+q3a12/fs = 0.004516x +0.999990y +0.000000z
+q3a12/ss = -0.999990x +0.004516y +0.000000z
+
+q3a13/min_fs = 1358
+q3a13/max_fs = 1551
+q3a13/min_ss = 1110
+q3a13/max_ss = 1294
+q3a13/corner_x = 770.066000
+q3a13/corner_y = -263.512000
+q3a13/fs = 0.004516x +0.999990y +0.000000z
+q3a13/ss = -0.999990x +0.004516y +0.000000z
+
+q3a14/min_fs = 1164
+q3a14/max_fs = 1357
+q3a14/min_ss = 1295
+q3a14/max_ss = 1479
+q3a14/corner_x = 554.764000
+q3a14/corner_y = -460.250000
+q3a14/fs = 0.004918x +0.999989y +0.000000z
+q3a14/ss = -0.999989x +0.004918y +0.000000z
+
+q3a15/min_fs = 1358
+q3a15/max_fs = 1551
+q3a15/min_ss = 1295
+q3a15/max_ss = 1479
+q3a15/corner_x = 555.732000
+q3a15/corner_y = -263.253000
+q3a15/fs = 0.004918x +0.999989y +0.000000z
+q3a15/ss = -0.999989x +0.004918y +0.000000z
+
+group_a0 = q0a0,q0a1
+group_a1 = q0a2,q0a3
+group_a2 = q0a4,q0a5
+group_a3 = q0a6,q0a7
+group_a4 = q0a8,q0a9
+group_a5 = q0a10,q0a11
+group_a6 = q0a12,q0a13
+group_a7 = q0a14,q0a15
+group_a8 = q1a0,q1a1
+group_a9 = q1a2,q1a3
+group_a10 = q1a4,q1a5
+group_a11 = q1a6,q1a7
+group_a12 = q1a8,q1a9
+group_a13 = q1a10,q1a11
+group_a14 = q1a12,q1a13
+group_a15 = q1a14,q1a15
+group_a16 = q2a0,q2a1
+group_a17 = q2a2,q2a3
+group_a18 = q2a4,q2a5
+group_a19 = q2a6,q2a7
+group_a20 = q2a8,q2a9
+group_a21 = q2a10,q2a11
+group_a22 = q2a12,q2a13
+group_a23 = q2a14,q2a15
+group_a24 = q3a0,q3a1
+group_a25 = q3a2,q3a3
+group_a26 = q3a4,q3a5
+group_a27 = q3a6,q3a7
+group_a28 = q3a8,q3a9
+group_a29 = q3a10,q3a11
+group_a30 = q3a12,q3a13
+group_a31 = q3a14,q3a15
+group_q0 = a0,a1,a2,a3,a4,a5,a6,a7
+group_q1 = a8,a9,a10,a11,a12,a13,a14,a15
+group_q2 = a16,a17,a18,a19,a20,a21,a22,a23
+group_q3 = a24,a25,a26,a27,a28,a29,a30,a31
+group_all = q0,q1,q2,q3
+EOF
+
+$ADJUST_DETECTOR -i roundtrip.geom -o roundtrip-out.geom
+if [ $? -ne 0 ]; then
+ exit 1;
+fi
+
+diff roundtrip.geom roundtrip-out.geom
+if [ $? -ne 0 ]; then
+ exit 1
+fi
+#rm -f roundtrip.geom roundtrip-out.geom
+exit 0
diff --git a/tests/gradient_check.c b/tests/gradient_check.c
new file mode 100644
index 00000000..001bff7a
--- /dev/null
+++ b/tests/gradient_check.c
@@ -0,0 +1,187 @@
+/*
+ * gradient_panel_x.c
+ *
+ * Check gradients for prediction refinement
+ *
+ * Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2012-2023 Thomas White <taw@physics.org>
+ *
+ * This file is part of CrystFEL.
+ *
+ * CrystFEL is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CrystFEL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+
+#include <image.h>
+#include <geometry.h>
+#include <predict-refine.h>
+
+#include "gradient_check_utils.h"
+
+
+int main(int argc, char *argv[])
+{
+ struct image image;
+ struct reflpeak *rps;
+ int n_refls;
+ double **before;
+ double **after;
+ int i;
+ int n_wrong_r = 0;
+ int n_wrong_fs = 0;
+ int n_wrong_ss = 0;
+ int n_wrong_obsr = 0;
+ int fail = 0;
+ double step;
+ gsl_matrix **panel_matrices;
+ int didsomething = 0;
+ const double cx = 0.03; /* Detector is a 7.5 cm side length square */
+ const double cy = 0.02;
+ const double cz = 0.01;
+
+ rps = make_test_image(&n_refls, &image);
+ panel_matrices = make_panel_minvs(image.detgeom);
+
+ before = make_dev_list(rps, n_refls, image.detgeom);
+
+ #ifdef TRANSLATE_PANEL
+ struct detgeom_panel *p = &image.detgeom->panels[0];
+ step = 0.01e-3; /* metres */
+ image.detgeom->panels[0].THING_TO_MOVE += step/p->pixel_pitch;
+ didsomething = 1;
+ #endif
+
+ #ifdef ROTATE_PANEL_X
+ struct detgeom_panel *p = &image.detgeom->panels[0];
+ step = deg2rad(0.01);
+ rotate2d(&p->cny, &p->cnz, cy/p->pixel_pitch, cz/p->pixel_pitch, step);
+ rotate2d(&p->fsy, &p->fsz, 0, 0, step);
+ rotate2d(&p->ssy, &p->ssz, 0, 0, step);
+ didsomething = 1;
+ #endif
+
+ #ifdef ROTATE_PANEL_Y
+ struct detgeom_panel *p = &image.detgeom->panels[0];
+ step = deg2rad(0.01);
+ rotate2d(&p->cnz, &p->cnx, cz/p->pixel_pitch, cx/p->pixel_pitch, step);
+ rotate2d(&p->fsz, &p->fsx, 0, 0, step);
+ rotate2d(&p->ssz, &p->ssx, 0, 0, step);
+ didsomething = 1;
+ #endif
+
+ #ifdef ROTATE_PANEL_Z
+ struct detgeom_panel *p = &image.detgeom->panels[0];
+ step = deg2rad(0.01);
+ rotate2d(&p->cnx, &p->cny, cx/p->pixel_pitch, cy/p->pixel_pitch, step);
+ rotate2d(&p->fsx, &p->fsy, 0, 0, step);
+ rotate2d(&p->ssx, &p->ssy, 0, 0, step);
+ didsomething = 1;
+ #endif
+
+ #ifdef CHANGE_CELL
+ double asx, asy, asz, bsx, bsy, bsz, csx, csy, csz;
+ UnitCell *cell = crystal_get_cell(image.crystals[0]);
+ step = 0.5e5;
+ cell_get_reciprocal(cell, &asx, &asy, &asz,
+ &bsx, &bsy, &bsz,
+ &csx, &csy, &csz);
+ THING_TO_MOVE += step;
+ cell_set_reciprocal(cell, asx, asy, asz,
+ bsx, bsy, bsz,
+ csx, csy, csz);
+ didsomething = 1;
+ #endif
+
+ if ( !didsomething ) {
+ fprintf(stderr, "Nothing changed. Check the build system.\n");
+ return 1;
+ }
+
+ update_predictions(image.crystals[0]);
+ after = make_dev_list(rps, n_refls, image.detgeom);
+
+ for ( i=0; i<n_refls; i++ ) {
+
+ float calc[3];
+ double obs[3];
+
+ calc[0] = r_gradient(TEST_GPARAM, rps[i].refl,
+ crystal_get_cell(image.crystals[0]),
+ image.lambda);
+
+ fs_ss_gradient(TEST_GPARAM, rps[i].refl,
+ crystal_get_cell(image.crystals[0]),
+ &image.detgeom->panels[rps[i].peak->pn],
+ panel_matrices[rps[i].peak->pn], cx, cy, cz,
+ &calc[1], &calc[2]);
+
+ obs[0] = (after[0][i] - before[0][i]) / step;
+ obs[1] = (after[1][i] - before[1][i]) / step;
+ obs[2] = (after[2][i] - before[2][i]) / step;
+
+ #ifdef TRANSLATE_PANEL
+ if ( fabs(calc[0]) > 1e-12 ) n_wrong_r++; /* Should be zero */
+ if ( fabs(obs[0]) > 1e-12 ) n_wrong_obsr++; /* Should also be zero */
+ if ( fabs(obs[1] - calc[1]) > 10.0 ) n_wrong_fs++;
+ if ( fabs(obs[2] - calc[2]) > 10.0 ) n_wrong_ss++;
+ #endif
+
+ #if defined(ROTATE_PANEL_X) || defined(ROTATE_PANEL_Y) || defined(ROTATE_PANEL_Z)
+ if ( fabs(calc[0]) > 1e-12 ) n_wrong_r++; /* Should be zero */
+ if ( fabs(obs[0]) > 1e-12 ) n_wrong_obsr++; /* Should also be zero */
+ if ( fabs(obs[1] - calc[1]) > 1.0 ) n_wrong_fs++; /* Units are pixels/rad */
+ if ( fabs(obs[2] - calc[2]) > 1.0 ) n_wrong_ss++; /* (numbers are big) */
+ #endif
+
+ #ifdef CHANGE_CELL
+ if ( fabs(obs[0] - calc[0]) > 1e-2 ) n_wrong_r++;
+ if ( fabs(obs[1] - calc[1]) > 1e-8 ) n_wrong_fs++;
+ if ( fabs(obs[2] - calc[2]) > 1e-8 ) n_wrong_ss++;
+ #endif
+
+ }
+
+ if ( n_wrong_r > 0 ) {
+ fprintf(stderr, "%i out of %i R gradients were wrong.\n",
+ n_wrong_r, n_refls);
+ fail = 1;
+ }
+
+ if ( n_wrong_fs > 0 ) {
+ fprintf(stderr, "%i out of %i fs gradients were wrong.\n",
+ n_wrong_fs, n_refls);
+ fail = 1;
+ }
+
+ if ( n_wrong_ss > 0 ) {
+ fprintf(stderr, "%i out of %i ss gradients were wrong.\n",
+ n_wrong_ss, n_refls);
+ fail = 1;
+ }
+
+ if ( n_wrong_obsr > 0 ) {
+ fprintf(stderr, "%i out of %i observed R gradients were not zero as expected\n",
+ n_wrong_obsr, n_refls);
+ fail = 1;
+ }
+
+ return fail;
+}
diff --git a/tests/gradient_check_utils.c b/tests/gradient_check_utils.c
new file mode 100644
index 00000000..6b641b9d
--- /dev/null
+++ b/tests/gradient_check_utils.c
@@ -0,0 +1,184 @@
+/*
+ * gradient_check_utils.c
+ *
+ * Check gradients for prediction refinement (common component)
+ *
+ * Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2012-2023 Thomas White <taw@physics.org>
+ *
+ * This file is part of CrystFEL.
+ *
+ * CrystFEL is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CrystFEL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <gsl/gsl_randist.h>
+#include <getopt.h>
+
+#include <image.h>
+#include <cell.h>
+#include <cell-utils.h>
+#include <geometry.h>
+#include <reflist.h>
+
+
+double **make_dev_list(struct reflpeak *rps, int n_refls, struct detgeom *det)
+{
+ int i;
+ double **vals;
+
+ vals = malloc(3*sizeof(double *));
+ vals[0] = malloc(n_refls*sizeof(double));
+ vals[1] = malloc(n_refls*sizeof(double));
+ vals[2] = malloc(n_refls*sizeof(double));
+
+ for ( i=0; i<n_refls; i++ ) {
+ vals[0][i] = get_exerr(rps[i].refl);
+ vals[1][i] = fs_dev(&rps[i], det);
+ vals[2][i] = ss_dev(&rps[i], det);
+ }
+
+ return vals;
+}
+
+
+static UnitCell *random_rotated_cell(gsl_rng *rng)
+{
+ UnitCell *cell;
+ struct quaternion orientation;
+
+ cell = cell_new_from_parameters(10.0e-9, 10.0e-9, 10.0e-9,
+ deg2rad(90.0),
+ deg2rad(90.0),
+ deg2rad(90.0));
+ orientation = random_quaternion(rng);
+ return cell_rotate(cell, orientation);
+}
+
+
+static void rot(double *x, double *y, double ang)
+{
+ double nx, ny;
+ nx = (*x)*cos(ang) - (*y)*sin(ang);
+ ny = (*x)*sin(ang) + (*y)*cos(ang);
+ *x = nx; *y = ny;
+}
+
+
+struct reflpeak *make_test_image(int *pn_refls, struct image *image)
+{
+ Crystal *cr;
+ gsl_rng *rng;
+ RefList *refls;
+ Reflection *refl;
+ RefListIterator *iter;
+ int n_refls;
+ int i;
+ struct reflpeak *rps;
+
+ image->detgeom = malloc(sizeof(struct detgeom));
+ image->detgeom->n_panels = 1;
+ image->detgeom->panels = malloc(sizeof(struct detgeom_panel));
+ image->detgeom->panels[0].name = "panel";
+ image->detgeom->panels[0].adu_per_photon = 1.0;
+ image->detgeom->panels[0].max_adu = INFINITY;
+ image->detgeom->panels[0].fsx = 1.0;
+ image->detgeom->panels[0].fsy = 0.0;
+ image->detgeom->panels[0].fsz = 0.0;
+ image->detgeom->panels[0].ssx = 0.0;
+ image->detgeom->panels[0].ssy = 1.0;
+ image->detgeom->panels[0].ssz = 0.0;
+ rot(&image->detgeom->panels[0].fsx,
+ &image->detgeom->panels[0].fsy,
+ deg2rad(30));
+ rot(&image->detgeom->panels[0].ssx,
+ &image->detgeom->panels[0].ssy,
+ deg2rad(30));
+ rot(&image->detgeom->panels[0].fsx,
+ &image->detgeom->panels[0].fsz,
+ deg2rad(15));
+ rot(&image->detgeom->panels[0].ssx,
+ &image->detgeom->panels[0].ssz,
+ deg2rad(15));
+ image->detgeom->panels[0].cnx = -500.0;
+ image->detgeom->panels[0].cny = -500.0;
+ image->detgeom->panels[0].cnz = 1000.0; /* pixels */
+ image->detgeom->panels[0].w = 1000;
+ image->detgeom->panels[0].h = 1000;
+ image->detgeom->panels[0].pixel_pitch = 75e-6;
+
+ image->lambda = ph_en_to_lambda(eV_to_J(8000.0));
+ image->div = 1e-3;
+ image->bw = 0.00001;
+ image->filename = malloc(256);
+ image->spectrum = spectrum_generate_gaussian(image->lambda, image->bw);
+
+ image->crystals = NULL;
+ image->n_crystals = 0;
+
+ rng = gsl_rng_alloc(gsl_rng_mt19937);
+
+ cr = crystal_new();
+ if ( cr == NULL ) {
+ ERROR("Failed to allocate crystal.\n");
+ return NULL;
+ }
+ crystal_set_mosaicity(cr, 0.0);
+ crystal_set_profile_radius(cr, 0.005e9);
+ crystal_set_image(cr, image);
+ crystal_set_cell(cr, random_rotated_cell(rng));
+
+ refls = predict_to_res(cr, detgeom_max_resolution(image->detgeom, image->lambda));
+ crystal_set_reflections(cr, refls);
+ n_refls = num_reflections(refls);
+
+ /* Associate a peak with every reflection */
+ rps = malloc(n_refls*sizeof(struct reflpeak));
+ i = 0;
+ for ( refl = first_refl(refls, &iter);
+ refl != NULL;
+ refl = next_refl(refl, iter) )
+ {
+ double fs, ss;
+ int pn;
+ struct imagefeature *pk;
+
+ get_detector_pos(refl, &fs, &ss);
+ pn = get_panel_number(refl);
+
+ pk = malloc(sizeof(struct imagefeature));
+
+ pk->fs = fs + gsl_ran_gaussian(rng, 5.0);
+ pk->ss = ss + gsl_ran_gaussian(rng, 5.0);
+ pk->pn = pn;
+ pk->intensity = 1.0;
+
+ rps[i].peak = pk;
+ rps[i].refl = refl;
+ rps[i].Ih = 1.0;
+ i++;
+ }
+
+ image_add_crystal(image, cr);
+
+ gsl_rng_free(rng);
+
+ *pn_refls = n_refls;
+ return rps;
+}
diff --git a/tests/gradient_check_utils.h b/tests/gradient_check_utils.h
new file mode 100644
index 00000000..562081fe
--- /dev/null
+++ b/tests/gradient_check_utils.h
@@ -0,0 +1,32 @@
+/*
+ * gradient_check_utils.h
+ *
+ * Check gradients for prediction refinement (common component)
+ *
+ * Copyright © 2012-2023 Deutsches Elektronen-Synchrotron DESY,
+ * a research centre of the Helmholtz Association.
+ *
+ * Authors:
+ * 2012-2023 Thomas White <taw@physics.org>
+ *
+ * This file is part of CrystFEL.
+ *
+ * CrystFEL is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * CrystFEL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <image.h>
+
+extern struct reflpeak *make_test_image(int *n_refls, struct image *image);
+extern double **make_dev_list(struct reflpeak *rps, int n_refls, struct detgeom *det);
diff --git a/tests/meson.build b/tests/meson.build
index 917c59db..88a8d073 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -44,7 +44,6 @@ simple_tests = ['ambi_check',
'cell_check',
'centering_check',
'list_check',
- 'prediction_gradient_check',
'ring_check',
'symmetry_check',
'transformation_check',
@@ -96,6 +95,63 @@ exe = executable('prof2d_check',
dependencies : [libcrystfeldep, mdep, gsldep])
test('prof2d_check', exe)
+
+# Refinement gradient checks, part 1: panel translations
+panel_gradient_tests = [['gradient_panel_x', 'cnx', 'GPARAM_DET_TX'],
+ ['gradient_panel_y', 'cny', 'GPARAM_DET_TY'],
+ ['gradient_panel_z', 'cnz', 'GPARAM_DET_TZ']]
+
+foreach name : panel_gradient_tests
+ exe = executable(name[0],
+ ['gradient_check.c',
+ 'gradient_check_utils.c'],
+ c_args : ['-DTHING_TO_MOVE='+name[1],
+ '-DTEST_GPARAM='+name[2],
+ '-DTRANSLATE_PANEL=1'],
+ dependencies : [libcrystfeldep, mdep, gsldep])
+ test(name[0], exe)
+endforeach
+
+
+# Refinement gradient checks, part 2: panel rotations
+panel_gradient_tests = [['gradient_panel_rx', '-DROTATE_PANEL_X', 'GPARAM_DET_RX'],
+ ['gradient_panel_ry', '-DROTATE_PANEL_Y', 'GPARAM_DET_RY'],
+ ['gradient_panel_rz', '-DROTATE_PANEL_Z', 'GPARAM_DET_RZ']]
+
+foreach name : panel_gradient_tests
+ exe = executable(name[0],
+ ['gradient_check.c',
+ 'gradient_check_utils.c'],
+ c_args : [name[1],
+ '-DTEST_GPARAM='+name[2]],
+ dependencies : [libcrystfeldep, mdep, gsldep])
+ test(name[0], exe)
+endforeach
+
+
+# Refinement gradient checks, part 3: diffraction physics
+panel_gradient_tests = [['gradient_cell_asx', 'asx', 'GPARAM_ASX'],
+ ['gradient_cell_asy', 'asy', 'GPARAM_ASY'],
+ ['gradient_cell_asz', 'asz', 'GPARAM_ASZ'],
+ ['gradient_cell_bsx', 'bsx', 'GPARAM_BSX'],
+ ['gradient_cell_bsy', 'bsy', 'GPARAM_BSY'],
+ ['gradient_cell_bsz', 'bsz', 'GPARAM_BSZ'],
+ ['gradient_cell_csx', 'csx', 'GPARAM_CSX'],
+ ['gradient_cell_csy', 'csy', 'GPARAM_CSY'],
+ ['gradient_cell_csz', 'csz', 'GPARAM_CSZ']]
+
+foreach name : panel_gradient_tests
+ exe = executable(name[0],
+ ['gradient_check.c',
+ 'gradient_check_utils.c'],
+ c_args : ['-DTHING_TO_MOVE='+name[1],
+ '-DTEST_GPARAM='+name[2],
+ '-DCHANGE_CELL=1'],
+ dependencies : [libcrystfeldep, mdep, gsldep])
+ test(name[0], exe)
+endforeach
+
+
# Event enumeration tests
if hdf5dep.found()
ev_enum_tests = ['ev_enum1',
@@ -138,3 +194,8 @@ if hdf5dep.found()
test(p[0], exe, args : [h5, geom, expected_wl])
endforeach
endif
+
+
+test('geom_roundtrip',
+ find_program('geom_roundtrip'),
+ args: [adjust_detector.full_path()])
diff --git a/tests/partialator_merge_check_1 b/tests/partialator_merge_check_1
index 4ed03785..221a9327 100755
--- a/tests/partialator_merge_check_1
+++ b/tests/partialator_merge_check_1
@@ -7,6 +7,7 @@ CrystFEL stream format 2.3
Command line: indexamajig -i dummy.lst -o dummy.stream --kraken=prawn
----- Begin geometry file -----
photon_energy = 9000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 0
panel/max_fs = 1023
@@ -15,7 +16,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/partialator_merge_check_2 b/tests/partialator_merge_check_2
index 2ebcd74e..e392b82e 100755
--- a/tests/partialator_merge_check_2
+++ b/tests/partialator_merge_check_2
@@ -7,6 +7,7 @@ CrystFEL stream format 2.3
Command line: indexamajig -i dummy.lst -o dummy.stream --kraken=prawn
----- Begin geometry file -----
photon_energy = 9000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 0
panel/max_fs = 1023
@@ -15,7 +16,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/partialator_merge_check_3 b/tests/partialator_merge_check_3
index 8c998258..9f5fa350 100755
--- a/tests/partialator_merge_check_3
+++ b/tests/partialator_merge_check_3
@@ -7,6 +7,7 @@ CrystFEL stream format 2.3
Command line: indexamajig -i dummy.lst -o dummy.stream --kraken=prawn
----- Begin geometry file -----
photon_energy = 9000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 0
panel/max_fs = 1023
@@ -15,7 +16,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/plot_gradients b/tests/plot_gradients
deleted file mode 100755
index b5800d07..00000000
--- a/tests/plot_gradients
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-gnuplot -persist << EOF
-
-set key bottom right
-
-set xlabel "Calculated gradient"
-set ylabel "Observed gradient"
-
- plot "gradient-test-x.dat" using 1:2 w p lc 1 pt 1 title "x"
-replot "gradient-test-y.dat" using 1:2 w p lc 2 pt 1 title "y"
-replot "gradient-test-z.dat" using 1:2 w p lc 3 pt 1 title "z"
-
-EOF
-
-gnuplot -persist << EOF
-set key bottom right
-set xlabel "Calculated gradient"
-set ylabel "Observed gradient"
-plot "gradient-test-R.dat" using 1:2 w p lc 1 pt 1 title "profile radius"
-EOF
-
-gnuplot -persist << EOF
-set key bottom right
-set xlabel "Calculated gradient"
-set ylabel "Observed gradient"
-plot "gradient-test-div.dat" using 1:2 w p lc 1 pt 1 title "divergence"
-EOF
diff --git a/tests/prediction_gradient_check.c b/tests/prediction_gradient_check.c
deleted file mode 100644
index 493da445..00000000
--- a/tests/prediction_gradient_check.c
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
- * prediction_gradient_check.c
- *
- * Check partiality gradients for prediction refinement
- *
- * Copyright © 2012-2020 Deutsches Elektronen-Synchrotron DESY,
- * a research centre of the Helmholtz Association.
- *
- * Authors:
- * 2012-2020 Thomas White <taw@physics.org>
- *
- * This file is part of CrystFEL.
- *
- * CrystFEL is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * CrystFEL is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with CrystFEL. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <gsl/gsl_statistics.h>
-#include <getopt.h>
-
-#include <image.h>
-#include <cell.h>
-#include <cell-utils.h>
-#include <geometry.h>
-#include <reflist.h>
-
-
-int checkrxy;
-
-
-static void twod_mapping(double fs, double ss, double *px, double *py,
- struct detgeom_panel *p)
-{
- double xs, ys;
-
- xs = fs*p->fsx + ss*p->ssx;
- ys = fs*p->fsy + ss*p->ssy;
-
- *px = (xs + p->cnx) * p->pixel_pitch;
- *py = (ys + p->cny) * p->pixel_pitch;
-}
-
-
-static void scan(RefList *reflections, RefList *compare,
- int *valid, long double *vals[3], int idx,
- struct detgeom *det)
-{
- int i;
- Reflection *refl;
- RefListIterator *iter;
-
- i = 0;
- for ( refl = first_refl(reflections, &iter);
- refl != NULL;
- refl = next_refl(refl, iter) )
- {
- signed int h, k, l;
- Reflection *refl2;
- double fs, ss, xh, yh;
- int pn;
-
- get_indices(refl, &h, &k, &l);
- refl2 = find_refl(compare, h, k, l);
- if ( refl2 == NULL ) {
- valid[i] = 0;
- i++;
- continue;
- }
-
- get_detector_pos(refl2, &fs, &ss);
- pn = get_panel_number(refl2);
- twod_mapping(fs, ss, &xh, &yh, &det->panels[pn]);
-
- switch ( checkrxy ) {
-
- case 0 :
- vals[idx][i] = get_exerr(refl2);
- break;
-
- case 1 :
- vals[idx][i] = xh;
- break;
-
- case 2 :
- vals[idx][i] = yh;
- break;
- }
-
- i++;
- }
-}
-
-
-static UnitCell *new_shifted_cell(UnitCell *input, int k, double shift)
-{
- UnitCell *cell;
- double asx, asy, asz;
- double bsx, bsy, bsz;
- double csx, csy, csz;
-
- cell = cell_new();
- cell_get_reciprocal(input, &asx, &asy, &asz, &bsx, &bsy, &bsz,
- &csx, &csy, &csz);
- switch ( k )
- {
- case GPARAM_ASX : asx += shift; break;
- case GPARAM_ASY : asy += shift; break;
- case GPARAM_ASZ : asz += shift; break;
- case GPARAM_BSX : bsx += shift; break;
- case GPARAM_BSY : bsy += shift; break;
- case GPARAM_BSZ : bsz += shift; break;
- case GPARAM_CSX : csx += shift; break;
- case GPARAM_CSY : csy += shift; break;
- case GPARAM_CSZ : csz += shift; break;
- }
- cell_set_reciprocal(cell, asx, asy, asz, bsx, bsy, bsz, csx, csy, csz);
-
- return cell;
-}
-
-
-static Crystal *new_shifted_crystal(Crystal *cr, int refine, double incr_val)
-{
- Crystal *cr_new;
- UnitCell *cell;
-
- cr_new = crystal_copy(cr);
- if ( cr_new == NULL ) {
- ERROR("Failed to allocate crystal.\n");
- return NULL;
- }
-
- crystal_set_image(cr_new, crystal_get_image(cr));
-
- switch ( refine ) {
-
- case GPARAM_ASX :
- case GPARAM_ASY :
- case GPARAM_ASZ :
- case GPARAM_BSX :
- case GPARAM_BSY :
- case GPARAM_BSZ :
- case GPARAM_CSX :
- case GPARAM_CSY :
- case GPARAM_CSZ :
- cell = new_shifted_cell(crystal_get_cell(cr), refine,
- incr_val);
- crystal_set_cell(cr_new, cell);
- break;
-
- default:
- ERROR("Can't shift %i\n", refine);
- break;
-
- }
-
- return cr_new;
-}
-
-
-static void calc_either_side(Crystal *cr, double incr_val,
- int *valid, long double *vals[3],
- int refine, struct detgeom *det)
-{
- RefList *compare;
- struct image *image = crystal_get_image(cr);
- Crystal *cr_new;
-
- cr_new = new_shifted_crystal(cr, refine, -incr_val);
- compare = predict_to_res(cr_new, detgeom_max_resolution(image->detgeom,
- image->lambda));
- scan(crystal_get_reflections(cr), compare, valid, vals, 0, det);
- cell_free(crystal_get_cell(cr_new));
- crystal_free(cr_new);
- reflist_free(compare);
-
- cr_new = new_shifted_crystal(cr, refine, +incr_val);
- compare = predict_to_res(cr_new, detgeom_max_resolution(image->detgeom,
- image->lambda));
- scan(crystal_get_reflections(cr), compare, valid, vals, 2, det);
- cell_free(crystal_get_cell(cr_new));
- crystal_free(cr_new);
- reflist_free(compare);
-}
-
-
-static double max_resolution(const struct image *image)
-{
- return detgeom_max_resolution(image->detgeom,
- image->lambda);
-}
-
-
-static double test_gradients(Crystal *cr, double incr_val, int refine,
- const char *str, const char *file,
- int quiet, int plot, struct detgeom *det)
-{
- Reflection *refl;
- RefListIterator *iter;
- long double *vals[3];
- int i;
- int *valid;
- int nref;
- int n_good, n_invalid, n_small, n_nan, n_bad;
- RefList *reflections;
- FILE *fh = NULL;
- int ntot = 0;
- char tmp[32];
- double *vec1;
- double *vec2;
- int n_line;
- double cc;
-
- reflections = predict_to_res(cr, max_resolution(crystal_get_image(cr)));
- crystal_set_reflections(cr, reflections);
-
- nref = num_reflections(reflections);
- if ( nref < 10 ) {
- ERROR("Too few reflections found. Failing test by default.\n");
- return 0.0;
- }
-
- vals[0] = malloc(nref*sizeof(long double));
- vals[1] = malloc(nref*sizeof(long double));
- vals[2] = malloc(nref*sizeof(long double));
- if ( (vals[0] == NULL) || (vals[1] == NULL) || (vals[2] == NULL) ) {
- ERROR("Couldn't allocate memory.\n");
- return 0.0;
- }
-
- valid = malloc(nref*sizeof(int));
- if ( valid == NULL ) {
- ERROR("Couldn't allocate memory.\n");
- return 0.0;
- }
- for ( i=0; i<nref; i++ ) valid[i] = 1;
-
- scan(reflections, reflections, valid, vals, 1, det);
-
- calc_either_side(cr, incr_val, valid, vals, refine, det);
-
- if ( plot ) {
- snprintf(tmp, 32, "gradient-test-%s.dat", file);
- fh = fopen(tmp, "w");
- }
-
- vec1 = malloc(nref*sizeof(double));
- vec2 = malloc(nref*sizeof(double));
- if ( (vec1 == NULL) || (vec2 == NULL) ) {
- ERROR("Couldn't allocate memory.\n");
- return 0.0;
- }
-
- n_invalid = 0; n_good = 0;
- n_nan = 0; n_small = 0; n_bad = 0; n_line = 0;
- i = 0;
- for ( refl = first_refl(reflections, &iter);
- refl != NULL;
- refl = next_refl(refl, iter) )
- {
- long double grad1, grad2, grad;
- double cgrad;
- signed int h, k, l;
-
- get_indices(refl, &h, &k, &l);
-
- if ( !valid[i] ) {
- n_invalid++;
- i++;
- } else {
-
- grad1 = (vals[1][i] - vals[0][i]) / incr_val;
- grad2 = (vals[2][i] - vals[1][i]) / incr_val;
- grad = (grad1 + grad2) / 2.0;
- i++;
-
- if ( checkrxy == 0 ) {
-
- cgrad = r_gradient(crystal_get_cell(cr), refine,
- refl, crystal_get_image(cr));
-
- } else {
-
- struct image *image;
-
- image = crystal_get_image(cr);
-
- if ( checkrxy == 1 ) {
- cgrad = x_gradient(refine, refl,
- crystal_get_cell(cr),
- &image->detgeom->panels[0]);
- } else {
- cgrad = y_gradient(refine, refl,
- crystal_get_cell(cr),
- &image->detgeom->panels[0]);
- }
- }
-
- if ( isnan(cgrad) ) {
- n_nan++;
- continue;
- }
-
- if ( plot ) {
- fprintf(fh, "%e %Le\n", cgrad, grad);
- }
-
- vec1[n_line] = cgrad;
- vec2[n_line] = grad;
- n_line++;
-
- if ( (fabsl(cgrad) < 5e-12) && (fabsl(grad) < 5e-12) ) {
- n_small++;
- continue;
- }
-
- ntot++;
-
- if ( !within_tolerance(grad, cgrad, 5.0)
- || !within_tolerance(cgrad, grad, 5.0) )
- {
-
- if ( !quiet ) {
- STATUS("!- %s %3i %3i %3i "
- "%10.2Le %10.2e "
- "ratio = %5.2Lf\n",
- str, h, k, l, grad, cgrad,
- cgrad/grad);
- }
- n_bad++;
-
- } else {
-
- //STATUS("OK %s %3i %3i %3i"
- // " %10.2Le %10.2e ratio = %5.2Lf"
- // " %10.2e %10.2e\n",
- // str, h, k, l, grad, cgrad, cgrad/grad,
- // r1, r2);
-
- n_good++;
-
- }
-
- }
-
- }
-
- STATUS("%3s: %3i within 5%%, %3i outside, %3i nan, %3i invalid, "
- "%3i small. ", str, n_good, n_bad, n_nan, n_invalid, n_small);
-
- if ( plot ) {
- fclose(fh);
- }
-
- cc = gsl_stats_correlation(vec1, 1, vec2, 1, n_line);
- STATUS("CC = %+f\n", cc);
- return cc;
-}
-
-
-int main(int argc, char *argv[])
-{
- struct image image;
- const double incr_frac = 1.0/100000.0;
- double incr_val;
- double ax, ay, az;
- double bx, by, bz;
- double cx, cy, cz;
- UnitCell *cell;
- Crystal *cr;
- struct quaternion orientation;
- int fail = 0;
- int quiet = 0;
- int plot = 0;
- int c;
- gsl_rng *rng;
- UnitCell *rot;
- double val;
-
- const struct option longopts[] = {
- {"quiet", 0, &quiet, 1},
- {"plot", 0, &plot, 1},
- {0, 0, NULL, 0}
- };
-
- while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
- switch (c) {
-
- case 0 :
- break;
-
- case '?' :
- break;
-
- default :
- ERROR("Unhandled option '%c'\n", c);
- break;
-
- }
-
- }
-
- image.detgeom = malloc(sizeof(struct detgeom));
- image.detgeom->n_panels = 1;
- image.detgeom->panels = malloc(sizeof(struct detgeom_panel));
- image.detgeom->panels[0].name = "panel";
- image.detgeom->panels[0].adu_per_photon = 1.0;
- image.detgeom->panels[0].max_adu = INFINITY;
- image.detgeom->panels[0].fsx = 1.0;
- image.detgeom->panels[0].fsy = 0.0;
- image.detgeom->panels[0].fsz = 0.0;
- image.detgeom->panels[0].ssx = 0.0;
- image.detgeom->panels[0].ssy = 1.0;
- image.detgeom->panels[0].ssz = 0.0;
- image.detgeom->panels[0].cnx = -500.0;
- image.detgeom->panels[0].cny = -500.0;
- image.detgeom->panels[0].cnz = 1000.0; /* pixels */
- image.detgeom->panels[0].w = 1000;
- image.detgeom->panels[0].h = 1000;
- image.detgeom->panels[0].pixel_pitch = 75e-6;
-
- image.lambda = ph_en_to_lambda(eV_to_J(8000.0));
- image.div = 1e-3;
- image.bw = 0.00001;
- image.filename = malloc(256);
- image.spectrum = spectrum_generate_gaussian(image.lambda, image.bw);
-
- cr = crystal_new();
- if ( cr == NULL ) {
- ERROR("Failed to allocate crystal.\n");
- return 1;
- }
- crystal_set_mosaicity(cr, 0.0);
- crystal_set_profile_radius(cr, 0.005e9);
- crystal_set_image(cr, &image);
-
- cell = cell_new_from_parameters(10.0e-9, 10.0e-9, 10.0e-9,
- deg2rad(90.0),
- deg2rad(90.0),
- deg2rad(90.0));
-
- rng = gsl_rng_alloc(gsl_rng_mt19937);
-
- for ( checkrxy=0; checkrxy<3; checkrxy++ ) {
-
-
- switch ( checkrxy ) {
- case 0 :
- STATUS("Excitation error:\n");
- break;
- case 1:
- STATUS("x coordinate:\n");
- break;
- default:
- case 2:
- STATUS("y coordinate:\n");
- break;
- STATUS("WTF??\n");
- break;
- }
-
- orientation = random_quaternion(rng);
- rot = cell_rotate(cell, orientation);
- crystal_set_cell(cr, rot);
-
- cell_get_reciprocal(rot, &ax, &ay, &az,
- &bx, &by, &bz, &cx, &cy, &cz);
-
- if ( checkrxy != 2 ) {
-
- incr_val = incr_frac * ax;
- val = test_gradients(cr, incr_val, GPARAM_ASX,
- "ax*", "ax", quiet, plot,
- image.detgeom);
- if ( val < 0.99 ) fail = 1;
- incr_val = incr_frac * bx;
- val = test_gradients(cr, incr_val, GPARAM_BSX,
- "bx*", "bx", quiet, plot,
- image.detgeom);
- if ( val < 0.99 ) fail = 1;
- incr_val = incr_frac * cx;
- val = test_gradients(cr, incr_val, GPARAM_CSX,
- "cx*", "cx", quiet, plot,
- image.detgeom);
- if ( val < 0.99 ) fail = 1;
-
- }
-
- if ( checkrxy != 1 ) {
-
- incr_val = incr_frac * ay;
- val = test_gradients(cr, incr_val, GPARAM_ASY,
- "ay*", "ay", quiet, plot,
- image.detgeom);
- if ( val < 0.99 ) fail = 1;
- incr_val = incr_frac * by;
- val = test_gradients(cr, incr_val, GPARAM_BSY,
- "by*", "by", quiet, plot,
- image.detgeom);
- if ( val < 0.99 ) fail = 1;
- incr_val = incr_frac * cy;
- val = test_gradients(cr, incr_val, GPARAM_CSY,
- "cy*", "cy", quiet, plot,
- image.detgeom);
- if ( val < 0.99 ) fail = 1;
-
- }
-
- incr_val = incr_frac * az;
- val = test_gradients(cr, incr_val, GPARAM_ASZ, "az*", "az",
- quiet, plot, image.detgeom);
- if ( val < 0.99 ) fail = 1;
- incr_val = incr_frac * bz;
- val = test_gradients(cr, incr_val, GPARAM_BSZ, "bz*", "bz",
- quiet, plot, image.detgeom);
- if ( val < 0.99 ) fail = 1;
- incr_val = incr_frac * cz;
- val = test_gradients(cr, incr_val, GPARAM_CSZ, "cz*", "cz",
- quiet, plot, image.detgeom);
- if ( val < 0.99 ) fail = 1;
-
- }
-
- gsl_rng_free(rng);
-
- return fail;
-}
diff --git a/tests/process_hkl_check_1 b/tests/process_hkl_check_1
index 7cd29335..464fd528 100755
--- a/tests/process_hkl_check_1
+++ b/tests/process_hkl_check_1
@@ -7,6 +7,7 @@ CrystFEL stream format 2.3
Command line: indexamajig -i dummy.lst -o dummy.stream --kraken=prawn
----- Begin geometry file -----
photon_energy = 9000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 0
panel/max_fs = 1023
@@ -15,7 +16,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/process_hkl_check_2 b/tests/process_hkl_check_2
index 1ce4bc3c..2a523d1b 100755
--- a/tests/process_hkl_check_2
+++ b/tests/process_hkl_check_2
@@ -7,6 +7,7 @@ CrystFEL stream format 2.3
Command line: indexamajig -i dummy.lst -o dummy.stream --kraken=prawn
----- Begin geometry file -----
photon_energy = 9000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 0
panel/max_fs = 1023
@@ -15,7 +16,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/process_hkl_check_3 b/tests/process_hkl_check_3
index 4e884ad2..2440ae79 100755
--- a/tests/process_hkl_check_3
+++ b/tests/process_hkl_check_3
@@ -7,6 +7,7 @@ CrystFEL stream format 2.3
Command line: indexamajig -i dummy.lst -o dummy.stream --kraken=prawn
----- Begin geometry file -----
photon_energy = 9000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 0
panel/max_fs = 1023
@@ -15,7 +16,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/process_hkl_check_4 b/tests/process_hkl_check_4
index bfca3b80..63048dd4 100755
--- a/tests/process_hkl_check_4
+++ b/tests/process_hkl_check_4
@@ -7,6 +7,7 @@ CrystFEL stream format 2.3
Command line: indexamajig -i dummy.lst -o dummy.stream --kraken=prawn
----- Begin geometry file -----
photon_energy = 9000 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 0
panel/max_fs = 1023
@@ -15,7 +16,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom1.geom b/tests/wavelength_geom1.geom
index 280cc349..59567ecd 100644
--- a/tests/wavelength_geom1.geom
+++ b/tests/wavelength_geom1.geom
@@ -1,4 +1,5 @@
wavelength = /LCLS/wavelength m
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom10.geom b/tests/wavelength_geom10.geom
index f0d7ca96..2ad31d19 100644
--- a/tests/wavelength_geom10.geom
+++ b/tests/wavelength_geom10.geom
@@ -1,4 +1,5 @@
photon_energy = 9e3 eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom11.geom b/tests/wavelength_geom11.geom
index d0a49612..873ba099 100644
--- a/tests/wavelength_geom11.geom
+++ b/tests/wavelength_geom11.geom
@@ -1,4 +1,5 @@
wavelength = 1.125 A
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom12.geom b/tests/wavelength_geom12.geom
index 81308932..832467c8 100644
--- a/tests/wavelength_geom12.geom
+++ b/tests/wavelength_geom12.geom
@@ -1,4 +1,5 @@
wavelength = 1.125e-10 m
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom2.geom b/tests/wavelength_geom2.geom
index 68d77ff2..911b62fb 100644
--- a/tests/wavelength_geom2.geom
+++ b/tests/wavelength_geom2.geom
@@ -1,4 +1,5 @@
photon_energy = /LCLS/photon_energy
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom3.geom b/tests/wavelength_geom3.geom
index 5d989d48..a3a540c6 100644
--- a/tests/wavelength_geom3.geom
+++ b/tests/wavelength_geom3.geom
@@ -1,4 +1,5 @@
photon_energy = /LCLS/photon_energyK keV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom4.geom b/tests/wavelength_geom4.geom
index e3a70045..d3e8fce8 100644
--- a/tests/wavelength_geom4.geom
+++ b/tests/wavelength_geom4.geom
@@ -1,4 +1,5 @@
electron_voltage = /LCLS/electron_energy2 kV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom5.geom b/tests/wavelength_geom5.geom
index bc5de6cf..13b02bef 100644
--- a/tests/wavelength_geom5.geom
+++ b/tests/wavelength_geom5.geom
@@ -1,4 +1,5 @@
electron_voltage = /LCLS/electron_energy V
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom6.geom b/tests/wavelength_geom6.geom
index da09f6cc..59b342c5 100644
--- a/tests/wavelength_geom6.geom
+++ b/tests/wavelength_geom6.geom
@@ -1,4 +1,5 @@
photon_energy = /LCLS/photon_energy eV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom7.geom b/tests/wavelength_geom7.geom
index c22f89e8..7756475f 100644
--- a/tests/wavelength_geom7.geom
+++ b/tests/wavelength_geom7.geom
@@ -1,4 +1,5 @@
photon_energy = 9000
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom8.geom b/tests/wavelength_geom8.geom
index 4bcf2c98..f8b3a413 100644
--- a/tests/wavelength_geom8.geom
+++ b/tests/wavelength_geom8.geom
@@ -1,4 +1,5 @@
electron_voltage = 300 kV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array
diff --git a/tests/wavelength_geom9.geom b/tests/wavelength_geom9.geom
index 992c10a3..94394163 100644
--- a/tests/wavelength_geom9.geom
+++ b/tests/wavelength_geom9.geom
@@ -1,4 +1,5 @@
photon_energy = 9 keV
+clen = 50 mm
panel/min_fs = 0
panel/min_ss = 1
panel/max_fs = 0
@@ -7,7 +8,6 @@ panel/fs = x
panel/ss = y
panel/corner_x = -100
panel/corner_y = -100
-panel/clen = 50 mm
panel/res = 1000000
panel/adu_per_photon = 1
panel/data = /data/data_array