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>
148 lines
4.7 KiB
C++
148 lines
4.7 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "JFJochMath.h"
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
#include "GoniometerAxis.h"
|
|
#include "JFJochException.h"
|
|
|
|
#define check_finite(param, val) if (!std::isfinite(val)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, param)
|
|
|
|
std::string to_lower(std::string s) {
|
|
std::ranges::transform(s, s.begin(),
|
|
[](unsigned char c) { return std::tolower(c); });
|
|
return s;
|
|
}
|
|
|
|
GoniometerAxis::GoniometerAxis(const std::string& in_name,
|
|
float in_start,
|
|
float in_increment,
|
|
const Coord &in_axis,
|
|
const std::optional<Coord> &in_helical_step) {
|
|
if (in_name.empty())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Name of goniometer axis cannot be empty");
|
|
|
|
check_finite("Rotation angle increment", in_increment);
|
|
check_finite("Rotation angle start", in_start);
|
|
|
|
if (in_axis.Length() == 0.0f)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Rotation axis cannot have 0 length");
|
|
|
|
name = to_lower(in_name);
|
|
start = in_start;
|
|
increment = in_increment;
|
|
axis = in_axis.Normalize(); // Make sure rotation axis is normalized!
|
|
helical_step = in_helical_step;
|
|
}
|
|
|
|
GoniometerAxis &GoniometerAxis::ScreeningWedge(const std::optional<float> &input) {
|
|
screening_wedge = input;
|
|
return *this;
|
|
}
|
|
|
|
GoniometerAxis &GoniometerAxis::Axis(const Coord &input) {
|
|
float len = input.Length();
|
|
if (len == 0.0f)
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"Rotation axis cannot have 0 length");
|
|
// increment *= len;
|
|
axis = input.Normalize();
|
|
return *this;
|
|
}
|
|
|
|
std::string GoniometerAxis::GetName() const {
|
|
return name;
|
|
}
|
|
|
|
float GoniometerAxis::GetStart_deg() const {
|
|
return start;
|
|
}
|
|
|
|
float GoniometerAxis::GetIncrement_deg() const {
|
|
return increment;
|
|
}
|
|
|
|
bool GoniometerAxis::IsScanning() const {
|
|
return increment != 0.0f;
|
|
}
|
|
|
|
Coord GoniometerAxis::GetAxis() const {
|
|
return axis;
|
|
}
|
|
|
|
std::optional<Coord> GoniometerAxis::GetHelicalStep() const {
|
|
return helical_step;
|
|
}
|
|
|
|
Coord GoniometerAxis::GetPosition(int64_t image_number) const {
|
|
return helical_step.value_or(Coord()) * static_cast<float>(image_number);
|
|
}
|
|
|
|
float GoniometerAxis::GetAngle_deg(float image_number) const {
|
|
return start + increment * image_number;
|
|
}
|
|
|
|
std::vector<double> GoniometerAxis::GetAxisVector() const {
|
|
return {axis[0], axis[1], axis[2]};
|
|
}
|
|
|
|
std::vector<double> GoniometerAxis::GetXContainer_m(int64_t max_image_number) const {
|
|
if (!helical_step.has_value())
|
|
return {};
|
|
std::vector<double> angle_container(max_image_number);
|
|
for (int32_t i = 0; i < max_image_number; i++)
|
|
angle_container[i] = helical_step->x * i * 1e-6;
|
|
return angle_container;
|
|
}
|
|
|
|
std::vector<double> GoniometerAxis::GetYContainer_m(int64_t max_image_number) const {
|
|
if (!helical_step.has_value())
|
|
return {};
|
|
std::vector<double> angle_container(max_image_number);
|
|
for (int32_t i = 0; i < max_image_number; i++)
|
|
angle_container[i] = helical_step->y * i * 1e-6;
|
|
return angle_container;
|
|
}
|
|
|
|
std::vector<double> GoniometerAxis::GetZContainer_m(int64_t max_image_number) const {
|
|
if (!helical_step.has_value())
|
|
return {};
|
|
std::vector<double> angle_container(max_image_number);
|
|
for (int32_t i = 0; i < max_image_number; i++)
|
|
angle_container[i] = helical_step->z * i * 1e-6;
|
|
return angle_container;
|
|
}
|
|
|
|
std::vector<double> GoniometerAxis::GetAngleContainer(int64_t max_image_number) const {
|
|
std::vector<double> angle_container(max_image_number);
|
|
for (int32_t i = 0; i < max_image_number; i++)
|
|
angle_container[i] = GetAngle_deg(i);
|
|
return angle_container;
|
|
}
|
|
|
|
std::optional<float> GoniometerAxis::GetScreeningWedge() const {
|
|
return screening_wedge;
|
|
}
|
|
|
|
float GoniometerAxis::GetWedge_deg() const {
|
|
if (!screening_wedge.has_value())
|
|
return GetIncrement_deg();
|
|
return *screening_wedge;
|
|
}
|
|
|
|
std::vector<double> GoniometerAxis::GetAngleContainerEnd(int64_t max_image_number) const {
|
|
float wedge = GetWedge_deg();
|
|
std::vector<double> angle_container(max_image_number);
|
|
for (int32_t i = 0; i < max_image_number; i++)
|
|
angle_container[i] = GetAngle_deg(i) + wedge;
|
|
return angle_container;
|
|
}
|
|
|
|
RotMatrix GoniometerAxis::GetTransformationAngle(float angle_deg) const {
|
|
auto angle_rad = angle_deg / 180.0f * static_cast<float>(PI);
|
|
return {angle_rad, axis};
|
|
} |