v1.0.0-rc.131 (#39)
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 11m20s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 10m46s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 11m27s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 12m32s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 10m57s
Build Packages / build:rpm (rocky8) (push) Successful in 11m54s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 13m9s
Build Packages / build:rpm (rocky9) (push) Successful in 12m37s
Build Packages / Generate python client (push) Successful in 24s
Build Packages / Create release (push) Has been skipped
Build Packages / Build documentation (push) Successful in 57s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 9m12s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 8m4s
Build Packages / Unit tests (push) Successful in 1h17m43s

This is an UNSTABLE release. The release has significant modifications and bug fixes, if things go wrong, it is better to revert to 1.0.0-rc.124.

* jfjoch_broker: Fix bug in saving JUNGFRAU calibration (pedestal/pedestalRMS)
* jfjoch_viewer: Fix calibration (pedestal) images being open flipped
* jfjoch_process: Add space group detection (EXPERIMENTAL)

Reviewed-on: #39
This commit was merged in pull request #39.
This commit is contained in:
2026-03-07 11:34:04 +01:00
parent 928789a67c
commit 166fcdb68f
147 changed files with 1261 additions and 192 deletions
+86 -42
View File
@@ -10,6 +10,7 @@
#include <atomic>
#include <chrono>
#include <fstream>
#include <sstream>
#include "../reader/JFJochHDF5Reader.h"
#include "../common/Logger.h"
@@ -25,7 +26,8 @@
#include "../receiver/JFJochReceiverPlots.h"
#include "../compression/JFJochCompressor.h"
#include "../image_analysis/scale_merge/FrenchWilson.h"
#include "../image_analysis/WriteMmcif.h" // (actually include at top of file)
#include "../image_analysis/scale_merge/SearchSpaceGroup.h"
#include "../image_analysis/WriteMmcif.h"
void print_usage(Logger &logger) {
logger.Info("Usage ./jfjoch_analysis {<options>} <input.h5>");
@@ -517,6 +519,7 @@ int main(int argc, char **argv) {
logger.Info("Rotation Indexing found lattice");
}
// --- Optional: run scaling (mosaicity refinement) on accumulated reflections ---
// --- Optional: run scaling (mosaicity refinement) on accumulated reflections ---
if (run_scaling) {
logger.Info("Running scaling (mosaicity refinement) ...");
@@ -528,6 +531,8 @@ int main(int argc, char **argv) {
scale_opts.merge_friedel = !anomalous_mode;
scale_opts.d_min_limit_A = d_min_scale_merge.value_or(0.0);
const bool fixed_space_group = space_group || experiment.GetGemmiSpaceGroup().has_value();
if (space_group)
scale_opts.space_group = *space_group;
else
@@ -538,46 +543,89 @@ int main(int argc, char **argv) {
auto scale_end = std::chrono::steady_clock::now();
double scale_time = std::chrono::duration<double>(scale_end - scale_start).count();
if (scale_result && !fixed_space_group) {
logger.Info("Searching for space group from P1-merged reflections ...");
SearchSpaceGroupOptions sg_opts;
sg_opts.crystal_system.reset();
sg_opts.centering = '\0';
sg_opts.merge_friedel = !anomalous_mode;
sg_opts.d_min_limit_A = d_min_scale_merge.value_or(0.0);
sg_opts.min_i_over_sigma = 0.0;
sg_opts.min_operator_cc = 0.80;
sg_opts.min_pairs_per_operator = 20;
sg_opts.min_total_compared = 100;
sg_opts.test_systematic_absences = true;
const auto sg_search = SearchSpaceGroup(scale_result->merged, sg_opts);
logger.Info("");
{
std::istringstream iss(SearchSpaceGroupResultToText(sg_search));
std::string line;
while (std::getline(iss, line)) {
if (!line.empty())
logger.Info("{}", line);
}
}
logger.Info("");
if (sg_search.best_space_group.has_value()) {
logger.Info("Re-running scaling in detected space group {}", sg_search.best_space_group->short_name());
scale_opts.space_group = *sg_search.best_space_group;
auto rescale_start = std::chrono::steady_clock::now();
auto refined_scale_result = indexer.ScaleRotationData(scale_opts);
auto rescale_end = std::chrono::steady_clock::now();
if (refined_scale_result) {
scale_result = std::move(refined_scale_result);
scale_time += std::chrono::duration<double>(rescale_end - rescale_start).count();
}
} else {
logger.Warning("No space group accepted; keeping P1-merged result");
}
}
if (scale_result) {
end_msg.scale_factor = scale_result->image_scale_g;
logger.Info("Scaling completed in {:.2f} s ({} unique reflections)",
scale_time, scale_result->merged.size());
logger.Info("Scaling completed in {:.2f} s ({} unique reflections)",
scale_time, scale_result->merged.size());
// Print resolution-shell statistics table
// Print resolution-shell statistics table
{
const auto &stats = scale_result->statistics;
logger.Info("");
logger.Info(" {:>8s} {:>8s} {:>8s} {:>8s} {:>8s} {:>10s}",
"d_min", "N_obs", "N_uniq", "Rmeas", "<I/sig>", "Complete");
logger.Info(" {:->8s} {:->8s} {:->8s} {:->8s} {:->8s} {:->10s}",
"", "", "", "", "", "");
for (const auto &sh: stats.shells) {
if (sh.unique_reflections == 0)
continue;
std::string compl_str = (sh.completeness > 0.0)
? fmt::format("{:8.1f}%", sh.completeness * 100.0)
: " N/A";
logger.Info(" {:8.2f} {:8d} {:8d} {:8.3f}% {:8.1f} {:>10s}",
sh.d_min, sh.total_observations, sh.unique_reflections,
sh.rmeas * 100, sh.mean_i_over_sigma, compl_str);
}
{
const auto& stats = scale_result->statistics;
logger.Info("");
logger.Info(" {:>8s} {:>8s} {:>8s} {:>8s} {:>8s} {:>10s}",
"d_min", "N_obs", "N_uniq", "Rmeas", "<I/sig>", "Complete");
const auto &ov = stats.overall;
logger.Info(" {:->8s} {:->8s} {:->8s} {:->8s} {:->8s} {:->10s}",
"", "", "", "", "", "");
for (const auto& sh : stats.shells) {
if (sh.unique_reflections == 0)
continue;
std::string compl_str = (sh.completeness > 0.0)
? fmt::format("{:8.1f}%", sh.completeness * 100.0)
: " N/A";
logger.Info(" {:8.2f} {:8d} {:8d} {:8.3f}% {:8.1f} {:>10s}",
sh.d_min, sh.total_observations, sh.unique_reflections,
sh.rmeas * 100, sh.mean_i_over_sigma, compl_str);
}
// Overall
{
const auto& ov = stats.overall;
logger.Info(" {:->8s} {:->8s} {:->8s} {:->8s} {:->8s} {:->10s}",
"", "", "", "", "", "");
std::string compl_str = (ov.completeness > 0.0)
? fmt::format("{:8.1f}%", ov.completeness * 100.0)
: " N/A";
logger.Info(" {:>8s} {:8d} {:8d} {:8.3f}% {:8.1f} {:>10s}",
"Overall", ov.total_observations, ov.unique_reflections,
ov.rmeas * 100, ov.mean_i_over_sigma, compl_str);
}
logger.Info("");
std::string compl_str = (ov.completeness > 0.0)
? fmt::format("{:8.1f}%", ov.completeness * 100.0)
: " N/A";
logger.Info(" {:>8s} {:8d} {:8d} {:8.3f}% {:8.1f} {:>10s}",
"Overall", ov.total_observations, ov.unique_reflections,
ov.rmeas * 100, ov.mean_i_over_sigma, compl_str);
}
logger.Info("");
}
// Write image.dat (image_id mosaicity_deg K)
{
const std::string img_path = output_prefix + "_image.dat";
std::ofstream img_file(img_path);
@@ -586,13 +634,13 @@ int main(int argc, char **argv) {
} else {
img_file << "# image_id mosaicity_deg K\n";
for (size_t i = 0; i < scale_result->mosaicity_deg.size(); ++i) {
img_file << i << " " << scale_result->mosaicity_deg[i] << " " << scale_result->image_scale_g[i] << "\n";
img_file << i << " " << scale_result->mosaicity_deg[i] << " " << scale_result->image_scale_g[i]
<< "\n";
}
img_file.close();
}
}
// --- French-Wilson: convert I → F ---
{
FrenchWilsonOptions fw_opts;
fw_opts.acentric = true; // typical for MX
@@ -601,7 +649,6 @@ int main(int argc, char **argv) {
auto fw = FrenchWilson(scale_result->merged, fw_opts);
{
{
// Write scaled.hkl (h k l I sigma)
const std::string hkl_path = output_prefix + "_amplitudes.hkl";
std::ofstream hkl_file(hkl_path);
if (!hkl_file) {
@@ -619,23 +666,20 @@ int main(int argc, char **argv) {
}
MmcifMetadata cif_meta;
// Unit cell — from rotation indexing result or experiment setting
if (rotation_indexer_ret.has_value()) {
cif_meta.unit_cell = rotation_indexer_ret->lattice.GetUnitCell();
} else if (experiment.GetUnitCell().has_value()) {
cif_meta.unit_cell = experiment.GetUnitCell().value();
}
// Space group
if (auto sg = experiment.GetGemmiSpaceGroup(); sg.has_value()) {
if (scale_opts.space_group.has_value()) {
cif_meta.space_group_name = scale_opts.space_group->hm;
cif_meta.space_group_number = scale_opts.space_group->number;
} else if (auto sg = experiment.GetGemmiSpaceGroup(); sg.has_value()) {
cif_meta.space_group_name = sg->hm;
cif_meta.space_group_number = sg->number;
} else if (space_group) {
cif_meta.space_group_name = space_group->hm;
cif_meta.space_group_number = space_group->number;
}
// Detector & experiment info
cif_meta.detector_name = experiment.GetDetectorDescription();
cif_meta.wavelength_A = experiment.GetWavelength_A();
cif_meta.detector_distance_mm = experiment.GetDetectorDistance_mm();