diff --git a/Core/Instrument/IUnitConverter.h b/Core/Instrument/IUnitConverter.h index 38d589b775cfb1b64eb76d0ca2d0c03c7fdd51a6..a60d729b365896a0fa5ae3665884e9d7d8ca7395 100644 --- a/Core/Instrument/IUnitConverter.h +++ b/Core/Instrument/IUnitConverter.h @@ -31,7 +31,7 @@ class IAxis; // workaround for SWIG (instead of just writing enum class AxesUnits...) struct BA_CORE_API_ AxesUnitsWrap { - enum AxesUnits { DEFAULT, NBINS, RADIANS, DEGREES, MM, QSPACE }; + enum AxesUnits { DEFAULT, NBINS, RADIANS, DEGREES, MM, NM, QSPACE }; }; typedef AxesUnitsWrap::AxesUnits AxesUnits; @@ -51,7 +51,7 @@ public: virtual double calculateMax(size_t i_axis, AxesUnits units_type) const=0; virtual size_t axisSize(size_t i_axis) const=0; - std::string axisName(size_t i_axis, AxesUnits units_type = AxesUnits::DEFAULT) const; + virtual std::string axisName(size_t i_axis, AxesUnits units_type = AxesUnits::DEFAULT) const; virtual AxesUnits defaultUnits() const=0; #ifndef SWIG diff --git a/Core/Instrument/SimpleUnitConverters.cpp b/Core/Instrument/SimpleUnitConverters.cpp index 0dcd1fd09b23ebbfe1b9b5415fe91432684024a4..376cc33bd1927de3dd73a3666b44e61c2c6723e4 100644 --- a/Core/Instrument/SimpleUnitConverters.cpp +++ b/Core/Instrument/SimpleUnitConverters.cpp @@ -350,3 +350,105 @@ void OffSpecularConverter::addDetectorYAxis(const IDetector2D& detector) "wrong detector type"); } } + +/* DepthProbeConverter **********************************************/ + +const std::string z_axis_name = "Position [nm]"; + +DepthProbeConverter::DepthProbeConverter(const Beam& beam, const IAxis& alpha_axis, + const IAxis& z_axis) + : UnitConverterSimple(beam) +{ + auto alpha_axis_name = axisName(0); + addAxisData(alpha_axis_name, alpha_axis.getMin(), alpha_axis.getMax(), defaultUnits(), + alpha_axis.size()); + addZAxis(z_axis); +} + +DepthProbeConverter::~DepthProbeConverter() = default; + +DepthProbeConverter* DepthProbeConverter::clone() const +{ + return new DepthProbeConverter(*this); +} + +double DepthProbeConverter::calculateMin(size_t i_axis, AxesUnits units_type) const +{ + checkForDefaultUnits(units_type); + if (i_axis > 0) { + checkIndex(i_axis); + const AxisData& axis_data = m_axis_data_table[i_axis]; + return axis_data.min; + } + return UnitConverterSimple::calculateMin(i_axis, units_type); +} + +double DepthProbeConverter::calculateMax(size_t i_axis, AxesUnits units_type) const +{ + checkForDefaultUnits(units_type); + if (i_axis > 0) { + checkIndex(i_axis); + const AxisData& axis_data = m_axis_data_table[i_axis]; + return axis_data.max; + } + return UnitConverterSimple::calculateMax(i_axis, units_type); +} + +std::string DepthProbeConverter::axisName(size_t i_axis, AxesUnits units_type) const +{ + checkForDefaultUnits(units_type); + if (i_axis > 0) { + checkIndex(i_axis); + return z_axis_name; + } + return IUnitConverter::axisName(i_axis, units_type); + +} + +std::unique_ptr<IAxis> DepthProbeConverter::createConvertedAxis(size_t i_axis, + AxesUnits units) const +{ + checkForDefaultUnits(units); + if (i_axis > 0) { + checkIndex(i_axis); + const AxisData& axis_data = m_axis_data_table[i_axis]; + return std::make_unique<FixedBinAxis>(axis_data.name, axis_data.nbins, axis_data.min, + axis_data.max); + } + return UnitConverterSimple::createConvertedAxis(i_axis, units); +} + +DepthProbeConverter::DepthProbeConverter(const DepthProbeConverter& other) + : UnitConverterSimple(other) +{} + +void DepthProbeConverter::checkForDefaultUnits(AxesUnits units_type) const +{ + if (units_type != AxesUnits::DEFAULT) + throw std::runtime_error( + "Error DepthProbeConverter::checkForDefaultUnits: only default units are allowed."); +} + +double DepthProbeConverter::calculateValue(size_t, AxesUnits units_type, double value) const +{ + switch(units_type) { + case AxesUnits::DEGREES: + return Units::rad2deg(value); + default: + throw std::runtime_error("Error in DepthProbeConverter::calculateValue: " + "target units not available: " + + std::to_string(static_cast<int>(units_type))); + } +} + +std::vector<std::map<AxesUnits, std::string>> DepthProbeConverter::createNameMaps() const +{ + std::vector<std::map<AxesUnits, std::string>> result; + result.push_back(AxisNames::InitSpecAxis()); + return result; +} + +void DepthProbeConverter::addZAxis(const IAxis &z_axis) +{ + addAxisData(z_axis_name, z_axis.getMin(), z_axis.getMax(), AxesUnits::NM, z_axis.size()); +} diff --git a/Core/Instrument/SimpleUnitConverters.h b/Core/Instrument/SimpleUnitConverters.h index 5e0eb7d9bfeb730503311253cd4f973a394f75fe..6f48b538f9bf0f9ee19b2125a44b769df68d7d95 100644 --- a/Core/Instrument/SimpleUnitConverters.h +++ b/Core/Instrument/SimpleUnitConverters.h @@ -134,4 +134,32 @@ private: void addDetectorYAxis(const IDetector2D& detector); }; +//! DepthProbeConverter class handles the unit translations for depth probe simulations +//! Its default units are radians for x-axis and nm for y-axis +//! @ingroup simulation_internal + +class BA_CORE_API_ DepthProbeConverter : public UnitConverterSimple +{ +public: + DepthProbeConverter(const Beam& beam, const IAxis& alpha_axis, const IAxis& z_axis); + virtual ~DepthProbeConverter(); + + DepthProbeConverter* clone() const override; + + double calculateMin(size_t i_axis, AxesUnits units_type) const override; + double calculateMax(size_t i_axis, AxesUnits units_type) const override; + + // TODO: remove axisName after implementing functionality for two axis in different units + std::string axisName(size_t i_axis, AxesUnits units_type = AxesUnits::DEFAULT) const override; + AxesUnits defaultUnits() const override { return AxesUnits::DEGREES; } + std::unique_ptr<IAxis> createConvertedAxis(size_t i_axis, AxesUnits units) const override; + +private: + DepthProbeConverter(const DepthProbeConverter& other); + void checkForDefaultUnits(AxesUnits units_type) const; + double calculateValue(size_t, AxesUnits units_type, double value) const override; + std::vector<std::map<AxesUnits, std::string>> createNameMaps() const override; + void addZAxis(const IAxis& z_axis); +}; + #endif // SIMPLEUNITCONVERTERS_H diff --git a/Tests/UnitTests/Core/Axes/DepthProbeConverterTest.h b/Tests/UnitTests/Core/Axes/DepthProbeConverterTest.h new file mode 100644 index 0000000000000000000000000000000000000000..6863ef3d2f01a6dfb35688bae4ff6f7c21e9d74b --- /dev/null +++ b/Tests/UnitTests/Core/Axes/DepthProbeConverterTest.h @@ -0,0 +1,120 @@ +#include "google_test.h" +#include "Beam.h" +#include "FixedBinAxis.h" +#include "MathConstants.h" +#include "SimpleUnitConverters.h" +#include "Units.h" + +class DepthProbeConverterTest : public ::testing::Test +{ +public: + DepthProbeConverterTest(); + ~DepthProbeConverterTest(); + +protected: + void checkMainFunctionality(const DepthProbeConverter& test_object); + void checkAlphaAxis(AxesUnits units, const DepthProbeConverter& test_object); + void checkZAxis(AxesUnits units, const DepthProbeConverter& test_object); + const double m_alpha_start = 0.5; // first axis value in rads + const double m_alpha_end = 1.0; // last axis value in rads + const double m_z_start = -30.0; + const double m_z_end = 10.0; + const size_t m_nbins = 100; + Beam m_beam; + FixedBinAxis m_inclination_axis; + FixedBinAxis m_z_axis; +}; + +DepthProbeConverterTest::DepthProbeConverterTest() + : m_inclination_axis("Angles", m_nbins, m_alpha_start, m_alpha_end) // angles in radians + , m_z_axis("Positions", m_nbins, m_z_start, m_z_end) // z positions in nm +{ + m_beam.setCentralK(1.0, 0.0, 0.0); // wavelength = 1.0 nm +} + +DepthProbeConverterTest::~DepthProbeConverterTest() = default; + +void DepthProbeConverterTest::checkMainFunctionality(const DepthProbeConverter& test_object) +{ + EXPECT_EQ(test_object.dimension(), 2u); + + EXPECT_NEAR(test_object.calculateMin(0, AxesUnits::DEFAULT), Units::rad2deg(m_alpha_start), + Units::rad2deg(m_alpha_start) * 1e-10); + + EXPECT_NEAR(test_object.calculateMax(0, AxesUnits::DEFAULT), Units::rad2deg(m_alpha_end), + Units::rad2deg(m_alpha_end) * 1e-10); + + checkAlphaAxis(AxesUnits::DEFAULT, test_object); + checkZAxis(AxesUnits::DEFAULT, test_object); +} + +void DepthProbeConverterTest::checkAlphaAxis(AxesUnits units, + const DepthProbeConverter& test_object) +{ + auto axis = test_object.createConvertedAxis(0, units); + EXPECT_TRUE(dynamic_cast<FixedBinAxis*>(axis.get())); + EXPECT_EQ(axis->size(), test_object.axisSize(0)); + EXPECT_EQ(axis->size(), m_nbins); + EXPECT_EQ(axis->getName(), test_object.axisName(0, units)); + EXPECT_EQ(axis->getMin(), test_object.calculateMin(0, units)); + EXPECT_EQ(axis->getMax(), test_object.calculateMax(0, units)); +} + +void DepthProbeConverterTest::checkZAxis(AxesUnits units, const DepthProbeConverter& test_object) +{ + auto axis = test_object.createConvertedAxis(1, units); + EXPECT_TRUE(dynamic_cast<FixedBinAxis*>(axis.get())); + EXPECT_EQ(axis->size(), test_object.axisSize(1)); + EXPECT_EQ(axis->size(), m_nbins); + EXPECT_EQ(axis->getName(), test_object.axisName(1, units)); + EXPECT_EQ(axis->getMin(), test_object.calculateMin(1, units)); + EXPECT_NEAR(axis->getMin(), m_z_start, std::abs(m_z_start) * 1e-10); + EXPECT_EQ(axis->getMax(), test_object.calculateMax(1, units)); + EXPECT_NEAR(axis->getMax(), m_z_end, std::abs(m_z_end) * 1e-10); +} + +TEST_F(DepthProbeConverterTest, DepthProbeConverter) +{ + DepthProbeConverter converter(m_beam, m_inclination_axis, m_z_axis); + checkMainFunctionality(converter); +} + +TEST_F(DepthProbeConverterTest, DepthProbeConverterExceptions) +{ + DepthProbeConverter converter(m_beam, m_inclination_axis, m_z_axis); + + EXPECT_THROW(converter.axisName(0, AxesUnits::MM), std::runtime_error); + EXPECT_THROW(converter.axisName(0, AxesUnits::DEGREES), std::runtime_error); + EXPECT_THROW(converter.axisName(0, AxesUnits::RADIANS), std::runtime_error); + EXPECT_THROW(converter.axisName(0, AxesUnits::QSPACE), std::runtime_error); + EXPECT_THROW(converter.axisName(1, AxesUnits::MM), std::runtime_error); + EXPECT_THROW(converter.axisName(2, AxesUnits::DEFAULT), std::runtime_error); + + EXPECT_THROW(converter.calculateMin(0, AxesUnits::MM), std::runtime_error); + EXPECT_THROW(converter.calculateMin(0, AxesUnits::DEGREES), std::runtime_error); + EXPECT_THROW(converter.calculateMin(0, AxesUnits::RADIANS), std::runtime_error); + EXPECT_THROW(converter.calculateMin(0, AxesUnits::QSPACE), std::runtime_error); + EXPECT_THROW(converter.calculateMin(1, AxesUnits::MM), std::runtime_error); + EXPECT_THROW(converter.calculateMin(2, AxesUnits::DEFAULT), std::runtime_error); + + EXPECT_THROW(converter.calculateMax(0, AxesUnits::MM), std::runtime_error); + EXPECT_THROW(converter.calculateMax(0, AxesUnits::DEGREES), std::runtime_error); + EXPECT_THROW(converter.calculateMax(0, AxesUnits::RADIANS), std::runtime_error); + EXPECT_THROW(converter.calculateMax(0, AxesUnits::QSPACE), std::runtime_error); + EXPECT_THROW(converter.calculateMax(1, AxesUnits::MM), std::runtime_error); + EXPECT_THROW(converter.calculateMax(2, AxesUnits::RADIANS), std::runtime_error); + + EXPECT_THROW(converter.createConvertedAxis(0, AxesUnits::MM), std::runtime_error); + EXPECT_THROW(converter.createConvertedAxis(0, AxesUnits::DEGREES), std::runtime_error); + EXPECT_THROW(converter.createConvertedAxis(0, AxesUnits::RADIANS), std::runtime_error); + EXPECT_THROW(converter.createConvertedAxis(0, AxesUnits::QSPACE), std::runtime_error); + EXPECT_THROW(converter.createConvertedAxis(1, AxesUnits::MM), std::runtime_error); + EXPECT_THROW(converter.createConvertedAxis(2, AxesUnits::DEFAULT), std::runtime_error); +} + +TEST_F(DepthProbeConverterTest, DepthProbeConverterClone) +{ + DepthProbeConverter converter(m_beam, m_inclination_axis, m_z_axis); + std::unique_ptr<DepthProbeConverter> converter_clone(converter.clone()); + checkMainFunctionality(*converter_clone); +} diff --git a/Tests/UnitTests/Core/Axes/testlist.h b/Tests/UnitTests/Core/Axes/testlist.h index e8a56eb52d4d3f625eea5b2e812f6be5a63e3502..43c82e305c659088ce7d1f5f75c0710f34978653 100644 --- a/Tests/UnitTests/Core/Axes/testlist.h +++ b/Tests/UnitTests/Core/Axes/testlist.h @@ -1,6 +1,7 @@ // To renew this file, run /G/ba/dev-tools/code-tools/update-gtestlist.py <directory> #include "CustomBinAxisTest.h" +#include "DepthProbeConverterTest.h" #include "Histogram1DTest.h" #include "VariableBinAxisTest.h" #include "Histogram2DTest.h"