viewer: no uncaught exceptions from slots (grid-scan "Image out of bounds" crash)
An exception escaping a Qt slot terminates the viewer. At the beamline it died with "Input parameter invalid (Image out of bounds)": the grid-scan map in the dataset-info panel asked GridScanSettings for the cell of an image outside the grid. Image and dataset can disagree - live, the broker's image and dataset are fetched apart and can belong to different collections, and the worker re-emits its current image against a newer dataset. The map now marks no cell for such an image. Opening a file with an image number it does not have (a D-Bus caller keeping its position from the previous dataset) showed no image and kept the previous file's image, which was then re-emitted against the new dataset. The file now opens at image 0 and the previous image is dropped. The rest of the viewer was audited for the same failure and guarded: - worker slots that were not covered: dataset update, azimuthal/Bragg settings, analysis (Analyze, re-analysis toggle, spot-finding settings), beam-centre fit (incl. the setters rejecting a diverged fit), user mask update/save/upload, ROI definition, run removal; azimuthal settings with more bins than the mapping supports are refused up front, so they cannot be kept and fail every later file open; - settings dock: azimuthal Q range, Bragg radii and experiment fields are built on a copy and an invalid combination keeps the previous settings; R1 and cell-angle field ranges now match the setters; - azimuthal-image double-click on a bin with no pixel, azimuthal ROI handle dragged onto the beam centre (infinite d), ROI editor given inf/nan, non-numeric JUNGFRAUJOCH_HTTP_PORT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
67e9c84bd1
commit
18e50c590b
7 files changed
+200
-102
No files matched your search
@@ -352,8 +352,17 @@ void JFJochImageReadingWorker::LoadFile_i(const QString &filename, qint64 image_
|
||||
}
|
||||
|
||||
current_image.reset();
|
||||
current_image_ptr.reset(); // the previous file's image must not be re-emitted against this dataset
|
||||
current_summation = 1;
|
||||
current_file = filename;
|
||||
// An image number carried over from the previous dataset (e.g. a D-Bus caller keeping its
|
||||
// position) may not exist in this one: open the file at its first image instead of showing none.
|
||||
if (!http_mode && image_number >= 0 && image_number + summation > total_images) {
|
||||
logger.Warning("Image {} out of range for {} ({} images), showing image 0",
|
||||
image_number, filename.toStdString(), total_images);
|
||||
image_number = 0;
|
||||
summation = 1;
|
||||
}
|
||||
if (dataset) {
|
||||
curr_experiment = dataset->experiment;
|
||||
roi_override_.reset(); // new file: use its ROIs, forget any earlier edits
|
||||
@@ -581,7 +590,11 @@ void JFJochImageReadingWorker::UpdateDataset_i(const std::optional<DiffractionEx
|
||||
|
||||
void JFJochImageReadingWorker::UpdateDataset(const DiffractionExperiment &experiment) {
|
||||
QMutexLocker ul(&m);
|
||||
UpdateDataset_i(experiment);
|
||||
try {
|
||||
UpdateDataset_i(experiment);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("Dataset update failed: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::ReanalyzeImage_i() {
|
||||
@@ -602,7 +615,12 @@ void JFJochImageReadingWorker::ReanalyzeImage_i() {
|
||||
// Azimuthal profile for the analysis/display pipeline. AzimuthalIntegrationProfile
|
||||
// holds a mutex (non-copyable), so keep it via unique_ptr re-created each analysis.
|
||||
last_profile_ = std::make_unique<AzimuthalIntegrationProfile>(*azint_mapping);
|
||||
image_analysis->Analyze(new_image->ImageData(), *last_profile_, spot_finding_settings);
|
||||
try {
|
||||
image_analysis->Analyze(new_image->ImageData(), *last_profile_, spot_finding_settings);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("Analysis of image failed: {}", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
current_image_ptr = new_image;
|
||||
auto end_time = std::chrono::high_resolution_clock::now();
|
||||
@@ -624,27 +642,28 @@ void JFJochImageReadingWorker::FindCenter(QVector<float> ring_q, bool guess, boo
|
||||
|
||||
DiffractionGeometry geom = current_image_ptr->Dataset().experiment.GetDiffractionGeometry();
|
||||
const std::vector<float> ring_Q(ring_q.begin(), ring_q.end());
|
||||
// A diverged fit is rejected by the setters (NaN beam, sub-mm distance), so they sit in the try too.
|
||||
try {
|
||||
if (guess)
|
||||
GuessGeometry(geom, current_image_ptr->ImageData().spots, ring_Q, refine_tilt);
|
||||
else
|
||||
OptimizeGeometry(geom, current_image_ptr->ImageData().spots, ring_Q, refine_tilt);
|
||||
} catch (const JFJochException &e) {
|
||||
logger.ErrorException(e);
|
||||
|
||||
logger.Info("Geometry found X: {} pxl Y: {} pxl Dist: {} mm", geom.GetBeamX_pxl(), geom.GetBeamY_pxl(),
|
||||
geom.GetDetectorDistance_mm());
|
||||
|
||||
DiffractionExperiment new_experiment = current_image_ptr->Dataset().experiment;
|
||||
new_experiment.BeamX_pxl(geom.GetBeamX_pxl()).BeamY_pxl(geom.GetBeamY_pxl())
|
||||
.DetectorDistance_mm(geom.GetDetectorDistance_mm())
|
||||
.PoniRot1_rad(geom.GetPoniRot1_rad())
|
||||
.PoniRot2_rad(geom.GetPoniRot2_rad())
|
||||
.PoniRot3_rad(geom.GetPoniRot3_rad());
|
||||
UpdateDataset_i(new_experiment);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("Finding beam center failed: {}", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
logger.Info("Geometry found X: {} pxl Y: {} pxl Dist: {} mm", geom.GetBeamX_pxl(), geom.GetBeamY_pxl(),
|
||||
geom.GetDetectorDistance_mm());
|
||||
|
||||
DiffractionExperiment new_experiment = current_image_ptr->Dataset().experiment;
|
||||
new_experiment.BeamX_pxl(geom.GetBeamX_pxl()).BeamY_pxl(geom.GetBeamY_pxl())
|
||||
.DetectorDistance_mm(geom.GetDetectorDistance_mm())
|
||||
.PoniRot1_rad(geom.GetPoniRot1_rad())
|
||||
.PoniRot2_rad(geom.GetPoniRot2_rad())
|
||||
.PoniRot3_rad(geom.GetPoniRot3_rad());
|
||||
UpdateDataset_i(new_experiment);
|
||||
|
||||
QVector<float> rings;
|
||||
for (int i = 0; i < 15 && i < ring_Q.size(); i++) {
|
||||
rings.push_back(2 * PI / ring_Q[i]);
|
||||
@@ -695,14 +714,29 @@ void JFJochImageReadingWorker::ReanalyzeImages(bool input) {
|
||||
|
||||
void JFJochImageReadingWorker::UpdateAzintSettings(const AzimuthalIntegrationSettings &settings) {
|
||||
QMutexLocker locker(&m);
|
||||
// Checked here, not only where the mapping is built: settings that cannot be mapped would
|
||||
// otherwise be kept and fail every later file open and dataset update.
|
||||
if (settings.GetBinCount() >= UINT16_MAX) {
|
||||
logger.Error("Azimuthal integration settings ignored: {} bins, at most 65534 supported",
|
||||
settings.GetBinCount());
|
||||
return;
|
||||
}
|
||||
azint_settings = settings;
|
||||
UpdateDataset_i(std::nullopt);
|
||||
try {
|
||||
UpdateDataset_i(std::nullopt);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("Azimuthal integration update failed: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::UpdateBraggIntegrationSettings(BraggIntegrationSettings settings) {
|
||||
QMutexLocker locker(&m);
|
||||
bragg_settings = settings;
|
||||
UpdateDataset_i(std::nullopt);
|
||||
try {
|
||||
UpdateDataset_i(std::nullopt);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("Bragg integration update failed: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::UpdateScalingSettings(ScalingSettings settings) {
|
||||
@@ -778,7 +812,11 @@ void JFJochImageReadingWorker::EmitReferenceInfo_i() {
|
||||
|
||||
void JFJochImageReadingWorker::SetROIDefinition(const ROIDefinition &rois) {
|
||||
QMutexLocker locker(&m);
|
||||
SetROIDefinition_i(rois);
|
||||
try {
|
||||
SetROIDefinition_i(rois);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("ROI update failed: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::MaskFromSelectedROI(QString name, bool add) {
|
||||
@@ -879,25 +917,30 @@ void JFJochImageReadingWorker::ApplyROIOverrideToImage_i() {
|
||||
|
||||
void JFJochImageReadingWorker::UpdateUserMask_i(const std::vector<uint32_t> &mask) {
|
||||
std::shared_ptr<const JFJochReaderDataset> dataset;
|
||||
if (http_mode) {
|
||||
http_reader.UpdateUserMask(mask);
|
||||
dataset = http_reader.GetDataset();
|
||||
} else {
|
||||
file_reader->UpdateUserMask(mask);
|
||||
dataset = file_reader->GetDataset();
|
||||
}
|
||||
try {
|
||||
if (http_mode) {
|
||||
http_reader.UpdateUserMask(mask);
|
||||
dataset = http_reader.GetDataset();
|
||||
} else {
|
||||
file_reader->UpdateUserMask(mask);
|
||||
dataset = file_reader->GetDataset();
|
||||
}
|
||||
|
||||
if (!dataset) {
|
||||
logger.Error("UpdateUserMask_i: dataset is null (http_mode={}) - skipping update to avoid crash", http_mode);
|
||||
if (!dataset) {
|
||||
logger.Error("UpdateUserMask_i: dataset is null (http_mode={}) - skipping update to avoid crash", http_mode);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateAzint_i(dataset.get());
|
||||
|
||||
EmitDatasetLoaded_i(dataset);
|
||||
|
||||
current_image_ptr = std::make_shared<JFJochReaderImage>(current_image_ptr->ImageData(), dataset);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("User mask update failed: {}", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateAzint_i(dataset.get());
|
||||
|
||||
EmitDatasetLoaded_i(dataset);
|
||||
|
||||
current_image_ptr = std::make_shared<JFJochReaderImage>(current_image_ptr->ImageData(), dataset);
|
||||
|
||||
if (current_image.has_value())
|
||||
LoadImage_i(current_image.value(), current_summation);
|
||||
}
|
||||
@@ -918,7 +961,11 @@ void JFJochImageReadingWorker::SaveUserMaskTIFF(QString filename) {
|
||||
return;
|
||||
auto user_mask = current_image_ptr->Dataset().pixel_mask->GetUserMask();
|
||||
CompressedImage mask_image(user_mask, current_image_ptr->Dataset().experiment.GetXPixelsNum(), current_image_ptr->Dataset().experiment.GetYPixelsNum());
|
||||
WriteTIFFToFile(filename.toStdString(), mask_image);
|
||||
try {
|
||||
WriteTIFFToFile(filename.toStdString(), mask_image);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("Could not write mask TIFF {}: {}", filename.toStdString(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::LoadUserMaskTIFF(QString filename, bool replace) {
|
||||
@@ -961,8 +1008,15 @@ void JFJochImageReadingWorker::UploadUserMask() {
|
||||
return;
|
||||
|
||||
auto user_mask = current_image_ptr->Dataset().pixel_mask->GetUserMask();
|
||||
if (http_mode)
|
||||
if (!http_mode)
|
||||
return;
|
||||
try {
|
||||
http_reader.UploadUserMask(user_mask);
|
||||
} catch (const HttpUnauthorized &) {
|
||||
HandleUnauthorized_i(current_file);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("Upload of user mask failed: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::LoadCalibration(QString dataset) {
|
||||
@@ -1265,7 +1319,11 @@ void JFJochImageReadingWorker::RemoveRun(QString id) {
|
||||
}
|
||||
run_labels_.erase(id.toStdString());
|
||||
// Refresh the (possibly now-Original) active view; ActivateSnapshot_i also re-emits runsChanged.
|
||||
ActivateSnapshot_i(QString::fromStdString(ActiveRun_i()));
|
||||
try {
|
||||
ActivateSnapshot_i(QString::fromStdString(ActiveRun_i()));
|
||||
} catch (const std::exception &e) {
|
||||
logger.Error("Could not activate run after removal: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::RegisterProcessingSnapshot(QString id, QString label, QString master_path) {
|
||||
|
||||
@@ -327,7 +327,7 @@ void JFJochViewerMenu::openHttpSelected() {
|
||||
|
||||
char *port_std = std::getenv("JUNGFRAUJOCH_HTTP_PORT");
|
||||
if (port_std != nullptr) {
|
||||
const int tmp = std::stoi(std::string(port_std));
|
||||
const int tmp = QString(port_std).toInt(); // 0 for a non-number, unlike std::stoi which throws
|
||||
if (tmp < 65536 && tmp > 0)
|
||||
portSpinBox->setValue(tmp);
|
||||
} else
|
||||
|
||||
@@ -107,7 +107,11 @@ void JFJochAzIntImage::mouseDoubleClickEvent(QMouseEvent *event) {
|
||||
float phi = image->Dataset().az_int_bin_to_phi[idx];
|
||||
|
||||
auto geom = image->Dataset().experiment.GetDiffractionGeometry();
|
||||
auto coord = geom.ResPhiToPxl(2 * PI / q, phi / 180.0 * PI);
|
||||
emit zoomOnBin(QPointF(coord.first, coord.second));
|
||||
try {
|
||||
auto coord = geom.ResPhiToPxl(2 * PI / q, phi / 180.0 * PI);
|
||||
emit zoomOnBin(QPointF(coord.first, coord.second));
|
||||
} catch (const std::exception &) {
|
||||
// a bin beyond 2 theta = 180 deg (manual High Q above 4 pi / lambda) is on no pixel
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -834,11 +834,14 @@ void JFJochDiffractionImage::roiEditMove(const QPointF &scenePos) {
|
||||
case RoiEdit::ResizeCircle:
|
||||
edit_radius_ = std::max(1.0, QLineF(edit_center_, scenePos).length());
|
||||
break;
|
||||
// PxlToRes is infinite on the beam centre, which ROIAzimuthal rejects: keep the last finite value
|
||||
case RoiEdit::AzimInner:
|
||||
edit_d_max_ = geom.PxlToRes(cursor_r);
|
||||
if (const float d = geom.PxlToRes(cursor_r); std::isfinite(d))
|
||||
edit_d_max_ = d;
|
||||
break;
|
||||
case RoiEdit::AzimOuter:
|
||||
edit_d_min_ = geom.PxlToRes(cursor_r);
|
||||
if (const float d = geom.PxlToRes(cursor_r); std::isfinite(d))
|
||||
edit_d_min_ = d;
|
||||
break;
|
||||
case RoiEdit::RotatePhiMin:
|
||||
edit_phi_min_ = geom.Phi_rad(scenePos.x(), scenePos.y()) * 180.0f / static_cast<float>(PI);
|
||||
|
||||
@@ -155,7 +155,10 @@ void JFJochGridScanImage::mouseDoubleClickEvent(QMouseEvent *event) {
|
||||
}
|
||||
|
||||
void JFJochGridScanImage::setImage(int64_t val) {
|
||||
if (settings) {
|
||||
// The image can lie outside the grid: live, the broker's image and dataset are fetched apart and
|
||||
// can belong to different collections, and a re-emitted image can meet a newer dataset's grid.
|
||||
// Such an image has no cell to mark (GetElementPos*_step would throw on the GUI thread).
|
||||
if (settings && val >= 0 && val < settings->GetNElem()) {
|
||||
current_image_W = settings->GetElementPosX_step(val);
|
||||
current_image_H = settings->GetElementPosY_step(val);
|
||||
} else {
|
||||
|
||||
@@ -114,29 +114,35 @@ void JFJochViewerROIList::ApplyEditor() {
|
||||
const std::string sel = SelectedName().toStdString();
|
||||
bool changed = false;
|
||||
|
||||
for (auto &b : rois_.boxes)
|
||||
if (b.GetName() == sel) {
|
||||
b = ROIBox(sel, fedit_[0]->text().toLongLong(), fedit_[1]->text().toLongLong(),
|
||||
fedit_[2]->text().toLongLong(), fedit_[3]->text().toLongLong());
|
||||
changed = true; break;
|
||||
}
|
||||
for (auto &c : rois_.circles)
|
||||
if (c.GetName() == sel) {
|
||||
c = ROICircle(sel, fedit_[0]->text().toDouble(), fedit_[1]->text().toDouble(),
|
||||
std::max(0.1, fedit_[2]->text().toDouble()));
|
||||
changed = true; break;
|
||||
}
|
||||
for (auto &a : rois_.azimuthal)
|
||||
if (a.GetName() == sel) {
|
||||
const float qmin = fedit_[0]->text().toFloat();
|
||||
const float qmax = fedit_[1]->text().toFloat();
|
||||
const float phimin = fedit_[2]->text().toFloat();
|
||||
const float phimax = fedit_[3]->text().toFloat();
|
||||
const float d_min = (qmax > 0) ? 2.0f * static_cast<float>(PI) / qmax : a.GetDMin_A();
|
||||
const float d_max = (qmin > 0) ? 2.0f * static_cast<float>(PI) / qmin : a.GetDMax_A();
|
||||
a = ROIAzimuthal(sel, d_min, d_max, phimin, phimax);
|
||||
changed = true; break;
|
||||
}
|
||||
// The ROI constructors reject what QString::toDouble lets through ("inf", "nan"); an exception
|
||||
// escaping a slot terminates the viewer, so such an entry is just not applied.
|
||||
try {
|
||||
for (auto &b : rois_.boxes)
|
||||
if (b.GetName() == sel) {
|
||||
b = ROIBox(sel, fedit_[0]->text().toLongLong(), fedit_[1]->text().toLongLong(),
|
||||
fedit_[2]->text().toLongLong(), fedit_[3]->text().toLongLong());
|
||||
changed = true; break;
|
||||
}
|
||||
for (auto &c : rois_.circles)
|
||||
if (c.GetName() == sel) {
|
||||
c = ROICircle(sel, fedit_[0]->text().toDouble(), fedit_[1]->text().toDouble(),
|
||||
std::max(0.1, fedit_[2]->text().toDouble()));
|
||||
changed = true; break;
|
||||
}
|
||||
for (auto &a : rois_.azimuthal)
|
||||
if (a.GetName() == sel) {
|
||||
const float qmin = fedit_[0]->text().toFloat();
|
||||
const float qmax = fedit_[1]->text().toFloat();
|
||||
const float phimin = fedit_[2]->text().toFloat();
|
||||
const float phimax = fedit_[3]->text().toFloat();
|
||||
const float d_min = (qmax > 0) ? 2.0f * static_cast<float>(PI) / qmax : a.GetDMin_A();
|
||||
const float d_max = (qmin > 0) ? 2.0f * static_cast<float>(PI) / qmin : a.GetDMax_A();
|
||||
a = ROIAzimuthal(sel, d_min, d_max, phimin, phimax);
|
||||
changed = true; break;
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
emit roisChanged(rois_);
|
||||
|
||||
@@ -230,9 +230,9 @@ QWidget *JFJochViewerSettingsDock::BuildMXPage() {
|
||||
cellA_ = new NumberLineEdit(1.0, 2000.0, 79.0, 3, "", page);
|
||||
cellB_ = new NumberLineEdit(1.0, 2000.0, 79.0, 3, "", page);
|
||||
cellC_ = new NumberLineEdit(1.0, 2000.0, 38.0, 3, "", page);
|
||||
cellAlpha_ = new NumberLineEdit(1.0, 179.0, 90.0, 2, "", page);
|
||||
cellBeta_ = new NumberLineEdit(1.0, 179.0, 90.0, 2, "", page);
|
||||
cellGamma_ = new NumberLineEdit(1.0, 179.0, 90.0, 2, "", page);
|
||||
cellAlpha_ = new NumberLineEdit(5.0, 179.0, 90.0, 2, "", page);
|
||||
cellBeta_ = new NumberLineEdit(5.0, 179.0, 90.0, 2, "", page);
|
||||
cellGamma_ = new NumberLineEdit(5.0, 179.0, 90.0, 2, "", page);
|
||||
spaceGroup_ = new NumberLineEdit(0.0, 230.0, 0.0, 0, "", page);
|
||||
spaceGroup_->setToolTip("Space group number (1–230); 0 = unset.");
|
||||
spaceGroupName_ = new QLabel("—", page);
|
||||
@@ -682,12 +682,20 @@ QWidget *JFJochViewerSettingsDock::BuildAzIntSection() {
|
||||
az->addRow("Azimuthal bins", azimBins);
|
||||
azSection->setContentLayout(az);
|
||||
|
||||
// A combination can still be invalid (Low Q above High Q): the setters throw, and an exception
|
||||
// escaping a slot terminates the viewer. Build on a copy and keep the previous settings instead.
|
||||
auto emitAz = [=, this] {
|
||||
azint_.QRange_recipA(static_cast<float>(lowQ->value()),
|
||||
autoHighQ->isChecked() ? std::nullopt
|
||||
: std::optional<float>(highQ->value()));
|
||||
azint_.QSpacing_recipA(static_cast<float>(spacing->value()));
|
||||
azint_.AzimuthalBinCount(azimBins->currentData().toInt());
|
||||
AzimuthalIntegrationSettings s = azint_;
|
||||
try {
|
||||
s.QRange_recipA(static_cast<float>(lowQ->value()),
|
||||
autoHighQ->isChecked() ? std::nullopt
|
||||
: std::optional<float>(highQ->value()));
|
||||
s.QSpacing_recipA(static_cast<float>(spacing->value()));
|
||||
s.AzimuthalBinCount(azimBins->currentData().toInt());
|
||||
} catch (const std::exception &) {
|
||||
return;
|
||||
}
|
||||
azint_ = s;
|
||||
emit azintChanged(azint_);
|
||||
};
|
||||
connect(lowQ, &SliderPlusBox::valueChanged, this, [emitAz] { emitAz(); });
|
||||
@@ -792,7 +800,7 @@ QWidget *JFJochViewerSettingsDock::BuildBraggSection() {
|
||||
auto *gaussian = new QCheckBox("Gaussian profile fit", this);
|
||||
gaussian->setChecked(bragg_.GetIntegrator() != IntegratorMode::BoxSum);
|
||||
gaussian->setToolTip("Profile-fit the spots (more accurate intensities); off = classical box-sum.");
|
||||
auto *r1 = new NumberLineEdit(1.0f, 30.0f, bragg_.GetR1(), 1, "", this);
|
||||
auto *r1 = new NumberLineEdit(1.0f, 20.0f, bragg_.GetR1(), 1, "", this);
|
||||
auto *r2 = new NumberLineEdit(1.0f, 30.0f, bragg_.GetR2(), 1, "", this);
|
||||
auto *r3 = new NumberLineEdit(1.0f, 30.0f, bragg_.GetR3(), 1, "", this);
|
||||
auto *radii = new QHBoxLayout();
|
||||
@@ -817,15 +825,23 @@ QWidget *JFJochViewerSettingsDock::BuildBraggSection() {
|
||||
form->addRow(trimRow);
|
||||
section->setContentLayout(form);
|
||||
|
||||
// The radii must grow r1 < r2 < r3, which a single typed value can break: the setters throw, and an
|
||||
// exception escaping a slot terminates the viewer. Build on a copy and keep the previous settings.
|
||||
auto emitBragg = [=, this] {
|
||||
bragg_.Integrator(gaussian->isChecked() ? IntegratorMode::ProfileGaussian : IntegratorMode::BoxSum);
|
||||
bragg_.R1(static_cast<float>(r1->value())).R2(static_cast<float>(r2->value()))
|
||||
.R3(static_cast<float>(r3->value()));
|
||||
// Either estimator clears the other, so unchecking has to put the default clip back explicitly.
|
||||
if (bkgTrim->isChecked())
|
||||
bragg_.BackgroundTrimFraction(static_cast<float>(bkgTrimFrac->value()));
|
||||
else
|
||||
bragg_.BackgroundClipNSigma(BraggIntegrationSettings().GetBackgroundClipNSigma());
|
||||
BraggIntegrationSettings s = bragg_;
|
||||
try {
|
||||
s.Integrator(gaussian->isChecked() ? IntegratorMode::ProfileGaussian : IntegratorMode::BoxSum);
|
||||
s.R1(static_cast<float>(r1->value())).R2(static_cast<float>(r2->value()))
|
||||
.R3(static_cast<float>(r3->value()));
|
||||
// Either estimator clears the other, so unchecking has to put the default clip back explicitly.
|
||||
if (bkgTrim->isChecked())
|
||||
s.BackgroundTrimFraction(static_cast<float>(bkgTrimFrac->value()));
|
||||
else
|
||||
s.BackgroundClipNSigma(BraggIntegrationSettings().GetBackgroundClipNSigma());
|
||||
} catch (const std::exception &) {
|
||||
return;
|
||||
}
|
||||
bragg_ = s;
|
||||
emit braggChanged(bragg_);
|
||||
};
|
||||
connect(gaussian, &QCheckBox::toggled, this, [emitBragg] { emitBragg(); });
|
||||
@@ -909,27 +925,35 @@ QWidget *JFJochViewerSettingsDock::BuildScalingSection() {
|
||||
void JFJochViewerSettingsDock::EmitExperiment() {
|
||||
if (!have_experiment_)
|
||||
return;
|
||||
experiment_.IncidentEnergy_keV(static_cast<float>(energy_->value()));
|
||||
experiment_.DetectorDistance_mm(static_cast<float>(distance_->value()));
|
||||
experiment_.BeamX_pxl(static_cast<float>(beamX_->value()));
|
||||
experiment_.BeamY_pxl(static_cast<float>(beamY_->value()));
|
||||
experiment_.PoniRot1_rad(static_cast<float>(rot1_->value() * PI / 180.0));
|
||||
experiment_.PoniRot2_rad(static_cast<float>(rot2_->value() * PI / 180.0));
|
||||
experiment_.PolarizationFactor(polarizationOn_->isChecked()
|
||||
? std::optional<float>(static_cast<float>(polarization_->value())) : std::nullopt);
|
||||
experiment_.Goniometer(GoniometerFromFields());
|
||||
experiment_.GridScan(GridScanFromFields());
|
||||
if (cellKnown_->isChecked()) {
|
||||
experiment_.SetUnitCell(UnitCell{
|
||||
static_cast<float>(cellA_->value()), static_cast<float>(cellB_->value()),
|
||||
static_cast<float>(cellC_->value()), static_cast<float>(cellAlpha_->value()),
|
||||
static_cast<float>(cellBeta_->value()), static_cast<float>(cellGamma_->value())});
|
||||
const int sg = static_cast<int>(std::lround(spaceGroup_->value()));
|
||||
experiment_.SpaceGroupNumber(sg > 0 ? std::optional<int64_t>(sg) : std::nullopt);
|
||||
} else {
|
||||
experiment_.SetUnitCell(std::nullopt);
|
||||
experiment_.SpaceGroupNumber(std::nullopt);
|
||||
// The setters validate and throw; an exception escaping a slot terminates the viewer, so
|
||||
// an invalid entry keeps the previous experiment instead.
|
||||
DiffractionExperiment e = experiment_;
|
||||
try {
|
||||
e.IncidentEnergy_keV(static_cast<float>(energy_->value()));
|
||||
e.DetectorDistance_mm(static_cast<float>(distance_->value()));
|
||||
e.BeamX_pxl(static_cast<float>(beamX_->value()));
|
||||
e.BeamY_pxl(static_cast<float>(beamY_->value()));
|
||||
e.PoniRot1_rad(static_cast<float>(rot1_->value() * PI / 180.0));
|
||||
e.PoniRot2_rad(static_cast<float>(rot2_->value() * PI / 180.0));
|
||||
e.PolarizationFactor(polarizationOn_->isChecked()
|
||||
? std::optional<float>(static_cast<float>(polarization_->value())) : std::nullopt);
|
||||
e.Goniometer(GoniometerFromFields());
|
||||
e.GridScan(GridScanFromFields());
|
||||
if (cellKnown_->isChecked()) {
|
||||
e.SetUnitCell(UnitCell{
|
||||
static_cast<float>(cellA_->value()), static_cast<float>(cellB_->value()),
|
||||
static_cast<float>(cellC_->value()), static_cast<float>(cellAlpha_->value()),
|
||||
static_cast<float>(cellBeta_->value()), static_cast<float>(cellGamma_->value())});
|
||||
const int sg = static_cast<int>(std::lround(spaceGroup_->value()));
|
||||
e.SpaceGroupNumber(sg > 0 ? std::optional<int64_t>(sg) : std::nullopt);
|
||||
} else {
|
||||
e.SetUnitCell(std::nullopt);
|
||||
e.SpaceGroupNumber(std::nullopt);
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
return;
|
||||
}
|
||||
experiment_ = e;
|
||||
emit experimentChanged(experiment_);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user