[{"id":25881,"web_url":"https://patchwork.libcamera.org/comment/25881/","msgid":"<CAHW6GYJCUEKrrh_SDigG6-QhFmHYKiUrd1CdB+C8YappWMBiig@mail.gmail.com>","date":"2022-11-23T14:34:20","subject":"Re: [libcamera-devel] [PATCH v1 4/5] ipa: raspberrypi: Use the\n\tgeneric statistics structure in the algorithms","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi Naush\n\nThanks for all these updates!\n\nOn Tue, 22 Nov 2022 at 11:22, Naushir Patuck via libcamera-devel\n<libcamera-devel@lists.libcamera.org> wrote:\n>\n> Repurpose the StatisticsPtr type from being a shared_ptr<bcm2835_isp_stats> to\n> shared_ptr<RPiController::Statistics>. This removes any hardware specific header\n> files and structures from the algorithms source code.\n>\n> Add a new function in the Raspberry Pi IPA to populate the generic statistics\n> structure from the values provided by the hardware in the bcm2835_isp_stats\n> structure.\n>\n> Update the Lux, AWB, AGC, ALCS, Contrast, and Focus algorithms to use the\n\ns/ALCS/ALSC/  I think, I get muddled too!\n\n> generic statistics structure appropriately in their calculations. Additionally,\n> remove references to any hardware specific headers and defines in these source\n> files.\n>\n> Signed-off-by: Naushir Patuck <naush@raspberrypi.com>\n> ---\n>  src/ipa/raspberrypi/controller/controller.h   |  4 +-\n>  src/ipa/raspberrypi/controller/rpi/agc.cpp    | 26 ++++++-------\n>  src/ipa/raspberrypi/controller/rpi/agc.h      |  2 +-\n>  src/ipa/raspberrypi/controller/rpi/alsc.cpp   | 33 +++++++++-------\n>  src/ipa/raspberrypi/controller/rpi/alsc.h     |  3 +-\n>  src/ipa/raspberrypi/controller/rpi/awb.cpp    | 20 +++++-----\n>  src/ipa/raspberrypi/controller/rpi/awb.h      |  1 +\n>  .../raspberrypi/controller/rpi/contrast.cpp   |  8 ++--\n>  src/ipa/raspberrypi/controller/rpi/focus.cpp  |  7 ++--\n>  src/ipa/raspberrypi/controller/rpi/lux.cpp    | 13 +------\n>  src/ipa/raspberrypi/raspberrypi.cpp           | 39 ++++++++++++++++++-\n>  src/ipa/raspberrypi/statistics.h              |  2 +\n>  12 files changed, 95 insertions(+), 63 deletions(-)\n>\n> diff --git a/src/ipa/raspberrypi/controller/controller.h b/src/ipa/raspberrypi/controller/controller.h\n> index 3e1e051703b3..e6c950c3a509 100644\n> --- a/src/ipa/raspberrypi/controller/controller.h\n> +++ b/src/ipa/raspberrypi/controller/controller.h\n> @@ -15,19 +15,17 @@\n>  #include <vector>\n>  #include <string>\n>\n> -#include <linux/bcm2835-isp.h>\n> -\n>  #include \"libcamera/internal/yaml_parser.h\"\n>\n>  #include \"camera_mode.h\"\n>  #include \"device_status.h\"\n>  #include \"metadata.h\"\n> +#include \"statistics.h\"\n>\n>  namespace RPiController {\n>\n>  class Algorithm;\n>  typedef std::unique_ptr<Algorithm> AlgorithmPtr;\n> -typedef std::shared_ptr<bcm2835_isp_stats> StatisticsPtr;\n>\n>  /*\n>   * The Controller holds a pointer to some global_metadata, which is how\n> diff --git a/src/ipa/raspberrypi/controller/rpi/agc.cpp b/src/ipa/raspberrypi/controller/rpi/agc.cpp\n> index bd54a639d637..79c83e0a9eae 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/agc.cpp\n> +++ b/src/ipa/raspberrypi/controller/rpi/agc.cpp\n> @@ -9,8 +9,6 @@\n>  #include <map>\n>  #include <tuple>\n>\n> -#include <linux/bcm2835-isp.h>\n> -\n>  #include <libcamera/base/log.h>\n>\n>  #include \"../awb_status.h\"\n> @@ -450,7 +448,7 @@ void Agc::process(StatisticsPtr &stats, Metadata *imageMetadata)\n>         fetchCurrentExposure(imageMetadata);\n>         /* Compute the total gain we require relative to the current exposure. */\n>         double gain, targetY;\n> -       computeGain(stats.get(), imageMetadata, gain, targetY);\n> +       computeGain(stats, imageMetadata, gain, targetY);\n\nI suppose \"stats.get()\" was a bit of a promise that the function\ncouldn't possibly acquire another reference or anything like that, but\nI don't think it matters!\n\n>         /* Now compute the target (final) exposure which we think we want. */\n>         computeTargetExposure(gain);\n>         /*\n> @@ -584,20 +582,20 @@ void Agc::fetchAwbStatus(Metadata *imageMetadata)\n>                 LOG(RPiAgc, Debug) << \"No AWB status found\";\n>  }\n>\n> -static double computeInitialY(bcm2835_isp_stats *stats, AwbStatus const &awb,\n> +static double computeInitialY(StatisticsPtr &stats, AwbStatus const &awb,\n>                               double weights[], double gain)\n>  {\n> -       bcm2835_isp_stats_region *regions = stats->agc_stats;\n>         /*\n>          * Note how the calculation below means that equal weights give you\n>          * \"average\" metering (i.e. all pixels equally important).\n>          */\n>         double rSum = 0, gSum = 0, bSum = 0, pixelSum = 0;\n> -       for (unsigned int i = 0; i < AgcStatsSize; i++) {\n> -               double counted = regions[i].counted;\n> -               double rAcc = std::min(regions[i].r_sum * gain, ((1 << PipelineBits) - 1) * counted);\n> -               double gAcc = std::min(regions[i].g_sum * gain, ((1 << PipelineBits) - 1) * counted);\n> -               double bAcc = std::min(regions[i].b_sum * gain, ((1 << PipelineBits) - 1) * counted);\n> +       for (unsigned int i = 0; i < stats->agcRegions.numRegions(); i++) {\n> +               uint32_t counted, uncounted;\n> +               auto s = stats->agcRegions.get(i, counted, uncounted);\n> +               double rAcc = std::min<double>(s.rSum * gain, ((1 << PipelineBits) - 1) * counted);\n> +               double gAcc = std::min<double>(s.gSum * gain, ((1 << PipelineBits) - 1) * counted);\n> +               double bAcc = std::min<double>(s.bSum * gain, ((1 << PipelineBits) - 1) * counted);\n>                 rSum += rAcc * weights[i];\n>                 gSum += gAcc * weights[i];\n>                 bSum += bAcc * weights[i];\n> @@ -623,23 +621,23 @@ static double computeInitialY(bcm2835_isp_stats *stats, AwbStatus const &awb,\n>\n>  static constexpr double EvGainYTargetLimit = 0.9;\n>\n> -static double constraintComputeGain(AgcConstraint &c, Histogram &h, double lux,\n> +static double constraintComputeGain(AgcConstraint &c, const Histogram &h, double lux,\n>                                     double evGain, double &targetY)\n>  {\n>         targetY = c.yTarget.eval(c.yTarget.domain().clip(lux));\n>         targetY = std::min(EvGainYTargetLimit, targetY * evGain);\n>         double iqm = h.interQuantileMean(c.qLo, c.qHi);\n> -       return (targetY * NUM_HISTOGRAM_BINS) / iqm;\n> +       return (targetY * h.bins()) / iqm;\n>  }\n>\n> -void Agc::computeGain(bcm2835_isp_stats *statistics, Metadata *imageMetadata,\n> +void Agc::computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,\n>                       double &gain, double &targetY)\n>  {\n>         struct LuxStatus lux = {};\n>         lux.lux = 400; /* default lux level to 400 in case no metadata found */\n>         if (imageMetadata->get(\"lux.status\", lux) != 0)\n>                 LOG(RPiAgc, Warning) << \"No lux level found\";\n> -       Histogram h(statistics->hist[0].g_hist, NUM_HISTOGRAM_BINS);\n> +       const Histogram &h = statistics->yHist;\n>         double evGain = status_.ev * config_.baseEv;\n>         /*\n>          * The initial gain and target_Y come from some of the regions. After\n> diff --git a/src/ipa/raspberrypi/controller/rpi/agc.h b/src/ipa/raspberrypi/controller/rpi/agc.h\n> index 6d6b0e5ad857..0ea71e6d1aa0 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/agc.h\n> +++ b/src/ipa/raspberrypi/controller/rpi/agc.h\n> @@ -98,7 +98,7 @@ private:\n>         void housekeepConfig();\n>         void fetchCurrentExposure(Metadata *imageMetadata);\n>         void fetchAwbStatus(Metadata *imageMetadata);\n> -       void computeGain(bcm2835_isp_stats *statistics, Metadata *imageMetadata,\n> +       void computeGain(StatisticsPtr &statistics, Metadata *imageMetadata,\n>                          double &gain, double &targetY);\n>         void computeTargetExposure(double gain);\n>         bool applyDigitalGain(double gain, double targetY);\n> diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> index a4afaf841c41..d8c650e6faa6 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> +++ b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> @@ -310,18 +310,22 @@ double getCt(Metadata *metadata, double defaultCt)\n>         return awbStatus.temperatureK;\n>  }\n>\n> -static void copyStats(bcm2835_isp_stats_region regions[XY], StatisticsPtr &stats,\n> +static void copyStats(RgbyRegions &regions, StatisticsPtr &stats,\n>                       AlscStatus const &status)\n>  {\n> -       bcm2835_isp_stats_region *inputRegions = stats->awb_stats;\n> +       if (!regions.numRegions())\n> +               regions.init(stats->awbRegions.numRegions());\n> +\n>         double *rTable = (double *)status.r;\n>         double *gTable = (double *)status.g;\n>         double *bTable = (double *)status.b;\n> -       for (int i = 0; i < XY; i++) {\n> -               regions[i].r_sum = inputRegions[i].r_sum / rTable[i];\n> -               regions[i].g_sum = inputRegions[i].g_sum / gTable[i];\n> -               regions[i].b_sum = inputRegions[i].b_sum / bTable[i];\n> -               regions[i].counted = inputRegions[i].counted;\n> +       for (unsigned int i = 0; i < stats->awbRegions.numRegions(); i++) {\n> +               uint32_t counted, uncounted;\n> +               auto s = stats->awbRegions.get(i, counted, uncounted);\n> +               regions.set(i, { s.rSum / static_cast<uint64_t>(rTable[i]),\n\nJust wondering a bit about the casts here, it doesn't look obviously\nthe same as what we have above...?\n\n> +                                s.gSum / static_cast<uint64_t>(gTable[i]),\n> +                                s.bSum / static_cast<uint64_t>(bTable[i]) },\n> +                           counted, uncounted);\n>                 /* (don't care about the uncounted value) */\n>         }\n>  }\n> @@ -512,19 +516,20 @@ void resampleCalTable(double const calTableIn[XY],\n>  }\n>\n>  /* Calculate chrominance statistics (R/G and B/G) for each region. */\n> -static_assert(XY == AWB_REGIONS, \"ALSC/AWB statistics region mismatch\");\n> -static void calculateCrCb(bcm2835_isp_stats_region *awbRegion, double cr[XY],\n> +static void calculateCrCb(const RgbyRegions &awbRegion, double cr[XY],\n>                           double cb[XY], uint32_t minCount, uint16_t minG)\n>  {\n>         for (int i = 0; i < XY; i++) {\n> -               bcm2835_isp_stats_region &zone = awbRegion[i];\n> -               if (zone.counted <= minCount ||\n> -                   zone.g_sum / zone.counted <= minG) {\n> +               uint32_t counted, uncounted;\n> +               auto s = awbRegion.get(i, counted, uncounted);\n> +\n> +               if (counted <= minCount || s.gSum / counted <= minG) {\n>                         cr[i] = cb[i] = InsufficientData;\n>                         continue;\n>                 }\n> -               cr[i] = zone.r_sum / (double)zone.g_sum;\n> -               cb[i] = zone.b_sum / (double)zone.g_sum;\n> +\n> +               cr[i] = s.rSum / (double)s.gSum;\n> +               cb[i] = s.bSum / (double)s.gSum;\n>         }\n>  }\n>\n> diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.h b/src/ipa/raspberrypi/controller/rpi/alsc.h\n> index a858ef5a6551..9167c9ffa2e3 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/alsc.h\n> +++ b/src/ipa/raspberrypi/controller/rpi/alsc.h\n> @@ -12,6 +12,7 @@\n>\n>  #include \"../algorithm.h\"\n>  #include \"../alsc_status.h\"\n> +#include \"../statistics.h\"\n>\n>  namespace RPiController {\n>\n> @@ -98,7 +99,7 @@ private:\n>         /* copy out the results from the async thread so that it can be restarted */\n>         void fetchAsyncResults();\n>         double ct_;\n> -       bcm2835_isp_stats_region statistics_[AlscCellsY * AlscCellsX];\n> +       RgbyRegions statistics_;\n>         double asyncResults_[3][AlscCellsY][AlscCellsX];\n>         double asyncLambdaR_[AlscCellsX * AlscCellsY];\n>         double asyncLambdaB_[AlscCellsX * AlscCellsY];\n> diff --git a/src/ipa/raspberrypi/controller/rpi/awb.cpp b/src/ipa/raspberrypi/controller/rpi/awb.cpp\n> index 861022014896..901498382c6b 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/awb.cpp\n> +++ b/src/ipa/raspberrypi/controller/rpi/awb.cpp\n> @@ -21,9 +21,6 @@ LOG_DEFINE_CATEGORY(RPiAwb)\n>\n>  #define NAME \"rpi.awb\"\n>\n> -static constexpr unsigned int AwbStatsSizeX = DEFAULT_AWB_REGIONS_X;\n> -static constexpr unsigned int AwbStatsSizeY = DEFAULT_AWB_REGIONS_Y;\n> -\n>  /*\n>   * todo - the locking in this algorithm needs some tidying up as has been done\n>   * elsewhere (ALSC and AGC).\n> @@ -406,17 +403,18 @@ void Awb::asyncFunc()\n>  }\n>\n>  static void generateStats(std::vector<Awb::RGB> &zones,\n> -                         bcm2835_isp_stats_region *stats, double minPixels,\n> +                         RgbyRegions &stats, double minPixels,\n>                           double minG)\n>  {\n> -       for (unsigned int i = 0; i < AwbStatsSizeX * AwbStatsSizeY; i++) {\n> +       for (unsigned int i = 0; i < stats.numRegions(); i++) {\n>                 Awb::RGB zone;\n> -               double counted = stats[i].counted;\n> +               uint32_t counted, uncounted;\n> +               auto s = stats.get(i, counted, uncounted);\n>                 if (counted >= minPixels) {\n> -                       zone.G = stats[i].g_sum / counted;\n> +                       zone.G = s.gSum / counted;\n>                         if (zone.G >= minG) {\n> -                               zone.R = stats[i].r_sum / counted;\n> -                               zone.B = stats[i].b_sum / counted;\n> +                               zone.R = s.rSum / counted;\n> +                               zone.B = s.bSum / counted;\n>                                 zones.push_back(zone);\n>                         }\n>                 }\n> @@ -430,7 +428,7 @@ void Awb::prepareStats()\n>          * LSC has already been applied to the stats in this pipeline, so stop\n>          * any LSC compensation.  We also ignore config_.fast in this version.\n>          */\n> -       generateStats(zones_, statistics_->awb_stats, config_.minPixels,\n> +       generateStats(zones_, statistics_->awbRegions, config_.minPixels,\n>                       config_.minG);\n>         /*\n>          * apply sensitivities, so values appear to come from our \"canonical\"\n> @@ -646,7 +644,7 @@ void Awb::awbBayes()\n>          * valid... not entirely sure about this.\n>          */\n>         Pwl prior = interpolatePrior();\n> -       prior *= zones_.size() / (double)(AwbStatsSizeX * AwbStatsSizeY);\n> +       prior *= zones_.size() / (double)(statistics_->awbRegions.numRegions());\n>         prior.map([](double x, double y) {\n>                 LOG(RPiAwb, Debug) << \"(\" << x << \",\" << y << \")\";\n>         });\n> diff --git a/src/ipa/raspberrypi/controller/rpi/awb.h b/src/ipa/raspberrypi/controller/rpi/awb.h\n> index 30acd89d0969..d81779dd51ff 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/awb.h\n> +++ b/src/ipa/raspberrypi/controller/rpi/awb.h\n> @@ -13,6 +13,7 @@\n>  #include \"../awb_algorithm.h\"\n>  #include \"../pwl.h\"\n>  #include \"../awb_status.h\"\n> +#include \"../statistics.h\"\n>\n>  namespace RPiController {\n>\n> diff --git a/src/ipa/raspberrypi/controller/rpi/contrast.cpp b/src/ipa/raspberrypi/controller/rpi/contrast.cpp\n> index 5b37edcbd46a..a4f8c4f04fc4 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/contrast.cpp\n> +++ b/src/ipa/raspberrypi/controller/rpi/contrast.cpp\n> @@ -106,7 +106,7 @@ Pwl computeStretchCurve(Histogram const &histogram,\n>          * bit.\n>          */\n>         double histLo = histogram.quantile(config.loHistogram) *\n> -                       (65536 / NUM_HISTOGRAM_BINS);\n> +                       (65536 / histogram.bins());\n>         double levelLo = config.loLevel * 65536;\n>         LOG(RPiContrast, Debug)\n>                 << \"Move histogram point \" << histLo << \" to \" << levelLo;\n> @@ -119,7 +119,7 @@ Pwl computeStretchCurve(Histogram const &histogram,\n>          * Keep the mid-point (median) in the same place, though, to limit the\n>          * apparent amount of global brightness shift.\n>          */\n> -       double mid = histogram.quantile(0.5) * (65536 / NUM_HISTOGRAM_BINS);\n> +       double mid = histogram.quantile(0.5) * (65536 / histogram.bins());\n>         enhance.append(mid, mid);\n>\n>         /*\n> @@ -127,7 +127,7 @@ Pwl computeStretchCurve(Histogram const &histogram,\n>          * there up.\n>          */\n>         double histHi = histogram.quantile(config.hiHistogram) *\n> -                       (65536 / NUM_HISTOGRAM_BINS);\n> +                       (65536 / histogram.bins());\n>         double levelHi = config.hiLevel * 65536;\n>         LOG(RPiContrast, Debug)\n>                 << \"Move histogram point \" << histHi << \" to \" << levelHi;\n> @@ -158,7 +158,7 @@ Pwl applyManualContrast(Pwl const &gammaCurve, double brightness,\n>  void Contrast::process(StatisticsPtr &stats,\n>                        [[maybe_unused]] Metadata *imageMetadata)\n>  {\n> -       Histogram histogram(stats->hist[0].g_hist, NUM_HISTOGRAM_BINS);\n> +       Histogram &histogram = stats->yHist;\n>         /*\n>          * We look at the histogram and adjust the gamma curve in the following\n>          * ways: 1. Adjust the gamma curve so as to pull the start of the\n> diff --git a/src/ipa/raspberrypi/controller/rpi/focus.cpp b/src/ipa/raspberrypi/controller/rpi/focus.cpp\n> index 8c5029bd0e95..41afbf43f2b7 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/focus.cpp\n> +++ b/src/ipa/raspberrypi/controller/rpi/focus.cpp\n> @@ -32,9 +32,10 @@ void Focus::process(StatisticsPtr &stats, Metadata *imageMetadata)\n>  {\n>         FocusStatus status;\n>         unsigned int i;\n> -       for (i = 0; i < FOCUS_REGIONS; i++)\n> -               status.focusMeasures[i] = stats->focus_stats[i].contrast_val[1][1] / 1000;\n> -       status.num = i;\n> +       uint32_t counted, uncounted;\n> +       for (i = 0; i < stats->focusRegions.numRegions(); i++)\n> +               status.focusMeasures[i] = stats->focusRegions.get(i, counted, uncounted);\n> +       status.num = stats->focusRegions.numRegions();\n>         imageMetadata->set(\"focus.status\", status);\n>\n>         LOG(RPiFocus, Debug)\n> diff --git a/src/ipa/raspberrypi/controller/rpi/lux.cpp b/src/ipa/raspberrypi/controller/rpi/lux.cpp\n> index 9759186afacf..a49d402707c7 100644\n> --- a/src/ipa/raspberrypi/controller/rpi/lux.cpp\n> +++ b/src/ipa/raspberrypi/controller/rpi/lux.cpp\n> @@ -6,8 +6,6 @@\n>   */\n>  #include <math.h>\n>\n> -#include <linux/bcm2835-isp.h>\n> -\n>  #include <libcamera/base/log.h>\n>\n>  #include \"../device_status.h\"\n> @@ -83,20 +81,13 @@ void Lux::process(StatisticsPtr &stats, Metadata *imageMetadata)\n>         if (imageMetadata->get(\"device.status\", deviceStatus) == 0) {\n>                 double currentGain = deviceStatus.analogueGain;\n>                 double currentAperture = deviceStatus.aperture.value_or(currentAperture_);\n> -               uint64_t sum = 0;\n> -               uint32_t num = 0;\n> -               uint32_t *bin = stats->hist[0].g_hist;\n> -               const int numBins = sizeof(stats->hist[0].g_hist) /\n> -                                   sizeof(stats->hist[0].g_hist[0]);\n> -               for (int i = 0; i < numBins; i++)\n> -                       sum += bin[i] * (uint64_t)i, num += bin[i];\n>                 /* add .5 to reflect the mid-points of bins */\n> -               double currentY = sum / (double)num + .5;\n> +               double currentY = stats->yHist.total() / stats->yHist.bins() + .5;\n\nIs this the same thing? I have a bad feeling this might be the average\nnumber of pixels per bin, or something. Maybe we need something\ninvolving yHist.interQuantileMean(0, 1)...?\n\nThanks!\nDavid\n\n>                 double gainRatio = referenceGain_ / currentGain;\n>                 double shutterSpeedRatio =\n>                         referenceShutterSpeed_ / deviceStatus.shutterSpeed;\n>                 double apertureRatio = referenceAperture_ / currentAperture;\n> -               double yRatio = currentY * (65536 / numBins) / referenceY_;\n> +               double yRatio = currentY * (65536 / stats->yHist.bins()) / referenceY_;\n>                 double estimatedLux = shutterSpeedRatio * gainRatio *\n>                                       apertureRatio * apertureRatio *\n>                                       yRatio * referenceLux_;\n> diff --git a/src/ipa/raspberrypi/raspberrypi.cpp b/src/ipa/raspberrypi/raspberrypi.cpp\n> index b74f1ecf738f..8fcfa0b3ea50 100644\n> --- a/src/ipa/raspberrypi/raspberrypi.cpp\n> +++ b/src/ipa/raspberrypi/raspberrypi.cpp\n> @@ -51,6 +51,7 @@\n>  #include \"metadata.h\"\n>  #include \"sharpen_algorithm.h\"\n>  #include \"sharpen_status.h\"\n> +#include \"statistics.h\"\n>\n>  namespace libcamera {\n>\n> @@ -136,6 +137,7 @@ private:\n>         void prepareISP(const ISPConfig &data);\n>         void reportMetadata();\n>         void fillDeviceStatus(const ControlList &sensorControls);\n> +       RPiController::StatisticsPtr fillStatistics(bcm2835_isp_stats *stats) const;\n>         void processStats(unsigned int bufferId);\n>         void applyFrameDurations(Duration minFrameDuration, Duration maxFrameDuration);\n>         void applyAGC(const struct AgcStatus *agcStatus, ControlList &ctrls);\n> @@ -1119,6 +1121,41 @@ void IPARPi::fillDeviceStatus(const ControlList &sensorControls)\n>         rpiMetadata_.set(\"device.status\", deviceStatus);\n>  }\n>\n> +RPiController::StatisticsPtr IPARPi::fillStatistics(bcm2835_isp_stats *stats) const\n> +{\n> +       using namespace RPiController;\n> +\n> +       unsigned int i;\n> +       StatisticsPtr statistics =\n> +               std::make_unique<Statistics>(Statistics::AgcStatsPos::PreWb, Statistics::ColourStatsPos::PostLsc);\n> +\n> +       /* RGB histograms are not used, so do not populate them. */\n> +       statistics->yHist = std::move(RPiController::Histogram(stats->hist[0].g_hist, NUM_HISTOGRAM_BINS));\n> +\n> +       statistics->awbRegions.init(DEFAULT_AWB_REGIONS_X, DEFAULT_AWB_REGIONS_Y);\n> +       for (i = 0; i < statistics->awbRegions.numRegions(); i++)\n> +               statistics->awbRegions.set(i, { stats->awb_stats[i].r_sum,\n> +                                               stats->awb_stats[i].g_sum,\n> +                                               stats->awb_stats[i].b_sum },\n> +                                          stats->awb_stats[i].counted, stats->awb_stats[i].notcounted);\n> +\n> +       /* There are only ever 15 regions computed by the firmware, but the HW defines AGC_REGIONS == 16! */\n> +       statistics->agcRegions.init(15);\n> +       for (i = 0; i < statistics->agcRegions.numRegions(); i++)\n> +               statistics->agcRegions.set(i, { stats->agc_stats[i].r_sum,\n> +                                               stats->agc_stats[i].g_sum,\n> +                                               stats->agc_stats[i].b_sum },\n> +                                          stats->agc_stats[i].counted, 0);\n> +\n> +       statistics->focusRegions.init(4, 3);\n> +       for (i = 0; i < FOCUS_REGIONS; i++)\n> +               statistics->focusRegions.set(i, stats->focus_stats[i].contrast_val[1][1] / 1000,\n> +                                            stats->focus_stats[i].contrast_val_num[1][1],\n> +                                            stats->focus_stats[i].contrast_val_num[1][0]);\n> +\n> +       return statistics;\n> +}\n> +\n>  void IPARPi::processStats(unsigned int bufferId)\n>  {\n>         auto it = buffers_.find(bufferId);\n> @@ -1129,7 +1166,7 @@ void IPARPi::processStats(unsigned int bufferId)\n>\n>         Span<uint8_t> mem = it->second.planes()[0];\n>         bcm2835_isp_stats *stats = reinterpret_cast<bcm2835_isp_stats *>(mem.data());\n> -       RPiController::StatisticsPtr statistics = std::make_shared<bcm2835_isp_stats>(*stats);\n> +       RPiController::StatisticsPtr statistics = fillStatistics(stats);\n>         helper_->process(statistics, rpiMetadata_);\n>         controller_.process(statistics, &rpiMetadata_);\n>\n> diff --git a/src/ipa/raspberrypi/statistics.h b/src/ipa/raspberrypi/statistics.h\n> index a762bf3d41aa..affb7272c963 100644\n> --- a/src/ipa/raspberrypi/statistics.h\n> +++ b/src/ipa/raspberrypi/statistics.h\n> @@ -67,4 +67,6 @@ struct Statistics {\n>         FocusRegions focusRegions;\n>  };\n>\n> +using StatisticsPtr = std::shared_ptr<Statistics>;\n> +\n>  } /* namespace RPiController */\n> --\n> 2.25.1\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id B65F7BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 23 Nov 2022 14:34:36 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 2980263313;\n\tWed, 23 Nov 2022 15:34:36 +0100 (CET)","from mail-pj1-x102f.google.com (mail-pj1-x102f.google.com\n\t[IPv6:2607:f8b0:4864:20::102f])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 1241863311\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 23 Nov 2022 15:34:34 +0100 (CET)","by mail-pj1-x102f.google.com with SMTP id\n\to5-20020a17090a678500b00218cd5a21c9so1977841pjj.4\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 23 Nov 2022 06:34:33 -0800 (PST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1669214076;\n\tbh=JtlUO+i5eE/yq5Z3n7tg1LYJyaCpOUMeZIIibbCQyDI=;\n\th=References:In-Reply-To:Date:To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=WmcXwYZ6GcaVjYacFOO7HE8n279LcMEqtguJKPs9XQmKmxuhzr0VWZETD0KqAlSAH\n\t9o2pQfswBlM8uLSAYTqHpF06vdVvu7VnulouqPhm4mb18VaixHjS0RXWWThL5VGFnB\n\t/dM80GcW6CzYqFskmTAkYcP+/3cDTojScbUTXXEXMmjErg01se92twNhVtKVAse+63\n\txAnYA/Uq7di2jrp84iWf84cEqcSfrYDob60frvoFlT3E4NGg7+MA+1rTUg+ag3py4O\n\t16m+QiXptcU/HXthuEurjHSs8/+ZjYQnHymIZXyywhrvTbN3tR7rxU9rYrdmbWVgDG\n\t4aOs4FU/OZHDQ==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=vlRe4WaJx+UxogahggLOyqJKIFe34bMvqocZQdCuGLI=;\n\tb=APLTUO4b14uPC6w1YXJME5NwcEsPzukkuyEJf8tC/KrBAzS/r4kI2NMcn8nJfyPXFY\n\tiGt/NN+uP0GDGenxa6lK13lZGVSaWw7cv9Hwxujw4+MvFDwBKr22g4mdA/npjyb93zFK\n\t/uycxfEPFC4yhaJ9JwughnsIJe/Tdc/X+XD1Tu1LsLL+lYsdDwnhQ70IMLq8YGArqsqq\n\tVqkrqKU/95aBuMNlKCZ+IZU95C5wKroECW7T+h3uc2w+aoXUJIhrQMPT4o0I5zXOkblW\n\tTp0Pc43EjVV8UtzyKnQTstUovcwlbDRrXOUdw4a8HxZfsGnPCxr1Yq9YzKQFoQezyelP\n\tXarg=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"APLTUO4b\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=vlRe4WaJx+UxogahggLOyqJKIFe34bMvqocZQdCuGLI=;\n\tb=ANbmmp5dooGTyyRSyAtGZrXioL51uvXVZp8s7eK5YNiSsoxLPcsMAGYTmenQrksmHe\n\tXfVRmqKcmXC74K6nL0XjfO6Fvedm7he5LQ6B6+x1ORQ844W7Y3xiQ3IZEa6/nEzQUJtn\n\tSoBIyT7c19B+4OgMkY082X8BotIOdAqWGey5gvvdieA6K7mWDE61QdNT3MFiada64jZ2\n\t+Omt/ktE9KyoEGMgqYuZdWZcDcFm8sZW3yJSdR6Q1nZMyQ64EjrekdDXb/4gqLYG+m6H\n\tEWsHxNRwxOkd/sBqM5iO9BZg7BdpqdvGOL9RcrtbZpuSnGhO1MXvBeNMG1bMmVXbDLBN\n\tashQ==","X-Gm-Message-State":"ANoB5pkFTOdxQc4WeUww3pRYizzM0+wAX0ip/apV9vD44zoOEygOc6cc\n\tIzxF3c+BkbDnkjbiAT15SsjwemFPcWQgXBY5jn1Fu6i4jLo=","X-Google-Smtp-Source":"AA0mqf7f6Zxbo+Nny3q23Qh+ZHHRECl/5davJZYiBSMo8+P7Hv9AKhZLMOq90fT8HixdoBvHv98j/ezY0aght8M719s=","X-Received":"by 2002:a17:902:7d93:b0:186:9cf4:e53b with SMTP id\n\ta19-20020a1709027d9300b001869cf4e53bmr13258444plm.50.1669214072131;\n\tWed, 23 Nov 2022 06:34:32 -0800 (PST)","MIME-Version":"1.0","References":"<20221122112224.31691-1-naush@raspberrypi.com>\n\t<20221122112224.31691-5-naush@raspberrypi.com>","In-Reply-To":"<20221122112224.31691-5-naush@raspberrypi.com>","Date":"Wed, 23 Nov 2022 14:34:20 +0000","Message-ID":"<CAHW6GYJCUEKrrh_SDigG6-QhFmHYKiUrd1CdB+C8YappWMBiig@mail.gmail.com>","To":"Naushir Patuck <naush@raspberrypi.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [libcamera-devel] [PATCH v1 4/5] ipa: raspberrypi: Use the\n\tgeneric statistics structure in the algorithms","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"David Plowman via libcamera-devel <libcamera-devel@lists.libcamera.org>","Reply-To":"David Plowman <david.plowman@raspberrypi.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":25885,"web_url":"https://patchwork.libcamera.org/comment/25885/","msgid":"<CAEmqJPrFm=pGCXc7b5oyokoYTspy+N1Y+8adAUWOvgmcQCYaAQ@mail.gmail.com>","date":"2022-11-23T14:53:03","subject":"Re: [libcamera-devel] [PATCH v1 4/5] ipa: raspberrypi: Use the\n\tgeneric statistics structure in the algorithms","submitter":{"id":34,"url":"https://patchwork.libcamera.org/api/people/34/","name":"Naushir Patuck","email":"naush@raspberrypi.com"},"content":"Hi David,\n\nThank you for your feedback.\n\nOn Wed, 23 Nov 2022 at 14:34, David Plowman <david.plowman@raspberrypi.com>\nwrote:\n\n> Hi Naush\n>\n> Thanks for all these updates!\n>\n> On Tue, 22 Nov 2022 at 11:22, Naushir Patuck via libcamera-devel\n> <libcamera-devel@lists.libcamera.org> wrote:\n> >\n> > Repurpose the StatisticsPtr type from being a\n> shared_ptr<bcm2835_isp_stats> to\n> > shared_ptr<RPiController::Statistics>. This removes any hardware\n> specific header\n> > files and structures from the algorithms source code.\n> >\n> > Add a new function in the Raspberry Pi IPA to populate the generic\n> statistics\n> > structure from the values provided by the hardware in the\n> bcm2835_isp_stats\n> > structure.\n> >\n> > Update the Lux, AWB, AGC, ALCS, Contrast, and Focus algorithms to use the\n>\n> s/ALCS/ALSC/  I think, I get muddled too!\n>\n> > generic statistics structure appropriately in their calculations.\n> Additionally,\n> > remove references to any hardware specific headers and defines in these\n> source\n> > files.\n> >\n> > Signed-off-by: Naushir Patuck <naush@raspberrypi.com>\n> > ---\n> >  src/ipa/raspberrypi/controller/controller.h   |  4 +-\n> >  src/ipa/raspberrypi/controller/rpi/agc.cpp    | 26 ++++++-------\n> >  src/ipa/raspberrypi/controller/rpi/agc.h      |  2 +-\n> >  src/ipa/raspberrypi/controller/rpi/alsc.cpp   | 33 +++++++++-------\n> >  src/ipa/raspberrypi/controller/rpi/alsc.h     |  3 +-\n> >  src/ipa/raspberrypi/controller/rpi/awb.cpp    | 20 +++++-----\n> >  src/ipa/raspberrypi/controller/rpi/awb.h      |  1 +\n> >  .../raspberrypi/controller/rpi/contrast.cpp   |  8 ++--\n> >  src/ipa/raspberrypi/controller/rpi/focus.cpp  |  7 ++--\n> >  src/ipa/raspberrypi/controller/rpi/lux.cpp    | 13 +------\n> >  src/ipa/raspberrypi/raspberrypi.cpp           | 39 ++++++++++++++++++-\n> >  src/ipa/raspberrypi/statistics.h              |  2 +\n> >  12 files changed, 95 insertions(+), 63 deletions(-)\n> >\n> > diff --git a/src/ipa/raspberrypi/controller/controller.h\n> b/src/ipa/raspberrypi/controller/controller.h\n> > index 3e1e051703b3..e6c950c3a509 100644\n> > --- a/src/ipa/raspberrypi/controller/controller.h\n> > +++ b/src/ipa/raspberrypi/controller/controller.h\n> > @@ -15,19 +15,17 @@\n> >  #include <vector>\n> >  #include <string>\n> >\n> > -#include <linux/bcm2835-isp.h>\n> > -\n> >  #include \"libcamera/internal/yaml_parser.h\"\n> >\n> >  #include \"camera_mode.h\"\n> >  #include \"device_status.h\"\n> >  #include \"metadata.h\"\n> > +#include \"statistics.h\"\n> >\n> >  namespace RPiController {\n> >\n> >  class Algorithm;\n> >  typedef std::unique_ptr<Algorithm> AlgorithmPtr;\n> > -typedef std::shared_ptr<bcm2835_isp_stats> StatisticsPtr;\n> >\n> >  /*\n> >   * The Controller holds a pointer to some global_metadata, which is how\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/agc.cpp\n> b/src/ipa/raspberrypi/controller/rpi/agc.cpp\n> > index bd54a639d637..79c83e0a9eae 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/agc.cpp\n> > +++ b/src/ipa/raspberrypi/controller/rpi/agc.cpp\n> > @@ -9,8 +9,6 @@\n> >  #include <map>\n> >  #include <tuple>\n> >\n> > -#include <linux/bcm2835-isp.h>\n> > -\n> >  #include <libcamera/base/log.h>\n> >\n> >  #include \"../awb_status.h\"\n> > @@ -450,7 +448,7 @@ void Agc::process(StatisticsPtr &stats, Metadata\n> *imageMetadata)\n> >         fetchCurrentExposure(imageMetadata);\n> >         /* Compute the total gain we require relative to the current\n> exposure. */\n> >         double gain, targetY;\n> > -       computeGain(stats.get(), imageMetadata, gain, targetY);\n> > +       computeGain(stats, imageMetadata, gain, targetY);\n>\n> I suppose \"stats.get()\" was a bit of a promise that the function\n> couldn't possibly acquire another reference or anything like that, but\n> I don't think it matters!\n>\n> >         /* Now compute the target (final) exposure which we think we\n> want. */\n> >         computeTargetExposure(gain);\n> >         /*\n> > @@ -584,20 +582,20 @@ void Agc::fetchAwbStatus(Metadata *imageMetadata)\n> >                 LOG(RPiAgc, Debug) << \"No AWB status found\";\n> >  }\n> >\n> > -static double computeInitialY(bcm2835_isp_stats *stats, AwbStatus const\n> &awb,\n> > +static double computeInitialY(StatisticsPtr &stats, AwbStatus const\n> &awb,\n> >                               double weights[], double gain)\n> >  {\n> > -       bcm2835_isp_stats_region *regions = stats->agc_stats;\n> >         /*\n> >          * Note how the calculation below means that equal weights give\n> you\n> >          * \"average\" metering (i.e. all pixels equally important).\n> >          */\n> >         double rSum = 0, gSum = 0, bSum = 0, pixelSum = 0;\n> > -       for (unsigned int i = 0; i < AgcStatsSize; i++) {\n> > -               double counted = regions[i].counted;\n> > -               double rAcc = std::min(regions[i].r_sum * gain, ((1 <<\n> PipelineBits) - 1) * counted);\n> > -               double gAcc = std::min(regions[i].g_sum * gain, ((1 <<\n> PipelineBits) - 1) * counted);\n> > -               double bAcc = std::min(regions[i].b_sum * gain, ((1 <<\n> PipelineBits) - 1) * counted);\n> > +       for (unsigned int i = 0; i < stats->agcRegions.numRegions();\n> i++) {\n> > +               uint32_t counted, uncounted;\n> > +               auto s = stats->agcRegions.get(i, counted, uncounted);\n> > +               double rAcc = std::min<double>(s.rSum * gain, ((1 <<\n> PipelineBits) - 1) * counted);\n> > +               double gAcc = std::min<double>(s.gSum * gain, ((1 <<\n> PipelineBits) - 1) * counted);\n> > +               double bAcc = std::min<double>(s.bSum * gain, ((1 <<\n> PipelineBits) - 1) * counted);\n> >                 rSum += rAcc * weights[i];\n> >                 gSum += gAcc * weights[i];\n> >                 bSum += bAcc * weights[i];\n> > @@ -623,23 +621,23 @@ static double computeInitialY(bcm2835_isp_stats\n> *stats, AwbStatus const &awb,\n> >\n> >  static constexpr double EvGainYTargetLimit = 0.9;\n> >\n> > -static double constraintComputeGain(AgcConstraint &c, Histogram &h,\n> double lux,\n> > +static double constraintComputeGain(AgcConstraint &c, const Histogram\n> &h, double lux,\n> >                                     double evGain, double &targetY)\n> >  {\n> >         targetY = c.yTarget.eval(c.yTarget.domain().clip(lux));\n> >         targetY = std::min(EvGainYTargetLimit, targetY * evGain);\n> >         double iqm = h.interQuantileMean(c.qLo, c.qHi);\n> > -       return (targetY * NUM_HISTOGRAM_BINS) / iqm;\n> > +       return (targetY * h.bins()) / iqm;\n> >  }\n> >\n> > -void Agc::computeGain(bcm2835_isp_stats *statistics, Metadata\n> *imageMetadata,\n> > +void Agc::computeGain(StatisticsPtr &statistics, Metadata\n> *imageMetadata,\n> >                       double &gain, double &targetY)\n> >  {\n> >         struct LuxStatus lux = {};\n> >         lux.lux = 400; /* default lux level to 400 in case no metadata\n> found */\n> >         if (imageMetadata->get(\"lux.status\", lux) != 0)\n> >                 LOG(RPiAgc, Warning) << \"No lux level found\";\n> > -       Histogram h(statistics->hist[0].g_hist, NUM_HISTOGRAM_BINS);\n> > +       const Histogram &h = statistics->yHist;\n> >         double evGain = status_.ev * config_.baseEv;\n> >         /*\n> >          * The initial gain and target_Y come from some of the regions.\n> After\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/agc.h\n> b/src/ipa/raspberrypi/controller/rpi/agc.h\n> > index 6d6b0e5ad857..0ea71e6d1aa0 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/agc.h\n> > +++ b/src/ipa/raspberrypi/controller/rpi/agc.h\n> > @@ -98,7 +98,7 @@ private:\n> >         void housekeepConfig();\n> >         void fetchCurrentExposure(Metadata *imageMetadata);\n> >         void fetchAwbStatus(Metadata *imageMetadata);\n> > -       void computeGain(bcm2835_isp_stats *statistics, Metadata\n> *imageMetadata,\n> > +       void computeGain(StatisticsPtr &statistics, Metadata\n> *imageMetadata,\n> >                          double &gain, double &targetY);\n> >         void computeTargetExposure(double gain);\n> >         bool applyDigitalGain(double gain, double targetY);\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> > index a4afaf841c41..d8c650e6faa6 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> > +++ b/src/ipa/raspberrypi/controller/rpi/alsc.cpp\n> > @@ -310,18 +310,22 @@ double getCt(Metadata *metadata, double defaultCt)\n> >         return awbStatus.temperatureK;\n> >  }\n> >\n> > -static void copyStats(bcm2835_isp_stats_region regions[XY],\n> StatisticsPtr &stats,\n> > +static void copyStats(RgbyRegions &regions, StatisticsPtr &stats,\n> >                       AlscStatus const &status)\n> >  {\n> > -       bcm2835_isp_stats_region *inputRegions = stats->awb_stats;\n> > +       if (!regions.numRegions())\n> > +               regions.init(stats->awbRegions.numRegions());\n> > +\n> >         double *rTable = (double *)status.r;\n> >         double *gTable = (double *)status.g;\n> >         double *bTable = (double *)status.b;\n> > -       for (int i = 0; i < XY; i++) {\n> > -               regions[i].r_sum = inputRegions[i].r_sum / rTable[i];\n> > -               regions[i].g_sum = inputRegions[i].g_sum / gTable[i];\n> > -               regions[i].b_sum = inputRegions[i].b_sum / bTable[i];\n> > -               regions[i].counted = inputRegions[i].counted;\n> > +       for (unsigned int i = 0; i < stats->awbRegions.numRegions();\n> i++) {\n> > +               uint32_t counted, uncounted;\n> > +               auto s = stats->awbRegions.get(i, counted, uncounted);\n> > +               regions.set(i, { s.rSum /\n> static_cast<uint64_t>(rTable[i]),\n>\n> Just wondering a bit about the casts here, it doesn't look obviously\n> the same as what we have above...?\n>\n\nI'll cast the result of the div to minimise rounding errors.\n\n\n>\n> > +                                s.gSum /\n> static_cast<uint64_t>(gTable[i]),\n> > +                                s.bSum /\n> static_cast<uint64_t>(bTable[i]) },\n> > +                           counted, uncounted);\n> >                 /* (don't care about the uncounted value) */\n> >         }\n> >  }\n> > @@ -512,19 +516,20 @@ void resampleCalTable(double const calTableIn[XY],\n> >  }\n> >\n> >  /* Calculate chrominance statistics (R/G and B/G) for each region. */\n> > -static_assert(XY == AWB_REGIONS, \"ALSC/AWB statistics region mismatch\");\n> > -static void calculateCrCb(bcm2835_isp_stats_region *awbRegion, double\n> cr[XY],\n> > +static void calculateCrCb(const RgbyRegions &awbRegion, double cr[XY],\n> >                           double cb[XY], uint32_t minCount, uint16_t\n> minG)\n> >  {\n> >         for (int i = 0; i < XY; i++) {\n> > -               bcm2835_isp_stats_region &zone = awbRegion[i];\n> > -               if (zone.counted <= minCount ||\n> > -                   zone.g_sum / zone.counted <= minG) {\n> > +               uint32_t counted, uncounted;\n> > +               auto s = awbRegion.get(i, counted, uncounted);\n> > +\n> > +               if (counted <= minCount || s.gSum / counted <= minG) {\n> >                         cr[i] = cb[i] = InsufficientData;\n> >                         continue;\n> >                 }\n> > -               cr[i] = zone.r_sum / (double)zone.g_sum;\n> > -               cb[i] = zone.b_sum / (double)zone.g_sum;\n> > +\n> > +               cr[i] = s.rSum / (double)s.gSum;\n> > +               cb[i] = s.bSum / (double)s.gSum;\n> >         }\n> >  }\n> >\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/alsc.h\n> b/src/ipa/raspberrypi/controller/rpi/alsc.h\n> > index a858ef5a6551..9167c9ffa2e3 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/alsc.h\n> > +++ b/src/ipa/raspberrypi/controller/rpi/alsc.h\n> > @@ -12,6 +12,7 @@\n> >\n> >  #include \"../algorithm.h\"\n> >  #include \"../alsc_status.h\"\n> > +#include \"../statistics.h\"\n> >\n> >  namespace RPiController {\n> >\n> > @@ -98,7 +99,7 @@ private:\n> >         /* copy out the results from the async thread so that it can be\n> restarted */\n> >         void fetchAsyncResults();\n> >         double ct_;\n> > -       bcm2835_isp_stats_region statistics_[AlscCellsY * AlscCellsX];\n> > +       RgbyRegions statistics_;\n> >         double asyncResults_[3][AlscCellsY][AlscCellsX];\n> >         double asyncLambdaR_[AlscCellsX * AlscCellsY];\n> >         double asyncLambdaB_[AlscCellsX * AlscCellsY];\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/awb.cpp\n> b/src/ipa/raspberrypi/controller/rpi/awb.cpp\n> > index 861022014896..901498382c6b 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/awb.cpp\n> > +++ b/src/ipa/raspberrypi/controller/rpi/awb.cpp\n> > @@ -21,9 +21,6 @@ LOG_DEFINE_CATEGORY(RPiAwb)\n> >\n> >  #define NAME \"rpi.awb\"\n> >\n> > -static constexpr unsigned int AwbStatsSizeX = DEFAULT_AWB_REGIONS_X;\n> > -static constexpr unsigned int AwbStatsSizeY = DEFAULT_AWB_REGIONS_Y;\n> > -\n> >  /*\n> >   * todo - the locking in this algorithm needs some tidying up as has\n> been done\n> >   * elsewhere (ALSC and AGC).\n> > @@ -406,17 +403,18 @@ void Awb::asyncFunc()\n> >  }\n> >\n> >  static void generateStats(std::vector<Awb::RGB> &zones,\n> > -                         bcm2835_isp_stats_region *stats, double\n> minPixels,\n> > +                         RgbyRegions &stats, double minPixels,\n> >                           double minG)\n> >  {\n> > -       for (unsigned int i = 0; i < AwbStatsSizeX * AwbStatsSizeY; i++)\n> {\n> > +       for (unsigned int i = 0; i < stats.numRegions(); i++) {\n> >                 Awb::RGB zone;\n> > -               double counted = stats[i].counted;\n> > +               uint32_t counted, uncounted;\n> > +               auto s = stats.get(i, counted, uncounted);\n> >                 if (counted >= minPixels) {\n> > -                       zone.G = stats[i].g_sum / counted;\n> > +                       zone.G = s.gSum / counted;\n> >                         if (zone.G >= minG) {\n> > -                               zone.R = stats[i].r_sum / counted;\n> > -                               zone.B = stats[i].b_sum / counted;\n> > +                               zone.R = s.rSum / counted;\n> > +                               zone.B = s.bSum / counted;\n> >                                 zones.push_back(zone);\n> >                         }\n> >                 }\n> > @@ -430,7 +428,7 @@ void Awb::prepareStats()\n> >          * LSC has already been applied to the stats in this pipeline,\n> so stop\n> >          * any LSC compensation.  We also ignore config_.fast in this\n> version.\n> >          */\n> > -       generateStats(zones_, statistics_->awb_stats, config_.minPixels,\n> > +       generateStats(zones_, statistics_->awbRegions, config_.minPixels,\n> >                       config_.minG);\n> >         /*\n> >          * apply sensitivities, so values appear to come from our\n> \"canonical\"\n> > @@ -646,7 +644,7 @@ void Awb::awbBayes()\n> >          * valid... not entirely sure about this.\n> >          */\n> >         Pwl prior = interpolatePrior();\n> > -       prior *= zones_.size() / (double)(AwbStatsSizeX * AwbStatsSizeY);\n> > +       prior *= zones_.size() /\n> (double)(statistics_->awbRegions.numRegions());\n> >         prior.map([](double x, double y) {\n> >                 LOG(RPiAwb, Debug) << \"(\" << x << \",\" << y << \")\";\n> >         });\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/awb.h\n> b/src/ipa/raspberrypi/controller/rpi/awb.h\n> > index 30acd89d0969..d81779dd51ff 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/awb.h\n> > +++ b/src/ipa/raspberrypi/controller/rpi/awb.h\n> > @@ -13,6 +13,7 @@\n> >  #include \"../awb_algorithm.h\"\n> >  #include \"../pwl.h\"\n> >  #include \"../awb_status.h\"\n> > +#include \"../statistics.h\"\n> >\n> >  namespace RPiController {\n> >\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/contrast.cpp\n> b/src/ipa/raspberrypi/controller/rpi/contrast.cpp\n> > index 5b37edcbd46a..a4f8c4f04fc4 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/contrast.cpp\n> > +++ b/src/ipa/raspberrypi/controller/rpi/contrast.cpp\n> > @@ -106,7 +106,7 @@ Pwl computeStretchCurve(Histogram const &histogram,\n> >          * bit.\n> >          */\n> >         double histLo = histogram.quantile(config.loHistogram) *\n> > -                       (65536 / NUM_HISTOGRAM_BINS);\n> > +                       (65536 / histogram.bins());\n> >         double levelLo = config.loLevel * 65536;\n> >         LOG(RPiContrast, Debug)\n> >                 << \"Move histogram point \" << histLo << \" to \" <<\n> levelLo;\n> > @@ -119,7 +119,7 @@ Pwl computeStretchCurve(Histogram const &histogram,\n> >          * Keep the mid-point (median) in the same place, though, to\n> limit the\n> >          * apparent amount of global brightness shift.\n> >          */\n> > -       double mid = histogram.quantile(0.5) * (65536 /\n> NUM_HISTOGRAM_BINS);\n> > +       double mid = histogram.quantile(0.5) * (65536 /\n> histogram.bins());\n> >         enhance.append(mid, mid);\n> >\n> >         /*\n> > @@ -127,7 +127,7 @@ Pwl computeStretchCurve(Histogram const &histogram,\n> >          * there up.\n> >          */\n> >         double histHi = histogram.quantile(config.hiHistogram) *\n> > -                       (65536 / NUM_HISTOGRAM_BINS);\n> > +                       (65536 / histogram.bins());\n> >         double levelHi = config.hiLevel * 65536;\n> >         LOG(RPiContrast, Debug)\n> >                 << \"Move histogram point \" << histHi << \" to \" <<\n> levelHi;\n> > @@ -158,7 +158,7 @@ Pwl applyManualContrast(Pwl const &gammaCurve,\n> double brightness,\n> >  void Contrast::process(StatisticsPtr &stats,\n> >                        [[maybe_unused]] Metadata *imageMetadata)\n> >  {\n> > -       Histogram histogram(stats->hist[0].g_hist, NUM_HISTOGRAM_BINS);\n> > +       Histogram &histogram = stats->yHist;\n> >         /*\n> >          * We look at the histogram and adjust the gamma curve in the\n> following\n> >          * ways: 1. Adjust the gamma curve so as to pull the start of the\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/focus.cpp\n> b/src/ipa/raspberrypi/controller/rpi/focus.cpp\n> > index 8c5029bd0e95..41afbf43f2b7 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/focus.cpp\n> > +++ b/src/ipa/raspberrypi/controller/rpi/focus.cpp\n> > @@ -32,9 +32,10 @@ void Focus::process(StatisticsPtr &stats, Metadata\n> *imageMetadata)\n> >  {\n> >         FocusStatus status;\n> >         unsigned int i;\n> > -       for (i = 0; i < FOCUS_REGIONS; i++)\n> > -               status.focusMeasures[i] =\n> stats->focus_stats[i].contrast_val[1][1] / 1000;\n> > -       status.num = i;\n> > +       uint32_t counted, uncounted;\n> > +       for (i = 0; i < stats->focusRegions.numRegions(); i++)\n> > +               status.focusMeasures[i] = stats->focusRegions.get(i,\n> counted, uncounted);\n> > +       status.num = stats->focusRegions.numRegions();\n> >         imageMetadata->set(\"focus.status\", status);\n> >\n> >         LOG(RPiFocus, Debug)\n> > diff --git a/src/ipa/raspberrypi/controller/rpi/lux.cpp\n> b/src/ipa/raspberrypi/controller/rpi/lux.cpp\n> > index 9759186afacf..a49d402707c7 100644\n> > --- a/src/ipa/raspberrypi/controller/rpi/lux.cpp\n> > +++ b/src/ipa/raspberrypi/controller/rpi/lux.cpp\n> > @@ -6,8 +6,6 @@\n> >   */\n> >  #include <math.h>\n> >\n> > -#include <linux/bcm2835-isp.h>\n> > -\n> >  #include <libcamera/base/log.h>\n> >\n> >  #include \"../device_status.h\"\n> > @@ -83,20 +81,13 @@ void Lux::process(StatisticsPtr &stats, Metadata\n> *imageMetadata)\n> >         if (imageMetadata->get(\"device.status\", deviceStatus) == 0) {\n> >                 double currentGain = deviceStatus.analogueGain;\n> >                 double currentAperture =\n> deviceStatus.aperture.value_or(currentAperture_);\n> > -               uint64_t sum = 0;\n> > -               uint32_t num = 0;\n> > -               uint32_t *bin = stats->hist[0].g_hist;\n> > -               const int numBins = sizeof(stats->hist[0].g_hist) /\n> > -                                   sizeof(stats->hist[0].g_hist[0]);\n> > -               for (int i = 0; i < numBins; i++)\n> > -                       sum += bin[i] * (uint64_t)i, num += bin[i];\n> >                 /* add .5 to reflect the mid-points of bins */\n> > -               double currentY = sum / (double)num + .5;\n> > +               double currentY = stats->yHist.total() /\n> stats->yHist.bins() + .5;\n>\n> Is this the same thing? I have a bad feeling this might be the average\n> number of pixels per bin, or something. Maybe we need something\n> involving yHist.interQuantileMean(0, 1)...?\n>\n\nArgh, luckily we never use Lux values :-)\nyHist.interQuantileMean(0, 1) will be the right substitute here.\n\nNaush\n\n\n\n>\n> Thanks!\n> David\n>\n> >                 double gainRatio = referenceGain_ / currentGain;\n> >                 double shutterSpeedRatio =\n> >                         referenceShutterSpeed_ /\n> deviceStatus.shutterSpeed;\n> >                 double apertureRatio = referenceAperture_ /\n> currentAperture;\n> > -               double yRatio = currentY * (65536 / numBins) /\n> referenceY_;\n> > +               double yRatio = currentY * (65536 / stats->yHist.bins())\n> / referenceY_;\n> >                 double estimatedLux = shutterSpeedRatio * gainRatio *\n> >                                       apertureRatio * apertureRatio *\n> >                                       yRatio * referenceLux_;\n> > diff --git a/src/ipa/raspberrypi/raspberrypi.cpp\n> b/src/ipa/raspberrypi/raspberrypi.cpp\n> > index b74f1ecf738f..8fcfa0b3ea50 100644\n> > --- a/src/ipa/raspberrypi/raspberrypi.cpp\n> > +++ b/src/ipa/raspberrypi/raspberrypi.cpp\n> > @@ -51,6 +51,7 @@\n> >  #include \"metadata.h\"\n> >  #include \"sharpen_algorithm.h\"\n> >  #include \"sharpen_status.h\"\n> > +#include \"statistics.h\"\n> >\n> >  namespace libcamera {\n> >\n> > @@ -136,6 +137,7 @@ private:\n> >         void prepareISP(const ISPConfig &data);\n> >         void reportMetadata();\n> >         void fillDeviceStatus(const ControlList &sensorControls);\n> > +       RPiController::StatisticsPtr fillStatistics(bcm2835_isp_stats\n> *stats) const;\n> >         void processStats(unsigned int bufferId);\n> >         void applyFrameDurations(Duration minFrameDuration, Duration\n> maxFrameDuration);\n> >         void applyAGC(const struct AgcStatus *agcStatus, ControlList\n> &ctrls);\n> > @@ -1119,6 +1121,41 @@ void IPARPi::fillDeviceStatus(const ControlList\n> &sensorControls)\n> >         rpiMetadata_.set(\"device.status\", deviceStatus);\n> >  }\n> >\n> > +RPiController::StatisticsPtr IPARPi::fillStatistics(bcm2835_isp_stats\n> *stats) const\n> > +{\n> > +       using namespace RPiController;\n> > +\n> > +       unsigned int i;\n> > +       StatisticsPtr statistics =\n> > +\n>  std::make_unique<Statistics>(Statistics::AgcStatsPos::PreWb,\n> Statistics::ColourStatsPos::PostLsc);\n> > +\n> > +       /* RGB histograms are not used, so do not populate them. */\n> > +       statistics->yHist =\n> std::move(RPiController::Histogram(stats->hist[0].g_hist,\n> NUM_HISTOGRAM_BINS));\n> > +\n> > +       statistics->awbRegions.init(DEFAULT_AWB_REGIONS_X,\n> DEFAULT_AWB_REGIONS_Y);\n> > +       for (i = 0; i < statistics->awbRegions.numRegions(); i++)\n> > +               statistics->awbRegions.set(i, {\n> stats->awb_stats[i].r_sum,\n> > +\n>  stats->awb_stats[i].g_sum,\n> > +\n>  stats->awb_stats[i].b_sum },\n> > +                                          stats->awb_stats[i].counted,\n> stats->awb_stats[i].notcounted);\n> > +\n> > +       /* There are only ever 15 regions computed by the firmware, but\n> the HW defines AGC_REGIONS == 16! */\n> > +       statistics->agcRegions.init(15);\n> > +       for (i = 0; i < statistics->agcRegions.numRegions(); i++)\n> > +               statistics->agcRegions.set(i, {\n> stats->agc_stats[i].r_sum,\n> > +\n>  stats->agc_stats[i].g_sum,\n> > +\n>  stats->agc_stats[i].b_sum },\n> > +                                          stats->agc_stats[i].counted,\n> 0);\n> > +\n> > +       statistics->focusRegions.init(4, 3);\n> > +       for (i = 0; i < FOCUS_REGIONS; i++)\n> > +               statistics->focusRegions.set(i,\n> stats->focus_stats[i].contrast_val[1][1] / 1000,\n> > +\n> stats->focus_stats[i].contrast_val_num[1][1],\n> > +\n> stats->focus_stats[i].contrast_val_num[1][0]);\n> > +\n> > +       return statistics;\n> > +}\n> > +\n> >  void IPARPi::processStats(unsigned int bufferId)\n> >  {\n> >         auto it = buffers_.find(bufferId);\n> > @@ -1129,7 +1166,7 @@ void IPARPi::processStats(unsigned int bufferId)\n> >\n> >         Span<uint8_t> mem = it->second.planes()[0];\n> >         bcm2835_isp_stats *stats = reinterpret_cast<bcm2835_isp_stats\n> *>(mem.data());\n> > -       RPiController::StatisticsPtr statistics =\n> std::make_shared<bcm2835_isp_stats>(*stats);\n> > +       RPiController::StatisticsPtr statistics = fillStatistics(stats);\n> >         helper_->process(statistics, rpiMetadata_);\n> >         controller_.process(statistics, &rpiMetadata_);\n> >\n> > diff --git a/src/ipa/raspberrypi/statistics.h\n> b/src/ipa/raspberrypi/statistics.h\n> > index a762bf3d41aa..affb7272c963 100644\n> > --- a/src/ipa/raspberrypi/statistics.h\n> > +++ b/src/ipa/raspberrypi/statistics.h\n> > @@ -67,4 +67,6 @@ struct Statistics {\n> >         FocusRegions focusRegions;\n> >  };\n> >\n> > +using StatisticsPtr = std::shared_ptr<Statistics>;\n> > +\n> >  } /* namespace RPiController */\n> > --\n> > 2.25.1\n> >\n>","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id A3353BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed, 23 Nov 2022 14:53:22 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id EBABA63313;\n\tWed, 23 Nov 2022 15:53:21 +0100 (CET)","from mail-io1-xd36.google.com (mail-io1-xd36.google.com\n\t[IPv6:2607:f8b0:4864:20::d36])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 7C69D63311\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 23 Nov 2022 15:53:19 +0100 (CET)","by mail-io1-xd36.google.com with SMTP id r81so13307103iod.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 23 Nov 2022 06:53:19 -0800 (PST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1669215202;\n\tbh=ahHPZwpaLTr15OtpUE2NmzR67o+cQt7AozdOl/GD2hI=;\n\th=References:In-Reply-To:Date:To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=URpwzCFiUiTDyeiAdqcMnoRX+hASHvJdjSU2zpXNZvzl/sLsXsPVHAbVE3tLE9Wb8\n\tkJYDGCy5XHb7Px1pUZ/azqWOuiXJX+FplUVaE+E9FcAvdu92ujtLROfHivmuYCHntg\n\tny3pTpMmhyPoftpNHmiryJb+zkp/e5QS25wXqorQ4XwDZc1+2E+jeDKCbj5Ng0UpIr\n\tNuVPjUX04vU3Xu+c0jWEUn2GvmpwO9KJYmzL2RdA35jQKh7m8Tdts9I32G3vdQrgNn\n\tRTShW8+SRQRcolDvezca2BlY8iRIChyF3SI+jpU1g7XaoRBf/n6srln92p1+IEcLgE\n\t9HvsVguvznFSg==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:from:to:cc:subject:date:message-id:reply-to;\n\tbh=YOsDkOiacyZebKlvbaw4a9mWmAcVwY4+gmNJsEj5Byo=;\n\tb=eO63CGiZHIvBjsnHtbVoZkNaqdjfihBfOyYg28/MMOwDZkLn/qvCVLc0CaXl+4xlps\n\tZvIAc1FRbtifq8/Zx4yNQNPwwOaCFBVKWTaZwlzik3YWHSt0CvNjHbV5s2FsIrHYW5c/\n\tmVhYGCF6CpVVxr52h7vgwdRmULcQbhxbT9pUUmNeQ6hvYOJLQ9/DrBKjs3b7QxxdNu/E\n\t9Akkcu9GJl5pEY3ljKEFYtVEQDcGCvIv69EGwV0DIM+hodbWearx1mg6U/uUvi2v6/Yv\n\t3sERCYKMgwhPin5Bea3pI8SqUyO5ZpwNsbmvZlSUeqeNJ8sugzvdjwil4Mk5JgJWwPdB\n\tKPHA=="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"eO63CGiZ\"; dkim-atps=neutral","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=cc:to:subject:message-id:date:from:in-reply-to:references\n\t:mime-version:x-gm-message-state:from:to:cc:subject:date:message-id\n\t:reply-to;\n\tbh=YOsDkOiacyZebKlvbaw4a9mWmAcVwY4+gmNJsEj5Byo=;\n\tb=6wZyKxEMgtqJs32SHKUkn+JsENrN6wsUiHH+j47FKZjUHUGb8LDWeh31BaO9cJ8bsb\n\tXyxK0M311xeIcFGd4asQoJVt4aZhCwJ+RxmEtcxsBkQLSVur5/qeDesEDDGh+LP5QI7G\n\tkPC7jumaTjSLhKXrjYadeIVBLh1+cxlbuZUOIVawtWmlewFbcv5sm/zdvzfqQmhpREEA\n\tHk69OqOvBMnI/BAlWsDQz/UEo1MCF7KuUtEs/pwsgVE2QhNXzpDEH/fEJq3wPF6cfR1U\n\tSRE9HczkNGVWMwZq/j27FjkuUEoL/yFHT18MsFGkcATJyIortiQV19fq+p9ae7dwTTIf\n\taLPw==","X-Gm-Message-State":"ANoB5pmpcXKSzElZ/xPcX2wykfvTzL/n7+xxoxJwP86uRz6dRu+pyPCl\n\tTghF85SHbaEVHtMnXIsn0WilnbBw1GJOBmtHR5wQTD0YH2b1FA==","X-Google-Smtp-Source":"AA0mqf5z+cGaUwCc9KqLJwcux1HTP0e5SiDb0rIbU/R0gLJ0IT8fNTb7UkHag/AFPHUcRkzg7ELatvQHnii8RLtTxL8=","X-Received":"by 2002:a02:6a43:0:b0:375:4725:4b4f with SMTP id\n\tm3-20020a026a43000000b0037547254b4fmr13084600jaf.52.1669215198086;\n\tWed, 23 Nov 2022 06:53:18 -0800 (PST)","MIME-Version":"1.0","References":"<20221122112224.31691-1-naush@raspberrypi.com>\n\t<20221122112224.31691-5-naush@raspberrypi.com>\n\t<CAHW6GYJCUEKrrh_SDigG6-QhFmHYKiUrd1CdB+C8YappWMBiig@mail.gmail.com>","In-Reply-To":"<CAHW6GYJCUEKrrh_SDigG6-QhFmHYKiUrd1CdB+C8YappWMBiig@mail.gmail.com>","Date":"Wed, 23 Nov 2022 14:53:03 +0000","Message-ID":"<CAEmqJPrFm=pGCXc7b5oyokoYTspy+N1Y+8adAUWOvgmcQCYaAQ@mail.gmail.com>","To":"David Plowman <david.plowman@raspberrypi.com>","Content-Type":"multipart/alternative; boundary=\"000000000000ef9b0305ee247231\"","Subject":"Re: [libcamera-devel] [PATCH v1 4/5] ipa: raspberrypi: Use the\n\tgeneric statistics structure in the algorithms","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Naushir Patuck via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Naushir Patuck <naush@raspberrypi.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]