mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 10:40:47 +03:00
Merge branch 'feat/isp_dpc_support_v6.0' into 'release/v6.0'
feat(isp): support isp dpc dead pixel correction (v6.0) See merge request espressif/esp-idf!51470
This commit is contained in:
@@ -21,7 +21,8 @@ if(CONFIG_SOC_ISP_SUPPORTED)
|
||||
"src/isp_awb.c"
|
||||
"src/isp_ae.c"
|
||||
"src/isp_gamma.c"
|
||||
"src/isp_hist.c")
|
||||
"src/isp_hist.c"
|
||||
"src/isp_dpc.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_ISP_BF_SUPPORTED)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "driver/isp_ccm.h"
|
||||
#include "driver/isp_color.h"
|
||||
#include "driver/isp_demosaic.h"
|
||||
#include "driver/isp_dpc.h"
|
||||
#include "driver/isp_gamma.h"
|
||||
#include "driver/isp_hist.h"
|
||||
#include "driver/isp_lsc.h"
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/isp_types.h"
|
||||
#include "driver/isp_dpc_dynamic.h"
|
||||
#include "driver/isp_dpc_static.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*---------------------------------------------------------------
|
||||
DPC (Dead Pixel Correction)
|
||||
---------------------------------------------------------------*/
|
||||
/**
|
||||
* @brief ISP DPC common configuration
|
||||
*
|
||||
* This configuration controls common DPC behavior only. Configure static LUT
|
||||
* coordinates with esp_isp_dpc_static_configure() and dynamic algorithm
|
||||
* parameters with esp_isp_dpc_dynamic_configure().
|
||||
*/
|
||||
typedef struct {
|
||||
struct {
|
||||
uint32_t update_once_configured : 1; ///< If set, apply configuration to hardware immediately; otherwise defer to frame boundary
|
||||
} flags; ///< Driver behavior flags
|
||||
} esp_isp_dpc_config_t;
|
||||
|
||||
/**
|
||||
* @brief Configure common ISP DPC settings
|
||||
*
|
||||
* @note Call this while DPC is disabled, after configuring any required static
|
||||
* and dynamic correction paths, and before esp_isp_dpc_enable(). This API
|
||||
* configures the common clock and shadow-register update behavior; it does
|
||||
* not configure or enable static or dynamic correction.
|
||||
*
|
||||
* @param[in] isp_proc Processor handle
|
||||
* @param[in] config DPC common configuration
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_STATE DPC is enabled
|
||||
* - ESP_ERR_INVALID_ARG Invalid argument
|
||||
*/
|
||||
esp_err_t esp_isp_dpc_configure(isp_proc_handle_t isp_proc, const esp_isp_dpc_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Enable ISP DPC function
|
||||
*
|
||||
* @note Call `esp_isp_dpc_static_configure` and/or `esp_isp_dpc_dynamic_configure`,
|
||||
* then `esp_isp_dpc_configure`, first. Static and dynamic correction can be
|
||||
* configured together.
|
||||
* Static calibration is a separate flow started by
|
||||
* `esp_isp_dpc_static_calibration_start_once`; that API enables DPC internally.
|
||||
*
|
||||
* @param[in] isp_proc Processor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_ARG If the combination of arguments is invalid.
|
||||
* - ESP_ERR_INVALID_STATE Driver state is invalid.
|
||||
*/
|
||||
esp_err_t esp_isp_dpc_enable(isp_proc_handle_t isp_proc);
|
||||
|
||||
/**
|
||||
* @brief Disable ISP DPC function
|
||||
*
|
||||
* @param[in] isp_proc Processor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_ARG If the combination of arguments is invalid.
|
||||
* - ESP_ERR_INVALID_STATE Driver state is invalid.
|
||||
*/
|
||||
esp_err_t esp_isp_dpc_disable(isp_proc_handle_t isp_proc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/isp_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief ISP DPC dynamic correction method
|
||||
*
|
||||
* Method 1 uses absolute thresholds around the minimum and maximum of the
|
||||
* eight neighboring pixels. Method 2 uses ratios and a second adaptive test
|
||||
* against the neighboring-pixel estimate.
|
||||
*/
|
||||
typedef enum {
|
||||
ESP_ISP_DPC_DYNAMIC_METHOD_1 = 0, ///< Detects a pixel outside [min8 - low_threshold, max8 + high_threshold]
|
||||
ESP_ISP_DPC_DYNAMIC_METHOD_2 = 1, ///< Applies ratio screening followed by an estimate-based adaptive threshold test
|
||||
} esp_isp_dpc_dynamic_method_t;
|
||||
|
||||
/**
|
||||
* @brief ISP DPC dynamic correction configurations
|
||||
*
|
||||
* @note Both methods examine the eight same-color neighboring pixels and replace a
|
||||
* detected dead pixel with their median value. Method 1 directly compares the center
|
||||
* pixel with the neighboring minimum and maximum using absolute thresholds. Method 2
|
||||
* first keeps pixels within a max8-based ratio range. For pixels outside that range,
|
||||
* it compares the center pixel with the neighboring-pixel estimate using adaptive
|
||||
* bright and dark thresholds.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_isp_dpc_dynamic_method_t method; ///< Dynamic correction method
|
||||
union {
|
||||
struct {
|
||||
uint8_t high_threshold; ///< A pixel above max8 + high_threshold is a bright dead-pixel candidate (0-255)
|
||||
uint8_t low_threshold; ///< A pixel below min8 - low_threshold is a dark dead-pixel candidate (0-255)
|
||||
} method_1;
|
||||
struct {
|
||||
isp_dpc_ratio_t first_stage_upper_ratio; /*!< Upper bound of the first-stage normal-pixel range.
|
||||
* Value = integer + decimal / ISP_DPC_RATIO_MAX. Range 0 ~ 1.
|
||||
* Fractional values use integer 0 and decimal 0 ~ (ISP_DPC_RATIO_MAX - 1);
|
||||
* 1.0 uses integer 1 and decimal 0. Must be greater than first_stage_lower_ratio.
|
||||
*/
|
||||
isp_dpc_ratio_t first_stage_lower_ratio; /*!< Lower bound of the first-stage normal-pixel range.
|
||||
* Value = integer + decimal / ISP_DPC_RATIO_MAX. Range 0 ~ 1.
|
||||
* Fractional values use integer 0 and decimal 0 ~ (ISP_DPC_RATIO_MAX - 1);
|
||||
* 1.0 uses integer 1 and decimal 0.
|
||||
*/
|
||||
isp_dpc_deviation_factor_t bright_deviation_factor; /*!< Second-stage bright-pixel sensitivity.
|
||||
* Value = integer + decimal / ISP_DPC_DEVIATION_FACTOR_MAX. Range 0 ~ 1.
|
||||
* Fractional values use integer 0 and decimal 0 ~ (ISP_DPC_DEVIATION_FACTOR_MAX - 1);
|
||||
* 1.0 uses integer 1 and decimal 0. A smaller value corrects bright pixels more aggressively.
|
||||
*/
|
||||
isp_dpc_deviation_factor_t dark_deviation_factor; /*!< Second-stage dark-pixel sensitivity.
|
||||
* Value = integer + decimal / ISP_DPC_DEVIATION_FACTOR_MAX. Range 0 ~ 1.
|
||||
* Fractional values use integer 0 and decimal 0 ~ (ISP_DPC_DEVIATION_FACTOR_MAX - 1);
|
||||
* 1.0 uses integer 1 and decimal 0. A smaller value corrects dark pixels more aggressively.
|
||||
*/
|
||||
} method_2;
|
||||
};
|
||||
} esp_isp_dpc_dynamic_config_t;
|
||||
|
||||
/**
|
||||
* @brief Configure dynamic DPC correction
|
||||
*
|
||||
* @note Call this while DPC is disabled. This function enables dynamic correction but
|
||||
* does not enable the DPC module; call esp_isp_dpc_configure() and then
|
||||
* esp_isp_dpc_enable() afterwards. Static and dynamic correction can be
|
||||
* configured together.
|
||||
*
|
||||
* @param[in] isp_proc Processor handle
|
||||
* @param[in] config Dynamic correction configuration
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_ARG Invalid argument or dynamic parameters
|
||||
* - ESP_ERR_INVALID_STATE DPC is enabled
|
||||
* - ESP_ERR_NOT_SUPPORTED Not supported
|
||||
*/
|
||||
esp_err_t esp_isp_dpc_dynamic_configure(isp_proc_handle_t isp_proc, const esp_isp_dpc_dynamic_config_t *config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/isp_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define ESP_ISP_DPC_MAX_DEAD_PIXELS 512 ///< Maximum number of dead pixels supported by the static DPC LUT
|
||||
|
||||
/**
|
||||
* @brief DPC pixel coordinate
|
||||
*/
|
||||
typedef struct {
|
||||
uint16_t x; ///< Horizontal coordinate
|
||||
uint16_t y; ///< Vertical coordinate
|
||||
} esp_isp_dpc_pixel_coord_t;
|
||||
|
||||
/**
|
||||
* @brief ISP DPC static correction configurations
|
||||
*/
|
||||
typedef struct {
|
||||
const esp_isp_dpc_pixel_coord_t *dead_pixel_coords; ///< Unique coordinates in ascending y/x order. Coordinates must be inside the input frame. The driver reads this array only during esp_isp_dpc_static_configure().
|
||||
uint32_t dead_pixel_count; ///< Number of dead pixels in the array (0 to ESP_ISP_DPC_MAX_DEAD_PIXELS)
|
||||
} esp_isp_dpc_static_config_t;
|
||||
|
||||
/**
|
||||
* @brief Configure static DPC correction
|
||||
*
|
||||
* @note Call this while DPC is disabled. This function writes the coordinates to the
|
||||
* hardware LUT and enables static correction. It does not enable the DPC module;
|
||||
* call esp_isp_dpc_configure() and then esp_isp_dpc_enable() afterwards. The
|
||||
* driver reads dead_pixel_coords only during this call and does not retain the
|
||||
* array after it returns.
|
||||
*
|
||||
* @param[in] isp_proc Processor handle
|
||||
* @param[in] config Static correction configuration
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_STATE DPC is enabled
|
||||
* - ESP_ERR_INVALID_ARG Invalid argument or coordinates
|
||||
* - ESP_ERR_NOT_SUPPORTED Not supported
|
||||
*/
|
||||
esp_err_t esp_isp_dpc_static_configure(isp_proc_handle_t isp_proc, const esp_isp_dpc_static_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief ISP DPC static calibration configuration
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t threshold; ///< Dead-pixel threshold. For a white image, lower pixels are considered dead pixels; for a black image, higher pixels are considered dead pixels
|
||||
bool enable_output; ///< Whether to output the calibration frame. When false, DPC records detected coordinates without outputting image data.
|
||||
} esp_isp_dpc_calibration_config_t;
|
||||
|
||||
/**
|
||||
* @brief DPC calibration image type
|
||||
*/
|
||||
typedef enum {
|
||||
ESP_ISP_DPC_CALIBRATION_IMAGE_WHITE, ///< Uniform white frame, used to detect dark dead pixels
|
||||
ESP_ISP_DPC_CALIBRATION_IMAGE_BLACK, ///< Uniform black frame, used to detect bright dead pixels
|
||||
} esp_isp_dpc_calibration_image_t;
|
||||
|
||||
/**
|
||||
* @brief ISP DPC calibration coordinate reference
|
||||
*/
|
||||
typedef struct {
|
||||
esp_isp_dpc_pixel_coord_t dead_pixel_coords[ESP_ISP_DPC_MAX_DEAD_PIXELS]; ///< Dead pixel coordinates
|
||||
uint32_t dead_pixel_count; ///< Number of valid coordinates in dead_pixel_coords
|
||||
} esp_isp_dpc_calibration_ref_t;
|
||||
|
||||
/**
|
||||
* @brief Start DPC static calibration onetime
|
||||
*
|
||||
* @note After calling this function, input a uniform frame matching image_type,
|
||||
* then call esp_isp_dpc_calibration_read_result to wait for completion and
|
||||
* read the results.
|
||||
* Static correction is enabled and dynamic correction is disabled while
|
||||
* calibration check mode is enabled.
|
||||
* DPC must not be enabled before calling this function.
|
||||
*
|
||||
* @param[in] isp_proc Processor handle
|
||||
* @param[in] image_type Calibration image type
|
||||
* @param[in] config Calibration configuration
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_STATE Not allowed to be called under current state
|
||||
* - ESP_ERR_INVALID_ARG If the combination of arguments is invalid
|
||||
* - ESP_ERR_NOT_SUPPORTED Not supported
|
||||
*/
|
||||
esp_err_t esp_isp_dpc_static_calibration_start_once(isp_proc_handle_t isp_proc,
|
||||
esp_isp_dpc_calibration_image_t image_type,
|
||||
const esp_isp_dpc_calibration_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Read DPC static calibration results
|
||||
*
|
||||
* @note This function waits for calibration completion, then disables check mode and
|
||||
* DPC before reading the LUT. The result storage is supplied by the caller.
|
||||
*
|
||||
* @param[in] isp_proc Processor handle
|
||||
* @param[in] timeout_ms Maximum wait time in milliseconds; zero polls once
|
||||
* @param[out] result Calibration coordinate reference
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_ARG If the combination of arguments is invalid
|
||||
* - ESP_ERR_INVALID_STATE Calibration is not running
|
||||
* - ESP_ERR_TIMEOUT Calibration did not complete within the timeout
|
||||
*/
|
||||
esp_err_t esp_isp_dpc_calibration_read_result(isp_proc_handle_t isp_proc, uint32_t timeout_ms,
|
||||
esp_isp_dpc_calibration_ref_t *result);
|
||||
|
||||
/**
|
||||
* @brief Merge static calibration coordinate references
|
||||
*
|
||||
* @note This is a software-only utility. It does not access an ISP processor or any
|
||||
* hardware resource. It combines the coordinates from ref_count references, sorts
|
||||
* them by y/x, removes duplicates, and keeps at most ESP_ISP_DPC_MAX_DEAD_PIXELS
|
||||
* coordinates. The output reference must not also appear in refs.
|
||||
*
|
||||
* @param[in] refs Array of pointers to the coordinate references to merge
|
||||
* @param[in] ref_count Number of entries in refs, must be greater than zero
|
||||
* @param[out] merged_ref Merged coordinate reference
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK On success
|
||||
* - ESP_ERR_INVALID_ARG If the combination of arguments is invalid
|
||||
*/
|
||||
esp_err_t esp_isp_dpc_calibration_merge_result(const esp_isp_dpc_calibration_ref_t *const refs[],
|
||||
size_t ref_count,
|
||||
esp_isp_dpc_calibration_ref_t *merged_ref);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -95,6 +95,7 @@ typedef struct isp_processor_t {
|
||||
ISP_ATOMIC_TYPE(isp_fsm_t) lsc_fsm;
|
||||
ISP_ATOMIC_TYPE(isp_fsm_t) sharpen_fsm;
|
||||
ISP_ATOMIC_TYPE(isp_fsm_t) wbg_fsm;
|
||||
ISP_ATOMIC_TYPE(isp_fsm_t) dpc_fsm;
|
||||
esp_isp_evt_cbs_t cbs;
|
||||
void *user_data;
|
||||
|
||||
@@ -112,6 +113,7 @@ typedef struct isp_processor_t {
|
||||
|
||||
struct {
|
||||
uint32_t wbg_update_once_configured: 1;
|
||||
uint32_t dpc_update_once_configured: 1;
|
||||
} sub_module_flags;
|
||||
|
||||
} isp_processor_t;
|
||||
|
||||
@@ -146,6 +146,7 @@ esp_err_t esp_isp_new_processor(const esp_isp_processor_cfg_t *proc_config, isp_
|
||||
atomic_init(&proc->lsc_fsm, ISP_FSM_INIT);
|
||||
atomic_init(&proc->sharpen_fsm, ISP_FSM_INIT);
|
||||
atomic_init(&proc->wbg_fsm, ISP_FSM_INIT);
|
||||
atomic_init(&proc->dpc_fsm, ISP_FSM_INIT);
|
||||
|
||||
//Input & Output color format
|
||||
isp_color_t in_color_format = proc_config->input_data_color_type;
|
||||
|
||||
@@ -288,5 +288,8 @@ esp_err_t esp_isp_dma_process_frame(isp_proc_handle_t proc, void *output_buffer,
|
||||
ESP_LOGE(TAG, "wait output done timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_cache_msync(output_buffer, output_frame_size, ESP_CACHE_MSYNC_FLAG_DIR_M2C),
|
||||
TAG, "sync output buffer failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdatomic.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/isp_core.h"
|
||||
#include "driver/isp_dpc.h"
|
||||
#include "esp_private/isp_private.h"
|
||||
#include "hal/efuse_hal.h"
|
||||
#include "soc/chip_revision.h"
|
||||
|
||||
/*---------------------------------------------------------------
|
||||
DPC (Dead Pixel Correction)
|
||||
---------------------------------------------------------------*/
|
||||
|
||||
static const char *TAG = "ISP_DPC";
|
||||
|
||||
#define DPC_MAX_DEAD_PIXELS ESP_ISP_DPC_MAX_DEAD_PIXELS
|
||||
#define DPC_INVALID_DEAD_PIXEL_COORD UINT32_MAX
|
||||
#define DPC_HW_COORD_X_BITS 11
|
||||
#define DPC_HW_COORD_X_MASK ((1U << DPC_HW_COORD_X_BITS) - 1)
|
||||
|
||||
static int compare_pixel_coords(const void *a, const void *b)
|
||||
{
|
||||
const esp_isp_dpc_pixel_coord_t *coord_a = a;
|
||||
const esp_isp_dpc_pixel_coord_t *coord_b = b;
|
||||
if (coord_a->y < coord_b->y || (coord_a->y == coord_b->y && coord_a->x < coord_b->x)) {
|
||||
return -1;
|
||||
}
|
||||
if (coord_a->y > coord_b->y || (coord_a->y == coord_b->y && coord_a->x > coord_b->x)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static esp_err_t s_dpc_update_shadow(isp_proc_handle_t isp_proc, bool update_once)
|
||||
{
|
||||
bool valid = isp_ll_shadow_update_dpc(isp_proc->hal.hw, update_once);
|
||||
ESP_RETURN_ON_FALSE(valid, ESP_ERR_INVALID_STATE, TAG, "shadow update failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static uint32_t s_dpc_coord_to_hw(esp_isp_dpc_pixel_coord_t coord)
|
||||
{
|
||||
return ((uint32_t)coord.y << DPC_HW_COORD_X_BITS) | coord.x;
|
||||
}
|
||||
|
||||
static esp_isp_dpc_pixel_coord_t s_dpc_coord_from_hw(uint32_t coord)
|
||||
{
|
||||
return (esp_isp_dpc_pixel_coord_t) {
|
||||
.x = coord & DPC_HW_COORD_X_MASK,
|
||||
.y = coord >> DPC_HW_COORD_X_BITS,
|
||||
};
|
||||
}
|
||||
|
||||
static void s_dpc_lut_write(isp_proc_handle_t isp_proc, uint32_t index, esp_isp_dpc_pixel_coord_t coord)
|
||||
{
|
||||
isp_ll_lut_dpc_set_wdata(isp_proc->hal.hw, s_dpc_coord_to_hw(coord));
|
||||
isp_ll_lut_dpc_set_cmd(isp_proc->hal.hw, true, index);
|
||||
}
|
||||
|
||||
static void s_dpc_lut_clear_range(isp_proc_handle_t isp_proc, uint32_t start_index)
|
||||
{
|
||||
for (uint32_t i = start_index; i < DPC_MAX_DEAD_PIXELS; i++) {
|
||||
isp_ll_lut_dpc_set_wdata(isp_proc->hal.hw, DPC_INVALID_DEAD_PIXEL_COORD);
|
||||
isp_ll_lut_dpc_set_cmd(isp_proc->hal.hw, true, i);
|
||||
}
|
||||
}
|
||||
|
||||
static bool s_dpc_take_check_done_event(isp_proc_handle_t isp_proc)
|
||||
{
|
||||
uint32_t events = isp_ll_get_intr_raw(isp_proc->hal.hw);
|
||||
if (events & ISP_LL_EVENT_DPC_CHECK_DONE) {
|
||||
isp_ll_clear_intr(isp_proc->hal.hw, ISP_LL_EVENT_DPC_CHECK_DONE);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static esp_err_t s_dpc_configure_dynamic(isp_proc_handle_t isp_proc, const esp_isp_dpc_dynamic_config_t *config)
|
||||
{
|
||||
if (config->method == ESP_ISP_DPC_DYNAMIC_METHOD_1) {
|
||||
isp_ll_dpc_set_dynamic_correction_method(isp_proc->hal.hw, ISP_LL_DPC_DYNAMIC_CORRECTION_METHOD_SIMPLE);
|
||||
isp_ll_dpc_set_high_thresh(isp_proc->hal.hw, config->method_1.high_threshold);
|
||||
isp_ll_dpc_set_low_thresh(isp_proc->hal.hw, config->method_1.low_threshold);
|
||||
} else if (config->method == ESP_ISP_DPC_DYNAMIC_METHOD_2) {
|
||||
uint32_t upper_ratio_raw = config->method_2.first_stage_upper_ratio.val;
|
||||
uint32_t lower_ratio_raw = config->method_2.first_stage_lower_ratio.val;
|
||||
uint32_t bright_factor_raw = config->method_2.bright_deviation_factor.val;
|
||||
uint32_t dark_factor_raw = config->method_2.dark_deviation_factor.val;
|
||||
ESP_RETURN_ON_FALSE(upper_ratio_raw <= ISP_DPC_RATIO_MAX,
|
||||
ESP_ERR_INVALID_ARG, TAG, "first-stage upper ratio out of range");
|
||||
ESP_RETURN_ON_FALSE(lower_ratio_raw <= ISP_DPC_RATIO_MAX,
|
||||
ESP_ERR_INVALID_ARG, TAG, "first-stage lower ratio out of range");
|
||||
ESP_RETURN_ON_FALSE(bright_factor_raw <= ISP_DPC_DEVIATION_FACTOR_MAX,
|
||||
ESP_ERR_INVALID_ARG, TAG, "bright deviation factor out of range");
|
||||
ESP_RETURN_ON_FALSE(dark_factor_raw <= ISP_DPC_DEVIATION_FACTOR_MAX,
|
||||
ESP_ERR_INVALID_ARG, TAG, "dark deviation factor out of range");
|
||||
ESP_RETURN_ON_FALSE(upper_ratio_raw > lower_ratio_raw,
|
||||
ESP_ERR_INVALID_ARG, TAG, "first-stage upper ratio must exceed lower ratio");
|
||||
|
||||
isp_ll_dpc_set_dynamic_correction_method(isp_proc->hal.hw, ISP_LL_DPC_DYNAMIC_CORRECTION_METHOD_HARD);
|
||||
isp_ll_dpc_set_high_thresh(isp_proc->hal.hw, upper_ratio_raw);
|
||||
isp_ll_dpc_set_low_thresh(isp_proc->hal.hw, lower_ratio_raw);
|
||||
isp_ll_dpc_set_dynamic_correction_method_1_bright_factor(isp_proc->hal.hw, bright_factor_raw);
|
||||
isp_ll_dpc_set_dynamic_correction_method_1_dark_factor(isp_proc->hal.hw, dark_factor_raw);
|
||||
} else {
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid dynamic correction method");
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_isp_dpc_static_calibration_start_once(isp_proc_handle_t isp_proc,
|
||||
esp_isp_dpc_calibration_image_t image_type,
|
||||
const esp_isp_dpc_calibration_config_t *config)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
unsigned chip_version = efuse_hal_chip_revision();
|
||||
if (!ESP_CHIP_REV_ABOVE(chip_version, 300)) {
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "DPC is not supported on ESP32P4 chips prior than v3.0");
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_RETURN_ON_FALSE(isp_proc && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
ESP_RETURN_ON_FALSE(image_type == ESP_ISP_DPC_CALIBRATION_IMAGE_WHITE || image_type == ESP_ISP_DPC_CALIBRATION_IMAGE_BLACK,
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid calibration image type");
|
||||
isp_fsm_t expected_fsm = ISP_FSM_INIT;
|
||||
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&isp_proc->dpc_fsm, &expected_fsm, ISP_FSM_ENABLE),
|
||||
ESP_ERR_INVALID_STATE, TAG, "dpc is enabled already");
|
||||
|
||||
s_dpc_take_check_done_event(isp_proc);
|
||||
|
||||
if (image_type == ESP_ISP_DPC_CALIBRATION_IMAGE_WHITE) {
|
||||
isp_ll_dpc_set_high_thresh(isp_proc->hal.hw, config->threshold);
|
||||
} else {
|
||||
isp_ll_dpc_set_low_thresh(isp_proc->hal.hw, config->threshold);
|
||||
}
|
||||
|
||||
// Static calibration requires static correction enabled and dynamic correction disabled.
|
||||
isp_ll_dpc_enable_dynamic_correction(isp_proc->hal.hw, false);
|
||||
isp_ll_dpc_enable_static_correction(isp_proc->hal.hw, true);
|
||||
isp_ll_dpc_enable_check_mode_data(isp_proc->hal.hw, config->enable_output);
|
||||
isp_ll_dpc_set_input_color(isp_proc->hal.hw, image_type == ESP_ISP_DPC_CALIBRATION_IMAGE_BLACK ?
|
||||
ISP_LL_DPC_INPUT_COLOR_BLACK : ISP_LL_DPC_INPUT_COLOR_WHITE);
|
||||
isp_ll_dpc_enable_check_mode(isp_proc->hal.hw, true);
|
||||
isp_ll_dpc_enable(isp_proc->hal.hw, true);
|
||||
esp_err_t ret = s_dpc_update_shadow(isp_proc, true);
|
||||
if (ret != ESP_OK) {
|
||||
atomic_store(&isp_proc->dpc_fsm, ISP_FSM_INIT);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "DPC calibration started for %s frame", image_type == ESP_ISP_DPC_CALIBRATION_IMAGE_WHITE ? "white" : "black");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t s_dpc_wait_calibration_done(isp_proc_handle_t isp_proc, uint32_t timeout_ms)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(isp_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
ESP_RETURN_ON_FALSE(atomic_load(&isp_proc->dpc_fsm) == ISP_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "dpc calibration isn't running");
|
||||
|
||||
TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
|
||||
if (timeout_ms > 0 && timeout_ticks == 0) {
|
||||
timeout_ticks = 1;
|
||||
}
|
||||
TickType_t start_ticks = xTaskGetTickCount();
|
||||
do {
|
||||
if (s_dpc_take_check_done_event(isp_proc)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (timeout_ms == 0 || (xTaskGetTickCount() - start_ticks) >= timeout_ticks) {
|
||||
break;
|
||||
}
|
||||
vTaskDelay(1);
|
||||
} while (true);
|
||||
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
esp_err_t esp_isp_dpc_calibration_read_result(isp_proc_handle_t isp_proc, uint32_t timeout_ms,
|
||||
esp_isp_dpc_calibration_ref_t *result)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(isp_proc && result, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
ESP_RETURN_ON_ERROR(s_dpc_wait_calibration_done(isp_proc, timeout_ms), TAG, "DPC calibration did not complete");
|
||||
isp_fsm_t expected_fsm = ISP_FSM_ENABLE;
|
||||
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&isp_proc->dpc_fsm, &expected_fsm, ISP_FSM_INIT),
|
||||
ESP_ERR_INVALID_STATE, TAG, "dpc calibration isn't running");
|
||||
|
||||
// Disable check mode before reading LUT.
|
||||
isp_ll_dpc_enable_check_mode(isp_proc->hal.hw, false);
|
||||
isp_ll_dpc_enable(isp_proc->hal.hw, false);
|
||||
ESP_RETURN_ON_ERROR(s_dpc_update_shadow(isp_proc, true), TAG, "failed to update DPC shadow registers");
|
||||
|
||||
result->dead_pixel_count = 0;
|
||||
|
||||
// Read dead pixel count
|
||||
uint32_t deadpix_cnt = isp_ll_dpc_get_deadpix_cnt(isp_proc->hal.hw);
|
||||
if (deadpix_cnt > DPC_MAX_DEAD_PIXELS) {
|
||||
ESP_LOGW(TAG, "dead pixel count exceeds LUT size (%lu), limiting to %d", (unsigned long)deadpix_cnt, DPC_MAX_DEAD_PIXELS);
|
||||
deadpix_cnt = DPC_MAX_DEAD_PIXELS;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < deadpix_cnt; i++) {
|
||||
isp_ll_lut_dpc_set_cmd(isp_proc->hal.hw, false, i);
|
||||
result->dead_pixel_coords[i] = s_dpc_coord_from_hw(isp_ll_lut_dpc_get_rdata(isp_proc->hal.hw));
|
||||
}
|
||||
result->dead_pixel_count = deadpix_cnt;
|
||||
|
||||
ESP_LOGD(TAG, "Read %lu dead pixels", (unsigned long)deadpix_cnt);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_isp_dpc_calibration_merge_result(const esp_isp_dpc_calibration_ref_t *const refs[],
|
||||
size_t ref_count,
|
||||
esp_isp_dpc_calibration_ref_t *merged_ref)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(refs && ref_count > 0 && merged_ref,
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
|
||||
for (size_t i = 0; i < ref_count; i++) {
|
||||
const esp_isp_dpc_calibration_ref_t *ref = refs[i];
|
||||
ESP_RETURN_ON_FALSE(ref, ESP_ERR_INVALID_ARG, TAG, "input reference is NULL");
|
||||
ESP_RETURN_ON_FALSE(ref != merged_ref, ESP_ERR_INVALID_ARG, TAG, "output reference must not be an input");
|
||||
ESP_RETURN_ON_FALSE(ref->dead_pixel_count <= DPC_MAX_DEAD_PIXELS,
|
||||
ESP_ERR_INVALID_ARG, TAG, "dead pixel count exceeds maximum");
|
||||
}
|
||||
|
||||
merged_ref->dead_pixel_count = 0;
|
||||
bool truncated = false;
|
||||
for (size_t i = 0; i < ref_count; i++) {
|
||||
const esp_isp_dpc_calibration_ref_t *ref = refs[i];
|
||||
for (uint32_t j = 0; j < ref->dead_pixel_count; j++) {
|
||||
const esp_isp_dpc_pixel_coord_t coord = ref->dead_pixel_coords[j];
|
||||
uint32_t insert_index = 0;
|
||||
while (insert_index < merged_ref->dead_pixel_count &&
|
||||
compare_pixel_coords(&merged_ref->dead_pixel_coords[insert_index], &coord) < 0) {
|
||||
insert_index++;
|
||||
}
|
||||
if (insert_index < merged_ref->dead_pixel_count &&
|
||||
compare_pixel_coords(&merged_ref->dead_pixel_coords[insert_index], &coord) == 0) {
|
||||
continue;
|
||||
}
|
||||
if (insert_index == DPC_MAX_DEAD_PIXELS) {
|
||||
truncated = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t new_count = merged_ref->dead_pixel_count;
|
||||
if (new_count < DPC_MAX_DEAD_PIXELS) {
|
||||
new_count++;
|
||||
} else {
|
||||
truncated = true;
|
||||
}
|
||||
for (uint32_t k = new_count - 1; k > insert_index; k--) {
|
||||
merged_ref->dead_pixel_coords[k] = merged_ref->dead_pixel_coords[k - 1];
|
||||
}
|
||||
merged_ref->dead_pixel_coords[insert_index] = coord;
|
||||
merged_ref->dead_pixel_count = new_count;
|
||||
}
|
||||
}
|
||||
|
||||
if (truncated) {
|
||||
ESP_LOGW(TAG, "Dead pixel count exceeds maximum, limiting to %d", DPC_MAX_DEAD_PIXELS);
|
||||
}
|
||||
ESP_LOGD(TAG, "Merged %zu references into %lu unique dead pixels",
|
||||
ref_count, (unsigned long)merged_ref->dead_pixel_count);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t s_dpc_load_static_lut(isp_proc_handle_t isp_proc,
|
||||
const esp_isp_dpc_pixel_coord_t *dead_pixel_coords,
|
||||
uint32_t dead_pixel_count)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
unsigned chip_version = efuse_hal_chip_revision();
|
||||
if (!ESP_CHIP_REV_ABOVE(chip_version, 300)) {
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "DPC is not supported on ESP32P4 chips prior than v3.0");
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_RETURN_ON_FALSE(isp_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null processor");
|
||||
ESP_RETURN_ON_FALSE(atomic_load(&isp_proc->dpc_fsm) == ISP_FSM_INIT,
|
||||
ESP_ERR_INVALID_STATE, TAG, "dpc is enabled already");
|
||||
ESP_RETURN_ON_FALSE(dead_pixel_count <= DPC_MAX_DEAD_PIXELS,
|
||||
ESP_ERR_INVALID_ARG, TAG, "dead pixel count exceeds maximum");
|
||||
ESP_RETURN_ON_FALSE(dead_pixel_count == 0 || dead_pixel_coords,
|
||||
ESP_ERR_INVALID_ARG, TAG, "dead pixel coordinates are NULL");
|
||||
|
||||
for (uint32_t i = 0; i < dead_pixel_count; i++) {
|
||||
const esp_isp_dpc_pixel_coord_t *coord = &dead_pixel_coords[i];
|
||||
ESP_RETURN_ON_FALSE(coord->x < isp_proc->h_res && coord->y < isp_proc->v_res,
|
||||
ESP_ERR_INVALID_ARG, TAG, "dead pixel coordinate is outside the input frame");
|
||||
ESP_RETURN_ON_FALSE(i == 0 || compare_pixel_coords(coord, &dead_pixel_coords[i - 1]) > 0,
|
||||
ESP_ERR_INVALID_ARG, TAG, "dead pixel coordinates must be sorted by y/x and unique");
|
||||
s_dpc_lut_write(isp_proc, i, *coord);
|
||||
}
|
||||
s_dpc_lut_clear_range(isp_proc, dead_pixel_count);
|
||||
ESP_LOGD(TAG, "Loaded %lu dead pixels into static DPC LUT", (unsigned long)dead_pixel_count);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_isp_dpc_static_configure(isp_proc_handle_t isp_proc, const esp_isp_dpc_static_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(isp_proc && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
ESP_RETURN_ON_FALSE(atomic_load(&isp_proc->dpc_fsm) == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "dpc is enabled already");
|
||||
|
||||
ESP_RETURN_ON_ERROR(s_dpc_load_static_lut(isp_proc, config->dead_pixel_coords, config->dead_pixel_count),
|
||||
TAG, "failed to load static DPC LUT");
|
||||
isp_ll_dpc_enable_static_correction(isp_proc->hal.hw, true);
|
||||
|
||||
ESP_LOGD(TAG, "Static DPC configured");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_isp_dpc_dynamic_configure(isp_proc_handle_t isp_proc, const esp_isp_dpc_dynamic_config_t *config)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32P4
|
||||
unsigned chip_version = efuse_hal_chip_revision();
|
||||
if (!ESP_CHIP_REV_ABOVE(chip_version, 300)) {
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "DPC is not supported on ESP32P4 chips prior than v3.0");
|
||||
}
|
||||
#endif
|
||||
|
||||
ESP_RETURN_ON_FALSE(isp_proc && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
ESP_RETURN_ON_FALSE(atomic_load(&isp_proc->dpc_fsm) == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "dpc is enabled already");
|
||||
|
||||
ESP_RETURN_ON_ERROR(s_dpc_configure_dynamic(isp_proc, config), TAG, "failed to configure dynamic DPC");
|
||||
isp_ll_dpc_enable_dynamic_correction(isp_proc->hal.hw, true);
|
||||
|
||||
ESP_LOGD(TAG, "Dynamic DPC configured");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_isp_dpc_configure(isp_proc_handle_t isp_proc, const esp_isp_dpc_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(isp_proc && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
ESP_RETURN_ON_FALSE(atomic_load(&isp_proc->dpc_fsm) == ISP_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "dpc is enabled already");
|
||||
|
||||
isp_ll_dpc_set_clk_ctrl_mode(isp_proc->hal.hw, ISP_LL_PIPELINE_CLK_CTRL_AUTO);
|
||||
isp_ll_dpc_enable_check_mode(isp_proc->hal.hw, false);
|
||||
isp_ll_dpc_enable_check_mode_data(isp_proc->hal.hw, false);
|
||||
ESP_RETURN_ON_ERROR(s_dpc_update_shadow(isp_proc, config->flags.update_once_configured), TAG,
|
||||
"failed to update DPC shadow registers");
|
||||
isp_proc->sub_module_flags.dpc_update_once_configured = config->flags.update_once_configured;
|
||||
|
||||
ESP_LOGD(TAG, "DPC common configuration applied");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_isp_dpc_enable(isp_proc_handle_t isp_proc)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(isp_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
isp_fsm_t expected_fsm = ISP_FSM_INIT;
|
||||
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&isp_proc->dpc_fsm, &expected_fsm, ISP_FSM_ENABLE), ESP_ERR_INVALID_STATE, TAG, "dpc is enabled already");
|
||||
|
||||
isp_ll_dpc_enable(isp_proc->hal.hw, true);
|
||||
esp_err_t ret = s_dpc_update_shadow(isp_proc, isp_proc->sub_module_flags.dpc_update_once_configured);
|
||||
if (ret != ESP_OK) {
|
||||
atomic_store(&isp_proc->dpc_fsm, ISP_FSM_INIT);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "DPC enabled");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_isp_dpc_disable(isp_proc_handle_t isp_proc)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(isp_proc, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null pointer");
|
||||
isp_fsm_t expected_fsm = ISP_FSM_ENABLE;
|
||||
ESP_RETURN_ON_FALSE(atomic_compare_exchange_strong(&isp_proc->dpc_fsm, &expected_fsm, ISP_FSM_INIT), ESP_ERR_INVALID_STATE, TAG, "dpc isn't enabled yet");
|
||||
|
||||
isp_ll_dpc_enable(isp_proc->hal.hw, false);
|
||||
esp_err_t ret = s_dpc_update_shadow(isp_proc, isp_proc->sub_module_flags.dpc_update_once_configured);
|
||||
if (ret != ESP_OK) {
|
||||
atomic_store(&isp_proc->dpc_fsm, ISP_FSM_ENABLE);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "DPC disabled");
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_isp_driver.c")
|
||||
"test_isp_driver.c"
|
||||
"test_isp_dpc.c")
|
||||
|
||||
if(CONFIG_SOC_ISP_SHARE_CSI_BRG)
|
||||
list(APPEND srcs "test_isp_csi.c")
|
||||
@@ -8,6 +9,7 @@ endif()
|
||||
set(priv_requires
|
||||
unity
|
||||
esp_driver_isp
|
||||
esp_mm
|
||||
esp_psram
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,506 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "unity.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "driver/isp_core.h"
|
||||
#include "driver/isp_dma.h"
|
||||
#include "driver/isp_color.h"
|
||||
#include "driver/isp_dpc.h"
|
||||
#include "hal/color_types.h"
|
||||
|
||||
#define TEST_DPC_IMAGE_WIDTH 240
|
||||
#define TEST_DPC_IMAGE_HEIGHT 240
|
||||
#define TEST_DPC_IMAGE_SIZE (TEST_DPC_IMAGE_WIDTH * TEST_DPC_IMAGE_HEIGHT)
|
||||
#define TEST_DPC_RGB888_SIZE (TEST_DPC_IMAGE_SIZE * 3)
|
||||
#define TEST_DPC_BASE_PIXEL 0x80
|
||||
#define TEST_DPC_DARK_PIXEL 0x00
|
||||
#define TEST_DPC_DIM_PIXEL 0x30
|
||||
#define TEST_DPC_LIGHT_PIXEL 0xd0
|
||||
#define TEST_DPC_BRIGHT_PIXEL 0xff
|
||||
#define TEST_DPC_VERIFY_WINDOW_RADIUS 2
|
||||
#define TEST_DPC_VERIFY_MIN_CORRECTED_PIXELS_PERCENT 70
|
||||
|
||||
#define TEST_DPC_DUMP_PPM_TO_CONSOLE 0
|
||||
|
||||
typedef struct {
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint8_t value;
|
||||
} test_dpc_bad_pixel_t;
|
||||
|
||||
static const test_dpc_bad_pixel_t s_test_dpc_bad_pixels[] = {
|
||||
// isolated dark/bright pixels on BGGR B sites (even row, even column)
|
||||
{ 24, 24, TEST_DPC_DARK_PIXEL},
|
||||
{ 56, 24, TEST_DPC_BRIGHT_PIXEL},
|
||||
{ 96, 32, TEST_DPC_DIM_PIXEL},
|
||||
{136, 32, TEST_DPC_LIGHT_PIXEL},
|
||||
|
||||
// isolated pixels on BGGR G sites (even row, odd column)
|
||||
{ 25, 72, TEST_DPC_DARK_PIXEL},
|
||||
{ 57, 72, TEST_DPC_BRIGHT_PIXEL},
|
||||
{ 97, 80, TEST_DPC_DIM_PIXEL},
|
||||
{137, 80, TEST_DPC_LIGHT_PIXEL},
|
||||
|
||||
// isolated pixels on BGGR G sites (odd row, even column)
|
||||
{ 24, 121, TEST_DPC_BRIGHT_PIXEL},
|
||||
{ 64, 121, TEST_DPC_DARK_PIXEL},
|
||||
{104, 129, TEST_DPC_LIGHT_PIXEL},
|
||||
{144, 129, TEST_DPC_DIM_PIXEL},
|
||||
|
||||
// isolated pixels on BGGR R sites (odd row, odd column)
|
||||
{ 25, 177, TEST_DPC_DARK_PIXEL},
|
||||
{ 57, 177, TEST_DPC_BRIGHT_PIXEL},
|
||||
{ 97, 185, TEST_DPC_DIM_PIXEL},
|
||||
{137, 185, TEST_DPC_LIGHT_PIXEL},
|
||||
|
||||
// extra isolated pixels spread across the frame
|
||||
{184, 48, TEST_DPC_DARK_PIXEL},
|
||||
{217, 49, TEST_DPC_BRIGHT_PIXEL},
|
||||
{184, 160, TEST_DPC_LIGHT_PIXEL},
|
||||
{217, 217, TEST_DPC_DIM_PIXEL},
|
||||
};
|
||||
|
||||
static void test_isp_dpc_skip_if_unsupported(void)
|
||||
{
|
||||
#if CONFIG_IDF_TARGET_ESP32P4 && CONFIG_ESP32P4_SELECTS_REV_LESS_V3
|
||||
TEST_IGNORE_MESSAGE("DPC is not supported on ESP32P4 chips prior than v3.0");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void test_isp_dpc_generate_bad_pixel_image(uint8_t *buf)
|
||||
{
|
||||
memset(buf, TEST_DPC_BASE_PIXEL, TEST_DPC_IMAGE_SIZE);
|
||||
for (size_t i = 0; i < sizeof(s_test_dpc_bad_pixels) / sizeof(s_test_dpc_bad_pixels[0]); i++) {
|
||||
const test_dpc_bad_pixel_t *bad_pixel = &s_test_dpc_bad_pixels[i];
|
||||
buf[bad_pixel->y * TEST_DPC_IMAGE_WIDTH + bad_pixel->x] = bad_pixel->value;
|
||||
}
|
||||
}
|
||||
|
||||
static void test_isp_dpc_generate_calibration_image(uint8_t *buf, bool white_image)
|
||||
{
|
||||
memset(buf, white_image ? 0xff : 0x00, TEST_DPC_IMAGE_SIZE);
|
||||
for (size_t i = 0; i < sizeof(s_test_dpc_bad_pixels) / sizeof(s_test_dpc_bad_pixels[0]); i++) {
|
||||
const test_dpc_bad_pixel_t *bad_pixel = &s_test_dpc_bad_pixels[i];
|
||||
bool dark_pixel = bad_pixel->value < TEST_DPC_BASE_PIXEL;
|
||||
if ((white_image && dark_pixel) || (!white_image && !dark_pixel)) {
|
||||
buf[bad_pixel->y * TEST_DPC_IMAGE_WIDTH + bad_pixel->x] = white_image ? 0x00 : 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if TEST_DPC_DUMP_PPM_TO_CONSOLE
|
||||
static void test_isp_dpc_dump_bgr888_ppm_to_console(const char *name, const uint8_t *buf)
|
||||
{
|
||||
printf("\nBEGIN_PPM:%s\n", name);
|
||||
printf("P3\n%d %d\n255\n", TEST_DPC_IMAGE_WIDTH, TEST_DPC_IMAGE_HEIGHT);
|
||||
for (size_t i = 0; i < TEST_DPC_IMAGE_SIZE; i++) {
|
||||
const uint8_t *bgr = &buf[i * 3];
|
||||
printf("%u %u %u%c", bgr[2], bgr[1], bgr[0], (i + 1) % TEST_DPC_IMAGE_WIDTH == 0 ? '\n' : ' ');
|
||||
}
|
||||
printf("END_PPM:%s\n\n", name);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void test_isp_dpc_write_bgr888_ppm(const char *path, const uint8_t *buf)
|
||||
{
|
||||
FILE *fp = fopen(path, "wb");
|
||||
if (!fp) {
|
||||
#if TEST_DPC_DUMP_PPM_TO_CONSOLE
|
||||
printf("Failed to open %s for writing, dump PPM to console instead\n", path);
|
||||
test_isp_dpc_dump_bgr888_ppm_to_console(path, buf);
|
||||
#else
|
||||
printf("Failed to open %s for writing, skip PPM dump\n", path);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(fp, "P6\n%d %d\n255\n", TEST_DPC_IMAGE_WIDTH, TEST_DPC_IMAGE_HEIGHT);
|
||||
for (size_t i = 0; i < TEST_DPC_IMAGE_SIZE; i++) {
|
||||
const uint8_t *bgr = &buf[i * 3];
|
||||
uint8_t rgb[3] = {bgr[2], bgr[1], bgr[0]};
|
||||
TEST_ASSERT_EQUAL_size_t(sizeof(rgb), fwrite(rgb, 1, sizeof(rgb), fp));
|
||||
}
|
||||
TEST_ASSERT_EQUAL(0, fclose(fp));
|
||||
}
|
||||
|
||||
static void *test_isp_dpc_alloc_dma_buffer(size_t size)
|
||||
{
|
||||
void *buf = heap_caps_aligned_calloc(64, 1, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_8BIT);
|
||||
TEST_ASSERT_NOT_NULL(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void test_isp_dpc_configure_neutral_color(isp_proc_handle_t isp_proc)
|
||||
{
|
||||
esp_isp_color_config_t color_cfg = {
|
||||
.color_contrast = { .integer = 1, .decimal = 0 },
|
||||
.color_saturation = { .integer = 1, .decimal = 0 },
|
||||
.color_hue = 0,
|
||||
.color_brightness = 0,
|
||||
};
|
||||
|
||||
TEST_ESP_OK(esp_isp_color_configure(isp_proc, &color_cfg));
|
||||
TEST_ESP_OK(esp_isp_color_enable(isp_proc));
|
||||
}
|
||||
|
||||
static void test_isp_dpc_process_frame(isp_proc_handle_t isp_proc, uint8_t *output, uint8_t *input)
|
||||
{
|
||||
TEST_ESP_OK(esp_isp_dma_process_frame(isp_proc, output, input, 1000));
|
||||
}
|
||||
|
||||
static uint32_t test_isp_dpc_local_frame_error(const uint8_t *actual, const uint8_t *reference, uint32_t center_x, uint32_t center_y)
|
||||
{
|
||||
uint32_t error = 0;
|
||||
int32_t min_y = (int32_t)center_y - TEST_DPC_VERIFY_WINDOW_RADIUS;
|
||||
int32_t max_y = (int32_t)center_y + TEST_DPC_VERIFY_WINDOW_RADIUS;
|
||||
int32_t min_x = (int32_t)center_x - TEST_DPC_VERIFY_WINDOW_RADIUS;
|
||||
int32_t max_x = (int32_t)center_x + TEST_DPC_VERIFY_WINDOW_RADIUS;
|
||||
|
||||
for (int32_t y = min_y; y <= max_y; y++) {
|
||||
if (y < 0 || y >= TEST_DPC_IMAGE_HEIGHT) {
|
||||
continue;
|
||||
}
|
||||
for (int32_t x = min_x; x <= max_x; x++) {
|
||||
if (x < 0 || x >= TEST_DPC_IMAGE_WIDTH) {
|
||||
continue;
|
||||
}
|
||||
size_t pixel_offset = ((uint32_t)y * TEST_DPC_IMAGE_WIDTH + (uint32_t)x) * 3;
|
||||
error += abs((int)actual[pixel_offset] - reference[pixel_offset]);
|
||||
error += abs((int)actual[pixel_offset + 1] - reference[pixel_offset + 1]);
|
||||
error += abs((int)actual[pixel_offset + 2] - reference[pixel_offset + 2]);
|
||||
}
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
static void test_isp_dpc_verify_result(const char *name, const uint8_t *reference, const uint8_t *original,
|
||||
const uint8_t *corrected, bool allow_unimproved_pixels)
|
||||
{
|
||||
uint32_t min_improvement = 100;
|
||||
uint32_t max_improvement = 0;
|
||||
uint32_t total_improvement = 0;
|
||||
uint32_t verified_count = 0;
|
||||
uint32_t corrected_count = 0;
|
||||
|
||||
for (size_t i = 0; i < sizeof(s_test_dpc_bad_pixels) / sizeof(s_test_dpc_bad_pixels[0]); i++) {
|
||||
const test_dpc_bad_pixel_t *bad_pixel = &s_test_dpc_bad_pixels[i];
|
||||
uint32_t original_delta = test_isp_dpc_local_frame_error(original, reference, bad_pixel->x, bad_pixel->y);
|
||||
uint32_t corrected_delta = test_isp_dpc_local_frame_error(corrected, reference, bad_pixel->x, bad_pixel->y);
|
||||
uint32_t improvement = 100;
|
||||
if (original_delta) {
|
||||
improvement = corrected_delta >= original_delta ? 0 : (original_delta - corrected_delta) * 100 / original_delta;
|
||||
}
|
||||
bool improved = original_delta > 0 && corrected_delta < original_delta;
|
||||
bool pass = original_delta > 0 && corrected_delta <= original_delta && (allow_unimproved_pixels || improved);
|
||||
|
||||
printf("%s verify %s: pixel[%u] (%" PRIu32 ", %" PRIu32 ") raw=0x%02x optimized %" PRIu32 "%%, original_delta=%" PRIu32 ", corrected_delta=%" PRIu32 "\n",
|
||||
name, pass ? "pass" : "failed", (unsigned)i, bad_pixel->x, bad_pixel->y, bad_pixel->value,
|
||||
improvement, original_delta, corrected_delta);
|
||||
|
||||
TEST_ASSERT_TRUE(pass);
|
||||
if (improved) {
|
||||
corrected_count++;
|
||||
}
|
||||
|
||||
if (improvement < min_improvement) {
|
||||
min_improvement = improvement;
|
||||
}
|
||||
if (improvement > max_improvement) {
|
||||
max_improvement = improvement;
|
||||
}
|
||||
total_improvement += improvement;
|
||||
verified_count++;
|
||||
}
|
||||
|
||||
uint32_t corrected_percent = verified_count ? corrected_count * 100 / verified_count : 0;
|
||||
if (allow_unimproved_pixels && corrected_percent < TEST_DPC_VERIFY_MIN_CORRECTED_PIXELS_PERCENT) {
|
||||
printf("%s verify failed: only %" PRIu32 "%% of bad pixels were corrected, minimum is %d%%\n",
|
||||
name, corrected_percent, TEST_DPC_VERIFY_MIN_CORRECTED_PIXELS_PERCENT);
|
||||
TEST_FAIL();
|
||||
}
|
||||
|
||||
printf("\n%s verify pass: %u bad pixels, corrected=%" PRIu32 "%%, optimized avg=%" PRIu32 "%% min=%" PRIu32 "%% max=%" PRIu32 "%%\n\n",
|
||||
name, (unsigned)verified_count, corrected_percent, total_improvement / verified_count, min_improvement, max_improvement);
|
||||
}
|
||||
|
||||
static void test_isp_dpc_dump_with_config(const char *name, const esp_isp_dpc_dynamic_config_t *dpc_config,
|
||||
bool allow_unimproved_pixels)
|
||||
{
|
||||
test_isp_dpc_skip_if_unsupported();
|
||||
|
||||
isp_proc_handle_t isp_proc = NULL;
|
||||
uint8_t *input = test_isp_dpc_alloc_dma_buffer(TEST_DPC_IMAGE_SIZE);
|
||||
uint8_t *reference = test_isp_dpc_alloc_dma_buffer(TEST_DPC_RGB888_SIZE);
|
||||
uint8_t *original = test_isp_dpc_alloc_dma_buffer(TEST_DPC_RGB888_SIZE);
|
||||
uint8_t *output = test_isp_dpc_alloc_dma_buffer(TEST_DPC_RGB888_SIZE);
|
||||
char original_path[64] = {};
|
||||
char corrected_path[64] = {};
|
||||
|
||||
memset(input, TEST_DPC_BASE_PIXEL, TEST_DPC_IMAGE_SIZE);
|
||||
snprintf(original_path, sizeof(original_path), "dpc_%s_original.ppm", name);
|
||||
snprintf(corrected_path, sizeof(corrected_path), "dpc_%s_corrected.ppm", name);
|
||||
|
||||
esp_isp_processor_cfg_t isp_config = {
|
||||
.clk_hz = 240 * 1000 * 1000,
|
||||
.input_data_source = ISP_INPUT_DATA_SOURCE_DWGDMA,
|
||||
.input_data_color_type = ISP_COLOR_RAW8,
|
||||
.output_data_color_type = ISP_COLOR_RGB888,
|
||||
.bayer_order = COLOR_RAW_ELEMENT_ORDER_BGGR,
|
||||
.has_line_start_packet = false,
|
||||
.has_line_end_packet = false,
|
||||
.h_res = TEST_DPC_IMAGE_WIDTH,
|
||||
.v_res = TEST_DPC_IMAGE_HEIGHT,
|
||||
.dma_burst_size = 8,
|
||||
};
|
||||
TEST_ESP_OK(esp_isp_new_processor(&isp_config, &isp_proc));
|
||||
TEST_ESP_OK(esp_isp_enable(isp_proc));
|
||||
test_isp_dpc_configure_neutral_color(isp_proc);
|
||||
|
||||
test_isp_dpc_process_frame(isp_proc, reference, input);
|
||||
|
||||
test_isp_dpc_generate_bad_pixel_image(input);
|
||||
test_isp_dpc_process_frame(isp_proc, original, input);
|
||||
|
||||
test_isp_dpc_write_bgr888_ppm(original_path, original);
|
||||
|
||||
TEST_ESP_OK(esp_isp_dpc_dynamic_configure(isp_proc, dpc_config));
|
||||
esp_isp_dpc_config_t common_config = {
|
||||
.flags.update_once_configured = true,
|
||||
};
|
||||
TEST_ESP_OK(esp_isp_dpc_configure(isp_proc, &common_config));
|
||||
TEST_ESP_OK(esp_isp_dpc_enable(isp_proc));
|
||||
|
||||
test_isp_dpc_process_frame(isp_proc, output, input);
|
||||
|
||||
test_isp_dpc_write_bgr888_ppm(corrected_path, output);
|
||||
test_isp_dpc_verify_result(name, reference, original, output, allow_unimproved_pixels);
|
||||
|
||||
TEST_ESP_OK(esp_isp_dpc_disable(isp_proc));
|
||||
TEST_ESP_OK(esp_isp_color_disable(isp_proc));
|
||||
TEST_ESP_OK(esp_isp_disable(isp_proc));
|
||||
TEST_ESP_OK(esp_isp_del_processor(isp_proc));
|
||||
heap_caps_free(output);
|
||||
heap_caps_free(original);
|
||||
heap_caps_free(reference);
|
||||
heap_caps_free(input);
|
||||
}
|
||||
|
||||
static void test_isp_dpc_dump_dynamic1(void)
|
||||
{
|
||||
esp_isp_dpc_dynamic_config_t dpc_config = {
|
||||
.method = ESP_ISP_DPC_DYNAMIC_METHOD_1,
|
||||
.method_1 = {
|
||||
.high_threshold = 8,
|
||||
.low_threshold = 8,
|
||||
},
|
||||
};
|
||||
|
||||
test_isp_dpc_dump_with_config("dynamic1", &dpc_config, false);
|
||||
}
|
||||
|
||||
static void test_isp_dpc_dump_static(const char *name, bool enable_dynamic)
|
||||
{
|
||||
test_isp_dpc_skip_if_unsupported();
|
||||
|
||||
isp_proc_handle_t isp_proc = NULL;
|
||||
static esp_isp_dpc_calibration_ref_t black_ref;
|
||||
static esp_isp_dpc_calibration_ref_t white_ref;
|
||||
static esp_isp_dpc_calibration_ref_t merged_ref;
|
||||
uint8_t *input = test_isp_dpc_alloc_dma_buffer(TEST_DPC_IMAGE_SIZE);
|
||||
uint8_t *reference = test_isp_dpc_alloc_dma_buffer(TEST_DPC_RGB888_SIZE);
|
||||
uint8_t *original = test_isp_dpc_alloc_dma_buffer(TEST_DPC_RGB888_SIZE);
|
||||
uint8_t *output = test_isp_dpc_alloc_dma_buffer(TEST_DPC_RGB888_SIZE);
|
||||
|
||||
memset(input, TEST_DPC_BASE_PIXEL, TEST_DPC_IMAGE_SIZE);
|
||||
|
||||
esp_isp_processor_cfg_t isp_config = {
|
||||
.clk_hz = 240 * 1000 * 1000,
|
||||
.input_data_source = ISP_INPUT_DATA_SOURCE_DWGDMA,
|
||||
.input_data_color_type = ISP_COLOR_RAW8,
|
||||
.output_data_color_type = ISP_COLOR_RGB888,
|
||||
.bayer_order = COLOR_RAW_ELEMENT_ORDER_BGGR,
|
||||
.has_line_start_packet = false,
|
||||
.has_line_end_packet = false,
|
||||
.h_res = TEST_DPC_IMAGE_WIDTH,
|
||||
.v_res = TEST_DPC_IMAGE_HEIGHT,
|
||||
.dma_burst_size = 8,
|
||||
};
|
||||
TEST_ESP_OK(esp_isp_new_processor(&isp_config, &isp_proc));
|
||||
TEST_ESP_OK(esp_isp_enable(isp_proc));
|
||||
test_isp_dpc_configure_neutral_color(isp_proc);
|
||||
|
||||
test_isp_dpc_process_frame(isp_proc, reference, input);
|
||||
|
||||
test_isp_dpc_generate_bad_pixel_image(input);
|
||||
test_isp_dpc_process_frame(isp_proc, original, input);
|
||||
test_isp_dpc_write_bgr888_ppm("dpc_static_original.ppm", original);
|
||||
|
||||
esp_isp_dpc_calibration_config_t white_calibration_config = {
|
||||
.threshold = 0xf0,
|
||||
.enable_output = true,
|
||||
};
|
||||
esp_isp_dpc_calibration_config_t black_calibration_config = {
|
||||
.threshold = 0x0a,
|
||||
.enable_output = true,
|
||||
};
|
||||
|
||||
// Use a white frame to calibrate dark pixels.
|
||||
test_isp_dpc_generate_calibration_image(input, true);
|
||||
TEST_ESP_OK(esp_isp_dpc_static_calibration_start_once(isp_proc, ESP_ISP_DPC_CALIBRATION_IMAGE_WHITE, &white_calibration_config));
|
||||
test_isp_dpc_process_frame(isp_proc, output, input);
|
||||
TEST_ESP_OK(esp_isp_dpc_calibration_read_result(isp_proc, 1000, &white_ref));
|
||||
|
||||
// Use a black frame to calibrate bright pixels.
|
||||
test_isp_dpc_generate_calibration_image(input, false);
|
||||
TEST_ESP_OK(esp_isp_dpc_static_calibration_start_once(isp_proc, ESP_ISP_DPC_CALIBRATION_IMAGE_BLACK, &black_calibration_config));
|
||||
test_isp_dpc_process_frame(isp_proc, output, input);
|
||||
TEST_ESP_OK(esp_isp_dpc_calibration_read_result(isp_proc, 1000, &black_ref));
|
||||
|
||||
// Merge the calibration references and configure the resulting coordinate list.
|
||||
const esp_isp_dpc_calibration_ref_t *calibration_refs[] = {
|
||||
&black_ref,
|
||||
&white_ref,
|
||||
};
|
||||
TEST_ESP_OK(esp_isp_dpc_calibration_merge_result(calibration_refs,
|
||||
sizeof(calibration_refs) / sizeof(calibration_refs[0]),
|
||||
&merged_ref));
|
||||
TEST_ASSERT_GREATER_THAN(0, merged_ref.dead_pixel_count);
|
||||
|
||||
esp_isp_dpc_static_config_t static_config = {
|
||||
.dead_pixel_coords = merged_ref.dead_pixel_coords,
|
||||
.dead_pixel_count = merged_ref.dead_pixel_count,
|
||||
};
|
||||
TEST_ESP_OK(esp_isp_dpc_static_configure(isp_proc, &static_config));
|
||||
|
||||
if (enable_dynamic) {
|
||||
esp_isp_dpc_dynamic_config_t dynamic_config = {
|
||||
.method = ESP_ISP_DPC_DYNAMIC_METHOD_1,
|
||||
.method_1 = {
|
||||
.high_threshold = 8,
|
||||
.low_threshold = 8,
|
||||
},
|
||||
};
|
||||
TEST_ESP_OK(esp_isp_dpc_dynamic_configure(isp_proc, &dynamic_config));
|
||||
}
|
||||
esp_isp_dpc_config_t common_config = {
|
||||
.flags.update_once_configured = true,
|
||||
};
|
||||
TEST_ESP_OK(esp_isp_dpc_configure(isp_proc, &common_config));
|
||||
TEST_ESP_OK(esp_isp_dpc_enable(isp_proc));
|
||||
|
||||
test_isp_dpc_generate_bad_pixel_image(input);
|
||||
test_isp_dpc_process_frame(isp_proc, output, input);
|
||||
test_isp_dpc_write_bgr888_ppm("dpc_static_corrected.ppm", output);
|
||||
test_isp_dpc_verify_result(name, reference, original, output, false);
|
||||
|
||||
TEST_ESP_OK(esp_isp_dpc_disable(isp_proc));
|
||||
TEST_ESP_OK(esp_isp_color_disable(isp_proc));
|
||||
TEST_ESP_OK(esp_isp_disable(isp_proc));
|
||||
TEST_ESP_OK(esp_isp_del_processor(isp_proc));
|
||||
heap_caps_free(output);
|
||||
heap_caps_free(original);
|
||||
heap_caps_free(reference);
|
||||
heap_caps_free(input);
|
||||
}
|
||||
|
||||
TEST_CASE("ISP DPC merges multiple calibration references without hardware", "[isp][dpc]")
|
||||
{
|
||||
static const esp_isp_dpc_calibration_ref_t input_refs[] = {
|
||||
{
|
||||
.dead_pixel_coords = {
|
||||
{ .x = 5, .y = 2 },
|
||||
{ .x = 1, .y = 1 },
|
||||
},
|
||||
.dead_pixel_count = 2,
|
||||
},
|
||||
{
|
||||
.dead_pixel_coords = {
|
||||
{ .x = 3, .y = 1 },
|
||||
{ .x = 5, .y = 2 },
|
||||
},
|
||||
.dead_pixel_count = 2,
|
||||
},
|
||||
{
|
||||
.dead_pixel_coords = {
|
||||
{ .x = 2, .y = 3 },
|
||||
},
|
||||
.dead_pixel_count = 1,
|
||||
},
|
||||
};
|
||||
const esp_isp_dpc_calibration_ref_t *refs[] = {
|
||||
&input_refs[0],
|
||||
&input_refs[1],
|
||||
&input_refs[2],
|
||||
};
|
||||
const esp_isp_dpc_pixel_coord_t expected[] = {
|
||||
{ .x = 1, .y = 1 },
|
||||
{ .x = 3, .y = 1 },
|
||||
{ .x = 5, .y = 2 },
|
||||
{ .x = 2, .y = 3 },
|
||||
};
|
||||
static esp_isp_dpc_calibration_ref_t merged_ref;
|
||||
|
||||
TEST_ESP_OK(esp_isp_dpc_calibration_merge_result(refs,
|
||||
sizeof(refs) / sizeof(refs[0]),
|
||||
&merged_ref));
|
||||
TEST_ASSERT_EQUAL_UINT32(sizeof(expected) / sizeof(expected[0]), merged_ref.dead_pixel_count);
|
||||
for (size_t i = 0; i < merged_ref.dead_pixel_count; i++) {
|
||||
TEST_ASSERT_EQUAL_UINT16(expected[i].x, merged_ref.dead_pixel_coords[i].x);
|
||||
TEST_ASSERT_EQUAL_UINT16(expected[i].y, merged_ref.dead_pixel_coords[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_isp_dpc_dump_dynamic2(void)
|
||||
{
|
||||
esp_isp_dpc_dynamic_config_t dpc_config = {
|
||||
.method = ESP_ISP_DPC_DYNAMIC_METHOD_2,
|
||||
.method_2 = {
|
||||
.first_stage_upper_ratio = {
|
||||
.integer = 1,
|
||||
.decimal = 0,
|
||||
},
|
||||
.first_stage_lower_ratio = {
|
||||
.integer = 0,
|
||||
.decimal = 8,
|
||||
},
|
||||
.bright_deviation_factor = {
|
||||
.integer = 0,
|
||||
.decimal = 16,
|
||||
},
|
||||
.dark_deviation_factor = {
|
||||
.integer = 0,
|
||||
.decimal = 16,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test_isp_dpc_dump_with_config("dynamic2", &dpc_config, true);
|
||||
}
|
||||
|
||||
TEST_CASE("ISP DPC dynamic1 dump bad pixel image", "[isp][dpc]")
|
||||
{
|
||||
test_isp_dpc_dump_dynamic1();
|
||||
}
|
||||
|
||||
TEST_CASE("ISP DPC dynamic2 dump bad pixel image", "[isp][dpc]")
|
||||
{
|
||||
test_isp_dpc_dump_dynamic2();
|
||||
}
|
||||
|
||||
TEST_CASE("ISP DPC static dump bad pixel image", "[isp][dpc]")
|
||||
{
|
||||
test_isp_dpc_dump_static("static", false);
|
||||
}
|
||||
|
||||
TEST_CASE("ISP DPC static and dynamic dump bad pixel image", "[isp][dpc]")
|
||||
{
|
||||
test_isp_dpc_dump_static("static_dynamic", true);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
BEGIN_RE = re.compile(r'BEGIN_PPM:(.+)')
|
||||
END_RE = re.compile(r'END_PPM:(.+)')
|
||||
|
||||
|
||||
def _safe_name(name: str) -> str:
|
||||
return Path(name.strip()).name
|
||||
|
||||
|
||||
def _save_ppm(name: str, lines: list[str], output_dir: Path) -> Path:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
path = output_dir / _safe_name(name)
|
||||
path.write_text(''.join(lines), encoding='ascii')
|
||||
return path
|
||||
|
||||
|
||||
def _consume_line(line: str, state: dict, output_dir: Path) -> Path | None:
|
||||
begin = BEGIN_RE.search(line)
|
||||
if begin:
|
||||
state['name'] = begin.group(1)
|
||||
state['lines'] = []
|
||||
return None
|
||||
|
||||
name = state.get('name')
|
||||
if not name:
|
||||
return None
|
||||
|
||||
end = END_RE.search(line)
|
||||
if end:
|
||||
path = _save_ppm(name, state['lines'], output_dir)
|
||||
state.clear()
|
||||
return path
|
||||
|
||||
state['lines'].append(line)
|
||||
return None
|
||||
|
||||
|
||||
def _extract_from_log(log_path: Path, output_dir: Path) -> list[Path]:
|
||||
saved = []
|
||||
state: dict = {}
|
||||
with log_path.open('r', encoding='utf-8', errors='ignore') as fp:
|
||||
for line in fp:
|
||||
path = _consume_line(line, state, output_dir)
|
||||
if path:
|
||||
saved.append(path)
|
||||
return saved
|
||||
|
||||
|
||||
def _extract_from_serial(port: str, baud: int, output_dir: Path, test_id: str, timeout: float) -> list[Path]:
|
||||
try:
|
||||
import serial
|
||||
except ImportError as e:
|
||||
raise SystemExit('pyserial is required. Please run this script in the ESP-IDF Python environment.') from e
|
||||
|
||||
saved = []
|
||||
state: dict = {}
|
||||
deadline = time.monotonic() + timeout
|
||||
|
||||
with serial.Serial(port, baudrate=baud, timeout=0.2) as ser:
|
||||
ser.reset_input_buffer()
|
||||
ser.write(b'\n')
|
||||
time.sleep(0.2)
|
||||
ser.write(test_id.encode('ascii') + b'\n')
|
||||
|
||||
while time.monotonic() < deadline:
|
||||
raw = ser.readline()
|
||||
if not raw:
|
||||
continue
|
||||
line = raw.decode('utf-8', errors='ignore')
|
||||
sys.stdout.write(line)
|
||||
sys.stdout.flush()
|
||||
|
||||
path = _consume_line(line, state, output_dir)
|
||||
if path:
|
||||
saved.append(path)
|
||||
print(f'\nSaved {path}')
|
||||
if len(saved) >= 2:
|
||||
break
|
||||
|
||||
return saved
|
||||
|
||||
|
||||
def main() -> None:
|
||||
default_output = Path(__file__).resolve().parent / 'dpc_images'
|
||||
parser = argparse.ArgumentParser(description='Save ISP DPC PPM dumps from serial output or a monitor log.')
|
||||
parser.add_argument('--output-dir', type=Path, default=default_output, help='Directory to write .ppm files')
|
||||
parser.add_argument('--log', type=Path, help='Extract PPM files from an existing monitor log')
|
||||
parser.add_argument('--port', help='Serial port, for example /dev/ttyACM0')
|
||||
parser.add_argument('--baud', type=int, default=115200, help='Serial baud rate')
|
||||
parser.add_argument('--test-id', default='11', help='Unity test menu id to run')
|
||||
parser.add_argument('--timeout', type=float, default=30.0, help='Serial capture timeout in seconds')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.log:
|
||||
saved = _extract_from_log(args.log, args.output_dir)
|
||||
elif args.port:
|
||||
saved = _extract_from_serial(args.port, args.baud, args.output_dir, args.test_id, args.timeout)
|
||||
else:
|
||||
parser.error('Either --log or --port is required')
|
||||
|
||||
if not saved:
|
||||
raise SystemExit('No PPM images were found.')
|
||||
|
||||
print('Saved images:')
|
||||
for path in saved:
|
||||
print(path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user