| Message ID | 20260615-libipa-algorithms-v1-5-e949c937422e@ideasonboard.com |
|---|---|
| State | Superseded |
| Headers | show |
| Series |
|
| Related | show |
Quoting Jacopo Mondi (2026-06-15 15:05:30) > Add a CcmAlgorithm to libipa. > > The CcmAlgorithm performs interpolation of the colour correction > matrices as loaded from tuning file on a colour temperature. > > The implementation is based on the existing RkISP1 CCM algorihtm. > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > --- > src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ > src/ipa/libipa/ccm.h | 87 +++++++++++++++ > src/ipa/libipa/meson.build | 2 + > 3 files changed, 362 insertions(+) > > diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp > new file mode 100644 > index 000000000000..278f8534e899 > --- /dev/null > +++ b/src/ipa/libipa/ccm.cpp > @@ -0,0 +1,273 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026 Ideas on Board Oy > + * > + * libIPA CCM algorithm > + */ > + > +#include "ccm.h" > + > +/** > + * \file ccm.h > + * \brief libipa ccm (Colour Correction Matrix) algorithm > + */ > + > +namespace libcamera { > + > +namespace ipa { > + > +LOG_DEFINE_CATEGORY(Ccm) > + > +namespace ccm { > + > +/** > + * \struct ActiveState > + * \brief Active ccm state > + * > + * \var ActiveState::manual > + * \brief The most recent manually requested ccm state > + * > + * \var ActiveState::automatic > + * \brief The most recent automatically calculated ccm state > + */ > + > +/** > + * \struct ActiveState::CcmState > + * \brief Ccm coefficients and offsets > + * > + * \var ActiveState::CcmState::ccm > + * \brief Matrix of 3x3 ccm coefficients > + * > + * \var ActiveState::CcmState::offsets > + * \brief Vector of RGB ccm offsets > + */ > + > +/** > + * \struct FrameContext > + * \brief Per-frame ccm state > + * > + * \var FrameContext::ccm > + * \brief Matrix of 3x3 ccm coefficients > + * > + * \var FrameContext::offsets > + * \brief Vector of RGB ccm offsets > + */ > + > +} /* namespace ccm */ > + > +/** > + * \class CcmAlgorithmBase > + * \brief Base class for CcmAlgorithm for non-templated functions implementation > + * > + * Base class for CcmAlgorithm where non-templated functions are implemented. > + * IPA implementations shall use CcmAlgorithm and not this class. > + */ > + > +/** > + * \brief Initialize the algorithm with the given tuning data > + * \param[in] tuningData The tuning data to use for the algorithm > + * > + * Parse \a tuningData to initialize the ccm algorithm and register controls. > + * IPA modules are expected to call this function as part of their > + * implementation of Algorithm::init(). > + * > + * \return 0 on success, a negative error code otherwise > + */ > +int CcmAlgorithmBase::init(const ValueNode &tuningData) > +{ > + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); > + if (ret < 0) { > + LOG(Ccm, Warning) > + << "Failed to parse 'ccm' " > + << "parameter from tuning file; falling back to unit matrix"; > + ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); > + } > + > + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); > + if (ret < 0) { > + LOG(Ccm, Warning) > + << "Failed to parse 'offsets' " > + << "parameter from tuning file; falling back to zero offsets"; > + > + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); > + } > + > + return 0; > +} > + > +/** > + * \brief Configure the ccm algorithm > + * \param[in] state The ccm active state > + * \param[in] temperatureK The colour temperature in Kelvin > + * > + * Configure the ccm algorithm by initializing the manual and automatic > + * states in \a state by interpolating the default colour correction matrix > + * with the given colour temperature \a temperatureK. > + * > + * \return 0 if successful, an error code otherwise > + */ > +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) > +{ > + state.manual.ccm = ccm_.getInterpolated(temperatureK); > + state.manual.offsets = offsets_.getInterpolated(temperatureK); > + state.automatic.ccm = ccm_.getInterpolated(temperatureK); > + state.automatic.offsets = offsets_.getInterpolated(temperatureK); > + > + return 0; > +} > + > +/** > + * \brief Queue a Request to the ccm algorithm > + * \param[in] state The ccm active state > + * \param[in] context The ccm frame context > + * \param[in] controls The list of controls part of the Request > + * > + * Queue a new Request to the ccm algorithm and store the manual colour > + * correction matrix and temperature in \a frameContext. > + * > + * The currently handled controls are: > + * - controls::ColourTemperature > + * - controls::ColourCorrectionMatrix > + * > + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is > + * stored in \a state and \a context. > + * > + * When controls::ColourTemperature is passed in, the matrices loaded from > + * configuration file are interpolated with the give temperature and the result s/give/given/ > + * is stored in \a state and \a context. > + * > + * If the IPA is running in manual mode, the IPA ccm algorithm implementations > + * can use the matrix coefficients and offsets directly from \a context after > + * calling this function to program the HW ccm engine, without calling prepare(). > + */ > +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, > + ccm::FrameContext &context, > + const ControlList &controls) > +{ > + const auto &colourTemperature = controls.get(controls::ColourTemperature); > + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); > + if (ccmMatrix) { > + state.manual.ccm = Matrix<float, 3, 3>(*ccmMatrix); > + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " > + << state.manual.ccm; > + } else if (colourTemperature) { > + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); > + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " > + << state.manual.ccm; > + } > + > + context.ccm = state.manual.ccm; > + context.offsets = state.manual.offsets; From below - if we use the using statement these two lines could become: context = state.manual; I wonder if that's clear enough on the intent. > +} > + > +/** > + * \brief Calculate the matrix coefficients for a colour temperature > + * \param[in] state The ccm active state > + * \param[in] context The ccm frame context > + * \param[in] frame The frame number > + * \param[in] temperatureK The colour temperature in Kelvin > + * > + * Interpolate the colour correction matrices as loaded from configuration file > + * for colour temperature \a temperatureK. > + * > + * The function shall only be called if the IPA algorithm is running in auto > + * mode. If running in manual mode the application supplied correction matrix is > + * stored in \a frameContext at queueRequest() time. > + */ > +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, > + ccm::FrameContext &context, > + unsigned int frame, unsigned int temperatureK) > +{ > + if (frame > 0 && temperatureK == ct_) { > + context.ccm = state.automatic.ccm; > + return; > + } > + > + ct_ = temperatureK; > + context.ccm = ccm_.getInterpolated(ct_); > + context.offsets = offsets_.getInterpolated(ct_); > + > + state.automatic.ccm = context.ccm; > + state.automatic.offsets = context.offsets; > +} > + > +/** > + * \brief Populate metadata with the latest correction matrix coefficients > + * \param[in] context The ccm frame context > + * \param[out] metadata The metadata list > + */ > +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) > +{ > + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); > +} > + > +/** > + * \var CcmAlgorithmBase::coeffMin_ > + * \brief The minimum supported coefficients value > + * > + * Minimum coefficient value used to clamp the ccm algorithm calculation results > + * in the range supported by the platform ccm engine. > + * > + * The min and max gain values are initialized by CcmAlgorithm::init(). > + */ > + > +/** > + * \var CcmAlgorithmBase::coeffMax_ > + * \brief The maximum supported coefficients value > + * > + * Maximum coefficient value used to clamp the ccm algorithm calculation results > + * in the range supported by the platform ccm engine. > + * > + * The min and max gain values are initialized by CcmAlgorithm::init(). > + */ > + > +/** > + * \class CcmAlgorithm > + * \brief The libipa ccm algorithm > + * \tparam Q The fixedpoint register representation of the colour correction > + * coefficients > + * > + * Implement the ccm algorithm for libipa. > + * > + * The CcmAlgorithm class implement an interface similar in spirit to the one > + * of the Algorithm class. IPA modules are expected to store an instance of > + * CcmAlgorithm as class member, template it with the ccm coefficients register > + * representation and call its function in their implementations of the > + * Algorithm interface. > + * > + * The CcmAlgorithm class provides an init() function where tuning data are where tuning data s/are/is/ parsed > + * parsed and the per-colour temperature correction matrices are loaded from > + * the tuning file. > + * > + * CcmAlgorithm supports both automatic and manual colour correction operations, > + * but doesn't offer a way to select one of them. Enabling or disabling > + * automatic ccm operations usually goes through the Awb algorithm > + * enable/disable as the two algorithms should work with the same mode. > + * > + * When the Awb algorithm runs in manual mode a custom colour correction matrix > + * or a custom colour temperature can be supplied to the ccm algorithm at > + * queueRequest() time. If the Request contains a color correction matrix > + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in > + * the FrameContext and the IPA module can immediately use them and doesn't need > + * to call process(). If a custom colour temperature is provided > + * (controls::ColourTemperature) then the matrices loaded from configuration are > + * interpolated with it and the result is saved in the FrameContext. In this > + * case as well IPA modules can use the result immediately and should avoid > + * calling process(). > + * > + * When the Awb algorithm runs in automatic mode instead, it estimates the scene > + * colour temperature. The estimated colour temperature shall be passed to > + * process(), where it is used to interpolate the matrices loaded from the > + * tuning file. The resulting coefficients are stored in the FrameContext for > + * the IPA algorithm to use them to program their ccm engine registers. > + */ > + > +/** > + * \fn CcmAlgorithm::init() > + * \param[in] controls The info map of the IPA controls > + * \copydoc CcmAlgorithmBase::init() > + */ > + > +} /* namespace ipa */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h > new file mode 100644 > index 000000000000..26ea0789481d > --- /dev/null > +++ b/src/ipa/libipa/ccm.h > @@ -0,0 +1,87 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026 Ideas on Board Oy > + * > + * libIPA CCM algorithm > + */ > + > +#pragma once > + > +#include <libcamera/control_ids.h> > +#include <libcamera/controls.h> > + > +#include "libcamera/internal/matrix.h" > + > +#include "fixedpoint.h" > +#include "interpolator.h" > + > +namespace libcamera { > + > +namespace ipa { > + > +namespace ccm { > + > +struct ActiveState { > + struct CcmState { > + Matrix<float, 3, 3> ccm; > + Matrix<int16_t, 3, 1> offsets; > + }; > + > + struct CcmState manual; > + struct CcmState automatic; > +}; > + > +struct FrameContext { > + Matrix<float, 3, 3> ccm; > + Matrix<int16_t, 3, 1> offsets; > +}; I wonder in these situations if we should do somethign like: struct CcmContext { Matrix<float, 3, 3> ccm; Matrix<int16_t, 3, 1> offsets; }; struct ActiveState { CcmContext manual; CcmContext automatic; }; using FrameContext = CcmContext; then later they could be directly assigned: frameContext.ccm = activestate.ccm.manual; and frameContext.ccm = activestate.ccm.automatic; (I think?) > + > +} /* namespace ccm */ > + > +class CcmAlgorithmBase > +{ > +public: > + int init(const ValueNode &tuningData); > + int configure(ccm::ActiveState &state, unsigned int temperatureK); > + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, > + const ControlList &controls); > + > + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, > + unsigned int frame, unsigned int temperatureK); > + void process(ccm::FrameContext &context, ControlList &metadata); > + > +protected: > + float coeffMin_; > + float coeffMax_; > + > +private: > + unsigned int ct_; > + Interpolator<Matrix<float, 3, 3>> ccm_; > + Interpolator<Matrix<int16_t, 3, 1>> offsets_; > +}; > + > +template<typename Q> > +class CcmAlgorithm : public CcmAlgorithmBase > +{ > +public: > + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) > + { > + int ret = CcmAlgorithmBase::init(tuningData); > + if (ret) > + return ret; > + > + coeffMin_ = Q::TraitsType::min; > + coeffMax_ = Q::TraitsType::max; If these are only used here, then lets make them locals and drop the class member storages and associated doxy doc? Anyway, trivials aside: Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > + > + controls[&controls::ColourCorrectionMatrix] = > + ControlInfo(ControlValue(coeffMin_), > + ControlValue(coeffMax_), > + ControlValue(Matrix<float, 3, 3>::identity().data())); > + > + return 0; > + } > +}; > + > +} /* namespace ipa */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build > index 963c5ee73063..edf8eabd8b78 100644 > --- a/src/ipa/libipa/meson.build > +++ b/src/ipa/libipa/meson.build > @@ -7,6 +7,7 @@ libipa_headers = files([ > 'awb_grey.h', > 'awb.h', > 'camera_sensor_helper.h', > + 'ccm.h', > 'colours.h', > 'exposure_mode_helper.h', > 'fc_queue.h', > @@ -28,6 +29,7 @@ libipa_sources = files([ > 'awb_grey.cpp', > 'awb.cpp', > 'camera_sensor_helper.cpp', > + 'ccm.cpp', > 'colours.cpp', > 'exposure_mode_helper.cpp', > 'fc_queue.cpp', > > -- > 2.54.0 >
Hi Kieran +stefan On Tue, Jun 16, 2026 at 01:16:58PM +0100, Kieran Bingham wrote: > Quoting Jacopo Mondi (2026-06-15 15:05:30) > > Add a CcmAlgorithm to libipa. > > > > The CcmAlgorithm performs interpolation of the colour correction > > matrices as loaded from tuning file on a colour temperature. > > > > The implementation is based on the existing RkISP1 CCM algorihtm. > > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > --- > > src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ > > src/ipa/libipa/ccm.h | 87 +++++++++++++++ > > src/ipa/libipa/meson.build | 2 + > > 3 files changed, 362 insertions(+) > > > > diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp > > new file mode 100644 > > index 000000000000..278f8534e899 > > --- /dev/null > > +++ b/src/ipa/libipa/ccm.cpp > > @@ -0,0 +1,273 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2026 Ideas on Board Oy > > + * > > + * libIPA CCM algorithm > > + */ > > + > > +#include "ccm.h" > > + > > +/** > > + * \file ccm.h > > + * \brief libipa ccm (Colour Correction Matrix) algorithm > > + */ > > + > > +namespace libcamera { > > + > > +namespace ipa { > > + > > +LOG_DEFINE_CATEGORY(Ccm) > > + > > +namespace ccm { > > + > > +/** > > + * \struct ActiveState > > + * \brief Active ccm state > > + * > > + * \var ActiveState::manual > > + * \brief The most recent manually requested ccm state > > + * > > + * \var ActiveState::automatic > > + * \brief The most recent automatically calculated ccm state > > + */ > > + > > +/** > > + * \struct ActiveState::CcmState > > + * \brief Ccm coefficients and offsets > > + * > > + * \var ActiveState::CcmState::ccm > > + * \brief Matrix of 3x3 ccm coefficients > > + * > > + * \var ActiveState::CcmState::offsets > > + * \brief Vector of RGB ccm offsets > > + */ > > + > > +/** > > + * \struct FrameContext > > + * \brief Per-frame ccm state > > + * > > + * \var FrameContext::ccm > > + * \brief Matrix of 3x3 ccm coefficients > > + * > > + * \var FrameContext::offsets > > + * \brief Vector of RGB ccm offsets > > + */ > > + > > +} /* namespace ccm */ > > + > > +/** > > + * \class CcmAlgorithmBase > > + * \brief Base class for CcmAlgorithm for non-templated functions implementation > > + * > > + * Base class for CcmAlgorithm where non-templated functions are implemented. > > + * IPA implementations shall use CcmAlgorithm and not this class. > > + */ > > + > > +/** > > + * \brief Initialize the algorithm with the given tuning data > > + * \param[in] tuningData The tuning data to use for the algorithm > > + * > > + * Parse \a tuningData to initialize the ccm algorithm and register controls. > > + * IPA modules are expected to call this function as part of their > > + * implementation of Algorithm::init(). > > + * > > + * \return 0 on success, a negative error code otherwise > > + */ > > +int CcmAlgorithmBase::init(const ValueNode &tuningData) > > +{ > > + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); > > + if (ret < 0) { > > + LOG(Ccm, Warning) > > + << "Failed to parse 'ccm' " > > + << "parameter from tuning file; falling back to unit matrix"; > > + ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); > > + } > > + > > + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); > > + if (ret < 0) { > > + LOG(Ccm, Warning) > > + << "Failed to parse 'offsets' " > > + << "parameter from tuning file; falling back to zero offsets"; > > + > > + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); > > + } > > + > > + return 0; > > +} > > + > > +/** > > + * \brief Configure the ccm algorithm > > + * \param[in] state The ccm active state > > + * \param[in] temperatureK The colour temperature in Kelvin > > + * > > + * Configure the ccm algorithm by initializing the manual and automatic > > + * states in \a state by interpolating the default colour correction matrix > > + * with the given colour temperature \a temperatureK. > > + * > > + * \return 0 if successful, an error code otherwise > > + */ > > +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) > > +{ > > + state.manual.ccm = ccm_.getInterpolated(temperatureK); > > + state.manual.offsets = offsets_.getInterpolated(temperatureK); > > + state.automatic.ccm = ccm_.getInterpolated(temperatureK); > > + state.automatic.offsets = offsets_.getInterpolated(temperatureK); > > + > > + return 0; > > +} > > + > > +/** > > + * \brief Queue a Request to the ccm algorithm > > + * \param[in] state The ccm active state > > + * \param[in] context The ccm frame context > > + * \param[in] controls The list of controls part of the Request > > + * > > + * Queue a new Request to the ccm algorithm and store the manual colour > > + * correction matrix and temperature in \a frameContext. > > + * > > + * The currently handled controls are: > > + * - controls::ColourTemperature > > + * - controls::ColourCorrectionMatrix > > + * > > + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is > > + * stored in \a state and \a context. > > + * > > + * When controls::ColourTemperature is passed in, the matrices loaded from > > + * configuration file are interpolated with the give temperature and the result > > s/give/given/ > > > + * is stored in \a state and \a context. > > + * > > + * If the IPA is running in manual mode, the IPA ccm algorithm implementations > > + * can use the matrix coefficients and offsets directly from \a context after > > + * calling this function to program the HW ccm engine, without calling prepare(). > > + */ > > +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, > > + ccm::FrameContext &context, > > + const ControlList &controls) > > +{ > > + const auto &colourTemperature = controls.get(controls::ColourTemperature); > > + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); > > + if (ccmMatrix) { > > + state.manual.ccm = Matrix<float, 3, 3>(*ccmMatrix); > > + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " > > + << state.manual.ccm; > > + } else if (colourTemperature) { > > + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); > > + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " > > + << state.manual.ccm; > > + } > > + > > + context.ccm = state.manual.ccm; > > + context.offsets = state.manual.offsets; > > From below - if we use the using statement these two lines could become: > > context = state.manual; > > I wonder if that's clear enough on the intent. Yes, it works! > > > +} > > + > > +/** > > + * \brief Calculate the matrix coefficients for a colour temperature > > + * \param[in] state The ccm active state > > + * \param[in] context The ccm frame context > > + * \param[in] frame The frame number > > + * \param[in] temperatureK The colour temperature in Kelvin > > + * > > + * Interpolate the colour correction matrices as loaded from configuration file > > + * for colour temperature \a temperatureK. > > + * > > + * The function shall only be called if the IPA algorithm is running in auto > > + * mode. If running in manual mode the application supplied correction matrix is > > + * stored in \a frameContext at queueRequest() time. > > + */ > > +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, > > + ccm::FrameContext &context, > > + unsigned int frame, unsigned int temperatureK) > > +{ > > + if (frame > 0 && temperatureK == ct_) { > > + context.ccm = state.automatic.ccm; > > + return; > > + } > > + > > + ct_ = temperatureK; > > + context.ccm = ccm_.getInterpolated(ct_); > > + context.offsets = offsets_.getInterpolated(ct_); > > + > > + state.automatic.ccm = context.ccm; > > + state.automatic.offsets = context.offsets; > > +} > > + > > +/** > > + * \brief Populate metadata with the latest correction matrix coefficients > > + * \param[in] context The ccm frame context > > + * \param[out] metadata The metadata list > > + */ > > +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) > > +{ > > + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); > > +} > > + > > +/** > > + * \var CcmAlgorithmBase::coeffMin_ > > + * \brief The minimum supported coefficients value > > + * > > + * Minimum coefficient value used to clamp the ccm algorithm calculation results > > + * in the range supported by the platform ccm engine. > > + * > > + * The min and max gain values are initialized by CcmAlgorithm::init(). > > + */ > > + > > +/** > > + * \var CcmAlgorithmBase::coeffMax_ > > + * \brief The maximum supported coefficients value > > + * > > + * Maximum coefficient value used to clamp the ccm algorithm calculation results > > + * in the range supported by the platform ccm engine. > > + * > > + * The min and max gain values are initialized by CcmAlgorithm::init(). > > + */ > > + > > +/** > > + * \class CcmAlgorithm > > + * \brief The libipa ccm algorithm > > + * \tparam Q The fixedpoint register representation of the colour correction > > + * coefficients > > + * > > + * Implement the ccm algorithm for libipa. > > + * > > + * The CcmAlgorithm class implement an interface similar in spirit to the one > > + * of the Algorithm class. IPA modules are expected to store an instance of > > + * CcmAlgorithm as class member, template it with the ccm coefficients register > > + * representation and call its function in their implementations of the > > + * Algorithm interface. > > + * > > + * The CcmAlgorithm class provides an init() function where tuning data are > > where tuning data s/are/is/ parsed > > > + * parsed and the per-colour temperature correction matrices are loaded from > > + * the tuning file. > > + * > > + * CcmAlgorithm supports both automatic and manual colour correction operations, > > + * but doesn't offer a way to select one of them. Enabling or disabling > > + * automatic ccm operations usually goes through the Awb algorithm > > + * enable/disable as the two algorithms should work with the same mode. > > + * > > + * When the Awb algorithm runs in manual mode a custom colour correction matrix > > + * or a custom colour temperature can be supplied to the ccm algorithm at > > + * queueRequest() time. If the Request contains a color correction matrix > > + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in > > + * the FrameContext and the IPA module can immediately use them and doesn't need > > + * to call process(). If a custom colour temperature is provided > > + * (controls::ColourTemperature) then the matrices loaded from configuration are > > + * interpolated with it and the result is saved in the FrameContext. In this > > + * case as well IPA modules can use the result immediately and should avoid > > + * calling process(). > > + * > > + * When the Awb algorithm runs in automatic mode instead, it estimates the scene > > + * colour temperature. The estimated colour temperature shall be passed to > > + * process(), where it is used to interpolate the matrices loaded from the > > + * tuning file. The resulting coefficients are stored in the FrameContext for > > + * the IPA algorithm to use them to program their ccm engine registers. > > + */ > > + > > +/** > > + * \fn CcmAlgorithm::init() > > + * \param[in] controls The info map of the IPA controls > > + * \copydoc CcmAlgorithmBase::init() > > + */ > > + > > +} /* namespace ipa */ > > + > > +} /* namespace libcamera */ > > diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h > > new file mode 100644 > > index 000000000000..26ea0789481d > > --- /dev/null > > +++ b/src/ipa/libipa/ccm.h > > @@ -0,0 +1,87 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2026 Ideas on Board Oy > > + * > > + * libIPA CCM algorithm > > + */ > > + > > +#pragma once > > + > > +#include <libcamera/control_ids.h> > > +#include <libcamera/controls.h> > > + > > +#include "libcamera/internal/matrix.h" > > + > > +#include "fixedpoint.h" > > +#include "interpolator.h" > > + > > +namespace libcamera { > > + > > +namespace ipa { > > + > > +namespace ccm { > > + > > +struct ActiveState { > > + struct CcmState { > > + Matrix<float, 3, 3> ccm; > > + Matrix<int16_t, 3, 1> offsets; > > + }; > > + > > + struct CcmState manual; > > + struct CcmState automatic; > > +}; > > + > > +struct FrameContext { > > + Matrix<float, 3, 3> ccm; > > + Matrix<int16_t, 3, 1> offsets; > > +}; > > I wonder in these situations if we should do somethign like: > > struct CcmContext { > Matrix<float, 3, 3> ccm; > Matrix<int16_t, 3, 1> offsets; > }; > > struct ActiveState { > CcmContext manual; > CcmContext automatic; > }; > > using FrameContext = CcmContext; > > > then later they could be directly assigned: > > frameContext.ccm = activestate.ccm.manual; > > and > > frameContext.ccm = activestate.ccm.automatic; > > > (I think?) Thanks, it works > > > > + > > +} /* namespace ccm */ > > + > > +class CcmAlgorithmBase > > +{ > > +public: > > + int init(const ValueNode &tuningData); > > + int configure(ccm::ActiveState &state, unsigned int temperatureK); > > + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, > > + const ControlList &controls); > > + > > + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, > > + unsigned int frame, unsigned int temperatureK); > > + void process(ccm::FrameContext &context, ControlList &metadata); > > + > > +protected: > > + float coeffMin_; > > + float coeffMax_; > > + > > +private: > > + unsigned int ct_; > > + Interpolator<Matrix<float, 3, 3>> ccm_; > > + Interpolator<Matrix<int16_t, 3, 1>> offsets_; > > +}; > > + > > +template<typename Q> > > +class CcmAlgorithm : public CcmAlgorithmBase > > +{ > > +public: > > + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) > > + { > > + int ret = CcmAlgorithmBase::init(tuningData); > > + if (ret) > > + return ret; > > + > > + coeffMin_ = Q::TraitsType::min; > > + coeffMax_ = Q::TraitsType::max; > > If these are only used here, then lets make them locals and drop the > class member storages and associated doxy doc? > > Anyway, trivials aside: mmm, it doesn't seem we clamp the calculated coefficients to the min/max, nor we validate the ones loaded from tuning file. Loading from file goes through the Interpolator class readYaml() int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); if (ret < 0) { LOG(Ccm, Warning) << "Failed to parse 'ccm' " << "parameter from tuning file; falling back to unit matrix"; ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); } Should we validate the loaded values to be in range ? Do we need to validate the coefficients resulting from the interpolation ? > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > > + > > + controls[&controls::ColourCorrectionMatrix] = > > + ControlInfo(ControlValue(coeffMin_), > > + ControlValue(coeffMax_), > > + ControlValue(Matrix<float, 3, 3>::identity().data())); > > + > > + return 0; > > + } > > +}; > > + > > +} /* namespace ipa */ > > + > > +} /* namespace libcamera */ > > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build > > index 963c5ee73063..edf8eabd8b78 100644 > > --- a/src/ipa/libipa/meson.build > > +++ b/src/ipa/libipa/meson.build > > @@ -7,6 +7,7 @@ libipa_headers = files([ > > 'awb_grey.h', > > 'awb.h', > > 'camera_sensor_helper.h', > > + 'ccm.h', > > 'colours.h', > > 'exposure_mode_helper.h', > > 'fc_queue.h', > > @@ -28,6 +29,7 @@ libipa_sources = files([ > > 'awb_grey.cpp', > > 'awb.cpp', > > 'camera_sensor_helper.cpp', > > + 'ccm.cpp', > > 'colours.cpp', > > 'exposure_mode_helper.cpp', > > 'fc_queue.cpp', > > > > -- > > 2.54.0 > >
Hi, Quoting Jacopo Mondi (2026-06-16 15:22:05) > Hi Kieran > > +stefan > > On Tue, Jun 16, 2026 at 01:16:58PM +0100, Kieran Bingham wrote: > > Quoting Jacopo Mondi (2026-06-15 15:05:30) > > > Add a CcmAlgorithm to libipa. > > > > > > The CcmAlgorithm performs interpolation of the colour correction > > > matrices as loaded from tuning file on a colour temperature. > > > > > > The implementation is based on the existing RkISP1 CCM algorihtm. > > > > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > > --- > > > src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ > > > src/ipa/libipa/ccm.h | 87 +++++++++++++++ > > > src/ipa/libipa/meson.build | 2 + > > > 3 files changed, 362 insertions(+) > > > > > > diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp > > > new file mode 100644 > > > index 000000000000..278f8534e899 > > > --- /dev/null > > > +++ b/src/ipa/libipa/ccm.cpp > > > @@ -0,0 +1,273 @@ > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > > +/* > > > + * Copyright (C) 2026 Ideas on Board Oy > > > + * > > > + * libIPA CCM algorithm > > > + */ > > > + > > > +#include "ccm.h" > > > + > > > +/** > > > + * \file ccm.h > > > + * \brief libipa ccm (Colour Correction Matrix) algorithm > > > + */ > > > + > > > +namespace libcamera { > > > + > > > +namespace ipa { > > > + > > > +LOG_DEFINE_CATEGORY(Ccm) > > > + > > > +namespace ccm { > > > + > > > +/** > > > + * \struct ActiveState > > > + * \brief Active ccm state > > > + * > > > + * \var ActiveState::manual > > > + * \brief The most recent manually requested ccm state > > > + * > > > + * \var ActiveState::automatic > > > + * \brief The most recent automatically calculated ccm state > > > + */ > > > + > > > +/** > > > + * \struct ActiveState::CcmState > > > + * \brief Ccm coefficients and offsets > > > + * > > > + * \var ActiveState::CcmState::ccm > > > + * \brief Matrix of 3x3 ccm coefficients > > > + * > > > + * \var ActiveState::CcmState::offsets > > > + * \brief Vector of RGB ccm offsets > > > + */ > > > + > > > +/** > > > + * \struct FrameContext > > > + * \brief Per-frame ccm state > > > + * > > > + * \var FrameContext::ccm > > > + * \brief Matrix of 3x3 ccm coefficients > > > + * > > > + * \var FrameContext::offsets > > > + * \brief Vector of RGB ccm offsets > > > + */ > > > + > > > +} /* namespace ccm */ > > > + > > > +/** > > > + * \class CcmAlgorithmBase > > > + * \brief Base class for CcmAlgorithm for non-templated functions implementation > > > + * > > > + * Base class for CcmAlgorithm where non-templated functions are implemented. > > > + * IPA implementations shall use CcmAlgorithm and not this class. > > > + */ > > > + > > > +/** > > > + * \brief Initialize the algorithm with the given tuning data > > > + * \param[in] tuningData The tuning data to use for the algorithm > > > + * > > > + * Parse \a tuningData to initialize the ccm algorithm and register controls. > > > + * IPA modules are expected to call this function as part of their > > > + * implementation of Algorithm::init(). > > > + * > > > + * \return 0 on success, a negative error code otherwise > > > + */ > > > +int CcmAlgorithmBase::init(const ValueNode &tuningData) > > > +{ > > > + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); > > > + if (ret < 0) { > > > + LOG(Ccm, Warning) > > > + << "Failed to parse 'ccm' " > > > + << "parameter from tuning file; falling back to unit matrix"; > > > + ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); > > > + } > > > + > > > + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); > > > + if (ret < 0) { > > > + LOG(Ccm, Warning) > > > + << "Failed to parse 'offsets' " > > > + << "parameter from tuning file; falling back to zero offsets"; > > > + > > > + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); > > > + } > > > + > > > + return 0; > > > +} > > > + > > > +/** > > > + * \brief Configure the ccm algorithm > > > + * \param[in] state The ccm active state > > > + * \param[in] temperatureK The colour temperature in Kelvin > > > + * > > > + * Configure the ccm algorithm by initializing the manual and automatic > > > + * states in \a state by interpolating the default colour correction matrix > > > + * with the given colour temperature \a temperatureK. > > > + * > > > + * \return 0 if successful, an error code otherwise > > > + */ > > > +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) > > > +{ > > > + state.manual.ccm = ccm_.getInterpolated(temperatureK); > > > + state.manual.offsets = offsets_.getInterpolated(temperatureK); > > > + state.automatic.ccm = ccm_.getInterpolated(temperatureK); > > > + state.automatic.offsets = offsets_.getInterpolated(temperatureK); > > > + > > > + return 0; > > > +} > > > + > > > +/** > > > + * \brief Queue a Request to the ccm algorithm > > > + * \param[in] state The ccm active state > > > + * \param[in] context The ccm frame context > > > + * \param[in] controls The list of controls part of the Request > > > + * > > > + * Queue a new Request to the ccm algorithm and store the manual colour > > > + * correction matrix and temperature in \a frameContext. > > > + * > > > + * The currently handled controls are: > > > + * - controls::ColourTemperature > > > + * - controls::ColourCorrectionMatrix > > > + * > > > + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is > > > + * stored in \a state and \a context. > > > + * > > > + * When controls::ColourTemperature is passed in, the matrices loaded from > > > + * configuration file are interpolated with the give temperature and the result > > > > s/give/given/ > > > > > + * is stored in \a state and \a context. > > > + * > > > + * If the IPA is running in manual mode, the IPA ccm algorithm implementations > > > + * can use the matrix coefficients and offsets directly from \a context after > > > + * calling this function to program the HW ccm engine, without calling prepare(). > > > + */ > > > +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, > > > + ccm::FrameContext &context, > > > + const ControlList &controls) > > > +{ > > > + const auto &colourTemperature = controls.get(controls::ColourTemperature); > > > + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); > > > + if (ccmMatrix) { > > > + state.manual.ccm = Matrix<float, 3, 3>(*ccmMatrix); > > > + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " > > > + << state.manual.ccm; > > > + } else if (colourTemperature) { > > > + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); > > > + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " > > > + << state.manual.ccm; > > > + } > > > + > > > + context.ccm = state.manual.ccm; > > > + context.offsets = state.manual.offsets; > > > > From below - if we use the using statement these two lines could become: > > > > context = state.manual; > > > > I wonder if that's clear enough on the intent. > > Yes, it works! > > > > > > +} > > > + > > > +/** > > > + * \brief Calculate the matrix coefficients for a colour temperature > > > + * \param[in] state The ccm active state > > > + * \param[in] context The ccm frame context > > > + * \param[in] frame The frame number > > > + * \param[in] temperatureK The colour temperature in Kelvin > > > + * > > > + * Interpolate the colour correction matrices as loaded from configuration file > > > + * for colour temperature \a temperatureK. > > > + * > > > + * The function shall only be called if the IPA algorithm is running in auto > > > + * mode. If running in manual mode the application supplied correction matrix is > > > + * stored in \a frameContext at queueRequest() time. > > > + */ > > > +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, > > > + ccm::FrameContext &context, > > > + unsigned int frame, unsigned int temperatureK) > > > +{ > > > + if (frame > 0 && temperatureK == ct_) { > > > + context.ccm = state.automatic.ccm; > > > + return; > > > + } > > > + > > > + ct_ = temperatureK; > > > + context.ccm = ccm_.getInterpolated(ct_); > > > + context.offsets = offsets_.getInterpolated(ct_); > > > + > > > + state.automatic.ccm = context.ccm; > > > + state.automatic.offsets = context.offsets; > > > +} > > > + > > > +/** > > > + * \brief Populate metadata with the latest correction matrix coefficients > > > + * \param[in] context The ccm frame context > > > + * \param[out] metadata The metadata list > > > + */ > > > +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) > > > +{ > > > + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); > > > +} > > > + > > > +/** > > > + * \var CcmAlgorithmBase::coeffMin_ > > > + * \brief The minimum supported coefficients value > > > + * > > > + * Minimum coefficient value used to clamp the ccm algorithm calculation results > > > + * in the range supported by the platform ccm engine. > > > + * > > > + * The min and max gain values are initialized by CcmAlgorithm::init(). > > > + */ > > > + > > > +/** > > > + * \var CcmAlgorithmBase::coeffMax_ > > > + * \brief The maximum supported coefficients value > > > + * > > > + * Maximum coefficient value used to clamp the ccm algorithm calculation results > > > + * in the range supported by the platform ccm engine. > > > + * > > > + * The min and max gain values are initialized by CcmAlgorithm::init(). > > > + */ > > > + > > > +/** > > > + * \class CcmAlgorithm > > > + * \brief The libipa ccm algorithm > > > + * \tparam Q The fixedpoint register representation of the colour correction > > > + * coefficients > > > + * > > > + * Implement the ccm algorithm for libipa. > > > + * > > > + * The CcmAlgorithm class implement an interface similar in spirit to the one > > > + * of the Algorithm class. IPA modules are expected to store an instance of > > > + * CcmAlgorithm as class member, template it with the ccm coefficients register > > > + * representation and call its function in their implementations of the > > > + * Algorithm interface. > > > + * > > > + * The CcmAlgorithm class provides an init() function where tuning data are > > > > where tuning data s/are/is/ parsed > > > > > + * parsed and the per-colour temperature correction matrices are loaded from > > > + * the tuning file. > > > + * > > > + * CcmAlgorithm supports both automatic and manual colour correction operations, > > > + * but doesn't offer a way to select one of them. Enabling or disabling > > > + * automatic ccm operations usually goes through the Awb algorithm > > > + * enable/disable as the two algorithms should work with the same mode. > > > + * > > > + * When the Awb algorithm runs in manual mode a custom colour correction matrix > > > + * or a custom colour temperature can be supplied to the ccm algorithm at > > > + * queueRequest() time. If the Request contains a color correction matrix > > > + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in > > > + * the FrameContext and the IPA module can immediately use them and doesn't need > > > + * to call process(). If a custom colour temperature is provided > > > + * (controls::ColourTemperature) then the matrices loaded from configuration are > > > + * interpolated with it and the result is saved in the FrameContext. In this > > > + * case as well IPA modules can use the result immediately and should avoid > > > + * calling process(). > > > + * > > > + * When the Awb algorithm runs in automatic mode instead, it estimates the scene > > > + * colour temperature. The estimated colour temperature shall be passed to > > > + * process(), where it is used to interpolate the matrices loaded from the > > > + * tuning file. The resulting coefficients are stored in the FrameContext for > > > + * the IPA algorithm to use them to program their ccm engine registers. > > > + */ > > > + > > > +/** > > > + * \fn CcmAlgorithm::init() > > > + * \param[in] controls The info map of the IPA controls > > > + * \copydoc CcmAlgorithmBase::init() > > > + */ > > > + > > > +} /* namespace ipa */ > > > + > > > +} /* namespace libcamera */ > > > diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h > > > new file mode 100644 > > > index 000000000000..26ea0789481d > > > --- /dev/null > > > +++ b/src/ipa/libipa/ccm.h > > > @@ -0,0 +1,87 @@ > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > > +/* > > > + * Copyright (C) 2026 Ideas on Board Oy > > > + * > > > + * libIPA CCM algorithm > > > + */ > > > + > > > +#pragma once > > > + > > > +#include <libcamera/control_ids.h> > > > +#include <libcamera/controls.h> > > > + > > > +#include "libcamera/internal/matrix.h" > > > + > > > +#include "fixedpoint.h" > > > +#include "interpolator.h" > > > + > > > +namespace libcamera { > > > + > > > +namespace ipa { > > > + > > > +namespace ccm { > > > + > > > +struct ActiveState { > > > + struct CcmState { > > > + Matrix<float, 3, 3> ccm; > > > + Matrix<int16_t, 3, 1> offsets; > > > + }; > > > + > > > + struct CcmState manual; > > > + struct CcmState automatic; > > > +}; > > > + > > > +struct FrameContext { > > > + Matrix<float, 3, 3> ccm; > > > + Matrix<int16_t, 3, 1> offsets; > > > +}; > > > > I wonder in these situations if we should do somethign like: > > > > struct CcmContext { > > Matrix<float, 3, 3> ccm; > > Matrix<int16_t, 3, 1> offsets; > > }; > > > > struct ActiveState { > > CcmContext manual; > > CcmContext automatic; > > }; > > > > using FrameContext = CcmContext; > > > > > > then later they could be directly assigned: > > > > frameContext.ccm = activestate.ccm.manual; > > > > and > > > > frameContext.ccm = activestate.ccm.automatic; > > > > > > (I think?) > > Thanks, it works > > > > > > > > + > > > +} /* namespace ccm */ > > > + > > > +class CcmAlgorithmBase > > > +{ > > > +public: > > > + int init(const ValueNode &tuningData); > > > + int configure(ccm::ActiveState &state, unsigned int temperatureK); > > > + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, > > > + const ControlList &controls); > > > + > > > + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, > > > + unsigned int frame, unsigned int temperatureK); > > > + void process(ccm::FrameContext &context, ControlList &metadata); > > > + > > > +protected: > > > + float coeffMin_; > > > + float coeffMax_; > > > + > > > +private: > > > + unsigned int ct_; > > > + Interpolator<Matrix<float, 3, 3>> ccm_; > > > + Interpolator<Matrix<int16_t, 3, 1>> offsets_; > > > +}; > > > + > > > +template<typename Q> > > > +class CcmAlgorithm : public CcmAlgorithmBase > > > +{ > > > +public: > > > + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) > > > + { > > > + int ret = CcmAlgorithmBase::init(tuningData); > > > + if (ret) > > > + return ret; > > > + > > > + coeffMin_ = Q::TraitsType::min; > > > + coeffMax_ = Q::TraitsType::max; > > > > If these are only used here, then lets make them locals and drop the > > class member storages and associated doxy doc? > > > > Anyway, trivials aside: > > mmm, it doesn't seem we clamp the calculated coefficients to the > min/max, nor we validate the ones loaded from tuning file. > > Loading from file goes through the Interpolator class readYaml() > > int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); > if (ret < 0) { > LOG(Ccm, Warning) > << "Failed to parse 'ccm' " > << "parameter from tuning file; falling back to unit matrix"; > ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); > } > > Should we validate the loaded values to be in range ? > Do we need to validate the coefficients resulting from the > interpolation ? Clamping happens in the conversion from float to the fixed point type when it gets passed to the hardware. So there won't be nasty wrap arounds or so. The only thing I could imagine is a early warning that a tuning file contains a matrix unsupported by the hardware - but does that really help and justify the effort? It could even worsen things. If one matrix is out of range, the interpolated result could still be in range for some interpolation factors. But if we clamp that matrix before interpolation, all the values will be wrong. In summary I would keep the float maths unconstrained as is and leave the rest on a best effort basis. Cheers, Stefan > > > > > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > > > > + > > > + controls[&controls::ColourCorrectionMatrix] = > > > + ControlInfo(ControlValue(coeffMin_), > > > + ControlValue(coeffMax_), > > > + ControlValue(Matrix<float, 3, 3>::identity().data())); > > > + > > > + return 0; > > > + } > > > +}; > > > + > > > +} /* namespace ipa */ > > > + > > > +} /* namespace libcamera */ > > > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build > > > index 963c5ee73063..edf8eabd8b78 100644 > > > --- a/src/ipa/libipa/meson.build > > > +++ b/src/ipa/libipa/meson.build > > > @@ -7,6 +7,7 @@ libipa_headers = files([ > > > 'awb_grey.h', > > > 'awb.h', > > > 'camera_sensor_helper.h', > > > + 'ccm.h', > > > 'colours.h', > > > 'exposure_mode_helper.h', > > > 'fc_queue.h', > > > @@ -28,6 +29,7 @@ libipa_sources = files([ > > > 'awb_grey.cpp', > > > 'awb.cpp', > > > 'camera_sensor_helper.cpp', > > > + 'ccm.cpp', > > > 'colours.cpp', > > > 'exposure_mode_helper.cpp', > > > 'fc_queue.cpp', > > > > > > -- > > > 2.54.0 > > >
Hi Stefan On Tue, Jun 16, 2026 at 03:45:32PM +0200, Stefan Klug wrote: > Hi, > > Quoting Jacopo Mondi (2026-06-16 15:22:05) > > Hi Kieran > > > > +stefan > > > > On Tue, Jun 16, 2026 at 01:16:58PM +0100, Kieran Bingham wrote: > > > Quoting Jacopo Mondi (2026-06-15 15:05:30) > > > > Add a CcmAlgorithm to libipa. > > > > > > > > The CcmAlgorithm performs interpolation of the colour correction > > > > matrices as loaded from tuning file on a colour temperature. > > > > > > > > The implementation is based on the existing RkISP1 CCM algorihtm. > > > > > > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > > > --- > > > > src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ > > > > src/ipa/libipa/ccm.h | 87 +++++++++++++++ > > > > src/ipa/libipa/meson.build | 2 + > > > > 3 files changed, 362 insertions(+) > > > > > > > > diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp > > > > new file mode 100644 > > > > index 000000000000..278f8534e899 > > > > --- /dev/null > > > > +++ b/src/ipa/libipa/ccm.cpp > > > > @@ -0,0 +1,273 @@ > > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > > > +/* > > > > + * Copyright (C) 2026 Ideas on Board Oy > > > > + * > > > > + * libIPA CCM algorithm > > > > + */ > > > > + > > > > +#include "ccm.h" > > > > + > > > > +/** > > > > + * \file ccm.h > > > > + * \brief libipa ccm (Colour Correction Matrix) algorithm > > > > + */ > > > > + > > > > +namespace libcamera { > > > > + > > > > +namespace ipa { > > > > + > > > > +LOG_DEFINE_CATEGORY(Ccm) > > > > + > > > > +namespace ccm { > > > > + > > > > +/** > > > > + * \struct ActiveState > > > > + * \brief Active ccm state > > > > + * > > > > + * \var ActiveState::manual > > > > + * \brief The most recent manually requested ccm state > > > > + * > > > > + * \var ActiveState::automatic > > > > + * \brief The most recent automatically calculated ccm state > > > > + */ > > > > + > > > > +/** > > > > + * \struct ActiveState::CcmState > > > > + * \brief Ccm coefficients and offsets > > > > + * > > > > + * \var ActiveState::CcmState::ccm > > > > + * \brief Matrix of 3x3 ccm coefficients > > > > + * > > > > + * \var ActiveState::CcmState::offsets > > > > + * \brief Vector of RGB ccm offsets > > > > + */ > > > > + > > > > +/** > > > > + * \struct FrameContext > > > > + * \brief Per-frame ccm state > > > > + * > > > > + * \var FrameContext::ccm > > > > + * \brief Matrix of 3x3 ccm coefficients > > > > + * > > > > + * \var FrameContext::offsets > > > > + * \brief Vector of RGB ccm offsets > > > > + */ > > > > + > > > > +} /* namespace ccm */ > > > > + > > > > +/** > > > > + * \class CcmAlgorithmBase > > > > + * \brief Base class for CcmAlgorithm for non-templated functions implementation > > > > + * > > > > + * Base class for CcmAlgorithm where non-templated functions are implemented. > > > > + * IPA implementations shall use CcmAlgorithm and not this class. > > > > + */ > > > > + > > > > +/** > > > > + * \brief Initialize the algorithm with the given tuning data > > > > + * \param[in] tuningData The tuning data to use for the algorithm > > > > + * > > > > + * Parse \a tuningData to initialize the ccm algorithm and register controls. > > > > + * IPA modules are expected to call this function as part of their > > > > + * implementation of Algorithm::init(). > > > > + * > > > > + * \return 0 on success, a negative error code otherwise > > > > + */ > > > > +int CcmAlgorithmBase::init(const ValueNode &tuningData) > > > > +{ > > > > + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); > > > > + if (ret < 0) { > > > > + LOG(Ccm, Warning) > > > > + << "Failed to parse 'ccm' " > > > > + << "parameter from tuning file; falling back to unit matrix"; > > > > + ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); > > > > + } > > > > + > > > > + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); > > > > + if (ret < 0) { > > > > + LOG(Ccm, Warning) > > > > + << "Failed to parse 'offsets' " > > > > + << "parameter from tuning file; falling back to zero offsets"; > > > > + > > > > + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); > > > > + } > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +/** > > > > + * \brief Configure the ccm algorithm > > > > + * \param[in] state The ccm active state > > > > + * \param[in] temperatureK The colour temperature in Kelvin > > > > + * > > > > + * Configure the ccm algorithm by initializing the manual and automatic > > > > + * states in \a state by interpolating the default colour correction matrix > > > > + * with the given colour temperature \a temperatureK. > > > > + * > > > > + * \return 0 if successful, an error code otherwise > > > > + */ > > > > +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) > > > > +{ > > > > + state.manual.ccm = ccm_.getInterpolated(temperatureK); > > > > + state.manual.offsets = offsets_.getInterpolated(temperatureK); > > > > + state.automatic.ccm = ccm_.getInterpolated(temperatureK); > > > > + state.automatic.offsets = offsets_.getInterpolated(temperatureK); > > > > + > > > > + return 0; > > > > +} > > > > + > > > > +/** > > > > + * \brief Queue a Request to the ccm algorithm > > > > + * \param[in] state The ccm active state > > > > + * \param[in] context The ccm frame context > > > > + * \param[in] controls The list of controls part of the Request > > > > + * > > > > + * Queue a new Request to the ccm algorithm and store the manual colour > > > > + * correction matrix and temperature in \a frameContext. > > > > + * > > > > + * The currently handled controls are: > > > > + * - controls::ColourTemperature > > > > + * - controls::ColourCorrectionMatrix > > > > + * > > > > + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is > > > > + * stored in \a state and \a context. > > > > + * > > > > + * When controls::ColourTemperature is passed in, the matrices loaded from > > > > + * configuration file are interpolated with the give temperature and the result > > > > > > s/give/given/ > > > > > > > + * is stored in \a state and \a context. > > > > + * > > > > + * If the IPA is running in manual mode, the IPA ccm algorithm implementations > > > > + * can use the matrix coefficients and offsets directly from \a context after > > > > + * calling this function to program the HW ccm engine, without calling prepare(). > > > > + */ > > > > +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, > > > > + ccm::FrameContext &context, > > > > + const ControlList &controls) > > > > +{ > > > > + const auto &colourTemperature = controls.get(controls::ColourTemperature); > > > > + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); > > > > + if (ccmMatrix) { > > > > + state.manual.ccm = Matrix<float, 3, 3>(*ccmMatrix); > > > > + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " > > > > + << state.manual.ccm; > > > > + } else if (colourTemperature) { > > > > + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); > > > > + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " > > > > + << state.manual.ccm; > > > > + } > > > > + > > > > + context.ccm = state.manual.ccm; > > > > + context.offsets = state.manual.offsets; > > > > > > From below - if we use the using statement these two lines could become: > > > > > > context = state.manual; > > > > > > I wonder if that's clear enough on the intent. > > > > Yes, it works! > > > > > > > > > +} > > > > + > > > > +/** > > > > + * \brief Calculate the matrix coefficients for a colour temperature > > > > + * \param[in] state The ccm active state > > > > + * \param[in] context The ccm frame context > > > > + * \param[in] frame The frame number > > > > + * \param[in] temperatureK The colour temperature in Kelvin > > > > + * > > > > + * Interpolate the colour correction matrices as loaded from configuration file > > > > + * for colour temperature \a temperatureK. > > > > + * > > > > + * The function shall only be called if the IPA algorithm is running in auto > > > > + * mode. If running in manual mode the application supplied correction matrix is > > > > + * stored in \a frameContext at queueRequest() time. > > > > + */ > > > > +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, > > > > + ccm::FrameContext &context, > > > > + unsigned int frame, unsigned int temperatureK) > > > > +{ > > > > + if (frame > 0 && temperatureK == ct_) { > > > > + context.ccm = state.automatic.ccm; > > > > + return; > > > > + } > > > > + > > > > + ct_ = temperatureK; > > > > + context.ccm = ccm_.getInterpolated(ct_); > > > > + context.offsets = offsets_.getInterpolated(ct_); > > > > + > > > > + state.automatic.ccm = context.ccm; > > > > + state.automatic.offsets = context.offsets; > > > > +} > > > > + > > > > +/** > > > > + * \brief Populate metadata with the latest correction matrix coefficients > > > > + * \param[in] context The ccm frame context > > > > + * \param[out] metadata The metadata list > > > > + */ > > > > +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) > > > > +{ > > > > + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); > > > > +} > > > > + > > > > +/** > > > > + * \var CcmAlgorithmBase::coeffMin_ > > > > + * \brief The minimum supported coefficients value > > > > + * > > > > + * Minimum coefficient value used to clamp the ccm algorithm calculation results > > > > + * in the range supported by the platform ccm engine. > > > > + * > > > > + * The min and max gain values are initialized by CcmAlgorithm::init(). > > > > + */ > > > > + > > > > +/** > > > > + * \var CcmAlgorithmBase::coeffMax_ > > > > + * \brief The maximum supported coefficients value > > > > + * > > > > + * Maximum coefficient value used to clamp the ccm algorithm calculation results > > > > + * in the range supported by the platform ccm engine. > > > > + * > > > > + * The min and max gain values are initialized by CcmAlgorithm::init(). > > > > + */ > > > > + > > > > +/** > > > > + * \class CcmAlgorithm > > > > + * \brief The libipa ccm algorithm > > > > + * \tparam Q The fixedpoint register representation of the colour correction > > > > + * coefficients > > > > + * > > > > + * Implement the ccm algorithm for libipa. > > > > + * > > > > + * The CcmAlgorithm class implement an interface similar in spirit to the one > > > > + * of the Algorithm class. IPA modules are expected to store an instance of > > > > + * CcmAlgorithm as class member, template it with the ccm coefficients register > > > > + * representation and call its function in their implementations of the > > > > + * Algorithm interface. > > > > + * > > > > + * The CcmAlgorithm class provides an init() function where tuning data are > > > > > > where tuning data s/are/is/ parsed > > > > > > > + * parsed and the per-colour temperature correction matrices are loaded from > > > > + * the tuning file. > > > > + * > > > > + * CcmAlgorithm supports both automatic and manual colour correction operations, > > > > + * but doesn't offer a way to select one of them. Enabling or disabling > > > > + * automatic ccm operations usually goes through the Awb algorithm > > > > + * enable/disable as the two algorithms should work with the same mode. > > > > + * > > > > + * When the Awb algorithm runs in manual mode a custom colour correction matrix > > > > + * or a custom colour temperature can be supplied to the ccm algorithm at > > > > + * queueRequest() time. If the Request contains a color correction matrix > > > > + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in > > > > + * the FrameContext and the IPA module can immediately use them and doesn't need > > > > + * to call process(). If a custom colour temperature is provided > > > > + * (controls::ColourTemperature) then the matrices loaded from configuration are > > > > + * interpolated with it and the result is saved in the FrameContext. In this > > > > + * case as well IPA modules can use the result immediately and should avoid > > > > + * calling process(). > > > > + * > > > > + * When the Awb algorithm runs in automatic mode instead, it estimates the scene > > > > + * colour temperature. The estimated colour temperature shall be passed to > > > > + * process(), where it is used to interpolate the matrices loaded from the > > > > + * tuning file. The resulting coefficients are stored in the FrameContext for > > > > + * the IPA algorithm to use them to program their ccm engine registers. > > > > + */ > > > > + > > > > +/** > > > > + * \fn CcmAlgorithm::init() > > > > + * \param[in] controls The info map of the IPA controls > > > > + * \copydoc CcmAlgorithmBase::init() > > > > + */ > > > > + > > > > +} /* namespace ipa */ > > > > + > > > > +} /* namespace libcamera */ > > > > diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h > > > > new file mode 100644 > > > > index 000000000000..26ea0789481d > > > > --- /dev/null > > > > +++ b/src/ipa/libipa/ccm.h > > > > @@ -0,0 +1,87 @@ > > > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > > > +/* > > > > + * Copyright (C) 2026 Ideas on Board Oy > > > > + * > > > > + * libIPA CCM algorithm > > > > + */ > > > > + > > > > +#pragma once > > > > + > > > > +#include <libcamera/control_ids.h> > > > > +#include <libcamera/controls.h> > > > > + > > > > +#include "libcamera/internal/matrix.h" > > > > + > > > > +#include "fixedpoint.h" > > > > +#include "interpolator.h" > > > > + > > > > +namespace libcamera { > > > > + > > > > +namespace ipa { > > > > + > > > > +namespace ccm { > > > > + > > > > +struct ActiveState { > > > > + struct CcmState { > > > > + Matrix<float, 3, 3> ccm; > > > > + Matrix<int16_t, 3, 1> offsets; > > > > + }; > > > > + > > > > + struct CcmState manual; > > > > + struct CcmState automatic; > > > > +}; > > > > + > > > > +struct FrameContext { > > > > + Matrix<float, 3, 3> ccm; > > > > + Matrix<int16_t, 3, 1> offsets; > > > > +}; > > > > > > I wonder in these situations if we should do somethign like: > > > > > > struct CcmContext { > > > Matrix<float, 3, 3> ccm; > > > Matrix<int16_t, 3, 1> offsets; > > > }; > > > > > > struct ActiveState { > > > CcmContext manual; > > > CcmContext automatic; > > > }; > > > > > > using FrameContext = CcmContext; > > > > > > > > > then later they could be directly assigned: > > > > > > frameContext.ccm = activestate.ccm.manual; > > > > > > and > > > > > > frameContext.ccm = activestate.ccm.automatic; > > > > > > > > > (I think?) > > > > Thanks, it works > > > > > > > > > > > > + > > > > +} /* namespace ccm */ > > > > + > > > > +class CcmAlgorithmBase > > > > +{ > > > > +public: > > > > + int init(const ValueNode &tuningData); > > > > + int configure(ccm::ActiveState &state, unsigned int temperatureK); > > > > + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, > > > > + const ControlList &controls); > > > > + > > > > + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, > > > > + unsigned int frame, unsigned int temperatureK); > > > > + void process(ccm::FrameContext &context, ControlList &metadata); > > > > + > > > > +protected: > > > > + float coeffMin_; > > > > + float coeffMax_; > > > > + > > > > +private: > > > > + unsigned int ct_; > > > > + Interpolator<Matrix<float, 3, 3>> ccm_; > > > > + Interpolator<Matrix<int16_t, 3, 1>> offsets_; > > > > +}; > > > > + > > > > +template<typename Q> > > > > +class CcmAlgorithm : public CcmAlgorithmBase > > > > +{ > > > > +public: > > > > + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) > > > > + { > > > > + int ret = CcmAlgorithmBase::init(tuningData); > > > > + if (ret) > > > > + return ret; > > > > + > > > > + coeffMin_ = Q::TraitsType::min; > > > > + coeffMax_ = Q::TraitsType::max; > > > > > > If these are only used here, then lets make them locals and drop the > > > class member storages and associated doxy doc? > > > > > > Anyway, trivials aside: > > > > mmm, it doesn't seem we clamp the calculated coefficients to the > > min/max, nor we validate the ones loaded from tuning file. > > > > Loading from file goes through the Interpolator class readYaml() > > > > int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); > > if (ret < 0) { > > LOG(Ccm, Warning) > > << "Failed to parse 'ccm' " > > << "parameter from tuning file; falling back to unit matrix"; > > ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); > > } > > > > Should we validate the loaded values to be in range ? > > Do we need to validate the coefficients resulting from the > > interpolation ? > > Clamping happens in the conversion from float to the fixed point type > when it gets passed to the hardware. So there won't be nasty wrap > arounds or so. The only thing I could imagine is a early warning that a > tuning file contains a matrix unsupported by the hardware - but does > that really help and justify the effort? It could even worsen things. If > one matrix is out of range, the interpolated result could still be in > range for some interpolation factors. But if we clamp that matrix before > interpolation, all the values will be wrong. In summary I would keep the > float maths unconstrained as is and leave the rest on a best effort > basis. > Thanks, we can always reconsider it later on. > Cheers, > Stefan > > > > > > > > > > > > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > > > > > > + > > > > + controls[&controls::ColourCorrectionMatrix] = > > > > + ControlInfo(ControlValue(coeffMin_), > > > > + ControlValue(coeffMax_), > > > > + ControlValue(Matrix<float, 3, 3>::identity().data())); > > > > + > > > > + return 0; > > > > + } > > > > +}; > > > > + > > > > +} /* namespace ipa */ > > > > + > > > > +} /* namespace libcamera */ > > > > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build > > > > index 963c5ee73063..edf8eabd8b78 100644 > > > > --- a/src/ipa/libipa/meson.build > > > > +++ b/src/ipa/libipa/meson.build > > > > @@ -7,6 +7,7 @@ libipa_headers = files([ > > > > 'awb_grey.h', > > > > 'awb.h', > > > > 'camera_sensor_helper.h', > > > > + 'ccm.h', > > > > 'colours.h', > > > > 'exposure_mode_helper.h', > > > > 'fc_queue.h', > > > > @@ -28,6 +29,7 @@ libipa_sources = files([ > > > > 'awb_grey.cpp', > > > > 'awb.cpp', > > > > 'camera_sensor_helper.cpp', > > > > + 'ccm.cpp', > > > > 'colours.cpp', > > > > 'exposure_mode_helper.cpp', > > > > 'fc_queue.cpp', > > > > > > > > -- > > > > 2.54.0 > > > >
Hi Jacopo On 15/06/2026 15:05, Jacopo Mondi wrote: > Add a CcmAlgorithm to libipa. > > The CcmAlgorithm performs interpolation of the colour correction > matrices as loaded from tuning file on a colour temperature. > > The implementation is based on the existing RkISP1 CCM algorihtm. > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > --- > src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ > src/ipa/libipa/ccm.h | 87 +++++++++++++++ > src/ipa/libipa/meson.build | 2 + > 3 files changed, 362 insertions(+) > > diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp > new file mode 100644 > index 000000000000..278f8534e899 > --- /dev/null > +++ b/src/ipa/libipa/ccm.cpp > @@ -0,0 +1,273 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026 Ideas on Board Oy > + * > + * libIPA CCM algorithm > + */ > + > +#include "ccm.h" > + > +/** > + * \file ccm.h > + * \brief libipa ccm (Colour Correction Matrix) algorithm > + */ > + > +namespace libcamera { > + > +namespace ipa { > + > +LOG_DEFINE_CATEGORY(Ccm) > + > +namespace ccm { > + > +/** > + * \struct ActiveState > + * \brief Active ccm state > + * > + * \var ActiveState::manual > + * \brief The most recent manually requested ccm state > + * > + * \var ActiveState::automatic > + * \brief The most recent automatically calculated ccm state > + */ > + > +/** > + * \struct ActiveState::CcmState > + * \brief Ccm coefficients and offsets > + * > + * \var ActiveState::CcmState::ccm > + * \brief Matrix of 3x3 ccm coefficients > + * > + * \var ActiveState::CcmState::offsets > + * \brief Vector of RGB ccm offsets > + */ > + > +/** > + * \struct FrameContext > + * \brief Per-frame ccm state > + * > + * \var FrameContext::ccm > + * \brief Matrix of 3x3 ccm coefficients > + * > + * \var FrameContext::offsets > + * \brief Vector of RGB ccm offsets > + */ > + > +} /* namespace ccm */ > + > +/** > + * \class CcmAlgorithmBase > + * \brief Base class for CcmAlgorithm for non-templated functions implementation > + * > + * Base class for CcmAlgorithm where non-templated functions are implemented. > + * IPA implementations shall use CcmAlgorithm and not this class. > + */ > + > +/** > + * \brief Initialize the algorithm with the given tuning data > + * \param[in] tuningData The tuning data to use for the algorithm > + * > + * Parse \a tuningData to initialize the ccm algorithm and register controls. > + * IPA modules are expected to call this function as part of their > + * implementation of Algorithm::init(). > + * > + * \return 0 on success, a negative error code otherwise > + */ > +int CcmAlgorithmBase::init(const ValueNode &tuningData) > +{ > + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); > + if (ret < 0) { > + LOG(Ccm, Warning) > + << "Failed to parse 'ccm' " > + << "parameter from tuning file; falling back to unit matrix"; > + ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); > + } > + > + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); > + if (ret < 0) { > + LOG(Ccm, Warning) > + << "Failed to parse 'offsets' " > + << "parameter from tuning file; falling back to zero offsets"; > + > + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); > + } > + > + return 0; > +} > + > +/** > + * \brief Configure the ccm algorithm > + * \param[in] state The ccm active state > + * \param[in] temperatureK The colour temperature in Kelvin > + * > + * Configure the ccm algorithm by initializing the manual and automatic > + * states in \a state by interpolating the default colour correction matrix > + * with the given colour temperature \a temperatureK. > + * > + * \return 0 if successful, an error code otherwise > + */ > +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) > +{ > + state.manual.ccm = ccm_.getInterpolated(temperatureK); > + state.manual.offsets = offsets_.getInterpolated(temperatureK); > + state.automatic.ccm = ccm_.getInterpolated(temperatureK); > + state.automatic.offsets = offsets_.getInterpolated(temperatureK); > + > + return 0; > +} > + > +/** > + * \brief Queue a Request to the ccm algorithm > + * \param[in] state The ccm active state > + * \param[in] context The ccm frame context > + * \param[in] controls The list of controls part of the Request Maybe "The list of controls associated with the Request"? > + * > + * Queue a new Request to the ccm algorithm and store the manual colour > + * correction matrix and temperature in \a frameContext. > + * > + * The currently handled controls are: > + * - controls::ColourTemperature > + * - controls::ColourCorrectionMatrix > + * > + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is > + * stored in \a state and \a context. > + * > + * When controls::ColourTemperature is passed in, the matrices loaded from > + * configuration file are interpolated with the give temperature and the result > + * is stored in \a state and \a context. > + * > + * If the IPA is running in manual mode, the IPA ccm algorithm implementations > + * can use the matrix coefficients and offsets directly from \a context after > + * calling this function to program the HW ccm engine, without calling prepare(). > + */ > +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, > + ccm::FrameContext &context, > + const ControlList &controls) > +{ > + const auto &colourTemperature = controls.get(controls::ColourTemperature); > + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); > + if (ccmMatrix) { > + state.manual.ccm = Matrix<float, 3, 3>(*ccmMatrix); > + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " > + << state.manual.ccm; > + } else if (colourTemperature) { > + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); > + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " > + << state.manual.ccm; > + } > + > + context.ccm = state.manual.ccm; > + context.offsets = state.manual.offsets; > +} > + > +/** > + * \brief Calculate the matrix coefficients for a colour temperature > + * \param[in] state The ccm active state > + * \param[in] context The ccm frame context > + * \param[in] frame The frame number > + * \param[in] temperatureK The colour temperature in Kelvin > + * > + * Interpolate the colour correction matrices as loaded from configuration file > + * for colour temperature \a temperatureK. > + * > + * The function shall only be called if the IPA algorithm is running in auto > + * mode. If running in manual mode the application supplied correction matrix is > + * stored in \a frameContext at queueRequest() time. > + */ > +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, > + ccm::FrameContext &context, > + unsigned int frame, unsigned int temperatureK) > +{ > + if (frame > 0 && temperatureK == ct_) { > + context.ccm = state.automatic.ccm; > + return; > + } If we've applied manual CCM with AwbEnable off, but we hit this check, won't that mean that the manual CCM we apply will be ignored and instead the last automatically calculated one applied? > + > + ct_ = temperatureK; > + context.ccm = ccm_.getInterpolated(ct_); > + context.offsets = offsets_.getInterpolated(ct_); > + > + state.automatic.ccm = context.ccm; > + state.automatic.offsets = context.offsets; > +} > + > +/** > + * \brief Populate metadata with the latest correction matrix coefficients > + * \param[in] context The ccm frame context > + * \param[out] metadata The metadata list > + */ > +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) > +{ > + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); > +} > + > +/** > + * \var CcmAlgorithmBase::coeffMin_ > + * \brief The minimum supported coefficients value > + * > + * Minimum coefficient value used to clamp the ccm algorithm calculation results > + * in the range supported by the platform ccm engine. > + * > + * The min and max gain values are initialized by CcmAlgorithm::init(). > + */ > + > +/** > + * \var CcmAlgorithmBase::coeffMax_ > + * \brief The maximum supported coefficients value > + * > + * Maximum coefficient value used to clamp the ccm algorithm calculation results > + * in the range supported by the platform ccm engine. > + * > + * The min and max gain values are initialized by CcmAlgorithm::init(). > + */ > + > +/** > + * \class CcmAlgorithm > + * \brief The libipa ccm algorithm > + * \tparam Q The fixedpoint register representation of the colour correction > + * coefficients > + * > + * Implement the ccm algorithm for libipa. > + * > + * The CcmAlgorithm class implement an interface similar in spirit to the one s/implement/implements > + * of the Algorithm class. IPA modules are expected to store an instance of > + * CcmAlgorithm as class member, template it with the ccm coefficients register > + * representation and call its function in their implementations of the s/function/functions > + * Algorithm interface. > + * > + * The CcmAlgorithm class provides an init() function where tuning data are > + * parsed and the per-colour temperature correction matrices are loaded from > + * the tuning file. > + * > + * CcmAlgorithm supports both automatic and manual colour correction operations, > + * but doesn't offer a way to select one of them. Enabling or disabling > + * automatic ccm operations usually goes through the Awb algorithm > + * enable/disable as the two algorithms should work with the same mode. > + * > + * When the Awb algorithm runs in manual mode a custom colour correction matrix > + * or a custom colour temperature can be supplied to the ccm algorithm at > + * queueRequest() time. If the Request contains a color correction matrix > + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in > + * the FrameContext and the IPA module can immediately use them and doesn't need > + * to call process(). If a custom colour temperature is provided Call prepare() I guess you mean...which answers my comment earlier I guess. Hm; I think in that case that either the wording here should be stronger - "should not call prepare()" and the prepare() function should check for context.awb.autoEnabled and return early if it's set...or maybe prepare() should just silently do that check regardless, and then we document that the IPA-specific implementations of Ccm should unconditionally call ccmAlgo_.prepare() in their prepare(). That feels more consistent to me actually. > + * (controls::ColourTemperature) then the matrices loaded from configuration are > + * interpolated with it and the result is saved in the FrameContext. In this > + * case as well IPA modules can use the result immediately and should avoid > + * calling process(). Ah...shoulda finished the paragraph to see the stronger wording was already there. Still; can we just silently return from prepare() if we're running in manual mode? > + * > + * When the Awb algorithm runs in automatic mode instead, it estimates the scene > + * colour temperature. The estimated colour temperature shall be passed to > + * process(), where it is used to interpolate the matrices loaded from the s/process/prepare > + * tuning file. The resulting coefficients are stored in the FrameContext for > + * the IPA algorithm to use them to program their ccm engine registers. > + */ > + > +/** > + * \fn CcmAlgorithm::init() > + * \param[in] controls The info map of the IPA controls > + * \copydoc CcmAlgorithmBase::init() > + */ > + > +} /* namespace ipa */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h > new file mode 100644 > index 000000000000..26ea0789481d > --- /dev/null > +++ b/src/ipa/libipa/ccm.h > @@ -0,0 +1,87 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * Copyright (C) 2026 Ideas on Board Oy > + * > + * libIPA CCM algorithm > + */ > + > +#pragma once > + > +#include <libcamera/control_ids.h> > +#include <libcamera/controls.h> > + > +#include "libcamera/internal/matrix.h" > + > +#include "fixedpoint.h" > +#include "interpolator.h" > + > +namespace libcamera { > + > +namespace ipa { > + > +namespace ccm { > + > +struct ActiveState { > + struct CcmState { > + Matrix<float, 3, 3> ccm; > + Matrix<int16_t, 3, 1> offsets; > + }; > + > + struct CcmState manual; > + struct CcmState automatic; > +}; > + > +struct FrameContext { > + Matrix<float, 3, 3> ccm; > + Matrix<int16_t, 3, 1> offsets; > +}; > + > +} /* namespace ccm */ > + > +class CcmAlgorithmBase > +{ > +public: > + int init(const ValueNode &tuningData); > + int configure(ccm::ActiveState &state, unsigned int temperatureK); > + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, > + const ControlList &controls); > + > + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, > + unsigned int frame, unsigned int temperatureK); > + void process(ccm::FrameContext &context, ControlList &metadata); > + > +protected: > + float coeffMin_; > + float coeffMax_; > + > +private: > + unsigned int ct_; > + Interpolator<Matrix<float, 3, 3>> ccm_; > + Interpolator<Matrix<int16_t, 3, 1>> offsets_; > +}; > + > +template<typename Q> > +class CcmAlgorithm : public CcmAlgorithmBase > +{ > +public: > + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) > + { > + int ret = CcmAlgorithmBase::init(tuningData); > + if (ret) > + return ret; > + > + coeffMin_ = Q::TraitsType::min; > + coeffMax_ = Q::TraitsType::max; > + > + controls[&controls::ColourCorrectionMatrix] = > + ControlInfo(ControlValue(coeffMin_), > + ControlValue(coeffMax_), > + ControlValue(Matrix<float, 3, 3>::identity().data())); > + > + return 0; > + } > +}; > + > +} /* namespace ipa */ > + > +} /* namespace libcamera */ > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build > index 963c5ee73063..edf8eabd8b78 100644 > --- a/src/ipa/libipa/meson.build > +++ b/src/ipa/libipa/meson.build > @@ -7,6 +7,7 @@ libipa_headers = files([ > 'awb_grey.h', > 'awb.h', > 'camera_sensor_helper.h', > + 'ccm.h', > 'colours.h', > 'exposure_mode_helper.h', > 'fc_queue.h', > @@ -28,6 +29,7 @@ libipa_sources = files([ > 'awb_grey.cpp', > 'awb.cpp', > 'camera_sensor_helper.cpp', > + 'ccm.cpp', > 'colours.cpp', > 'exposure_mode_helper.cpp', > 'fc_queue.cpp', >
Hi Dan On Mon, Jun 22, 2026 at 10:58:17PM +0100, Dan Scally wrote: > Hi Jacopo > > On 15/06/2026 15:05, Jacopo Mondi wrote: > > Add a CcmAlgorithm to libipa. > > > > The CcmAlgorithm performs interpolation of the colour correction > > matrices as loaded from tuning file on a colour temperature. > > > > The implementation is based on the existing RkISP1 CCM algorihtm. > > > > Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> > > --- > > src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ > > src/ipa/libipa/ccm.h | 87 +++++++++++++++ > > src/ipa/libipa/meson.build | 2 + > > 3 files changed, 362 insertions(+) > > > > diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp > > new file mode 100644 > > index 000000000000..278f8534e899 > > --- /dev/null > > +++ b/src/ipa/libipa/ccm.cpp > > @@ -0,0 +1,273 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2026 Ideas on Board Oy > > + * > > + * libIPA CCM algorithm > > + */ > > + > > +#include "ccm.h" > > + > > +/** > > + * \file ccm.h > > + * \brief libipa ccm (Colour Correction Matrix) algorithm > > + */ > > + > > +namespace libcamera { > > + > > +namespace ipa { > > + > > +LOG_DEFINE_CATEGORY(Ccm) > > + > > +namespace ccm { > > + > > +/** > > + * \struct ActiveState > > + * \brief Active ccm state > > + * > > + * \var ActiveState::manual > > + * \brief The most recent manually requested ccm state > > + * > > + * \var ActiveState::automatic > > + * \brief The most recent automatically calculated ccm state > > + */ > > + > > +/** > > + * \struct ActiveState::CcmState > > + * \brief Ccm coefficients and offsets > > + * > > + * \var ActiveState::CcmState::ccm > > + * \brief Matrix of 3x3 ccm coefficients > > + * > > + * \var ActiveState::CcmState::offsets > > + * \brief Vector of RGB ccm offsets > > + */ > > + > > +/** > > + * \struct FrameContext > > + * \brief Per-frame ccm state > > + * > > + * \var FrameContext::ccm > > + * \brief Matrix of 3x3 ccm coefficients > > + * > > + * \var FrameContext::offsets > > + * \brief Vector of RGB ccm offsets > > + */ > > + > > +} /* namespace ccm */ > > + > > +/** > > + * \class CcmAlgorithmBase > > + * \brief Base class for CcmAlgorithm for non-templated functions implementation > > + * > > + * Base class for CcmAlgorithm where non-templated functions are implemented. > > + * IPA implementations shall use CcmAlgorithm and not this class. > > + */ > > + > > +/** > > + * \brief Initialize the algorithm with the given tuning data > > + * \param[in] tuningData The tuning data to use for the algorithm > > + * > > + * Parse \a tuningData to initialize the ccm algorithm and register controls. > > + * IPA modules are expected to call this function as part of their > > + * implementation of Algorithm::init(). > > + * > > + * \return 0 on success, a negative error code otherwise > > + */ > > +int CcmAlgorithmBase::init(const ValueNode &tuningData) > > +{ > > + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); > > + if (ret < 0) { > > + LOG(Ccm, Warning) > > + << "Failed to parse 'ccm' " > > + << "parameter from tuning file; falling back to unit matrix"; > > + ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); > > + } > > + > > + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); > > + if (ret < 0) { > > + LOG(Ccm, Warning) > > + << "Failed to parse 'offsets' " > > + << "parameter from tuning file; falling back to zero offsets"; > > + > > + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); > > + } > > + > > + return 0; > > +} > > + > > +/** > > + * \brief Configure the ccm algorithm > > + * \param[in] state The ccm active state > > + * \param[in] temperatureK The colour temperature in Kelvin > > + * > > + * Configure the ccm algorithm by initializing the manual and automatic > > + * states in \a state by interpolating the default colour correction matrix > > + * with the given colour temperature \a temperatureK. > > + * > > + * \return 0 if successful, an error code otherwise > > + */ > > +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) > > +{ > > + state.manual.ccm = ccm_.getInterpolated(temperatureK); > > + state.manual.offsets = offsets_.getInterpolated(temperatureK); > > + state.automatic.ccm = ccm_.getInterpolated(temperatureK); > > + state.automatic.offsets = offsets_.getInterpolated(temperatureK); > > + > > + return 0; > > +} > > + > > +/** > > + * \brief Queue a Request to the ccm algorithm > > + * \param[in] state The ccm active state > > + * \param[in] context The ccm frame context > > + * \param[in] controls The list of controls part of the Request > > Maybe "The list of controls associated with the Request"? Ack, thanks > > + * > > + * Queue a new Request to the ccm algorithm and store the manual colour > > + * correction matrix and temperature in \a frameContext. > > + * > > + * The currently handled controls are: > > + * - controls::ColourTemperature > > + * - controls::ColourCorrectionMatrix > > + * > > + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is > > + * stored in \a state and \a context. > > + * > > + * When controls::ColourTemperature is passed in, the matrices loaded from > > + * configuration file are interpolated with the give temperature and the result > > + * is stored in \a state and \a context. > > + * > > + * If the IPA is running in manual mode, the IPA ccm algorithm implementations > > + * can use the matrix coefficients and offsets directly from \a context after > > + * calling this function to program the HW ccm engine, without calling prepare(). > > + */ > > +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, > > + ccm::FrameContext &context, > > + const ControlList &controls) > > +{ > > + const auto &colourTemperature = controls.get(controls::ColourTemperature); > > + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); > > + if (ccmMatrix) { > > + state.manual.ccm = Matrix<float, 3, 3>(*ccmMatrix); > > + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " > > + << state.manual.ccm; > > + } else if (colourTemperature) { > > + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); > > + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " > > + << state.manual.ccm; > > + } > > + > > + context.ccm = state.manual.ccm; > > + context.offsets = state.manual.offsets; > > +} > > + > > +/** > > + * \brief Calculate the matrix coefficients for a colour temperature > > + * \param[in] state The ccm active state > > + * \param[in] context The ccm frame context > > + * \param[in] frame The frame number > > + * \param[in] temperatureK The colour temperature in Kelvin > > + * > > + * Interpolate the colour correction matrices as loaded from configuration file > > + * for colour temperature \a temperatureK. > > + * > > + * The function shall only be called if the IPA algorithm is running in auto > > + * mode. If running in manual mode the application supplied correction matrix is > > + * stored in \a frameContext at queueRequest() time. > > + */ > > +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, > > + ccm::FrameContext &context, > > + unsigned int frame, unsigned int temperatureK) > > +{ > > + if (frame > 0 && temperatureK == ct_) { > > + context.ccm = state.automatic.ccm; > > + return; > > + } > > If we've applied manual CCM with AwbEnable off, but we hit this check, won't > that mean that the manual CCM we apply will be ignored and instead the last > automatically calculated one applied? I think you found your answer below > > > + > > + ct_ = temperatureK; > > + context.ccm = ccm_.getInterpolated(ct_); > > + context.offsets = offsets_.getInterpolated(ct_); > > + > > + state.automatic.ccm = context.ccm; > > + state.automatic.offsets = context.offsets; > > +} > > + > > +/** > > + * \brief Populate metadata with the latest correction matrix coefficients > > + * \param[in] context The ccm frame context > > + * \param[out] metadata The metadata list > > + */ > > +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) > > +{ > > + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); > > +} > > + > > +/** > > + * \var CcmAlgorithmBase::coeffMin_ > > + * \brief The minimum supported coefficients value > > + * > > + * Minimum coefficient value used to clamp the ccm algorithm calculation results > > + * in the range supported by the platform ccm engine. > > + * > > + * The min and max gain values are initialized by CcmAlgorithm::init(). > > + */ > > + > > +/** > > + * \var CcmAlgorithmBase::coeffMax_ > > + * \brief The maximum supported coefficients value > > + * > > + * Maximum coefficient value used to clamp the ccm algorithm calculation results > > + * in the range supported by the platform ccm engine. > > + * > > + * The min and max gain values are initialized by CcmAlgorithm::init(). > > + */ > > + > > +/** > > + * \class CcmAlgorithm > > + * \brief The libipa ccm algorithm > > + * \tparam Q The fixedpoint register representation of the colour correction > > + * coefficients > > + * > > + * Implement the ccm algorithm for libipa. > > + * > > + * The CcmAlgorithm class implement an interface similar in spirit to the one > > s/implement/implements > > > + * of the Algorithm class. IPA modules are expected to store an instance of > > + * CcmAlgorithm as class member, template it with the ccm coefficients register > > + * representation and call its function in their implementations of the > > s/function/functions > > > + * Algorithm interface. > > + * > > + * The CcmAlgorithm class provides an init() function where tuning data are > > + * parsed and the per-colour temperature correction matrices are loaded from > > + * the tuning file. > > + * > > + * CcmAlgorithm supports both automatic and manual colour correction operations, > > + * but doesn't offer a way to select one of them. Enabling or disabling > > + * automatic ccm operations usually goes through the Awb algorithm > > + * enable/disable as the two algorithms should work with the same mode. > > + * > > + * When the Awb algorithm runs in manual mode a custom colour correction matrix > > + * or a custom colour temperature can be supplied to the ccm algorithm at > > + * queueRequest() time. If the Request contains a color correction matrix > > + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in > > + * the FrameContext and the IPA module can immediately use them and doesn't need > > + * to call process(). If a custom colour temperature is provided > > Call prepare() I guess you mean...which answers my comment earlier I guess. Oh yes, thanks! > Hm; I think in that case that either the wording here should be stronger - > "should not call prepare()" and the prepare() function should check for > context.awb.autoEnabled and return early if it's set...or maybe prepare() > should just silently do that check regardless, and then we document that the > IPA-specific implementations of Ccm should unconditionally call > ccmAlgo_.prepare() in their prepare(). That feels more consistent to me > actually. One key design decision (which can be reconsidered indeed) is that libipa algorithms should not look into each other state or make assumptions about where specific values come from. This is the reason why the CCM expects colour temperature as a function argument instead of fishing it from the AWB. It's the IPA implementation that should tie things together and libipa algo should be independent one from each other. Unless we reconsider it I can't here look into the AWB state. > > > + * (controls::ColourTemperature) then the matrices loaded from configuration are > > + * interpolated with it and the result is saved in the FrameContext. In this > > + * case as well IPA modules can use the result immediately and should avoid > > + * calling process(). > > Ah...shoulda finished the paragraph to see the stronger wording was already > there. Still; can we just silently return from prepare() if we're running in > manual mode? As per above, libipa algorithms should not depend on each other. An IPA is free to compose them and replace any of the algorithms provided by libipa with its own implementation. > > + * > > + * When the Awb algorithm runs in automatic mode instead, it estimates the scene Considering what I just said, maybe I shouldn't say "the awb algo calculates the colour temperature" but rather "the IPA calculates the colour temperature" in order not to assume it's the AWB algorithm that does that > > + * colour temperature. The estimated colour temperature shall be passed to > > + * process(), where it is used to interpolate the matrices loaded from the > > s/process/prepare Ups, thanks > > > + * tuning file. The resulting coefficients are stored in the FrameContext for > > + * the IPA algorithm to use them to program their ccm engine registers. > > + */ > > + > > +/** > > + * \fn CcmAlgorithm::init() > > + * \param[in] controls The info map of the IPA controls > > + * \copydoc CcmAlgorithmBase::init() > > + */ > > + > > +} /* namespace ipa */ > > + > > +} /* namespace libcamera */ > > diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h > > new file mode 100644 > > index 000000000000..26ea0789481d > > --- /dev/null > > +++ b/src/ipa/libipa/ccm.h > > @@ -0,0 +1,87 @@ > > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > > +/* > > + * Copyright (C) 2026 Ideas on Board Oy > > + * > > + * libIPA CCM algorithm > > + */ > > + > > +#pragma once > > + > > +#include <libcamera/control_ids.h> > > +#include <libcamera/controls.h> > > + > > +#include "libcamera/internal/matrix.h" > > + > > +#include "fixedpoint.h" > > +#include "interpolator.h" > > + > > +namespace libcamera { > > + > > +namespace ipa { > > + > > +namespace ccm { > > + > > +struct ActiveState { > > + struct CcmState { > > + Matrix<float, 3, 3> ccm; > > + Matrix<int16_t, 3, 1> offsets; > > + }; > > + > > + struct CcmState manual; > > + struct CcmState automatic; > > +}; > > + > > +struct FrameContext { > > + Matrix<float, 3, 3> ccm; > > + Matrix<int16_t, 3, 1> offsets; > > +}; > > + > > +} /* namespace ccm */ > > + > > +class CcmAlgorithmBase > > +{ > > +public: > > + int init(const ValueNode &tuningData); > > + int configure(ccm::ActiveState &state, unsigned int temperatureK); > > + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, > > + const ControlList &controls); > > + > > + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, > > + unsigned int frame, unsigned int temperatureK); > > + void process(ccm::FrameContext &context, ControlList &metadata); > > + > > +protected: > > + float coeffMin_; > > + float coeffMax_; > > + > > +private: > > + unsigned int ct_; > > + Interpolator<Matrix<float, 3, 3>> ccm_; > > + Interpolator<Matrix<int16_t, 3, 1>> offsets_; > > +}; > > + > > +template<typename Q> > > +class CcmAlgorithm : public CcmAlgorithmBase > > +{ > > +public: > > + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) > > + { > > + int ret = CcmAlgorithmBase::init(tuningData); > > + if (ret) > > + return ret; > > + > > + coeffMin_ = Q::TraitsType::min; > > + coeffMax_ = Q::TraitsType::max; > > + > > + controls[&controls::ColourCorrectionMatrix] = > > + ControlInfo(ControlValue(coeffMin_), > > + ControlValue(coeffMax_), > > + ControlValue(Matrix<float, 3, 3>::identity().data())); > > + > > + return 0; > > + } > > +}; > > + > > +} /* namespace ipa */ > > + > > +} /* namespace libcamera */ > > diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build > > index 963c5ee73063..edf8eabd8b78 100644 > > --- a/src/ipa/libipa/meson.build > > +++ b/src/ipa/libipa/meson.build > > @@ -7,6 +7,7 @@ libipa_headers = files([ > > 'awb_grey.h', > > 'awb.h', > > 'camera_sensor_helper.h', > > + 'ccm.h', > > 'colours.h', > > 'exposure_mode_helper.h', > > 'fc_queue.h', > > @@ -28,6 +29,7 @@ libipa_sources = files([ > > 'awb_grey.cpp', > > 'awb.cpp', > > 'camera_sensor_helper.cpp', > > + 'ccm.cpp', > > 'colours.cpp', > > 'exposure_mode_helper.cpp', > > 'fc_queue.cpp', > > >
Hi Jacopo On 23/06/2026 08:16, Jacopo Mondi wrote: > Hi Dan > > On Mon, Jun 22, 2026 at 10:58:17PM +0100, Dan Scally wrote: >> Hi Jacopo >> >> On 15/06/2026 15:05, Jacopo Mondi wrote: >>> Add a CcmAlgorithm to libipa. >>> >>> The CcmAlgorithm performs interpolation of the colour correction >>> matrices as loaded from tuning file on a colour temperature. >>> >>> The implementation is based on the existing RkISP1 CCM algorihtm. >>> >>> Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> >>> --- >>> src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ >>> src/ipa/libipa/ccm.h | 87 +++++++++++++++ >>> src/ipa/libipa/meson.build | 2 + >>> 3 files changed, 362 insertions(+) >>> >>> diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp >>> new file mode 100644 >>> index 000000000000..278f8534e899 >>> --- /dev/null >>> +++ b/src/ipa/libipa/ccm.cpp >>> @@ -0,0 +1,273 @@ >>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */ >>> +/* >>> + * Copyright (C) 2026 Ideas on Board Oy >>> + * >>> + * libIPA CCM algorithm >>> + */ >>> + >>> +#include "ccm.h" >>> + >>> +/** >>> + * \file ccm.h >>> + * \brief libipa ccm (Colour Correction Matrix) algorithm >>> + */ >>> + >>> +namespace libcamera { >>> + >>> +namespace ipa { >>> + >>> +LOG_DEFINE_CATEGORY(Ccm) >>> + >>> +namespace ccm { >>> + >>> +/** >>> + * \struct ActiveState >>> + * \brief Active ccm state >>> + * >>> + * \var ActiveState::manual >>> + * \brief The most recent manually requested ccm state >>> + * >>> + * \var ActiveState::automatic >>> + * \brief The most recent automatically calculated ccm state >>> + */ >>> + >>> +/** >>> + * \struct ActiveState::CcmState >>> + * \brief Ccm coefficients and offsets >>> + * >>> + * \var ActiveState::CcmState::ccm >>> + * \brief Matrix of 3x3 ccm coefficients >>> + * >>> + * \var ActiveState::CcmState::offsets >>> + * \brief Vector of RGB ccm offsets >>> + */ >>> + >>> +/** >>> + * \struct FrameContext >>> + * \brief Per-frame ccm state >>> + * >>> + * \var FrameContext::ccm >>> + * \brief Matrix of 3x3 ccm coefficients >>> + * >>> + * \var FrameContext::offsets >>> + * \brief Vector of RGB ccm offsets >>> + */ >>> + >>> +} /* namespace ccm */ >>> + >>> +/** >>> + * \class CcmAlgorithmBase >>> + * \brief Base class for CcmAlgorithm for non-templated functions implementation >>> + * >>> + * Base class for CcmAlgorithm where non-templated functions are implemented. >>> + * IPA implementations shall use CcmAlgorithm and not this class. >>> + */ >>> + >>> +/** >>> + * \brief Initialize the algorithm with the given tuning data >>> + * \param[in] tuningData The tuning data to use for the algorithm >>> + * >>> + * Parse \a tuningData to initialize the ccm algorithm and register controls. >>> + * IPA modules are expected to call this function as part of their >>> + * implementation of Algorithm::init(). >>> + * >>> + * \return 0 on success, a negative error code otherwise >>> + */ >>> +int CcmAlgorithmBase::init(const ValueNode &tuningData) >>> +{ >>> + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); >>> + if (ret < 0) { >>> + LOG(Ccm, Warning) >>> + << "Failed to parse 'ccm' " >>> + << "parameter from tuning file; falling back to unit matrix"; >>> + ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); >>> + } >>> + >>> + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); >>> + if (ret < 0) { >>> + LOG(Ccm, Warning) >>> + << "Failed to parse 'offsets' " >>> + << "parameter from tuning file; falling back to zero offsets"; >>> + >>> + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); >>> + } >>> + >>> + return 0; >>> +} >>> + >>> +/** >>> + * \brief Configure the ccm algorithm >>> + * \param[in] state The ccm active state >>> + * \param[in] temperatureK The colour temperature in Kelvin >>> + * >>> + * Configure the ccm algorithm by initializing the manual and automatic >>> + * states in \a state by interpolating the default colour correction matrix >>> + * with the given colour temperature \a temperatureK. >>> + * >>> + * \return 0 if successful, an error code otherwise >>> + */ >>> +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) >>> +{ >>> + state.manual.ccm = ccm_.getInterpolated(temperatureK); >>> + state.manual.offsets = offsets_.getInterpolated(temperatureK); >>> + state.automatic.ccm = ccm_.getInterpolated(temperatureK); >>> + state.automatic.offsets = offsets_.getInterpolated(temperatureK); >>> + >>> + return 0; >>> +} >>> + >>> +/** >>> + * \brief Queue a Request to the ccm algorithm >>> + * \param[in] state The ccm active state >>> + * \param[in] context The ccm frame context >>> + * \param[in] controls The list of controls part of the Request >> >> Maybe "The list of controls associated with the Request"? > > Ack, thanks > >>> + * >>> + * Queue a new Request to the ccm algorithm and store the manual colour >>> + * correction matrix and temperature in \a frameContext. >>> + * >>> + * The currently handled controls are: >>> + * - controls::ColourTemperature >>> + * - controls::ColourCorrectionMatrix >>> + * >>> + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is >>> + * stored in \a state and \a context. >>> + * >>> + * When controls::ColourTemperature is passed in, the matrices loaded from >>> + * configuration file are interpolated with the give temperature and the result >>> + * is stored in \a state and \a context. >>> + * >>> + * If the IPA is running in manual mode, the IPA ccm algorithm implementations >>> + * can use the matrix coefficients and offsets directly from \a context after >>> + * calling this function to program the HW ccm engine, without calling prepare(). >>> + */ >>> +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, >>> + ccm::FrameContext &context, >>> + const ControlList &controls) >>> +{ >>> + const auto &colourTemperature = controls.get(controls::ColourTemperature); >>> + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); >>> + if (ccmMatrix) { >>> + state.manual.ccm = Matrix<float, 3, 3>(*ccmMatrix); >>> + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " >>> + << state.manual.ccm; >>> + } else if (colourTemperature) { >>> + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); >>> + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " >>> + << state.manual.ccm; >>> + } >>> + >>> + context.ccm = state.manual.ccm; >>> + context.offsets = state.manual.offsets; >>> +} >>> + >>> +/** >>> + * \brief Calculate the matrix coefficients for a colour temperature >>> + * \param[in] state The ccm active state >>> + * \param[in] context The ccm frame context >>> + * \param[in] frame The frame number >>> + * \param[in] temperatureK The colour temperature in Kelvin >>> + * >>> + * Interpolate the colour correction matrices as loaded from configuration file >>> + * for colour temperature \a temperatureK. >>> + * >>> + * The function shall only be called if the IPA algorithm is running in auto >>> + * mode. If running in manual mode the application supplied correction matrix is >>> + * stored in \a frameContext at queueRequest() time. >>> + */ >>> +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, >>> + ccm::FrameContext &context, >>> + unsigned int frame, unsigned int temperatureK) >>> +{ >>> + if (frame > 0 && temperatureK == ct_) { >>> + context.ccm = state.automatic.ccm; >>> + return; >>> + } >> >> If we've applied manual CCM with AwbEnable off, but we hit this check, won't >> that mean that the manual CCM we apply will be ignored and instead the last >> automatically calculated one applied? > > I think you found your answer below > >> >>> + >>> + ct_ = temperatureK; >>> + context.ccm = ccm_.getInterpolated(ct_); >>> + context.offsets = offsets_.getInterpolated(ct_); >>> + >>> + state.automatic.ccm = context.ccm; >>> + state.automatic.offsets = context.offsets; >>> +} >>> + >>> +/** >>> + * \brief Populate metadata with the latest correction matrix coefficients >>> + * \param[in] context The ccm frame context >>> + * \param[out] metadata The metadata list >>> + */ >>> +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) >>> +{ >>> + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); >>> +} >>> + >>> +/** >>> + * \var CcmAlgorithmBase::coeffMin_ >>> + * \brief The minimum supported coefficients value >>> + * >>> + * Minimum coefficient value used to clamp the ccm algorithm calculation results >>> + * in the range supported by the platform ccm engine. >>> + * >>> + * The min and max gain values are initialized by CcmAlgorithm::init(). >>> + */ >>> + >>> +/** >>> + * \var CcmAlgorithmBase::coeffMax_ >>> + * \brief The maximum supported coefficients value >>> + * >>> + * Maximum coefficient value used to clamp the ccm algorithm calculation results >>> + * in the range supported by the platform ccm engine. >>> + * >>> + * The min and max gain values are initialized by CcmAlgorithm::init(). >>> + */ >>> + >>> +/** >>> + * \class CcmAlgorithm >>> + * \brief The libipa ccm algorithm >>> + * \tparam Q The fixedpoint register representation of the colour correction >>> + * coefficients >>> + * >>> + * Implement the ccm algorithm for libipa. >>> + * >>> + * The CcmAlgorithm class implement an interface similar in spirit to the one >> >> s/implement/implements >> >>> + * of the Algorithm class. IPA modules are expected to store an instance of >>> + * CcmAlgorithm as class member, template it with the ccm coefficients register >>> + * representation and call its function in their implementations of the >> >> s/function/functions >> >>> + * Algorithm interface. >>> + * >>> + * The CcmAlgorithm class provides an init() function where tuning data are >>> + * parsed and the per-colour temperature correction matrices are loaded from >>> + * the tuning file. >>> + * >>> + * CcmAlgorithm supports both automatic and manual colour correction operations, >>> + * but doesn't offer a way to select one of them. Enabling or disabling >>> + * automatic ccm operations usually goes through the Awb algorithm >>> + * enable/disable as the two algorithms should work with the same mode. >>> + * >>> + * When the Awb algorithm runs in manual mode a custom colour correction matrix >>> + * or a custom colour temperature can be supplied to the ccm algorithm at >>> + * queueRequest() time. If the Request contains a color correction matrix >>> + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in >>> + * the FrameContext and the IPA module can immediately use them and doesn't need >>> + * to call process(). If a custom colour temperature is provided >> >> Call prepare() I guess you mean...which answers my comment earlier I guess. > > Oh yes, thanks! > >> Hm; I think in that case that either the wording here should be stronger - >> "should not call prepare()" and the prepare() function should check for >> context.awb.autoEnabled and return early if it's set...or maybe prepare() >> should just silently do that check regardless, and then we document that the >> IPA-specific implementations of Ccm should unconditionally call >> ccmAlgo_.prepare() in their prepare(). That feels more consistent to me >> actually. > > One key design decision (which can be reconsidered indeed) is that > libipa algorithms should not look into each other state or make > assumptions about where specific values come from. This is the > reason why the CCM expects colour temperature as a function argument > instead of fishing it from the AWB. > > It's the IPA implementation that should tie things together and libipa > algo should be independent one from each other. > > Unless we reconsider it I can't here look into the AWB state. Gotcha. OK, in that case: Reviewed-by: Daniel Scally <dan.scally@ideasonboard.com> > >> >>> + * (controls::ColourTemperature) then the matrices loaded from configuration are >>> + * interpolated with it and the result is saved in the FrameContext. In this >>> + * case as well IPA modules can use the result immediately and should avoid >>> + * calling process(). >> >> Ah...shoulda finished the paragraph to see the stronger wording was already >> there. Still; can we just silently return from prepare() if we're running in >> manual mode? > > As per above, libipa algorithms should not depend on each other. An > IPA is free to compose them and replace any of the algorithms provided > by libipa with its own implementation. > >>> + * >>> + * When the Awb algorithm runs in automatic mode instead, it estimates the scene > > Considering what I just said, maybe I shouldn't say "the awb algo > calculates the colour temperature" but rather "the IPA calculates the > colour temperature" in order not to assume it's the AWB algorithm that > does that > >>> + * colour temperature. The estimated colour temperature shall be passed to >>> + * process(), where it is used to interpolate the matrices loaded from the >> >> s/process/prepare > > Ups, thanks > >> >>> + * tuning file. The resulting coefficients are stored in the FrameContext for >>> + * the IPA algorithm to use them to program their ccm engine registers. >>> + */ >>> + >>> +/** >>> + * \fn CcmAlgorithm::init() >>> + * \param[in] controls The info map of the IPA controls >>> + * \copydoc CcmAlgorithmBase::init() >>> + */ >>> + >>> +} /* namespace ipa */ >>> + >>> +} /* namespace libcamera */ >>> diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h >>> new file mode 100644 >>> index 000000000000..26ea0789481d >>> --- /dev/null >>> +++ b/src/ipa/libipa/ccm.h >>> @@ -0,0 +1,87 @@ >>> +/* SPDX-License-Identifier: LGPL-2.1-or-later */ >>> +/* >>> + * Copyright (C) 2026 Ideas on Board Oy >>> + * >>> + * libIPA CCM algorithm >>> + */ >>> + >>> +#pragma once >>> + >>> +#include <libcamera/control_ids.h> >>> +#include <libcamera/controls.h> >>> + >>> +#include "libcamera/internal/matrix.h" >>> + >>> +#include "fixedpoint.h" >>> +#include "interpolator.h" >>> + >>> +namespace libcamera { >>> + >>> +namespace ipa { >>> + >>> +namespace ccm { >>> + >>> +struct ActiveState { >>> + struct CcmState { >>> + Matrix<float, 3, 3> ccm; >>> + Matrix<int16_t, 3, 1> offsets; >>> + }; >>> + >>> + struct CcmState manual; >>> + struct CcmState automatic; >>> +}; >>> + >>> +struct FrameContext { >>> + Matrix<float, 3, 3> ccm; >>> + Matrix<int16_t, 3, 1> offsets; >>> +}; >>> + >>> +} /* namespace ccm */ >>> + >>> +class CcmAlgorithmBase >>> +{ >>> +public: >>> + int init(const ValueNode &tuningData); >>> + int configure(ccm::ActiveState &state, unsigned int temperatureK); >>> + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, >>> + const ControlList &controls); >>> + >>> + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, >>> + unsigned int frame, unsigned int temperatureK); >>> + void process(ccm::FrameContext &context, ControlList &metadata); >>> + >>> +protected: >>> + float coeffMin_; >>> + float coeffMax_; >>> + >>> +private: >>> + unsigned int ct_; >>> + Interpolator<Matrix<float, 3, 3>> ccm_; >>> + Interpolator<Matrix<int16_t, 3, 1>> offsets_; >>> +}; >>> + >>> +template<typename Q> >>> +class CcmAlgorithm : public CcmAlgorithmBase >>> +{ >>> +public: >>> + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) >>> + { >>> + int ret = CcmAlgorithmBase::init(tuningData); >>> + if (ret) >>> + return ret; >>> + >>> + coeffMin_ = Q::TraitsType::min; >>> + coeffMax_ = Q::TraitsType::max; >>> + >>> + controls[&controls::ColourCorrectionMatrix] = >>> + ControlInfo(ControlValue(coeffMin_), >>> + ControlValue(coeffMax_), >>> + ControlValue(Matrix<float, 3, 3>::identity().data())); >>> + >>> + return 0; >>> + } >>> +}; >>> + >>> +} /* namespace ipa */ >>> + >>> +} /* namespace libcamera */ >>> diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build >>> index 963c5ee73063..edf8eabd8b78 100644 >>> --- a/src/ipa/libipa/meson.build >>> +++ b/src/ipa/libipa/meson.build >>> @@ -7,6 +7,7 @@ libipa_headers = files([ >>> 'awb_grey.h', >>> 'awb.h', >>> 'camera_sensor_helper.h', >>> + 'ccm.h', >>> 'colours.h', >>> 'exposure_mode_helper.h', >>> 'fc_queue.h', >>> @@ -28,6 +29,7 @@ libipa_sources = files([ >>> 'awb_grey.cpp', >>> 'awb.cpp', >>> 'camera_sensor_helper.cpp', >>> + 'ccm.cpp', >>> 'colours.cpp', >>> 'exposure_mode_helper.cpp', >>> 'fc_queue.cpp', >>> >>
diff --git a/src/ipa/libipa/ccm.cpp b/src/ipa/libipa/ccm.cpp new file mode 100644 index 000000000000..278f8534e899 --- /dev/null +++ b/src/ipa/libipa/ccm.cpp @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA CCM algorithm + */ + +#include "ccm.h" + +/** + * \file ccm.h + * \brief libipa ccm (Colour Correction Matrix) algorithm + */ + +namespace libcamera { + +namespace ipa { + +LOG_DEFINE_CATEGORY(Ccm) + +namespace ccm { + +/** + * \struct ActiveState + * \brief Active ccm state + * + * \var ActiveState::manual + * \brief The most recent manually requested ccm state + * + * \var ActiveState::automatic + * \brief The most recent automatically calculated ccm state + */ + +/** + * \struct ActiveState::CcmState + * \brief Ccm coefficients and offsets + * + * \var ActiveState::CcmState::ccm + * \brief Matrix of 3x3 ccm coefficients + * + * \var ActiveState::CcmState::offsets + * \brief Vector of RGB ccm offsets + */ + +/** + * \struct FrameContext + * \brief Per-frame ccm state + * + * \var FrameContext::ccm + * \brief Matrix of 3x3 ccm coefficients + * + * \var FrameContext::offsets + * \brief Vector of RGB ccm offsets + */ + +} /* namespace ccm */ + +/** + * \class CcmAlgorithmBase + * \brief Base class for CcmAlgorithm for non-templated functions implementation + * + * Base class for CcmAlgorithm where non-templated functions are implemented. + * IPA implementations shall use CcmAlgorithm and not this class. + */ + +/** + * \brief Initialize the algorithm with the given tuning data + * \param[in] tuningData The tuning data to use for the algorithm + * + * Parse \a tuningData to initialize the ccm algorithm and register controls. + * IPA modules are expected to call this function as part of their + * implementation of Algorithm::init(). + * + * \return 0 on success, a negative error code otherwise + */ +int CcmAlgorithmBase::init(const ValueNode &tuningData) +{ + int ret = ccm_.readYaml(tuningData["ccms"], "ct", "ccm"); + if (ret < 0) { + LOG(Ccm, Warning) + << "Failed to parse 'ccm' " + << "parameter from tuning file; falling back to unit matrix"; + ccm_.setData({ { 0, Matrix<float, 3, 3>::identity() } }); + } + + ret = offsets_.readYaml(tuningData["ccms"], "ct", "offsets"); + if (ret < 0) { + LOG(Ccm, Warning) + << "Failed to parse 'offsets' " + << "parameter from tuning file; falling back to zero offsets"; + + offsets_.setData({ { 0, Matrix<int16_t, 3, 1>({ 0, 0, 0 }) } }); + } + + return 0; +} + +/** + * \brief Configure the ccm algorithm + * \param[in] state The ccm active state + * \param[in] temperatureK The colour temperature in Kelvin + * + * Configure the ccm algorithm by initializing the manual and automatic + * states in \a state by interpolating the default colour correction matrix + * with the given colour temperature \a temperatureK. + * + * \return 0 if successful, an error code otherwise + */ +int CcmAlgorithmBase::configure(ccm::ActiveState &state, unsigned int temperatureK) +{ + state.manual.ccm = ccm_.getInterpolated(temperatureK); + state.manual.offsets = offsets_.getInterpolated(temperatureK); + state.automatic.ccm = ccm_.getInterpolated(temperatureK); + state.automatic.offsets = offsets_.getInterpolated(temperatureK); + + return 0; +} + +/** + * \brief Queue a Request to the ccm algorithm + * \param[in] state The ccm active state + * \param[in] context The ccm frame context + * \param[in] controls The list of controls part of the Request + * + * Queue a new Request to the ccm algorithm and store the manual colour + * correction matrix and temperature in \a frameContext. + * + * The currently handled controls are: + * - controls::ColourTemperature + * - controls::ColourCorrectionMatrix + * + * When controls::ColourCorrectionMatrix is passed in the supplied matrix is + * stored in \a state and \a context. + * + * When controls::ColourTemperature is passed in, the matrices loaded from + * configuration file are interpolated with the give temperature and the result + * is stored in \a state and \a context. + * + * If the IPA is running in manual mode, the IPA ccm algorithm implementations + * can use the matrix coefficients and offsets directly from \a context after + * calling this function to program the HW ccm engine, without calling prepare(). + */ +void CcmAlgorithmBase::queueRequest(ccm::ActiveState &state, + ccm::FrameContext &context, + const ControlList &controls) +{ + const auto &colourTemperature = controls.get(controls::ColourTemperature); + const auto &ccmMatrix = controls.get(controls::ColourCorrectionMatrix); + if (ccmMatrix) { + state.manual.ccm = Matrix<float, 3, 3>(*ccmMatrix); + LOG(Ccm, Debug) << "Setting manual CCM from CCM control to " + << state.manual.ccm; + } else if (colourTemperature) { + state.manual.ccm = ccm_.getInterpolated(*colourTemperature); + LOG(Ccm, Debug) << "Setting manual CCM from CT control to " + << state.manual.ccm; + } + + context.ccm = state.manual.ccm; + context.offsets = state.manual.offsets; +} + +/** + * \brief Calculate the matrix coefficients for a colour temperature + * \param[in] state The ccm active state + * \param[in] context The ccm frame context + * \param[in] frame The frame number + * \param[in] temperatureK The colour temperature in Kelvin + * + * Interpolate the colour correction matrices as loaded from configuration file + * for colour temperature \a temperatureK. + * + * The function shall only be called if the IPA algorithm is running in auto + * mode. If running in manual mode the application supplied correction matrix is + * stored in \a frameContext at queueRequest() time. + */ +void CcmAlgorithmBase::prepare(ccm::ActiveState &state, + ccm::FrameContext &context, + unsigned int frame, unsigned int temperatureK) +{ + if (frame > 0 && temperatureK == ct_) { + context.ccm = state.automatic.ccm; + return; + } + + ct_ = temperatureK; + context.ccm = ccm_.getInterpolated(ct_); + context.offsets = offsets_.getInterpolated(ct_); + + state.automatic.ccm = context.ccm; + state.automatic.offsets = context.offsets; +} + +/** + * \brief Populate metadata with the latest correction matrix coefficients + * \param[in] context The ccm frame context + * \param[out] metadata The metadata list + */ +void CcmAlgorithmBase::process(ccm::FrameContext &context, ControlList &metadata) +{ + metadata.set(controls::ColourCorrectionMatrix, context.ccm.data()); +} + +/** + * \var CcmAlgorithmBase::coeffMin_ + * \brief The minimum supported coefficients value + * + * Minimum coefficient value used to clamp the ccm algorithm calculation results + * in the range supported by the platform ccm engine. + * + * The min and max gain values are initialized by CcmAlgorithm::init(). + */ + +/** + * \var CcmAlgorithmBase::coeffMax_ + * \brief The maximum supported coefficients value + * + * Maximum coefficient value used to clamp the ccm algorithm calculation results + * in the range supported by the platform ccm engine. + * + * The min and max gain values are initialized by CcmAlgorithm::init(). + */ + +/** + * \class CcmAlgorithm + * \brief The libipa ccm algorithm + * \tparam Q The fixedpoint register representation of the colour correction + * coefficients + * + * Implement the ccm algorithm for libipa. + * + * The CcmAlgorithm class implement an interface similar in spirit to the one + * of the Algorithm class. IPA modules are expected to store an instance of + * CcmAlgorithm as class member, template it with the ccm coefficients register + * representation and call its function in their implementations of the + * Algorithm interface. + * + * The CcmAlgorithm class provides an init() function where tuning data are + * parsed and the per-colour temperature correction matrices are loaded from + * the tuning file. + * + * CcmAlgorithm supports both automatic and manual colour correction operations, + * but doesn't offer a way to select one of them. Enabling or disabling + * automatic ccm operations usually goes through the Awb algorithm + * enable/disable as the two algorithms should work with the same mode. + * + * When the Awb algorithm runs in manual mode a custom colour correction matrix + * or a custom colour temperature can be supplied to the ccm algorithm at + * queueRequest() time. If the Request contains a color correction matrix + * (controls::ColourCorrectionMatrix) then the matrix coefficients gets saved in + * the FrameContext and the IPA module can immediately use them and doesn't need + * to call process(). If a custom colour temperature is provided + * (controls::ColourTemperature) then the matrices loaded from configuration are + * interpolated with it and the result is saved in the FrameContext. In this + * case as well IPA modules can use the result immediately and should avoid + * calling process(). + * + * When the Awb algorithm runs in automatic mode instead, it estimates the scene + * colour temperature. The estimated colour temperature shall be passed to + * process(), where it is used to interpolate the matrices loaded from the + * tuning file. The resulting coefficients are stored in the FrameContext for + * the IPA algorithm to use them to program their ccm engine registers. + */ + +/** + * \fn CcmAlgorithm::init() + * \param[in] controls The info map of the IPA controls + * \copydoc CcmAlgorithmBase::init() + */ + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/ccm.h b/src/ipa/libipa/ccm.h new file mode 100644 index 000000000000..26ea0789481d --- /dev/null +++ b/src/ipa/libipa/ccm.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2026 Ideas on Board Oy + * + * libIPA CCM algorithm + */ + +#pragma once + +#include <libcamera/control_ids.h> +#include <libcamera/controls.h> + +#include "libcamera/internal/matrix.h" + +#include "fixedpoint.h" +#include "interpolator.h" + +namespace libcamera { + +namespace ipa { + +namespace ccm { + +struct ActiveState { + struct CcmState { + Matrix<float, 3, 3> ccm; + Matrix<int16_t, 3, 1> offsets; + }; + + struct CcmState manual; + struct CcmState automatic; +}; + +struct FrameContext { + Matrix<float, 3, 3> ccm; + Matrix<int16_t, 3, 1> offsets; +}; + +} /* namespace ccm */ + +class CcmAlgorithmBase +{ +public: + int init(const ValueNode &tuningData); + int configure(ccm::ActiveState &state, unsigned int temperatureK); + void queueRequest(ccm::ActiveState &state, ccm::FrameContext &context, + const ControlList &controls); + + void prepare(ccm::ActiveState &state, ccm::FrameContext &context, + unsigned int frame, unsigned int temperatureK); + void process(ccm::FrameContext &context, ControlList &metadata); + +protected: + float coeffMin_; + float coeffMax_; + +private: + unsigned int ct_; + Interpolator<Matrix<float, 3, 3>> ccm_; + Interpolator<Matrix<int16_t, 3, 1>> offsets_; +}; + +template<typename Q> +class CcmAlgorithm : public CcmAlgorithmBase +{ +public: + int init(const ValueNode &tuningData, ControlInfoMap::Map &controls) + { + int ret = CcmAlgorithmBase::init(tuningData); + if (ret) + return ret; + + coeffMin_ = Q::TraitsType::min; + coeffMax_ = Q::TraitsType::max; + + controls[&controls::ColourCorrectionMatrix] = + ControlInfo(ControlValue(coeffMin_), + ControlValue(coeffMax_), + ControlValue(Matrix<float, 3, 3>::identity().data())); + + return 0; + } +}; + +} /* namespace ipa */ + +} /* namespace libcamera */ diff --git a/src/ipa/libipa/meson.build b/src/ipa/libipa/meson.build index 963c5ee73063..edf8eabd8b78 100644 --- a/src/ipa/libipa/meson.build +++ b/src/ipa/libipa/meson.build @@ -7,6 +7,7 @@ libipa_headers = files([ 'awb_grey.h', 'awb.h', 'camera_sensor_helper.h', + 'ccm.h', 'colours.h', 'exposure_mode_helper.h', 'fc_queue.h', @@ -28,6 +29,7 @@ libipa_sources = files([ 'awb_grey.cpp', 'awb.cpp', 'camera_sensor_helper.cpp', + 'ccm.cpp', 'colours.cpp', 'exposure_mode_helper.cpp', 'fc_queue.cpp',
Add a CcmAlgorithm to libipa. The CcmAlgorithm performs interpolation of the colour correction matrices as loaded from tuning file on a colour temperature. The implementation is based on the existing RkISP1 CCM algorihtm. Signed-off-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> --- src/ipa/libipa/ccm.cpp | 273 +++++++++++++++++++++++++++++++++++++++++++++ src/ipa/libipa/ccm.h | 87 +++++++++++++++ src/ipa/libipa/meson.build | 2 + 3 files changed, 362 insertions(+)