summaryrefslogtreecommitdiff
path: root/src/ipa/raspberrypi/controller/rpi
diff options
context:
space:
mode:
authorNaushir Patuck <naush@raspberrypi.com>2023-05-03 13:20:27 +0100
committerLaurent Pinchart <laurent.pinchart@ideasonboard.com>2023-05-04 20:47:40 +0300
commit726e9274ea95fa46352556d340c5793a8da51fcd (patch)
tree80f6adcdbf744f9317e09eff3e80c602b384a753 /src/ipa/raspberrypi/controller/rpi
parent46aefed208fef4bc8d6f6e8882b92b9af710a60b (diff)
pipeline: ipa: raspberrypi: Refactor and move the Raspberry Pi code
Split the Raspberry Pi pipeline handler and IPA source code into common and VC4/BCM2835 specific file structures. For the pipeline handler, the common code files now live in src/libcamera/pipeline/rpi/common/ and the VC4-specific files in src/libcamera/pipeline/rpi/vc4/. For the IPA, the common code files now live in src/ipa/rpi/{cam_helper,controller}/ and the vc4 specific files in src/ipa/rpi/vc4/. With this change, the camera tuning files are now installed under share/libcamera/ipa/rpi/vc4/. To build the pipeline and IPA, the meson configuration options have now changed from "raspberrypi" to "rpi/vc4": meson setup build -Dipas=rpi/vc4 -Dpipelines=rpi/vc4 Signed-off-by: Naushir Patuck <naush@raspberrypi.com> Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Diffstat (limited to 'src/ipa/raspberrypi/controller/rpi')
-rw-r--r--src/ipa/raspberrypi/controller/rpi/af.cpp797
-rw-r--r--src/ipa/raspberrypi/controller/rpi/af.h165
-rw-r--r--src/ipa/raspberrypi/controller/rpi/agc.cpp922
-rw-r--r--src/ipa/raspberrypi/controller/rpi/agc.h133
-rw-r--r--src/ipa/raspberrypi/controller/rpi/alsc.cpp865
-rw-r--r--src/ipa/raspberrypi/controller/rpi/alsc.h174
-rw-r--r--src/ipa/raspberrypi/controller/rpi/awb.cpp734
-rw-r--r--src/ipa/raspberrypi/controller/rpi/awb.h191
-rw-r--r--src/ipa/raspberrypi/controller/rpi/black_level.cpp66
-rw-r--r--src/ipa/raspberrypi/controller/rpi/black_level.h30
-rw-r--r--src/ipa/raspberrypi/controller/rpi/ccm.cpp199
-rw-r--r--src/ipa/raspberrypi/controller/rpi/ccm.h75
-rw-r--r--src/ipa/raspberrypi/controller/rpi/contrast.cpp181
-rw-r--r--src/ipa/raspberrypi/controller/rpi/contrast.h51
-rw-r--r--src/ipa/raspberrypi/controller/rpi/dpc.cpp59
-rw-r--r--src/ipa/raspberrypi/controller/rpi/dpc.h32
-rw-r--r--src/ipa/raspberrypi/controller/rpi/focus.h28
-rw-r--r--src/ipa/raspberrypi/controller/rpi/geq.cpp89
-rw-r--r--src/ipa/raspberrypi/controller/rpi/geq.h34
-rw-r--r--src/ipa/raspberrypi/controller/rpi/lux.cpp115
-rw-r--r--src/ipa/raspberrypi/controller/rpi/lux.h45
-rw-r--r--src/ipa/raspberrypi/controller/rpi/noise.cpp89
-rw-r--r--src/ipa/raspberrypi/controller/rpi/noise.h32
-rw-r--r--src/ipa/raspberrypi/controller/rpi/sdn.cpp80
-rw-r--r--src/ipa/raspberrypi/controller/rpi/sdn.h32
-rw-r--r--src/ipa/raspberrypi/controller/rpi/sharpen.cpp92
-rw-r--r--src/ipa/raspberrypi/controller/rpi/sharpen.h34
27 files changed, 0 insertions, 5344 deletions
diff --git a/src/ipa/raspberrypi/controller/rpi/af.cpp b/src/ipa/raspberrypi/controller/rpi/af.cpp
deleted file mode 100644
index ed0c8a94..00000000
--- a/src/ipa/raspberrypi/controller/rpi/af.cpp
+++ /dev/null
@@ -1,797 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2022-2023, Raspberry Pi Ltd
- *
- * af.cpp - Autofocus control algorithm
- */
-
-#include "af.h"
-
-#include <iomanip>
-#include <math.h>
-#include <stdlib.h>
-
-#include <libcamera/base/log.h>
-
-#include <libcamera/control_ids.h>
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiAf)
-
-#define NAME "rpi.af"
-
-/*
- * Default values for parameters. All may be overridden in the tuning file.
- * Many of these values are sensor- or module-dependent; the defaults here
- * assume IMX708 in a Raspberry Pi V3 camera with the standard lens.
- *
- * Here all focus values are in dioptres (1/m). They are converted to hardware
- * units when written to status.lensSetting or returned from setLensPosition().
- *
- * Gain and delay values are relative to the update rate, since much (not all)
- * of the delay is in the sensor and (for CDAF) ISP, not the lens mechanism;
- * but note that algorithms are updated at no more than 30 Hz.
- */
-
-Af::RangeDependentParams::RangeDependentParams()
- : focusMin(0.0),
- focusMax(12.0),
- focusDefault(1.0)
-{
-}
-
-Af::SpeedDependentParams::SpeedDependentParams()
- : stepCoarse(1.0),
- stepFine(0.25),
- contrastRatio(0.75),
- pdafGain(-0.02),
- pdafSquelch(0.125),
- maxSlew(2.0),
- pdafFrames(20),
- dropoutFrames(6),
- stepFrames(4)
-{
-}
-
-Af::CfgParams::CfgParams()
- : confEpsilon(8),
- confThresh(16),
- confClip(512),
- skipFrames(5),
- map()
-{
-}
-
-template<typename T>
-static void readNumber(T &dest, const libcamera::YamlObject &params, char const *name)
-{
- auto value = params[name].get<T>();
- if (value)
- dest = *value;
- else
- LOG(RPiAf, Warning) << "Missing parameter \"" << name << "\"";
-}
-
-void Af::RangeDependentParams::read(const libcamera::YamlObject &params)
-{
-
- readNumber<double>(focusMin, params, "min");
- readNumber<double>(focusMax, params, "max");
- readNumber<double>(focusDefault, params, "default");
-}
-
-void Af::SpeedDependentParams::read(const libcamera::YamlObject &params)
-{
- readNumber<double>(stepCoarse, params, "step_coarse");
- readNumber<double>(stepFine, params, "step_fine");
- readNumber<double>(contrastRatio, params, "contrast_ratio");
- readNumber<double>(pdafGain, params, "pdaf_gain");
- readNumber<double>(pdafSquelch, params, "pdaf_squelch");
- readNumber<double>(maxSlew, params, "max_slew");
- readNumber<uint32_t>(pdafFrames, params, "pdaf_frames");
- readNumber<uint32_t>(dropoutFrames, params, "dropout_frames");
- readNumber<uint32_t>(stepFrames, params, "step_frames");
-}
-
-int Af::CfgParams::read(const libcamera::YamlObject &params)
-{
- if (params.contains("ranges")) {
- auto &rr = params["ranges"];
-
- if (rr.contains("normal"))
- ranges[AfRangeNormal].read(rr["normal"]);
- else
- LOG(RPiAf, Warning) << "Missing range \"normal\"";
-
- ranges[AfRangeMacro] = ranges[AfRangeNormal];
- if (rr.contains("macro"))
- ranges[AfRangeMacro].read(rr["macro"]);
-
- ranges[AfRangeFull].focusMin = std::min(ranges[AfRangeNormal].focusMin,
- ranges[AfRangeMacro].focusMin);
- ranges[AfRangeFull].focusMax = std::max(ranges[AfRangeNormal].focusMax,
- ranges[AfRangeMacro].focusMax);
- ranges[AfRangeFull].focusDefault = ranges[AfRangeNormal].focusDefault;
- if (rr.contains("full"))
- ranges[AfRangeFull].read(rr["full"]);
- } else
- LOG(RPiAf, Warning) << "No ranges defined";
-
- if (params.contains("speeds")) {
- auto &ss = params["speeds"];
-
- if (ss.contains("normal"))
- speeds[AfSpeedNormal].read(ss["normal"]);
- else
- LOG(RPiAf, Warning) << "Missing speed \"normal\"";
-
- speeds[AfSpeedFast] = speeds[AfSpeedNormal];
- if (ss.contains("fast"))
- speeds[AfSpeedFast].read(ss["fast"]);
- } else
- LOG(RPiAf, Warning) << "No speeds defined";
-
- readNumber<uint32_t>(confEpsilon, params, "conf_epsilon");
- readNumber<uint32_t>(confThresh, params, "conf_thresh");
- readNumber<uint32_t>(confClip, params, "conf_clip");
- readNumber<uint32_t>(skipFrames, params, "skip_frames");
-
- if (params.contains("map"))
- map.read(params["map"]);
- else
- LOG(RPiAf, Warning) << "No map defined";
-
- return 0;
-}
-
-void Af::CfgParams::initialise()
-{
- if (map.empty()) {
- /* Default mapping from dioptres to hardware setting */
- static constexpr double DefaultMapX0 = 0.0;
- static constexpr double DefaultMapY0 = 445.0;
- static constexpr double DefaultMapX1 = 15.0;
- static constexpr double DefaultMapY1 = 925.0;
-
- map.append(DefaultMapX0, DefaultMapY0);
- map.append(DefaultMapX1, DefaultMapY1);
- }
-}
-
-/* Af Algorithm class */
-
-static constexpr unsigned MaxWindows = 10;
-
-Af::Af(Controller *controller)
- : AfAlgorithm(controller),
- cfg_(),
- range_(AfRangeNormal),
- speed_(AfSpeedNormal),
- mode_(AfAlgorithm::AfModeManual),
- pauseFlag_(false),
- statsRegion_(0, 0, 0, 0),
- windows_(),
- useWindows_(false),
- phaseWeights_(),
- contrastWeights_(),
- scanState_(ScanState::Idle),
- initted_(false),
- ftarget_(-1.0),
- fsmooth_(-1.0),
- prevContrast_(0.0),
- skipCount_(0),
- stepCount_(0),
- dropCount_(0),
- scanMaxContrast_(0.0),
- scanMinContrast_(1.0e9),
- scanData_(),
- reportState_(AfState::Idle)
-{
- /*
- * Reserve space for data, to reduce memory fragmentation. It's too early
- * to query the size of the PDAF (from camera) and Contrast (from ISP)
- * statistics, but these are plausible upper bounds.
- */
- phaseWeights_.w.reserve(16 * 12);
- contrastWeights_.w.reserve(getHardwareConfig().focusRegions.width *
- getHardwareConfig().focusRegions.height);
- scanData_.reserve(32);
-}
-
-Af::~Af()
-{
-}
-
-char const *Af::name() const
-{
- return NAME;
-}
-
-int Af::read(const libcamera::YamlObject &params)
-{
- return cfg_.read(params);
-}
-
-void Af::initialise()
-{
- cfg_.initialise();
-}
-
-void Af::switchMode(CameraMode const &cameraMode, [[maybe_unused]] Metadata *metadata)
-{
- (void)metadata;
-
- /* Assume that PDAF and Focus stats grids cover the visible area */
- statsRegion_.x = (int)cameraMode.cropX;
- statsRegion_.y = (int)cameraMode.cropY;
- statsRegion_.width = (unsigned)(cameraMode.width * cameraMode.scaleX);
- statsRegion_.height = (unsigned)(cameraMode.height * cameraMode.scaleY);
- LOG(RPiAf, Debug) << "switchMode: statsRegion: "
- << statsRegion_.x << ','
- << statsRegion_.y << ','
- << statsRegion_.width << ','
- << statsRegion_.height;
- invalidateWeights();
-
- if (scanState_ >= ScanState::Coarse && scanState_ < ScanState::Settle) {
- /*
- * If a scan was in progress, re-start it, as CDAF statistics
- * may have changed. Though if the application is just about
- * to take a still picture, this will not help...
- */
- startProgrammedScan();
- }
- skipCount_ = cfg_.skipFrames;
-}
-
-void Af::computeWeights(RegionWeights *wgts, unsigned rows, unsigned cols)
-{
- wgts->rows = rows;
- wgts->cols = cols;
- wgts->sum = 0;
- wgts->w.resize(rows * cols);
- std::fill(wgts->w.begin(), wgts->w.end(), 0);
-
- if (rows > 0 && cols > 0 && useWindows_ &&
- statsRegion_.height >= rows && statsRegion_.width >= cols) {
- /*
- * Here we just merge all of the given windows, weighted by area.
- * \todo Perhaps a better approach might be to find the phase in each
- * window and choose either the closest or the highest-confidence one?
- * Ensure weights sum to less than (1<<16). 46080 is a "round number"
- * below 65536, for better rounding when window size is a simple
- * fraction of image dimensions.
- */
- const unsigned maxCellWeight = 46080u / (MaxWindows * rows * cols);
- const unsigned cellH = statsRegion_.height / rows;
- const unsigned cellW = statsRegion_.width / cols;
- const unsigned cellA = cellH * cellW;
-
- for (auto &w : windows_) {
- for (unsigned r = 0; r < rows; ++r) {
- int y0 = std::max(statsRegion_.y + (int)(cellH * r), w.y);
- int y1 = std::min(statsRegion_.y + (int)(cellH * (r + 1)),
- w.y + (int)(w.height));
- if (y0 >= y1)
- continue;
- y1 -= y0;
- for (unsigned c = 0; c < cols; ++c) {
- int x0 = std::max(statsRegion_.x + (int)(cellW * c), w.x);
- int x1 = std::min(statsRegion_.x + (int)(cellW * (c + 1)),
- w.x + (int)(w.width));
- if (x0 >= x1)
- continue;
- unsigned a = y1 * (x1 - x0);
- a = (maxCellWeight * a + cellA - 1) / cellA;
- wgts->w[r * cols + c] += a;
- wgts->sum += a;
- }
- }
- }
- }
-
- if (wgts->sum == 0) {
- /* Default AF window is the middle 1/2 width of the middle 1/3 height */
- for (unsigned r = rows / 3; r < rows - rows / 3; ++r) {
- for (unsigned c = cols / 4; c < cols - cols / 4; ++c) {
- wgts->w[r * cols + c] = 1;
- wgts->sum += 1;
- }
- }
- }
-}
-
-void Af::invalidateWeights()
-{
- phaseWeights_.sum = 0;
- contrastWeights_.sum = 0;
-}
-
-bool Af::getPhase(PdafRegions const &regions, double &phase, double &conf)
-{
- libcamera::Size size = regions.size();
- if (size.height != phaseWeights_.rows || size.width != phaseWeights_.cols ||
- phaseWeights_.sum == 0) {
- LOG(RPiAf, Debug) << "Recompute Phase weights " << size.width << 'x' << size.height;
- computeWeights(&phaseWeights_, size.height, size.width);
- }
-
- uint32_t sumWc = 0;
- int64_t sumWcp = 0;
- for (unsigned i = 0; i < regions.numRegions(); ++i) {
- unsigned w = phaseWeights_.w[i];
- if (w) {
- const PdafData &data = regions.get(i).val;
- unsigned c = data.conf;
- if (c >= cfg_.confThresh) {
- if (c > cfg_.confClip)
- c = cfg_.confClip;
- c -= (cfg_.confThresh >> 2);
- sumWc += w * c;
- c -= (cfg_.confThresh >> 2);
- sumWcp += (int64_t)(w * c) * (int64_t)data.phase;
- }
- }
- }
-
- if (0 < phaseWeights_.sum && phaseWeights_.sum <= sumWc) {
- phase = (double)sumWcp / (double)sumWc;
- conf = (double)sumWc / (double)phaseWeights_.sum;
- return true;
- } else {
- phase = 0.0;
- conf = 0.0;
- return false;
- }
-}
-
-double Af::getContrast(const FocusRegions &focusStats)
-{
- libcamera::Size size = focusStats.size();
- if (size.height != contrastWeights_.rows ||
- size.width != contrastWeights_.cols || contrastWeights_.sum == 0) {
- LOG(RPiAf, Debug) << "Recompute Contrast weights "
- << size.width << 'x' << size.height;
- computeWeights(&contrastWeights_, size.height, size.width);
- }
-
- uint64_t sumWc = 0;
- for (unsigned i = 0; i < focusStats.numRegions(); ++i)
- sumWc += contrastWeights_.w[i] * focusStats.get(i).val;
-
- return (contrastWeights_.sum > 0) ? ((double)sumWc / (double)contrastWeights_.sum) : 0.0;
-}
-
-void Af::doPDAF(double phase, double conf)
-{
- /* Apply loop gain */
- phase *= cfg_.speeds[speed_].pdafGain;
-
- if (mode_ == AfModeContinuous) {
- /*
- * PDAF in Continuous mode. Scale down lens movement when
- * delta is small or confidence is low, to suppress wobble.
- */
- phase *= conf / (conf + cfg_.confEpsilon);
- if (std::abs(phase) < cfg_.speeds[speed_].pdafSquelch) {
- double a = phase / cfg_.speeds[speed_].pdafSquelch;
- phase *= a * a;
- }
- } else {
- /*
- * PDAF in triggered-auto mode. Allow early termination when
- * phase delta is small; scale down lens movements towards
- * the end of the sequence, to ensure a stable image.
- */
- if (stepCount_ >= cfg_.speeds[speed_].stepFrames) {
- if (std::abs(phase) < cfg_.speeds[speed_].pdafSquelch)
- stepCount_ = cfg_.speeds[speed_].stepFrames;
- } else
- phase *= stepCount_ / cfg_.speeds[speed_].stepFrames;
- }
-
- /* Apply slew rate limit. Report failure if out of bounds. */
- if (phase < -cfg_.speeds[speed_].maxSlew) {
- phase = -cfg_.speeds[speed_].maxSlew;
- reportState_ = (ftarget_ <= cfg_.ranges[range_].focusMin) ? AfState::Failed
- : AfState::Scanning;
- } else if (phase > cfg_.speeds[speed_].maxSlew) {
- phase = cfg_.speeds[speed_].maxSlew;
- reportState_ = (ftarget_ >= cfg_.ranges[range_].focusMax) ? AfState::Failed
- : AfState::Scanning;
- } else
- reportState_ = AfState::Focused;
-
- ftarget_ = fsmooth_ + phase;
-}
-
-bool Af::earlyTerminationByPhase(double phase)
-{
- if (scanData_.size() > 0 &&
- scanData_[scanData_.size() - 1].conf >= cfg_.confEpsilon) {
- double oldFocus = scanData_[scanData_.size() - 1].focus;
- double oldPhase = scanData_[scanData_.size() - 1].phase;
-
- /*
- * Check that the gradient is finite and has the expected sign;
- * Interpolate/extrapolate the lens position for zero phase.
- * Check that the extrapolation is well-conditioned.
- */
- if ((ftarget_ - oldFocus) * (phase - oldPhase) > 0.0) {
- double param = phase / (phase - oldPhase);
- if (-3.0 <= param && param <= 3.5) {
- ftarget_ += param * (oldFocus - ftarget_);
- LOG(RPiAf, Debug) << "ETBP: param=" << param;
- return true;
- }
- }
- }
-
- return false;
-}
-
-double Af::findPeak(unsigned i) const
-{
- double f = scanData_[i].focus;
-
- if (i > 0 && i + 1 < scanData_.size()) {
- double dropLo = scanData_[i].contrast - scanData_[i - 1].contrast;
- double dropHi = scanData_[i].contrast - scanData_[i + 1].contrast;
- if (0.0 <= dropLo && dropLo < dropHi) {
- double param = 0.3125 * (1.0 - dropLo / dropHi) * (1.6 - dropLo / dropHi);
- f += param * (scanData_[i - 1].focus - f);
- } else if (0.0 <= dropHi && dropHi < dropLo) {
- double param = 0.3125 * (1.0 - dropHi / dropLo) * (1.6 - dropHi / dropLo);
- f += param * (scanData_[i + 1].focus - f);
- }
- }
-
- LOG(RPiAf, Debug) << "FindPeak: " << f;
- return f;
-}
-
-void Af::doScan(double contrast, double phase, double conf)
-{
- /* Record lens position, contrast and phase values for the current scan */
- if (scanData_.empty() || contrast > scanMaxContrast_) {
- scanMaxContrast_ = contrast;
- scanMaxIndex_ = scanData_.size();
- }
- if (contrast < scanMinContrast_)
- scanMinContrast_ = contrast;
- scanData_.emplace_back(ScanRecord{ ftarget_, contrast, phase, conf });
-
- if (scanState_ == ScanState::Coarse) {
- if (ftarget_ >= cfg_.ranges[range_].focusMax ||
- contrast < cfg_.speeds[speed_].contrastRatio * scanMaxContrast_) {
- /*
- * Finished course scan, or termination based on contrast.
- * Jump to just after max contrast and start fine scan.
- */
- ftarget_ = std::min(ftarget_, findPeak(scanMaxIndex_) +
- 2.0 * cfg_.speeds[speed_].stepFine);
- scanState_ = ScanState::Fine;
- scanData_.clear();
- } else
- ftarget_ += cfg_.speeds[speed_].stepCoarse;
- } else { /* ScanState::Fine */
- if (ftarget_ <= cfg_.ranges[range_].focusMin || scanData_.size() >= 5 ||
- contrast < cfg_.speeds[speed_].contrastRatio * scanMaxContrast_) {
- /*
- * Finished fine scan, or termination based on contrast.
- * Use quadratic peak-finding to find best contrast position.
- */
- ftarget_ = findPeak(scanMaxIndex_);
- scanState_ = ScanState::Settle;
- } else
- ftarget_ -= cfg_.speeds[speed_].stepFine;
- }
-
- stepCount_ = (ftarget_ == fsmooth_) ? 0 : cfg_.speeds[speed_].stepFrames;
-}
-
-void Af::doAF(double contrast, double phase, double conf)
-{
- /* Skip frames at startup and after sensor mode change */
- if (skipCount_ > 0) {
- LOG(RPiAf, Debug) << "SKIP";
- skipCount_--;
- return;
- }
-
- if (scanState_ == ScanState::Pdaf) {
- /*
- * Use PDAF closed-loop control whenever available, in both CAF
- * mode and (for a limited number of iterations) when triggered.
- * If PDAF fails (due to poor contrast, noise or large defocus),
- * fall back to a CDAF-based scan. To avoid "nuisance" scans,
- * scan only after a number of frames with low PDAF confidence.
- */
- if (conf > (dropCount_ ? 1.0 : 0.25) * cfg_.confEpsilon) {
- doPDAF(phase, conf);
- if (stepCount_ > 0)
- stepCount_--;
- else if (mode_ != AfModeContinuous)
- scanState_ = ScanState::Idle;
- dropCount_ = 0;
- } else if (++dropCount_ == cfg_.speeds[speed_].dropoutFrames)
- startProgrammedScan();
- } else if (scanState_ >= ScanState::Coarse && fsmooth_ == ftarget_) {
- /*
- * Scanning sequence. This means PDAF has become unavailable.
- * Allow a delay between steps for CDAF FoM statistics to be
- * updated, and a "settling time" at the end of the sequence.
- * [A coarse or fine scan can be abandoned if two PDAF samples
- * allow direct interpolation of the zero-phase lens position.]
- */
- if (stepCount_ > 0)
- stepCount_--;
- else if (scanState_ == ScanState::Settle) {
- if (prevContrast_ >= cfg_.speeds[speed_].contrastRatio * scanMaxContrast_ &&
- scanMinContrast_ <= cfg_.speeds[speed_].contrastRatio * scanMaxContrast_)
- reportState_ = AfState::Focused;
- else
- reportState_ = AfState::Failed;
- if (mode_ == AfModeContinuous && !pauseFlag_ &&
- cfg_.speeds[speed_].dropoutFrames > 0)
- scanState_ = ScanState::Pdaf;
- else
- scanState_ = ScanState::Idle;
- scanData_.clear();
- } else if (conf >= cfg_.confEpsilon && earlyTerminationByPhase(phase)) {
- scanState_ = ScanState::Settle;
- stepCount_ = (mode_ == AfModeContinuous) ? 0
- : cfg_.speeds[speed_].stepFrames;
- } else
- doScan(contrast, phase, conf);
- }
-}
-
-void Af::updateLensPosition()
-{
- if (scanState_ >= ScanState::Pdaf) {
- ftarget_ = std::clamp(ftarget_,
- cfg_.ranges[range_].focusMin,
- cfg_.ranges[range_].focusMax);
- }
-
- if (initted_) {
- /* from a known lens position: apply slew rate limit */
- fsmooth_ = std::clamp(ftarget_,
- fsmooth_ - cfg_.speeds[speed_].maxSlew,
- fsmooth_ + cfg_.speeds[speed_].maxSlew);
- } else {
- /* from an unknown position: go straight to target, but add delay */
- fsmooth_ = ftarget_;
- initted_ = true;
- skipCount_ = cfg_.skipFrames;
- }
-}
-
-void Af::startAF()
-{
- /* Use PDAF if the tuning file allows it; else CDAF. */
- if (cfg_.speeds[speed_].dropoutFrames > 0 &&
- (mode_ == AfModeContinuous || cfg_.speeds[speed_].pdafFrames > 0)) {
- if (!initted_) {
- ftarget_ = cfg_.ranges[range_].focusDefault;
- updateLensPosition();
- }
- stepCount_ = (mode_ == AfModeContinuous) ? 0 : cfg_.speeds[speed_].pdafFrames;
- scanState_ = ScanState::Pdaf;
- scanData_.clear();
- dropCount_ = 0;
- reportState_ = AfState::Scanning;
- } else
- startProgrammedScan();
-}
-
-void Af::startProgrammedScan()
-{
- ftarget_ = cfg_.ranges[range_].focusMin;
- updateLensPosition();
- scanState_ = ScanState::Coarse;
- scanMaxContrast_ = 0.0;
- scanMinContrast_ = 1.0e9;
- scanMaxIndex_ = 0;
- scanData_.clear();
- stepCount_ = cfg_.speeds[speed_].stepFrames;
- reportState_ = AfState::Scanning;
-}
-
-void Af::goIdle()
-{
- scanState_ = ScanState::Idle;
- reportState_ = AfState::Idle;
- scanData_.clear();
-}
-
-/*
- * PDAF phase data are available in prepare(), but CDAF statistics are not
- * available until process(). We are gambling on the availability of PDAF.
- * To expedite feedback control using PDAF, issue the V4L2 lens control from
- * prepare(). Conversely, during scans, we must allow an extra frame delay
- * between steps, to retrieve CDAF statistics from the previous process()
- * so we can terminate the scan early without having to change our minds.
- */
-
-void Af::prepare(Metadata *imageMetadata)
-{
- /* Initialize for triggered scan or start of CAF mode */
- if (scanState_ == ScanState::Trigger)
- startAF();
-
- if (initted_) {
- /* Get PDAF from the embedded metadata, and run AF algorithm core */
- PdafRegions regions;
- double phase = 0.0, conf = 0.0;
- double oldFt = ftarget_;
- double oldFs = fsmooth_;
- ScanState oldSs = scanState_;
- uint32_t oldSt = stepCount_;
- if (imageMetadata->get("pdaf.regions", regions) == 0)
- getPhase(regions, phase, conf);
- doAF(prevContrast_, phase, conf);
- updateLensPosition();
- LOG(RPiAf, Debug) << std::fixed << std::setprecision(2)
- << static_cast<unsigned int>(reportState_)
- << " sst" << static_cast<unsigned int>(oldSs)
- << "->" << static_cast<unsigned int>(scanState_)
- << " stp" << oldSt << "->" << stepCount_
- << " ft" << oldFt << "->" << ftarget_
- << " fs" << oldFs << "->" << fsmooth_
- << " cont=" << (int)prevContrast_
- << " phase=" << (int)phase << " conf=" << (int)conf;
- }
-
- /* Report status and produce new lens setting */
- AfStatus status;
- if (pauseFlag_)
- status.pauseState = (scanState_ == ScanState::Idle) ? AfPauseState::Paused
- : AfPauseState::Pausing;
- else
- status.pauseState = AfPauseState::Running;
-
- if (mode_ == AfModeAuto && scanState_ != ScanState::Idle)
- status.state = AfState::Scanning;
- else
- status.state = reportState_;
- status.lensSetting = initted_ ? std::optional<int>(cfg_.map.eval(fsmooth_))
- : std::nullopt;
- imageMetadata->set("af.status", status);
-}
-
-void Af::process(StatisticsPtr &stats, [[maybe_unused]] Metadata *imageMetadata)
-{
- (void)imageMetadata;
- prevContrast_ = getContrast(stats->focusRegions);
-}
-
-/* Controls */
-
-void Af::setRange(AfRange r)
-{
- LOG(RPiAf, Debug) << "setRange: " << (unsigned)r;
- if (r < AfAlgorithm::AfRangeMax)
- range_ = r;
-}
-
-void Af::setSpeed(AfSpeed s)
-{
- LOG(RPiAf, Debug) << "setSpeed: " << (unsigned)s;
- if (s < AfAlgorithm::AfSpeedMax) {
- if (scanState_ == ScanState::Pdaf &&
- cfg_.speeds[s].pdafFrames > cfg_.speeds[speed_].pdafFrames)
- stepCount_ += cfg_.speeds[s].pdafFrames - cfg_.speeds[speed_].pdafFrames;
- speed_ = s;
- }
-}
-
-void Af::setMetering(bool mode)
-{
- if (useWindows_ != mode) {
- useWindows_ = mode;
- invalidateWeights();
- }
-}
-
-void Af::setWindows(libcamera::Span<libcamera::Rectangle const> const &wins)
-{
- windows_.clear();
- for (auto &w : wins) {
- LOG(RPiAf, Debug) << "Window: "
- << w.x << ", "
- << w.y << ", "
- << w.width << ", "
- << w.height;
- windows_.push_back(w);
- if (windows_.size() >= MaxWindows)
- break;
- }
-
- if (useWindows_)
- invalidateWeights();
-}
-
-bool Af::setLensPosition(double dioptres, int *hwpos)
-{
- bool changed = false;
-
- if (mode_ == AfModeManual) {
- LOG(RPiAf, Debug) << "setLensPosition: " << dioptres;
- ftarget_ = cfg_.map.domain().clip(dioptres);
- changed = !(initted_ && fsmooth_ == ftarget_);
- updateLensPosition();
- }
-
- if (hwpos)
- *hwpos = cfg_.map.eval(fsmooth_);
-
- return changed;
-}
-
-std::optional<double> Af::getLensPosition() const
-{
- /*
- * \todo We ought to perform some precise timing here to determine
- * the current lens position.
- */
- return initted_ ? std::optional<double>(fsmooth_) : std::nullopt;
-}
-
-void Af::cancelScan()
-{
- LOG(RPiAf, Debug) << "cancelScan";
- if (mode_ == AfModeAuto)
- goIdle();
-}
-
-void Af::triggerScan()
-{
- LOG(RPiAf, Debug) << "triggerScan";
- if (mode_ == AfModeAuto && scanState_ == ScanState::Idle)
- scanState_ = ScanState::Trigger;
-}
-
-void Af::setMode(AfAlgorithm::AfMode mode)
-{
- LOG(RPiAf, Debug) << "setMode: " << (unsigned)mode;
- if (mode_ != mode) {
- mode_ = mode;
- pauseFlag_ = false;
- if (mode == AfModeContinuous)
- scanState_ = ScanState::Trigger;
- else if (mode != AfModeAuto || scanState_ < ScanState::Coarse)
- goIdle();
- }
-}
-
-AfAlgorithm::AfMode Af::getMode() const
-{
- return mode_;
-}
-
-void Af::pause(AfAlgorithm::AfPause pause)
-{
- LOG(RPiAf, Debug) << "pause: " << (unsigned)pause;
- if (mode_ == AfModeContinuous) {
- if (pause == AfPauseResume && pauseFlag_) {
- pauseFlag_ = false;
- if (scanState_ < ScanState::Coarse)
- scanState_ = ScanState::Trigger;
- } else if (pause != AfPauseResume && !pauseFlag_) {
- pauseFlag_ = true;
- if (pause == AfPauseImmediate || scanState_ < ScanState::Coarse)
- goIdle();
- }
- }
-}
-
-// Register algorithm with the system.
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Af(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/af.h b/src/ipa/raspberrypi/controller/rpi/af.h
deleted file mode 100644
index 6d2bae67..00000000
--- a/src/ipa/raspberrypi/controller/rpi/af.h
+++ /dev/null
@@ -1,165 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2022-2023, Raspberry Pi Ltd
- *
- * af.h - Autofocus control algorithm
- */
-#pragma once
-
-#include "../af_algorithm.h"
-#include "../af_status.h"
-#include "../pdaf_data.h"
-#include "../pwl.h"
-
-/*
- * This algorithm implements a hybrid of CDAF and PDAF, favouring PDAF.
- *
- * Whenever PDAF is available, it is used in a continuous feedback loop.
- * When triggered in auto mode, we simply enable AF for a limited number
- * of frames (it may terminate early if the delta becomes small enough).
- *
- * When PDAF confidence is low (due e.g. to low contrast or extreme defocus)
- * or PDAF data are absent, fall back to CDAF with a programmed scan pattern.
- * A coarse and fine scan are performed, using ISP's CDAF focus FoM to
- * estimate the lens position with peak contrast. This is slower due to
- * extra latency in the ISP, and requires a settling time between steps.
- *
- * Some hysteresis is applied to the switch between PDAF and CDAF, to avoid
- * "nuisance" scans. During each interval where PDAF is not working, only
- * ONE scan will be performed; CAF cannot track objects using CDAF alone.
- *
- */
-
-namespace RPiController {
-
-class Af : public AfAlgorithm
-{
-public:
- Af(Controller *controller = NULL);
- ~Af();
- char const *name() const override;
- int read(const libcamera::YamlObject &params) override;
- void initialise() override;
-
- /* IPA calls */
- void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
- void prepare(Metadata *imageMetadata) override;
- void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
-
- /* controls */
- void setRange(AfRange range) override;
- void setSpeed(AfSpeed speed) override;
- void setMetering(bool use_windows) override;
- void setWindows(libcamera::Span<libcamera::Rectangle const> const &wins) override;
- void setMode(AfMode mode) override;
- AfMode getMode() const override;
- bool setLensPosition(double dioptres, int32_t *hwpos) override;
- std::optional<double> getLensPosition() const override;
- void triggerScan() override;
- void cancelScan() override;
- void pause(AfPause pause) override;
-
-private:
- enum class ScanState {
- Idle = 0,
- Trigger,
- Pdaf,
- Coarse,
- Fine,
- Settle
- };
-
- struct RangeDependentParams {
- double focusMin; /* lower (far) limit in dipotres */
- double focusMax; /* upper (near) limit in dioptres */
- double focusDefault; /* default setting ("hyperfocal") */
-
- RangeDependentParams();
- void read(const libcamera::YamlObject &params);
- };
-
- struct SpeedDependentParams {
- double stepCoarse; /* used for scans */
- double stepFine; /* used for scans */
- double contrastRatio; /* used for scan termination and reporting */
- double pdafGain; /* coefficient for PDAF feedback loop */
- double pdafSquelch; /* PDAF stability parameter (device-specific) */
- double maxSlew; /* limit for lens movement per frame */
- uint32_t pdafFrames; /* number of iterations when triggered */
- uint32_t dropoutFrames; /* number of non-PDAF frames to switch to CDAF */
- uint32_t stepFrames; /* frames to skip in between steps of a scan */
-
- SpeedDependentParams();
- void read(const libcamera::YamlObject &params);
- };
-
- struct CfgParams {
- RangeDependentParams ranges[AfRangeMax];
- SpeedDependentParams speeds[AfSpeedMax];
- uint32_t confEpsilon; /* PDAF hysteresis threshold (sensor-specific) */
- uint32_t confThresh; /* PDAF confidence cell min (sensor-specific) */
- uint32_t confClip; /* PDAF confidence cell max (sensor-specific) */
- uint32_t skipFrames; /* frames to skip at start or modeswitch */
- Pwl map; /* converts dioptres -> lens driver position */
-
- CfgParams();
- int read(const libcamera::YamlObject &params);
- void initialise();
- };
-
- struct ScanRecord {
- double focus;
- double contrast;
- double phase;
- double conf;
- };
-
- struct RegionWeights {
- unsigned rows;
- unsigned cols;
- uint32_t sum;
- std::vector<uint16_t> w;
-
- RegionWeights()
- : rows(0), cols(0), sum(0), w() {}
- };
-
- void computeWeights(RegionWeights *wgts, unsigned rows, unsigned cols);
- void invalidateWeights();
- bool getPhase(PdafRegions const &regions, double &phase, double &conf);
- double getContrast(const FocusRegions &focusStats);
- void doPDAF(double phase, double conf);
- bool earlyTerminationByPhase(double phase);
- double findPeak(unsigned index) const;
- void doScan(double contrast, double phase, double conf);
- void doAF(double contrast, double phase, double conf);
- void updateLensPosition();
- void startAF();
- void startProgrammedScan();
- void goIdle();
-
- /* Configuration and settings */
- CfgParams cfg_;
- AfRange range_;
- AfSpeed speed_;
- AfMode mode_;
- bool pauseFlag_;
- libcamera::Rectangle statsRegion_;
- std::vector<libcamera::Rectangle> windows_;
- bool useWindows_;
- RegionWeights phaseWeights_;
- RegionWeights contrastWeights_;
-
- /* Working state. */
- ScanState scanState_;
- bool initted_;
- double ftarget_, fsmooth_;
- double prevContrast_;
- unsigned skipCount_, stepCount_, dropCount_;
- unsigned scanMaxIndex_;
- double scanMaxContrast_, scanMinContrast_;
- std::vector<ScanRecord> scanData_;
- AfState reportState_;
-};
-
-} // namespace RPiController
diff --git a/src/ipa/raspberrypi/controller/rpi/agc.cpp b/src/ipa/raspberrypi/controller/rpi/agc.cpp
deleted file mode 100644
index e6fb7b8d..00000000
--- a/src/ipa/raspberrypi/controller/rpi/agc.cpp
+++ /dev/null
@@ -1,922 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * agc.cpp - AGC/AEC control algorithm
- */
-
-#include <algorithm>
-#include <map>
-#include <tuple>
-
-#include <libcamera/base/log.h>
-
-#include "../awb_status.h"
-#include "../device_status.h"
-#include "../histogram.h"
-#include "../lux_status.h"
-#include "../metadata.h"
-
-#include "agc.h"
-
-using namespace RPiController;
-using namespace libcamera;
-using libcamera::utils::Duration;
-using namespace std::literals::chrono_literals;
-
-LOG_DEFINE_CATEGORY(RPiAgc)
-
-#define NAME "rpi.agc"
-
-int AgcMeteringMode::read(const libcamera::YamlObject &params)
-{
- const YamlObject &yamlWeights = params["weights"];
-
- for (const auto &p : yamlWeights.asList()) {
- auto value = p.get<double>();
- if (!value)
- return -EINVAL;
- weights.push_back(*value);
- }
-
- return 0;
-}
-
-static std::tuple<int, std::string>
-readMeteringModes(std::map<std::string, AgcMeteringMode> &metering_modes,
- const libcamera::YamlObject &params)
-{
- std::string first;
- int ret;
-
- for (const auto &[key, value] : params.asDict()) {
- AgcMeteringMode meteringMode;
- ret = meteringMode.read(value);
- if (ret)
- return { ret, {} };
-
- metering_modes[key] = std::move(meteringMode);
- if (first.empty())
- first = key;
- }
-
- return { 0, first };
-}
-
-int AgcExposureMode::read(const libcamera::YamlObject &params)
-{
- auto value = params["shutter"].getList<double>();
- if (!value)
- return -EINVAL;
- std::transform(value->begin(), value->end(), std::back_inserter(shutter),
- [](double v) { return v * 1us; });
-
- value = params["gain"].getList<double>();
- if (!value)
- return -EINVAL;
- gain = std::move(*value);
-
- if (shutter.size() < 2 || gain.size() < 2) {
- LOG(RPiAgc, Error)
- << "AgcExposureMode: must have at least two entries in exposure profile";
- return -EINVAL;
- }
-
- if (shutter.size() != gain.size()) {
- LOG(RPiAgc, Error)
- << "AgcExposureMode: expect same number of exposure and gain entries in exposure profile";
- return -EINVAL;
- }
-
- return 0;
-}
-
-static std::tuple<int, std::string>
-readExposureModes(std::map<std::string, AgcExposureMode> &exposureModes,
- const libcamera::YamlObject &params)
-{
- std::string first;
- int ret;
-
- for (const auto &[key, value] : params.asDict()) {
- AgcExposureMode exposureMode;
- ret = exposureMode.read(value);
- if (ret)
- return { ret, {} };
-
- exposureModes[key] = std::move(exposureMode);
- if (first.empty())
- first = key;
- }
-
- return { 0, first };
-}
-
-int AgcConstraint::read(const libcamera::YamlObject &params)
-{
- std::string boundString = params["bound"].get<std::string>("");
- transform(boundString.begin(), boundString.end(),
- boundString.begin(), ::toupper);
- if (boundString != "UPPER" && boundString != "LOWER") {
- LOG(RPiAgc, Error) << "AGC constraint type should be UPPER or LOWER";
- return -EINVAL;
- }
- bound = boundString == "UPPER" ? Bound::UPPER : Bound::LOWER;
-
- auto value = params["q_lo"].get<double>();
- if (!value)
- return -EINVAL;
- qLo = *value;
-
- value = params["q_hi"].get<double>();
- if (!value)
- return -EINVAL;
- qHi = *value;
-
- return yTarget.read(params["y_target"]);
-}
-
-static std::tuple<int, AgcConstraintMode>
-readConstraintMode(const libcamera::YamlObject &params)
-{
- AgcConstraintMode mode;
- int ret;
-
- for (const auto &p : params.asList()) {
- AgcConstraint constraint;
- ret = constraint.read(p);
- if (ret)
- return { ret, {} };
-
- mode.push_back(std::move(constraint));
- }
-
- return { 0, mode };
-}
-
-static std::tuple<int, std::string>
-readConstraintModes(std::map<std::string, AgcConstraintMode> &constraintModes,
- const libcamera::YamlObject &params)
-{
- std::string first;
- int ret;
-
- for (const auto &[key, value] : params.asDict()) {
- std::tie(ret, constraintModes[key]) = readConstraintMode(value);
- if (ret)
- return { ret, {} };
-
- if (first.empty())
- first = key;
- }
-
- return { 0, first };
-}
-
-int AgcConfig::read(const libcamera::YamlObject &params)
-{
- LOG(RPiAgc, Debug) << "AgcConfig";
- int ret;
-
- std::tie(ret, defaultMeteringMode) =
- readMeteringModes(meteringModes, params["metering_modes"]);
- if (ret)
- return ret;
- std::tie(ret, defaultExposureMode) =
- readExposureModes(exposureModes, params["exposure_modes"]);
- if (ret)
- return ret;
- std::tie(ret, defaultConstraintMode) =
- readConstraintModes(constraintModes, params["constraint_modes"]);
- if (ret)
- return ret;
-
- ret = yTarget.read(params["y_target"]);
- if (ret)
- return ret;
-
- speed = params["speed"].get<double>(0.2);
- startupFrames = params["startup_frames"].get<uint16_t>(10);
- convergenceFrames = params["convergence_frames"].get<unsigned int>(6);
- fastReduceThreshold = params["fast_reduce_threshold"].get<double>(0.4);
- baseEv = params["base_ev"].get<double>(1.0);
-
- /* Start with quite a low value as ramping up is easier than ramping down. */
- defaultExposureTime = params["default_exposure_time"].get<double>(1000) * 1us;
- defaultAnalogueGain = params["default_analogue_gain"].get<double>(1.0);
-
- return 0;
-}
-
-Agc::ExposureValues::ExposureValues()
- : shutter(0s), analogueGain(0),
- totalExposure(0s), totalExposureNoDG(0s)
-{
-}
-
-Agc::Agc(Controller *controller)
- : AgcAlgorithm(controller), meteringMode_(nullptr),
- exposureMode_(nullptr), constraintMode_(nullptr),
- frameCount_(0), lockCount_(0),
- lastTargetExposure_(0s), ev_(1.0), flickerPeriod_(0s),
- maxShutter_(0s), fixedShutter_(0s), fixedAnalogueGain_(0.0)
-{
- memset(&awb_, 0, sizeof(awb_));
- /*
- * Setting status_.totalExposureValue_ to zero initially tells us
- * it's not been calculated yet (i.e. Process hasn't yet run).
- */
- memset(&status_, 0, sizeof(status_));
- status_.ev = ev_;
-}
-
-char const *Agc::name() const
-{
- return NAME;
-}
-
-int Agc::read(const libcamera::YamlObject &params)
-{
- LOG(RPiAgc, Debug) << "Agc";
-
- int ret = config_.read(params);
- if (ret)
- return ret;
-
- const Size &size = getHardwareConfig().agcZoneWeights;
- for (auto const &modes : config_.meteringModes) {
- if (modes.second.weights.size() != size.width * size.height) {
- LOG(RPiAgc, Error) << "AgcMeteringMode: Incorrect number of weights";
- return -EINVAL;
- }
- }
-
- /*
- * Set the config's defaults (which are the first ones it read) as our
- * current modes, until someone changes them. (they're all known to
- * exist at this point)
- */
- meteringModeName_ = config_.defaultMeteringMode;
- meteringMode_ = &config_.meteringModes[meteringModeName_];
- exposureModeName_ = config_.defaultExposureMode;
- exposureMode_ = &config_.exposureModes[exposureModeName_];
- constraintModeName_ = config_.defaultConstraintMode;
- constraintMode_ = &config_.constraintModes[constraintModeName_];
- /* Set up the "last shutter/gain" values, in case AGC starts "disabled". */
- status_.shutterTime = config_.defaultExposureTime;
- status_.analogueGain = config_.defaultAnalogueGain;
- return 0;
-}
-
-void Agc::disableAuto()
-{
- fixedShutter_ = status_.shutterTime;
- fixedAnalogueGain_ = status_.analogueGain;
-}
-
-void Agc::enableAuto()
-{
- fixedShutter_ = 0s;
- fixedAnalogueGain_ = 0;
-}
-
-unsigned int Agc::getConvergenceFrames() const
-{
- /*
- * If shutter and gain have been explicitly set, there is no
- * convergence to happen, so no need to drop any frames - return zero.
- */
- if (fixedShutter_ && fixedAnalogueGain_)
- return 0;
- else
- return config_.convergenceFrames;
-}
-
-void Agc::setEv(double ev)
-{
- ev_ = ev;
-}
-
-void Agc::setFlickerPeriod(Duration flickerPeriod)
-{
- flickerPeriod_ = flickerPeriod;
-}
-
-void Agc::setMaxShutter(Duration maxShutter)
-{
- maxShutter_ = maxShutter;
-}
-
-void Agc::setFixedShutter(Duration fixedShutter)
-{
- fixedShutter_ = fixedShutter;
- /* Set this in case someone calls disableAuto() straight after. */
- status_.shutterTime = limitShutter(fixedShutter_);
-}
-
-void Agc::setFixedAnalogueGain(double fixedAnalogueGain)
-{
- fixedAnalogueGain_ = fixedAnalogueGain;
- /* Set this in case someone calls disableAuto() straight after. */
- status_.analogueGain = limitGain(fixedAnalogueGain);
-}
-
-void Agc::setMeteringMode(std::string const &meteringModeName)
-{
- meteringModeName_ = meteringModeName;
-}
-
-void Agc::setExposureMode(std::string const &exposureModeName)
-{
- exposureModeName_ = exposureModeName;
-}
-
-void Agc::setConstraintMode(std::string const &constraintModeName)
-{
- constraintModeName_ = constraintModeName;
-}
-
-void Agc::switchMode(CameraMode const &cameraMode,
- Metadata *metadata)
-{
- /* AGC expects the mode sensitivity always to be non-zero. */
- ASSERT(cameraMode.sensitivity);
-
- housekeepConfig();
-
- /*
- * Store the mode in the local state. We must cache the sensitivity of
- * of the previous mode for the calculations below.
- */
- double lastSensitivity = mode_.sensitivity;
- mode_ = cameraMode;
-
- Duration fixedShutter = limitShutter(fixedShutter_);
- if (fixedShutter && fixedAnalogueGain_) {
- /* We're going to reset the algorithm here with these fixed values. */
-
- fetchAwbStatus(metadata);
- double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
- ASSERT(minColourGain != 0.0);
-
- /* This is the equivalent of computeTargetExposure and applyDigitalGain. */
- target_.totalExposureNoDG = fixedShutter_ * fixedAnalogueGain_;
- target_.totalExposure = target_.totalExposureNoDG / minColourGain;
-
- /* Equivalent of filterExposure. This resets any "history". */
- filtered_ = target_;
-
- /* Equivalent of divideUpExposure. */
- filtered_.shutter = fixedShutter;
- filtered_.analogueGain = fixedAnalogueGain_;
- } else if (status_.totalExposureValue) {
- /*
- * On a mode switch, various things could happen:
- * - the exposure profile might change
- * - a fixed exposure or gain might be set
- * - the new mode's sensitivity might be different
- * We cope with the last of these by scaling the target values. After
- * that we just need to re-divide the exposure/gain according to the
- * current exposure profile, which takes care of everything else.
- */
-
- double ratio = lastSensitivity / cameraMode.sensitivity;
- target_.totalExposureNoDG *= ratio;
- target_.totalExposure *= ratio;
- filtered_.totalExposureNoDG *= ratio;
- filtered_.totalExposure *= ratio;
-
- divideUpExposure();
- } else {
- /*
- * We come through here on startup, when at least one of the shutter
- * or gain has not been fixed. We must still write those values out so
- * that they will be applied immediately. We supply some arbitrary defaults
- * for any that weren't set.
- */
-
- /* Equivalent of divideUpExposure. */
- filtered_.shutter = fixedShutter ? fixedShutter : config_.defaultExposureTime;
- filtered_.analogueGain = fixedAnalogueGain_ ? fixedAnalogueGain_ : config_.defaultAnalogueGain;
- }
-
- writeAndFinish(metadata, false);
-}
-
-void Agc::prepare(Metadata *imageMetadata)
-{
- Duration totalExposureValue = status_.totalExposureValue;
- AgcStatus delayedStatus;
-
- if (!imageMetadata->get("agc.delayed_status", delayedStatus))
- totalExposureValue = delayedStatus.totalExposureValue;
-
- status_.digitalGain = 1.0;
- fetchAwbStatus(imageMetadata); /* always fetch it so that Process knows it's been done */
-
- if (status_.totalExposureValue) {
- /* Process has run, so we have meaningful values. */
- DeviceStatus deviceStatus;
- if (imageMetadata->get("device.status", deviceStatus) == 0) {
- Duration actualExposure = deviceStatus.shutterSpeed *
- deviceStatus.analogueGain;
- if (actualExposure) {
- status_.digitalGain = totalExposureValue / actualExposure;
- LOG(RPiAgc, Debug) << "Want total exposure " << totalExposureValue;
- /*
- * Never ask for a gain < 1.0, and also impose
- * some upper limit. Make it customisable?
- */
- status_.digitalGain = std::max(1.0, std::min(status_.digitalGain, 4.0));
- LOG(RPiAgc, Debug) << "Actual exposure " << actualExposure;
- LOG(RPiAgc, Debug) << "Use digitalGain " << status_.digitalGain;
- LOG(RPiAgc, Debug) << "Effective exposure "
- << actualExposure * status_.digitalGain;
- /* Decide whether AEC/AGC has converged. */
- updateLockStatus(deviceStatus);
- }
- } else
- LOG(RPiAgc, Warning) << name() << ": no device metadata";
- imageMetadata->set("agc.status", status_);
- }
-}
-
-void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
-{
- frameCount_++;
- /*
- * First a little bit of housekeeping, fetching up-to-date settings and
- * configuration, that kind of thing.
- */
- housekeepConfig();
- /* Get the current exposure values for the frame that's just arrived. */
- fetchCurrentExposure(imageMetadata);
- /* Compute the total gain we require relative to the current exposure. */
- double gain, targetY;
- computeGain(stats, imageMetadata, gain, targetY);
- /* Now compute the target (final) exposure which we think we want. */
- computeTargetExposure(gain);
- /*
- * Some of the exposure has to be applied as digital gain, so work out
- * what that is. This function also tells us whether it's decided to
- * "desaturate" the image more quickly.
- */
- bool desaturate = applyDigitalGain(gain, targetY);
- /* The results have to be filtered so as not to change too rapidly. */
- filterExposure(desaturate);
- /*
- * The last thing is to divide up the exposure value into a shutter time
- * and analogue gain, according to the current exposure mode.
- */
- divideUpExposure();
- /* Finally advertise what we've done. */
- writeAndFinish(imageMetadata, desaturate);
-}
-
-void Agc::updateLockStatus(DeviceStatus const &deviceStatus)
-{
- const double errorFactor = 0.10; /* make these customisable? */
- const int maxLockCount = 5;
- /* Reset "lock count" when we exceed this multiple of errorFactor */
- const double resetMargin = 1.5;
-
- /* Add 200us to the exposure time error to allow for line quantisation. */
- Duration exposureError = lastDeviceStatus_.shutterSpeed * errorFactor + 200us;
- double gainError = lastDeviceStatus_.analogueGain * errorFactor;
- Duration targetError = lastTargetExposure_ * errorFactor;
-
- /*
- * Note that we don't know the exposure/gain limits of the sensor, so
- * the values we keep requesting may be unachievable. For this reason
- * we only insist that we're close to values in the past few frames.
- */
- if (deviceStatus.shutterSpeed > lastDeviceStatus_.shutterSpeed - exposureError &&
- deviceStatus.shutterSpeed < lastDeviceStatus_.shutterSpeed + exposureError &&
- deviceStatus.analogueGain > lastDeviceStatus_.analogueGain - gainError &&
- deviceStatus.analogueGain < lastDeviceStatus_.analogueGain + gainError &&
- status_.targetExposureValue > lastTargetExposure_ - targetError &&
- status_.targetExposureValue < lastTargetExposure_ + targetError)
- lockCount_ = std::min(lockCount_ + 1, maxLockCount);
- else if (deviceStatus.shutterSpeed < lastDeviceStatus_.shutterSpeed - resetMargin * exposureError ||
- deviceStatus.shutterSpeed > lastDeviceStatus_.shutterSpeed + resetMargin * exposureError ||
- deviceStatus.analogueGain < lastDeviceStatus_.analogueGain - resetMargin * gainError ||
- deviceStatus.analogueGain > lastDeviceStatus_.analogueGain + resetMargin * gainError ||
- status_.targetExposureValue < lastTargetExposure_ - resetMargin * targetError ||
- status_.targetExposureValue > lastTargetExposure_ + resetMargin * targetError)
- lockCount_ = 0;
-
- lastDeviceStatus_ = deviceStatus;
- lastTargetExposure_ = status_.targetExposureValue;
-
- LOG(RPiAgc, Debug) << "Lock count updated to " << lockCount_;
- status_.locked = lockCount_ == maxLockCount;
-}
-
-static void copyString(std::string const &s, char *d, size_t size)
-{
- size_t length = s.copy(d, size - 1);
- d[length] = '\0';
-}
-
-void Agc::housekeepConfig()
-{
- /* First fetch all the up-to-date settings, so no one else has to do it. */
- status_.ev = ev_;
- status_.fixedShutter = limitShutter(fixedShutter_);
- status_.fixedAnalogueGain = fixedAnalogueGain_;
- status_.flickerPeriod = flickerPeriod_;
- LOG(RPiAgc, Debug) << "ev " << status_.ev << " fixedShutter "
- << status_.fixedShutter << " fixedAnalogueGain "
- << status_.fixedAnalogueGain;
- /*
- * Make sure the "mode" pointers point to the up-to-date things, if
- * they've changed.
- */
- if (strcmp(meteringModeName_.c_str(), status_.meteringMode)) {
- auto it = config_.meteringModes.find(meteringModeName_);
- if (it == config_.meteringModes.end())
- LOG(RPiAgc, Fatal) << "No metering mode " << meteringModeName_;
- meteringMode_ = &it->second;
- copyString(meteringModeName_, status_.meteringMode,
- sizeof(status_.meteringMode));
- }
- if (strcmp(exposureModeName_.c_str(), status_.exposureMode)) {
- auto it = config_.exposureModes.find(exposureModeName_);
- if (it == config_.exposureModes.end())
- LOG(RPiAgc, Fatal) << "No exposure profile " << exposureModeName_;
- exposureMode_ = &it->second;
- copyString(exposureModeName_, status_.exposureMode,
- sizeof(status_.exposureMode));
- }
- if (strcmp(constraintModeName_.c_str(), status_.constraintMode)) {
- auto it =
- config_.constraintModes.find(constraintModeName_);
- if (it == config_.constraintModes.end())
- LOG(RPiAgc, Fatal) << "No constraint list " << constraintModeName_;
- constraintMode_ = &it->second;
- copyString(constraintModeName_, status_.constraintMode,
- sizeof(status_.constraintMode));
- }
- LOG(RPiAgc, Debug) << "exposureMode "
- << exposureModeName_ << " constraintMode "
- << constraintModeName_ << " meteringMode "
- << meteringModeName_;
-}
-
-void Agc::fetchCurrentExposure(Metadata *imageMetadata)
-{
- std::unique_lock<Metadata> lock(*imageMetadata);
- DeviceStatus *deviceStatus =
- imageMetadata->getLocked<DeviceStatus>("device.status");
- if (!deviceStatus)
- LOG(RPiAgc, Fatal) << "No device metadata";
- current_.shutter = deviceStatus->shutterSpeed;
- current_.analogueGain = deviceStatus->analogueGain;
- AgcStatus *agcStatus =
- imageMetadata->getLocked<AgcStatus>("agc.status");
- current_.totalExposure = agcStatus ? agcStatus->totalExposureValue : 0s;
- current_.totalExposureNoDG = current_.shutter * current_.analogueGain;
-}
-
-void Agc::fetchAwbStatus(Metadata *imageMetadata)
-{
- awb_.gainR = 1.0; /* in case not found in metadata */
- awb_.gainG = 1.0;
- awb_.gainB = 1.0;
- if (imageMetadata->get("awb.status", awb_) != 0)
- LOG(RPiAgc, Debug) << "No AWB status found";
-}
-
-static double computeInitialY(StatisticsPtr &stats, AwbStatus const &awb,
- std::vector<double> &weights, double gain)
-{
- constexpr uint64_t maxVal = 1 << Statistics::NormalisationFactorPow2;
-
- ASSERT(weights.size() == stats->agcRegions.numRegions());
-
- /*
- * Note how the calculation below means that equal weights give you
- * "average" metering (i.e. all pixels equally important).
- */
- double rSum = 0, gSum = 0, bSum = 0, pixelSum = 0;
- for (unsigned int i = 0; i < stats->agcRegions.numRegions(); i++) {
- auto &region = stats->agcRegions.get(i);
- double rAcc = std::min<double>(region.val.rSum * gain, (maxVal - 1) * region.counted);
- double gAcc = std::min<double>(region.val.gSum * gain, (maxVal - 1) * region.counted);
- double bAcc = std::min<double>(region.val.bSum * gain, (maxVal - 1) * region.counted);
- rSum += rAcc * weights[i];
- gSum += gAcc * weights[i];
- bSum += bAcc * weights[i];
- pixelSum += region.counted * weights[i];
- }
- if (pixelSum == 0.0) {
- LOG(RPiAgc, Warning) << "computeInitialY: pixelSum is zero";
- return 0;
- }
- double ySum = rSum * awb.gainR * .299 +
- gSum * awb.gainG * .587 +
- bSum * awb.gainB * .114;
- return ySum / pixelSum / maxVal;
-}
-
-/*
- * We handle extra gain through EV by adjusting our Y targets. However, you
- * simply can't monitor histograms once they get very close to (or beyond!)
- * saturation, so we clamp the Y targets to this value. It does mean that EV
- * increases don't necessarily do quite what you might expect in certain
- * (contrived) cases.
- */
-
-static constexpr double EvGainYTargetLimit = 0.9;
-
-static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux,
- double evGain, double &targetY)
-{
- targetY = c.yTarget.eval(c.yTarget.domain().clip(lux));
- targetY = std::min(EvGainYTargetLimit, targetY * evGain);
- double iqm = h.interQuantileMean(c.qLo, c.qHi);
- return (targetY * h.bins()) / iqm;
-}
-
-void Agc::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
- double &gain, double &targetY)
-{
- struct LuxStatus lux = {};
- lux.lux = 400; /* default lux level to 400 in case no metadata found */
- if (imageMetadata->get("lux.status", lux) != 0)
- LOG(RPiAgc, Warning) << "No lux level found";
- const Histogram &h = statistics->yHist;
- double evGain = status_.ev * config_.baseEv;
- /*
- * The initial gain and target_Y come from some of the regions. After
- * that we consider the histogram constraints.
- */
- targetY = config_.yTarget.eval(config_.yTarget.domain().clip(lux.lux));
- targetY = std::min(EvGainYTargetLimit, targetY * evGain);
-
- /*
- * Do this calculation a few times as brightness increase can be
- * non-linear when there are saturated regions.
- */
- gain = 1.0;
- for (int i = 0; i < 8; i++) {
- double initialY = computeInitialY(statistics, awb_, meteringMode_->weights, gain);
- double extraGain = std::min(10.0, targetY / (initialY + .001));
- gain *= extraGain;
- LOG(RPiAgc, Debug) << "Initial Y " << initialY << " target " << targetY
- << " gives gain " << gain;
- if (extraGain < 1.01) /* close enough */
- break;
- }
-
- for (auto &c : *constraintMode_) {
- double newTargetY;
- double newGain = constraintComputeGain(c, h, lux.lux, evGain, newTargetY);
- LOG(RPiAgc, Debug) << "Constraint has target_Y "
- << newTargetY << " giving gain " << newGain;
- if (c.bound == AgcConstraint::Bound::LOWER && newGain > gain) {
- LOG(RPiAgc, Debug) << "Lower bound constraint adopted";
- gain = newGain;
- targetY = newTargetY;
- } else if (c.bound == AgcConstraint::Bound::UPPER && newGain < gain) {
- LOG(RPiAgc, Debug) << "Upper bound constraint adopted";
- gain = newGain;
- targetY = newTargetY;
- }
- }
- LOG(RPiAgc, Debug) << "Final gain " << gain << " (target_Y " << targetY << " ev "
- << status_.ev << " base_ev " << config_.baseEv
- << ")";
-}
-
-void Agc::computeTargetExposure(double gain)
-{
- if (status_.fixedShutter && status_.fixedAnalogueGain) {
- /*
- * When ag and shutter are both fixed, we need to drive the
- * total exposure so that we end up with a digital gain of at least
- * 1/minColourGain. Otherwise we'd desaturate channels causing
- * white to go cyan or magenta.
- */
- double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
- ASSERT(minColourGain != 0.0);
- target_.totalExposure =
- status_.fixedShutter * status_.fixedAnalogueGain / minColourGain;
- } else {
- /*
- * The statistics reflect the image without digital gain, so the final
- * total exposure we're aiming for is:
- */
- target_.totalExposure = current_.totalExposureNoDG * gain;
- /* The final target exposure is also limited to what the exposure mode allows. */
- Duration maxShutter = status_.fixedShutter
- ? status_.fixedShutter
- : exposureMode_->shutter.back();
- maxShutter = limitShutter(maxShutter);
- Duration maxTotalExposure =
- maxShutter *
- (status_.fixedAnalogueGain != 0.0
- ? status_.fixedAnalogueGain
- : exposureMode_->gain.back());
- target_.totalExposure = std::min(target_.totalExposure, maxTotalExposure);
- }
- LOG(RPiAgc, Debug) << "Target totalExposure " << target_.totalExposure;
-}
-
-bool Agc::applyDigitalGain(double gain, double targetY)
-{
- double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
- ASSERT(minColourGain != 0.0);
- double dg = 1.0 / minColourGain;
- /*
- * I think this pipeline subtracts black level and rescales before we
- * get the stats, so no need to worry about it.
- */
- LOG(RPiAgc, Debug) << "after AWB, target dg " << dg << " gain " << gain
- << " target_Y " << targetY;
- /*
- * Finally, if we're trying to reduce exposure but the target_Y is
- * "close" to 1.0, then the gain computed for that constraint will be
- * only slightly less than one, because the measured Y can never be
- * larger than 1.0. When this happens, demand a large digital gain so
- * that the exposure can be reduced, de-saturating the image much more
- * quickly (and we then approach the correct value more quickly from
- * below).
- */
- bool desaturate = targetY > config_.fastReduceThreshold &&
- gain < sqrt(targetY);
- if (desaturate)
- dg /= config_.fastReduceThreshold;
- LOG(RPiAgc, Debug) << "Digital gain " << dg << " desaturate? " << desaturate;
- target_.totalExposureNoDG = target_.totalExposure / dg;
- LOG(RPiAgc, Debug) << "Target totalExposureNoDG " << target_.totalExposureNoDG;
- return desaturate;
-}
-
-void Agc::filterExposure(bool desaturate)
-{
- double speed = config_.speed;
- /*
- * AGC adapts instantly if both shutter and gain are directly specified
- * or we're in the startup phase.
- */
- if ((status_.fixedShutter && status_.fixedAnalogueGain) ||
- frameCount_ <= config_.startupFrames)
- speed = 1.0;
- if (!filtered_.totalExposure) {
- filtered_.totalExposure = target_.totalExposure;
- filtered_.totalExposureNoDG = target_.totalExposureNoDG;
- } else {
- /*
- * If close to the result go faster, to save making so many
- * micro-adjustments on the way. (Make this customisable?)
- */
- if (filtered_.totalExposure < 1.2 * target_.totalExposure &&
- filtered_.totalExposure > 0.8 * target_.totalExposure)
- speed = sqrt(speed);
- filtered_.totalExposure = speed * target_.totalExposure +
- filtered_.totalExposure * (1.0 - speed);
- /*
- * When desaturing, take a big jump down in totalExposureNoDG,
- * which we'll hide with digital gain.
- */
- if (desaturate)
- filtered_.totalExposureNoDG =
- target_.totalExposureNoDG;
- else
- filtered_.totalExposureNoDG =
- speed * target_.totalExposureNoDG +
- filtered_.totalExposureNoDG * (1.0 - speed);
- }
- /*
- * We can't let the totalExposureNoDG exposure deviate too far below the
- * total exposure, as there might not be enough digital gain available
- * in the ISP to hide it (which will cause nasty oscillation).
- */
- if (filtered_.totalExposureNoDG <
- filtered_.totalExposure * config_.fastReduceThreshold)
- filtered_.totalExposureNoDG = filtered_.totalExposure * config_.fastReduceThreshold;
- LOG(RPiAgc, Debug) << "After filtering, totalExposure " << filtered_.totalExposure
- << " no dg " << filtered_.totalExposureNoDG;
-}
-
-void Agc::divideUpExposure()
-{
- /*
- * Sending the fixed shutter/gain cases through the same code may seem
- * unnecessary, but it will make more sense when extend this to cover
- * variable aperture.
- */
- Duration exposureValue = filtered_.totalExposureNoDG;
- Duration shutterTime;
- double analogueGain;
- shutterTime = status_.fixedShutter ? status_.fixedShutter
- : exposureMode_->shutter[0];
- shutterTime = limitShutter(shutterTime);
- analogueGain = status_.fixedAnalogueGain != 0.0 ? status_.fixedAnalogueGain
- : exposureMode_->gain[0];
- analogueGain = limitGain(analogueGain);
- if (shutterTime * analogueGain < exposureValue) {
- for (unsigned int stage = 1;
- stage < exposureMode_->gain.size(); stage++) {
- if (!status_.fixedShutter) {
- Duration stageShutter =
- limitShutter(exposureMode_->shutter[stage]);
- if (stageShutter * analogueGain >= exposureValue) {
- shutterTime = exposureValue / analogueGain;
- break;
- }
- shutterTime = stageShutter;
- }
- if (status_.fixedAnalogueGain == 0.0) {
- if (exposureMode_->gain[stage] * shutterTime >= exposureValue) {
- analogueGain = exposureValue / shutterTime;
- break;
- }
- analogueGain = exposureMode_->gain[stage];
- analogueGain = limitGain(analogueGain);
- }
- }
- }
- LOG(RPiAgc, Debug) << "Divided up shutter and gain are " << shutterTime << " and "
- << analogueGain;
- /*
- * Finally adjust shutter time for flicker avoidance (require both
- * shutter and gain not to be fixed).
- */
- if (!status_.fixedShutter && !status_.fixedAnalogueGain &&
- status_.flickerPeriod) {
- int flickerPeriods = shutterTime / status_.flickerPeriod;
- if (flickerPeriods) {
- Duration newShutterTime = flickerPeriods * status_.flickerPeriod;
- analogueGain *= shutterTime / newShutterTime;
- /*
- * We should still not allow the ag to go over the
- * largest value in the exposure mode. Note that this
- * may force more of the total exposure into the digital
- * gain as a side-effect.
- */
- analogueGain = std::min(analogueGain, exposureMode_->gain.back());
- analogueGain = limitGain(analogueGain);
- shutterTime = newShutterTime;
- }
- LOG(RPiAgc, Debug) << "After flicker avoidance, shutter "
- << shutterTime << " gain " << analogueGain;
- }
- filtered_.shutter = shutterTime;
- filtered_.analogueGain = analogueGain;
-}
-
-void Agc::writeAndFinish(Metadata *imageMetadata, bool desaturate)
-{
- status_.totalExposureValue = filtered_.totalExposure;
- status_.targetExposureValue = desaturate ? 0s : target_.totalExposureNoDG;
- status_.shutterTime = filtered_.shutter;
- status_.analogueGain = filtered_.analogueGain;
- /*
- * Write to metadata as well, in case anyone wants to update the camera
- * immediately.
- */
- imageMetadata->set("agc.status", status_);
- LOG(RPiAgc, Debug) << "Output written, total exposure requested is "
- << filtered_.totalExposure;
- LOG(RPiAgc, Debug) << "Camera exposure update: shutter time " << filtered_.shutter
- << " analogue gain " << filtered_.analogueGain;
-}
-
-Duration Agc::limitShutter(Duration shutter)
-{
- /*
- * shutter == 0 is a special case for fixed shutter values, and must pass
- * through unchanged
- */
- if (!shutter)
- return shutter;
-
- shutter = std::clamp(shutter, mode_.minShutter, maxShutter_);
- return shutter;
-}
-
-double Agc::limitGain(double gain) const
-{
- /*
- * Only limit the lower bounds of the gain value to what the sensor limits.
- * The upper bound on analogue gain will be made up with additional digital
- * gain applied by the ISP.
- *
- * gain == 0.0 is a special case for fixed shutter values, and must pass
- * through unchanged
- */
- if (!gain)
- return gain;
-
- gain = std::max(gain, mode_.minAnalogueGain);
- return gain;
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Agc(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/agc.h b/src/ipa/raspberrypi/controller/rpi/agc.h
deleted file mode 100644
index 4e5f272f..00000000
--- a/src/ipa/raspberrypi/controller/rpi/agc.h
+++ /dev/null
@@ -1,133 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * agc.h - AGC/AEC control algorithm
- */
-#pragma once
-
-#include <vector>
-#include <mutex>
-
-#include <libcamera/base/utils.h>
-
-#include "../agc_algorithm.h"
-#include "../agc_status.h"
-#include "../pwl.h"
-
-/* This is our implementation of AGC. */
-
-namespace RPiController {
-
-struct AgcMeteringMode {
- std::vector<double> weights;
- int read(const libcamera::YamlObject &params);
-};
-
-struct AgcExposureMode {
- std::vector<libcamera::utils::Duration> shutter;
- std::vector<double> gain;
- int read(const libcamera::YamlObject &params);
-};
-
-struct AgcConstraint {
- enum class Bound { LOWER = 0, UPPER = 1 };
- Bound bound;
- double qLo;
- double qHi;
- Pwl yTarget;
- int read(const libcamera::YamlObject &params);
-};
-
-typedef std::vector<AgcConstraint> AgcConstraintMode;
-
-struct AgcConfig {
- int read(const libcamera::YamlObject &params);
- std::map<std::string, AgcMeteringMode> meteringModes;
- std::map<std::string, AgcExposureMode> exposureModes;
- std::map<std::string, AgcConstraintMode> constraintModes;
- Pwl yTarget;
- double speed;
- uint16_t startupFrames;
- unsigned int convergenceFrames;
- double maxChange;
- double minChange;
- double fastReduceThreshold;
- double speedUpThreshold;
- std::string defaultMeteringMode;
- std::string defaultExposureMode;
- std::string defaultConstraintMode;
- double baseEv;
- libcamera::utils::Duration defaultExposureTime;
- double defaultAnalogueGain;
-};
-
-class Agc : public AgcAlgorithm
-{
-public:
- Agc(Controller *controller);
- char const *name() const override;
- int read(const libcamera::YamlObject &params) override;
- unsigned int getConvergenceFrames() const override;
- void setEv(double ev) override;
- void setFlickerPeriod(libcamera::utils::Duration flickerPeriod) override;
- void setMaxShutter(libcamera::utils::Duration maxShutter) override;
- void setFixedShutter(libcamera::utils::Duration fixedShutter) override;
- void setFixedAnalogueGain(double fixedAnalogueGain) override;
- void setMeteringMode(std::string const &meteringModeName) override;
- void setExposureMode(std::string const &exposureModeName) override;
- void setConstraintMode(std::string const &contraintModeName) override;
- void enableAuto() override;
- void disableAuto() override;
- void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
- void prepare(Metadata *imageMetadata) override;
- void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
-
-private:
- void updateLockStatus(DeviceStatus const &deviceStatus);
- AgcConfig config_;
- void housekeepConfig();
- void fetchCurrentExposure(Metadata *imageMetadata);
- void fetchAwbStatus(Metadata *imageMetadata);
- void computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,
- double &gain, double &targetY);
- void computeTargetExposure(double gain);
- bool applyDigitalGain(double gain, double targetY);
- void filterExposure(bool desaturate);
- void divideUpExposure();
- void writeAndFinish(Metadata *imageMetadata, bool desaturate);
- libcamera::utils::Duration limitShutter(libcamera::utils::Duration shutter);
- double limitGain(double gain) const;
- AgcMeteringMode *meteringMode_;
- AgcExposureMode *exposureMode_;
- AgcConstraintMode *constraintMode_;
- CameraMode mode_;
- uint64_t frameCount_;
- AwbStatus awb_;
- struct ExposureValues {
- ExposureValues();
-
- libcamera::utils::Duration shutter;
- double analogueGain;
- libcamera::utils::Duration totalExposure;
- libcamera::utils::Duration totalExposureNoDG; /* without digital gain */
- };
- ExposureValues current_; /* values for the current frame */
- ExposureValues target_; /* calculate the values we want here */
- ExposureValues filtered_; /* these values are filtered towards target */
- AgcStatus status_;
- int lockCount_;
- DeviceStatus lastDeviceStatus_;
- libcamera::utils::Duration lastTargetExposure_;
- /* Below here the "settings" that applications can change. */
- std::string meteringModeName_;
- std::string exposureModeName_;
- std::string constraintModeName_;
- double ev_;
- libcamera::utils::Duration flickerPeriod_;
- libcamera::utils::Duration maxShutter_;
- libcamera::utils::Duration fixedShutter_;
- double fixedAnalogueGain_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp b/src/ipa/raspberrypi/controller/rpi/alsc.cpp
deleted file mode 100644
index 3a2e8fe0..00000000
--- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp
+++ /dev/null
@@ -1,865 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * alsc.cpp - ALSC (auto lens shading correction) control algorithm
- */
-
-#include <algorithm>
-#include <functional>
-#include <math.h>
-#include <numeric>
-
-#include <libcamera/base/log.h>
-#include <libcamera/base/span.h>
-
-#include "../awb_status.h"
-#include "alsc.h"
-
-/* Raspberry Pi ALSC (Auto Lens Shading Correction) algorithm. */
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiAlsc)
-
-#define NAME "rpi.alsc"
-
-static const double InsufficientData = -1.0;
-
-Alsc::Alsc(Controller *controller)
- : Algorithm(controller)
-{
- asyncAbort_ = asyncStart_ = asyncStarted_ = asyncFinished_ = false;
- asyncThread_ = std::thread(std::bind(&Alsc::asyncFunc, this));
-}
-
-Alsc::~Alsc()
-{
- {
- std::lock_guard<std::mutex> lock(mutex_);
- asyncAbort_ = true;
- }
- asyncSignal_.notify_one();
- asyncThread_.join();
-}
-
-char const *Alsc::name() const
-{
- return NAME;
-}
-
-static int generateLut(Array2D<double> &lut, const libcamera::YamlObject &params)
-{
- /* These must be signed ints for the co-ordinate calculations below. */
- int X = lut.dimensions().width, Y = lut.dimensions().height;
- double cstrength = params["corner_strength"].get<double>(2.0);
- if (cstrength <= 1.0) {
- LOG(RPiAlsc, Error) << "corner_strength must be > 1.0";
- return -EINVAL;
- }
-
- double asymmetry = params["asymmetry"].get<double>(1.0);
- if (asymmetry < 0) {
- LOG(RPiAlsc, Error) << "asymmetry must be >= 0";
- return -EINVAL;
- }
-
- double f1 = cstrength - 1, f2 = 1 + sqrt(cstrength);
- double R2 = X * Y / 4 * (1 + asymmetry * asymmetry);
- int num = 0;
- for (int y = 0; y < Y; y++) {
- for (int x = 0; x < X; x++) {
- double dy = y - Y / 2 + 0.5,
- dx = (x - X / 2 + 0.5) * asymmetry;
- double r2 = (dx * dx + dy * dy) / R2;
- lut[num++] =
- (f1 * r2 + f2) * (f1 * r2 + f2) /
- (f2 * f2); /* this reproduces the cos^4 rule */
- }
- }
- return 0;
-}
-
-static int readLut(Array2D<double> &lut, const libcamera::YamlObject &params)
-{
- if (params.size() != lut.size()) {
- LOG(RPiAlsc, Error) << "Invalid number of entries in LSC table";
- return -EINVAL;
- }
-
- int num = 0;
- for (const auto &p : params.asList()) {
- auto value = p.get<double>();
- if (!value)
- return -EINVAL;
- lut[num++] = *value;
- }
-
- return 0;
-}
-
-static int readCalibrations(std::vector<AlscCalibration> &calibrations,
- const libcamera::YamlObject &params,
- std::string const &name, const Size &size)
-{
- if (params.contains(name)) {
- double lastCt = 0;
- for (const auto &p : params[name].asList()) {
- auto value = p["ct"].get<double>();
- if (!value)
- return -EINVAL;
- double ct = *value;
- if (ct <= lastCt) {
- LOG(RPiAlsc, Error)
- << "Entries in " << name << " must be in increasing ct order";
- return -EINVAL;
- }
- AlscCalibration calibration;
- calibration.ct = lastCt = ct;
-
- const libcamera::YamlObject &table = p["table"];
- if (table.size() != size.width * size.height) {
- LOG(RPiAlsc, Error)
- << "Incorrect number of values for ct "
- << ct << " in " << name;
- return -EINVAL;
- }
-
- int num = 0;
- calibration.table.resize(size);
- for (const auto &elem : table.asList()) {
- value = elem.get<double>();
- if (!value)
- return -EINVAL;
- calibration.table[num++] = *value;
- }
-
- calibrations.push_back(std::move(calibration));
- LOG(RPiAlsc, Debug)
- << "Read " << name << " calibration for ct " << ct;
- }
- }
- return 0;
-}
-
-int Alsc::read(const libcamera::YamlObject &params)
-{
- config_.tableSize = getHardwareConfig().awbRegions;
- config_.framePeriod = params["frame_period"].get<uint16_t>(12);
- config_.startupFrames = params["startup_frames"].get<uint16_t>(10);
- config_.speed = params["speed"].get<double>(0.05);
- double sigma = params["sigma"].get<double>(0.01);
- config_.sigmaCr = params["sigma_Cr"].get<double>(sigma);
- config_.sigmaCb = params["sigma_Cb"].get<double>(sigma);
- config_.minCount = params["min_count"].get<double>(10.0);
- config_.minG = params["min_G"].get<uint16_t>(50);
- config_.omega = params["omega"].get<double>(1.3);
- config_.nIter = params["n_iter"].get<uint32_t>(config_.tableSize.width + config_.tableSize.height);
- config_.luminanceStrength =
- params["luminance_strength"].get<double>(1.0);
-
- config_.luminanceLut.resize(config_.tableSize, 1.0);
- int ret = 0;
-
- if (params.contains("corner_strength"))
- ret = generateLut(config_.luminanceLut, params);
- else if (params.contains("luminance_lut"))
- ret = readLut(config_.luminanceLut, params["luminance_lut"]);
- else
- LOG(RPiAlsc, Warning)
- << "no luminance table - assume unity everywhere";
- if (ret)
- return ret;
-
- ret = readCalibrations(config_.calibrationsCr, params, "calibrations_Cr",
- config_.tableSize);
- if (ret)
- return ret;
- ret = readCalibrations(config_.calibrationsCb, params, "calibrations_Cb",
- config_.tableSize);
- if (ret)
- return ret;
-
- config_.defaultCt = params["default_ct"].get<double>(4500.0);
- config_.threshold = params["threshold"].get<double>(1e-3);
- config_.lambdaBound = params["lambda_bound"].get<double>(0.05);
-
- return 0;
-}
-
-static double getCt(Metadata *metadata, double defaultCt);
-static void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,
- Array2D<double> &calTable);
-static void resampleCalTable(const Array2D<double> &calTableIn, CameraMode const &cameraMode,
- Array2D<double> &calTableOut);
-static void compensateLambdasForCal(const Array2D<double> &calTable,
- const Array2D<double> &oldLambdas,
- Array2D<double> &newLambdas);
-static void addLuminanceToTables(std::array<Array2D<double>, 3> &results,
- const Array2D<double> &lambdaR, double lambdaG,
- const Array2D<double> &lambdaB,
- const Array2D<double> &luminanceLut,
- double luminanceStrength);
-
-void Alsc::initialise()
-{
- frameCount2_ = frameCount_ = framePhase_ = 0;
- firstTime_ = true;
- ct_ = config_.defaultCt;
-
- const size_t XY = config_.tableSize.width * config_.tableSize.height;
-
- for (auto &r : syncResults_)
- r.resize(config_.tableSize);
- for (auto &r : prevSyncResults_)
- r.resize(config_.tableSize);
- for (auto &r : asyncResults_)
- r.resize(config_.tableSize);
-
- luminanceTable_.resize(config_.tableSize);
- asyncLambdaR_.resize(config_.tableSize);
- asyncLambdaB_.resize(config_.tableSize);
- /* The lambdas are initialised in the SwitchMode. */
- lambdaR_.resize(config_.tableSize);
- lambdaB_.resize(config_.tableSize);
-
- /* Temporaries for the computations, but sensible to allocate this up-front! */
- for (auto &c : tmpC_)
- c.resize(config_.tableSize);
- for (auto &m : tmpM_)
- m.resize(XY);
-}
-
-void Alsc::waitForAysncThread()
-{
- if (asyncStarted_) {
- asyncStarted_ = false;
- std::unique_lock<std::mutex> lock(mutex_);
- syncSignal_.wait(lock, [&] {
- return asyncFinished_;
- });
- asyncFinished_ = false;
- }
-}
-
-static bool compareModes(CameraMode const &cm0, CameraMode const &cm1)
-{
- /*
- * Return true if the modes crop from the sensor significantly differently,
- * or if the user transform has changed.
- */
- if (cm0.transform != cm1.transform)
- return true;
- int leftDiff = abs(cm0.cropX - cm1.cropX);
- int topDiff = abs(cm0.cropY - cm1.cropY);
- int rightDiff = fabs(cm0.cropX + cm0.scaleX * cm0.width -
- cm1.cropX - cm1.scaleX * cm1.width);
- int bottomDiff = fabs(cm0.cropY + cm0.scaleY * cm0.height -
- cm1.cropY - cm1.scaleY * cm1.height);
- /*
- * These thresholds are a rather arbitrary amount chosen to trigger
- * when carrying on with the previously calculated tables might be
- * worse than regenerating them (but without the adaptive algorithm).
- */
- int thresholdX = cm0.sensorWidth >> 4;
- int thresholdY = cm0.sensorHeight >> 4;
- return leftDiff > thresholdX || rightDiff > thresholdX ||
- topDiff > thresholdY || bottomDiff > thresholdY;
-}
-
-void Alsc::switchMode(CameraMode const &cameraMode,
- [[maybe_unused]] Metadata *metadata)
-{
- /*
- * We're going to start over with the tables if there's any "significant"
- * change.
- */
- bool resetTables = firstTime_ || compareModes(cameraMode_, cameraMode);
-
- /* Believe the colour temperature from the AWB, if there is one. */
- ct_ = getCt(metadata, ct_);
-
- /* Ensure the other thread isn't running while we do this. */
- waitForAysncThread();
-
- cameraMode_ = cameraMode;
-
- /*
- * We must resample the luminance table like we do the others, but it's
- * fixed so we can simply do it up front here.
- */
- resampleCalTable(config_.luminanceLut, cameraMode_, luminanceTable_);
-
- if (resetTables) {
- /*
- * Upon every "table reset", arrange for something sensible to be
- * generated. Construct the tables for the previous recorded colour
- * temperature. In order to start over from scratch we initialise
- * the lambdas, but the rest of this code then echoes the code in
- * doAlsc, without the adaptive algorithm.
- */
- std::fill(lambdaR_.begin(), lambdaR_.end(), 1.0);
- std::fill(lambdaB_.begin(), lambdaB_.end(), 1.0);
- Array2D<double> &calTableR = tmpC_[0], &calTableB = tmpC_[1], &calTableTmp = tmpC_[2];
- getCalTable(ct_, config_.calibrationsCr, calTableTmp);
- resampleCalTable(calTableTmp, cameraMode_, calTableR);
- getCalTable(ct_, config_.calibrationsCb, calTableTmp);
- resampleCalTable(calTableTmp, cameraMode_, calTableB);
- compensateLambdasForCal(calTableR, lambdaR_, asyncLambdaR_);
- compensateLambdasForCal(calTableB, lambdaB_, asyncLambdaB_);
- addLuminanceToTables(syncResults_, asyncLambdaR_, 1.0, asyncLambdaB_,
- luminanceTable_, config_.luminanceStrength);
- prevSyncResults_ = syncResults_;
- framePhase_ = config_.framePeriod; /* run the algo again asap */
- firstTime_ = false;
- }
-}
-
-void Alsc::fetchAsyncResults()
-{
- LOG(RPiAlsc, Debug) << "Fetch ALSC results";
- asyncFinished_ = false;
- asyncStarted_ = false;
- syncResults_ = asyncResults_;
-}
-
-double getCt(Metadata *metadata, double defaultCt)
-{
- AwbStatus awbStatus;
- awbStatus.temperatureK = defaultCt; /* in case nothing found */
- if (metadata->get("awb.status", awbStatus) != 0)
- LOG(RPiAlsc, Debug) << "no AWB results found, using "
- << awbStatus.temperatureK;
- else
- LOG(RPiAlsc, Debug) << "AWB results found, using "
- << awbStatus.temperatureK;
- return awbStatus.temperatureK;
-}
-
-static void copyStats(RgbyRegions &regions, StatisticsPtr &stats,
- AlscStatus const &status)
-{
- if (!regions.numRegions())
- regions.init(stats->awbRegions.size());
-
- const std::vector<double> &rTable = status.r;
- const std::vector<double> &gTable = status.g;
- const std::vector<double> &bTable = status.b;
- for (unsigned int i = 0; i < stats->awbRegions.numRegions(); i++) {
- auto r = stats->awbRegions.get(i);
- r.val.rSum = static_cast<uint64_t>(r.val.rSum / rTable[i]);
- r.val.gSum = static_cast<uint64_t>(r.val.gSum / gTable[i]);
- r.val.bSum = static_cast<uint64_t>(r.val.bSum / bTable[i]);
- regions.set(i, r);
- }
-}
-
-void Alsc::restartAsync(StatisticsPtr &stats, Metadata *imageMetadata)
-{
- LOG(RPiAlsc, Debug) << "Starting ALSC calculation";
- /*
- * Get the current colour temperature. It's all we need from the
- * metadata. Default to the last CT value (which could be the default).
- */
- ct_ = getCt(imageMetadata, ct_);
- /*
- * We have to copy the statistics here, dividing out our best guess of
- * the LSC table that the pipeline applied to them.
- */
- AlscStatus alscStatus;
- if (imageMetadata->get("alsc.status", alscStatus) != 0) {
- LOG(RPiAlsc, Warning)
- << "No ALSC status found for applied gains!";
- alscStatus.r.resize(config_.tableSize.width * config_.tableSize.height, 1.0);
- alscStatus.g.resize(config_.tableSize.width * config_.tableSize.height, 1.0);
- alscStatus.b.resize(config_.tableSize.width * config_.tableSize.height, 1.0);
- }
- copyStats(statistics_, stats, alscStatus);
- framePhase_ = 0;
- asyncStarted_ = true;
- {
- std::lock_guard<std::mutex> lock(mutex_);
- asyncStart_ = true;
- }
- asyncSignal_.notify_one();
-}
-
-void Alsc::prepare(Metadata *imageMetadata)
-{
- /*
- * Count frames since we started, and since we last poked the async
- * thread.
- */
- if (frameCount_ < (int)config_.startupFrames)
- frameCount_++;
- double speed = frameCount_ < (int)config_.startupFrames
- ? 1.0
- : config_.speed;
- LOG(RPiAlsc, Debug)
- << "frame count " << frameCount_ << " speed " << speed;
- {
- std::unique_lock<std::mutex> lock(mutex_);
- if (asyncStarted_ && asyncFinished_)
- fetchAsyncResults();
- }
- /* Apply IIR filter to results and program into the pipeline. */
- for (unsigned int j = 0; j < syncResults_.size(); j++) {
- for (unsigned int i = 0; i < syncResults_[j].size(); i++)
- prevSyncResults_[j][i] = speed * syncResults_[j][i] + (1.0 - speed) * prevSyncResults_[j][i];
- }
- /* Put output values into status metadata. */
- AlscStatus status;
- status.r = prevSyncResults_[0].data();
- status.g = prevSyncResults_[1].data();
- status.b = prevSyncResults_[2].data();
- imageMetadata->set("alsc.status", status);
-}
-
-void Alsc::process(StatisticsPtr &stats, Metadata *imageMetadata)
-{
- /*
- * Count frames since we started, and since we last poked the async
- * thread.
- */
- if (framePhase_ < (int)config_.framePeriod)
- framePhase_++;
- if (frameCount2_ < (int)config_.startupFrames)
- frameCount2_++;
- LOG(RPiAlsc, Debug) << "frame_phase " << framePhase_;
- if (framePhase_ >= (int)config_.framePeriod ||
- frameCount2_ < (int)config_.startupFrames) {
- if (asyncStarted_ == false)
- restartAsync(stats, imageMetadata);
- }
-}
-
-void Alsc::asyncFunc()
-{
- while (true) {
- {
- std::unique_lock<std::mutex> lock(mutex_);
- asyncSignal_.wait(lock, [&] {
- return asyncStart_ || asyncAbort_;
- });
- asyncStart_ = false;
- if (asyncAbort_)
- break;
- }
- doAlsc();
- {
- std::lock_guard<std::mutex> lock(mutex_);
- asyncFinished_ = true;
- }
- syncSignal_.notify_one();
- }
-}
-
-void getCalTable(double ct, std::vector<AlscCalibration> const &calibrations,
- Array2D<double> &calTable)
-{
- if (calibrations.empty()) {
- std::fill(calTable.begin(), calTable.end(), 1.0);
- LOG(RPiAlsc, Debug) << "no calibrations found";
- } else if (ct <= calibrations.front().ct) {
- calTable = calibrations.front().table;
- LOG(RPiAlsc, Debug) << "using calibration for "
- << calibrations.front().ct;
- } else if (ct >= calibrations.back().ct) {
- calTable = calibrations.back().table;
- LOG(RPiAlsc, Debug) << "using calibration for "
- << calibrations.back().ct;
- } else {
- int idx = 0;
- while (ct > calibrations[idx + 1].ct)
- idx++;
- double ct0 = calibrations[idx].ct, ct1 = calibrations[idx + 1].ct;
- LOG(RPiAlsc, Debug)
- << "ct is " << ct << ", interpolating between "
- << ct0 << " and " << ct1;
- for (unsigned int i = 0; i < calTable.size(); i++)
- calTable[i] =
- (calibrations[idx].table[i] * (ct1 - ct) +
- calibrations[idx + 1].table[i] * (ct - ct0)) /
- (ct1 - ct0);
- }
-}
-
-void resampleCalTable(const Array2D<double> &calTableIn,
- CameraMode const &cameraMode,
- Array2D<double> &calTableOut)
-{
- int X = calTableIn.dimensions().width;
- int Y = calTableIn.dimensions().height;
-
- /*
- * Precalculate and cache the x sampling locations and phases to save
- * recomputing them on every row.
- */
- int xLo[X], xHi[X];
- double xf[X];
- double scaleX = cameraMode.sensorWidth /
- (cameraMode.width * cameraMode.scaleX);
- double xOff = cameraMode.cropX / (double)cameraMode.sensorWidth;
- double x = .5 / scaleX + xOff * X - .5;
- double xInc = 1 / scaleX;
- for (int i = 0; i < X; i++, x += xInc) {
- xLo[i] = floor(x);
- xf[i] = x - xLo[i];
- xHi[i] = std::min(xLo[i] + 1, X - 1);
- xLo[i] = std::max(xLo[i], 0);
- if (!!(cameraMode.transform & libcamera::Transform::HFlip)) {
- xLo[i] = X - 1 - xLo[i];
- xHi[i] = X - 1 - xHi[i];
- }
- }
- /* Now march over the output table generating the new values. */
- double scaleY = cameraMode.sensorHeight /
- (cameraMode.height * cameraMode.scaleY);
- double yOff = cameraMode.cropY / (double)cameraMode.sensorHeight;
- double y = .5 / scaleY + yOff * Y - .5;
- double yInc = 1 / scaleY;
- for (int j = 0; j < Y; j++, y += yInc) {
- int yLo = floor(y);
- double yf = y - yLo;
- int yHi = std::min(yLo + 1, Y - 1);
- yLo = std::max(yLo, 0);
- if (!!(cameraMode.transform & libcamera::Transform::VFlip)) {
- yLo = Y - 1 - yLo;
- yHi = Y - 1 - yHi;
- }
- double const *rowAbove = calTableIn.ptr() + X * yLo;
- double const *rowBelow = calTableIn.ptr() + X * yHi;
- double *out = calTableOut.ptr() + X * j;
- for (int i = 0; i < X; i++) {
- double above = rowAbove[xLo[i]] * (1 - xf[i]) +
- rowAbove[xHi[i]] * xf[i];
- double below = rowBelow[xLo[i]] * (1 - xf[i]) +
- rowBelow[xHi[i]] * xf[i];
- *(out++) = above * (1 - yf) + below * yf;
- }
- }
-}
-
-/* Calculate chrominance statistics (R/G and B/G) for each region. */
-static void calculateCrCb(const RgbyRegions &awbRegion, Array2D<double> &cr,
- Array2D<double> &cb, uint32_t minCount, uint16_t minG)
-{
- for (unsigned int i = 0; i < cr.size(); i++) {
- auto s = awbRegion.get(i);
-
- if (s.counted <= minCount || s.val.gSum / s.counted <= minG) {
- cr[i] = cb[i] = InsufficientData;
- continue;
- }
-
- cr[i] = s.val.rSum / (double)s.val.gSum;
- cb[i] = s.val.bSum / (double)s.val.gSum;
- }
-}
-
-static void applyCalTable(const Array2D<double> &calTable, Array2D<double> &C)
-{
- for (unsigned int i = 0; i < C.size(); i++)
- if (C[i] != InsufficientData)
- C[i] *= calTable[i];
-}
-
-void compensateLambdasForCal(const Array2D<double> &calTable,
- const Array2D<double> &oldLambdas,
- Array2D<double> &newLambdas)
-{
- double minNewLambda = std::numeric_limits<double>::max();
- for (unsigned int i = 0; i < newLambdas.size(); i++) {
- newLambdas[i] = oldLambdas[i] * calTable[i];
- minNewLambda = std::min(minNewLambda, newLambdas[i]);
- }
- for (unsigned int i = 0; i < newLambdas.size(); i++)
- newLambdas[i] /= minNewLambda;
-}
-
-[[maybe_unused]] static void printCalTable(const Array2D<double> &C)
-{
- const Size &size = C.dimensions();
- printf("table: [\n");
- for (unsigned int j = 0; j < size.height; j++) {
- for (unsigned int i = 0; i < size.width; i++) {
- printf("%5.3f", 1.0 / C[j * size.width + i]);
- if (i != size.width - 1 || j != size.height - 1)
- printf(",");
- }
- printf("\n");
- }
- printf("]\n");
-}
-
-/*
- * Compute weight out of 1.0 which reflects how similar we wish to make the
- * colours of these two regions.
- */
-static double computeWeight(double Ci, double Cj, double sigma)
-{
- if (Ci == InsufficientData || Cj == InsufficientData)
- return 0;
- double diff = (Ci - Cj) / sigma;
- return exp(-diff * diff / 2);
-}
-
-/* Compute all weights. */
-static void computeW(const Array2D<double> &C, double sigma,
- SparseArray<double> &W)
-{
- size_t XY = C.size();
- size_t X = C.dimensions().width;
-
- for (unsigned int i = 0; i < XY; i++) {
- /* Start with neighbour above and go clockwise. */
- W[i][0] = i >= X ? computeWeight(C[i], C[i - X], sigma) : 0;
- W[i][1] = i % X < X - 1 ? computeWeight(C[i], C[i + 1], sigma) : 0;
- W[i][2] = i < XY - X ? computeWeight(C[i], C[i + X], sigma) : 0;
- W[i][3] = i % X ? computeWeight(C[i], C[i - 1], sigma) : 0;
- }
-}
-
-/* Compute M, the large but sparse matrix such that M * lambdas = 0. */
-static void constructM(const Array2D<double> &C,
- const SparseArray<double> &W,
- SparseArray<double> &M)
-{
- size_t XY = C.size();
- size_t X = C.dimensions().width;
-
- double epsilon = 0.001;
- for (unsigned int i = 0; i < XY; i++) {
- /*
- * Note how, if C[i] == INSUFFICIENT_DATA, the weights will all
- * be zero so the equation is still set up correctly.
- */
- int m = !!(i >= X) + !!(i % X < X - 1) + !!(i < XY - X) +
- !!(i % X); /* total number of neighbours */
- /* we'll divide the diagonal out straight away */
- double diagonal = (epsilon + W[i][0] + W[i][1] + W[i][2] + W[i][3]) * C[i];
- M[i][0] = i >= X ? (W[i][0] * C[i - X] + epsilon / m * C[i]) / diagonal : 0;
- M[i][1] = i % X < X - 1 ? (W[i][1] * C[i + 1] + epsilon / m * C[i]) / diagonal : 0;
- M[i][2] = i < XY - X ? (W[i][2] * C[i + X] + epsilon / m * C[i]) / diagonal : 0;
- M[i][3] = i % X ? (W[i][3] * C[i - 1] + epsilon / m * C[i]) / diagonal : 0;
- }
-}
-
-/*
- * In the compute_lambda_ functions, note that the matrix coefficients for the
- * left/right neighbours are zero down the left/right edges, so we don't need
- * need to test the i value to exclude them.
- */
-static double computeLambdaBottom(int i, const SparseArray<double> &M,
- Array2D<double> &lambda)
-{
- return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + lambda.dimensions().width] +
- M[i][3] * lambda[i - 1];
-}
-static double computeLambdaBottomStart(int i, const SparseArray<double> &M,
- Array2D<double> &lambda)
-{
- return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + lambda.dimensions().width];
-}
-static double computeLambdaInterior(int i, const SparseArray<double> &M,
- Array2D<double> &lambda)
-{
- return M[i][0] * lambda[i - lambda.dimensions().width] + M[i][1] * lambda[i + 1] +
- M[i][2] * lambda[i + lambda.dimensions().width] + M[i][3] * lambda[i - 1];
-}
-static double computeLambdaTop(int i, const SparseArray<double> &M,
- Array2D<double> &lambda)
-{
- return M[i][0] * lambda[i - lambda.dimensions().width] + M[i][1] * lambda[i + 1] +
- M[i][3] * lambda[i - 1];
-}
-static double computeLambdaTopEnd(int i, const SparseArray<double> &M,
- Array2D<double> &lambda)
-{
- return M[i][0] * lambda[i - lambda.dimensions().width] + M[i][3] * lambda[i - 1];
-}
-
-/* Gauss-Seidel iteration with over-relaxation. */
-static double gaussSeidel2Sor(const SparseArray<double> &M, double omega,
- Array2D<double> &lambda, double lambdaBound)
-{
- int XY = lambda.size();
- int X = lambda.dimensions().width;
- const double min = 1 - lambdaBound, max = 1 + lambdaBound;
- Array2D<double> oldLambda = lambda;
- int i;
- lambda[0] = computeLambdaBottomStart(0, M, lambda);
- lambda[0] = std::clamp(lambda[0], min, max);
- for (i = 1; i < X; i++) {
- lambda[i] = computeLambdaBottom(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- for (; i < XY - X; i++) {
- lambda[i] = computeLambdaInterior(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- for (; i < XY - 1; i++) {
- lambda[i] = computeLambdaTop(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- lambda[i] = computeLambdaTopEnd(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- /*
- * Also solve the system from bottom to top, to help spread the updates
- * better.
- */
- lambda[i] = computeLambdaTopEnd(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- for (i = XY - 2; i >= XY - X; i--) {
- lambda[i] = computeLambdaTop(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- for (; i >= X; i--) {
- lambda[i] = computeLambdaInterior(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- for (; i >= 1; i--) {
- lambda[i] = computeLambdaBottom(i, M, lambda);
- lambda[i] = std::clamp(lambda[i], min, max);
- }
- lambda[0] = computeLambdaBottomStart(0, M, lambda);
- lambda[0] = std::clamp(lambda[0], min, max);
- double maxDiff = 0;
- for (i = 0; i < XY; i++) {
- lambda[i] = oldLambda[i] + (lambda[i] - oldLambda[i]) * omega;
- if (fabs(lambda[i] - oldLambda[i]) > fabs(maxDiff))
- maxDiff = lambda[i] - oldLambda[i];
- }
- return maxDiff;
-}
-
-/* Normalise the values so that the smallest value is 1. */
-static void normalise(Array2D<double> &results)
-{
- double minval = *std::min_element(results.begin(), results.end());
- std::for_each(results.begin(), results.end(),
- [minval](double val) { return val / minval; });
-}
-
-/* Rescale the values so that the average value is 1. */
-static void reaverage(Array2D<double> &data)
-{
- double sum = std::accumulate(data.begin(), data.end(), 0.0);
- double ratio = 1 / (sum / data.size());
- std::for_each(data.begin(), data.end(),
- [ratio](double val) { return val * ratio; });
-}
-
-static void runMatrixIterations(const Array2D<double> &C,
- Array2D<double> &lambda,
- const SparseArray<double> &W,
- SparseArray<double> &M, double omega,
- unsigned int nIter, double threshold, double lambdaBound)
-{
- constructM(C, W, M);
- double lastMaxDiff = std::numeric_limits<double>::max();
- for (unsigned int i = 0; i < nIter; i++) {
- double maxDiff = fabs(gaussSeidel2Sor(M, omega, lambda, lambdaBound));
- if (maxDiff < threshold) {
- LOG(RPiAlsc, Debug)
- << "Stop after " << i + 1 << " iterations";
- break;
- }
- /*
- * this happens very occasionally (so make a note), though
- * doesn't seem to matter
- */
- if (maxDiff > lastMaxDiff)
- LOG(RPiAlsc, Debug)
- << "Iteration " << i << ": maxDiff gone up "
- << lastMaxDiff << " to " << maxDiff;
- lastMaxDiff = maxDiff;
- }
- /* We're going to normalise the lambdas so the total average is 1. */
- reaverage(lambda);
-}
-
-static void addLuminanceRb(Array2D<double> &result, const Array2D<double> &lambda,
- const Array2D<double> &luminanceLut,
- double luminanceStrength)
-{
- for (unsigned int i = 0; i < result.size(); i++)
- result[i] = lambda[i] * ((luminanceLut[i] - 1) * luminanceStrength + 1);
-}
-
-static void addLuminanceG(Array2D<double> &result, double lambda,
- const Array2D<double> &luminanceLut,
- double luminanceStrength)
-{
- for (unsigned int i = 0; i < result.size(); i++)
- result[i] = lambda * ((luminanceLut[i] - 1) * luminanceStrength + 1);
-}
-
-void addLuminanceToTables(std::array<Array2D<double>, 3> &results,
- const Array2D<double> &lambdaR,
- double lambdaG, const Array2D<double> &lambdaB,
- const Array2D<double> &luminanceLut,
- double luminanceStrength)
-{
- addLuminanceRb(results[0], lambdaR, luminanceLut, luminanceStrength);
- addLuminanceG(results[1], lambdaG, luminanceLut, luminanceStrength);
- addLuminanceRb(results[2], lambdaB, luminanceLut, luminanceStrength);
- for (auto &r : results)
- normalise(r);
-}
-
-void Alsc::doAlsc()
-{
- Array2D<double> &cr = tmpC_[0], &cb = tmpC_[1], &calTableR = tmpC_[2],
- &calTableB = tmpC_[3], &calTableTmp = tmpC_[4];
- SparseArray<double> &wr = tmpM_[0], &wb = tmpM_[1], &M = tmpM_[2];
-
- /*
- * Calculate our R/B ("Cr"/"Cb") colour statistics, and assess which are
- * usable.
- */
- calculateCrCb(statistics_, cr, cb, config_.minCount, config_.minG);
- /*
- * Fetch the new calibrations (if any) for this CT. Resample them in
- * case the camera mode is not full-frame.
- */
- getCalTable(ct_, config_.calibrationsCr, calTableTmp);
- resampleCalTable(calTableTmp, cameraMode_, calTableR);
- getCalTable(ct_, config_.calibrationsCb, calTableTmp);
- resampleCalTable(calTableTmp, cameraMode_, calTableB);
- /*
- * You could print out the cal tables for this image here, if you're
- * tuning the algorithm...
- * Apply any calibration to the statistics, so the adaptive algorithm
- * makes only the extra adjustments.
- */
- applyCalTable(calTableR, cr);
- applyCalTable(calTableB, cb);
- /* Compute weights between zones. */
- computeW(cr, config_.sigmaCr, wr);
- computeW(cb, config_.sigmaCb, wb);
- /* Run Gauss-Seidel iterations over the resulting matrix, for R and B. */
- runMatrixIterations(cr, lambdaR_, wr, M, config_.omega, config_.nIter,
- config_.threshold, config_.lambdaBound);
- runMatrixIterations(cb, lambdaB_, wb, M, config_.omega, config_.nIter,
- config_.threshold, config_.lambdaBound);
- /*
- * Fold the calibrated gains into our final lambda values. (Note that on
- * the next run, we re-start with the lambda values that don't have the
- * calibration gains included.)
- */
- compensateLambdasForCal(calTableR, lambdaR_, asyncLambdaR_);
- compensateLambdasForCal(calTableB, lambdaB_, asyncLambdaB_);
- /* Fold in the luminance table at the appropriate strength. */
- addLuminanceToTables(asyncResults_, asyncLambdaR_, 1.0,
- asyncLambdaB_, luminanceTable_,
- config_.luminanceStrength);
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Alsc(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.h b/src/ipa/raspberrypi/controller/rpi/alsc.h
deleted file mode 100644
index 0b6d9478..00000000
--- a/src/ipa/raspberrypi/controller/rpi/alsc.h
+++ /dev/null
@@ -1,174 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * alsc.h - ALSC (auto lens shading correction) control algorithm
- */
-#pragma once
-
-#include <array>
-#include <mutex>
-#include <condition_variable>
-#include <thread>
-#include <vector>
-
-#include <libcamera/geometry.h>
-
-#include "../algorithm.h"
-#include "../alsc_status.h"
-#include "../statistics.h"
-
-namespace RPiController {
-
-/* Algorithm to generate automagic LSC (Lens Shading Correction) tables. */
-
-/*
- * The Array2D class is a very thin wrapper round std::vector so that it can
- * be used in exactly the same way in the code but carries its correct width
- * and height ("dimensions") with it.
- */
-
-template<typename T>
-class Array2D
-{
-public:
- using Size = libcamera::Size;
-
- const Size &dimensions() const { return dimensions_; }
-
- size_t size() const { return data_.size(); }
-
- const std::vector<T> &data() const { return data_; }
-
- void resize(const Size &dims)
- {
- dimensions_ = dims;
- data_.resize(dims.width * dims.height);
- }
-
- void resize(const Size &dims, const T &value)
- {
- resize(dims);
- std::fill(data_.begin(), data_.end(), value);
- }
-
- T &operator[](int index) { return data_[index]; }
-
- const T &operator[](int index) const { return data_[index]; }
-
- T *ptr() { return data_.data(); }
-
- const T *ptr() const { return data_.data(); }
-
- auto begin() { return data_.begin(); }
- auto end() { return data_.end(); }
-
-private:
- Size dimensions_;
- std::vector<T> data_;
-};
-
-/*
- * We'll use the term SparseArray for the large sparse matrices that are
- * XY tall but have only 4 non-zero elements on each row.
- */
-
-template<typename T>
-using SparseArray = std::vector<std::array<T, 4>>;
-
-struct AlscCalibration {
- double ct;
- Array2D<double> table;
-};
-
-struct AlscConfig {
- /* Only repeat the ALSC calculation every "this many" frames */
- uint16_t framePeriod;
- /* number of initial frames for which speed taken as 1.0 (maximum) */
- uint16_t startupFrames;
- /* IIR filter speed applied to algorithm results */
- double speed;
- double sigmaCr;
- double sigmaCb;
- double minCount;
- uint16_t minG;
- double omega;
- uint32_t nIter;
- Array2D<double> luminanceLut;
- double luminanceStrength;
- std::vector<AlscCalibration> calibrationsCr;
- std::vector<AlscCalibration> calibrationsCb;
- double defaultCt; /* colour temperature if no metadata found */
- double threshold; /* iteration termination threshold */
- double lambdaBound; /* upper/lower bound for lambda from a value of 1 */
- libcamera::Size tableSize;
-};
-
-class Alsc : public Algorithm
-{
-public:
- Alsc(Controller *controller = NULL);
- ~Alsc();
- char const *name() const override;
- void initialise() override;
- void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
- int read(const libcamera::YamlObject &params) override;
- void prepare(Metadata *imageMetadata) override;
- void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
-
-private:
- /* configuration is read-only, and available to both threads */
- AlscConfig config_;
- bool firstTime_;
- CameraMode cameraMode_;
- Array2D<double> luminanceTable_;
- std::thread asyncThread_;
- void asyncFunc(); /* asynchronous thread function */
- std::mutex mutex_;
- /* condvar for async thread to wait on */
- std::condition_variable asyncSignal_;
- /* condvar for synchronous thread to wait on */
- std::condition_variable syncSignal_;
- /* for sync thread to check if async thread finished (requires mutex) */
- bool asyncFinished_;
- /* for async thread to check if it's been told to run (requires mutex) */
- bool asyncStart_;
- /* for async thread to check if it's been told to quit (requires mutex) */
- bool asyncAbort_;
-
- /*
- * The following are only for the synchronous thread to use:
- * for sync thread to note its has asked async thread to run
- */
- bool asyncStarted_;
- /* counts up to framePeriod before restarting the async thread */
- int framePhase_;
- /* counts up to startupFrames */
- int frameCount_;
- /* counts up to startupFrames for Process function */
- int frameCount2_;
- std::array<Array2D<double>, 3> syncResults_;
- std::array<Array2D<double>, 3> prevSyncResults_;
- void waitForAysncThread();
- /*
- * The following are for the asynchronous thread to use, though the main
- * thread can set/reset them if the async thread is known to be idle:
- */
- void restartAsync(StatisticsPtr &stats, Metadata *imageMetadata);
- /* copy out the results from the async thread so that it can be restarted */
- void fetchAsyncResults();
- double ct_;
- RgbyRegions statistics_;
- std::array<Array2D<double>, 3> asyncResults_;
- Array2D<double> asyncLambdaR_;
- Array2D<double> asyncLambdaB_;
- void doAlsc();
- Array2D<double> lambdaR_;
- Array2D<double> lambdaB_;
-
- /* Temporaries for the computations */
- std::array<Array2D<double>, 5> tmpC_;
- std::array<SparseArray<double>, 3> tmpM_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/awb.cpp b/src/ipa/raspberrypi/controller/rpi/awb.cpp
deleted file mode 100644
index ef3435d6..00000000
--- a/src/ipa/raspberrypi/controller/rpi/awb.cpp
+++ /dev/null
@@ -1,734 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * awb.cpp - AWB control algorithm
- */
-
-#include <assert.h>
-#include <functional>
-
-#include <libcamera/base/log.h>
-
-#include "../lux_status.h"
-
-#include "awb.h"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiAwb)
-
-#define NAME "rpi.awb"
-
-/*
- * todo - the locking in this algorithm needs some tidying up as has been done
- * elsewhere (ALSC and AGC).
- */
-
-int AwbMode::read(const libcamera::YamlObject &params)
-{
- auto value = params["lo"].get<double>();
- if (!value)
- return -EINVAL;
- ctLo = *value;
-
- value = params["hi"].get<double>();
- if (!value)
- return -EINVAL;
- ctHi = *value;
-
- return 0;
-}
-
-int AwbPrior::read(const libcamera::YamlObject &params)
-{
- auto value = params["lux"].get<double>();
- if (!value)
- return -EINVAL;
- lux = *value;
-
- return prior.read(params["prior"]);
-}
-
-static int readCtCurve(Pwl &ctR, Pwl &ctB, const libcamera::YamlObject &params)
-{
- if (params.size() % 3) {
- LOG(RPiAwb, Error) << "AwbConfig: incomplete CT curve entry";
- return -EINVAL;
- }
-
- if (params.size() < 6) {
- LOG(RPiAwb, Error) << "AwbConfig: insufficient points in CT curve";
- return -EINVAL;
- }
-
- const auto &list = params.asList();
-
- for (auto it = list.begin(); it != list.end(); it++) {
- auto value = it->get<double>();
- if (!value)
- return -EINVAL;
- double ct = *value;
-
- assert(it == list.begin() || ct != ctR.domain().end);
-
- value = (++it)->get<double>();
- if (!value)
- return -EINVAL;
- ctR.append(ct, *value);
-
- value = (++it)->get<double>();
- if (!value)
- return -EINVAL;
- ctB.append(ct, *value);
- }
-
- return 0;
-}
-
-int AwbConfig::read(const libcamera::YamlObject &params)
-{
- int ret;
-
- bayes = params["bayes"].get<int>(1);
- framePeriod = params["frame_period"].get<uint16_t>(10);
- startupFrames = params["startup_frames"].get<uint16_t>(10);
- convergenceFrames = params["convergence_frames"].get<unsigned int>(3);
- speed = params["speed"].get<double>(0.05);
-
- if (params.contains("ct_curve")) {
- ret = readCtCurve(ctR, ctB, params["ct_curve"]);
- if (ret)
- return ret;
- /* We will want the inverse functions of these too. */
- ctRInverse = ctR.inverse();
- ctBInverse = ctB.inverse();
- }
-
- if (params.contains("priors")) {
- for (const auto &p : params["priors"].asList()) {
- AwbPrior prior;
- ret = prior.read(p);
- if (ret)
- return ret;
- if (!priors.empty() && prior.lux <= priors.back().lux) {
- LOG(RPiAwb, Error) << "AwbConfig: Prior must be ordered in increasing lux value";
- return -EINVAL;
- }
- priors.push_back(prior);
- }
- if (priors.empty()) {
- LOG(RPiAwb, Error) << "AwbConfig: no AWB priors configured";
- return ret;
- }
- }
- if (params.contains("modes")) {
- for (const auto &[key, value] : params["modes"].asDict()) {
- ret = modes[key].read(value);
- if (ret)
- return ret;
- if (defaultMode == nullptr)
- defaultMode = &modes[key];
- }
- if (defaultMode == nullptr) {
- LOG(RPiAwb, Error) << "AwbConfig: no AWB modes configured";
- return -EINVAL;
- }
- }
-
- minPixels = params["min_pixels"].get<double>(16.0);
- minG = params["min_G"].get<uint16_t>(32);
- minRegions = params["min_regions"].get<uint32_t>(10);
- deltaLimit = params["delta_limit"].get<double>(0.2);
- coarseStep = params["coarse_step"].get<double>(0.2);
- transversePos = params["transverse_pos"].get<double>(0.01);
- transverseNeg = params["transverse_neg"].get<double>(0.01);
- if (transversePos <= 0 || transverseNeg <= 0) {
- LOG(RPiAwb, Error) << "AwbConfig: transverse_pos/neg must be > 0";
- return -EINVAL;
- }
-
- sensitivityR = params["sensitivity_r"].get<double>(1.0);
- sensitivityB = params["sensitivity_b"].get<double>(1.0);
-
- if (bayes) {
- if (ctR.empty() || ctB.empty() || priors.empty() ||
- defaultMode == nullptr) {
- LOG(RPiAwb, Warning)
- << "Bayesian AWB mis-configured - switch to Grey method";
- bayes = false;
- }
- }
- fast = params[fast].get<int>(bayes); /* default to fast for Bayesian, otherwise slow */
- whitepointR = params["whitepoint_r"].get<double>(0.0);
- whitepointB = params["whitepoint_b"].get<double>(0.0);
- if (bayes == false)
- sensitivityR = sensitivityB = 1.0; /* nor do sensitivities make any sense */
- return 0;
-}
-
-Awb::Awb(Controller *controller)
- : AwbAlgorithm(controller)
-{
- asyncAbort_ = asyncStart_ = asyncStarted_ = asyncFinished_ = false;
- mode_ = nullptr;
- manualR_ = manualB_ = 0.0;
- asyncThread_ = std::thread(std::bind(&Awb::asyncFunc, this));
-}
-
-Awb::~Awb()
-{
- {
- std::lock_guard<std::mutex> lock(mutex_);
- asyncAbort_ = true;
- }
- asyncSignal_.notify_one();
- asyncThread_.join();
-}
-
-char const *Awb::name() const
-{
- return NAME;
-}
-
-int Awb::read(const libcamera::YamlObject &params)
-{
- return config_.read(params);
-}
-
-void Awb::initialise()
-{
- frameCount_ = framePhase_ = 0;
- /*
- * Put something sane into the status that we are filtering towards,
- * just in case the first few frames don't have anything meaningful in
- * them.
- */
- if (!config_.ctR.empty() && !config_.ctB.empty()) {
- syncResults_.temperatureK = config_.ctR.domain().clip(4000);
- syncResults_.gainR = 1.0 / config_.ctR.eval(syncResults_.temperatureK);
- syncResults_.gainG = 1.0;
- syncResults_.gainB = 1.0 / config_.ctB.eval(syncResults_.temperatureK);
- } else {
- /* random values just to stop the world blowing up */
- syncResults_.temperatureK = 4500;
- syncResults_.gainR = syncResults_.gainG = syncResults_.gainB = 1.0;
- }
- prevSyncResults_ = syncResults_;
- asyncResults_ = syncResults_;
-}
-
-void Awb::disableAuto()
-{
- /* Freeze the most recent values, and treat them as manual gains */
- manualR_ = syncResults_.gainR = prevSyncResults_.gainR;
- manualB_ = syncResults_.gainB = prevSyncResults_.gainB;
- syncResults_.gainG = prevSyncResults_.gainG;
- syncResults_.temperatureK = prevSyncResults_.temperatureK;
-}
-
-void Awb::enableAuto()
-{
- manualR_ = 0.0;
- manualB_ = 0.0;
-}
-
-unsigned int Awb::getConvergenceFrames() const
-{
- /*
- * If not in auto mode, there is no convergence
- * to happen, so no need to drop any frames - return zero.
- */
- if (!isAutoEnabled())
- return 0;
- else
- return config_.convergenceFrames;
-}
-
-void Awb::setMode(std::string const &modeName)
-{
- modeName_ = modeName;
-}
-
-void Awb::setManualGains(double manualR, double manualB)
-{
- /* If any of these are 0.0, we swich back to auto. */
- manualR_ = manualR;
- manualB_ = manualB;
- /*
- * If not in auto mode, set these values into the syncResults which
- * means that Prepare() will adopt them immediately.
- */
- if (!isAutoEnabled()) {
- syncResults_.gainR = prevSyncResults_.gainR = manualR_;
- syncResults_.gainG = prevSyncResults_.gainG = 1.0;
- syncResults_.gainB = prevSyncResults_.gainB = manualB_;
- if (config_.bayes) {
- /* Also estimate the best corresponding colour temperature from the curves. */
- double ctR = config_.ctRInverse.eval(config_.ctRInverse.domain().clip(1 / manualR_));
- double ctB = config_.ctBInverse.eval(config_.ctBInverse.domain().clip(1 / manualB_));
- prevSyncResults_.temperatureK = (ctR + ctB) / 2;
- syncResults_.temperatureK = prevSyncResults_.temperatureK;
- }
- }
-}
-
-void Awb::switchMode([[maybe_unused]] CameraMode const &cameraMode,
- Metadata *metadata)
-{
- /* Let other algorithms know the current white balance values. */
- metadata->set("awb.status", prevSyncResults_);
-}
-
-bool Awb::isAutoEnabled() const
-{
- return manualR_ == 0.0 || manualB_ == 0.0;
-}
-
-void Awb::fetchAsyncResults()
-{
- LOG(RPiAwb, Debug) << "Fetch AWB results";
- asyncFinished_ = false;
- asyncStarted_ = false;
- /*
- * It's possible manual gains could be set even while the async
- * thread was running, so only copy the results if still in auto mode.
- */
- if (isAutoEnabled())
- syncResults_ = asyncResults_;
-}
-
-void Awb::restartAsync(StatisticsPtr &stats, double lux)
-{
- LOG(RPiAwb, Debug) << "Starting AWB calculation";
- /* this makes a new reference which belongs to the asynchronous thread */
- statistics_ = stats;
- /* store the mode as it could technically change */
- auto m = config_.modes.find(modeName_);
- mode_ = m != config_.modes.end()
- ? &m->second
- : (mode_ == nullptr ? config_.defaultMode : mode_);
- lux_ = lux;
- framePhase_ = 0;
- asyncStarted_ = true;
- size_t len = modeName_.copy(asyncResults_.mode,
- sizeof(asyncResults_.mode) - 1);
- asyncResults_.mode[len] = '\0';
- {
- std::lock_guard<std::mutex> lock(mutex_);
- asyncStart_ = true;
- }
- asyncSignal_.notify_one();
-}
-
-void Awb::prepare(Metadata *imageMetadata)
-{
- if (frameCount_ < (int)config_.startupFrames)
- frameCount_++;
- double speed = frameCount_ < (int)config_.startupFrames
- ? 1.0
- : config_.speed;
- LOG(RPiAwb, Debug)
- << "frame_count " << frameCount_ << " speed " << speed;
- {
- std::unique_lock<std::mutex> lock(mutex_);
- if (asyncStarted_ && asyncFinished_)
- fetchAsyncResults();
- }
- /* Finally apply IIR filter to results and put into metadata. */
- memcpy(prevSyncResults_.mode, syncResults_.mode,
- sizeof(prevSyncResults_.mode));
- prevSyncResults_.temperatureK = speed * syncResults_.temperatureK +
- (1.0 - speed) * prevSyncResults_.temperatureK;
- prevSyncResults_.gainR = speed * syncResults_.gainR +
- (1.0 - speed) * prevSyncResults_.gainR;
- prevSyncResults_.gainG = speed * syncResults_.gainG +
- (1.0 - speed) * prevSyncResults_.gainG;
- prevSyncResults_.gainB = speed * syncResults_.gainB +
- (1.0 - speed) * prevSyncResults_.gainB;
- imageMetadata->set("awb.status", prevSyncResults_);
- LOG(RPiAwb, Debug)
- << "Using AWB gains r " << prevSyncResults_.gainR << " g "
- << prevSyncResults_.gainG << " b "
- << prevSyncResults_.gainB;
-}
-
-void Awb::process(StatisticsPtr &stats, Metadata *imageMetadata)
-{
- /* Count frames since we last poked the async thread. */
- if (framePhase_ < (int)config_.framePeriod)
- framePhase_++;
- LOG(RPiAwb, Debug) << "frame_phase " << framePhase_;
- /* We do not restart the async thread if we're not in auto mode. */
- if (isAutoEnabled() &&
- (framePhase_ >= (int)config_.framePeriod ||
- frameCount_ < (int)config_.startupFrames)) {
- /* Update any settings and any image metadata that we need. */
- struct LuxStatus luxStatus = {};
- luxStatus.lux = 400; /* in case no metadata */
- if (imageMetadata->get("lux.status", luxStatus) != 0)
- LOG(RPiAwb, Debug) << "No lux metadata found";
- LOG(RPiAwb, Debug) << "Awb lux value is " << luxStatus.lux;
-
- if (asyncStarted_ == false)
- restartAsync(stats, luxStatus.lux);
- }
-}
-
-void Awb::asyncFunc()
-{
- while (true) {
- {
- std::unique_lock<std::mutex> lock(mutex_);
- asyncSignal_.wait(lock, [&] {
- return asyncStart_ || asyncAbort_;
- });
- asyncStart_ = false;
- if (asyncAbort_)
- break;
- }
- doAwb();
- {
- std::lock_guard<std::mutex> lock(mutex_);
- asyncFinished_ = true;
- }
- syncSignal_.notify_one();
- }
-}
-
-static void generateStats(std::vector<Awb::RGB> &zones,
- RgbyRegions &stats, double minPixels,
- double minG)
-{
- for (auto const &region : stats) {
- Awb::RGB zone;
- if (region.counted >= minPixels) {
- zone.G = region.val.gSum / region.counted;
- if (zone.G >= minG) {
- zone.R = region.val.rSum / region.counted;
- zone.B = region.val.bSum / region.counted;
- zones.push_back(zone);
- }
- }
- }
-}
-
-void Awb::prepareStats()
-{
- zones_.clear();
- /*
- * LSC has already been applied to the stats in this pipeline, so stop
- * any LSC compensation. We also ignore config_.fast in this version.
- */
- generateStats(zones_, statistics_->awbRegions, config_.minPixels,
- config_.minG);
- /*
- * apply sensitivities, so values appear to come from our "canonical"
- * sensor.
- */
- for (auto &zone : zones_) {
- zone.R *= config_.sensitivityR;
- zone.B *= config_.sensitivityB;
- }
-}
-
-double Awb::computeDelta2Sum(double gainR, double gainB)
-{
- /*
- * Compute the sum of the squared colour error (non-greyness) as it
- * appears in the log likelihood equation.
- */
- double delta2Sum = 0;
- for (auto &z : zones_) {
- double deltaR = gainR * z.R - 1 - config_.whitepointR;
- double deltaB = gainB * z.B - 1 - config_.whitepointB;
- double delta2 = deltaR * deltaR + deltaB * deltaB;
- /* LOG(RPiAwb, Debug) << "deltaR " << deltaR << " deltaB " << deltaB << " delta2 " << delta2; */
- delta2 = std::min(delta2, config_.deltaLimit);
- delta2Sum += delta2;
- }
- return delta2Sum;
-}
-
-Pwl Awb::interpolatePrior()
-{
- /*
- * Interpolate the prior log likelihood function for our current lux
- * value.
- */
- if (lux_ <= config_.priors.front().lux)
- return config_.priors.front().prior;
- else if (lux_ >= config_.priors.back().lux)
- return config_.priors.back().prior;
- else {
- int idx = 0;
- /* find which two we lie between */
- while (config_.priors[idx + 1].lux < lux_)
- idx++;
- double lux0 = config_.priors[idx].lux,
- lux1 = config_.priors[idx + 1].lux;
- return Pwl::combine(config_.priors[idx].prior,
- config_.priors[idx + 1].prior,
- [&](double /*x*/, double y0, double y1) {
- return y0 + (y1 - y0) *
- (lux_ - lux0) / (lux1 - lux0);
- });
- }
-}
-
-static double interpolateQuadatric(Pwl::Point const &a, Pwl::Point const &b,
- Pwl::Point const &c)
-{
- /*
- * Given 3 points on a curve, find the extremum of the function in that
- * interval by fitting a quadratic.
- */
- const double eps = 1e-3;
- Pwl::Point ca = c - a, ba = b - a;
- double denominator = 2 * (ba.y * ca.x - ca.y * ba.x);
- if (abs(denominator) > eps) {
- double numerator = ba.y * ca.x * ca.x - ca.y * ba.x * ba.x;
- double result = numerator / denominator + a.x;
- return std::max(a.x, std::min(c.x, result));
- }
- /* has degenerated to straight line segment */
- return a.y < c.y - eps ? a.x : (c.y < a.y - eps ? c.x : b.x);
-}
-
-double Awb::coarseSearch(Pwl const &prior)
-{
- points_.clear(); /* assume doesn't deallocate memory */
- size_t bestPoint = 0;
- double t = mode_->ctLo;
- int spanR = 0, spanB = 0;
- /* Step down the CT curve evaluating log likelihood. */
- while (true) {
- double r = config_.ctR.eval(t, &spanR);
- double b = config_.ctB.eval(t, &spanB);
- double gainR = 1 / r, gainB = 1 / b;
- double delta2Sum = computeDelta2Sum(gainR, gainB);
- double priorLogLikelihood = prior.eval(prior.domain().clip(t));
- double finalLogLikelihood = delta2Sum - priorLogLikelihood;
- LOG(RPiAwb, Debug)
- << "t: " << t << " gain R " << gainR << " gain B "
- << gainB << " delta2_sum " << delta2Sum
- << " prior " << priorLogLikelihood << " final "
- << finalLogLikelihood;
- points_.push_back(Pwl::Point(t, finalLogLikelihood));
- if (points_.back().y < points_[bestPoint].y)
- bestPoint = points_.size() - 1;
- if (t == mode_->ctHi)
- break;
- /* for even steps along the r/b curve scale them by the current t */
- t = std::min(t + t / 10 * config_.coarseStep, mode_->ctHi);
- }
- t = points_[bestPoint].x;
- LOG(RPiAwb, Debug) << "Coarse search found CT " << t;
- /*
- * We have the best point of the search, but refine it with a quadratic
- * interpolation around its neighbours.
- */
- if (points_.size() > 2) {
- unsigned long bp = std::min(bestPoint, points_.size() - 2);
- bestPoint = std::max(1UL, bp);
- t = interpolateQuadatric(points_[bestPoint - 1],
- points_[bestPoint],
- points_[bestPoint + 1]);
- LOG(RPiAwb, Debug)
- << "After quadratic refinement, coarse search has CT "
- << t;
- }
- return t;
-}
-
-void Awb::fineSearch(double &t, double &r, double &b, Pwl const &prior)
-{
- int spanR = -1, spanB = -1;
- config_.ctR.eval(t, &spanR);
- config_.ctB.eval(t, &spanB);
- double step = t / 10 * config_.coarseStep * 0.1;
- int nsteps = 5;
- double rDiff = config_.ctR.eval(t + nsteps * step, &spanR) -
- config_.ctR.eval(t - nsteps * step, &spanR);
- double bDiff = config_.ctB.eval(t + nsteps * step, &spanB) -
- config_.ctB.eval(t - nsteps * step, &spanB);
- Pwl::Point transverse(bDiff, -rDiff);
- if (transverse.len2() < 1e-6)
- return;
- /*
- * unit vector orthogonal to the b vs. r function (pointing outwards
- * with r and b increasing)
- */
- transverse = transverse / transverse.len();
- double bestLogLikelihood = 0, bestT = 0, bestR = 0, bestB = 0;
- double transverseRange = config_.transverseNeg + config_.transversePos;
- const int maxNumDeltas = 12;
- /* a transverse step approximately every 0.01 r/b units */
- int numDeltas = floor(transverseRange * 100 + 0.5) + 1;
- numDeltas = numDeltas < 3 ? 3 : (numDeltas > maxNumDeltas ? maxNumDeltas : numDeltas);
- /*
- * Step down CT curve. March a bit further if the transverse range is
- * large.
- */
- nsteps += numDeltas;
- for (int i = -nsteps; i <= nsteps; i++) {
- double tTest = t + i * step;
- double priorLogLikelihood =
- prior.eval(prior.domain().clip(tTest));
- double rCurve = config_.ctR.eval(tTest, &spanR);
- double bCurve = config_.ctB.eval(tTest, &spanB);
- /* x will be distance off the curve, y the log likelihood there */
- Pwl::Point points[maxNumDeltas];
- int bestPoint = 0;
- /* Take some measurements transversely *off* the CT curve. */
- for (int j = 0; j < numDeltas; j++) {
- points[j].x = -config_.transverseNeg +
- (transverseRange * j) / (numDeltas - 1);
- Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) +
- transverse * points[j].x;
- double rTest = rbTest.x, bTest = rbTest.y;
- double gainR = 1 / rTest, gainB = 1 / bTest;
- double delta2Sum = computeDelta2Sum(gainR, gainB);
- points[j].y = delta2Sum - priorLogLikelihood;
- LOG(RPiAwb, Debug)
- << "At t " << tTest << " r " << rTest << " b "
- << bTest << ": " << points[j].y;
- if (points[j].y < points[bestPoint].y)
- bestPoint = j;
- }
- /*
- * We have NUM_DELTAS points transversely across the CT curve,
- * now let's do a quadratic interpolation for the best result.
- */
- bestPoint = std::max(1, std::min(bestPoint, numDeltas - 2));
- Pwl::Point rbTest = Pwl::Point(rCurve, bCurve) +
- transverse * interpolateQuadatric(points[bestPoint - 1],
- points[bestPoint],
- points[bestPoint + 1]);
- double rTest = rbTest.x, bTest = rbTest.y;
- double gainR = 1 / rTest, gainB = 1 / bTest;
- double delta2Sum = computeDelta2Sum(gainR, gainB);
- double finalLogLikelihood = delta2Sum - priorLogLikelihood;
- LOG(RPiAwb, Debug)
- << "Finally "
- << tTest << " r " << rTest << " b " << bTest << ": "
- << finalLogLikelihood
- << (finalLogLikelihood < bestLogLikelihood ? " BEST" : "");
- if (bestT == 0 || finalLogLikelihood < bestLogLikelihood)
- bestLogLikelihood = finalLogLikelihood,
- bestT = tTest, bestR = rTest, bestB = bTest;
- }
- t = bestT, r = bestR, b = bestB;
- LOG(RPiAwb, Debug)
- << "Fine search found t " << t << " r " << r << " b " << b;
-}
-
-void Awb::awbBayes()
-{
- /*
- * May as well divide out G to save computeDelta2Sum from doing it over
- * and over.
- */
- for (auto &z : zones_)
- z.R = z.R / (z.G + 1), z.B = z.B / (z.G + 1);
- /*
- * Get the current prior, and scale according to how many zones are
- * valid... not entirely sure about this.
- */
- Pwl prior = interpolatePrior();
- prior *= zones_.size() / (double)(statistics_->awbRegions.numRegions());
- prior.map([](double x, double y) {
- LOG(RPiAwb, Debug) << "(" << x << "," << y << ")";
- });
- double t = coarseSearch(prior);
- double r = config_.ctR.eval(t);
- double b = config_.ctB.eval(t);
- LOG(RPiAwb, Debug)
- << "After coarse search: r " << r << " b " << b << " (gains r "
- << 1 / r << " b " << 1 / b << ")";
- /*
- * Not entirely sure how to handle the fine search yet. Mostly the
- * estimated CT is already good enough, but the fine search allows us to
- * wander transverely off the CT curve. Under some illuminants, where
- * there may be more or less green light, this may prove beneficial,
- * though I probably need more real datasets before deciding exactly how
- * this should be controlled and tuned.
- */
- fineSearch(t, r, b, prior);
- LOG(RPiAwb, Debug)
- << "After fine search: r " << r << " b " << b << " (gains r "
- << 1 / r << " b " << 1 / b << ")";
- /*
- * Write results out for the main thread to pick up. Remember to adjust
- * the gains from the ones that the "canonical sensor" would require to
- * the ones needed by *this* sensor.
- */
- asyncResults_.temperatureK = t;
- asyncResults_.gainR = 1.0 / r * config_.sensitivityR;
- asyncResults_.gainG = 1.0;
- asyncResults_.gainB = 1.0 / b * config_.sensitivityB;
-}
-
-void Awb::awbGrey()
-{
- LOG(RPiAwb, Debug) << "Grey world AWB";
- /*
- * Make a separate list of the derivatives for each of red and blue, so
- * that we can sort them to exclude the extreme gains. We could
- * consider some variations, such as normalising all the zones first, or
- * doing an L2 average etc.
- */
- std::vector<RGB> &derivsR(zones_);
- std::vector<RGB> derivsB(derivsR);
- std::sort(derivsR.begin(), derivsR.end(),
- [](RGB const &a, RGB const &b) {
- return a.G * b.R < b.G * a.R;
- });
- std::sort(derivsB.begin(), derivsB.end(),
- [](RGB const &a, RGB const &b) {
- return a.G * b.B < b.G * a.B;
- });
- /* Average the middle half of the values. */
- int discard = derivsR.size() / 4;
- RGB sumR(0, 0, 0), sumB(0, 0, 0);
- for (auto ri = derivsR.begin() + discard,
- bi = derivsB.begin() + discard;
- ri != derivsR.end() - discard; ri++, bi++)
- sumR += *ri, sumB += *bi;
- double gainR = sumR.G / (sumR.R + 1),
- gainB = sumB.G / (sumB.B + 1);
- asyncResults_.temperatureK = 4500; /* don't know what it is */
- asyncResults_.gainR = gainR;
- asyncResults_.gainG = 1.0;
- asyncResults_.gainB = gainB;
-}
-
-void Awb::doAwb()
-{
- prepareStats();
- LOG(RPiAwb, Debug) << "Valid zones: " << zones_.size();
- if (zones_.size() > config_.minRegions) {
- if (config_.bayes)
- awbBayes();
- else
- awbGrey();
- LOG(RPiAwb, Debug)
- << "CT found is "
- << asyncResults_.temperatureK
- << " with gains r " << asyncResults_.gainR
- << " and b " << asyncResults_.gainB;
- }
- /*
- * we're done with these; we may as well relinquish our hold on the
- * pointer.
- */
- statistics_.reset();
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Awb(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/awb.h b/src/ipa/raspberrypi/controller/rpi/awb.h
deleted file mode 100644
index e7d49cd8..00000000
--- a/src/ipa/raspberrypi/controller/rpi/awb.h
+++ /dev/null
@@ -1,191 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * awb.h - AWB control algorithm
- */
-#pragma once
-
-#include <mutex>
-#include <condition_variable>
-#include <thread>
-
-#include "../awb_algorithm.h"
-#include "../pwl.h"
-#include "../awb_status.h"
-#include "../statistics.h"
-
-namespace RPiController {
-
-/* Control algorithm to perform AWB calculations. */
-
-struct AwbMode {
- int read(const libcamera::YamlObject &params);
- double ctLo; /* low CT value for search */
- double ctHi; /* high CT value for search */
-};
-
-struct AwbPrior {
- int read(const libcamera::YamlObject &params);
- double lux; /* lux level */
- Pwl prior; /* maps CT to prior log likelihood for this lux level */
-};
-
-struct AwbConfig {
- AwbConfig() : defaultMode(nullptr) {}
- int read(const libcamera::YamlObject &params);
- /* Only repeat the AWB calculation every "this many" frames */
- uint16_t framePeriod;
- /* number of initial frames for which speed taken as 1.0 (maximum) */
- uint16_t startupFrames;
- unsigned int convergenceFrames; /* approx number of frames to converge */
- double speed; /* IIR filter speed applied to algorithm results */
- bool fast; /* "fast" mode uses a 16x16 rather than 32x32 grid */
- Pwl ctR; /* function maps CT to r (= R/G) */
- Pwl ctB; /* function maps CT to b (= B/G) */
- Pwl ctRInverse; /* inverse of ctR */
- Pwl ctBInverse; /* inverse of ctB */
- /* table of illuminant priors at different lux levels */
- std::vector<AwbPrior> priors;
- /* AWB "modes" (determines the search range) */
- std::map<std::string, AwbMode> modes;
- AwbMode *defaultMode; /* mode used if no mode selected */
- /*
- * minimum proportion of pixels counted within AWB region for it to be
- * "useful"
- */
- double minPixels;
- /* minimum G value of those pixels, to be regarded a "useful" */
- uint16_t minG;
- /*
- * number of AWB regions that must be "useful" in order to do the AWB
- * calculation
- */
- uint32_t minRegions;
- /* clamp on colour error term (so as not to penalise non-grey excessively) */
- double deltaLimit;
- /* step size control in coarse search */
- double coarseStep;
- /* how far to wander off CT curve towards "more purple" */
- double transversePos;
- /* how far to wander off CT curve towards "more green" */
- double transverseNeg;
- /*
- * red sensitivity ratio (set to canonical sensor's R/G divided by this
- * sensor's R/G)
- */
- double sensitivityR;
- /*
- * blue sensitivity ratio (set to canonical sensor's B/G divided by this
- * sensor's B/G)
- */
- double sensitivityB;
- /* The whitepoint (which we normally "aim" for) can be moved. */
- double whitepointR;
- double whitepointB;
- bool bayes; /* use Bayesian algorithm */
-};
-
-class Awb : public AwbAlgorithm
-{
-public:
- Awb(Controller *controller = NULL);
- ~Awb();
- char const *name() const override;
- void initialise() override;
- int read(const libcamera::YamlObject &params) override;
- unsigned int getConvergenceFrames() const override;
- void setMode(std::string const &name) override;
- void setManualGains(double manualR, double manualB) override;
- void enableAuto() override;
- void disableAuto() override;
- void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
- void prepare(Metadata *imageMetadata) override;
- void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
- struct RGB {
- RGB(double r = 0, double g = 0, double b = 0)
- : R(r), G(g), B(b)
- {
- }
- double R, G, B;
- RGB &operator+=(RGB const &other)
- {
- R += other.R, G += other.G, B += other.B;
- return *this;
- }
- };
-
-private:
- bool isAutoEnabled() const;
- /* configuration is read-only, and available to both threads */
- AwbConfig config_;
- std::thread asyncThread_;
- void asyncFunc(); /* asynchronous thread function */
- std::mutex mutex_;
- /* condvar for async thread to wait on */
- std::condition_variable asyncSignal_;
- /* condvar for synchronous thread to wait on */
- std::condition_variable syncSignal_;
- /* for sync thread to check if async thread finished (requires mutex) */
- bool asyncFinished_;
- /* for async thread to check if it's been told to run (requires mutex) */
- bool asyncStart_;
- /* for async thread to check if it's been told to quit (requires mutex) */
- bool asyncAbort_;
-
- /*
- * The following are only for the synchronous thread to use:
- * for sync thread to note its has asked async thread to run
- */
- bool asyncStarted_;
- /* counts up to framePeriod before restarting the async thread */
- int framePhase_;
- int frameCount_; /* counts up to startup_frames */
- AwbStatus syncResults_;
- AwbStatus prevSyncResults_;
- std::string modeName_;
- /*
- * The following are for the asynchronous thread to use, though the main
- * thread can set/reset them if the async thread is known to be idle:
- */
- void restartAsync(StatisticsPtr &stats, double lux);
- /* copy out the results from the async thread so that it can be restarted */
- void fetchAsyncResults();
- StatisticsPtr statistics_;
- AwbMode *mode_;
- double lux_;
- AwbStatus asyncResults_;
- void doAwb();
- void awbBayes();
- void awbGrey();
- void prepareStats();
- double computeDelta2Sum(double gainR, double gainB);
- Pwl interpolatePrior();
- double coarseSearch(Pwl const &prior);
- void fineSearch(double &t, double &r, double &b, Pwl const &prior);
- std::vector<RGB> zones_;
- std::vector<Pwl::Point> points_;
- /* manual r setting */
- double manualR_;
- /* manual b setting */
- double manualB_;
-};
-
-static inline Awb::RGB operator+(Awb::RGB const &a, Awb::RGB const &b)
-{
- return Awb::RGB(a.R + b.R, a.G + b.G, a.B + b.B);
-}
-static inline Awb::RGB operator-(Awb::RGB const &a, Awb::RGB const &b)
-{
- return Awb::RGB(a.R - b.R, a.G - b.G, a.B - b.B);
-}
-static inline Awb::RGB operator*(double d, Awb::RGB const &rgb)
-{
- return Awb::RGB(d * rgb.R, d * rgb.G, d * rgb.B);
-}
-static inline Awb::RGB operator*(Awb::RGB const &rgb, double d)
-{
- return d * rgb;
-}
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/black_level.cpp b/src/ipa/raspberrypi/controller/rpi/black_level.cpp
deleted file mode 100644
index 85baec3f..00000000
--- a/src/ipa/raspberrypi/controller/rpi/black_level.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * black_level.cpp - black level control algorithm
- */
-
-#include <math.h>
-#include <stdint.h>
-
-#include <libcamera/base/log.h>
-
-#include "../black_level_status.h"
-
-#include "black_level.h"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiBlackLevel)
-
-#define NAME "rpi.black_level"
-
-BlackLevel::BlackLevel(Controller *controller)
- : Algorithm(controller)
-{
-}
-
-char const *BlackLevel::name() const
-{
- return NAME;
-}
-
-int BlackLevel::read(const libcamera::YamlObject &params)
-{
- /* 64 in 10 bits scaled to 16 bits */
- uint16_t blackLevel = params["black_level"].get<uint16_t>(4096);
- blackLevelR_ = params["black_level_r"].get<uint16_t>(blackLevel);
- blackLevelG_ = params["black_level_g"].get<uint16_t>(blackLevel);
- blackLevelB_ = params["black_level_b"].get<uint16_t>(blackLevel);
- LOG(RPiBlackLevel, Debug)
- << " Read black levels red " << blackLevelR_
- << " green " << blackLevelG_
- << " blue " << blackLevelB_;
- return 0;
-}
-
-void BlackLevel::prepare(Metadata *imageMetadata)
-{
- /*
- * Possibly we should think about doing this in a switchMode or
- * something?
- */
- struct BlackLevelStatus status;
- status.blackLevelR = blackLevelR_;
- status.blackLevelG = blackLevelG_;
- status.blackLevelB = blackLevelB_;
- imageMetadata->set("black_level.status", status);
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return new BlackLevel(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/black_level.h b/src/ipa/raspberrypi/controller/rpi/black_level.h
deleted file mode 100644
index 2403f7f7..00000000
--- a/src/ipa/raspberrypi/controller/rpi/black_level.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * black_level.h - black level control algorithm
- */
-#pragma once
-
-#include "../algorithm.h"
-#include "../black_level_status.h"
-
-/* This is our implementation of the "black level algorithm". */
-
-namespace RPiController {
-
-class BlackLevel : public Algorithm
-{
-public:
- BlackLevel(Controller *controller);
- char const *name() const override;
- int read(const libcamera::YamlObject &params) override;
- void prepare(Metadata *imageMetadata) override;
-
-private:
- double blackLevelR_;
- double blackLevelG_;
- double blackLevelB_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/ccm.cpp b/src/ipa/raspberrypi/controller/rpi/ccm.cpp
deleted file mode 100644
index 2e2e6664..00000000
--- a/src/ipa/raspberrypi/controller/rpi/ccm.cpp
+++ /dev/null
@@ -1,199 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * ccm.cpp - CCM (colour correction matrix) control algorithm
- */
-
-#include <libcamera/base/log.h>
-
-#include "../awb_status.h"
-#include "../ccm_status.h"
-#include "../lux_status.h"
-#include "../metadata.h"
-
-#include "ccm.h"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiCcm)
-
-/*
- * This algorithm selects a CCM (Colour Correction Matrix) according to the
- * colour temperature estimated by AWB (interpolating between known matricies as
- * necessary). Additionally the amount of colour saturation can be controlled
- * both according to the current estimated lux level and according to a
- * saturation setting that is exposed to applications.
- */
-
-#define NAME "rpi.ccm"
-
-Matrix::Matrix()
-{
- memset(m, 0, sizeof(m));
-}
-Matrix::Matrix(double m0, double m1, double m2, double m3, double m4, double m5,
- double m6, double m7, double m8)
-{
- m[0][0] = m0, m[0][1] = m1, m[0][2] = m2, m[1][0] = m3, m[1][1] = m4,
- m[1][2] = m5, m[2][0] = m6, m[2][1] = m7, m[2][2] = m8;
-}
-int Matrix::read(const libcamera::YamlObject &params)
-{
- double *ptr = (double *)m;
-
- if (params.size() != 9) {
- LOG(RPiCcm, Error) << "Wrong number of values in CCM";
- return -EINVAL;
- }
-
- for (const auto &param : params.asList()) {
- auto value = param.get<double>();
- if (!value)
- return -EINVAL;
- *ptr++ = *value;
- }
-
- return 0;
-}
-
-Ccm::Ccm(Controller *controller)
- : CcmAlgorithm(controller), saturation_(1.0) {}
-
-char const *Ccm::name() const
-{
- return NAME;
-}
-
-int Ccm::read(const libcamera::YamlObject &params)
-{
- int ret;
-
- if (params.contains("saturation")) {
- ret = config_.saturation.read(params["saturation"]);
- if (ret)
- return ret;
- }
-
- for (auto &p : params["ccms"].asList()) {
- auto value = p["ct"].get<double>();
- if (!value)
- return -EINVAL;
-
- CtCcm ctCcm;
- ctCcm.ct = *value;
- ret = ctCcm.ccm.read(p["ccm"]);
- if (ret)
- return ret;
-
- if (!config_.ccms.empty() && ctCcm.ct <= config_.ccms.back().ct) {
- LOG(RPiCcm, Error)
- << "CCM not in increasing colour temperature order";
- return -EINVAL;
- }
-
- config_.ccms.push_back(std::move(ctCcm));
- }
-
- if (config_.ccms.empty()) {
- LOG(RPiCcm, Error) << "No CCMs specified";
- return -EINVAL;
- }
-
- return 0;
-}
-
-void Ccm::setSaturation(double saturation)
-{
- saturation_ = saturation;
-}
-
-void Ccm::initialise()
-{
-}
-
-template<typename T>
-static bool getLocked(Metadata *metadata, std::string const &tag, T &value)
-{
- T *ptr = metadata->getLocked<T>(tag);
- if (ptr == nullptr)
- return false;
- value = *ptr;
- return true;
-}
-
-Matrix calculateCcm(std::vector<CtCcm> const &ccms, double ct)
-{
- if (ct <= ccms.front().ct)
- return ccms.front().ccm;
- else if (ct >= ccms.back().ct)
- return ccms.back().ccm;
- else {
- int i = 0;
- for (; ct > ccms[i].ct; i++)
- ;
- double lambda =
- (ct - ccms[i - 1].ct) / (ccms[i].ct - ccms[i - 1].ct);
- return lambda * ccms[i].ccm + (1.0 - lambda) * ccms[i - 1].ccm;
- }
-}
-
-Matrix applySaturation(Matrix const &ccm, double saturation)
-{
- Matrix RGB2Y(0.299, 0.587, 0.114, -0.169, -0.331, 0.500, 0.500, -0.419,
- -0.081);
- Matrix Y2RGB(1.000, 0.000, 1.402, 1.000, -0.345, -0.714, 1.000, 1.771,
- 0.000);
- Matrix S(1, 0, 0, 0, saturation, 0, 0, 0, saturation);
- return Y2RGB * S * RGB2Y * ccm;
-}
-
-void Ccm::prepare(Metadata *imageMetadata)
-{
- bool awbOk = false, luxOk = false;
- struct AwbStatus awb = {};
- awb.temperatureK = 4000; /* in case no metadata */
- struct LuxStatus lux = {};
- lux.lux = 400; /* in case no metadata */
- {
- /* grab mutex just once to get everything */
- std::lock_guard<Metadata> lock(*imageMetadata);
- awbOk = getLocked(imageMetadata, "awb.status", awb);
- luxOk = getLocked(imageMetadata, "lux.status", lux);
- }
- if (!awbOk)
- LOG(RPiCcm, Warning) << "no colour temperature found";
- if (!luxOk)
- LOG(RPiCcm, Warning) << "no lux value found";
- Matrix ccm = calculateCcm(config_.ccms, awb.temperatureK);
- double saturation = saturation_;
- struct CcmStatus ccmStatus;
- ccmStatus.saturation = saturation;
- if (!config_.saturation.empty())
- saturation *= config_.saturation.eval(
- config_.saturation.domain().clip(lux.lux));
- ccm = applySaturation(ccm, saturation);
- for (int j = 0; j < 3; j++)
- for (int i = 0; i < 3; i++)
- ccmStatus.matrix[j * 3 + i] =
- std::max(-8.0, std::min(7.9999, ccm.m[j][i]));
- LOG(RPiCcm, Debug)
- << "colour temperature " << awb.temperatureK << "K";
- LOG(RPiCcm, Debug)
- << "CCM: " << ccmStatus.matrix[0] << " " << ccmStatus.matrix[1]
- << " " << ccmStatus.matrix[2] << " "
- << ccmStatus.matrix[3] << " " << ccmStatus.matrix[4]
- << " " << ccmStatus.matrix[5] << " "
- << ccmStatus.matrix[6] << " " << ccmStatus.matrix[7]
- << " " << ccmStatus.matrix[8];
- imageMetadata->set("ccm.status", ccmStatus);
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Ccm(controller);
- ;
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/ccm.h b/src/ipa/raspberrypi/controller/rpi/ccm.h
deleted file mode 100644
index 286d0b33..00000000
--- a/src/ipa/raspberrypi/controller/rpi/ccm.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * ccm.h - CCM (colour correction matrix) control algorithm
- */
-#pragma once
-
-#include <vector>
-
-#include "../ccm_algorithm.h"
-#include "../pwl.h"
-
-namespace RPiController {
-
-/* Algorithm to calculate colour matrix. Should be placed after AWB. */
-
-struct Matrix {
- Matrix(double m0, double m1, double m2, double m3, double m4, double m5,
- double m6, double m7, double m8);
- Matrix();
- double m[3][3];
- int read(const libcamera::YamlObject &params);
-};
-static inline Matrix operator*(double d, Matrix const &m)
-{
- return Matrix(m.m[0][0] * d, m.m[0][1] * d, m.m[0][2] * d,
- m.m[1][0] * d, m.m[1][1] * d, m.m[1][2] * d,
- m.m[2][0] * d, m.m[2][1] * d, m.m[2][2] * d);
-}
-static inline Matrix operator*(Matrix const &m1, Matrix const &m2)
-{
- Matrix m;
- for (int i = 0; i < 3; i++)
- for (int j = 0; j < 3; j++)
- m.m[i][j] = m1.m[i][0] * m2.m[0][j] +
- m1.m[i][1] * m2.m[1][j] +
- m1.m[i][2] * m2.m[2][j];
- return m;
-}
-static inline Matrix operator+(Matrix const &m1, Matrix const &m2)
-{
- Matrix m;
- for (int i = 0; i < 3; i++)
- for (int j = 0; j < 3; j++)
- m.m[i][j] = m1.m[i][j] + m2.m[i][j];
- return m;
-}
-
-struct CtCcm {
- double ct;
- Matrix ccm;
-};
-
-struct CcmConfig {
- std::vector<CtCcm> ccms;
- Pwl saturation;
-};
-
-class Ccm : public CcmAlgorithm
-{
-public:
- Ccm(Controller *controller = NULL);
- char const *name() const override;
- int read(const libcamera::YamlObject &params) override;
- void setSaturation(double saturation) override;
- void initialise() override;
- void prepare(Metadata *imageMetadata) override;
-
-private:
- CcmConfig config_;
- double saturation_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/contrast.cpp b/src/ipa/raspberrypi/controller/rpi/contrast.cpp
deleted file mode 100644
index bee1eadd..00000000
--- a/src/ipa/raspberrypi/controller/rpi/contrast.cpp
+++ /dev/null
@@ -1,181 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * contrast.cpp - contrast (gamma) control algorithm
- */
-#include <stdint.h>
-
-#include <libcamera/base/log.h>
-
-#include "../contrast_status.h"
-#include "../histogram.h"
-
-#include "contrast.h"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiContrast)
-
-/*
- * This is a very simple control algorithm which simply retrieves the results of
- * AGC and AWB via their "status" metadata, and applies digital gain to the
- * colour channels in accordance with those instructions. We take care never to
- * apply less than unity gains, as that would cause fully saturated pixels to go
- * off-white.
- */
-
-#define NAME "rpi.contrast"
-
-Contrast::Contrast(Controller *controller)
- : ContrastAlgorithm(controller), brightness_(0.0), contrast_(1.0)
-{
-}
-
-char const *Contrast::name() const
-{
- return NAME;
-}
-
-int Contrast::read(const libcamera::YamlObject &params)
-{
- // enable adaptive enhancement by default
- config_.ceEnable = params["ce_enable"].get<int>(1);
- // the point near the bottom of the histogram to move
- config_.loHistogram = params["lo_histogram"].get<double>(0.01);
- // where in the range to try and move it to
- config_.loLevel = params["lo_level"].get<double>(0.015);
- // but don't move by more than this
- config_.loMax = params["lo_max"].get<double>(500);
- // equivalent values for the top of the histogram...
- config_.hiHistogram = params["hi_histogram"].get<double>(0.95);
- config_.hiLevel = params["hi_level"].get<double>(0.95);
- config_.hiMax = params["hi_max"].get<double>(2000);
- return config_.gammaCurve.read(params["gamma_curve"]);
-}
-
-void Contrast::setBrightness(double brightness)
-{
- brightness_ = brightness;
-}
-
-void Contrast::setContrast(double contrast)
-{
- contrast_ = contrast;
-}
-
-void Contrast::initialise()
-{
- /*
- * Fill in some default values as Prepare will run before Process gets
- * called.
- */
- status_.brightness = brightness_;
- status_.contrast = contrast_;
- status_.gammaCurve = config_.gammaCurve;
-}
-
-void Contrast::prepare(Metadata *imageMetadata)
-{
- imageMetadata->set("contrast.status", status_);
-}
-
-Pwl computeStretchCurve(Histogram const &histogram,
- ContrastConfig const &config)
-{
- Pwl enhance;
- enhance.append(0, 0);
- /*
- * If the start of the histogram is rather empty, try to pull it down a
- * bit.
- */
- double histLo = histogram.quantile(config.loHistogram) *
- (65536 / histogram.bins());
- double levelLo = config.loLevel * 65536;
- LOG(RPiContrast, Debug)
- << "Move histogram point " << histLo << " to " << levelLo;
- histLo = std::max(levelLo,
- std::min(65535.0, std::min(histLo, levelLo + config.loMax)));
- LOG(RPiContrast, Debug)
- << "Final values " << histLo << " -> " << levelLo;
- enhance.append(histLo, levelLo);
- /*
- * Keep the mid-point (median) in the same place, though, to limit the
- * apparent amount of global brightness shift.
- */
- double mid = histogram.quantile(0.5) * (65536 / histogram.bins());
- enhance.append(mid, mid);
-
- /*
- * If the top to the histogram is empty, try to pull the pixel values
- * there up.
- */
- double histHi = histogram.quantile(config.hiHistogram) *
- (65536 / histogram.bins());
- double levelHi = config.hiLevel * 65536;
- LOG(RPiContrast, Debug)
- << "Move histogram point " << histHi << " to " << levelHi;
- histHi = std::min(levelHi,
- std::max(0.0, std::max(histHi, levelHi - config.hiMax)));
- LOG(RPiContrast, Debug)
- << "Final values " << histHi << " -> " << levelHi;
- enhance.append(histHi, levelHi);
- enhance.append(65535, 65535);
- return enhance;
-}
-
-Pwl applyManualContrast(Pwl const &gammaCurve, double brightness,
- double contrast)
-{
- Pwl newGammaCurve;
- LOG(RPiContrast, Debug)
- << "Manual brightness " << brightness << " contrast " << contrast;
- gammaCurve.map([&](double x, double y) {
- newGammaCurve.append(
- x, std::max(0.0, std::min(65535.0,
- (y - 32768) * contrast +
- 32768 + brightness)));
- });
- return newGammaCurve;
-}
-
-void Contrast::process(StatisticsPtr &stats,
- [[maybe_unused]] Metadata *imageMetadata)
-{
- Histogram &histogram = stats->yHist;
- /*
- * We look at the histogram and adjust the gamma curve in the following
- * ways: 1. Adjust the gamma curve so as to pull the start of the
- * histogram down, and possibly push the end up.
- */
- Pwl gammaCurve = config_.gammaCurve;
- if (config_.ceEnable) {
- if (config_.loMax != 0 || config_.hiMax != 0)
- gammaCurve = computeStretchCurve(histogram, config_).compose(gammaCurve);
- /*
- * We could apply other adjustments (e.g. partial equalisation)
- * based on the histogram...?
- */
- }
- /*
- * 2. Finally apply any manually selected brightness/contrast
- * adjustment.
- */
- if (brightness_ != 0 || contrast_ != 1.0)
- gammaCurve = applyManualContrast(gammaCurve, brightness_, contrast_);
- /*
- * And fill in the status for output. Use more points towards the bottom
- * of the curve.
- */
- status_.brightness = brightness_;
- status_.contrast = contrast_;
- status_.gammaCurve = std::move(gammaCurve);
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Contrast(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/contrast.h b/src/ipa/raspberrypi/controller/rpi/contrast.h
deleted file mode 100644
index 9c81277a..00000000
--- a/src/ipa/raspberrypi/controller/rpi/contrast.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * contrast.h - contrast (gamma) control algorithm
- */
-#pragma once
-
-#include <mutex>
-
-#include "../contrast_algorithm.h"
-#include "../pwl.h"
-
-namespace RPiController {
-
-/*
- * Back End algorithm to appaly correct digital gain. Should be placed after
- * Back End AWB.
- */
-
-struct ContrastConfig {
- bool ceEnable;
- double loHistogram;
- double loLevel;
- double loMax;
- double hiHistogram;
- double hiLevel;
- double hiMax;
- Pwl gammaCurve;
-};
-
-class Contrast : public ContrastAlgorithm
-{
-public:
- Contrast(Controller *controller = NULL);
- char const *name() const override;
- int read(const libcamera::YamlObject &params) override;
- void setBrightness(double brightness) override;
- void setContrast(double contrast) override;
- void initialise() override;
- void prepare(Metadata *imageMetadata) override;
- void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
-
-private:
- ContrastConfig config_;
- double brightness_;
- double contrast_;
- ContrastStatus status_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/dpc.cpp b/src/ipa/raspberrypi/controller/rpi/dpc.cpp
deleted file mode 100644
index be3871df..00000000
--- a/src/ipa/raspberrypi/controller/rpi/dpc.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * dpc.cpp - DPC (defective pixel correction) control algorithm
- */
-
-#include <libcamera/base/log.h>
-
-#include "dpc.h"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiDpc)
-
-/*
- * We use the lux status so that we can apply stronger settings in darkness (if
- * necessary).
- */
-
-#define NAME "rpi.dpc"
-
-Dpc::Dpc(Controller *controller)
- : Algorithm(controller)
-{
-}
-
-char const *Dpc::name() const
-{
- return NAME;
-}
-
-int Dpc::read(const libcamera::YamlObject &params)
-{
- config_.strength = params["strength"].get<int>(1);
- if (config_.strength < 0 || config_.strength > 2) {
- LOG(RPiDpc, Error) << "Bad strength value";
- return -EINVAL;
- }
-
- return 0;
-}
-
-void Dpc::prepare(Metadata *imageMetadata)
-{
- DpcStatus dpcStatus = {};
- /* Should we vary this with lux level or analogue gain? TBD. */
- dpcStatus.strength = config_.strength;
- LOG(RPiDpc, Debug) << "strength " << dpcStatus.strength;
- imageMetadata->set("dpc.status", dpcStatus);
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Dpc(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/dpc.h b/src/ipa/raspberrypi/controller/rpi/dpc.h
deleted file mode 100644
index 84a05604..00000000
--- a/src/ipa/raspberrypi/controller/rpi/dpc.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * dpc.h - DPC (defective pixel correction) control algorithm
- */
-#pragma once
-
-#include "../algorithm.h"
-#include "../dpc_status.h"
-
-namespace RPiController {
-
-/* Back End algorithm to apply appropriate GEQ settings. */
-
-struct DpcConfig {
- int strength;
-};
-
-class Dpc : public Algorithm
-{
-public:
- Dpc(Controller *controller);
- char const *name() const override;
- int read(const libcamera::YamlObject &params) override;
- void prepare(Metadata *imageMetadata) override;
-
-private:
- DpcConfig config_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/focus.h b/src/ipa/raspberrypi/controller/rpi/focus.h
deleted file mode 100644
index 8556039d..00000000
--- a/src/ipa/raspberrypi/controller/rpi/focus.h
+++ /dev/null
@@ -1,28 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2020, Raspberry Pi Ltd
- *
- * focus.h - focus algorithm
- */
-#pragma once
-
-#include "../algorithm.h"
-#include "../metadata.h"
-
-/*
- * The "focus" algorithm. All it does it print out a version of the
- * focus contrast measure; there is no actual auto-focus mechanism to
- * control.
- */
-
-namespace RPiController {
-
-class Focus : public Algorithm
-{
-public:
- Focus(Controller *controller);
- char const *name() const override;
- void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/geq.cpp b/src/ipa/raspberrypi/controller/rpi/geq.cpp
deleted file mode 100644
index 510870e9..00000000
--- a/src/ipa/raspberrypi/controller/rpi/geq.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * geq.cpp - GEQ (green equalisation) control algorithm
- */
-
-#include <libcamera/base/log.h>
-
-#include "../device_status.h"
-#include "../lux_status.h"
-#include "../pwl.h"
-
-#include "geq.h"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiGeq)
-
-/*
- * We use the lux status so that we can apply stronger settings in darkness (if
- * necessary).
- */
-
-#define NAME "rpi.geq"
-
-Geq::Geq(Controller *controller)
- : Algorithm(controller)
-{
-}
-
-char const *Geq::name() const
-{
- return NAME;
-}
-
-int Geq::read(const libcamera::YamlObject &params)
-{
- config_.offset = params["offset"].get<uint16_t>(0);
- config_.slope = params["slope"].get<double>(0.0);
- if (config_.slope < 0.0 || config_.slope >= 1.0) {
- LOG(RPiGeq, Error) << "Bad slope value";
- return -EINVAL;
- }
-
- if (params.contains("strength")) {
- int ret = config_.strength.read(params["strength"]);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
-void Geq::prepare(Metadata *imageMetadata)
-{
- LuxStatus luxStatus = {};
- luxStatus.lux = 400;
- if (imageMetadata->get("lux.status", luxStatus))
- LOG(RPiGeq, Warning) << "no lux data found";
- DeviceStatus deviceStatus;
- deviceStatus.analogueGain = 1.0; /* in case not found */
- if (imageMetadata->get("device.status", deviceStatus))
- LOG(RPiGeq, Warning)
- << "no device metadata - use analogue gain of 1x";
- GeqStatus geqStatus = {};
- double strength = config_.strength.empty()
- ? 1.0
- : config_.strength.eval(config_.strength.domain().clip(luxStatus.lux));
- strength *= deviceStatus.analogueGain;
- double offset = config_.offset * strength;
- double slope = config_.slope * strength;
- geqStatus.offset = std::min(65535.0, std::max(0.0, offset));
- geqStatus.slope = std::min(.99999, std::max(0.0, slope));
- LOG(RPiGeq, Debug)
- << "offset " << geqStatus.offset << " slope "
- << geqStatus.slope << " (analogue gain "
- << deviceStatus.analogueGain << " lux "
- << luxStatus.lux << ")";
- imageMetadata->set("geq.status", geqStatus);
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Geq(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/geq.h b/src/ipa/raspberrypi/controller/rpi/geq.h
deleted file mode 100644
index ee3a52ff..00000000
--- a/src/ipa/raspberrypi/controller/rpi/geq.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * geq.h - GEQ (green equalisation) control algorithm
- */
-#pragma once
-
-#include "../algorithm.h"
-#include "../geq_status.h"
-
-namespace RPiController {
-
-/* Back End algorithm to apply appropriate GEQ settings. */
-
-struct GeqConfig {
- uint16_t offset;
- double slope;
- Pwl strength; /* lux to strength factor */
-};
-
-class Geq : public Algorithm
-{
-public:
- Geq(Controller *controller);
- char const *name() const override;
- int read(const libcamera::YamlObject &params) override;
- void prepare(Metadata *imageMetadata) override;
-
-private:
- GeqConfig config_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/lux.cpp b/src/ipa/raspberrypi/controller/rpi/lux.cpp
deleted file mode 100644
index 06625f3a..00000000
--- a/src/ipa/raspberrypi/controller/rpi/lux.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * lux.cpp - Lux control algorithm
- */
-#include <math.h>
-
-#include <libcamera/base/log.h>
-
-#include "../device_status.h"
-
-#include "lux.h"
-
-using namespace RPiController;
-using namespace libcamera;
-using namespace std::literals::chrono_literals;
-
-LOG_DEFINE_CATEGORY(RPiLux)
-
-#define NAME "rpi.lux"
-
-Lux::Lux(Controller *controller)
- : Algorithm(controller)
-{
- /*
- * Put in some defaults as there will be no meaningful values until
- * Process has run.
- */
- status_.aperture = 1.0;
- status_.lux = 400;
-}
-
-char const *Lux::name() const
-{
- return NAME;
-}
-
-int Lux::read(const libcamera::YamlObject &params)
-{
- auto value = params["reference_shutter_speed"].get<double>();
- if (!value)
- return -EINVAL;
- referenceShutterSpeed_ = *value * 1.0us;
-
- value = params["reference_gain"].get<double>();
- if (!value)
- return -EINVAL;
- referenceGain_ = *value;
-
- referenceAperture_ = params["reference_aperture"].get<double>(1.0);
-
- value = params["reference_Y"].get<double>();
- if (!value)
- return -EINVAL;
- referenceY_ = *value;
-
- value = params["reference_lux"].get<double>();
- if (!value)
- return -EINVAL;
- referenceLux_ = *value;
-
- currentAperture_ = referenceAperture_;
- return 0;
-}
-
-void Lux::setCurrentAperture(double aperture)
-{
- currentAperture_ = aperture;
-}
-
-void Lux::prepare(Metadata *imageMetadata)
-{
- std::unique_lock<std::mutex> lock(mutex_);
- imageMetadata->set("lux.status", status_);
-}
-
-void Lux::process(StatisticsPtr &stats, Metadata *imageMetadata)
-{
- DeviceStatus deviceStatus;
- if (imageMetadata->get("device.status", deviceStatus) == 0) {
- double currentGain = deviceStatus.analogueGain;
- double currentAperture = deviceStatus.aperture.value_or(currentAperture_);
- double currentY = stats->yHist.interQuantileMean(0, 1);
- double gainRatio = referenceGain_ / currentGain;
- double shutterSpeedRatio =
- referenceShutterSpeed_ / deviceStatus.shutterSpeed;
- double apertureRatio = referenceAperture_ / currentAperture;
- double yRatio = currentY * (65536 / stats->yHist.bins()) / referenceY_;
- double estimatedLux = shutterSpeedRatio * gainRatio *
- apertureRatio * apertureRatio *
- yRatio * referenceLux_;
- LuxStatus status;
- status.lux = estimatedLux;
- status.aperture = currentAperture;
- LOG(RPiLux, Debug) << ": estimated lux " << estimatedLux;
- {
- std::unique_lock<std::mutex> lock(mutex_);
- status_ = status;
- }
- /*
- * Overwrite the metadata here as well, so that downstream
- * algorithms get the latest value.
- */
- imageMetadata->set("lux.status", status);
- } else
- LOG(RPiLux, Warning) << ": no device metadata";
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Lux(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/lux.h b/src/ipa/raspberrypi/controller/rpi/lux.h
deleted file mode 100644
index 89411a54..00000000
--- a/src/ipa/raspberrypi/controller/rpi/lux.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * lux.h - Lux control algorithm
- */
-#pragma once
-
-#include <mutex>
-
-#include <libcamera/base/utils.h>
-
-#include "../lux_status.h"
-#include "../algorithm.h"
-
-/* This is our implementation of the "lux control algorithm". */
-
-namespace RPiController {
-
-class Lux : public Algorithm
-{
-public:
- Lux(Controller *controller);
- char const *name() const override;
- int read(const libcamera::YamlObject &params) override;
- void prepare(Metadata *imageMetadata) override;
- void process(StatisticsPtr &stats, Metadata *imageMetadata) override;
- void setCurrentAperture(double aperture);
-
-private:
- /*
- * These values define the conditions of the reference image, against
- * which we compare the new image.
- */
- libcamera::utils::Duration referenceShutterSpeed_;
- double referenceGain_;
- double referenceAperture_; /* units of 1/f */
- double referenceY_; /* out of 65536 */
- double referenceLux_;
- double currentAperture_;
- LuxStatus status_;
- std::mutex mutex_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/noise.cpp b/src/ipa/raspberrypi/controller/rpi/noise.cpp
deleted file mode 100644
index bcd8b9ed..00000000
--- a/src/ipa/raspberrypi/controller/rpi/noise.cpp
+++ /dev/null
@@ -1,89 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * noise.cpp - Noise control algorithm
- */
-
-#include <math.h>
-
-#include <libcamera/base/log.h>
-
-#include "../device_status.h"
-#include "../noise_status.h"
-
-#include "noise.h"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiNoise)
-
-#define NAME "rpi.noise"
-
-Noise::Noise(Controller *controller)
- : Algorithm(controller), modeFactor_(1.0)
-{
-}
-
-char const *Noise::name() const
-{
- return NAME;
-}
-
-void Noise::switchMode(CameraMode const &cameraMode,
- [[maybe_unused]] Metadata *metadata)
-{
- /*
- * For example, we would expect a 2x2 binned mode to have a "noise
- * factor" of sqrt(2x2) = 2. (can't be less than one, right?)
- */
- modeFactor_ = std::max(1.0, cameraMode.noiseFactor);
-}
-
-int Noise::read(const libcamera::YamlObject &params)
-{
- auto value = params["reference_constant"].get<double>();
- if (!value)
- return -EINVAL;
- referenceConstant_ = *value;
-
- value = params["reference_slope"].get<double>();
- if (!value)
- return -EINVAL;
- referenceSlope_ = *value;
-
- return 0;
-}
-
-void Noise::prepare(Metadata *imageMetadata)
-{
- struct DeviceStatus deviceStatus;
- deviceStatus.analogueGain = 1.0; /* keep compiler calm */
- if (imageMetadata->get("device.status", deviceStatus) == 0) {
- /*
- * There is a slight question as to exactly how the noise
- * profile, specifically the constant part of it, scales. For
- * now we assume it all scales the same, and we'll revisit this
- * if it proves substantially wrong. NOTE: we may also want to
- * make some adjustments based on the camera mode (such as
- * binning), if we knew how to discover it...
- */
- double factor = sqrt(deviceStatus.analogueGain) / modeFactor_;
- struct NoiseStatus status;
- status.noiseConstant = referenceConstant_ * factor;
- status.noiseSlope = referenceSlope_ * factor;
- imageMetadata->set("noise.status", status);
- LOG(RPiNoise, Debug)
- << "constant " << status.noiseConstant
- << " slope " << status.noiseSlope;
- } else
- LOG(RPiNoise, Warning) << " no metadata";
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return new Noise(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/noise.h b/src/ipa/raspberrypi/controller/rpi/noise.h
deleted file mode 100644
index 74c31e64..00000000
--- a/src/ipa/raspberrypi/controller/rpi/noise.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * noise.h - Noise control algorithm
- */
-#pragma once
-
-#include "../algorithm.h"
-#include "../noise_status.h"
-
-/* This is our implementation of the "noise algorithm". */
-
-namespace RPiController {
-
-class Noise : public Algorithm
-{
-public:
- Noise(Controller *controller);
- char const *name() const override;
- void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
- int read(const libcamera::YamlObject &params) override;
- void prepare(Metadata *imageMetadata) override;
-
-private:
- /* the noise profile for analogue gain of 1.0 */
- double referenceConstant_;
- double referenceSlope_;
- double modeFactor_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/sdn.cpp b/src/ipa/raspberrypi/controller/rpi/sdn.cpp
deleted file mode 100644
index b6b66251..00000000
--- a/src/ipa/raspberrypi/controller/rpi/sdn.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019-2021, Raspberry Pi Ltd
- *
- * sdn.cpp - SDN (spatial denoise) control algorithm
- */
-
-#include <libcamera/base/log.h>
-
-#include "../denoise_status.h"
-#include "../noise_status.h"
-
-#include "sdn.h"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiSdn)
-
-/*
- * Calculate settings for the spatial denoise block using the noise profile in
- * the image metadata.
- */
-
-#define NAME "rpi.sdn"
-
-Sdn::Sdn(Controller *controller)
- : DenoiseAlgorithm(controller), mode_(DenoiseMode::ColourOff)
-{
-}
-
-char const *Sdn::name() const
-{
- return NAME;
-}
-
-int Sdn::read(const libcamera::YamlObject &params)
-{
- deviation_ = params["deviation"].get<double>(3.2);
- strength_ = params["strength"].get<double>(0.75);
- return 0;
-}
-
-void Sdn::initialise()
-{
-}
-
-void Sdn::prepare(Metadata *imageMetadata)
-{
- struct NoiseStatus noiseStatus = {};
- noiseStatus.noiseSlope = 3.0; /* in case no metadata */
- if (imageMetadata->get("noise.status", noiseStatus) != 0)
- LOG(RPiSdn, Warning) << "no noise profile found";
- LOG(RPiSdn, Debug)
- << "Noise profile: constant " << noiseStatus.noiseConstant
- << " slope " << noiseStatus.noiseSlope;
- struct DenoiseStatus status;
- status.noiseConstant = noiseStatus.noiseConstant * deviation_;
- status.noiseSlope = noiseStatus.noiseSlope * deviation_;
- status.strength = strength_;
- status.mode = static_cast<std::underlying_type_t<DenoiseMode>>(mode_);
- imageMetadata->set("denoise.status", status);
- LOG(RPiSdn, Debug)
- << "programmed constant " << status.noiseConstant
- << " slope " << status.noiseSlope
- << " strength " << status.strength;
-}
-
-void Sdn::setMode(DenoiseMode mode)
-{
- /* We only distinguish between off and all other modes. */
- mode_ = mode;
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return (Algorithm *)new Sdn(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/sdn.h b/src/ipa/raspberrypi/controller/rpi/sdn.h
deleted file mode 100644
index 9dd73c38..00000000
--- a/src/ipa/raspberrypi/controller/rpi/sdn.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * sdn.h - SDN (spatial denoise) control algorithm
- */
-#pragma once
-
-#include "../algorithm.h"
-#include "../denoise_algorithm.h"
-
-namespace RPiController {
-
-/* Algorithm to calculate correct spatial denoise (SDN) settings. */
-
-class Sdn : public DenoiseAlgorithm
-{
-public:
- Sdn(Controller *controller = NULL);
- char const *name() const override;
- int read(const libcamera::YamlObject &params) override;
- void initialise() override;
- void prepare(Metadata *imageMetadata) override;
- void setMode(DenoiseMode mode) override;
-
-private:
- double deviation_;
- double strength_;
- DenoiseMode mode_;
-};
-
-} /* namespace RPiController */
diff --git a/src/ipa/raspberrypi/controller/rpi/sharpen.cpp b/src/ipa/raspberrypi/controller/rpi/sharpen.cpp
deleted file mode 100644
index 4f6f020a..00000000
--- a/src/ipa/raspberrypi/controller/rpi/sharpen.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * sharpen.cpp - sharpening control algorithm
- */
-
-#include <math.h>
-
-#include <libcamera/base/log.h>
-
-#include "../sharpen_status.h"
-
-#include "sharpen.h"
-
-using namespace RPiController;
-using namespace libcamera;
-
-LOG_DEFINE_CATEGORY(RPiSharpen)
-
-#define NAME "rpi.sharpen"
-
-Sharpen::Sharpen(Controller *controller)
- : SharpenAlgorithm(controller), userStrength_(1.0)
-{
-}
-
-char const *Sharpen::name() const
-{
- return NAME;
-}
-
-void Sharpen::switchMode(CameraMode const &cameraMode,
- [[maybe_unused]] Metadata *metadata)
-{
- /* can't be less than one, right? */
- modeFactor_ = std::max(1.0, cameraMode.noiseFactor);
-}
-
-int Sharpen::read(const libcamera::YamlObject &params)
-{
- threshold_ = params["threshold"].get<double>(1.0);
- strength_ = params["strength"].get<double>(1.0);
- limit_ = params["limit"].get<double>(1.0);
- LOG(RPiSharpen, Debug)
- << "Read threshold " << threshold_
- << " strength " << strength_
- << " limit " << limit_;
- return 0;
-}
-
-void Sharpen::setStrength(double strength)
-{
- /*
- * Note that this function is how an application sets the overall
- * sharpening "strength". We call this the "user strength" field
- * as there already is a strength_ field - being an internal gain
- * parameter that gets passed to the ISP control code. Negative
- * values are not allowed - coerce them to zero (no sharpening).
- */
- userStrength_ = std::max(0.0, strength);
-}
-
-void Sharpen::prepare(Metadata *imageMetadata)
-{
- /*
- * The userStrength_ affects the algorithm's internal gain directly, but
- * we adjust the limit and threshold less aggressively. Using a sqrt
- * function is an arbitrary but gentle way of accomplishing this.
- */
- double userStrengthSqrt = sqrt(userStrength_);
- struct SharpenStatus status;
- /*
- * Binned modes seem to need the sharpening toned down with this
- * pipeline, thus we use the modeFactor_ here. Also avoid
- * divide-by-zero with the userStrengthSqrt.
- */
- status.threshold = threshold_ * modeFactor_ /
- std::max(0.01, userStrengthSqrt);
- status.strength = strength_ / modeFactor_ * userStrength_;
- status.limit = limit_ / modeFactor_ * userStrengthSqrt;
- /* Finally, report any application-supplied parameters that were used. */
- status.userStrength = userStrength_;
- imageMetadata->set("sharpen.status", status);
-}
-
-/* Register algorithm with the system. */
-static Algorithm *create(Controller *controller)
-{
- return new Sharpen(controller);
-}
-static RegisterAlgorithm reg(NAME, &create);
diff --git a/src/ipa/raspberrypi/controller/rpi/sharpen.h b/src/ipa/raspberrypi/controller/rpi/sharpen.h
deleted file mode 100644
index 8bb7631e..00000000
--- a/src/ipa/raspberrypi/controller/rpi/sharpen.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: BSD-2-Clause */
-/*
- * Copyright (C) 2019, Raspberry Pi Ltd
- *
- * sharpen.h - sharpening control algorithm
- */
-#pragma once
-
-#include "../sharpen_algorithm.h"
-#include "../sharpen_status.h"
-
-/* This is our implementation of the "sharpen algorithm". */
-
-namespace RPiController {
-
-class Sharpen : public SharpenAlgorithm
-{
-public:
- Sharpen(Controller *controller);
- char const *name() const override;
- void switchMode(CameraMode const &cameraMode, Metadata *metadata) override;
- int read(const libcamera::YamlObject &params) override;
- void setStrength(double strength) override;
- void prepare(Metadata *imageMetadata) override;
-
-private:
- double threshold_;
- double strength_;
- double limit_;
- double modeFactor_;
- double userStrength_;
-};
-
-} /* namespace RPiController */