Files
Jungfraujoch/viewer/windows/JFJochProcessingJobsWindow.h
T
leonarski_fandClaude Opus 5 26fc4b02b3 symmetry: carry the space group as the group, not as its number
The adopted space group travelled the pipeline as a bare int and was rebuilt
downstream with find_spacegroup_by_number, which returns the reference setting.
So every setting a number cannot name was destroyed one line after it was
determined: P 1 1 2 came back as P 1 2 1, I 1 1 2 as C 1 2 1, R 3:R as R 3:H.

DatasetSettings now holds the gemmi::SpaceGroup itself, DiffractionExperiment
exposes it as GetGemmiSpaceGroup() / GetSpaceGroupOrP1(), and everything that
used to take an int - HKLKeyGenerator (its int constructor is gone, so the
compiler finds the callers), the merge, the R-free flags, French-Wilson, the
reindexing ambiguity, the completeness enumeration, the MTZ and mmCIF exports,
the model validation - takes the group. -S keeps the setting the symbol names
rather than reducing it to a number.

The end message carries both spellings and a reader prefers the name, since
only the name keeps the setting while the number is what a reader written
before the name understands. It carries them over CBOR too: the determined
group was never serialised at all, so a group rugnux chose reached the master
file only when the same process wrote it, and an online writer fell back to
whatever the user had supplied at the start. Both keys are optional additions,
so an older reader skips them and a newer one reads an older sender.

On disk the master's /entry/sample/space_group carries the extended
Hermann-Mauguin name and is what the reader takes the group from, so a setting
survives a _process.h5 and the --mode scale that re-reads it; the number stays
beside it and is the fallback for files written before. Every one of the 230
reference settings the old writer could produce reads back as itself, so older
files are unaffected.

Stage A and Stage B of the search still enumerate reference settings only, so
this determines no group differently today - it is what the enumeration needs
before it can be widened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
2026-08-31 07:16:43 +02:00

125 lines
6.3 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <QElapsedTimer>
#include <QString>
#include <QWidget>
#include <cstdint>
#include <optional>
#include <vector>
#include "../JFJochProcessController.h"
#include "../JFJochImageReadingWorker.h" // ReprocessingInputs
#include "../widgets/PowderCalibrationWidget.h" // CalibrationSelection
class QTableWidget;
class QProgressBar;
class QStackedWidget;
class QToolBar;
class QToolButton;
// Makes processing a first-class GUI activity: a dockable panel with a table of processing jobs run
// on the current dataset. A job can be run locally (off the GUI thread, via JFJochProcessController)
// or its rugnux command line copied for a cluster. A finished local run is registered as a
// reader snapshot so its results become a selectable view of the dataset. Re-processing reads a
// stored HDF5 file, so the panel shows an explanatory message while connected to a live HTTP stream.
class JFJochProcessingJobsWindow : public QWidget {
Q_OBJECT
public:
explicit JFJochProcessingJobsWindow(JFJochImageReadingWorker *worker, QWidget *parent = nullptr);
signals:
void registerSnapshot(QString id, QString label, QString master_path);
void activateSnapshot(QString id);
void renameRun(QString id, QString label);
void removeRun(QString id);
void writeStatusBar(QString message, int timeout_ms = 0);
// Live per-image results while a job runs, for the dataset-info plots (nullptr clears it).
void liveDataset(std::shared_ptr<const JFJochReaderDataset> dataset);
void jobStarted(); // a reprocessing job was launched — reveal the (hidden-by-default) panel
public slots:
void onHttpConnectionChanged(bool connected, QString addr);
void clearJobs(); // reset the table on a new file (re-adds the Original row)
void setActiveRun(QString active_id); // bold the row of the run currently shown in the plots
// The panel's "Analyze dataset" action; the mode is the panel's selected page. The calibrant
// selection is only used by ProcessMode::Calibration.
void newJob(ProcessMode mode = ProcessMode::FullAnalysis, CalibrationSelection calibration = {});
private slots:
void cancelJob();
void removeResult();
void viewResults();
void onPhase(QString phase);
void onProgress(quint64 done, quint64 total);
void onFinished(ProcessResult result);
void onFailed(QString error);
private:
struct JobInfo {
QString id; // stable reader-snapshot key
QString label; // editable display name (legend / table)
QString snapshot_path; // empty unless saved
bool has_result = false;
// Merge statistics captured when a scaling/merging job finished (drive the analysis window).
bool has_merge_stats = false;
MergeStatistics merge_stats;
double isa = 0.0;
bool merge_has_reference = false;
TwinningAnalysisResult twinning; // twinning test of the merged intensities
std::optional<gemmi::SpaceGroup> space_group; // final space group (searched or fixed)
std::optional<SearchSpaceGroupResult> space_group_search; // ranked candidates, when a search ran
std::vector<std::pair<float, float>> merged_i_sigma; // ISa diagnostic scatter
// Detector calibration result (Calibration mode): the fit, the header geometry it is compared
// against, and where the .poni was written.
std::optional<CalibrationResult> calibration;
std::optional<DiffractionGeometry> calibration_header;
QString poni_path;
QToolButton *graph_btn = nullptr; // per-row "show statistics" button (enabled once stats exist)
};
struct JobSpec {
ProcessMode mode = ProcessMode::FullAnalysis;
int start_image = 0;
int end_image = 0; // 0 == to the end
int threads = 4;
bool save_h5 = true; // write the per-image _process.h5 (also drives the viewer snapshot)
bool save_merged = true; // write the merged .mtz/.cif
QString prefix;
bool rotation = false;
int rotation_images = 30; // images used to find the rotation lattice (first pass)
bool scaling = false;
bool refine_geometry = false; // stills-only global geometry bundle-adjust (needs a known cell)
int refine_geometry_frames = 200; // strong indexed frames fed to that bundle adjustment
bool rotation_postrefine = true; // rotation-only two-pass geometry post-refine (default on)
bool detect_beam_stop = true; // project frames and mask the beam-stop shadow (default on)
int detect_beam_stop_frames = 60; // frames projected to find it
bool estimate_beam_center = false; // measure the direct beam in the same pre-scan (default off)
CalibrationSelection calibration; // Calibration mode: calibrant rings + rings/spots method
};
// Returns 0 = cancel, 1 = run locally, 2 = copy command line; fills spec from the dialog.
int askJob(const ReprocessingInputs &inputs, JobSpec &spec);
ProcessConfig buildConfig(const JobSpec &spec, const ReprocessingInputs &inputs) const;
void setStatus(int row, const QString &text);
void addOriginalRow(); // the file's own data as the first, always-present run
void addRowActions(int row, const QString &id); // per-row graph (stats) + trash (remove) buttons
void showStats(const QString &id); // open the merge-statistics window for a run
void removeRunById(const QString &id); // remove a run from the table + reader
JFJochImageReadingWorker *worker_;
JFJochProcessController *controller_;
// The experiment a running calibration job was launched with: WritePoniFile needs the detector
// size and wavelength from it once the fit comes back.
DiffractionExperiment calibration_experiment_;
QToolBar *toolbar_;
QStackedWidget *stack_; // page 0: jobs table, page 1: HTTP-mode message
QTableWidget *table_;
QProgressBar *running_bar_ = nullptr; // lives in the running row's Status cell
QElapsedTimer job_timer_; // running job wall-clock, for rate + ETA
int running_row_ = -1;
int job_counter_ = 0;
std::vector<JobInfo> jobs_;
};