[3/5] ipa: rkisp1: blc: Query black levels from camera sensor helper
diff mbox series

Message ID 20240701144122.3418955-4-stefan.klug@ideasonboard.com
State Superseded
Headers show
Series
  • ipa: Add black level to camera sensor helpers
Related show

Commit Message

Stefan Klug July 1, 2024, 2:38 p.m. UTC
The black levels from the camera sensor helper are then used to do the
black level correction. Black levels can still be overwritten by the
tuning file.

Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
---
 src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
 1 file changed, 24 insertions(+), 4 deletions(-)

Comments

Jacopo Mondi July 2, 2024, 8 a.m. UTC | #1
Hi Stefan

On Mon, Jul 01, 2024 at 04:38:26PM GMT, Stefan Klug wrote:
> The black levels from the camera sensor helper are then used to do the
> black level correction. Black levels can still be overwritten by the
> tuning file.
>
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> ---
>  src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
>  1 file changed, 24 insertions(+), 4 deletions(-)
>
> diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> index d2e743541c99..0c39c3b47da5 100644
> --- a/src/ipa/rkisp1/algorithms/blc.cpp
> +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> @@ -46,10 +46,30 @@ BlackLevelCorrection::BlackLevelCorrection()
>  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
>  			       const YamlObject &tuningData)
>  {
> -	blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> -	blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> -	blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> -	blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> +	auto blackLevels = context.camHelper->blackLevels();
> +	if (blackLevels) {
> +		Span<const int32_t, 4> levels = *blackLevels;
> +		blackLevelRed_ = levels[0];
> +		blackLevelGreenR_ = levels[1];
> +		blackLevelGreenB_ = levels[2];
> +		blackLevelBlue_ = levels[3];
> +	} else
> +		LOG(RkISP1Blc, Warning)
> +			<< "No black levels provided by camera sensor helper";
> +
> +	if (!blackLevels || (tuningData.contains("R") &&

Is there any need to get context.camHelper->blackLevels() if the
values are in the config file ?


> +			     tuningData.contains("Gr") &&
> +			     tuningData.contains("Gb") &&
> +			     tuningData.contains("B"))) {
> +		blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> +		blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> +		blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> +		blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> +
> +		if (blackLevels)

Is this a Warning ? Same question above. I would Warn only if no BLC in
sensor helpers AND in config file.

> +			LOG(RkISP1Blc, Warning)
> +				<< "Black levels overwritten by tuning file";
> +	}
>
>  	tuningParameters_ = true;
>
> --
> 2.43.0
>
Kieran Bingham July 2, 2024, 10:51 a.m. UTC | #2
Quoting Jacopo Mondi (2024-07-02 09:00:44)
> Hi Stefan
> 
> On Mon, Jul 01, 2024 at 04:38:26PM GMT, Stefan Klug wrote:
> > The black levels from the camera sensor helper are then used to do the
> > black level correction. Black levels can still be overwritten by the
> > tuning file.
> >
> > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > ---
> >  src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
> >  1 file changed, 24 insertions(+), 4 deletions(-)
> >
> > diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> > index d2e743541c99..0c39c3b47da5 100644
> > --- a/src/ipa/rkisp1/algorithms/blc.cpp
> > +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> > @@ -46,10 +46,30 @@ BlackLevelCorrection::BlackLevelCorrection()
> >  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
> >                              const YamlObject &tuningData)
> >  {
> > -     blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > -     blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > -     blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > -     blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > +     auto blackLevels = context.camHelper->blackLevels();
> > +     if (blackLevels) {
> > +             Span<const int32_t, 4> levels = *blackLevels;
> > +             blackLevelRed_ = levels[0];
> > +             blackLevelGreenR_ = levels[1];
> > +             blackLevelGreenB_ = levels[2];
> > +             blackLevelBlue_ = levels[3];
> > +     } else
> > +             LOG(RkISP1Blc, Warning)
> > +                     << "No black levels provided by camera sensor helper";
> > +
> > +     if (!blackLevels || (tuningData.contains("R") &&
> 
> Is there any need to get context.camHelper->blackLevels() if the
> values are in the config file ?
> 
> 
> > +                          tuningData.contains("Gr") &&
> > +                          tuningData.contains("Gb") &&
> > +                          tuningData.contains("B"))) {
> > +             blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > +             blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > +             blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > +             blackLevelBlue_ = tuningData["B"].get<int16_t>(256);

Why 256 here? Does a value have to be specified?

Maybe we should move a parse black level helper out to some common yaml
helpers (Do we have anywhere for that yet, that can read defined
libcamera types from yaml?) ... and it should only return all values if
all of them are present and parsed correctly - otherwise R=3200, Gr=256,
Gb=256, B=256 probably wouldn't make sense?



> > +
> > +             if (blackLevels)
> 
> Is this a Warning ? Same question above. I would Warn only if no BLC in
> sensor helpers AND in config file.
> 
> > +                     LOG(RkISP1Blc, Warning)
> > +                             << "Black levels overwritten by tuning file";

Yes, I think the tuning file should take precedence, so I don't think
it's a warning. Perhaps a debug print to say which values get loaded and
where from ?


> > +     }
> >
> >       tuningParameters_ = true;
> >
> > --
> > 2.43.0
> >
Stefan Klug July 2, 2024, 1:18 p.m. UTC | #3
Hi Jacopo,

On Tue, Jul 02, 2024 at 10:00:44AM +0200, Jacopo Mondi wrote:
> Hi Stefan
> 
> On Mon, Jul 01, 2024 at 04:38:26PM GMT, Stefan Klug wrote:
> > The black levels from the camera sensor helper are then used to do the
> > black level correction. Black levels can still be overwritten by the
> > tuning file.
> >
> > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > ---
> >  src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
> >  1 file changed, 24 insertions(+), 4 deletions(-)
> >
> > diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> > index d2e743541c99..0c39c3b47da5 100644
> > --- a/src/ipa/rkisp1/algorithms/blc.cpp
> > +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> > @@ -46,10 +46,30 @@ BlackLevelCorrection::BlackLevelCorrection()
> >  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
> >  			       const YamlObject &tuningData)
> >  {
> > -	blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > -	blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > -	blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > -	blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > +	auto blackLevels = context.camHelper->blackLevels();
> > +	if (blackLevels) {
> > +		Span<const int32_t, 4> levels = *blackLevels;
> > +		blackLevelRed_ = levels[0];
> > +		blackLevelGreenR_ = levels[1];
> > +		blackLevelGreenB_ = levels[2];
> > +		blackLevelBlue_ = levels[3];
> > +	} else
> > +		LOG(RkISP1Blc, Warning)
> > +			<< "No black levels provided by camera sensor helper";
> > +
> > +	if (!blackLevels || (tuningData.contains("R") &&
> 
> Is there any need to get context.camHelper->blackLevels() if the
> values are in the config file ?

The strategy now is to move away from the having the back levels in the
config. After discussion with Laurent, libcamera should at least know
the pedestal values. When we add additional measurements they could be
added to the tuning file as offsets. So camHelper->blackLevels() is the
default, the tuning file is kept for backwards compatibility and to be
able to specify the values when libcamera doesn't have that data.

> 
> 
> > +			     tuningData.contains("Gr") &&
> > +			     tuningData.contains("Gb") &&
> > +			     tuningData.contains("B"))) {
> > +		blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > +		blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > +		blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > +		blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > +
> > +		if (blackLevels)
> 
> Is this a Warning ? Same question above. I would Warn only if no BLC in
> sensor helpers AND in config file.

Yes, I would say so. Basically we are depricating the black levels from
the tuning file and moving them into sensor helpers. The first one
should be issued as it points to an incomplete implementation in
libcamera. The second one only shows up, if the fisrt one didn't show
up.

Best regards,
Stefan

> 
> > +			LOG(RkISP1Blc, Warning)
> > +				<< "Black levels overwritten by tuning file";
> > +	}
> >
> >  	tuningParameters_ = true;
> >
> > --
> > 2.43.0
> >
Stefan Klug July 2, 2024, 1:27 p.m. UTC | #4
On Tue, Jul 02, 2024 at 11:51:03AM +0100, Kieran Bingham wrote:
> Quoting Jacopo Mondi (2024-07-02 09:00:44)
> > Hi Stefan
> > 
> > On Mon, Jul 01, 2024 at 04:38:26PM GMT, Stefan Klug wrote:
> > > The black levels from the camera sensor helper are then used to do the
> > > black level correction. Black levels can still be overwritten by the
> > > tuning file.
> > >
> > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > > ---
> > >  src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
> > >  1 file changed, 24 insertions(+), 4 deletions(-)
> > >
> > > diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> > > index d2e743541c99..0c39c3b47da5 100644
> > > --- a/src/ipa/rkisp1/algorithms/blc.cpp
> > > +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> > > @@ -46,10 +46,30 @@ BlackLevelCorrection::BlackLevelCorrection()
> > >  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
> > >                              const YamlObject &tuningData)
> > >  {
> > > -     blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > -     blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > -     blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > -     blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > +     auto blackLevels = context.camHelper->blackLevels();
> > > +     if (blackLevels) {
> > > +             Span<const int32_t, 4> levels = *blackLevels;
> > > +             blackLevelRed_ = levels[0];
> > > +             blackLevelGreenR_ = levels[1];
> > > +             blackLevelGreenB_ = levels[2];
> > > +             blackLevelBlue_ = levels[3];
> > > +     } else
> > > +             LOG(RkISP1Blc, Warning)
> > > +                     << "No black levels provided by camera sensor helper";
> > > +
> > > +     if (!blackLevels || (tuningData.contains("R") &&
> > 
> > Is there any need to get context.camHelper->blackLevels() if the
> > values are in the config file ?
> > 
> > 
> > > +                          tuningData.contains("Gr") &&
> > > +                          tuningData.contains("Gb") &&
> > > +                          tuningData.contains("B"))) {
> > > +             blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > +             blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > +             blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > +             blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> 
> Why 256 here? Does a value have to be specified?

Digging into the git history... these were taken from the imx219 tuning
file (and scaled to 12bits) as sensible defaults. We could either keep
them or error out. My strategy was to stay compatible with existing
tuning files. If we go the "Either correct data or fail" route, we
should remove the defaults.

> 
> Maybe we should move a parse black level helper out to some common yaml
> helpers (Do we have anywhere for that yet, that can read defined
> libcamera types from yaml?) ... and it should only return all values if
> all of them are present and parsed correctly - otherwise R=3200, Gr=256,
> Gb=256, B=256 probably wouldn't make sense?

No that wouldn't make much sense, but is the old behaviour :-). I can
have a look there.

> 
> 
> 
> > > +
> > > +             if (blackLevels)
> > 
> > Is this a Warning ? Same question above. I would Warn only if no BLC in
> > sensor helpers AND in config file.
> > 
> > > +                     LOG(RkISP1Blc, Warning)
> > > +                             << "Black levels overwritten by tuning file";
> 
> Yes, I think the tuning file should take precedence, so I don't think
> it's a warning. Perhaps a debug print to say which values get loaded and
> where from ?

As said in my answer to Jacopo, the warning was on purpose, but I'm fine
with making it a debug output (or info?)

Cheers,
Stefan

> 
> 
> > > +     }
> > >
> > >       tuningParameters_ = true;
> > >
> > > --
> > > 2.43.0
> > >
Laurent Pinchart July 2, 2024, 2:50 p.m. UTC | #5
On Tue, Jul 02, 2024 at 03:27:56PM +0200, Stefan Klug wrote:
> On Tue, Jul 02, 2024 at 11:51:03AM +0100, Kieran Bingham wrote:
> > Quoting Jacopo Mondi (2024-07-02 09:00:44)
> > > On Mon, Jul 01, 2024 at 04:38:26PM GMT, Stefan Klug wrote:
> > > > The black levels from the camera sensor helper are then used to do the
> > > > black level correction. Black levels can still be overwritten by the
> > > > tuning file.

The commit message should be readable independently from the subject
line. The first sentence here sounds weird when you do so. I feel
obliged to point to the obligatory https://cbea.ms/git-commit/ and to
emphasize the "why" of rule 7 :-)

> > > >
> > > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > > > ---
> > > >  src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
> > > >  1 file changed, 24 insertions(+), 4 deletions(-)
> > > >
> > > > diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > index d2e743541c99..0c39c3b47da5 100644
> > > > --- a/src/ipa/rkisp1/algorithms/blc.cpp
> > > > +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > @@ -46,10 +46,30 @@ BlackLevelCorrection::BlackLevelCorrection()
> > > >  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
> > > >                              const YamlObject &tuningData)
> > > >  {
> > > > -     blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > -     blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > -     blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > -     blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > > +     auto blackLevels = context.camHelper->blackLevels();
> > > > +     if (blackLevels) {
> > > > +             Span<const int32_t, 4> levels = *blackLevels;
> > > > +             blackLevelRed_ = levels[0];
> > > > +             blackLevelGreenR_ = levels[1];
> > > > +             blackLevelGreenB_ = levels[2];
> > > > +             blackLevelBlue_ = levels[3];
> > > > +     } else
> > > > +             LOG(RkISP1Blc, Warning)
> > > > +                     << "No black levels provided by camera sensor helper";

			<< "No black levels provided by camera sensor helper, please fix";

Another form of \todo. Maybe we ned a Fixme log level :-)

> > > > +
> > > > +     if (!blackLevels || (tuningData.contains("R") &&
> > > 
> > > Is there any need to get context.camHelper->blackLevels() if the
> > > values are in the config file ?
> > > 
> > > > +                          tuningData.contains("Gr") &&
> > > > +                          tuningData.contains("Gb") &&
> > > > +                          tuningData.contains("B"))) {
> > > > +             blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > +             blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > +             blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > +             blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > 
> > Why 256 here? Does a value have to be specified?
> 
> Digging into the git history... these were taken from the imx219 tuning
> file (and scaled to 12bits) as sensible defaults. We could either keep
> them or error out. My strategy was to stay compatible with existing
> tuning files. If we go the "Either correct data or fail" route, we
> should remove the defaults.

We use defaults here in two cases:

- The camera sensor helper doesn't provide black levels, so we fall back
  to the tuning file for backward compatibility. We should keep the
  current behaviour in that case.

- The keys are provided in the tuning file but the data is incorrect.
  That's an error. We can error out, or pick a default, I don't mind
  much either way. Given that the first case points to keeping the
  current behaviour, using the same defaults without printing any
  message seems the simplest option.

> > Maybe we should move a parse black level helper out to some common yaml
> > helpers (Do we have anywhere for that yet, that can read defined
> > libcamera types from yaml?) ... and it should only return all values if
> > all of them are present and parsed correctly - otherwise R=3200, Gr=256,
> > Gb=256, B=256 probably wouldn't make sense?
> 
> No that wouldn't make much sense, but is the old behaviour :-). I can
> have a look there.
> 
> > > > +
> > > > +             if (blackLevels)
> > > 
> > > Is this a Warning ? Same question above. I would Warn only if no BLC in
> > > sensor helpers AND in config file.
> > > 
> > > > +                     LOG(RkISP1Blc, Warning)
> > > > +                             << "Black levels overwritten by tuning file";
> > 
> > Yes, I think the tuning file should take precedence, so I don't think
> > it's a warning. Perhaps a debug print to say which values get loaded and
> > where from ?
> 
> As said in my answer to Jacopo, the warning was on purpose, but I'm fine
> with making it a debug output (or info?)

Maybe we can make it clearer:

		LOG(RkISP1Blc, Warning)
			<< "Deprecated: black levels overwritten by tuning file";

Do we need a Deprecated log level ? :-)

And now that I wrote that, I wonder if we need to preserve this
behaviour. The goal, when I discussed it with Stefan, was to avoid
breaking existing setups using out-of-tree tuning data that contains
black levels different from the data pedestal provided by the camera
sensor helpers. One reason for doing is is to take dark currents into
account, and I'm not sure anyone would have done this kind of tuning
given that we didn't provide any tooling to measure the black level.
Another reason would be to tweak the values "just because". Given how
we're evolving the black level compensation implementation (not just in
the algorithm but also in the tuning tools), I'm tempted to say we
shouldn't preserve backward compatibility here.

Also, would the following express the logic more clearly ?

	std::optional<int16_t> levelRed = tuningData.contains("R").get<int16_t>;
	std::optional<int16_t> levelGreenR = tuningData.contains("Gr").get<int16_t>;
	std::optional<int16_t> levelGreenB = tuningData.contains("Gb").get<int16_t>;
	std::optional<int16_t> levelBlue = tuningData.contains("B").get<int16_t>;

	bool tuningHasLevels = levelRed && levelGreenR && levelGreenB && levelBlue;

	auto blackLevels = context.camHelper->blackLevels();

	if (!blackLevels) {
		/*
		 * Not all camera sensor helpers have been updated with black
		 * levels. Print a warning and fall back to the levels from the
		 * tuning data to preserve backward compatibility. This should
		 * be removed once allhelpers provide the data.
		 */
		LOG(RkISP1Blc, Warning)
			<< "No black levels provided by camera sensor helper, please fix";

		blackLevelRed_ = levelRed.value_or(256);
		blackLevelGreenR_ = levelGreenR.value_or(256);
		blackLevelGreenB_ = levelGreenB.value_or(256);
		blackLevelBlue_ = levelBlue.value_or(256);
	} else if (tuningHasLevels) {
		/*
		 * If black levels are provided in the tuning file, use them to
		 * avoid breaking existing camera tuning. This is deprecated and
		 * will be removed.
		 */
		LOG(RkISP1Blc, Warning)
			<< "Deprecated: black levels overwritten by tuning file";

		blackLevelRed_ = *levelRed;
		blackLevelGreenR_ = *levelGreenR;
		blackLevelGreenB_ = *levelGreenB;
		blackLevelBlue_ = *levelBlue;
	} else {
		Span<const int32_t, 4> levels = *blackLevels;
		blackLevelRed_ = levels[0];
		blackLevelGreenR_ = levels[1];
		blackLevelGreenB_ = levels[2];
		blackLevelBlue_ = levels[3];
	}

If we decide to drop the second case then it could be simplified to

	auto blackLevels = context.camHelper->blackLevels();

	if (blackLevels) {
		Span<const int32_t, 4> levels = *blackLevels;
		blackLevelRed_ = levels[0];
		blackLevelGreenR_ = levels[1];
		blackLevelGreenB_ = levels[2];
		blackLevelBlue_ = levels[3];
	} else {
		/*
		 * Not all camera sensor helpers have been updated with black
		 * levels. Print a warning and fall back to the levels from the
		 * tuning data to preserve backward compatibility. This should
		 * be removed once allhelpers provide the data.
		 */
		LOG(RkISP1Blc, Warning)
			<< "No black levels provided by camera sensor helper, please fix";

		blackLevelRed_ = tuningData["R"].get<int16_t>(256);
		blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
		blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
		blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
	}

> > > > +     }
> > > >
> > > >       tuningParameters_ = true;
> > > >
Stefan Klug July 3, 2024, 7:30 a.m. UTC | #6
Hi Laurent,

On Tue, Jul 02, 2024 at 05:50:25PM +0300, Laurent Pinchart wrote:
> On Tue, Jul 02, 2024 at 03:27:56PM +0200, Stefan Klug wrote:
> > On Tue, Jul 02, 2024 at 11:51:03AM +0100, Kieran Bingham wrote:
> > > Quoting Jacopo Mondi (2024-07-02 09:00:44)
> > > > On Mon, Jul 01, 2024 at 04:38:26PM GMT, Stefan Klug wrote:
> > > > > The black levels from the camera sensor helper are then used to do the
> > > > > black level correction. Black levels can still be overwritten by the
> > > > > tuning file.
> 
> The commit message should be readable independently from the subject
> line. The first sentence here sounds weird when you do so. I feel
> obliged to point to the obligatory https://cbea.ms/git-commit/ and to
> emphasize the "why" of rule 7 :-)
> 
> > > > >
> > > > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > > > > ---
> > > > >  src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
> > > > >  1 file changed, 24 insertions(+), 4 deletions(-)
> > > > >
> > > > > diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > index d2e743541c99..0c39c3b47da5 100644
> > > > > --- a/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > @@ -46,10 +46,30 @@ BlackLevelCorrection::BlackLevelCorrection()
> > > > >  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
> > > > >                              const YamlObject &tuningData)
> > > > >  {
> > > > > -     blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > > -     blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > > -     blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > > -     blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > > > +     auto blackLevels = context.camHelper->blackLevels();
> > > > > +     if (blackLevels) {
> > > > > +             Span<const int32_t, 4> levels = *blackLevels;
> > > > > +             blackLevelRed_ = levels[0];
> > > > > +             blackLevelGreenR_ = levels[1];
> > > > > +             blackLevelGreenB_ = levels[2];
> > > > > +             blackLevelBlue_ = levels[3];
> > > > > +     } else
> > > > > +             LOG(RkISP1Blc, Warning)
> > > > > +                     << "No black levels provided by camera sensor helper";
> 
> 			<< "No black levels provided by camera sensor helper, please fix";
> 
> Another form of \todo. Maybe we ned a Fixme log level :-)
> 
> > > > > +
> > > > > +     if (!blackLevels || (tuningData.contains("R") &&
> > > > 
> > > > Is there any need to get context.camHelper->blackLevels() if the
> > > > values are in the config file ?
> > > > 
> > > > > +                          tuningData.contains("Gr") &&
> > > > > +                          tuningData.contains("Gb") &&
> > > > > +                          tuningData.contains("B"))) {
> > > > > +             blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > > +             blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > > +             blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > > +             blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > 
> > > Why 256 here? Does a value have to be specified?
> > 
> > Digging into the git history... these were taken from the imx219 tuning
> > file (and scaled to 12bits) as sensible defaults. We could either keep
> > them or error out. My strategy was to stay compatible with existing
> > tuning files. If we go the "Either correct data or fail" route, we
> > should remove the defaults.
> 
> We use defaults here in two cases:
> 
> - The camera sensor helper doesn't provide black levels, so we fall back
>   to the tuning file for backward compatibility. We should keep the
>   current behaviour in that case.
> 
> - The keys are provided in the tuning file but the data is incorrect.
>   That's an error. We can error out, or pick a default, I don't mind
>   much either way. Given that the first case points to keeping the
>   current behaviour, using the same defaults without printing any
>   message seems the simplest option.
> 
> > > Maybe we should move a parse black level helper out to some common yaml
> > > helpers (Do we have anywhere for that yet, that can read defined
> > > libcamera types from yaml?) ... and it should only return all values if
> > > all of them are present and parsed correctly - otherwise R=3200, Gr=256,
> > > Gb=256, B=256 probably wouldn't make sense?
> > 
> > No that wouldn't make much sense, but is the old behaviour :-). I can
> > have a look there.
> > 
> > > > > +
> > > > > +             if (blackLevels)
> > > > 
> > > > Is this a Warning ? Same question above. I would Warn only if no BLC in
> > > > sensor helpers AND in config file.
> > > > 
> > > > > +                     LOG(RkISP1Blc, Warning)
> > > > > +                             << "Black levels overwritten by tuning file";
> > > 
> > > Yes, I think the tuning file should take precedence, so I don't think
> > > it's a warning. Perhaps a debug print to say which values get loaded and
> > > where from ?
> > 
> > As said in my answer to Jacopo, the warning was on purpose, but I'm fine
> > with making it a debug output (or info?)
> 
> Maybe we can make it clearer:
> 
> 		LOG(RkISP1Blc, Warning)
> 			<< "Deprecated: black levels overwritten by tuning file";
> 
> Do we need a Deprecated log level ? :-)
> 
> And now that I wrote that, I wonder if we need to preserve this
> behaviour. The goal, when I discussed it with Stefan, was to avoid
> breaking existing setups using out-of-tree tuning data that contains
> black levels different from the data pedestal provided by the camera
> sensor helpers. One reason for doing is is to take dark currents into
> account, and I'm not sure anyone would have done this kind of tuning
> given that we didn't provide any tooling to measure the black level.
> Another reason would be to tweak the values "just because". Given how
> we're evolving the black level compensation implementation (not just in
> the algorithm but also in the tuning tools), I'm tempted to say we
> shouldn't preserve backward compatibility here.
> 
> Also, would the following express the logic more clearly ?
> 
> 	std::optional<int16_t> levelRed = tuningData.contains("R").get<int16_t>;
> 	std::optional<int16_t> levelGreenR = tuningData.contains("Gr").get<int16_t>;
> 	std::optional<int16_t> levelGreenB = tuningData.contains("Gb").get<int16_t>;
> 	std::optional<int16_t> levelBlue = tuningData.contains("B").get<int16_t>;
> 
> 	bool tuningHasLevels = levelRed && levelGreenR && levelGreenB && levelBlue;
> 
> 	auto blackLevels = context.camHelper->blackLevels();
> 
> 	if (!blackLevels) {
> 		/*
> 		 * Not all camera sensor helpers have been updated with black
> 		 * levels. Print a warning and fall back to the levels from the
> 		 * tuning data to preserve backward compatibility. This should
> 		 * be removed once allhelpers provide the data.
> 		 */
> 		LOG(RkISP1Blc, Warning)
> 			<< "No black levels provided by camera sensor helper, please fix";
> 
> 		blackLevelRed_ = levelRed.value_or(256);
> 		blackLevelGreenR_ = levelGreenR.value_or(256);
> 		blackLevelGreenB_ = levelGreenB.value_or(256);
> 		blackLevelBlue_ = levelBlue.value_or(256);
> 	} else if (tuningHasLevels) {
> 		/*
> 		 * If black levels are provided in the tuning file, use them to
> 		 * avoid breaking existing camera tuning. This is deprecated and
> 		 * will be removed.
> 		 */
> 		LOG(RkISP1Blc, Warning)
> 			<< "Deprecated: black levels overwritten by tuning file";
> 
> 		blackLevelRed_ = *levelRed;
> 		blackLevelGreenR_ = *levelGreenR;
> 		blackLevelGreenB_ = *levelGreenB;
> 		blackLevelBlue_ = *levelBlue;
> 	} else {
> 		Span<const int32_t, 4> levels = *blackLevels;
> 		blackLevelRed_ = levels[0];
> 		blackLevelGreenR_ = levels[1];
> 		blackLevelGreenB_ = levels[2];
> 		blackLevelBlue_ = levels[3];
> 	}

I like that proposal. It is definitely easier to follow. While
implementing that, I realized that we are breaking backwards
compatibility anyways as we should interpret the values differently
(based on 16bit width instead of 12bit). To stay really backwards
compatible we could add a "referenceBitDepth" field to the tuning data
and fall back to 12bit if that is not set (I implemented that in the
companding branch). But honestly I would rather switch to 16bit and
break the old files instead of carrying around that additional flag.

When we break the old files, we could directly go to the solution you
proposed below.

Opinions?

Best regards,
Stefan

> 
> If we decide to drop the second case then it could be simplified to
> 
> 	auto blackLevels = context.camHelper->blackLevels();
> 
> 	if (blackLevels) {
> 		Span<const int32_t, 4> levels = *blackLevels;
> 		blackLevelRed_ = levels[0];
> 		blackLevelGreenR_ = levels[1];
> 		blackLevelGreenB_ = levels[2];
> 		blackLevelBlue_ = levels[3];
> 	} else {
> 		/*
> 		 * Not all camera sensor helpers have been updated with black
> 		 * levels. Print a warning and fall back to the levels from the
> 		 * tuning data to preserve backward compatibility. This should
> 		 * be removed once allhelpers provide the data.
> 		 */
> 		LOG(RkISP1Blc, Warning)
> 			<< "No black levels provided by camera sensor helper, please fix";
> 
> 		blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> 		blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> 		blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> 		blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> 	}
> 
> > > > > +     }
> > > > >
> > > > >       tuningParameters_ = true;
> > > > >
> 
> -- 
> Regards,
> 
> Laurent Pinchart
Laurent Pinchart July 3, 2024, 8:02 a.m. UTC | #7
On Wed, Jul 03, 2024 at 09:30:19AM +0200, Stefan Klug wrote:
> Hi Laurent,
> 
> On Tue, Jul 02, 2024 at 05:50:25PM +0300, Laurent Pinchart wrote:
> > On Tue, Jul 02, 2024 at 03:27:56PM +0200, Stefan Klug wrote:
> > > On Tue, Jul 02, 2024 at 11:51:03AM +0100, Kieran Bingham wrote:
> > > > Quoting Jacopo Mondi (2024-07-02 09:00:44)
> > > > > On Mon, Jul 01, 2024 at 04:38:26PM GMT, Stefan Klug wrote:
> > > > > > The black levels from the camera sensor helper are then used to do the
> > > > > > black level correction. Black levels can still be overwritten by the
> > > > > > tuning file.
> > 
> > The commit message should be readable independently from the subject
> > line. The first sentence here sounds weird when you do so. I feel
> > obliged to point to the obligatory https://cbea.ms/git-commit/ and to
> > emphasize the "why" of rule 7 :-)
> > 
> > > > > >
> > > > > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > > > > > ---
> > > > > >  src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
> > > > > >  1 file changed, 24 insertions(+), 4 deletions(-)
> > > > > >
> > > > > > diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > > index d2e743541c99..0c39c3b47da5 100644
> > > > > > --- a/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > > +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > > @@ -46,10 +46,30 @@ BlackLevelCorrection::BlackLevelCorrection()
> > > > > >  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
> > > > > >                              const YamlObject &tuningData)
> > > > > >  {
> > > > > > -     blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > > > -     blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > > > -     blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > > > -     blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > > > > +     auto blackLevels = context.camHelper->blackLevels();
> > > > > > +     if (blackLevels) {
> > > > > > +             Span<const int32_t, 4> levels = *blackLevels;
> > > > > > +             blackLevelRed_ = levels[0];
> > > > > > +             blackLevelGreenR_ = levels[1];
> > > > > > +             blackLevelGreenB_ = levels[2];
> > > > > > +             blackLevelBlue_ = levels[3];
> > > > > > +     } else
> > > > > > +             LOG(RkISP1Blc, Warning)
> > > > > > +                     << "No black levels provided by camera sensor helper";
> > 
> > 			<< "No black levels provided by camera sensor helper, please fix";
> > 
> > Another form of \todo. Maybe we ned a Fixme log level :-)
> > 
> > > > > > +
> > > > > > +     if (!blackLevels || (tuningData.contains("R") &&
> > > > > 
> > > > > Is there any need to get context.camHelper->blackLevels() if the
> > > > > values are in the config file ?
> > > > > 
> > > > > > +                          tuningData.contains("Gr") &&
> > > > > > +                          tuningData.contains("Gb") &&
> > > > > > +                          tuningData.contains("B"))) {
> > > > > > +             blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > > > +             blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > > > +             blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > > > +             blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > > 
> > > > Why 256 here? Does a value have to be specified?
> > > 
> > > Digging into the git history... these were taken from the imx219 tuning
> > > file (and scaled to 12bits) as sensible defaults. We could either keep
> > > them or error out. My strategy was to stay compatible with existing
> > > tuning files. If we go the "Either correct data or fail" route, we
> > > should remove the defaults.
> > 
> > We use defaults here in two cases:
> > 
> > - The camera sensor helper doesn't provide black levels, so we fall back
> >   to the tuning file for backward compatibility. We should keep the
> >   current behaviour in that case.
> > 
> > - The keys are provided in the tuning file but the data is incorrect.
> >   That's an error. We can error out, or pick a default, I don't mind
> >   much either way. Given that the first case points to keeping the
> >   current behaviour, using the same defaults without printing any
> >   message seems the simplest option.
> > 
> > > > Maybe we should move a parse black level helper out to some common yaml
> > > > helpers (Do we have anywhere for that yet, that can read defined
> > > > libcamera types from yaml?) ... and it should only return all values if
> > > > all of them are present and parsed correctly - otherwise R=3200, Gr=256,
> > > > Gb=256, B=256 probably wouldn't make sense?
> > > 
> > > No that wouldn't make much sense, but is the old behaviour :-). I can
> > > have a look there.
> > > 
> > > > > > +
> > > > > > +             if (blackLevels)
> > > > > 
> > > > > Is this a Warning ? Same question above. I would Warn only if no BLC in
> > > > > sensor helpers AND in config file.
> > > > > 
> > > > > > +                     LOG(RkISP1Blc, Warning)
> > > > > > +                             << "Black levels overwritten by tuning file";
> > > > 
> > > > Yes, I think the tuning file should take precedence, so I don't think
> > > > it's a warning. Perhaps a debug print to say which values get loaded and
> > > > where from ?
> > > 
> > > As said in my answer to Jacopo, the warning was on purpose, but I'm fine
> > > with making it a debug output (or info?)
> > 
> > Maybe we can make it clearer:
> > 
> > 		LOG(RkISP1Blc, Warning)
> > 			<< "Deprecated: black levels overwritten by tuning file";
> > 
> > Do we need a Deprecated log level ? :-)
> > 
> > And now that I wrote that, I wonder if we need to preserve this
> > behaviour. The goal, when I discussed it with Stefan, was to avoid
> > breaking existing setups using out-of-tree tuning data that contains
> > black levels different from the data pedestal provided by the camera
> > sensor helpers. One reason for doing is is to take dark currents into
> > account, and I'm not sure anyone would have done this kind of tuning
> > given that we didn't provide any tooling to measure the black level.
> > Another reason would be to tweak the values "just because". Given how
> > we're evolving the black level compensation implementation (not just in
> > the algorithm but also in the tuning tools), I'm tempted to say we
> > shouldn't preserve backward compatibility here.
> > 
> > Also, would the following express the logic more clearly ?
> > 
> > 	std::optional<int16_t> levelRed = tuningData.contains("R").get<int16_t>;
> > 	std::optional<int16_t> levelGreenR = tuningData.contains("Gr").get<int16_t>;
> > 	std::optional<int16_t> levelGreenB = tuningData.contains("Gb").get<int16_t>;
> > 	std::optional<int16_t> levelBlue = tuningData.contains("B").get<int16_t>;
> > 
> > 	bool tuningHasLevels = levelRed && levelGreenR && levelGreenB && levelBlue;
> > 
> > 	auto blackLevels = context.camHelper->blackLevels();
> > 
> > 	if (!blackLevels) {
> > 		/*
> > 		 * Not all camera sensor helpers have been updated with black
> > 		 * levels. Print a warning and fall back to the levels from the
> > 		 * tuning data to preserve backward compatibility. This should
> > 		 * be removed once allhelpers provide the data.
> > 		 */
> > 		LOG(RkISP1Blc, Warning)
> > 			<< "No black levels provided by camera sensor helper, please fix";
> > 
> > 		blackLevelRed_ = levelRed.value_or(256);
> > 		blackLevelGreenR_ = levelGreenR.value_or(256);
> > 		blackLevelGreenB_ = levelGreenB.value_or(256);
> > 		blackLevelBlue_ = levelBlue.value_or(256);
> > 	} else if (tuningHasLevels) {
> > 		/*
> > 		 * If black levels are provided in the tuning file, use them to
> > 		 * avoid breaking existing camera tuning. This is deprecated and
> > 		 * will be removed.
> > 		 */
> > 		LOG(RkISP1Blc, Warning)
> > 			<< "Deprecated: black levels overwritten by tuning file";
> > 
> > 		blackLevelRed_ = *levelRed;
> > 		blackLevelGreenR_ = *levelGreenR;
> > 		blackLevelGreenB_ = *levelGreenB;
> > 		blackLevelBlue_ = *levelBlue;
> > 	} else {
> > 		Span<const int32_t, 4> levels = *blackLevels;
> > 		blackLevelRed_ = levels[0];
> > 		blackLevelGreenR_ = levels[1];
> > 		blackLevelGreenB_ = levels[2];
> > 		blackLevelBlue_ = levels[3];
> > 	}
> 
> I like that proposal. It is definitely easier to follow. While
> implementing that, I realized that we are breaking backwards
> compatibility anyways as we should interpret the values differently
> (based on 16bit width instead of 12bit). To stay really backwards
> compatible we could add a "referenceBitDepth" field to the tuning data
> and fall back to 12bit if that is not set (I implemented that in the
> companding branch). But honestly I would rather switch to 16bit and
> break the old files instead of carrying around that additional flag.
> 
> When we break the old files, we could directly go to the solution you
> proposed below.
> 
> Opinions?

I'm fine with that, but I think we should then add black levels to the
camera sensor helpers for all sensors that currently have the levels set
in tuning files. That's OV4689, OV5640 and IMX219.

> > If we decide to drop the second case then it could be simplified to
> > 
> > 	auto blackLevels = context.camHelper->blackLevels();
> > 
> > 	if (blackLevels) {
> > 		Span<const int32_t, 4> levels = *blackLevels;
> > 		blackLevelRed_ = levels[0];
> > 		blackLevelGreenR_ = levels[1];
> > 		blackLevelGreenB_ = levels[2];
> > 		blackLevelBlue_ = levels[3];
> > 	} else {
> > 		/*
> > 		 * Not all camera sensor helpers have been updated with black
> > 		 * levels. Print a warning and fall back to the levels from the
> > 		 * tuning data to preserve backward compatibility. This should
> > 		 * be removed once allhelpers provide the data.
> > 		 */
> > 		LOG(RkISP1Blc, Warning)
> > 			<< "No black levels provided by camera sensor helper, please fix";
> > 
> > 		blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > 		blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > 		blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > 		blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > 	}
> > 
> > > > > > +     }
> > > > > >
> > > > > >       tuningParameters_ = true;
> > > > > >
Kieran Bingham July 3, 2024, 8:28 a.m. UTC | #8
Quoting Stefan Klug (2024-07-03 08:30:19)
> Hi Laurent,
> 
> On Tue, Jul 02, 2024 at 05:50:25PM +0300, Laurent Pinchart wrote:
> > On Tue, Jul 02, 2024 at 03:27:56PM +0200, Stefan Klug wrote:
> > > On Tue, Jul 02, 2024 at 11:51:03AM +0100, Kieran Bingham wrote:
> > > > Quoting Jacopo Mondi (2024-07-02 09:00:44)
> > > > > On Mon, Jul 01, 2024 at 04:38:26PM GMT, Stefan Klug wrote:
> > > > > > The black levels from the camera sensor helper are then used to do the
> > > > > > black level correction. Black levels can still be overwritten by the
> > > > > > tuning file.
> > 
> > The commit message should be readable independently from the subject
> > line. The first sentence here sounds weird when you do so. I feel
> > obliged to point to the obligatory https://cbea.ms/git-commit/ and to
> > emphasize the "why" of rule 7 :-)
> > 
> > > > > >
> > > > > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > > > > > ---
> > > > > >  src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
> > > > > >  1 file changed, 24 insertions(+), 4 deletions(-)
> > > > > >
> > > > > > diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > > index d2e743541c99..0c39c3b47da5 100644
> > > > > > --- a/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > > +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > > @@ -46,10 +46,30 @@ BlackLevelCorrection::BlackLevelCorrection()
> > > > > >  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
> > > > > >                              const YamlObject &tuningData)
> > > > > >  {
> > > > > > -     blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > > > -     blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > > > -     blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > > > -     blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > > > > +     auto blackLevels = context.camHelper->blackLevels();
> > > > > > +     if (blackLevels) {
> > > > > > +             Span<const int32_t, 4> levels = *blackLevels;
> > > > > > +             blackLevelRed_ = levels[0];
> > > > > > +             blackLevelGreenR_ = levels[1];
> > > > > > +             blackLevelGreenB_ = levels[2];
> > > > > > +             blackLevelBlue_ = levels[3];
> > > > > > +     } else
> > > > > > +             LOG(RkISP1Blc, Warning)
> > > > > > +                     << "No black levels provided by camera sensor helper";
> > 
> >                       << "No black levels provided by camera sensor helper, please fix";
> > 
> > Another form of \todo. Maybe we ned a Fixme log level :-)
> > 
> > > > > > +
> > > > > > +     if (!blackLevels || (tuningData.contains("R") &&
> > > > > 
> > > > > Is there any need to get context.camHelper->blackLevels() if the
> > > > > values are in the config file ?
> > > > > 
> > > > > > +                          tuningData.contains("Gr") &&
> > > > > > +                          tuningData.contains("Gb") &&
> > > > > > +                          tuningData.contains("B"))) {
> > > > > > +             blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > > > +             blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > > > +             blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > > > +             blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > > 
> > > > Why 256 here? Does a value have to be specified?
> > > 
> > > Digging into the git history... these were taken from the imx219 tuning
> > > file (and scaled to 12bits) as sensible defaults. We could either keep
> > > them or error out. My strategy was to stay compatible with existing
> > > tuning files. If we go the "Either correct data or fail" route, we
> > > should remove the defaults.
> > 
> > We use defaults here in two cases:
> > 
> > - The camera sensor helper doesn't provide black levels, so we fall back
> >   to the tuning file for backward compatibility. We should keep the
> >   current behaviour in that case.
> > 
> > - The keys are provided in the tuning file but the data is incorrect.
> >   That's an error. We can error out, or pick a default, I don't mind
> >   much either way. Given that the first case points to keeping the
> >   current behaviour, using the same defaults without printing any
> >   message seems the simplest option.
> > 
> > > > Maybe we should move a parse black level helper out to some common yaml
> > > > helpers (Do we have anywhere for that yet, that can read defined
> > > > libcamera types from yaml?) ... and it should only return all values if
> > > > all of them are present and parsed correctly - otherwise R=3200, Gr=256,
> > > > Gb=256, B=256 probably wouldn't make sense?
> > > 
> > > No that wouldn't make much sense, but is the old behaviour :-). I can
> > > have a look there.
> > > 
> > > > > > +
> > > > > > +             if (blackLevels)
> > > > > 
> > > > > Is this a Warning ? Same question above. I would Warn only if no BLC in
> > > > > sensor helpers AND in config file.
> > > > > 
> > > > > > +                     LOG(RkISP1Blc, Warning)
> > > > > > +                             << "Black levels overwritten by tuning file";
> > > > 
> > > > Yes, I think the tuning file should take precedence, so I don't think
> > > > it's a warning. Perhaps a debug print to say which values get loaded and
> > > > where from ?
> > > 
> > > As said in my answer to Jacopo, the warning was on purpose, but I'm fine
> > > with making it a debug output (or info?)
> > 
> > Maybe we can make it clearer:
> > 
> >               LOG(RkISP1Blc, Warning)
> >                       << "Deprecated: black levels overwritten by tuning file";
> > 
> > Do we need a Deprecated log level ? :-)
> > 
> > And now that I wrote that, I wonder if we need to preserve this
> > behaviour. The goal, when I discussed it with Stefan, was to avoid
> > breaking existing setups using out-of-tree tuning data that contains
> > black levels different from the data pedestal provided by the camera
> > sensor helpers. One reason for doing is is to take dark currents into
> > account, and I'm not sure anyone would have done this kind of tuning
> > given that we didn't provide any tooling to measure the black level.
> > Another reason would be to tweak the values "just because". Given how
> > we're evolving the black level compensation implementation (not just in
> > the algorithm but also in the tuning tools), I'm tempted to say we
> > shouldn't preserve backward compatibility here.
> > 
> > Also, would the following express the logic more clearly ?
> > 
> >       std::optional<int16_t> levelRed = tuningData.contains("R").get<int16_t>;
> >       std::optional<int16_t> levelGreenR = tuningData.contains("Gr").get<int16_t>;
> >       std::optional<int16_t> levelGreenB = tuningData.contains("Gb").get<int16_t>;
> >       std::optional<int16_t> levelBlue = tuningData.contains("B").get<int16_t>;
> > 
> >       bool tuningHasLevels = levelRed && levelGreenR && levelGreenB && levelBlue;
> > 
> >       auto blackLevels = context.camHelper->blackLevels();
> > 
> >       if (!blackLevels) {
> >               /*
> >                * Not all camera sensor helpers have been updated with black
> >                * levels. Print a warning and fall back to the levels from the
> >                * tuning data to preserve backward compatibility. This should
> >                * be removed once allhelpers provide the data.
> >                */
> >               LOG(RkISP1Blc, Warning)
> >                       << "No black levels provided by camera sensor helper, please fix";
> > 
> >               blackLevelRed_ = levelRed.value_or(256);
> >               blackLevelGreenR_ = levelGreenR.value_or(256);
> >               blackLevelGreenB_ = levelGreenB.value_or(256);
> >               blackLevelBlue_ = levelBlue.value_or(256);
> >       } else if (tuningHasLevels) {
> >               /*
> >                * If black levels are provided in the tuning file, use them to
> >                * avoid breaking existing camera tuning. This is deprecated and
> >                * will be removed.
> >                */
> >               LOG(RkISP1Blc, Warning)
> >                       << "Deprecated: black levels overwritten by tuning file";
> > 
> >               blackLevelRed_ = *levelRed;
> >               blackLevelGreenR_ = *levelGreenR;
> >               blackLevelGreenB_ = *levelGreenB;
> >               blackLevelBlue_ = *levelBlue;
> >       } else {
> >               Span<const int32_t, 4> levels = *blackLevels;
> >               blackLevelRed_ = levels[0];
> >               blackLevelGreenR_ = levels[1];
> >               blackLevelGreenB_ = levels[2];
> >               blackLevelBlue_ = levels[3];
> >       }
> 
> I like that proposal. It is definitely easier to follow. While
> implementing that, I realized that we are breaking backwards
> compatibility anyways as we should interpret the values differently
> (based on 16bit width instead of 12bit). To stay really backwards
> compatible we could add a "referenceBitDepth" field to the tuning data
> and fall back to 12bit if that is not set (I implemented that in the
> companding branch). But honestly I would rather switch to 16bit and
> break the old files instead of carrying around that additional flag.
> 
> When we break the old files, we could directly go to the solution you
> proposed below.

I didn't think we needed to maintain any backwards compatibilty here, as
I thought we didn't have BLC fully implemented - but now I realise it
was/is working on rk3399 ... so that's more awkward.

It's 'unlikely' that anyone else is specifically using external tuning
files, but in theory we don't know - and others could be.

Then if we were going to do an ABI jump on libcamera, I would say it's
even easier to justify breaking the previous values too ... but I was
actually anticipating the next libcamera release in ~2 weeks to be the
first opportunity to make a non-ABI breaking release!

I'm so conflicted ;D

We could also keep the backwards compatiblity for 'one' release if that
helps with a warning, and clean the code up after the next libcamera
tag?

--
Kieran



> 
> Opinions?
> 
> Best regards,
> Stefan
> 
> > 
> > If we decide to drop the second case then it could be simplified to
> > 
> >       auto blackLevels = context.camHelper->blackLevels();
> > 
> >       if (blackLevels) {
> >               Span<const int32_t, 4> levels = *blackLevels;
> >               blackLevelRed_ = levels[0];
> >               blackLevelGreenR_ = levels[1];
> >               blackLevelGreenB_ = levels[2];
> >               blackLevelBlue_ = levels[3];
> >       } else {
> >               /*
> >                * Not all camera sensor helpers have been updated with black
> >                * levels. Print a warning and fall back to the levels from the
> >                * tuning data to preserve backward compatibility. This should
> >                * be removed once allhelpers provide the data.
> >                */
> >               LOG(RkISP1Blc, Warning)
> >                       << "No black levels provided by camera sensor helper, please fix";
> > 
> >               blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> >               blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> >               blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> >               blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> >       }
> > 
> > > > > > +     }
> > > > > >
> > > > > >       tuningParameters_ = true;
> > > > > >
> > 
> > -- 
> > Regards,
> > 
> > Laurent Pinchart
Kieran Bingham July 3, 2024, 8:30 a.m. UTC | #9
Quoting Laurent Pinchart (2024-07-03 09:02:06)
> On Wed, Jul 03, 2024 at 09:30:19AM +0200, Stefan Klug wrote:
> > Hi Laurent,
> > 
> > On Tue, Jul 02, 2024 at 05:50:25PM +0300, Laurent Pinchart wrote:
> > > On Tue, Jul 02, 2024 at 03:27:56PM +0200, Stefan Klug wrote:
> > > > On Tue, Jul 02, 2024 at 11:51:03AM +0100, Kieran Bingham wrote:
> > > > > Quoting Jacopo Mondi (2024-07-02 09:00:44)
> > > > > > On Mon, Jul 01, 2024 at 04:38:26PM GMT, Stefan Klug wrote:
> > > > > > > The black levels from the camera sensor helper are then used to do the
> > > > > > > black level correction. Black levels can still be overwritten by the
> > > > > > > tuning file.
> > > 
> > > The commit message should be readable independently from the subject
> > > line. The first sentence here sounds weird when you do so. I feel
> > > obliged to point to the obligatory https://cbea.ms/git-commit/ and to
> > > emphasize the "why" of rule 7 :-)
> > > 
> > > > > > >
> > > > > > > Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> > > > > > > ---
> > > > > > >  src/ipa/rkisp1/algorithms/blc.cpp | 28 ++++++++++++++++++++++++----
> > > > > > >  1 file changed, 24 insertions(+), 4 deletions(-)
> > > > > > >
> > > > > > > diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > > > index d2e743541c99..0c39c3b47da5 100644
> > > > > > > --- a/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > > > +++ b/src/ipa/rkisp1/algorithms/blc.cpp
> > > > > > > @@ -46,10 +46,30 @@ BlackLevelCorrection::BlackLevelCorrection()
> > > > > > >  int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
> > > > > > >                              const YamlObject &tuningData)
> > > > > > >  {
> > > > > > > -     blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > > > > -     blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > > > > -     blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > > > > -     blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > > > > > +     auto blackLevels = context.camHelper->blackLevels();
> > > > > > > +     if (blackLevels) {
> > > > > > > +             Span<const int32_t, 4> levels = *blackLevels;
> > > > > > > +             blackLevelRed_ = levels[0];
> > > > > > > +             blackLevelGreenR_ = levels[1];
> > > > > > > +             blackLevelGreenB_ = levels[2];
> > > > > > > +             blackLevelBlue_ = levels[3];
> > > > > > > +     } else
> > > > > > > +             LOG(RkISP1Blc, Warning)
> > > > > > > +                     << "No black levels provided by camera sensor helper";
> > > 
> > >                     << "No black levels provided by camera sensor helper, please fix";
> > > 
> > > Another form of \todo. Maybe we ned a Fixme log level :-)
> > > 
> > > > > > > +
> > > > > > > +     if (!blackLevels || (tuningData.contains("R") &&
> > > > > > 
> > > > > > Is there any need to get context.camHelper->blackLevels() if the
> > > > > > values are in the config file ?
> > > > > > 
> > > > > > > +                          tuningData.contains("Gr") &&
> > > > > > > +                          tuningData.contains("Gb") &&
> > > > > > > +                          tuningData.contains("B"))) {
> > > > > > > +             blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > > > > > > +             blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > > > > > > +             blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > > > > > > +             blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > > > > 
> > > > > Why 256 here? Does a value have to be specified?
> > > > 
> > > > Digging into the git history... these were taken from the imx219 tuning
> > > > file (and scaled to 12bits) as sensible defaults. We could either keep
> > > > them or error out. My strategy was to stay compatible with existing
> > > > tuning files. If we go the "Either correct data or fail" route, we
> > > > should remove the defaults.
> > > 
> > > We use defaults here in two cases:
> > > 
> > > - The camera sensor helper doesn't provide black levels, so we fall back
> > >   to the tuning file for backward compatibility. We should keep the
> > >   current behaviour in that case.
> > > 
> > > - The keys are provided in the tuning file but the data is incorrect.
> > >   That's an error. We can error out, or pick a default, I don't mind
> > >   much either way. Given that the first case points to keeping the
> > >   current behaviour, using the same defaults without printing any
> > >   message seems the simplest option.
> > > 
> > > > > Maybe we should move a parse black level helper out to some common yaml
> > > > > helpers (Do we have anywhere for that yet, that can read defined
> > > > > libcamera types from yaml?) ... and it should only return all values if
> > > > > all of them are present and parsed correctly - otherwise R=3200, Gr=256,
> > > > > Gb=256, B=256 probably wouldn't make sense?
> > > > 
> > > > No that wouldn't make much sense, but is the old behaviour :-). I can
> > > > have a look there.
> > > > 
> > > > > > > +
> > > > > > > +             if (blackLevels)
> > > > > > 
> > > > > > Is this a Warning ? Same question above. I would Warn only if no BLC in
> > > > > > sensor helpers AND in config file.
> > > > > > 
> > > > > > > +                     LOG(RkISP1Blc, Warning)
> > > > > > > +                             << "Black levels overwritten by tuning file";
> > > > > 
> > > > > Yes, I think the tuning file should take precedence, so I don't think
> > > > > it's a warning. Perhaps a debug print to say which values get loaded and
> > > > > where from ?
> > > > 
> > > > As said in my answer to Jacopo, the warning was on purpose, but I'm fine
> > > > with making it a debug output (or info?)
> > > 
> > > Maybe we can make it clearer:
> > > 
> > >             LOG(RkISP1Blc, Warning)
> > >                     << "Deprecated: black levels overwritten by tuning file";
> > > 
> > > Do we need a Deprecated log level ? :-)
> > > 
> > > And now that I wrote that, I wonder if we need to preserve this
> > > behaviour. The goal, when I discussed it with Stefan, was to avoid
> > > breaking existing setups using out-of-tree tuning data that contains
> > > black levels different from the data pedestal provided by the camera
> > > sensor helpers. One reason for doing is is to take dark currents into
> > > account, and I'm not sure anyone would have done this kind of tuning
> > > given that we didn't provide any tooling to measure the black level.
> > > Another reason would be to tweak the values "just because". Given how
> > > we're evolving the black level compensation implementation (not just in
> > > the algorithm but also in the tuning tools), I'm tempted to say we
> > > shouldn't preserve backward compatibility here.
> > > 
> > > Also, would the following express the logic more clearly ?
> > > 
> > >     std::optional<int16_t> levelRed = tuningData.contains("R").get<int16_t>;
> > >     std::optional<int16_t> levelGreenR = tuningData.contains("Gr").get<int16_t>;
> > >     std::optional<int16_t> levelGreenB = tuningData.contains("Gb").get<int16_t>;
> > >     std::optional<int16_t> levelBlue = tuningData.contains("B").get<int16_t>;
> > > 
> > >     bool tuningHasLevels = levelRed && levelGreenR && levelGreenB && levelBlue;
> > > 
> > >     auto blackLevels = context.camHelper->blackLevels();
> > > 
> > >     if (!blackLevels) {
> > >             /*
> > >              * Not all camera sensor helpers have been updated with black
> > >              * levels. Print a warning and fall back to the levels from the
> > >              * tuning data to preserve backward compatibility. This should
> > >              * be removed once allhelpers provide the data.
> > >              */
> > >             LOG(RkISP1Blc, Warning)
> > >                     << "No black levels provided by camera sensor helper, please fix";
> > > 
> > >             blackLevelRed_ = levelRed.value_or(256);
> > >             blackLevelGreenR_ = levelGreenR.value_or(256);
> > >             blackLevelGreenB_ = levelGreenB.value_or(256);
> > >             blackLevelBlue_ = levelBlue.value_or(256);
> > >     } else if (tuningHasLevels) {
> > >             /*
> > >              * If black levels are provided in the tuning file, use them to
> > >              * avoid breaking existing camera tuning. This is deprecated and
> > >              * will be removed.
> > >              */
> > >             LOG(RkISP1Blc, Warning)
> > >                     << "Deprecated: black levels overwritten by tuning file";
> > > 
> > >             blackLevelRed_ = *levelRed;
> > >             blackLevelGreenR_ = *levelGreenR;
> > >             blackLevelGreenB_ = *levelGreenB;
> > >             blackLevelBlue_ = *levelBlue;
> > >     } else {
> > >             Span<const int32_t, 4> levels = *blackLevels;
> > >             blackLevelRed_ = levels[0];
> > >             blackLevelGreenR_ = levels[1];
> > >             blackLevelGreenB_ = levels[2];
> > >             blackLevelBlue_ = levels[3];
> > >     }
> > 
> > I like that proposal. It is definitely easier to follow. While
> > implementing that, I realized that we are breaking backwards
> > compatibility anyways as we should interpret the values differently
> > (based on 16bit width instead of 12bit). To stay really backwards
> > compatible we could add a "referenceBitDepth" field to the tuning data
> > and fall back to 12bit if that is not set (I implemented that in the
> > companding branch). But honestly I would rather switch to 16bit and
> > break the old files instead of carrying around that additional flag.
> > 
> > When we break the old files, we could directly go to the solution you
> > proposed below.
> > 
> > Opinions?
> 
> I'm fine with that, but I think we should then add black levels to the
> camera sensor helpers for all sensors that currently have the levels set
> in tuning files. That's OV4689, OV5640 and IMX219.
> 

To be clear, following my reply on this too - I don't object to going
straight to the 'right' implementation here.

Storing Black levels as 16 bit consistently makes more sense to me than
varied bit depths.

--
Kieran

> > > If we decide to drop the second case then it could be simplified to
> > > 
> > >     auto blackLevels = context.camHelper->blackLevels();
> > > 
> > >     if (blackLevels) {
> > >             Span<const int32_t, 4> levels = *blackLevels;
> > >             blackLevelRed_ = levels[0];
> > >             blackLevelGreenR_ = levels[1];
> > >             blackLevelGreenB_ = levels[2];
> > >             blackLevelBlue_ = levels[3];
> > >     } else {
> > >             /*
> > >              * Not all camera sensor helpers have been updated with black
> > >              * levels. Print a warning and fall back to the levels from the
> > >              * tuning data to preserve backward compatibility. This should
> > >              * be removed once allhelpers provide the data.
> > >              */
> > >             LOG(RkISP1Blc, Warning)
> > >                     << "No black levels provided by camera sensor helper, please fix";
> > > 
> > >             blackLevelRed_ = tuningData["R"].get<int16_t>(256);
> > >             blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
> > >             blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
> > >             blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
> > >     }
> > > 
> > > > > > > +     }
> > > > > > >
> > > > > > >       tuningParameters_ = true;
> > > > > > >
> 
> -- 
> Regards,
> 
> Laurent Pinchart

Patch
diff mbox series

diff --git a/src/ipa/rkisp1/algorithms/blc.cpp b/src/ipa/rkisp1/algorithms/blc.cpp
index d2e743541c99..0c39c3b47da5 100644
--- a/src/ipa/rkisp1/algorithms/blc.cpp
+++ b/src/ipa/rkisp1/algorithms/blc.cpp
@@ -46,10 +46,30 @@  BlackLevelCorrection::BlackLevelCorrection()
 int BlackLevelCorrection::init([[maybe_unused]] IPAContext &context,
 			       const YamlObject &tuningData)
 {
-	blackLevelRed_ = tuningData["R"].get<int16_t>(256);
-	blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
-	blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
-	blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
+	auto blackLevels = context.camHelper->blackLevels();
+	if (blackLevels) {
+		Span<const int32_t, 4> levels = *blackLevels;
+		blackLevelRed_ = levels[0];
+		blackLevelGreenR_ = levels[1];
+		blackLevelGreenB_ = levels[2];
+		blackLevelBlue_ = levels[3];
+	} else
+		LOG(RkISP1Blc, Warning)
+			<< "No black levels provided by camera sensor helper";
+
+	if (!blackLevels || (tuningData.contains("R") &&
+			     tuningData.contains("Gr") &&
+			     tuningData.contains("Gb") &&
+			     tuningData.contains("B"))) {
+		blackLevelRed_ = tuningData["R"].get<int16_t>(256);
+		blackLevelGreenR_ = tuningData["Gr"].get<int16_t>(256);
+		blackLevelGreenB_ = tuningData["Gb"].get<int16_t>(256);
+		blackLevelBlue_ = tuningData["B"].get<int16_t>(256);
+
+		if (blackLevels)
+			LOG(RkISP1Blc, Warning)
+				<< "Black levels overwritten by tuning file";
+	}
 
 	tuningParameters_ = true;