One changeset, developed together in response to a review of this branch, so the files carry several of the changes at once. Full test suite passes (733 cases). Spot finding - Split ImageSpotFinder into Detect() (flag strong pixels - the expensive per-pixel pass) and ExtractSpots() (CCL + min/max-pix + resolution mask), with Run() = both. The per-image min-pix escalation now detects ONCE and repeats only the cheap extraction, instead of re-running the whole finder four times per frame as it did on the default path. It also keeps the winning attempt's spot list rather than re-extracting it, so the frame that is integrated is exactly the frame that was scored - which a GPU re-extract could not guarantee (float atomic ordering). - spot_finding_time_s no longer swallows indexing time, and indexing_time_s now sums every escalation call instead of reporting only the last. Detection limits follow the detector - The azimuthal-integration upper q and the spot-finding high-resolution limit are now std::optional, in the C++ structs AND in the OpenAPI schema, and resolve to the detector's own maximum (DiffractionExperiment::GetDetectorMaxQ_ recipA). Adaptive detection reads a pixel's ring from the azimuthal bins, so a pixel outside that q range could never be strong - the integration range silently bounded what detection could see, regardless of the requested resolution limit. Regenerated the C++ and TypeScript clients; the viewer and the web frontend each gained a "to detector edge" switch. Detection defaults are now per workflow (measured, not assumed) - Stills: adaptive detection, min-pix chosen per image, no resolution clipping. - Rotation: fixed-threshold finder, min-pix 2, 1.5 A limit. On a 33-crystal rotation battery, adaptive detection helped four hard crystals but deterministically broke three (a lost space group, a halved indexing rate, a collapsed merge), and the detector-edge limit cost indexing on a strong rotation set (100.0 -> 96.8%). Each is still overridable by its flag, and --no-adaptive-spots is new. Indexer seed escalation - Stop escalating once a seed's lattice explains >= 90% of the seed spots. Previously any frame with >= 80 spots always paid three indexer calls, online broker included. Merge-consistency filter - --min-image-cc gated on a per-image CC computed BEFORE the stills partiality post-refinement and never refreshed; the refiner now recomputes it, so the reported CC describes the data that are actually merged. - Replaced the per-call cc_mask argument with one MergeOnTheFly flag, so the merge, the error model and MergeStats can no longer disagree about which images are in (the --scale path merged unfiltered while its statistics were filtered). Per-image B-factor refinement (-B) removed - Measured on four serial-stills datasets: it is a no-op where the per-image fit is well conditioned and actively harmful where it is not (CC1/2 -8.1, R_meas +23.2 on the weakest large-cell set, whose fits hit their [-50, 200] bounds on 14-25% of images). It had also been silently DISCARDED since the partiality post-refinement landed - reported but not applied. Rather than fix and keep a knob with no demonstrated benefit, the flag and the whole image_scale_b_factor chain are gone: setting, scaling fit, message field, CBOR, HDF5 write and read-back, per-image plot, OpenAPI enum, viewer column and checkbox, docs. ScaleOnTheFly no longer needs Ceres at all - the fit is a linear IRLS. (The Wilson per-image b_factor is a different quantity and stays.) Stills partiality width now fits both of its components - sigma^2 = gamma0^2 + (gamma_e*d*)^2 instead of a purely angular gamma_e*d* with gamma0 pinned to 0. Fitted per crystal by least squares of dist_ewald^2 on d*^2. The angular-only width is fitted over a d*^2-dense population, so it was pinned by the high-resolution edge and collapsed at low d*: median partiality 0.008 beyond 13 A for reflections that were plainly recorded, 55% of them under the merge's partiality floor, and the survivors divided by those values - which inflated the merged low-resolution intensity scale 3.6x (~ +9 A^2 of apparent B). Measured on 5000 stills: the ramp flattens to 0.89x, no observation is dropped any more (701750 -> 716811), shell-mean CC1/2 and R-free improve slightly. Note CC1/2, R_meas, completeness and a B-refining R-free are all blind to that ramp, which is why it survived earlier validation; the cost is high-resolution R_meas (98.5 -> 101.9 shell-averaged). Removed dead code from add-then-remove churn - Prediction-time "still partiality" (unreachable: no setter), the phantom IndexingSettings::min_indexed_spot_fraction knob (getter, no setter - now the constant it always was), StillsPartialityRefine's caller-less Settings constructor and its reference to a long-gone env var, ProcessImage's unread bool return, an unused include, and a dead viewer overlay hook. Also - Viewer: the magnifier compared a QImage with itself, so its scene rect was set once ever and it could not pan into a larger dataset; the hover tail timer could fire after leaveEvent and resurrect the resolution readout outside the image. - update_version.sh regenerated the frontend lock file BEFORE bumping the version (every release shipped an off-by-one lock), and did git rm/git add on a path that has not existed since the client moved to src/client - with no set -e, both failed silently. - fpga/pcie_driver/postinstall.sh tested "[ ! occurrences > 0 ]", which is a redirect, not a test, so dkms add never ran. - Unit tests for the adaptive-threshold host functions, which had none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
292 lines
8.3 KiB
C++
292 lines
8.3 KiB
C++
/**
|
|
* Jungfraujoch
|
|
* API to control Jungfraujoch developed by the Paul Scherrer Institute (Switzerland). Jungfraujoch is a data acquisition and analysis system for pixel array detectors, primarly PSI JUNGFRAU. Jungfraujoch uses FPGA boards to acquire data at high data rates. # License Clarification While this API definition is licensed under GPL-3.0, **the GPL copyleft provisions do not apply** when this file is used solely to generate OpenAPI clients or when implementing applications that interact with the API. Generated client code and applications using this API definition are not subject to the GPL license requirements and may be distributed under terms of your choosing. This exception is similar in spirit to the Linux Kernel's approach to userspace API headers and the GCC Runtime Library Exception. The Linux Kernel developers have explicitly stated that user programs that merely use the kernel interfaces (syscalls, ioctl definitions, etc.) are not derivative works of the kernel and are not subject to the terms of the GPL. This exception is intended to allow wider use of this API specification without imposing GPL requirements on applications that merely interact with the API, regardless of whether they communicate through network calls or other mechanisms.
|
|
*
|
|
* The version of the OpenAPI document: 1.0.0-rc.160
|
|
* Contact: filip.leonarski@psi.ch
|
|
*
|
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
|
* https://openapi-generator.tech
|
|
* Do not edit the class manually.
|
|
*/
|
|
|
|
|
|
#include "Azim_int_settings.h"
|
|
#include "Helpers.h"
|
|
|
|
#include <sstream>
|
|
|
|
namespace org::openapitools::server::model
|
|
{
|
|
|
|
Azim_int_settings::Azim_int_settings()
|
|
{
|
|
m_Polarization_corr = true;
|
|
m_Solid_angle_corr = true;
|
|
m_High_q_recipA = 0.0f;
|
|
m_High_q_recipAIsSet = false;
|
|
m_Low_q_recipA = 0.0f;
|
|
m_Q_spacing = 0.0f;
|
|
m_Azimuthal_bins = 1L;
|
|
m_Azimuthal_binsIsSet = false;
|
|
m_Force_cpu = false;
|
|
m_Force_cpuIsSet = false;
|
|
|
|
}
|
|
|
|
void Azim_int_settings::validate() const
|
|
{
|
|
std::stringstream msg;
|
|
if (!validate(msg))
|
|
{
|
|
throw org::openapitools::server::helpers::ValidationException(msg.str());
|
|
}
|
|
}
|
|
|
|
bool Azim_int_settings::validate(std::stringstream& msg) const
|
|
{
|
|
return validate(msg, "");
|
|
}
|
|
|
|
bool Azim_int_settings::validate(std::stringstream& msg, const std::string& pathPrefix) const
|
|
{
|
|
bool success = true;
|
|
const std::string _pathPrefix = pathPrefix.empty() ? "Azim_int_settings" : pathPrefix;
|
|
|
|
|
|
if (highQRecipAIsSet())
|
|
{
|
|
const float& value = m_High_q_recipA;
|
|
const std::string currentValuePath = _pathPrefix + ".highQRecipA";
|
|
|
|
|
|
if (value < static_cast<float>(0.000020))
|
|
{
|
|
success = false;
|
|
msg << currentValuePath << ": must be greater than or equal to 0.000020;";
|
|
}
|
|
if (value > static_cast<float>(10.0))
|
|
{
|
|
success = false;
|
|
msg << currentValuePath << ": must be less than or equal to 10.0;";
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Low_q_recipA */ {
|
|
const float& value = m_Low_q_recipA;
|
|
const std::string currentValuePath = _pathPrefix + ".lowQRecipA";
|
|
|
|
|
|
if (value < static_cast<float>(0.000010))
|
|
{
|
|
success = false;
|
|
msg << currentValuePath << ": must be greater than or equal to 0.000010;";
|
|
}
|
|
if (value > static_cast<float>(10))
|
|
{
|
|
success = false;
|
|
msg << currentValuePath << ": must be less than or equal to 10;";
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/* Q_spacing */ {
|
|
const float& value = m_Q_spacing;
|
|
const std::string currentValuePath = _pathPrefix + ".qSpacing";
|
|
|
|
|
|
if (value < static_cast<float>(0.000010))
|
|
{
|
|
success = false;
|
|
msg << currentValuePath << ": must be greater than or equal to 0.000010;";
|
|
}
|
|
|
|
}
|
|
|
|
if (azimuthalBinsIsSet())
|
|
{
|
|
const int64_t& value = m_Azimuthal_bins;
|
|
const std::string currentValuePath = _pathPrefix + ".azimuthalBins";
|
|
|
|
|
|
if (value < 1ll)
|
|
{
|
|
success = false;
|
|
msg << currentValuePath << ": must be greater than or equal to 1;";
|
|
}
|
|
if (value > 512ll)
|
|
{
|
|
success = false;
|
|
msg << currentValuePath << ": must be less than or equal to 512;";
|
|
}
|
|
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
bool Azim_int_settings::operator==(const Azim_int_settings& rhs) const
|
|
{
|
|
return
|
|
|
|
|
|
(isPolarizationCorr() == rhs.isPolarizationCorr())
|
|
&&
|
|
|
|
(isSolidAngleCorr() == rhs.isSolidAngleCorr())
|
|
&&
|
|
|
|
|
|
((!highQRecipAIsSet() && !rhs.highQRecipAIsSet()) || (highQRecipAIsSet() && rhs.highQRecipAIsSet() && getHighQRecipA() == rhs.getHighQRecipA())) &&
|
|
|
|
(getLowQRecipA() == rhs.getLowQRecipA())
|
|
&&
|
|
|
|
(getQSpacing() == rhs.getQSpacing())
|
|
&&
|
|
|
|
|
|
((!azimuthalBinsIsSet() && !rhs.azimuthalBinsIsSet()) || (azimuthalBinsIsSet() && rhs.azimuthalBinsIsSet() && getAzimuthalBins() == rhs.getAzimuthalBins())) &&
|
|
|
|
|
|
((!forceCpuIsSet() && !rhs.forceCpuIsSet()) || (forceCpuIsSet() && rhs.forceCpuIsSet() && isForceCpu() == rhs.isForceCpu()))
|
|
|
|
;
|
|
}
|
|
|
|
bool Azim_int_settings::operator!=(const Azim_int_settings& rhs) const
|
|
{
|
|
return !(*this == rhs);
|
|
}
|
|
|
|
void to_json(nlohmann::json& j, const Azim_int_settings& o)
|
|
{
|
|
j = nlohmann::json::object();
|
|
j["polarization_corr"] = o.m_Polarization_corr;
|
|
j["solid_angle_corr"] = o.m_Solid_angle_corr;
|
|
if(o.highQRecipAIsSet())
|
|
j["high_q_recipA"] = o.m_High_q_recipA;
|
|
j["low_q_recipA"] = o.m_Low_q_recipA;
|
|
j["q_spacing"] = o.m_Q_spacing;
|
|
if(o.azimuthalBinsIsSet())
|
|
j["azimuthal_bins"] = o.m_Azimuthal_bins;
|
|
if(o.forceCpuIsSet())
|
|
j["force_cpu"] = o.m_Force_cpu;
|
|
|
|
}
|
|
|
|
void from_json(const nlohmann::json& j, Azim_int_settings& o)
|
|
{
|
|
j.at("polarization_corr").get_to(o.m_Polarization_corr);
|
|
j.at("solid_angle_corr").get_to(o.m_Solid_angle_corr);
|
|
if(j.find("high_q_recipA") != j.end())
|
|
{
|
|
j.at("high_q_recipA").get_to(o.m_High_q_recipA);
|
|
o.m_High_q_recipAIsSet = true;
|
|
}
|
|
j.at("low_q_recipA").get_to(o.m_Low_q_recipA);
|
|
j.at("q_spacing").get_to(o.m_Q_spacing);
|
|
if(j.find("azimuthal_bins") != j.end())
|
|
{
|
|
j.at("azimuthal_bins").get_to(o.m_Azimuthal_bins);
|
|
o.m_Azimuthal_binsIsSet = true;
|
|
}
|
|
if(j.find("force_cpu") != j.end())
|
|
{
|
|
j.at("force_cpu").get_to(o.m_Force_cpu);
|
|
o.m_Force_cpuIsSet = true;
|
|
}
|
|
|
|
}
|
|
|
|
bool Azim_int_settings::isPolarizationCorr() const
|
|
{
|
|
return m_Polarization_corr;
|
|
}
|
|
void Azim_int_settings::setPolarizationCorr(bool const value)
|
|
{
|
|
m_Polarization_corr = value;
|
|
}
|
|
bool Azim_int_settings::isSolidAngleCorr() const
|
|
{
|
|
return m_Solid_angle_corr;
|
|
}
|
|
void Azim_int_settings::setSolidAngleCorr(bool const value)
|
|
{
|
|
m_Solid_angle_corr = value;
|
|
}
|
|
float Azim_int_settings::getHighQRecipA() const
|
|
{
|
|
return m_High_q_recipA;
|
|
}
|
|
void Azim_int_settings::setHighQRecipA(float const value)
|
|
{
|
|
m_High_q_recipA = value;
|
|
m_High_q_recipAIsSet = true;
|
|
}
|
|
bool Azim_int_settings::highQRecipAIsSet() const
|
|
{
|
|
return m_High_q_recipAIsSet;
|
|
}
|
|
void Azim_int_settings::unsetHigh_q_recipA()
|
|
{
|
|
m_High_q_recipAIsSet = false;
|
|
}
|
|
float Azim_int_settings::getLowQRecipA() const
|
|
{
|
|
return m_Low_q_recipA;
|
|
}
|
|
void Azim_int_settings::setLowQRecipA(float const value)
|
|
{
|
|
m_Low_q_recipA = value;
|
|
}
|
|
float Azim_int_settings::getQSpacing() const
|
|
{
|
|
return m_Q_spacing;
|
|
}
|
|
void Azim_int_settings::setQSpacing(float const value)
|
|
{
|
|
m_Q_spacing = value;
|
|
}
|
|
int64_t Azim_int_settings::getAzimuthalBins() const
|
|
{
|
|
return m_Azimuthal_bins;
|
|
}
|
|
void Azim_int_settings::setAzimuthalBins(int64_t const value)
|
|
{
|
|
m_Azimuthal_bins = value;
|
|
m_Azimuthal_binsIsSet = true;
|
|
}
|
|
bool Azim_int_settings::azimuthalBinsIsSet() const
|
|
{
|
|
return m_Azimuthal_binsIsSet;
|
|
}
|
|
void Azim_int_settings::unsetAzimuthal_bins()
|
|
{
|
|
m_Azimuthal_binsIsSet = false;
|
|
}
|
|
bool Azim_int_settings::isForceCpu() const
|
|
{
|
|
return m_Force_cpu;
|
|
}
|
|
void Azim_int_settings::setForceCpu(bool const value)
|
|
{
|
|
m_Force_cpu = value;
|
|
m_Force_cpuIsSet = true;
|
|
}
|
|
bool Azim_int_settings::forceCpuIsSet() const
|
|
{
|
|
return m_Force_cpuIsSet;
|
|
}
|
|
void Azim_int_settings::unsetForce_cpu()
|
|
{
|
|
m_Force_cpuIsSet = false;
|
|
}
|
|
|
|
|
|
} // namespace org::openapitools::server::model
|
|
|