Read any rotation axis by name, and tell a stationary axis from a sweep
Two things the goniometer handling conflated. The axis name is free-form everywhere that writes it - the API imposes only minLength, the CBOR map uses the name as its key, and tests/CBORTest.cpp round trips one literally called "z" - but the reader looked for exactly "/entry/sample/transformations/omega". A sweep recorded as "phi" therefore came back as stills, in the viewer and in rugnux, with nothing to indicate it. The reader now walks the transformations group and takes whichever axis is a rotation, preferring one that turns; the grid scan is read independently rather than as the else-branch of the same test, since a grid scan can be taken at a given head position. Second: "an axis is defined" and "the axis is turning" were the same question, answered inconsistently - GetImagesPerFile checked the increment, IsRotationIndexing did not, and the CBOR decoder deleted zero-increment axes outright so the ambiguity could never surface. GoniometerAxis::IsScanning now asks it explicitly and the call sites go through it, so a stationary axis can be carried without being mistaken for rotation data. That mistake is not hypothetical: RotationIndexerCounter leaves its stride at zero for a zero increment, and Process() then never fires, so indexing would silently never run. Keeping stationary axes is also what lets the writer state where the head was for a still or a grid scan, which is the next step. JFJochReader_Goniometer_NonOmegaName covers the naming case through the writer and back; nothing did before, because both existing round trips use "omega". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -604,21 +604,40 @@ HDF5MetadataSource::OpenResult HDF5MetadataSource::Open(const std::string &filen
|
||||
metadata.SourceName(master_file->GetString("/entry/source/name"));
|
||||
dataset->experiment.ImportInstrumentMetadata(metadata);
|
||||
|
||||
// The rotation axis is whatever the file calls it. The name is free-form throughout the API,
|
||||
// the CBOR stream and the writer, so looking only for "omega" - as this did - read a sweep
|
||||
// recorded as "phi" back as stills, silently. Prefer an axis that actually turns; fall back
|
||||
// to a stationary one, which still says where the head was.
|
||||
if (master_file->Exists("/entry/sample/transformations")) {
|
||||
if (master_file->Exists("/entry/sample/transformations/omega")) {
|
||||
auto omega = ReadAxis(master_file.get(), "omega");
|
||||
dataset->experiment.Goniometer(omega);
|
||||
} else if (master_file->Exists("/entry/sample/grid_scan")) {
|
||||
GridScanSettings grid(
|
||||
master_file->GetInt("/entry/sample/grid_scan/n_fast"),
|
||||
master_file->GetFloat("/entry/sample/grid_scan/step_x") * 1e6f,
|
||||
master_file->GetFloat("/entry/sample/grid_scan/step_y") * 1e6f,
|
||||
master_file->GetOptBool("/entry/sample/grid_scan/snake_scan").value_or(false),
|
||||
master_file->GetOptBool("/entry/sample/grid_scan/vertical_scan").value_or(false)
|
||||
);
|
||||
grid.ImageNum(number_of_images);
|
||||
dataset->experiment.GridScan(grid);
|
||||
std::optional<GoniometerAxis> stationary;
|
||||
for (const auto &name: master_file->FindLeafs("/entry/sample/transformations")) {
|
||||
auto axis = ReadAxis(master_file.get(), name);
|
||||
if (!axis.has_value())
|
||||
continue;
|
||||
if (axis->IsScanning()) {
|
||||
dataset->experiment.Goniometer(axis);
|
||||
stationary.reset();
|
||||
break;
|
||||
}
|
||||
if (!stationary.has_value())
|
||||
stationary = axis;
|
||||
}
|
||||
if (stationary.has_value())
|
||||
dataset->experiment.Goniometer(stationary);
|
||||
}
|
||||
|
||||
// Independent of the axis: a grid scan can be taken at a given head position, so the two are
|
||||
// not alternatives.
|
||||
if (master_file->Exists("/entry/sample/grid_scan")) {
|
||||
GridScanSettings grid(
|
||||
master_file->GetInt("/entry/sample/grid_scan/n_fast"),
|
||||
master_file->GetFloat("/entry/sample/grid_scan/step_x") * 1e6f,
|
||||
master_file->GetFloat("/entry/sample/grid_scan/step_y") * 1e6f,
|
||||
master_file->GetOptBool("/entry/sample/grid_scan/snake_scan").value_or(false),
|
||||
master_file->GetOptBool("/entry/sample/grid_scan/vertical_scan").value_or(false)
|
||||
);
|
||||
grid.ImageNum(number_of_images);
|
||||
dataset->experiment.GridScan(grid);
|
||||
}
|
||||
|
||||
auto tmp = master_file->ReadOptVector<float>("/entry/sample/unit_cell");
|
||||
@@ -1060,16 +1079,19 @@ std::optional<GoniometerAxis> HDF5MetadataSource::ReadAxis(HDF5Object *file, con
|
||||
std::vector<double> angle;
|
||||
dataset.ReadVector(angle);
|
||||
|
||||
if (angle.size() < 2)
|
||||
if (angle.empty())
|
||||
return {};
|
||||
|
||||
if (dataset.ReadAttrStr("transformation_type") != "rotation")
|
||||
return {};
|
||||
|
||||
std::vector<double> end = file->ReadOptVector<double>(dname + "_end");
|
||||
|
||||
// A single value, or every value the same, is a stationary axis: it says where the head was
|
||||
// rather than that anything turned. Increment 0 is the honest description of that, and
|
||||
// GoniometerAxis::IsScanning is what separates it from a sweep.
|
||||
double start = angle[0];
|
||||
double incr = angle[1] - angle[0];
|
||||
|
||||
if (dataset.ReadAttrStr("transformation_type") != "rotation")
|
||||
return {};
|
||||
double incr = (angle.size() < 2) ? 0.0 : angle[1] - angle[0];
|
||||
|
||||
std::vector<double> axis_vec = dataset.ReadAttrVec("vector");
|
||||
if (axis_vec.size() != 3)
|
||||
|
||||
Reference in New Issue
Block a user