diff --git a/docs/CPU_DATA_ANALYSIS_INDEXING.md b/docs/CPU_DATA_ANALYSIS_INDEXING.md index 0005ae78d..8f703a9b8 100644 --- a/docs/CPU_DATA_ANALYSIS_INDEXING.md +++ b/docs/CPU_DATA_ANALYSIS_INDEXING.md @@ -436,10 +436,14 @@ report), with its cell to process it with `-C`. The cell written is the merged f without a given cell. **Detector modules.** Where the detector is described module by module -([Module geometry](RUGNUX_FORMATS.md#module-geometry)), with `--panel-refine` the same refinement places each module with +([Module geometry](RUGNUX_FORMATS.md#module-geometry)), the same refinement places each module with enough spots on its frames' lattices as a rigid block - a shift and an in-plane turn - where that predicts held-out spots better: the strong frames are split in half, each half fitted with the modules -fixed and with them placed, and scored on the other. The modules move only relative to one another: a +fixed and with them placed, and scored on the other. The placement is used if it predicts the held-out +spots better as a whole, and then each module keeps its move only if the held-out spots on that module +are predicted better; the others go back to where they were given and the placement is fitted again. +On a detector of many modules the outer ones, reached only by a few strong high-resolution spots, are +the ones this decides on. `--panel-refine` keeps every move without the tests, `--panel-refine=off` none. The modules move only relative to one another: a gauge holds their spot-weighted mean shift, turn and outward push, and with the detector tilt refined also the two tilt-like gradients across the detector, at zero, so the beam centre, distance, tilt and crystal orientations carry what all modules share. A coarse search of each module's position comes diff --git a/docs/JFJOCH_BROKER.md b/docs/JFJOCH_BROKER.md index 7ff212b12..81a22e8f6 100644 --- a/docs/JFJOCH_BROKER.md +++ b/docs/JFJOCH_BROKER.md @@ -25,8 +25,8 @@ has any, its statistics (`/statistics/data_collection`, `/result/scan`), buffere and answer `401` otherwise; `/statistics` omits its `measurement` block instead. See [Security](SECURITY.md#3-authenticated-read-access--per-dataset-bearer-tokens). -`/start` also takes an optional `module_geometry`: where the JUNGFRAU modules are, as `rugnux ---panel-refine` measures them on an earlier run (the `module_geometry` array of its `_panels.json`). +`/start` also takes an optional `module_geometry`: where the JUNGFRAU modules are, as `rugnux` +measures them on an earlier run (the `module_geometry` array of its `_panels.json`). Each entry names its module by the module's box in the assembled image, so the order does not matter, and a list that does not name exactly the detector's modules is refused with `400`. The image is assembled as configured; the placement goes into the start message, the master file's `module_NN` groups and the diff --git a/docs/RUGNUX_CLI.md b/docs/RUGNUX_CLI.md index e2729ed22..bba548482 100644 --- a/docs/RUGNUX_CLI.md +++ b/docs/RUGNUX_CLI.md @@ -362,7 +362,7 @@ Defaults are taken from the input file; override them to reprocess with a correc | `--crystfel-geometry ` | Place the panels of an HDF5/CXI input by a CrystFEL geometry file instead of the file's own metadata: module geometry, camera length, mask arrays and bad regions (optional; [Serial XFEL inputs](RUGNUX_FORMATS.md#serial-xfel-inputs)). Photon energy, camera length and - where the geometry names no mask array - the masks otherwise come from the data files (Cheetah CXI, SACLA) or the command line; gain and saturation are measured. Tilted panels, `dimN` layouts, per-panel data and detector shifts are refused | | `--module-geometry ` | Where the detector's modules are: an NXmx file with one NXdetector_module per module (what a run writes as `_modules.h5`) or a CrystFEL geometry file whose panels are laid on the image. Overrides the input's own | | `--panels ` | Place the modules as a module placement file says (`_panels.json` of an earlier run; [Module placement file](RUGNUX_FORMATS.md#module-placement-file-prefix_panelsjson)). Entries name modules by their box in the image; one that matches no module, or a module left out, is an error | -| `--panel-refine[=diagnose]` | Stills: place each module as a rigid block in the geometry bundle adjustment, where that predicts held-out spots better, and use the placement from there on (default: off - modules stay where they were given, a CrystFEL `.geom` included). The placement is written to `_modules.h5` and `_panels.json`. `=diagnose` also runs a set of tests on the placement and writes them to the log | +| `--panel-refine[=off\|diagnose]` | Stills on a detector of several modules: by default the geometry bundle adjustment places each module as a rigid block where that predicts held-out spots better - the whole placement must beat the given one, and each module keeps its move only where its own held-out spots are predicted better, the others staying where they were given. `--panel-refine` keeps every move the spots support, without these tests; `=off` keeps the modules as given (a CrystFEL `.geom` included). A placement used is written to `_modules.h5` and `_panels.json`. `=diagnose` also runs a set of tests on the placement and writes them to the log | | `--mask-good [:][@]` / `--mask-bad [:][@]` | HDF5/CXI stills: a mask array in the frames' stored layout, in `` or in each data file; usable only where non-zero (or with all ``) / unusable where non-zero (or with any ``). Repeatable ([Serial XFEL inputs](RUGNUX_FORMATS.md#serial-xfel-inputs)) | | `--rot1 ` | PONI detector rotation 1 (rad). On stills, `--rot1` or `--rot2` holds the tilt (not refined); without them stills refine the tilt where held-out frames agree | | `--rot2 ` | PONI detector rotation 2 (rad) | diff --git a/docs/RUGNUX_FORMATS.md b/docs/RUGNUX_FORMATS.md index c82d88acc..25a172b1b 100644 --- a/docs/RUGNUX_FORMATS.md +++ b/docs/RUGNUX_FORMATS.md @@ -152,8 +152,8 @@ geometry, never from the image: Spots are mapped from the image onto the plane, and predictions from the plane back onto the image for integration, so the image itself is never re-assembled. With a cell given, the stills geometry refinement can place each module with enough spots on its frames' lattices as a rigid block (shift and in-plane -turn, `--panel-refine`, off by default: without it the modules stay where they were given, a CrystFEL -`.geom`'s panels included) where that predicts held-out spots better; modules move only relative to one +turn; `--panel-refine=off` keeps them where they were given, a CrystFEL `.geom`'s panels included) +where that predicts held-out spots better, module by module; modules move only relative to one another, the beam centre, distance and crystal orientations carrying what all of them share. The placement is used from there on, and written to `_modules.h5` (read back with `--module-geometry`) and `_panels.json` (read back with `--panels`, and taken by the broker's diff --git a/docs/RUGNUX_TUTORIAL.md b/docs/RUGNUX_TUTORIAL.md index 992aa1af0..f0575a2c6 100644 --- a/docs/RUGNUX_TUTORIAL.md +++ b/docs/RUGNUX_TUTORIAL.md @@ -324,8 +324,8 @@ Absorption is corrected only by the empirical surface a rotation run fits and ke [The results report](RUGNUX_REPORT.md#the-results-report). - `_log.txt` — the full timestamped log of the run, at debug level, written by every run with an output prefix; send it with the report when asking about a run. -- `_modules.h5`, `_panels.json` — serial stills on a detector of several modules, with - `--panel-refine`: where the geometry refinement placed each module, readable back with +- `_modules.h5`, `_panels.json` — serial stills on a detector of several modules, where + the geometry refinement placed the modules: where it placed each one, readable back with `--module-geometry` / `--panels` (the JSON also by the broker's `/start`; see [Module geometry](RUGNUX_FORMATS.md#module-geometry)). - `_plot.txt` — **the per-image table**, one row per processed image, whitespace-separated, diff --git a/image_analysis/geom_refinement/GeometryRefiner.cpp b/image_analysis/geom_refinement/GeometryRefiner.cpp index 581aebfd7..835cd677a 100644 --- a/image_analysis/geom_refinement/GeometryRefiner.cpp +++ b/image_analysis/geom_refinement/GeometryRefiner.cpp @@ -435,7 +435,8 @@ GeometryRefinerResult RefineGlobalGeometry(const DiffractionGeometry &nominal_ge std::vector refined(modules.size(), false); int refined_count = 0; for (size_t m = 0; m < modules.size(); ++m) - if (settings.refine_modules && module_spots[m] >= settings.min_module_spots) { + if (settings.refine_modules && module_spots[m] >= settings.min_module_spots + && !(m < settings.modules_held.size() && settings.modules_held[m])) { refined[m] = true; refined_count++; } diff --git a/image_analysis/geom_refinement/GeometryRefiner.h b/image_analysis/geom_refinement/GeometryRefiner.h index ac8ebe192..d992c3a69 100644 --- a/image_analysis/geom_refinement/GeometryRefiner.h +++ b/image_analysis/geom_refinement/GeometryRefiner.h @@ -59,7 +59,9 @@ struct GeometryRefinerSettings { // go back to where they were given (no move), whatever their start. std::vector modules; bool refine_modules = false; - int min_module_spots = 500; + int min_module_spots = 100; + // Modules held where they were given whatever their spots (as modules; empty: none held). + std::vector modules_held; // Refine the detector tilt (PONI rot1, rot2), within 2 degrees of the nominal one, together with the beam, // the distance and (with refine_modules) the modules. The beam is then fitted as the direct beam, and its // box (beam_range_px) is about where the nominal geometry puts the direct beam: in PONI terms a tilt moves diff --git a/rugnux/Rugnux.h b/rugnux/Rugnux.h index 442d6c027..79d4e019e 100644 --- a/rugnux/Rugnux.h +++ b/rugnux/Rugnux.h @@ -168,8 +168,11 @@ struct ProcessConfig { // Within that bundle adjustment, a detector of several modules (the experiment's DetectorModules: the // file's, the reader's panels, or --module-geometry) has each module placed as a rigid block, where // moving them predicts the spots better on frames the move was not fitted to; the placement found is - // used from there on (--panel-refine). Off, the modules stay where they were given. + // used from there on; each module keeps its move only where that predicts its own held-out spots better. + // Off (--panel-refine=off), the modules stay where they were given. bool refine_modules = false; + // Every module the spots place keeps its move, without the held-out tests (--panel-refine). + bool force_modules = false; // With it, a run of tests on the placement (ModuleDiagnostics.h) goes to the log (--panel-refine=diagnose). bool module_diagnostics = false; diff --git a/rugnux/RugnuxStills.cpp b/rugnux/RugnuxStills.cpp index 38945d030..2abb4fffc 100644 --- a/rugnux/RugnuxStills.cpp +++ b/rugnux/RugnuxStills.cpp @@ -124,49 +124,75 @@ struct HeldOutFit { DiffractionGeometry geom; }; -// PANEL STUDY (RUGNUX_PANEL_STUDY): the held-out residual of each fold per module and per resolution shell, -// and each module's shift on the two halves, to the log under "PanelStudy". -void LogPanelStudy(Logger &logger, const std::string &label, const std::vector half[2], - const HeldOutFit fits[2]) { - constexpr double CAP_PX = 2.0; - const std::vector edges = {1e9, 4.0, 3.0, 2.5, 2.2, 2.0, 1.8, 1.6, 1.4, 0.0}; - const size_t nm = fits[0].modules.size(); - std::vector m_sq(nm, 0), s_sq(edges.size(), 0); - std::vector m_n(nm, 0), m_hit(nm, 0), s_n(edges.size(), 0), s_hit(edges.size(), 0); - for (int fit = 0; fit < 2; fit++) { - const auto &e = fits[fit].eval; - const auto &frames = half[1 - fit]; - if (e.predicted_px.size() != frames.size()) - continue; - for (size_t i = 0; i < frames.size(); i++) - for (size_t j = 0; j < frames[i].spots.size() && j < e.predicted_px[i].size(); j++) { - const auto &sp = frames[i].spots[j]; - const auto &pr = e.predicted_px[i][j]; - double r = CAP_PX; - if (std::isfinite(pr[0]) && std::isfinite(pr[1])) - r = std::min(CAP_PX, static_cast(std::hypot(pr[0] - sp.x, pr[1] - sp.y))); - const double d = 1.0 / std::max(1e-6f, fits[fit].geom.DetectorToRecip(sp.x, sp.y).Length()); - size_t sh = 1; - while (sh + 1 < edges.size() && d < edges[sh]) - sh++; - s_sq[sh] += r * r; s_n[sh]++; s_hit[sh] += r < 1.0; - if (sp.module >= 0 && static_cast(sp.module) < nm) { - m_sq[sp.module] += r * r; m_n[sp.module]++; m_hit[sp.module] += r < 1.0; +// The held-out residuals of the two folds pooled, per module and per resolution shell: each spot's distance to +// its prediction, capped at 2 px as in capped_rms_px. hit counts the spots within 1 px. +struct HeldOutTally { + static constexpr double CAP_PX = 2.0; + static inline const std::vector EDGES = {1e9, 4.0, 3.0, 2.5, 2.2, 2.0, 1.8, 1.6, 1.4, 0.0}; + struct Sum { + double sq = 0; + int n = 0, hit = 0; + double Rms() const { return n > 0 ? std::sqrt(sq / n) : 0.0; } + double Hit() const { return n > 0 ? hit / static_cast(n) : 0.0; } + }; + std::vector module; // per module + std::vector> module_shell; // per module, per shell (EDGES[s - 1] > d >= EDGES[s]) + std::vector shell; + + HeldOutTally(const std::vector half[2], const HeldOutFit fits[2], size_t nmodules) + : module(nmodules), module_shell(nmodules, std::vector(EDGES.size())), shell(EDGES.size()) { + for (int fit = 0; fit < 2; fit++) { + const auto &e = fits[fit].eval; + const auto &frames = half[1 - fit]; + if (e.predicted_px.size() != frames.size()) + continue; + for (size_t i = 0; i < frames.size(); i++) + for (size_t j = 0; j < frames[i].spots.size() && j < e.predicted_px[i].size(); j++) { + const auto &sp = frames[i].spots[j]; + const auto &pr = e.predicted_px[i][j]; + double r = CAP_PX; + if (std::isfinite(pr[0]) && std::isfinite(pr[1])) + r = std::min(CAP_PX, static_cast(std::hypot(pr[0] - sp.x, pr[1] - sp.y))); + const double d = 1.0 / std::max(1e-6f, fits[fit].geom.DetectorToRecip(sp.x, sp.y).Length()); + size_t sh = 1; + while (sh + 1 < EDGES.size() && d < EDGES[sh]) + sh++; + Add(shell[sh], r); + if (sp.module >= 0 && static_cast(sp.module) < nmodules) { + Add(module[sp.module], r); + Add(module_shell[sp.module][sh], r); + } } - } + } + } + static void Add(Sum &s, double r) { + s.sq += r * r; + s.n++; + s.hit += r < 1.0; + } +}; + +// PANEL STUDY (RUGNUX_PANEL_STUDY): the tally of one hypothesis and each module's shift on the two halves, to +// the log under "PanelStudy". +void LogPanelStudy(Logger &logger, const std::string &label, const HeldOutTally &t, const HeldOutFit fits[2]) { + const auto &E = HeldOutTally::EDGES; + for (size_t sh = 1; sh < E.size(); sh++) + if (t.shell[sh].n > 0) + logger.Info("PanelStudy {} shell {:.2f} {:.2f} n {} rms {:.4f} hit1 {:.4f}", label, E[sh - 1], E[sh], + t.shell[sh].n, t.shell[sh].Rms(), t.shell[sh].Hit()); + for (size_t m = 0; m < t.module.size(); m++) { + if (t.module[m].n == 0 || fits[0].modules.size() <= m || fits[1].modules.size() <= m) + continue; + logger.Info("PanelStudy {} module {} x0 {:.0f} y0 {:.0f} n {} rms {:.4f} hit1 {:.4f} " + "fold0 {:+.3f} {:+.3f} {} fold1 {:+.3f} {:+.3f} {}", label, m, + fits[0].modules[m].x0, fits[0].modules[m].y0, t.module[m].n, t.module[m].Rms(), t.module[m].Hit(), + fits[0].modules[m].dx, fits[0].modules[m].dy, fits[0].modules[m].spots, + fits[1].modules[m].dx, fits[1].modules[m].dy, fits[1].modules[m].spots); + for (size_t sh = 1; sh < E.size(); sh++) + if (t.module_shell[m][sh].n > 0) + logger.Info("PanelStudy {} pshell {} {:.2f} {:.2f} n {} rms {:.4f} hit1 {:.4f}", label, m, E[sh - 1], + E[sh], t.module_shell[m][sh].n, t.module_shell[m][sh].Rms(), t.module_shell[m][sh].Hit()); } - for (size_t sh = 1; sh < edges.size(); sh++) - if (s_n[sh] > 0) - logger.Info("PanelStudy {} shell {:.2f} {:.2f} n {} rms {:.4f} hit1 {:.4f}", label, edges[sh - 1], edges[sh], - s_n[sh], std::sqrt(s_sq[sh] / s_n[sh]), s_hit[sh] / static_cast(s_n[sh])); - for (size_t m = 0; m < nm; m++) - if (m_n[m] > 0) - logger.Info("PanelStudy {} module {} x0 {:.0f} y0 {:.0f} n {} rms {:.4f} hit1 {:.4f} " - "fold0 {:+.3f} {:+.3f} {} fold1 {:+.3f} {:+.3f} {}", label, m, - fits[0].modules[m].x0, fits[0].modules[m].y0, m_n[m], std::sqrt(m_sq[m] / m_n[m]), - m_hit[m] / static_cast(m_n[m]), - fits[0].modules[m].dx, fits[0].modules[m].dy, fits[0].modules[m].spots, - fits[1].modules[m].dx, fits[1].modules[m].dy, fits[1].modules[m].spots); } bool SameGeometry(const DiffractionGeometry &a, const DiffractionGeometry &b) { @@ -559,13 +585,12 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_ }); // Held-out residual of the geometry fitted on half `fit`, scored on every spot of the other half. - auto held_out = [&](const std::vector &modules, bool refine, int fit, int min_spots) { + auto held_out = [&](const std::vector &modules, bool refine, int fit) { HeldOutFit h; GeometryRefinerSettings sf = gr_settings; sf.num_threads = std::max(1, config_.nthreads / 8); sf.modules = modules; sf.refine_modules = refine; - sf.min_module_spots = min_spots; const auto f = RefineGlobalGeometry(nominal, *cell, half[fit], sf); if (!f.ok) return h; @@ -582,55 +607,77 @@ void Rugnux::RefineStillsGeometry(int start_image, int end_image, int images_to_ h.rms = h.eval.capped_rms_px; return h; }; - const int min_spots = gr_settings.min_module_spots; std::future held_fits[2], moved_fits[2]; for (int fit = 0; fit < 2; fit++) { - held_fits[fit] = std::async(std::launch::async, held_out, std::cref(centres), false, fit, min_spots); - moved_fits[fit] = std::async(std::launch::async, held_out, std::cref(start), true, fit, min_spots); + held_fits[fit] = std::async(std::launch::async, held_out, std::cref(centres), false, fit); + moved_fits[fit] = std::async(std::launch::async, held_out, std::cref(start), true, fit); } - HeldOutFit held_f[2] = {held_fits[0].get(), held_fits[1].get()}; - HeldOutFit moved_f[2] = {moved_fits[0].get(), moved_fits[1].get()}; + const HeldOutFit held_f[2] = {held_fits[0].get(), held_fits[1].get()}; + const HeldOutFit moved_f[2] = {moved_fits[0].get(), moved_fits[1].get()}; const double held = (held_f[0].rms + held_f[1].rms) / 2.0; const double moved = (moved_f[0].rms + moved_f[1].rms) / 2.0; + const HeldOutTally held_t(half, held_f, centres.size()); + const HeldOutTally moved_t(half, moved_f, centres.size()); if (std::getenv("RUGNUX_PANEL_STUDY")) { - LogPanelStudy(logger, "given", half, held_f); - LogPanelStudy(logger, "placed", half, moved_f); - HeldOutFit low_f[2] = {held_out(start, true, 0, 100), held_out(start, true, 1, 100)}; - LogPanelStudy(logger, "placed100", half, low_f); - logger.Info("PanelStudy summary given {:.4f} placed {:.4f} placed100 {:.4f}", held, moved, - (low_f[0].rms + low_f[1].rms) / 2.0); + LogPanelStudy(logger, "given", held_t, held_f); + LogPanelStudy(logger, "placed", moved_t, moved_f); } - const GeometryRefinerResult rm = placement.get(); + GeometryRefinerResult rm = placement.get(); placement_mark.Stop(); logger.Info("Module placement: {} modules; held-out spot residual (capped rms) {:.3f} px as given, " "{:.3f} px with each placed", centres.size(), held, moved); - if (moved < held) { - if (rm.ok) { - for (size_t m = 0; m < rm.modules.size(); m++) { - const auto &ms = rm.modules[m]; - logger.Info("Module {} at ({:.0f}, {:.0f}): shift ({:+.2f}, {:+.2f}) px, turn {:+.3f} deg, {} spots", - m, ms.x0, ms.y0, ms.dx, ms.dy, ms.rot_rad * 180.0 / PI, ms.spots); + // Each module the placement moved keeps its move only where the spots on it, on the frames its move + // was not fitted to, are predicted better moved than as given; the others go back to where they were + // given and the placement is fitted again without them. --panel-refine keeps every move. + bool apply = rm.ok && (config_.force_modules || moved < held); + if (apply && !config_.force_modules) { + std::vector hold(centres.size(), false); + int held_back = 0; + for (size_t m = 0; m < rm.modules.size(); m++) { + const bool moved_m = rm.modules[m].dx != 0 || rm.modules[m].dy != 0 || rm.modules[m].rot_rad != 0; + if (moved_m && !(moved_t.module[m].Rms() < held_t.module[m].Rms())) { + hold[m] = true; + held_back++; + logger.Info("Module {} at ({:.0f}, {:.0f}): held as given - held-out residual {:.3f} px placed, " + "{:.3f} px as given ({} spots)", m, centres[m].x0, centres[m].y0, + moved_t.module[m].Rms(), held_t.module[m].Rms(), moved_t.module[m].n); } - // The move is a step from where the modules were given; the experiment now has them there, - // so every spot found and every prediction integrated from here on uses it. The image is - // not touched. - if (config_.module_diagnostics) { - ModuleDiagnosticsInput diag{nominal, *cell, placing, centres, start, st, rm, - experiment_.GetDetectorSetup().GetSensorMaterial(), - experiment_.GetDetectorSetup().GetSensorThickness_um()}; - RunModuleDiagnostics(diag); - } - module_shifts_ = rm.modules; - if (stills_tilt_bundle_) - stills_tilt_bundle_ = StillsTiltBundle{placing, rm.modules}; - DetectorModules placed = given; - placed.Apply(rm.modules); - experiment_.Modules(placed); - r = rm; - logger.Info("Module placement: modules placed (median residual {:.3f} px)", rm.median_residual_px); + } + if (held_back > 0) { + GeometryRefinerSettings again = st; + again.num_threads = config_.nthreads; + again.modules_held = hold; + rm = RefineGlobalGeometry(nominal, *cell, placing, again); + apply = rm.ok; } } + + if (apply) { + for (size_t m = 0; m < rm.modules.size(); m++) { + const auto &ms = rm.modules[m]; + logger.Info("Module {} at ({:.0f}, {:.0f}): shift ({:+.2f}, {:+.2f}) px, turn {:+.3f} deg, {} spots", + m, ms.x0, ms.y0, ms.dx, ms.dy, ms.rot_rad * 180.0 / PI, ms.spots); + } + // The move is a step from where the modules were given; the experiment now has them there, + // so every spot found and every prediction integrated from here on uses it. The image is + // not touched. + if (config_.module_diagnostics) { + ModuleDiagnosticsInput diag{nominal, *cell, placing, centres, start, st, rm, + experiment_.GetDetectorSetup().GetSensorMaterial(), + experiment_.GetDetectorSetup().GetSensorThickness_um()}; + RunModuleDiagnostics(diag); + } + module_shifts_ = rm.modules; + if (stills_tilt_bundle_) + stills_tilt_bundle_ = StillsTiltBundle{placing, rm.modules}; + DetectorModules placed = given; + placed.Apply(rm.modules); + experiment_.Modules(placed); + r = rm; + logger.Info("Module placement: modules placed (median residual {:.3f} px)", rm.median_residual_px); + } else + logger.Info("Module placement: modules kept as given"); } logger.Info("Geometry refinement: beam ({:.2f}, {:.2f}) -> ({:.2f}, {:.2f}) px, distance {:.4f} -> {:.4f} mm", diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index df7ccbf90..11d1e2fb6 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -978,7 +978,8 @@ struct RugnuxCli { bool beam_center_check = true; // --beam-center-check[=off]; on by default std::optional refine_geometry; // --refine-geometry[=N]: stills global geometry-refinement pass bool refine_geometry_disabled = false; // --refine-geometry=off: opt out of the stills default-on - bool refine_modules = false; // --panel-refine + bool refine_modules = true; // placed where held-out spots say so; --panel-refine=off: as given + bool force_modules = false; // --panel-refine: every placeable module moved, no held-out tests bool module_diagnostics = false; // --panel-refine=diagnose std::string module_geometry_file; // --module-geometry std::string panels_file; // --panels @@ -1178,13 +1179,14 @@ int RugnuxCli::Parse(int argc, char **argv) { break; } case OPT_REFINE_MODULES: - if (optarg && std::string(optarg) != "diagnose") { - logger.Error("--panel-refine takes no value or =diagnose, not {}", optarg); + if (optarg && std::string(optarg) != "diagnose" && std::string(optarg) != "off") { + logger.Error("--panel-refine takes no value, =off or =diagnose, not {}", optarg); print_usage(); exit(EXIT_FAILURE); } - refine_modules = true; - module_diagnostics = optarg != nullptr; + refine_modules = !optarg || std::string(optarg) != "off"; + force_modules = !optarg; + module_diagnostics = optarg && std::string(optarg) == "diagnose"; break; case OPT_MODULE_GEOMETRY: module_geometry_file = optarg; @@ -3654,6 +3656,7 @@ int RugnuxInputRun::PrepareRugnuxInput() { config.forced_rotation_lattice = forced_rotation_lattice; config.refine_geometry = refine_geometry; config.refine_modules = refine_modules; + config.force_modules = force_modules; config.module_diagnostics = module_diagnostics; config.run_scaling = run_scaling; config.scaling_iter = scaling_iter;