diff --git a/common/BraggIntegrationSettings.cpp b/common/BraggIntegrationSettings.cpp index aa8ae524..20f62598 100644 --- a/common/BraggIntegrationSettings.cpp +++ b/common/BraggIntegrationSettings.cpp @@ -108,3 +108,16 @@ BraggIntegrationSettings &BraggIntegrationSettings::BackgroundTrimFraction(float float BraggIntegrationSettings::GetBackgroundTrimFraction() const { return bkg_trim_fraction; } + +BraggIntegrationSettings &BraggIntegrationSettings::MaxHKL(int input) { + check_min("Maximum hkl index", input, 1); + // The GPU predictor launches one thread per candidate, so the cost is (2n+1)^3: 511 is 1.1e9 + // candidates per frame, already far past the point where prediction dominates a run. + check_max("Maximum hkl index", input, 511); + max_hkl = input; + return *this; +} + +int BraggIntegrationSettings::GetMaxHKL() const { + return max_hkl; +} diff --git a/common/BraggIntegrationSettings.h b/common/BraggIntegrationSettings.h index 974dc47c..0ff58a9a 100644 --- a/common/BraggIntegrationSettings.h +++ b/common/BraggIntegrationSettings.h @@ -27,6 +27,12 @@ class BraggIntegrationSettings { // keeps the tuned high-side sigma-clip for broadband (non-zero bandwidth) data instead. 0 = plain ring // mean (rugnux --background-trim). float bkg_trim_fraction = 0.10f; + // Half-width of the hkl cube the predictor walks: every reflection with |h|,|k|,|l| <= this is + // tested against the Ewald sphere, and nothing outside it can ever be predicted. An axis is + // truncated once a/d_min exceeds this, so 100 covers a 150 A axis at 1.5 A but not at 1.0 A. The + // cost is the cube (2n+1)^3 of candidates on the GPU, which is why it is not simply set high: + // raising it lengthens every frame's prediction whether or not the cell needs the room. + int max_hkl = 100; public: BraggIntegrationSettings& R1(float input); @@ -36,6 +42,7 @@ public: BraggIntegrationSettings& FixedProfileRadius_recipA(std::optional input); BraggIntegrationSettings& Integrator(IntegratorMode input); BraggIntegrationSettings& BackgroundTrimFraction(float input); + BraggIntegrationSettings& MaxHKL(int input); [[nodiscard]] IntegratorMode GetIntegrator() const; @@ -47,4 +54,5 @@ public: [[nodiscard]] float GetMinimumSigmaInRegardsToI() const; [[nodiscard]] float GetBackgroundTrimFraction() const; + [[nodiscard]] int GetMaxHKL() const; }; diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2d795fec..d1e5e728 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -13,6 +13,7 @@ This is an UNSTABLE release. It includes many experimental features, as well as * rugnux: Per-image **geometry refinement** weights spots by confidence. * rugnux: `--min-image-cc` now works for rotation data (opt-in). * rugnux: New `--search-min-zeta` (rotation default 0.85). +* rugnux: New `--max-hkl` sets how far the Bragg predictor walks the lattice (default 100, unchanged). An axis is truncated once `a/d_min` exceeds it, so a long-axis or high-resolution dataset was quietly losing its outermost reflections; raising it recovers them at a cost that grows as the cube. * rugnux: Reports how close a symmetry axis lies to the spindle. * rugnux: Azimuthal-integration and spot-finding resolution limits default to the detector — including **rotation** data, which no longer keeps a 1.5 Å spot-finding limit (`--spot-high-resolution ` still sets one). * **API (breaking for older clients)**: the `image_scale_b` value is gone from the `plot_type` enum, so a client that requests that plot now gets an error rather than a curve. Also, `azim_int_settings.high_q_recipA` and `spot_finding_settings.high_resolution_limit` are no longer `required`. Both now mean "as far as the detector reaches" when unset, and are **omitted** from the response instead of carrying a placeholder value. A client generated from an rc.160-or-earlier spec that assumes the field is always present (the generated C++ `from_json` does `j.at(...)`) raises when it is missing. Because the azimuthal-integration limit now defaults to unset, a stock rc.161 broker omits `high_q_recipA` from `GET /config/azim_int` with no operator action. Regenerate the client (`jfjoch-client` 1.0.0-rc.161, `frontend/src/client`) or read both fields as optional. diff --git a/docs/RUGNUX.md b/docs/RUGNUX.md index 2c524ed9..135a2d39 100644 --- a/docs/RUGNUX.md +++ b/docs/RUGNUX.md @@ -268,6 +268,7 @@ Integration: | `--integrator ` | Spot integrator: `gaussian` (profile-fit, default) \| `empirical` \| `boxsum` (classical fallback) | | `--integration-radius ` | Signal-box radius `r1`, or `r1,r2,r3` (px). One value ⇒ `r2=r1+2`, `r3=r1+4` | | `--background-trim ` | Monochromatic (rotation + still): symmetric trimmed-mean fraction for the background ring, 0≤f<0.5 (default 0.10; 0 = plain mean) — removes the high-side bias that over-subtracts weak high-angle spots | +| `--max-hkl ` | Predict reflections with \|h\|,\|k\|,\|l\| ≤ `n` (default 100, max 511). An axis is truncated once `a/d_min` exceeds `n`, so a long axis — or a short one taken to high resolution — silently loses its outermost reflections. Cost grows as `(2n+1)³` candidates per frame, so raise it only where the cell needs the room | | `--bandwidth ` | Relative X-ray bandwidth FWHM (e.g. `0.01` for a 1% DMM); default from file or 0 (monochromatic) | Geometry overrides (defaults are taken from the input file; override them to reprocess with a corrected geometry): diff --git a/image_analysis/IndexAndRefine.cpp b/image_analysis/IndexAndRefine.cpp index b89f7ade..a429e5e4 100644 --- a/image_analysis/IndexAndRefine.cpp +++ b/image_analysis/IndexAndRefine.cpp @@ -438,7 +438,7 @@ void IndexAndRefine::QuickPredictAndIntegrate(DataMessage &msg, const BraggPredictionSettings settings_prediction{ .high_res_A = experiment.GetBraggIntegrationSettings().GetDMinLimit_A(), .ewald_dist_cutoff = ewald_dist_cutoff, - .max_hkl = 100, + .max_hkl = experiment.GetBraggIntegrationSettings().GetMaxHKL(), // Centering is a hypothesis to confirm, not assume: with no user-fixed space group, predict // in P so the centering-absent reflections are integrated and the space-group search can // confirm or disprove centering (and catch a missed superstructure). A user-fixed space diff --git a/rugnux/RugnuxCommandLine.cpp b/rugnux/RugnuxCommandLine.cpp index 83118757..a6b7683f 100644 --- a/rugnux/RugnuxCommandLine.cpp +++ b/rugnux/RugnuxCommandLine.cpp @@ -124,6 +124,8 @@ std::string RugnuxCommandLine(const ProcessConfig &config, // custom fraction, or 0 when the box is unchecked) to reproduce the GUI's choice faithfully. if (bragg.GetBackgroundTrimFraction() != 0.10f) add("--background-trim", num(bragg.GetBackgroundTrimFraction())); + if (bragg.GetMaxHKL() != 100) + add("--max-hkl", std::to_string(bragg.GetMaxHKL())); if (config.rotation_indexing) { if (config.two_pass_rotation) diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index 15d6b4c9..32c6f891 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -117,6 +117,7 @@ void print_usage() { std::cout << " Integration" << std::endl; std::cout << " --bandwidth Relative X-ray bandwidth FWHM (e.g. 0.01 for 1% DMM); default from file or 0" << std::endl; std::cout << " --integration-radius Signal-box radius r1, or r1,r2,r3 (px). One value => r2=r1+2, r3=r1+4" << std::endl; + std::cout << " --max-hkl Predict reflections with |h|,|k|,|l| <= n (default 100). Raise for a long axis: an axis is truncated once a/d_min exceeds n. Cost grows as (2n+1)^3" << std::endl; std::cout << " --background-trim Monochromatic (rotation + still): symmetric trimmed-mean fraction for the background ring (0<=f<0.5, default 0.10; 0 = plain mean). Removes the high-side bias that over-subtracts weak high-angle spots (broadband data keep the sigma-clip instead)" << std::endl; std::cout << " --integrator Spot integrator boxsum|gaussian|empirical (default: gaussian profile-fit; boxsum is the classical fallback)" << std::endl; std::cout << " --simple-stills stills: treat every reflection as a full (p=1, single-pass scale/merge); disables the default physical partiality post-refinement" << std::endl; @@ -164,6 +165,7 @@ enum { OPT_BANDWIDTH, OPT_INTEGRATION_RADIUS, OPT_BACKGROUND_TRIM, + OPT_MAX_HKL, OPT_REJECT_OUTLIERS, OPT_REFERENCE_COLUMN, OPT_MODEL, @@ -271,6 +273,7 @@ static option long_options[] = { {"bandwidth", required_argument, nullptr, OPT_BANDWIDTH}, {"integration-radius", required_argument, nullptr, OPT_INTEGRATION_RADIUS}, {"background-trim", required_argument, nullptr, OPT_BACKGROUND_TRIM}, + {"max-hkl", required_argument, nullptr, OPT_MAX_HKL}, {"integrator", required_argument, nullptr, OPT_INTEGRATOR}, {"simple-stills", no_argument, nullptr, OPT_SIMPLE_STILLS}, {"detect-ice-rings", optional_argument, nullptr, OPT_DETECT_ICE_RINGS}, @@ -550,6 +553,7 @@ static int RunRugnux(int argc, char **argv) { std::optional report_shell_count; // --resolution-shells std::optional integration_radius_arg; // "r1" or "r1,r2,r3" std::optional background_trim_arg; // --background-trim: background-ring trimmed-mean fraction + std::optional max_hkl_arg; // --max-hkl: half-width of the predicted hkl cube std::optional integrator_mode; // --integrator boxsum|gaussian|empirical bool simple_stills_flag = false; // --simple-stills: disable the default stills partiality post-refinement std::optional outlier_reject_nsigma; // merge per-observation outlier rejection @@ -841,6 +845,9 @@ static int RunRugnux(int argc, char **argv) { case OPT_BACKGROUND_TRIM: background_trim_arg = parse_double_arg(optarg, "--background-trim", logger); break; + case OPT_MAX_HKL: + max_hkl_arg = parse_number_arg(optarg, "--max-hkl", logger, 1, 511); + break; case OPT_INTEGRATOR: if (strcmp(optarg, "boxsum") == 0) integrator_mode = IntegratorMode::BoxSum; else if (strcmp(optarg, "gaussian") == 0) integrator_mode = IntegratorMode::ProfileGaussian; @@ -1490,6 +1497,13 @@ static int RunRugnux(int argc, char **argv) { : "profile (empirical)"); } + if (max_hkl_arg) { + BraggIntegrationSettings bis = experiment.GetBraggIntegrationSettings(); + bis.MaxHKL(static_cast(*max_hkl_arg)); + experiment.ImportBraggIntegrationSettings(bis); + logger.Info("Predicting reflections with |h|,|k|,|l| <= {}", *max_hkl_arg); + } + if (background_trim_arg) { BraggIntegrationSettings bis = experiment.GetBraggIntegrationSettings(); bis.BackgroundTrimFraction(static_cast(*background_trim_arg));