[libcamera-devel,v3,5/5] ipa: rpi: agc: Use channel constraints in the AGC algorithm
diff mbox series

Message ID 20230912102442.169001-6-david.plowman@raspberrypi.com
State Accepted
Headers show
Series
  • Multi-channel AGC
Related show

Commit Message

David Plowman Sept. 12, 2023, 10:24 a.m. UTC
Whenever we run Agc::process(), we store the most recent total
exposure requested for each channel.

With these values we can apply the channel constraints after
time-filtering the requested total exposure, but before working out
how much digital gain is needed.

Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
---
 src/ipa/rpi/controller/rpi/agc.cpp         | 22 ++++++--
 src/ipa/rpi/controller/rpi/agc.h           |  1 +
 src/ipa/rpi/controller/rpi/agc_channel.cpp | 58 +++++++++++++++++++---
 src/ipa/rpi/controller/rpi/agc_channel.h   |  8 ++-
 4 files changed, 75 insertions(+), 14 deletions(-)

Comments

Jacopo Mondi Sept. 12, 2023, 3:21 p.m. UTC | #1
On Tue, Sep 12, 2023 at 11:24:42AM +0100, David Plowman via libcamera-devel wrote:
> Whenever we run Agc::process(), we store the most recent total
> exposure requested for each channel.
>
> With these values we can apply the channel constraints after
> time-filtering the requested total exposure, but before working out
> how much digital gain is needed.
>
> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>
> Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
> ---
>  src/ipa/rpi/controller/rpi/agc.cpp         | 22 ++++++--
>  src/ipa/rpi/controller/rpi/agc.h           |  1 +
>  src/ipa/rpi/controller/rpi/agc_channel.cpp | 58 +++++++++++++++++++---
>  src/ipa/rpi/controller/rpi/agc_channel.h   |  8 ++-
>  4 files changed, 75 insertions(+), 14 deletions(-)
>
> diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
> index 58ba8839..170432c9 100644
> --- a/src/ipa/rpi/controller/rpi/agc.cpp
> +++ b/src/ipa/rpi/controller/rpi/agc.cpp
> @@ -40,6 +40,7 @@ int Agc::read(const libcamera::YamlObject &params)
>  	 */
>  	if (!params.contains("channels")) {
>  		LOG(RPiAgc, Debug) << "Single channel only";
> +		channelTotalExposures_.resize(1, 0s);
>  		channelData_.emplace_back();
>  		return channelData_.back().channel.read(params, getHardwareConfig());
>  	}
> @@ -59,6 +60,8 @@ int Agc::read(const libcamera::YamlObject &params)
>  		return -1;
>  	}
>
> +	channelTotalExposures_.resize(channelData_.size(), 0s);
> +
>  	return 0;
>  }
>
> @@ -236,15 +239,21 @@ static void getChannelIndex(Metadata *metadata, const char *message, unsigned in
>  		LOG(RPiAgc, Debug) << message;
>  }
>
> -static void setChannelIndex(Metadata *metadata, const char *message, unsigned int channelIndex)
> +static libcamera::utils::Duration
> +setChannelIndexGetExposure(Metadata *metadata, const char *message, unsigned int channelIndex)
>  {
>  	std::unique_lock<RPiController::Metadata> lock(*metadata);
>  	AgcStatus *status = metadata->getLocked<AgcStatus>("agc.status");
> -	if (status)
> +	libcamera::utils::Duration dur = 0s;
> +
> +	if (status) {
>  		status->channel = channelIndex;
> -	else
> +		dur = status->totalExposureValue;
> +	} else
>  		/* This does happen at startup, otherwise it would be a Warning or Error. */
>  		LOG(RPiAgc, Debug) << message;
> +
> +	return dur;
>  }
>
>  void Agc::prepare(Metadata *imageMetadata)
> @@ -308,8 +317,11 @@ void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
>  		/* Can also happen when new channels start. */
>  		LOG(RPiAgc, Debug) << "process: channel " << channelIndex << " not seen yet";
>
> -	channelData.channel.process(stats, &deviceStatus, imageMetadata);
> -	setChannelIndex(imageMetadata, "process: no AGC status found", channelIndex);
> +	channelData.channel.process(stats, &deviceStatus, imageMetadata, channelTotalExposures_);
> +	auto dur = setChannelIndexGetExposure(imageMetadata, "process: no AGC status found",
> +					      channelIndex);
> +	if (dur)
> +		channelTotalExposures_[channelIndex] = dur;
>
>  	/* And onto the next channel for the next call. */
>  	index_ = (index_ + 1) % activeChannels_.size();
> diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h
> index ee85c693..90890439 100644
> --- a/src/ipa/rpi/controller/rpi/agc.h
> +++ b/src/ipa/rpi/controller/rpi/agc.h
> @@ -55,6 +55,7 @@ private:
>  	std::vector<AgcChannelData> channelData_;
>  	std::vector<unsigned int> activeChannels_;
>  	unsigned int index_; /* index into the activeChannels_ */
> +	AgcChannelTotalExposures channelTotalExposures_;
>  };
>
>  } /* namespace RPiController */
> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
> index 7fce35b0..4669e8da 100644
> --- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
> +++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
> @@ -493,7 +493,9 @@ void AgcChannel::prepare(Metadata *imageMetadata)
>  	}
>  }
>
> -void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata)
> +void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus,
> +			 Metadata *imageMetadata,
> +			 const AgcChannelTotalExposures &channelTotalExposures)
>  {
>  	frameCount_++;
>  	/*
> @@ -512,12 +514,17 @@ void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus,
>  	computeTargetExposure(gain);
>  	/* The results have to be filtered so as not to change too rapidly. */
>  	filterExposure();
> +	/*
> +	 * We may be asked to limit the exposure using other channels. If another channel
> +	 * determines our upper bound we may want to know this later.
> +	 */
> +	bool channelBound = applyChannelConstraints(channelTotalExposures);
>  	/*
>  	 * 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.
> +	 * what that is. It also tells us whether it's trying to desaturate the image
> +	 * more quickly, which can only happen when another channel is not limiting us.
>  	 */
> -	bool desaturate = applyDigitalGain(gain, targetY);
> +	bool desaturate = applyDigitalGain(gain, targetY, channelBound);
>  	/*
>  	 * The last thing is to divide up the exposure value into a shutter time
>  	 * and analogue gain, according to the current exposure mode.
> @@ -796,7 +803,44 @@ void AgcChannel::computeTargetExposure(double gain)
>  	LOG(RPiAgc, Debug) << "Target totalExposure " << target_.totalExposure;
>  }
>
> -bool AgcChannel::applyDigitalGain(double gain, double targetY)
> +bool AgcChannel::applyChannelConstraints(const AgcChannelTotalExposures &channelTotalExposures)
> +{
> +	bool channelBound = false;
> +	LOG(RPiAgc, Debug)
> +		<< "Total exposure before channel constraints " << filtered_.totalExposure;
> +
> +	for (const auto &constraint : config_.channelConstraints) {
> +		LOG(RPiAgc, Debug)
> +			<< "Check constraint: channel " << constraint.channel << " bound "
> +			<< (constraint.bound == AgcChannelConstraint::Bound::UPPER ? "UPPER" : "LOWER")
> +			<< " factor " << constraint.factor;
> +		if (constraint.channel >= channelTotalExposures.size() ||
> +		    !channelTotalExposures[constraint.channel]) {
> +			LOG(RPiAgc, Debug) << "no such channel or no exposure available- skipped";
> +			continue;
> +		}
> +
> +		libcamera::utils::Duration limitExposure =
> +			channelTotalExposures[constraint.channel] * constraint.factor;

I'm still having troubles visualizing how this gets specified in a config file,
specifically what guarantees the interdepencies between channels are
correct. But that's likely not a problem for now.

Code-wise this is fine
Reviewed-by: Jacopo Mondi <jacopo.mondi@ideasonboard.com>

Thanks
  j

> +		LOG(RPiAgc, Debug) << "Limit exposure " << limitExposure;
> +		if ((constraint.bound == AgcChannelConstraint::Bound::UPPER &&
> +		     filtered_.totalExposure > limitExposure) ||
> +		    (constraint.bound == AgcChannelConstraint::Bound::LOWER &&
> +		     filtered_.totalExposure < limitExposure)) {
> +			filtered_.totalExposure = limitExposure;
> +			LOG(RPiAgc, Debug) << "Constraint applies";
> +			channelBound = true;
> +		} else
> +			LOG(RPiAgc, Debug) << "Constraint does not apply";
> +	}
> +
> +	LOG(RPiAgc, Debug)
> +		<< "Total exposure after channel constraints " << filtered_.totalExposure;
> +
> +	return channelBound;
> +}
> +
> +bool AgcChannel::applyDigitalGain(double gain, double targetY, bool channelBound)
>  {
>  	double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
>  	ASSERT(minColourGain != 0.0);
> @@ -816,8 +860,8 @@ bool AgcChannel::applyDigitalGain(double gain, double targetY)
>  	 * quickly (and we then approach the correct value more quickly from
>  	 * below).
>  	 */
> -	bool desaturate = targetY > config_.fastReduceThreshold &&
> -			  gain < sqrt(targetY);
> +	bool desaturate = !channelBound &&
> +			  targetY > config_.fastReduceThreshold && gain < sqrt(targetY);
>  	if (desaturate)
>  		dg /= config_.fastReduceThreshold;
>  	LOG(RPiAgc, Debug) << "Digital gain " << dg << " desaturate? " << desaturate;
> diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
> index 5d2d8a11..1745f2ef 100644
> --- a/src/ipa/rpi/controller/rpi/agc_channel.h
> +++ b/src/ipa/rpi/controller/rpi/agc_channel.h
> @@ -21,6 +21,8 @@
>
>  namespace RPiController {
>
> +using AgcChannelTotalExposures = std::vector<libcamera::utils::Duration>;
> +
>  struct AgcMeteringMode {
>  	std::vector<double> weights;
>  	int read(const libcamera::YamlObject &params);
> @@ -95,7 +97,8 @@ public:
>  	void disableAuto();
>  	void switchMode(CameraMode const &cameraMode, Metadata *metadata);
>  	void prepare(Metadata *imageMetadata);
> -	void process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata);
> +	void process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata,
> +		     const AgcChannelTotalExposures &channelTotalExposures);
>
>  private:
>  	bool updateLockStatus(DeviceStatus const &deviceStatus);
> @@ -107,7 +110,8 @@ private:
>  			 double &gain, double &targetY);
>  	void computeTargetExposure(double gain);
>  	void filterExposure();
> -	bool applyDigitalGain(double gain, double targetY);
> +	bool applyChannelConstraints(const AgcChannelTotalExposures &channelTotalExposures);
> +	bool applyDigitalGain(double gain, double targetY, bool channelBound);
>  	void divideUpExposure();
>  	void writeAndFinish(Metadata *imageMetadata, bool desaturate);
>  	libcamera::utils::Duration limitShutter(libcamera::utils::Duration shutter);
> --
> 2.30.2
>

Patch
diff mbox series

diff --git a/src/ipa/rpi/controller/rpi/agc.cpp b/src/ipa/rpi/controller/rpi/agc.cpp
index 58ba8839..170432c9 100644
--- a/src/ipa/rpi/controller/rpi/agc.cpp
+++ b/src/ipa/rpi/controller/rpi/agc.cpp
@@ -40,6 +40,7 @@  int Agc::read(const libcamera::YamlObject &params)
 	 */
 	if (!params.contains("channels")) {
 		LOG(RPiAgc, Debug) << "Single channel only";
+		channelTotalExposures_.resize(1, 0s);
 		channelData_.emplace_back();
 		return channelData_.back().channel.read(params, getHardwareConfig());
 	}
@@ -59,6 +60,8 @@  int Agc::read(const libcamera::YamlObject &params)
 		return -1;
 	}
 
+	channelTotalExposures_.resize(channelData_.size(), 0s);
+
 	return 0;
 }
 
@@ -236,15 +239,21 @@  static void getChannelIndex(Metadata *metadata, const char *message, unsigned in
 		LOG(RPiAgc, Debug) << message;
 }
 
-static void setChannelIndex(Metadata *metadata, const char *message, unsigned int channelIndex)
+static libcamera::utils::Duration
+setChannelIndexGetExposure(Metadata *metadata, const char *message, unsigned int channelIndex)
 {
 	std::unique_lock<RPiController::Metadata> lock(*metadata);
 	AgcStatus *status = metadata->getLocked<AgcStatus>("agc.status");
-	if (status)
+	libcamera::utils::Duration dur = 0s;
+
+	if (status) {
 		status->channel = channelIndex;
-	else
+		dur = status->totalExposureValue;
+	} else
 		/* This does happen at startup, otherwise it would be a Warning or Error. */
 		LOG(RPiAgc, Debug) << message;
+
+	return dur;
 }
 
 void Agc::prepare(Metadata *imageMetadata)
@@ -308,8 +317,11 @@  void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)
 		/* Can also happen when new channels start. */
 		LOG(RPiAgc, Debug) << "process: channel " << channelIndex << " not seen yet";
 
-	channelData.channel.process(stats, &deviceStatus, imageMetadata);
-	setChannelIndex(imageMetadata, "process: no AGC status found", channelIndex);
+	channelData.channel.process(stats, &deviceStatus, imageMetadata, channelTotalExposures_);
+	auto dur = setChannelIndexGetExposure(imageMetadata, "process: no AGC status found",
+					      channelIndex);
+	if (dur)
+		channelTotalExposures_[channelIndex] = dur;
 
 	/* And onto the next channel for the next call. */
 	index_ = (index_ + 1) % activeChannels_.size();
diff --git a/src/ipa/rpi/controller/rpi/agc.h b/src/ipa/rpi/controller/rpi/agc.h
index ee85c693..90890439 100644
--- a/src/ipa/rpi/controller/rpi/agc.h
+++ b/src/ipa/rpi/controller/rpi/agc.h
@@ -55,6 +55,7 @@  private:
 	std::vector<AgcChannelData> channelData_;
 	std::vector<unsigned int> activeChannels_;
 	unsigned int index_; /* index into the activeChannels_ */
+	AgcChannelTotalExposures channelTotalExposures_;
 };
 
 } /* namespace RPiController */
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.cpp b/src/ipa/rpi/controller/rpi/agc_channel.cpp
index 7fce35b0..4669e8da 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.cpp
+++ b/src/ipa/rpi/controller/rpi/agc_channel.cpp
@@ -493,7 +493,9 @@  void AgcChannel::prepare(Metadata *imageMetadata)
 	}
 }
 
-void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata)
+void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus,
+			 Metadata *imageMetadata,
+			 const AgcChannelTotalExposures &channelTotalExposures)
 {
 	frameCount_++;
 	/*
@@ -512,12 +514,17 @@  void AgcChannel::process(StatisticsPtr &stats, const DeviceStatus *deviceStatus,
 	computeTargetExposure(gain);
 	/* The results have to be filtered so as not to change too rapidly. */
 	filterExposure();
+	/*
+	 * We may be asked to limit the exposure using other channels. If another channel
+	 * determines our upper bound we may want to know this later.
+	 */
+	bool channelBound = applyChannelConstraints(channelTotalExposures);
 	/*
 	 * 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.
+	 * what that is. It also tells us whether it's trying to desaturate the image
+	 * more quickly, which can only happen when another channel is not limiting us.
 	 */
-	bool desaturate = applyDigitalGain(gain, targetY);
+	bool desaturate = applyDigitalGain(gain, targetY, channelBound);
 	/*
 	 * The last thing is to divide up the exposure value into a shutter time
 	 * and analogue gain, according to the current exposure mode.
@@ -796,7 +803,44 @@  void AgcChannel::computeTargetExposure(double gain)
 	LOG(RPiAgc, Debug) << "Target totalExposure " << target_.totalExposure;
 }
 
-bool AgcChannel::applyDigitalGain(double gain, double targetY)
+bool AgcChannel::applyChannelConstraints(const AgcChannelTotalExposures &channelTotalExposures)
+{
+	bool channelBound = false;
+	LOG(RPiAgc, Debug)
+		<< "Total exposure before channel constraints " << filtered_.totalExposure;
+
+	for (const auto &constraint : config_.channelConstraints) {
+		LOG(RPiAgc, Debug)
+			<< "Check constraint: channel " << constraint.channel << " bound "
+			<< (constraint.bound == AgcChannelConstraint::Bound::UPPER ? "UPPER" : "LOWER")
+			<< " factor " << constraint.factor;
+		if (constraint.channel >= channelTotalExposures.size() ||
+		    !channelTotalExposures[constraint.channel]) {
+			LOG(RPiAgc, Debug) << "no such channel or no exposure available- skipped";
+			continue;
+		}
+
+		libcamera::utils::Duration limitExposure =
+			channelTotalExposures[constraint.channel] * constraint.factor;
+		LOG(RPiAgc, Debug) << "Limit exposure " << limitExposure;
+		if ((constraint.bound == AgcChannelConstraint::Bound::UPPER &&
+		     filtered_.totalExposure > limitExposure) ||
+		    (constraint.bound == AgcChannelConstraint::Bound::LOWER &&
+		     filtered_.totalExposure < limitExposure)) {
+			filtered_.totalExposure = limitExposure;
+			LOG(RPiAgc, Debug) << "Constraint applies";
+			channelBound = true;
+		} else
+			LOG(RPiAgc, Debug) << "Constraint does not apply";
+	}
+
+	LOG(RPiAgc, Debug)
+		<< "Total exposure after channel constraints " << filtered_.totalExposure;
+
+	return channelBound;
+}
+
+bool AgcChannel::applyDigitalGain(double gain, double targetY, bool channelBound)
 {
 	double minColourGain = std::min({ awb_.gainR, awb_.gainG, awb_.gainB, 1.0 });
 	ASSERT(minColourGain != 0.0);
@@ -816,8 +860,8 @@  bool AgcChannel::applyDigitalGain(double gain, double targetY)
 	 * quickly (and we then approach the correct value more quickly from
 	 * below).
 	 */
-	bool desaturate = targetY > config_.fastReduceThreshold &&
-			  gain < sqrt(targetY);
+	bool desaturate = !channelBound &&
+			  targetY > config_.fastReduceThreshold && gain < sqrt(targetY);
 	if (desaturate)
 		dg /= config_.fastReduceThreshold;
 	LOG(RPiAgc, Debug) << "Digital gain " << dg << " desaturate? " << desaturate;
diff --git a/src/ipa/rpi/controller/rpi/agc_channel.h b/src/ipa/rpi/controller/rpi/agc_channel.h
index 5d2d8a11..1745f2ef 100644
--- a/src/ipa/rpi/controller/rpi/agc_channel.h
+++ b/src/ipa/rpi/controller/rpi/agc_channel.h
@@ -21,6 +21,8 @@ 
 
 namespace RPiController {
 
+using AgcChannelTotalExposures = std::vector<libcamera::utils::Duration>;
+
 struct AgcMeteringMode {
 	std::vector<double> weights;
 	int read(const libcamera::YamlObject &params);
@@ -95,7 +97,8 @@  public:
 	void disableAuto();
 	void switchMode(CameraMode const &cameraMode, Metadata *metadata);
 	void prepare(Metadata *imageMetadata);
-	void process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata);
+	void process(StatisticsPtr &stats, const DeviceStatus *deviceStatus, Metadata *imageMetadata,
+		     const AgcChannelTotalExposures &channelTotalExposures);
 
 private:
 	bool updateLockStatus(DeviceStatus const &deviceStatus);
@@ -107,7 +110,8 @@  private:
 			 double &gain, double &targetY);
 	void computeTargetExposure(double gain);
 	void filterExposure();
-	bool applyDigitalGain(double gain, double targetY);
+	bool applyChannelConstraints(const AgcChannelTotalExposures &channelTotalExposures);
+	bool applyDigitalGain(double gain, double targetY, bool channelBound);
 	void divideUpExposure();
 	void writeAndFinish(Metadata *imageMetadata, bool desaturate);
 	libcamera::utils::Duration limitShutter(libcamera::utils::Duration shutter);