/* SPDX-License-Identifier: BSD-2-Clause */ /* * Copyright (C) 2019, Raspberry Pi (Trading) Limited * * alsc.cpp - ALSC (auto lens shading correction) control algorithm */ #include #include "../awb_status.h" #include "alsc.hpp" // Raspberry Pi ALSC (Auto Lens Shading Correction) algorithm. using namespace RPiController; #define NAME "rpi.alsc" static const int X = ALSC_CELLS_X; static const int Y = ALSC_CELLS_Y; static const int XY = X * Y; static const double INSUFFICIENT_DATA = -1.0; Alsc::Alsc(Controller *controller) : Algorithm(controller) { async_abort_ = async_start_ = async_started_ = async_finished_ = false; async_thread_ = std::thread(std::bind(&Alsc::asyncFunc, this)); } Alsc::~Alsc() { { std::lock_guard lock(mutex_); async_abort_ = true; } async_signal_.notify_one(); async_thread_.join(); } char const *Alsc::Name() const { return NAME; } static void generate_lut(double *lut, boost::property_tree::ptree const ¶ms) { double cstrength = params.get("corner_strength", 2.0); if (cstrength <= 1.0) throw std::runtime_error("Alsc: corner_strength must be > 1.0"); double asymmetry = params.get("asymmetry", 1.0); if (asymmetry < 0) throw std::runtime_error("Alsc: asymmetry must be >= 0"); double f1 = cstrength - 1, f2 = 1 + sqrt(cstrength); double R2 = X * Y / 4 * (1 + asymmetry * asymmetry); int num = 0; for (int y = 0; y < Y; y++) { for (int x = 0; x < X; x++) { double dy = y - Y / 2 + 0.5, dx = (x - X / 2 + 0.5) * asymmetry; double r2 = (dx * dx + dy * dy) / R2; lut[num++] = (f1 * r2 + f2) * (f1 * r2 + f2) / (f2 * f2); // this reproduces the cos^4 rule } } } static void read_lut(double *lut, boost::property_tree::ptree const ¶ms) { int num = 0; const int max_num = XY; for (auto &p : params) { if (num == max_num) throw std::runtime_error( "Alsc: too many entries in LSC table"); lut[num++] = p.second.get_value(); } if (num < max_num) throw std::runtime_error("Alsc: too few entries in LSC table"); } static void read_calibrations(std::vector &calibrations, boost::property_tree::ptree const ¶ms, std::string const &name) { if (params.get_child_optional(name)) { double last_ct = 0; for (auto &p : params.get_child(name)) { double ct = p.second.get("ct"); if (ct <= last_ct) throw std::runtime_error( "Alsc: entries in " + name + " must be in increasing ct order"); AlscCalibration calibration; calibration.ct = last_ct = ct; boost::property_tree::ptree const &table = p.second.get_child("table"); int num = 0; for (auto it = table.begin(); it != table.end(); it++) { if (num == XY) throw std::runtime_error( "Alsc: too many values for ct " + std::to_string(ct) + " in " + name); calibration.table[num++] = it->second.get_value(); } if (num != XY) throw std::runtime_error( "Alsc: too few values for ct " + std::to_string(ct) + " in " + name); calibrations.push_back(calibration); RPI_LOG("Read " << name << " calibration for ct " << ct); } } } void Alsc::Read(boost::property_tree::ptree const ¶ms) { RPI_LOG("Alsc"); config_.frame_period = params.get("frame_period", 12); config_.startup_frames = params.get("startup_frames", 10); config_.speed = params.get("speed", 0.05); double sigma = params.get("sigma", 0.01); config_.sigma_Cr = params.get("sigma_Cr", sigma); config_.sigma_Cb = params.get("sigma_Cb", sigma); config_.min_count = params.get("min_count", 10.0); config_.min_G = params.get("min_G", 50); config_.omega = params.get("omega", 1.3); config_.n_iter = params.get("n_iter", X + Y); config_.luminance_strength = params.get("luminance_strength", 1.0); for (int i = 0; i < XY; i++) config_.luminance_lut[i] = 1.0; if (params.get_child_optional("corner_strength")) generate_lut(config_.luminance_lut, params); else if (params.get_child_optional("luminance_lut")) read_lut(config_.luminance_lut, params.get_child("luminance_lut")); else RPI_WARN("Alsc: no luminance table - assume unity everywhere"); read_calibrations(config_.calibrations_Cr, params, "calibrations_Cr"); read_calibrations(config_.calibrations_Cb, params, "calibrations_Cb"); config_.default_ct = params.get("default_ct", 4500.0); config_.threshold = params.get("threshold", 1e-3); } static void get_cal_table(double ct, std::vector const &calibrations, double cal_table[XY]); static void resample_cal_table(double const cal_table_in[XY], CameraMode const &camera_mode, double cal_table_out[XY]); static void compensate_lambdas_for_cal(double const cal_table[XY], double const old_lambdas[XY], double new_lambdas[XY]); static void add_luminance_to_tables(double results[3][Y][X], double const lambda_r[XY], double lambda_g, double const lambda_b[XY], double const luminance_lut[XY], double luminance_strength); void Alsc::Initialise() { RPI_LOG("Alsc"); frame_count2_ = frame_count_ = frame_phase_ = 0; first_time_ = true; ct_ = config_.default_ct; // The lambdas are initialised in the SwitchMode. } void Alsc::waitForAysncThread() { if (async_started_) { async_started_ = false; std::unique_lock lock(mutex_); sync_signal_.wait(lock, [&] { return async_finished_; }); async_finished_ = false; } } static bool compare_modes(CameraMode const &cm0, CameraMode const &cm1) { // Return true if the modes crop from the sensor significantly differently, // or if the user transform has changed. if (cm0.transform != cm1.transform) return true; int left_diff = abs(cm0.crop_x - cm1.crop_x); int top_diff = abs(cm0.crop_y - cm1.crop_y); int right_diff = fabs(cm0.crop_x + cm0.scale_x * cm0.width - cm1.crop_x - cm1.scale_x * cm1.width); int bottom_diff = fabs(cm0.crop_y + cm0.scale_y * cm0.height - cm1.crop_y - cm1.scale_y * cm1.height); // These thresholds are a rather arbitrary amount chosen to trigger // when carrying on with the previously calculated tables might be // worse than regenerating them (but without the adaptive algorithm). int threshold_x = cm0.sensor_width >> 4; int threshold_y = cm0.sensor_height >> 4; return left_diff > threshold_x || right_diff > threshold_x || top_diff > threshold_y || bottom_diff > threshold_y; } void Alsc::SwitchMode(CameraMode const &camera_mode, [[maybe_unused]] Metadata *metadata) { // We're going to start over with the tables if there's any "significant" // change. bool reset_tables = first_time_ || compare_modes(camera_mode_, camera_mode); // Ensure the other thread isn't running while we do this. waitForAysncThread(); camera_mode_ = camera_mode; // We must resample the luminance table like we do the others, but it's // fixed so we can simply do it up front here. resample_cal_table(config_.luminance_lut, camera_mode_, luminance_table_); if (reset_tables) { // Upon every "table reset", arrange for something sensible to be // generated. Construct the tables for the previous recorded colour // temperature. In order to start over from scratch we initialise // the lambdas, but the rest of this code then echoes the code in // doAlsc, without the adaptive algorithm. for (int i = 0; i < XY; i++) lambda_r_[i] = lambda_b_[i] = 1.0; double cal_table_r[XY], cal_table_b[XY], cal_table_tmp[XY]; get_cal_table(ct_, config_.calibrations_Cr, cal_table_tmp); resample_cal_table(cal_table_tmp, camera_mode_, cal_table_r); get_cal_table(ct_, config_.calibrations_Cb, cal_table_tmp); resample_cal_table(cal_table_tmp, camera_mode_, cal_table_b); compensate_lambdas_for_cal(cal_table_r, lambda_r_, async_lambda_r_); compensate_lambdas_for_cal(cal_table_b, lambda_b_, async_lambda_b_); add_luminance_to_tables(sync_results_, async_lambda_r_, 1.0, async_lambda_b_, luminance_table_, config_.luminance_strength); memcpy(prev_sync_results_, sync_results_, sizeof(prev_sync_results_)); frame_phase_ = config_.frame_period; // run the algo again asap first_time_ = false; } } void Alsc::fetchAsyncResults() { RPI_LOG("Fetch ALSC results"); async_finished_ = false; async_started_ = false; memcpy(sync_results_, async_results_, sizeof(sync_results_)); } static double get_ct(Metadata *metadata, double default_ct) { AwbStatus awb_status; awb_status.temperature_K = default_ct; // in case nothing found if (metadata->Get("awb.status", awb_status) != 0) RPI_WARN("Alsc: no AWB results found, using " << awb_status.temperature_K); else RPI_LOG("Alsc: AWB results found, using " << awb_status.temperature_K); return awb_status.temperature_K; } static void copy_stats(bcm2835_isp_stats_region regions[XY], StatisticsPtr &stats, AlscStatus const &status) { bcm2835_isp_stats_region *input_regions = stats->awb_stats; double *r_table = (double *)status.r; double *g_table = (double *)status.g; double *b_table = (double *)status.b; for (int i = 0; i < XY; i++) { regions[i].r_sum = input_regions[i].r_sum / r_table[i]; regions[i].g_sum = input_regions[i].g_sum / g_table[i]; regions[i].b_sum = input_regions[i].b_sum / b_table[i]; regions[i].counted = input_regions[i].counted; // (don't care about the uncounted value) } } void Alsc::restartAsync(StatisticsPtr &stats, Metadata *image_metadata) { RPI_LOG("Starting ALSC thread"); // Get the current colour temperature. It's all we need from the // metadata. Default to the last CT value (which could be the default). ct_ = get_ct(image_metadata, ct_); // We have to copy the statistics here, dividing out our best guess of // the LSC table that the pipeline applied to them. AlscStatus alsc_status; if (image_metadata->Get("alsc.status", alsc_status) != 0) { RPI_WARN("No ALSC status found for applied gains!"); for (int y = 0; y < Y; y++) for (int x = 0; x < X; x++) { alsc_status.r[y][x] = 1.0; alsc_status.g[y][x] = 1.0; alsc_status.b[y][x] = 1.0; } } copy_stats(statistics_, stats, alsc_status); frame_phase_ = 0; async_started_ = true; { std::lock_guard lock(mutex_); async_start_ = true; } async_signal_.notify_one(); } void Alsc::Prepare(Metadata *image_metadata) { // Count frames since we started, and since we last poked the async // thread. if (frame_count_ < (int)config_.startup_frames) frame_count_++; double speed = frame_count_ < (int)config_.startup_frames ? 1.0 : config_.speed; RPI_LOG("Alsc: frame_count " << frame_count_ << " speed " << speed); { std::unique_lock lock(mutex_); if (async_started_ && async_finished_) { RPI_LOG("ALSC thread finished"); fetchAsyncResults(); } } // Apply IIR filter to results and program into the pipeline. double *ptr = (double *)sync_results_, *pptr = (double *)prev_sync_results_; for (unsigned int i = 0; i < sizeof(sync_results_) / sizeof(double); i++) pptr[i] = speed * ptr[i] + (1.0 - speed) * pptr[i]; // Put output values into status metadata. AlscStatus status; memcpy(status.r, prev_sync_results_[0], sizeof(status.r)); memcpy(status.g, prev_sync_results_[1], sizeof(status.g)); memcpy(status.b, prev_sync_results_[2], sizeof(status.b)); image_metadata->Set("alsc.status", status); } void Alsc::Process(StatisticsPtr &stats, Metadata *image_metadata) { // Count frames since we started, and since we last poked the async // thread. if (frame_phase_ < (int)config_.frame_period) frame_phase_++; if (frame_count2_ < (int)config_.startup_frames) frame_count2_++; RPI_LOG("Alsc: frame_phase " << frame_phase_); if (frame_phase_ >= (int)config_.frame_period || frame_count2_ < (int)config_.startup_frames) { if (async_started_ == false) { RPI_LOG("ALSC thread starting"); restartAsync(stats, image_metadata); } } } void Alsc::asyncFunc() { while (true) { { std::unique_lock lock(mutex_); async_signal_.wait(lock, [&] { return async_start_ || async_abort_; }); async_start_ = false; if (async_abort_) break; } doAlsc(); { std::lock_guard lock(mutex_); async_finished_ = true; } sync_signal_.notify_one(); } } void get_cal_table(double ct, std::vector const &calibrations, double cal_table[XY]) { if (calibrations.empty()) { for (int i = 0; i < XY; i++) cal_table[i] = 1.0; RPI_LOG("Alsc: no calibrations found"); } else if (ct <= calibrations.front().ct) { memcpy(cal_table, calibrations.front().table, XY * sizeof(double)); RPI_LOG("Alsc: using calibration for " << calibrations.front().ct); } else if (ct >= calibrations.back().ct) { memcpy(cal_table, calibrations.back().table, XY * sizeof(double)); RPI_LOG("Alsc: using calibration for " << calibrations.front().ct); } else { int idx = 0; while (ct > calibrations[idx + 1].ct) idx++; double ct0 = calibrations[idx].ct, ct1 = calibrations[idx + 1].ct; RPI_LOG("Alsc: ct is " << ct << ", interpolating between " << ct0 << " and " << ct1); for (int i = 0; i < XY; i++) cal_table[i] = (calibrations[idx].table[i] * (ct1 - ct) + calibrations[idx + 1].table[i] * (ct - ct0)) / (ct1 - ct0); } } void resample_cal_table(double const cal_table_in[XY], CameraMode const &camera_mode, double cal_table_out[XY]) { // Precalculate and cache the x sampling locations and phases to save // recomputing them on every row. int x_lo[X], x_hi[X]; double xf[X]; double scale_x = camera_mode.sensor_width / (camera_mode.width * camera_mode.scale_x); double x_off = camera_mode.crop_x / (double)camera_mode.sensor_width; double x = .5 / scale_x + x_off * X - .5; double x_inc = 1 / scale_x; for (int i = 0; i < X; i++, x += x_inc) { x_lo[i] = floor(x); xf[i] = x - x_lo[i]; x_hi[i] = std::min(x_lo[i] + 1, X - 1); x_lo[i] = std::max(x_lo[i], 0); if (!!(camera_mode.transform & libcamera::Transform::HFlip)) { x_lo[i] = X - 1 - x_lo[i]; x_hi[i] = X - 1 - x_hi[i]; } } // Now march over the output table generating the new values. double scale_y = camera_mode.sensor_height / (camera_mode.height * camera_mode.scale_y); double y_off = camera_mode.crop_y / (double)camera_mode.sensor_height; double y = .5 / scale_y + y_off * Y - .5; double y_inc = 1 / scale_y; for (int j = 0; j < Y; j++, y += y_inc) { int y_lo = floor(y); double yf = y - y_lo; int y_hi = std::min(y_lo + 1, Y - 1); y_lo = std::max(y_lo, 0); if (!!(camera_mode.transform & libcamera::Transform::VFlip)) { y_lo = Y - 1 - y_lo; y_hi = Y - 1 - y_hi; } double const *row_above = cal_table_in + X * y_lo; double const *row_below = cal_table_in + X * y_hi; for (int i = 0; i < X; i++) { double above = row_above[x_lo[i]] * (1 - xf[i]) + row_above[x_hi[i]] * xf[i]; double below = row_below[x_lo[i]] * (1 - xf[i]) + row_below[x_hi[i]] * xf[i]; *(cal_table_out++) = above * (1 - yf) + below * yf; } } } // Calculate chrominance statistics (R/G and B/G) for each region. static_assert(XY == AWB_REGIONS, "ALSC/AWB statistics region mismatch"); static void calculate_Cr_Cb(bcm2835_isp_stats_region *awb_region, double Cr[XY], double Cb[XY], uint32_t min_count, uint16_t min_G) { for (int i = 0; i < XY; i++) { bcm2835_isp_stats_region &zone = awb_region[i]; if (zone.counted <= min_count || zone.g_sum / zone.counted <= min_G) { Cr[i] = Cb[i] = INSUFFICIENT_DATA; continue; } Cr[i] = zone.r_sum / (double)zone.g_sum; Cb[i] = zone.b_sum / (double)zone.g_sum; } } static void apply_cal_table(double const cal_table[XY], double C[XY]) { for (int i = 0; i < XY; i++) if (C[i] != INSUFFICIENT_DATA) C[i] *= cal_table[i]; } void compensate_lambdas_for_cal(double const cal_table[XY], double const old_lambdas[XY], double new_lambdas[XY]) { double min_new_lambda = std::numeric_limits::max(); for (int i = 0; i < XY; i++) { new_lambdas[i] = old_lambdas[i] * cal_table[i]; min_new_lambda = std::min(min_new_lambda, new_lambdas[i]); } for (int i = 0; i < XY; i++) new_lambdas[i] /= min_new_lambda; } [[maybe_unused]] static void print_cal_table(double const C[XY]) { printf("table: [\n"); for (int j = 0; j < Y; j++) { for (int i = 0; i < X; i++) { printf("%5.3f", 1.0 / C[j * X + i]); if (i != X - 1 || j != Y - 1) printf(","); } printf("\n"); } printf("]\n"); } // Compute weight out of 1.0 which reflects how similar we wish to make the // colours of these two regions. static double compute_weight(double C_i, double C_j, double sigma) { if (C_i == INSUFFICIENT_DATA || C_j == INSUFFICIENT_DATA) return 0; double diff = (C_i - C_j) / sigma; return exp(-diff * diff / 2); } // Compute all weights. static void compute_W(double const C[XY], double sigma, double W[XY][4]) { for (int i = 0; i < XY; i++) { // Start with neighbour above and go clockwise. W[i][0] = i >= X ? compute_weight(C[i], C[i - X], sigma) : 0; W[i][1] = i % X < X - 1 ? compute_weight(C[i], C[i + 1], sigma) : 0; W[i][2] = i < XY - X ? compute_weight(C[i], C[i + X], sigma) : 0; W[i][3] = i % X ? compute_weight(C[i], C[i - 1], sigma) : 0; } } // Compute M, the large but sparse matrix such that M * lambdas = 0. static void construct_M(double const C[XY], double const W[XY][4], double M[XY][4]) { double epsilon = 0.001; for (int i = 0; i < XY; i++) { // Note how, if C[i] == INSUFFICIENT_DATA, the weights will all // be zero so the equation is still set up correctly. int m = !!(i >= X) + !!(i % X < X - 1) + !!(i < XY - X) + !!(i % X); // total number of neighbours // we'll divide the diagonal out straight away double diagonal = (epsilon + W[i][0] + W[i][1] + W[i][2] + W[i][3]) * C[i]; M[i][0] = i >= X ? (W[i][0] * C[i - X] + epsilon / m * C[i]) / diagonal : 0; M[i][1] = i % X < X - 1 ? (W[i][1] * C[i + 1] + epsilon / m * C[i]) / diagonal : 0; M[i][2] = i < XY - X ? (W[i][2] * C[i + X] + epsilon / m * C[i]) / diagonal : 0; M[i][3] = i % X ? (W[i][3] * C[i - 1] + epsilon / m * C[i]) / diagonal : 0; } } // In the compute_lambda_ functions, note that the matrix coefficients for the // left/right neighbours are zero down the left/right edges, so we don't need // need to test the i value to exclude them. static double compute_lambda_bottom(int i, double const M[XY][4], double lambda[XY]) { return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X] + M[i][3] * lambda[i - 1]; } static double compute_lambda_bottom_start(int i, double const M[XY][4], double lambda[XY]) { return M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X]; } static double compute_lambda_interior(int i, double const M[XY][4], double lambda[XY]) { return M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] + M[i][2] * lambda[i + X] + M[i][3] * lambda[i - 1]; } static double compute_lambda_top(int i, double const M[XY][4], double lambda[XY]) { return M[i][0] * lambda[i - X] + M[i][1] * lambda[i + 1] + M[i][3] * lambda[i - 1]; } static double compute_lambda_top_end(int i, double const M[XY][4], double lambda[XY]) { return M[i][0] * lambda[i - X] + M[i][3] * lambda[i - 1]; } // Gauss-Seidel iteration with over-relaxation. static double gauss_seidel2_SOR(double const M[XY][4], double omega, double lambda[XY]) { double old_lambda[XY]; int i; for (i = 0; i < XY; i++) old_lambda[i] = lambda[i]; lambda[0] = compute_lambda_bottom_start(0, M, lambda); for (i = 1; i < X; i++) lambda[i] = compute_lambda_bottom(i, M, lambda); for (; i < XY - X; i++) lambda[i] = compute_lambda_interior(i, M, lambda); for (; i < XY - 1; i++) lambda[i] = compute_lambda_top(i, M, lambda); lambda[i] = compute_lambda_top_end(i, M, lambda); // Also solve the system from bottom to top, to help spread the updates // better. lambda[i] = compute_lambda_top_end(i, M, lambda); for (i = XY - 2; i >= XY - X; i--) lambda[i] = compute_lambda_top(i, M, lambda); for (; i >= X; i--) lambda[i] = compute_lambda_interior(i, M, lambda); for (; i >= 1; i--) lambda[i] = compute_lambda_bottom(i, M, lambda); lambda[0] = compute_lambda_bottom_start(0, M, lambda); double max_diff = 0; for (i = 0; i < XY; i++) { lambda[i] = old_lambda[i] + (lambda[i] - old_lambda[i]) * omega; if (fabs(lambda[i] - old_lambda[i]) > fabs(max_diff)) max_diff = lambda[i] - old_lambda[i]; } return max_diff; } // Normalise the values so that the smallest value is 1. static void normalise(double *ptr, size_t n) { double minval = ptr[0]; for (size_t i = 1; i < n; i++) minval = std::min(minval, ptr[i]); for (size_t i = 0; i < n; i++) ptr[i] /= minval; } static void run_matrix_iterations(double const C[XY], double lambda[XY], double const W[XY][4], double omega, int n_iter, double threshold) { double M[XY][4]; construct_M(C, W, M); double last_max_diff = std::numeric_limits::max(); for (int i = 0; i < n_iter; i++) { double max_diff = fabs(gauss_seidel2_SOR(M, omega, lambda)); if (max_diff < threshold) { RPI_LOG("Stop after " << i + 1 << " iterations"); break; } // this happens very occasionally (so make a note), though // doesn't seem to matter if (max_diff > last_max_diff) RPI_LOG("Iteration " << i << ": max_diff gone up " << last_max_diff << " to " << max_diff); last_max_diff = max_diff; } // We're going to normalise the lambdas so the smallest is 1. Not sure // this is really necessary as they get renormalised later, but I // suppose it does stop these quantities from wandering off... normalise(lambda, XY); } static void add_luminance_rb(double result[XY], double const lambda[XY], double const luminance_lut[XY], double luminance_strength) { for (int i = 0; i < XY; i++) result[i] = lambda[i] * ((luminance_lut[i] - 1) * luminance_strength + 1); } static void add_luminance_g(double result[XY], double lambda, double const luminance_lut[XY], double luminance_strength) { for (int i = 0; i < XY; i++) result[i] = lambda * ((luminance_lut[i] - 1) * luminance_strength + 1); } void add_luminance_to_tables(double results[3][Y][X], double const lambda_r[XY], double lambda_g, double const lambda_b[XY], double const luminance_lut[XY], double luminance_strength) { add_luminance_rb((double *)results[0], lambda_r, luminance_lut, luminance_strength); add_luminance_g((double *)results[1], lambda_g, luminance_lut, luminance_strength); add_luminance_rb((double *)results[2], lambda_b, luminance_lut, luminance_strength); normalise((double *)results, 3 * XY); } void Alsc::doAlsc() { double Cr[XY], Cb[XY], Wr[XY][4], Wb[XY][4], cal_table_r[XY], cal_table_b[XY], cal_table_tmp[XY]; // Calculate our R/B ("Cr"/"Cb") colour statistics, and assess which are // usable. calculate_Cr_Cb(statistics_, Cr, Cb, config_.min_count, config_.min_G); // Fetch the new calibrations (if any) for this CT. Resample them in // case the camera mode is not full-frame. get_cal_table(ct_, config_.calibrations_Cr, cal_table_tmp); resample_cal_table(cal_table_tmp, camera_mode_, cal_table_r); get_cal_table(ct_, config_.calibrations_Cb, cal_table_tmp); resample_cal_table(cal_table_tmp, camera_mode_, cal_table_b); // You could print out the cal tables for this image here, if you're // tuning the algorithm... // Apply any calibration to the statistics, so the adaptive algorithm // makes only the extra adjustments. apply_cal_table(cal_table_r, Cr); apply_cal_table(cal_table_b, Cb); // Compute weights between zones. compute_W(Cr, config_.sigma_Cr, Wr); compute_W(Cb, config_.sigma_Cb, Wb); // Run Gauss-Seidel iterations over the resulting matrix, for R and B. run_matrix_iterations(Cr, lambda_r_, Wr, config_.omega, config_.n_iter, config_.threshold); run_matrix_iterations(Cb, lambda_b_, Wb, config_.omega, config_.n_iter, config_.threshold); // Fold the calibrated gains into our final lambda values. (Note that on // the next run, we re-start with the lambda values that don't have the // calibration gains included.) compensate_lambdas_for_cal(cal_table_r, lambda_r_, async_lambda_r_); compensate_lambdas_for_cal(cal_table_b, lambda_b_, async_lambda_b_); // Fold in the luminance table at the appropriate strength. add_luminance_to_tables(async_results_, async_lambda_r_, 1.0, async_lambda_b_, luminance_table_, config_.luminance_strength); } // Register algorithm with the system. static Algorithm *Create(Controller *controller) { return (Algorithm *)new Alsc(controller); } static RegisterAlgorithm reg(NAME, &Create); 9' href='#n649'>649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * options.cpp - cam - Options parsing
 */

#include <assert.h>
#include <getopt.h>
#include <iomanip>
#include <iostream>
#include <string.h>

#include "options.h"

/**
 * \enum OptionArgument
 * \brief Indicate if an option takes an argument
 *
 * \var OptionArgument::ArgumentNone
 * \brief The option doesn't accept any argument
 *
 * \var OptionArgument::ArgumentRequired
 * \brief The option requires an argument
 *
 * \var OptionArgument::ArgumentOptional
 * \brief The option accepts an optional argument
 */

/**
 * \enum OptionType
 * \brief The type of argument for an option
 *
 * \var OptionType::OptionNone
 * \brief No argument type, used for options that take no argument
 *
 * \var OptionType::OptionInteger
 * \brief Integer argument type, with an optional base prefix (`0` for base 8,
 * `0x` for base 16, none for base 10)
 *
 * \var OptionType::OptionString
 * \brief String argument
 *
 * \var OptionType::OptionKeyValue
 * \brief key=value list argument
 */

/* -----------------------------------------------------------------------------
 * Option
 */

/**
 * \struct Option
 * \brief Store metadata about an option
 *
 * \var Option::opt
 * \brief The option identifier
 *
 * \var Option::type
 * \brief The type of the option argument
 *
 * \var Option::name
 * \brief The option name
 *
 * \var Option::argument
 * \brief Whether the option accepts an optional argument, a mandatory
 * argument, or no argument at all
 *
 * \var Option::argumentName
 * \brief The argument name used in the help text
 *
 * \var Option::help
 * \brief The help text (may be a multi-line string)
 *
 * \var Option::keyValueParser
 * \brief For options of type OptionType::OptionKeyValue, the key-value parser
 * to parse the argument
 *
 * \var Option::isArray
 * \brief Whether the option can appear once or multiple times
 *
 * \var Option::parent
 * \brief The parent option
 *
 * \var Option::children
 * \brief List of child options, storing all options whose parent is this option
 *
 * \fn Option::hasShortOption()
 * \brief Tell if the option has a short option specifier (e.g. `-f`)
 * \return True if the option has a short option specifier, false otherwise
 *
 * \fn Option::hasLongOption()
 * \brief Tell if the option has a long option specifier (e.g. `--foo`)
 * \return True if the option has a long option specifier, false otherwise
 */
struct Option {
	int opt;
	OptionType type;
	const char *name;
	OptionArgument argument;
	const char *argumentName;
	const char *help;
	KeyValueParser *keyValueParser;
	bool isArray;
	Option *parent;
	std::list<Option> children;

	bool hasShortOption() const { return isalnum(opt); }
	bool hasLongOption() const { return name != nullptr; }
	const char *typeName() const;
	std::string optionName() const;
};

/**
 * \brief Retrieve a string describing the option type
 * \return A string describing the option type
 */
const char *Option::typeName() const
{
	switch (type) {
	case OptionNone:
		return "none";

	case OptionInteger:
		return "integer";

	case OptionString:
		return "string";

	case OptionKeyValue:
		return "key=value";
	}

	return "unknown";
}

/**
 * \brief Retrieve a string describing the option name, with leading dashes
 * \return A string describing the option name, as a long option identifier
 * (double dash) if the option has a name, or a short option identifier (single
 * dash) otherwise
 */
std::string Option::optionName() const
{
	if (name)
		return "--" + std::string(name);
	else
		return "-" + std::string(1, opt);
}

/* -----------------------------------------------------------------------------
 * OptionBase<T>
 */

/**
 * \class template<typename T> OptionBase
 * \brief Container to store the values of parsed options
 * \tparam T The type through which options are identified
 *
 * The OptionsBase class is generated by a parser (either OptionsParser or
 * KeyValueParser) when parsing options. It stores values for all the options
 * found, and exposes accessor functions to retrieve them. The options are
 * accessed through an identifier to type \a T, which is an int referencing an
 * Option::opt for OptionsParser, or a std::string referencing an Option::name
 * for KeyValueParser.
 */

/**
 * \fn OptionsBase::OptionsBase()
 * \brief Construct an OptionsBase instance
 *
 * The constructed instance is initially invalid, and will be populated by the
 * options parser.
 */

/**
 * \brief Tell if the stored options list is empty
 * \return True if the container is empty, false otherwise
 */
template<typename T>
bool OptionsBase<T>::empty() const
{
	return values_.empty();
}

/**
 * \brief Tell if the options parsing completed successfully
 * \return True if the container is returned after successfully parsing
 * options, false if it is returned after an error was detected during parsing
 */
template<typename T>
bool OptionsBase<T>::valid() const
{
	return valid_;
}

/**
 * \brief Tell if the option \a opt is specified
 * \param[in] opt The option to search for
 * \return True if the \a opt option is set, false otherwise
 */
template<typename T>
bool OptionsBase<T>::isSet(const T &opt) const
{
	return values_.find(opt) != values_.end();
}

/**
 * \brief Retrieve the value of option \a opt
 * \param[in] opt The option to retrieve
 * \return The value of option \a opt if found, an empty OptionValue otherwise
 */
template<typename T>
const OptionValue &OptionsBase<T>::operator[](const T &opt) const
{
	static const OptionValue empty;

	auto it = values_.find(opt);
	if (it != values_.end())
		return it->second;
	return empty;
}

/**
 * \brief Mark the container as invalid
 *
 * This function can be used in a key-value parser's override of the
 * KeyValueParser::parse() function to mark the returned options as invalid if
 * a validation error occurs.
 */
template<typename T>
void OptionsBase<T>::invalidate()
{
	valid_ = false;
}

template<typename T>
bool OptionsBase<T>::parseValue(const T &opt, const Option &option,
				const char *arg)
{
	OptionValue value;

	switch (option.type) {
	case OptionNone:
		break;

	case OptionInteger:
		unsigned int integer;

		if (arg) {
			char *endptr;
			integer = strtoul(arg, &endptr, 0);
			if (*endptr != '\0')
				return false;
		} else {
			integer = 0;
		}

		value = OptionValue(integer);
		break;

	case OptionString:
		value = OptionValue(arg ? arg : "");
		break;

	case OptionKeyValue:
		KeyValueParser *kvParser = option.keyValueParser;
		KeyValueParser::Options keyValues = kvParser->parse(arg);
		if (!keyValues.valid())
			return false;

		value = OptionValue(keyValues);
		break;
	}

	if (option.isArray)
		values_[opt].addValue(value);
	else
		values_[opt] = value;

	return true;
}

template class OptionsBase<int>;
template class OptionsBase<std::string>;

/* -----------------------------------------------------------------------------
 * KeyValueParser
 */

/**
 * \class KeyValueParser
 * \brief A specialized parser for list of key-value pairs
 *
 * The KeyValueParser is an options parser for comma-separated lists of
 * `key=value` pairs. The supported keys are added to the parser with
 * addOption(). A given key can only appear once in the parsed list.
 *
 * Instances of this class can be passed to the OptionsParser::addOption()
 * function to create options that take key-value pairs as an option argument.
 * Specialized versions of the key-value parser can be created by inheriting
 * from this class, to pre-build the options list in the constructor, and to add
 * custom validation by overriding the parse() function.
 */

/**
 * \class KeyValueParser::Options
 * \brief An option list generated by the key-value parser
 *
 * This is a specialization of OptionsBase with the option reference type set to
 * std::string.
 */

KeyValueParser::KeyValueParser() = default;
KeyValueParser::~KeyValueParser() = default;

/**
 * \brief Add a supported option to the parser
 * \param[in] name The option name, corresponding to the key name in the
 * key=value pair. The name shall be unique.
 * \param[in] type The type of the value in the key=value pair
 * \param[in] help The help text
 * \param[in] argument Whether the value is optional, mandatory or not allowed.
 * Shall be ArgumentNone if \a type is OptionNone.
 *
 * \sa OptionsParser
 *
 * \return True if the option was added successfully, false if an error
 * occurred.
 */
bool KeyValueParser::addOption(const char *name, OptionType type,
			       const char *help, OptionArgument argument)
{
	if (!name)
		return false;
	if (!help || help[0] == '\0')
		return false;
	if (argument != ArgumentNone && type == OptionNone)
		return false;

	/* Reject duplicate options. */
	if (optionsMap_.find(name) != optionsMap_.end())
		return false;

	optionsMap_[name] = Option({ 0, type, name, argument, nullptr,
				     help, nullptr, false, nullptr, {} });
	return true;
}

/**
 * \brief Parse a string containing a list of key-value pairs
 * \param[in] arguments The key-value pairs string to parse
 *
 * If a parsing error occurs, the parsing stops and the function returns an
 * invalid container. The container is populated with the options successfully
 * parsed so far.
 *
 * \return A valid container with the list of parsed options on success, or an
 * invalid container otherwise
 */
KeyValueParser::Options KeyValueParser::parse(const char *arguments)
{
	Options options;

	for (const char *pair = arguments; *arguments != '\0'; pair = arguments) {
		const char *comma = strchrnul(arguments, ',');
		size_t len = comma - pair;

		/* Skip over the comma. */
		arguments = *comma == ',' ? comma + 1 : comma;

		/* Skip to the next pair if the pair is empty. */
		if (!len)
			continue;

		std::string key;
		std::string value;

		const char *separator = static_cast<const char *>(memchr(pair, '=', len));
		if (!separator) {
			key = std::string(pair, len);
			value = "";
		} else {
			key = std::string(pair, separator - pair);
			value = std::string(separator + 1, comma - separator - 1);
		}

		/* The key is mandatory, the value might be optional. */
		if (key.empty())
			continue;

		if (optionsMap_.find(key) == optionsMap_.end()) {
			std::cerr << "Invalid option " << key << std::endl;
			return options;
		}

		OptionArgument arg = optionsMap_[key].argument;
		if (value.empty() && arg == ArgumentRequired) {
			std::cerr << "Option " << key << " requires an argument"
				  << std::endl;
			return options;
		} else if (!value.empty() && arg == ArgumentNone) {
			std::cerr << "Option " << key << " takes no argument"
				  << std::endl;
			return options;
		}

		const Option &option = optionsMap_[key];
		if (!options.parseValue(key, option, value.c_str())) {
			std::cerr << "Failed to parse '" << value << "' as "
				  << option.typeName() << " for option " << key
				  << std::endl;
			return options;
		}
	}

	options.valid_ = true;
	return options;
}

unsigned int KeyValueParser::maxOptionLength() const
{
	unsigned int maxLength = 0;

	for (auto const &iter : optionsMap_) {
		const Option &option = iter.second;
		unsigned int length = 10 + strlen(option.name);
		if (option.argument != ArgumentNone)
			length += 1 + strlen(option.typeName());
		if (option.argument == ArgumentOptional)
			length += 2;

		if (length > maxLength)
			maxLength = length;
	}

	return maxLength;
}

void KeyValueParser::usage(int indent)
{
	for (auto const &iter : optionsMap_) {
		const Option &option = iter.second;
		std::string argument = std::string("          ") + option.name;

		if (option.argument != ArgumentNone) {
			if (option.argument == ArgumentOptional)
				argument += "[=";
			else
				argument += "=";
			argument += option.typeName();
			if (option.argument == ArgumentOptional)
				argument += "]";
		}

		std::cerr << std::setw(indent) << argument;

		for (const char *help = option.help, *end = help; end;) {
			end = strchr(help, '\n');
			if (end) {
				std::cerr << std::string(help, end - help + 1);
				std::cerr << std::setw(indent) << " ";
				help = end + 1;
			} else {
				std::cerr << help << std::endl;
			}
		}
	}
}

/* -----------------------------------------------------------------------------
 * OptionValue
 */

/**
 * \class OptionValue
 * \brief Container to store the value of an option
 *
 * The OptionValue class is a variant-type container to store the value of an
 * option. It supports empty values, integers, strings, key-value lists, as well
 * as arrays of those types. For array values, all array elements shall have the
 * same type.
 *
 * OptionValue instances are organized in a tree-based structure that matches
 * the parent-child relationship of the options added to the parser. Children
 * are retrieved with the children() function, and are stored as an
 * OptionsBase<int>.
 */

/**
 * \enum OptionValue::ValueType
 * \brief The option value type
 *
 * \var OptionValue::ValueType::ValueNone
 * \brief Empty value
 *
 * \var OptionValue::ValueType::ValueInteger
 * \brief Integer value (int)
 *
 * \var OptionValue::ValueType::ValueString
 * \brief String value (std::string)
 *
 * \var OptionValue::ValueType::ValueKeyValue
 * \brief Key-value list value (KeyValueParser::Options)
 *
 * \var OptionValue::ValueType::ValueArray
 * \brief Array value
 */

/**
 * \brief Construct an empty OptionValue instance
 *
 * The value type is set to ValueType::ValueNone.
 */
OptionValue::OptionValue()
	: type_(ValueNone), integer_(0)
{
}

/**
 * \brief Construct an integer OptionValue instance
 * \param[in] value The integer value
 *
 * The value type is set to ValueType::ValueInteger.
 */
OptionValue::OptionValue(int value)
	: type_(ValueInteger), integer_(value)
{
}

/**
 * \brief Construct a string OptionValue instance
 * \param[in] value The string value
 *
 * The value type is set to ValueType::ValueString.
 */
OptionValue::OptionValue(const char *value)
	: type_(ValueString), integer_(0), string_(value)
{
}

/**
 * \brief Construct a string OptionValue instance
 * \param[in] value The string value
 *
 * The value type is set to ValueType::ValueString.
 */
OptionValue::OptionValue(const std::string &value)
	: type_(ValueString), integer_(0), string_(value)
{
}

/**
 * \brief Construct a key-value OptionValue instance
 * \param[in] value The key-value list
 *
 * The value type is set to ValueType::ValueKeyValue.
 */
OptionValue::OptionValue(const KeyValueParser::Options &value)
	: type_(ValueKeyValue), integer_(0), keyValues_(value)
{
}

/**
 * \brief Add an entry to an array value
 * \param[in] value The entry value
 *
 * This function can only be called if the OptionValue type is
 * ValueType::ValueNone or ValueType::ValueArray. Upon return, the type will be
 * set to ValueType::ValueArray.
 */
void OptionValue::addValue(const OptionValue &value)
{
	assert(type_ == ValueNone || type_ == ValueArray);

	type_ = ValueArray;
	array_.push_back(value);
}

/**
 * \fn OptionValue::type()
 * \brief Retrieve the value type
 * \return The value type
 */

/**
 * \fn OptionValue::empty()
 * \brief Check if the value is empty
 * \return True if the value is empty (type set to ValueType::ValueNone), or
 * false otherwise
 */

/**
 * \brief Cast the value to an int
 * \return The option value as an int, or 0 if the value type isn't
 * ValueType::ValueInteger
 */
OptionValue::operator int() const
{
	return toInteger();
}

/**
 * \brief Cast the value to a std::string
 * \return The option value as an std::string, or an empty string if the value
 * type isn't ValueType::ValueString
 */
OptionValue::operator std::string() const
{
	return toString();
}

/**
 * \brief Retrieve the value as an int
 * \return The option value as an int, or 0 if the value type isn't
 * ValueType::ValueInteger
 */
int OptionValue::toInteger() const
{
	if (type_ != ValueInteger)
		return 0;

	return integer_;
}

/**
 * \brief Retrieve the value as a std::string
 * \return The option value as a std::string, or an empty string if the value
 * type isn't ValueType::ValueString
 */
std::string OptionValue::toString() const
{
	if (type_ != ValueString)
		return std::string();

	return string_;
}

/**
 * \brief Retrieve the value as a key-value list
 *
 * The behaviour is undefined if the value type isn't ValueType::ValueKeyValue.
 *
 * \return The option value as a KeyValueParser::Options
 */
const KeyValueParser::Options &OptionValue::toKeyValues() const
{
	assert(type_ == ValueKeyValue);
	return keyValues_;
}

/**
 * \brief Retrieve the value as an array
 *
 * The behaviour is undefined if the value type isn't ValueType::ValueArray.
 *
 * \return The option value as a std::vector of OptionValue
 */
const std::vector<OptionValue> &OptionValue::toArray() const
{
	assert(type_ == ValueArray);
	return array_;
}

/**
 * \brief Retrieve the list of child values
 * \return The list of child values
 */
const OptionsParser::Options &OptionValue::children() const
{
	return children_;
}

/* -----------------------------------------------------------------------------
 * OptionsParser
 */

/**
 * \class OptionsParser
 * \brief A command line options parser
 *
 * The OptionsParser class is an easy to use options parser for POSIX-style
 * command line options. Supports short (e.g. `-f`) and long (e.g. `--foo`)
 * options, optional and mandatory arguments, automatic parsing arguments for
 * integer types and comma-separated list of key=value pairs, and multi-value
 * arguments. It handles help text generation automatically.
 *
 * An OptionsParser instance is initialized by adding supported options with
 * addOption(). Options are specified by an identifier and a name. If the
 * identifier is an alphanumeric character, it will be used by the parser as a
 * short option identifier (e.g. `-f`). The name, if specified, will be used as
 * a long option identifier (e.g. `--foo`). It should not include the double
 * dashes. The name is optional if the option identifier is an alphanumeric
 * character and mandatory otherwise.
 *
 * An option has a mandatory help text, which is used to print the full options
 * list with the usage() function. The help text may be a multi-line string.
 * Correct indentation of the help text is handled automatically.
 *
 * Options accept arguments when created with OptionArgument::ArgumentRequired
 * or OptionArgument::ArgumentOptional. If the argument is required, it can be
 * specified as a positional argument after the option (e.g. `-f bar`,
 * `--foo bar`), collated with the short option (e.g. `-fbar`) or separated from
 * the long option by an equal sign (e.g. `--foo=bar`'). When the argument is
 * optional, it must be collated with the short option or separated from the
 * long option by an equal sign.
 *
 * If an option has a required or optional argument, an argument name must be
 * set when adding the option. The argument name is used in the help text as a
 * place holder for an argument value. For instance, a `--write` option that
 * takes a file name as an argument could set the argument name to `filename`,
 * and the help text would display `--write filename`. This is only used to
 * clarify the help text and has no effect on option parsing.
 *
 * The option type tells the parser how to process the argument. Arguments for
 * string options (OptionType::OptionString) are stored as-is without any
 * processing. Arguments for integer options (OptionType::OptionInteger) are
 * converted to an integer value, using an optional base prefix (`0` for base 8,
 * `0x` for base 16, none for base 10). Arguments for key-value options are
 * parsed by a KeyValueParser given to addOption().
 *
 * By default, a given option can appear once only in the parsed command line.
 * If the option is created as an array option, the parser will accept multiple
 * instances of the option. The order in which identical options are specified
 * is preserved in the values of an array option.
 *
 * After preparing the parser, it can be used any number of times to parse
 * command line options with the parse() function. The function returns an
 * Options instance that stores the values for the parsed options. The
 * Options::isSet() function can be used to test if an option has been found,
 * and is the only way to access options that take no argument (specified by
 * OptionType::OptionNone and OptionArgument::ArgumentNone). For options that
 * accept an argument, the option value can be access by Options::operator[]()
 * using the option identifier as the key. The order in which different options
 * are specified on the command line isn't preserved.
 *
 * Options can be created with parent-child relationships to organize them as a
 * tree instead of a flat list. When parsing a command line, the child options
 * are considered related to the parent option that precedes them. This is
 * useful when the parent is an array option. The Options values list generated
 * by the parser then turns into a tree, which each parent value storing the
 * values of child options that follow that instance of the parent option.
 * For instance, with a `capture` option specified as a child of a `camera`
 * array option, parsing the command line
 *
 * `--camera 1 --capture=10 --camera 2 --capture=20`
 *
 * will return an Options instance containing a single OptionValue instance of
 * array type, for the `camera` option. The OptionValue will contain two
 * entries, with the first entry containing the integer value 1 and the second
 * entry the integer value 2. Each of those entries will in turn store an
 * Options instance that contains the respective children. The first entry will
 * store in its children a `capture` option of value 10, and the second entry a
 * `capture` option of value 20.
 *
 * The command line
 *
 * `--capture=10 --camera 1`
 *
 * would result in a parsing error, as the `capture` option has no preceding
 * `camera` option on the command line.
 */

/**
 * \class OptionsParser::Options
 * \brief An option list generated by the options parser
 *
 * This is a specialization of OptionsBase with the option reference type set to
 * int.
 */

OptionsParser::OptionsParser() = default;
OptionsParser::~OptionsParser() = default;

/**
 * \brief Add an option to the parser
 * \param[in] opt The option identifier
 * \param[in] type The type of the option argument
 * \param[in] help The help text (may be a multi-line string)
 * \param[in] name The option name
 * \param[in] argument Whether the option accepts an optional argument, a
 * mandatory argument, or no argument at all
 * \param[in] argumentName The argument name used in the help text