Write the grid stage as a base stage, not head-mounted
The grid translations went innermost, i.e. mounted on the head, so a grid position turned with the spindle. At SLS the grid is an Aerotech xyz that the spindle is mounted ON, so the mounting order is base -> grid -> omega -> chi -> phi -> sample and a grid position is independent of omega. Identical to the previous chain at omega = 0, which is every grid scan collected so far, and correct rather than incorrect when it is not. A head-mounted stage exists too - the Smargon translates, and that is what helical uses - and would sit on the other side of omega. Only the base stage is modelled for now, which is the one actually used; the comment says so. Measured: dials.import reads a master with the new chain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@ This is an UNSTABLE release. It includes many experimental features, as well as
|
||||
* HDF5: a data file missing next to a VDS master now reads as the error-pixel marker instead of zero counts, so those frames are masked rather than silently integrated as blank.
|
||||
* The writer refuses a stream whose start message declares a different pixel format than its images carry, instead of writing a master that does not describe its own data.
|
||||
* A grid scan and a goniometer axis are no longer alternatives - both can be set, and the grid scan is no longer silently dropped when an axis is present.
|
||||
* HDF5: a grid-scan file now records the (stationary) spindle, so it can be opened by programs that require a rotation axis; DIALS refused the previous files outright.
|
||||
* HDF5: a grid-scan file now records the (stationary) spindle, so it can be opened by programs that require a rotation axis; DIALS refused the previous files outright. The grid stage is written as a base stage, so a grid position does not turn with the spindle.
|
||||
* The rotation axis is read back from HDF5 under whatever name it carries; only `omega` was recognised before, so a sweep recorded as e.g. `phi` re-opened as stills with nothing to say so.
|
||||
* A goniometer axis that does not turn is now kept rather than discarded, and is distinguished from a rotation sweep - it records where the head was for a still or a grid scan.
|
||||
* `images_per_file` is now chosen from the acquisition when it is not given: a rotation sweep of at most 20000 images goes into a single data file, a grid scan splits on whole fast-axis rows, and stills and serial keep 1000.
|
||||
|
||||
+21
-16
@@ -766,9 +766,13 @@ void NXmx::Sample(const StartMessage &start, const EndMessage &end) {
|
||||
};
|
||||
|
||||
// One chain, built from the base outwards, rather than the goniometer and the grid scan being
|
||||
// alternatives. NXmx applies the deepest dependency first, so writing spindle -> chi -> phi ->
|
||||
// grid translations makes the sample sit at the innermost end, which is what it physically is:
|
||||
// the grid stage rides on the head, and the head rides on the spindle.
|
||||
// alternatives. NXmx applies the deepest dependency first, so the order here is the mounting
|
||||
// order: the grid stage is a BASE stage (an Aerotech xyz at SLS) that the spindle is mounted on,
|
||||
// the spindle carries the head, and the head carries the sample. So
|
||||
// base -> grid -> omega -> chi -> phi -> sample
|
||||
// and a grid position therefore does NOT turn with omega. (A head-mounted grid stage exists too
|
||||
// - the Smargon can translate - and would sit on the other side of omega; only the base stage is
|
||||
// modelled for now, which is what is actually used.)
|
||||
const bool write_goniometer = (end.max_image_number > 0) && start.goniometer.has_value();
|
||||
const bool write_grid_scan = start.grid_scan.has_value();
|
||||
|
||||
@@ -788,6 +792,20 @@ void NXmx::Sample(const StartMessage &start, const EndMessage &end) {
|
||||
transformations.NXClass("NXtransformations");
|
||||
hdf5_file->HardLink("/entry/sample/transformations","/entry/sample/goniometer");
|
||||
|
||||
// Base stage first: everything else is mounted on it. The position containers hold one entry
|
||||
// per image and are empty if the scan stopped at the first image.
|
||||
if (write_grid_scan && (end.max_image_number > 0)) {
|
||||
SaveVector(transformations,"grid_scan_x", start.grid_scan->GetXContainer_m(end.max_image_number))
|
||||
->Transformation("m", depends_on, "", "",
|
||||
"translation", {1, 0, 0}, {0,0,0}, "");
|
||||
depends_on = "/entry/sample/transformations/grid_scan_x";
|
||||
|
||||
SaveVector(transformations,"grid_scan_y", start.grid_scan->GetYContainer_m(end.max_image_number))
|
||||
->Transformation("m", depends_on, "", "",
|
||||
"translation", {0, 1, 0}, {0,0,0}, "");
|
||||
depends_on = "/entry/sample/transformations/grid_scan_y";
|
||||
}
|
||||
|
||||
if (write_goniometer) {
|
||||
// Prefer the rotation axis refined by the offline analysis (rugnux); the broker leaves it empty
|
||||
// and the user-provided goniometer axis stands.
|
||||
@@ -852,19 +870,6 @@ void NXmx::Sample(const StartMessage &start, const EndMessage &end) {
|
||||
}
|
||||
}
|
||||
|
||||
// The grid stage is mounted on the head, so its translations are innermost. The position
|
||||
// containers hold one entry per image and are empty if the scan stopped at the first image.
|
||||
if (write_grid_scan && (end.max_image_number > 0)) {
|
||||
SaveVector(transformations,"grid_scan_x", start.grid_scan->GetXContainer_m(end.max_image_number))
|
||||
->Transformation("m", depends_on, "", "",
|
||||
"translation", {1, 0, 0}, {0,0,0}, "");
|
||||
depends_on = "/entry/sample/transformations/grid_scan_x";
|
||||
|
||||
SaveVector(transformations,"grid_scan_y", start.grid_scan->GetYContainer_m(end.max_image_number))
|
||||
->Transformation("m", depends_on, "", "",
|
||||
"translation", {0, 1, 0}, {0,0,0}, "");
|
||||
depends_on = "/entry/sample/transformations/grid_scan_y";
|
||||
}
|
||||
}
|
||||
|
||||
group.SaveScalar("depends_on", depends_on);
|
||||
|
||||
Reference in New Issue
Block a user