| Message ID | 20260918120949.191668-20-barnabas.pocze@ideasonboard.com |
|---|---|
| State | New |
| Headers | show |
| Series |
|
| Related | show |
Hi Barnabás On Fri, Sep 18, 2026 at 02:09:47PM +0200, Barnabás Pőcze wrote: > From: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > Add the algorithm to the rppx1 ipa module based on the corresponding > algorithm in the rkisp1 ipa module. > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> I just noticed all these patches are missing you sign-off > --- > src/ipa/rppx1/algorithms/gsl.cpp | 151 +++++++++++++++++++++++++++ > src/ipa/rppx1/algorithms/gsl.h | 35 +++++++ > src/ipa/rppx1/algorithms/meson.build | 1 + > src/ipa/rppx1/params.h | 4 + > 4 files changed, 191 insertions(+) > create mode 100644 src/ipa/rppx1/algorithms/gsl.cpp > create mode 100644 src/ipa/rppx1/algorithms/gsl.h > > diff --git a/src/ipa/rppx1/algorithms/gsl.cpp b/src/ipa/rppx1/algorithms/gsl.cpp > new file mode 100644 > index 0000000000..1f2d52d338 > --- /dev/null > +++ b/src/ipa/rppx1/algorithms/gsl.cpp > @@ -0,0 +1,151 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Ideas On Board > + * > + * RPP-X1 Gamma Sensor Linearization control > + */ > + > +#include "gsl.h" > + > +#include <algorithm> > +#include <array> > +#include <cmath> > +#include <span> > +#include <string_view> > + > +#include <libcamera/base/log.h> > +#include <libcamera/base/utils.h> > + > +#include "libcamera/internal/value_node.h" > + > +/** > + * \file gsl.h > + */ > + > +namespace libcamera { > + > +namespace ipa::rppx1::algorithms { > + > +LOG_DEFINE_CATEGORY(RppX1Gsl) > + > +/* `LinPre1` only */ > +constexpr uint32_t kDxOffset = 8; > +constexpr uint32_t kMaxDx = (1u << 4) - 1; > +constexpr uint32_t kResolution = 1u << 24; > + > +namespace { > + > +[[nodiscard]] > +bool > +parseYLut(const ValueNode &yObject, std::string_view key, uint32_t scale, bool parseYLut(const ValueNode &yObject, std::string_view key, uint32_t scale, > + std::span<uint32_t, RPPX1_LIN_DEGAMMA_CURVE_NUM> res) > +{ > + auto vals = yObject[key].get<std::vector<float>>().value_or(utils::defopt); > + if (vals.size() != res.size()) { > + LOG(RppX1Gsl, Error) > + << "Invalid 'y:" << key << "': expected " What's the "y:" for ? This will read "Invalid 'y:red': is it intentional ? > + << res.size() << " elements, got " << vals.size(); > + return false; > + } > + > + for (const auto &[i, y] : utils::enumerate(vals)) { > + if (y < 0 || y > 1) { rkisp1 has integers in the tuning file. I think it's better to have floats like you do here, but this means we can't exchange tuning data between the two platforms. > + LOG(RppX1Gsl, Error) > + << "Invalid 'y:" << key << "': elements must be in [0; 1]"; > + return false; > + } > + > + res[i] = std::lround(y * scale); > + } > + > + return true; > +} > + > +} /* namespace */ > + > +/** > + * \copydoc libcamera::ipa::Algorithm::init > + */ > +int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context, > + const ValueNode &tuningData) > +{ > + /* > + * \todo `LinPre2` has different ranges: > + * * kDxOffset = 4 > + * * kMaxDx = 7 > + * * kResolution = 2^12 > + * Maybe this parameter could be reworked to be compatible with both. > + */ > + gammaDx_ = tuningData["x-intervals"].get<std::vector<uint8_t>>().value_or(utils::defopt); > + if (gammaDx_.size() != RPPX1_LIN_SAMPLE_POINTS_NUM) { > + LOG(RppX1Gsl, Error) > + << "Invalid 'x-intervals': expected " > + << RPPX1_LIN_SAMPLE_POINTS_NUM << " elements, got " > + << gammaDx_.size(); > + > + return -EINVAL; > + } > + > + uint32_t xSum = 0; > + for (const auto &x : gammaDx_) { for (uint8_t x : gammaDx_) ? Or does a const & to a pod type make any difference ? > + if (x > kMaxDx) { > + LOG(RppX1Gsl, Error) > + << "Invalid 'x-intervals': must be at most " << kMaxDx > + << ", got " << x; > + return -EINVAL; > + } > + > + xSum += 1u << (x + kDxOffset); > + } > + > + /* > + * > Typically, the accumulated sum of dx[i] should cover the complete data Stray > at the begining of the line > + * > input range of [0; 2^12) or [0; 2^24). > + */ > + if (xSum != kResolution) { > + LOG(RppX1Gsl, Error) > + << "Invalid 'x-intervals': must cover full input range of " << kResolution > + << ", got " << xSum; > + return -EINVAL; > + } This is a stricter requirement compared to rkisp1, I wonder if we need to be so strict > + > + const ValueNode &yObject = tuningData["y"]; > + if (!yObject.isDictionary()) { > + LOG(RppX1Gsl, Error) > + << "Invalid 'y': must be a dictionary"; > + return -EINVAL; > + } > + > + if (!parseYLut(yObject, "red", kResolution - 1, curveYr_) || > + !parseYLut(yObject, "green", kResolution - 1, curveYg_) || > + !parseYLut(yObject, "blue", kResolution - 1, curveYb_)) > + return -EINVAL; > + > + return 0; > +} > + > +/** > + * \copydoc libcamera::ipa::Algorithm::prepare > + */ > +void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context, > + const uint32_t frame, > + [[maybe_unused]] IPAFrameContext &frameContext, > + RppX1Params *params) > +{ > + if (frame > 0) > + return; > + > + auto config = params->block<BlockType::LinPre1>(); > + config.setEnabled(true); > + > + std::copy(gammaDx_.begin(), gammaDx_.end(), config->dx); > + std::copy(curveYr_.begin(), curveYr_.end(), config->curve_r); > + std::copy(curveYg_.begin(), curveYg_.end(), config->curve_g); > + std::copy(curveYb_.begin(), curveYb_.end(), config->curve_b); > +} > + > +REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization") > + > +} /* namespace ipa::rppx1::algorithms */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/rppx1/algorithms/gsl.h b/src/ipa/rppx1/algorithms/gsl.h > new file mode 100644 > index 0000000000..0c86fbf1ff > --- /dev/null > +++ b/src/ipa/rppx1/algorithms/gsl.h > @@ -0,0 +1,35 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026, Ideas On Board > + * > + * RPP-X1 Gamma Sensor Linearization control > + */ > + > +#pragma once > + > +#include <vector> > + > +#include "algorithm.h" > + > +namespace libcamera { > + > +namespace ipa::rppx1::algorithms { > + > +class GammaSensorLinearization : public Algorithm > +{ > +public: > + int init(IPAContext &context, const ValueNode &tuningData) override; > + void prepare(IPAContext &context, const uint32_t frame, > + IPAFrameContext &frameContext, > + RppX1Params *params) override; > + > +private: > + std::vector<uint8_t> gammaDx_; > + std::array<uint32_t, RPPX1_LIN_DEGAMMA_CURVE_NUM> curveYr_; > + std::array<uint32_t, RPPX1_LIN_DEGAMMA_CURVE_NUM> curveYg_; > + std::array<uint32_t, RPPX1_LIN_DEGAMMA_CURVE_NUM> curveYb_; > +}; > + > +} /* namespace ipa::rppx1::algorithms */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build > index d93b22c634..e7d78a00d2 100644 > --- a/src/ipa/rppx1/algorithms/meson.build > +++ b/src/ipa/rppx1/algorithms/meson.build > @@ -6,5 +6,6 @@ rppx1_ipa_algorithms = files([ > 'blc.cpp', > 'ccm.cpp', > 'goc.cpp', > + 'gsl.cpp', > 'lux.cpp', > ]) > diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h > index b724fa3162..87a70d960f 100644 > --- a/src/ipa/rppx1/params.h > +++ b/src/ipa/rppx1/params.h > @@ -22,6 +22,8 @@ enum class BlockType : uint16_t { > ExmPre1, > GaHv, > HistPost, > + LinPre1, > + LinPre2, > WbMeasPost, > }; > > @@ -45,6 +47,8 @@ RPPX1_DEFINE_BLOCK_TYPE(CcorPost, ccor, CCOR_POST) > RPPX1_DEFINE_BLOCK_TYPE(ExmPre1, exm, EXM_PRE1) > RPPX1_DEFINE_BLOCK_TYPE(GaHv, ga, GA_HV) > RPPX1_DEFINE_BLOCK_TYPE(HistPost, hist, HIST_POST) > +RPPX1_DEFINE_BLOCK_TYPE(LinPre1, lin, LIN_PRE1) > +RPPX1_DEFINE_BLOCK_TYPE(LinPre2, lin, LIN_PRE2) > RPPX1_DEFINE_BLOCK_TYPE(WbMeasPost, wbmeas, WBMEAS_POST) > > struct params_traits { Minors apart Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > -- > 2.55.0 >
2026. 09. 25. 9:51 keltezéssel, Jacopo Mondi írta: > Hi Barnabás > > On Fri, Sep 18, 2026 at 02:09:47PM +0200, Barnabás Pőcze wrote: >> From: Jacopo Mondi <jacopo.mondi@ideasonboard.com> >> >> Add the algorithm to the rppx1 ipa module based on the corresponding >> algorithm in the rkisp1 ipa module. >> >> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > I just noticed all these patches are missing you sign-off > >> --- >> src/ipa/rppx1/algorithms/gsl.cpp | 151 +++++++++++++++++++++++++++ >> src/ipa/rppx1/algorithms/gsl.h | 35 +++++++ >> src/ipa/rppx1/algorithms/meson.build | 1 + >> src/ipa/rppx1/params.h | 4 + >> 4 files changed, 191 insertions(+) >> create mode 100644 src/ipa/rppx1/algorithms/gsl.cpp >> create mode 100644 src/ipa/rppx1/algorithms/gsl.h >> >> diff --git a/src/ipa/rppx1/algorithms/gsl.cpp b/src/ipa/rppx1/algorithms/gsl.cpp >> new file mode 100644 >> index 0000000000..1f2d52d338 >> --- /dev/null >> +++ b/src/ipa/rppx1/algorithms/gsl.cpp >> @@ -0,0 +1,151 @@ >> +/* SPDX-License-Identifier: LGPL-2.1-or-later */ >> +/* >> + * Copyright (C) 2026, Ideas On Board >> + * >> + * RPP-X1 Gamma Sensor Linearization control >> + */ >> + >> +#include "gsl.h" >> + >> +#include <algorithm> >> +#include <array> >> +#include <cmath> >> +#include <span> >> +#include <string_view> >> + >> +#include <libcamera/base/log.h> >> +#include <libcamera/base/utils.h> >> + >> +#include "libcamera/internal/value_node.h" >> + >> +/** >> + * \file gsl.h >> + */ >> + >> +namespace libcamera { >> + >> +namespace ipa::rppx1::algorithms { >> + >> +LOG_DEFINE_CATEGORY(RppX1Gsl) >> + >> +/* `LinPre1` only */ >> +constexpr uint32_t kDxOffset = 8; >> +constexpr uint32_t kMaxDx = (1u << 4) - 1; >> +constexpr uint32_t kResolution = 1u << 24; >> + >> +namespace { >> + >> +[[nodiscard]] >> +bool >> +parseYLut(const ValueNode &yObject, std::string_view key, uint32_t scale, > > bool parseYLut(const ValueNode &yObject, std::string_view key, uint32_t scale, > >> + std::span<uint32_t, RPPX1_LIN_DEGAMMA_CURVE_NUM> res) >> +{ >> + auto vals = yObject[key].get<std::vector<float>>().value_or(utils::defopt); >> + if (vals.size() != res.size()) { >> + LOG(RppX1Gsl, Error) >> + << "Invalid 'y:" << key << "': expected " > > What's the "y:" for ? > > This will read "Invalid 'y:red': > > is it intentional ? This is supposed to mirror the rkisp1 parsing, the "y" refers to the "y" subobject of the tuning file. GammaSensorLinearization: x-intervals: ... y: red: ... green: ... blue: ... > >> + << res.size() << " elements, got " << vals.size(); >> + return false; >> + } >> + >> + for (const auto &[i, y] : utils::enumerate(vals)) { >> + if (y < 0 || y > 1) { > > rkisp1 has integers in the tuning file. I think it's better to have > floats like you do here, but this means we can't exchange tuning data > between the two platforms. > >> + LOG(RppX1Gsl, Error) >> + << "Invalid 'y:" << key << "': elements must be in [0; 1]"; >> + return false; >> + } >> + >> + res[i] = std::lround(y * scale); >> + } >> + >> + return true; >> +} >> + >> +} /* namespace */ >> + >> +/** >> + * \copydoc libcamera::ipa::Algorithm::init >> + */ >> +int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context, >> + const ValueNode &tuningData) >> +{ >> + /* >> + * \todo `LinPre2` has different ranges: >> + * * kDxOffset = 4 >> + * * kMaxDx = 7 >> + * * kResolution = 2^12 >> + * Maybe this parameter could be reworked to be compatible with both. >> + */ >> + gammaDx_ = tuningData["x-intervals"].get<std::vector<uint8_t>>().value_or(utils::defopt); >> + if (gammaDx_.size() != RPPX1_LIN_SAMPLE_POINTS_NUM) { >> + LOG(RppX1Gsl, Error) >> + << "Invalid 'x-intervals': expected " >> + << RPPX1_LIN_SAMPLE_POINTS_NUM << " elements, got " >> + << gammaDx_.size(); >> + >> + return -EINVAL; >> + } >> + >> + uint32_t xSum = 0; >> + for (const auto &x : gammaDx_) { > > for (uint8_t x : gammaDx_) > ? > > Or does a const & to a pod type make any difference ? No. > >> + if (x > kMaxDx) { >> + LOG(RppX1Gsl, Error) >> + << "Invalid 'x-intervals': must be at most " << kMaxDx >> + << ", got " << x; >> + return -EINVAL; >> + } >> + >> + xSum += 1u << (x + kDxOffset); >> + } >> + >> + /* >> + * > Typically, the accumulated sum of dx[i] should cover the complete data > > Stray > at the begining of the line The intention was to signal that this is more or less a direct quote. > >> + * > input range of [0; 2^12) or [0; 2^24). >> + */ >> + if (xSum != kResolution) { >> + LOG(RppX1Gsl, Error) >> + << "Invalid 'x-intervals': must cover full input range of " << kResolution >> + << ", got " << xSum; >> + return -EINVAL; >> + } > > This is a stricter requirement compared to rkisp1, I wonder if we need > to be so strict I see no reason not to be, if this turns out to be too strict, it can always be relaxed, but it is usually harder to make it strict later. > >> + >> + const ValueNode &yObject = tuningData["y"]; >> + if (!yObject.isDictionary()) { >> + LOG(RppX1Gsl, Error) >> + << "Invalid 'y': must be a dictionary"; >> + return -EINVAL; >> + } >> + >> + if (!parseYLut(yObject, "red", kResolution - 1, curveYr_) || >> + !parseYLut(yObject, "green", kResolution - 1, curveYg_) || >> + !parseYLut(yObject, "blue", kResolution - 1, curveYb_)) >> + return -EINVAL; >> + >> + return 0; >> +} >> + >> +/** >> + * \copydoc libcamera::ipa::Algorithm::prepare >> + */ >> +void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context, >> + const uint32_t frame, >> + [[maybe_unused]] IPAFrameContext &frameContext, >> + RppX1Params *params) >> +{ >> + if (frame > 0) >> + return; >> + >> + auto config = params->block<BlockType::LinPre1>(); >> + config.setEnabled(true); >> + >> + std::copy(gammaDx_.begin(), gammaDx_.end(), config->dx); >> + std::copy(curveYr_.begin(), curveYr_.end(), config->curve_r); >> + std::copy(curveYg_.begin(), curveYg_.end(), config->curve_g); >> + std::copy(curveYb_.begin(), curveYb_.end(), config->curve_b); >> +} >> + >> +REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization") >> + >> +} /* namespace ipa::rppx1::algorithms */ >> + >> +} /* namespace libcamera */ > [...]
diff --git a/src/ipa/rppx1/algorithms/gsl.cpp b/src/ipa/rppx1/algorithms/gsl.cpp new file mode 100644 index 0000000000..1f2d52d338 --- /dev/null +++ b/src/ipa/rppx1/algorithms/gsl.cpp @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Gamma Sensor Linearization control + */ + +#include "gsl.h" + +#include <algorithm> +#include <array> +#include <cmath> +#include <span> +#include <string_view> + +#include <libcamera/base/log.h> +#include <libcamera/base/utils.h> + +#include "libcamera/internal/value_node.h" + +/** + * \file gsl.h + */ + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +LOG_DEFINE_CATEGORY(RppX1Gsl) + +/* `LinPre1` only */ +constexpr uint32_t kDxOffset = 8; +constexpr uint32_t kMaxDx = (1u << 4) - 1; +constexpr uint32_t kResolution = 1u << 24; + +namespace { + +[[nodiscard]] +bool +parseYLut(const ValueNode &yObject, std::string_view key, uint32_t scale, + std::span<uint32_t, RPPX1_LIN_DEGAMMA_CURVE_NUM> res) +{ + auto vals = yObject[key].get<std::vector<float>>().value_or(utils::defopt); + if (vals.size() != res.size()) { + LOG(RppX1Gsl, Error) + << "Invalid 'y:" << key << "': expected " + << res.size() << " elements, got " << vals.size(); + return false; + } + + for (const auto &[i, y] : utils::enumerate(vals)) { + if (y < 0 || y > 1) { + LOG(RppX1Gsl, Error) + << "Invalid 'y:" << key << "': elements must be in [0; 1]"; + return false; + } + + res[i] = std::lround(y * scale); + } + + return true; +} + +} /* namespace */ + +/** + * \copydoc libcamera::ipa::Algorithm::init + */ +int GammaSensorLinearization::init([[maybe_unused]] IPAContext &context, + const ValueNode &tuningData) +{ + /* + * \todo `LinPre2` has different ranges: + * * kDxOffset = 4 + * * kMaxDx = 7 + * * kResolution = 2^12 + * Maybe this parameter could be reworked to be compatible with both. + */ + gammaDx_ = tuningData["x-intervals"].get<std::vector<uint8_t>>().value_or(utils::defopt); + if (gammaDx_.size() != RPPX1_LIN_SAMPLE_POINTS_NUM) { + LOG(RppX1Gsl, Error) + << "Invalid 'x-intervals': expected " + << RPPX1_LIN_SAMPLE_POINTS_NUM << " elements, got " + << gammaDx_.size(); + + return -EINVAL; + } + + uint32_t xSum = 0; + for (const auto &x : gammaDx_) { + if (x > kMaxDx) { + LOG(RppX1Gsl, Error) + << "Invalid 'x-intervals': must be at most " << kMaxDx + << ", got " << x; + return -EINVAL; + } + + xSum += 1u << (x + kDxOffset); + } + + /* + * > Typically, the accumulated sum of dx[i] should cover the complete data + * > input range of [0; 2^12) or [0; 2^24). + */ + if (xSum != kResolution) { + LOG(RppX1Gsl, Error) + << "Invalid 'x-intervals': must cover full input range of " << kResolution + << ", got " << xSum; + return -EINVAL; + } + + const ValueNode &yObject = tuningData["y"]; + if (!yObject.isDictionary()) { + LOG(RppX1Gsl, Error) + << "Invalid 'y': must be a dictionary"; + return -EINVAL; + } + + if (!parseYLut(yObject, "red", kResolution - 1, curveYr_) || + !parseYLut(yObject, "green", kResolution - 1, curveYg_) || + !parseYLut(yObject, "blue", kResolution - 1, curveYb_)) + return -EINVAL; + + return 0; +} + +/** + * \copydoc libcamera::ipa::Algorithm::prepare + */ +void GammaSensorLinearization::prepare([[maybe_unused]] IPAContext &context, + const uint32_t frame, + [[maybe_unused]] IPAFrameContext &frameContext, + RppX1Params *params) +{ + if (frame > 0) + return; + + auto config = params->block<BlockType::LinPre1>(); + config.setEnabled(true); + + std::copy(gammaDx_.begin(), gammaDx_.end(), config->dx); + std::copy(curveYr_.begin(), curveYr_.end(), config->curve_r); + std::copy(curveYg_.begin(), curveYg_.end(), config->curve_g); + std::copy(curveYb_.begin(), curveYb_.end(), config->curve_b); +} + +REGISTER_IPA_ALGORITHM(GammaSensorLinearization, "GammaSensorLinearization") + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/gsl.h b/src/ipa/rppx1/algorithms/gsl.h new file mode 100644 index 0000000000..0c86fbf1ff --- /dev/null +++ b/src/ipa/rppx1/algorithms/gsl.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026, Ideas On Board + * + * RPP-X1 Gamma Sensor Linearization control + */ + +#pragma once + +#include <vector> + +#include "algorithm.h" + +namespace libcamera { + +namespace ipa::rppx1::algorithms { + +class GammaSensorLinearization : public Algorithm +{ +public: + int init(IPAContext &context, const ValueNode &tuningData) override; + void prepare(IPAContext &context, const uint32_t frame, + IPAFrameContext &frameContext, + RppX1Params *params) override; + +private: + std::vector<uint8_t> gammaDx_; + std::array<uint32_t, RPPX1_LIN_DEGAMMA_CURVE_NUM> curveYr_; + std::array<uint32_t, RPPX1_LIN_DEGAMMA_CURVE_NUM> curveYg_; + std::array<uint32_t, RPPX1_LIN_DEGAMMA_CURVE_NUM> curveYb_; +}; + +} /* namespace ipa::rppx1::algorithms */ + +} /* namespace libcamera */ diff --git a/src/ipa/rppx1/algorithms/meson.build b/src/ipa/rppx1/algorithms/meson.build index d93b22c634..e7d78a00d2 100644 --- a/src/ipa/rppx1/algorithms/meson.build +++ b/src/ipa/rppx1/algorithms/meson.build @@ -6,5 +6,6 @@ rppx1_ipa_algorithms = files([ 'blc.cpp', 'ccm.cpp', 'goc.cpp', + 'gsl.cpp', 'lux.cpp', ]) diff --git a/src/ipa/rppx1/params.h b/src/ipa/rppx1/params.h index b724fa3162..87a70d960f 100644 --- a/src/ipa/rppx1/params.h +++ b/src/ipa/rppx1/params.h @@ -22,6 +22,8 @@ enum class BlockType : uint16_t { ExmPre1, GaHv, HistPost, + LinPre1, + LinPre2, WbMeasPost, }; @@ -45,6 +47,8 @@ RPPX1_DEFINE_BLOCK_TYPE(CcorPost, ccor, CCOR_POST) RPPX1_DEFINE_BLOCK_TYPE(ExmPre1, exm, EXM_PRE1) RPPX1_DEFINE_BLOCK_TYPE(GaHv, ga, GA_HV) RPPX1_DEFINE_BLOCK_TYPE(HistPost, hist, HIST_POST) +RPPX1_DEFINE_BLOCK_TYPE(LinPre1, lin, LIN_PRE1) +RPPX1_DEFINE_BLOCK_TYPE(LinPre2, lin, LIN_PRE2) RPPX1_DEFINE_BLOCK_TYPE(WbMeasPost, wbmeas, WBMEAS_POST) struct params_traits {