process/viewer: command-line generator + JFJochProcessController (GUI engine)

Foundation for making processing a first-class GUI activity:

- process/JFJochProcessCommandLine: reconstruct the equivalent jfjoch_process / jfjoch_azint
  command line from a ProcessConfig + DiffractionExperiment + input path, for handing a job
  off to a cluster. Unit-tested (full + azint).
- viewer/JFJochProcessController: runs one JFJochProcess job off the GUI thread (its own private
  reader; HDF5 access is globally serialized so it is safe next to the interactive reader) and
  reports back via queued Qt signals (started/phaseChanged/progress/finished/failed). Cancel()
  forwards to JFJochProcess::Cancel(). Progress is throttled to ~200 updates per run.

The visible processing UI (jobs window + snapshot switcher + converged settings) builds on this.

Verified: tests/jfjoch_test [process]; jfjoch_viewer links and builds against JFJochProcess.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 14:26:48 +02:00
parent 39599bc090
commit 800ffecd20
7 changed files with 339 additions and 1 deletions
+48
View File
@@ -9,6 +9,7 @@
#include "../writer/FileWriter.h"
#include "../reader/JFJochHDF5Reader.h"
#include "../process/JFJochProcess.h"
#include "../process/JFJochProcessCommandLine.h"
namespace {
// Write a small VDS dataset of `n` flat images and return nothing (prefix_master.h5 +
@@ -131,3 +132,50 @@ TEST_CASE("JFJochProcess_Cancel", "[HDF5][Full]") {
remove("process_cancel_in_data_000001.h5");
REQUIRE(H5Fget_obj_count(H5F_OBJ_ALL, H5F_OBJ_ALL) == 0);
}
TEST_CASE("JFJochProcessCommandLine_Full", "[process]") {
DiffractionExperiment x(DetJF(1));
IndexingSettings idx;
idx.Algorithm(IndexingAlgorithmEnum::FFT);
idx.GeomRefinementAlgorithm(GeomRefinementAlgorithmEnum::BeamCenter);
x.ImportIndexingSettings(idx);
x.SpaceGroupNumber(96);
ProcessConfig config;
config.mode = ProcessMode::FullAnalysis;
config.nthreads = 8;
config.output_prefix = "run1";
config.end_image = 500;
config.rotation_indexing = true;
config.two_pass_rotation = true;
config.rotation_indexing_image_count = 30;
config.spot_finding = DiffractionExperiment::DefaultDataProcessingSettings();
const std::string cmd = JFJochProcessCommandLine(config, x, "/data/lyso_master.h5");
CHECK(cmd.rfind("jfjoch_process", 0) == 0);
CHECK(cmd.find("-N 8") != std::string::npos);
CHECK(cmd.find("-e 500") != std::string::npos);
CHECK(cmd.find("-o run1") != std::string::npos);
CHECK(cmd.find("-X fft") != std::string::npos);
CHECK(cmd.find("-S 96") != std::string::npos);
CHECK(cmd.find("-R 30") != std::string::npos);
CHECK(cmd.find("/data/lyso_master.h5") != std::string::npos);
}
TEST_CASE("JFJochProcessCommandLine_AzInt", "[process]") {
DiffractionExperiment x(DetJF(1));
AzimuthalIntegrationSettings a;
a.AzimuthalBinCount(4);
x.ImportAzimuthalIntegrationSettings(a);
ProcessConfig config;
config.mode = ProcessMode::AzimuthalIntegration;
config.nthreads = 2;
config.output_prefix = "az";
const std::string cmd = JFJochProcessCommandLine(config, x, "in.h5");
CHECK(cmd.rfind("jfjoch_azint", 0) == 0);
CHECK(cmd.find("--azimuthal-bins 4") != std::string::npos);
CHECK(cmd.find("--min-q") != std::string::npos);
CHECK(cmd.find("in.h5") != std::string::npos);
}