ajustes nas regras de controle radiométrico

This commit is contained in:
Diego Freitas 2026-05-12 19:28:24 -03:00
parent 50cd595fb8
commit ebc6de3557
4 changed files with 745 additions and 310 deletions

View File

@ -234,6 +234,7 @@ def main():
packed_by_camera = frame packed_by_camera = frame
preview_bgr, raw3_preview, preview_source_id = cam.build_preview_from_raw_payload(frame=frame, meta=meta) preview_bgr, raw3_preview, preview_source_id = cam.build_preview_from_raw_payload(frame=frame, meta=meta)
preview_bgr = cv2.cvtColor(preview_bgr, cv2.COLOR_RGB2BGR)
if beauty_preview: if beauty_preview:
rgb_preview = None rgb_preview = None

View File

@ -1,6 +1,6 @@
{ {
"schema": "multispec_module_params_v3", "schema": "multispec_module_params_v3",
"saved_at": "2026-05-12 10:07:11", "saved_at": "2026-05-12 14:20:13",
"frame_type": "RAW_BRUTO", "frame_type": "RAW_BRUTO",
"capture_mode_requested": "AUTO", "capture_mode_requested": "AUTO",
"capture_mode_effective": "AUTO", "capture_mode_effective": "AUTO",
@ -98,9 +98,9 @@
"spectral_control_mode": "shared", "spectral_control_mode": "shared",
"control_metric": "p50", "control_metric": "p50",
"target_value": 0.5, "target_value": 0.5,
"deadband": 0.035, "deadband": 0.055,
"p95_limit": 0.94, "p95_limit": 0.975,
"saturation_limit_pct": 0.5, "saturation_limit_pct": 5.0,
"alpha": 0.18, "alpha": 0.18,
"exp_step_gain": 0.55, "exp_step_gain": 0.55,
"prefer_exposure": true, "prefer_exposure": true,
@ -118,30 +118,30 @@
"nir" "nir"
], ],
"roi_pct": { "roi_pct": {
"x0": 0.304688, "x0": 0.26875,
"y0": 0.8275, "y0": 0.8175,
"x1": 0.364063, "x1": 0.328906,
"y1": 0.9875 "y1": 0.985
}, },
"target_value": 0.08, "target_value": 0.08,
"weight": 0.7, "weight": 0.7,
"roi_pct_by_role": { "roi_pct_by_role": {
"rgb": { "rgb": {
"x0": 0.304688, "x0": 0.26875,
"y0": 0.8275, "y0": 0.8175,
"x1": 0.364063, "x1": 0.328906,
"y1": 0.9875 "y1": 0.985
}, },
"re": { "re": {
"x0": 0.275, "x0": 0.235156,
"y0": 0.8225, "y0": 0.8175,
"x1": 0.334375, "x1": 0.289062,
"y1": 0.995 "y1": 0.985
}, },
"nir": { "nir": {
"x0": 0.3375, "x0": 0.3,
"y0": 0.8325, "y0": 0.83125,
"x1": 0.4, "x1": 0.360938,
"y1": 0.9925 "y1": 0.9925
} }
}, },
@ -151,13 +151,25 @@
"name": "rgb_legacy_01", "name": "rgb_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.304688, "x0": 0.26875,
"y0": 0.8275, "y0": 0.8175,
"x1": 0.364063, "x1": 0.328906,
"y1": 0.9875 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:26" "updated_at": "2026-05-12 14:16:15"
},
{
"name": "black_rgb_02",
"enabled": true,
"roi_pct": {
"x0": 0.813281,
"y0": 0.8275,
"x1": 0.873437,
"y1": 0.9875
},
"created_at": "2026-05-12 14:16:20",
"updated_at": "2026-05-12 14:16:32"
} }
], ],
"re": [ "re": [
@ -165,13 +177,25 @@
"name": "re_legacy_01", "name": "re_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.275, "x0": 0.235156,
"y0": 0.8225, "y0": 0.8175,
"x1": 0.334375, "x1": 0.289062,
"y1": 0.995 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:15" "updated_at": "2026-05-12 14:17:39"
},
{
"name": "black_re_02",
"enabled": true,
"roi_pct": {
"x0": 0.79375,
"y0": 0.83,
"x1": 0.85625,
"y1": 0.99125
},
"created_at": "2026-05-12 14:17:42",
"updated_at": "2026-05-12 14:17:49"
} }
], ],
"nir": [ "nir": [
@ -179,13 +203,25 @@
"name": "nir_legacy_01", "name": "nir_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.3375, "x0": 0.3,
"y0": 0.8325, "y0": 0.83125,
"x1": 0.4, "x1": 0.360938,
"y1": 0.9925 "y1": 0.9925
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:44" "updated_at": "2026-05-12 14:19:03"
},
{
"name": "black_nir_02",
"enabled": true,
"roi_pct": {
"x0": 0.851562,
"y0": 0.82,
"x1": 0.910156,
"y1": 0.97625
},
"created_at": "2026-05-12 14:19:04",
"updated_at": "2026-05-12 14:19:16"
} }
] ]
} }
@ -199,36 +235,36 @@
"nir" "nir"
], ],
"roi_pct": { "roi_pct": {
"x0": 0.238281, "x0": 0.198437,
"y0": 0.82625, "y0": 0.82625,
"x1": 0.298438, "x1": 0.257812,
"y1": 0.9875 "y1": 0.985
}, },
"target_value": 0.35, "target_value": 0.35,
"target_value_by_role": { "target_value_by_role": {
"rgb": 0.34, "rgb": 0.285,
"re": 0.24, "re": 0.22,
"nir": 0.3 "nir": 0.27
}, },
"weight": 1.0, "weight": 1.0,
"roi_pct_by_role": { "roi_pct_by_role": {
"rgb": { "rgb": {
"x0": 0.238281, "x0": 0.198437,
"y0": 0.82625, "y0": 0.82625,
"x1": 0.298438, "x1": 0.257812,
"y1": 0.9875 "y1": 0.985
}, },
"re": { "re": {
"x0": 0.203125, "x0": 0.163281,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.266406, "x1": 0.221875,
"y1": 0.995 "y1": 0.98625
}, },
"nir": { "nir": {
"x0": 0.266406, "x0": 0.228906,
"y0": 0.83125, "y0": 0.8325,
"x1": 0.332031, "x1": 0.290625,
"y1": 0.99625 "y1": 0.99125
} }
}, },
"roi_list_by_role": { "roi_list_by_role": {
@ -237,13 +273,25 @@
"name": "rgb_legacy_01", "name": "rgb_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.238281, "x0": 0.198437,
"y0": 0.82625, "y0": 0.82625,
"x1": 0.298438, "x1": 0.257812,
"y1": 0.9875 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:18" "updated_at": "2026-05-12 14:16:46"
},
{
"name": "gray_rgb_02",
"enabled": true,
"roi_pct": {
"x0": 0.744531,
"y0": 0.8275,
"x1": 0.804688,
"y1": 0.98625
},
"created_at": "2026-05-12 14:16:48",
"updated_at": "2026-05-12 14:16:59"
} }
], ],
"re": [ "re": [
@ -251,13 +299,25 @@
"name": "re_legacy_01", "name": "re_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.203125, "x0": 0.163281,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.266406, "x1": 0.221875,
"y1": 0.995 "y1": 0.98625
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:55" "updated_at": "2026-05-12 14:18:03"
},
{
"name": "gray_re_02",
"enabled": true,
"roi_pct": {
"x0": 0.722656,
"y0": 0.83,
"x1": 0.7875,
"y1": 0.99125
},
"created_at": "2026-05-12 14:18:05",
"updated_at": "2026-05-12 14:18:15"
} }
], ],
"nir": [ "nir": [
@ -265,13 +325,25 @@
"name": "nir_legacy_01", "name": "nir_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.266406, "x0": 0.228906,
"y0": 0.83125, "y0": 0.8325,
"x1": 0.332031, "x1": 0.290625,
"y1": 0.99625 "y1": 0.99125
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:33" "updated_at": "2026-05-12 14:19:24"
},
{
"name": "gray_nir_02",
"enabled": true,
"roi_pct": {
"x0": 0.782031,
"y0": 0.8225,
"x1": 0.84375,
"y1": 0.98
},
"created_at": "2026-05-12 14:19:25",
"updated_at": "2026-05-12 14:19:30"
} }
] ]
} }
@ -285,31 +357,31 @@
"nir" "nir"
], ],
"roi_pct": { "roi_pct": {
"x0": 0.166406, "x0": 0.126562,
"y0": 0.82625, "y0": 0.8225,
"x1": 0.228906, "x1": 0.189062,
"y1": 0.99125 "y1": 0.9825
}, },
"target_value": 0.82, "target_value": 0.82,
"weight": 0.8, "weight": 0.8,
"roi_pct_by_role": { "roi_pct_by_role": {
"rgb": { "rgb": {
"x0": 0.166406, "x0": 0.126562,
"y0": 0.82625, "y0": 0.8225,
"x1": 0.228906, "x1": 0.189062,
"y1": 0.99125 "y1": 0.9825
}, },
"re": { "re": {
"x0": 0.132812, "x0": 0.090625,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.194531, "x1": 0.148438,
"y1": 0.995 "y1": 0.985
}, },
"nir": { "nir": {
"x0": 0.195312, "x0": 0.154688,
"y0": 0.83625, "y0": 0.83125,
"x1": 0.260156, "x1": 0.21875,
"y1": 0.99125 "y1": 0.99625
} }
}, },
"roi_list_by_role": { "roi_list_by_role": {
@ -318,13 +390,25 @@
"name": "rgb_legacy_01", "name": "rgb_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.166406, "x0": 0.126562,
"y0": 0.82625, "y0": 0.8225,
"x1": 0.228906, "x1": 0.189062,
"y1": 0.99125 "y1": 0.9825
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:36" "updated_at": "2026-05-12 14:17:12"
},
{
"name": "white_rgb_02",
"enabled": true,
"roi_pct": {
"x0": 0.675,
"y0": 0.83,
"x1": 0.735156,
"y1": 0.985
},
"created_at": "2026-05-12 14:17:13",
"updated_at": "2026-05-12 14:17:23"
} }
], ],
"re": [ "re": [
@ -332,13 +416,25 @@
"name": "re_legacy_01", "name": "re_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.132812, "x0": 0.090625,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.194531, "x1": 0.148438,
"y1": 0.995 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:04" "updated_at": "2026-05-12 14:18:29"
},
{
"name": "white_re_02",
"enabled": true,
"roi_pct": {
"x0": 0.648438,
"y0": 0.83,
"x1": 0.714063,
"y1": 0.99125
},
"created_at": "2026-05-12 14:18:31",
"updated_at": "2026-05-12 14:18:45"
} }
], ],
"nir": [ "nir": [
@ -346,19 +442,31 @@
"name": "nir_legacy_01", "name": "nir_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.195312, "x0": 0.154688,
"y0": 0.83625, "y0": 0.83125,
"x1": 0.260156, "x1": 0.21875,
"y1": 0.99125 "y1": 0.99625
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:56" "updated_at": "2026-05-12 14:19:45"
},
{
"name": "white_nir_02",
"enabled": true,
"roi_pct": {
"x0": 0.713281,
"y0": 0.825,
"x1": 0.775,
"y1": 0.98125
},
"created_at": "2026-05-12 14:19:46",
"updated_at": "2026-05-12 14:19:54"
} }
] ]
} }
} }
], ],
"exp_apply_threshold_us": 80, "exp_apply_threshold_us": 15,
"gain_apply_threshold": 0.05, "gain_apply_threshold": 0.05,
"apply_same_spectral_to_both": true, "apply_same_spectral_to_both": true,
"spectral_roles": [ "spectral_roles": [
@ -367,17 +475,17 @@
], ],
"dark_limit_pct": 35.0, "dark_limit_pct": 35.0,
"control_strategy": "ratio", "control_strategy": "ratio",
"ratio_alpha": 0.35, "ratio_alpha": 0.42,
"ratio_min": 0.65, "ratio_min": 0.72,
"ratio_max": 1.35, "ratio_max": 1.38,
"reduce_fast_factor": 0.8, "reduce_fast_factor": 0.8,
"factor_min": 0.55, "factor_min": 0.62,
"factor_max": 1.28, "factor_max": 1.42,
"gain_return_enabled": true, "gain_return_enabled": true,
"gain_reduce_on_saturation": true, "gain_reduce_on_saturation": true,
"gain_increase_required_cycles": 5, "gain_increase_required_cycles": 3,
"gain_decrease_required_cycles": 2, "gain_decrease_required_cycles": 1,
"gain_step_up": 0.2, "gain_step_up": 0.3,
"gain_step_down": 0.5, "gain_step_down": 0.5,
"gain_hard_reset_on_saturation": false, "gain_hard_reset_on_saturation": false,
"exp_high_ratio_for_gain": 0.95, "exp_high_ratio_for_gain": 0.95,
@ -391,13 +499,13 @@
}, },
"re": { "re": {
"exp_min_us": 100, "exp_min_us": 100,
"exp_max_us": 2500, "exp_max_us": 3500,
"gain_min": 1.0, "gain_min": 1.0,
"gain_max": 2.0 "gain_max": 2.0
}, },
"nir": { "nir": {
"exp_min_us": 100, "exp_min_us": 100,
"exp_max_us": 3000, "exp_max_us": 3500,
"gain_min": 1.0, "gain_min": 1.0,
"gain_max": 2.0 "gain_max": 2.0
} }
@ -406,8 +514,8 @@
"patch_control_mode": "gray_primary", "patch_control_mode": "gray_primary",
"patch_require_order": true, "patch_require_order": true,
"patch_min_separation": 0.08, "patch_min_separation": 0.08,
"patch_white_sat_limit_pct": 0.5, "patch_white_sat_limit_pct": 5.0,
"patch_white_p95_limit": 0.94, "patch_white_p95_limit": 0.985,
"patch_black_dark_limit_pct": 80.0, "patch_black_dark_limit_pct": 80.0,
"patch_black_max_p50": 0.2, "patch_black_max_p50": 0.2,
"patch_gray_min_p50": 0.08, "patch_gray_min_p50": 0.08,
@ -421,36 +529,42 @@
"x0": 0.05, "x0": 0.05,
"y0": 0.05, "y0": 0.05,
"x1": 0.95, "x1": 0.95,
"y1": 0.95 "y1": 0.76
}, },
"global_guard_sat_threshold": 0.985, "global_guard_sat_threshold": 0.985,
"global_guard_near_sat_threshold": 0.94, "global_guard_near_sat_threshold": 0.94,
"global_guard_sat_pct_soft": 0.05, "global_guard_sat_pct_soft": 0.50,
"global_guard_sat_pct_hard": 0.2, "global_guard_sat_pct_hard": 1.5,
"global_guard_sat_pct_extreme": 0.8, "global_guard_sat_pct_extreme": 5.0,
"global_guard_blob_pct_soft": 0.015, "global_guard_blob_pct_soft": 0.20,
"global_guard_blob_pct_hard": 0.08, "global_guard_blob_pct_hard": 0.80,
"global_guard_blob_pct_extreme": 0.25, "global_guard_blob_pct_extreme": 2.2,
"global_guard_min_blob_px": 48, "global_guard_min_blob_px": 48,
"global_guard_downsample_max_side": 320, "global_guard_downsample_max_side": 320,
"global_guard_reduce_factor_soft": 0.82, "global_guard_reduce_factor_soft": 0.96,
"global_guard_reduce_factor_hard": 0.6, "global_guard_reduce_factor_hard": 0.82,
"global_guard_reduce_factor_extreme": 0.35, "global_guard_reduce_factor_extreme": 0.62,
"sun_guard_enabled": true, "sun_guard_enabled": true,
"sun_guard_p99_threshold": 0.9, "sun_guard_p99_threshold": 0.96,
"sun_guard_near_sat_pct_threshold": 0.8, "sun_guard_near_sat_pct_threshold": 2.0,
"sun_guard_freeze_increase_cycles": 2, "sun_guard_freeze_increase_cycles": 1,
"sun_guard_allow_decrease": true, "sun_guard_allow_decrease": true,
"guard_force_apply_enabled": true, "guard_force_apply_enabled": true,
"guard_force_apply_soft": true, "guard_force_apply_soft": false,
"guard_force_apply_hard": true, "guard_force_apply_hard": true,
"guard_force_apply_extreme": true, "guard_force_apply_extreme": true,
"guard_force_apply_on_patch_saturation": true, "guard_force_apply_on_patch_saturation": true,
"guard_freeze_cycles_soft": 3, "guard_freeze_cycles_soft": 1,
"guard_freeze_cycles_hard": 5, "guard_freeze_cycles_hard": 2,
"guard_freeze_cycles_extreme": 8, "guard_freeze_cycles_extreme": 3,
"guard_reapply_min_exp_on_emergency": true, "guard_reapply_min_exp_on_emergency": true,
"guard_min_exp_margin_us": 80 "guard_min_exp_margin_us": 80,
"patch_two_roi_soften_risk": true,
"patch_two_roi_white_risk_percentile": 75,
"patch_two_roi_other_risk_percentile": 50,
"patch_white_single_roi_saturation_reject": true,
"patch_white_roi_reject_sat_pct": 5.0,
"patch_white_roi_reject_p95": 0.995
}, },
"radiometric_normalization": { "radiometric_normalization": {
"enabled": false, "enabled": false,
@ -492,7 +606,7 @@
"save_patch_stats": true "save_patch_stats": true
}, },
"rgb_calibration": { "rgb_calibration": {
"enabled": true, "enabled": false,
"gains": { "gains": {
"R": 1.2500000000000002, "R": 1.2500000000000002,
"G": 1.0, "G": 1.0,

View File

@ -1,6 +1,6 @@
{ {
"schema": "multispec_radiometric_config_profiles_v4", "schema": "multispec_radiometric_config_profiles_v4",
"saved_at": "2026-05-12 09:55:04", "saved_at": "2026-05-12 14:20:05",
"active_profile": "three_reference_patches_mode", "active_profile": "three_reference_patches_mode",
"global_scene_mode": { "global_scene_mode": {
"radiometric_config": { "radiometric_config": {
@ -158,30 +158,30 @@
"nir" "nir"
], ],
"roi_pct": { "roi_pct": {
"x0": 0.304688, "x0": 0.26875,
"y0": 0.8275, "y0": 0.8175,
"x1": 0.364063, "x1": 0.328906,
"y1": 0.9875 "y1": 0.985
}, },
"target_value": 0.08, "target_value": 0.08,
"weight": 0.7, "weight": 0.7,
"roi_pct_by_role": { "roi_pct_by_role": {
"rgb": { "rgb": {
"x0": 0.304688, "x0": 0.26875,
"y0": 0.8275, "y0": 0.8175,
"x1": 0.364063, "x1": 0.328906,
"y1": 0.9875 "y1": 0.985
}, },
"re": { "re": {
"x0": 0.275, "x0": 0.235156,
"y0": 0.8225, "y0": 0.8175,
"x1": 0.334375, "x1": 0.289062,
"y1": 0.995 "y1": 0.985
}, },
"nir": { "nir": {
"x0": 0.3375, "x0": 0.3,
"y0": 0.8325, "y0": 0.83125,
"x1": 0.4, "x1": 0.360938,
"y1": 0.9925 "y1": 0.9925
} }
}, },
@ -191,13 +191,25 @@
"name": "rgb_legacy_01", "name": "rgb_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.304688, "x0": 0.26875,
"y0": 0.8275, "y0": 0.8175,
"x1": 0.364063, "x1": 0.328906,
"y1": 0.9875 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:26" "updated_at": "2026-05-12 14:16:15"
},
{
"name": "black_rgb_02",
"enabled": true,
"roi_pct": {
"x0": 0.813281,
"y0": 0.8275,
"x1": 0.873437,
"y1": 0.9875
},
"created_at": "2026-05-12 14:16:20",
"updated_at": "2026-05-12 14:16:32"
} }
], ],
"re": [ "re": [
@ -205,13 +217,25 @@
"name": "re_legacy_01", "name": "re_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.275, "x0": 0.235156,
"y0": 0.8225, "y0": 0.8175,
"x1": 0.334375, "x1": 0.289062,
"y1": 0.995 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:15" "updated_at": "2026-05-12 14:17:39"
},
{
"name": "black_re_02",
"enabled": true,
"roi_pct": {
"x0": 0.79375,
"y0": 0.83,
"x1": 0.85625,
"y1": 0.99125
},
"created_at": "2026-05-12 14:17:42",
"updated_at": "2026-05-12 14:17:49"
} }
], ],
"nir": [ "nir": [
@ -219,13 +243,25 @@
"name": "nir_legacy_01", "name": "nir_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.3375, "x0": 0.3,
"y0": 0.8325, "y0": 0.83125,
"x1": 0.4, "x1": 0.360938,
"y1": 0.9925 "y1": 0.9925
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:44" "updated_at": "2026-05-12 14:19:03"
},
{
"name": "black_nir_02",
"enabled": true,
"roi_pct": {
"x0": 0.851562,
"y0": 0.82,
"x1": 0.910156,
"y1": 0.97625
},
"created_at": "2026-05-12 14:19:04",
"updated_at": "2026-05-12 14:19:16"
} }
] ]
} }
@ -239,10 +275,10 @@
"nir" "nir"
], ],
"roi_pct": { "roi_pct": {
"x0": 0.238281, "x0": 0.198437,
"y0": 0.82625, "y0": 0.82625,
"x1": 0.298438, "x1": 0.257812,
"y1": 0.9875 "y1": 0.985
}, },
"target_value": 0.35, "target_value": 0.35,
"target_value_by_role": { "target_value_by_role": {
@ -253,22 +289,22 @@
"weight": 1.0, "weight": 1.0,
"roi_pct_by_role": { "roi_pct_by_role": {
"rgb": { "rgb": {
"x0": 0.238281, "x0": 0.198437,
"y0": 0.82625, "y0": 0.82625,
"x1": 0.298438, "x1": 0.257812,
"y1": 0.9875 "y1": 0.985
}, },
"re": { "re": {
"x0": 0.203125, "x0": 0.163281,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.266406, "x1": 0.221875,
"y1": 0.995 "y1": 0.98625
}, },
"nir": { "nir": {
"x0": 0.266406, "x0": 0.228906,
"y0": 0.83125, "y0": 0.8325,
"x1": 0.332031, "x1": 0.290625,
"y1": 0.99625 "y1": 0.99125
} }
}, },
"roi_list_by_role": { "roi_list_by_role": {
@ -277,13 +313,25 @@
"name": "rgb_legacy_01", "name": "rgb_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.238281, "x0": 0.198437,
"y0": 0.82625, "y0": 0.82625,
"x1": 0.298438, "x1": 0.257812,
"y1": 0.9875 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:18" "updated_at": "2026-05-12 14:16:46"
},
{
"name": "gray_rgb_02",
"enabled": true,
"roi_pct": {
"x0": 0.744531,
"y0": 0.8275,
"x1": 0.804688,
"y1": 0.98625
},
"created_at": "2026-05-12 14:16:48",
"updated_at": "2026-05-12 14:16:59"
} }
], ],
"re": [ "re": [
@ -291,13 +339,25 @@
"name": "re_legacy_01", "name": "re_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.203125, "x0": 0.163281,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.266406, "x1": 0.221875,
"y1": 0.995 "y1": 0.98625
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:55" "updated_at": "2026-05-12 14:18:03"
},
{
"name": "gray_re_02",
"enabled": true,
"roi_pct": {
"x0": 0.722656,
"y0": 0.83,
"x1": 0.7875,
"y1": 0.99125
},
"created_at": "2026-05-12 14:18:05",
"updated_at": "2026-05-12 14:18:15"
} }
], ],
"nir": [ "nir": [
@ -305,13 +365,25 @@
"name": "nir_legacy_01", "name": "nir_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.266406, "x0": 0.228906,
"y0": 0.83125, "y0": 0.8325,
"x1": 0.332031, "x1": 0.290625,
"y1": 0.99625 "y1": 0.99125
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:33" "updated_at": "2026-05-12 14:19:24"
},
{
"name": "gray_nir_02",
"enabled": true,
"roi_pct": {
"x0": 0.782031,
"y0": 0.8225,
"x1": 0.84375,
"y1": 0.98
},
"created_at": "2026-05-12 14:19:25",
"updated_at": "2026-05-12 14:19:30"
} }
] ]
} }
@ -325,31 +397,31 @@
"nir" "nir"
], ],
"roi_pct": { "roi_pct": {
"x0": 0.166406, "x0": 0.126562,
"y0": 0.82625, "y0": 0.8225,
"x1": 0.228906, "x1": 0.189062,
"y1": 0.99125 "y1": 0.9825
}, },
"target_value": 0.82, "target_value": 0.82,
"weight": 0.8, "weight": 0.8,
"roi_pct_by_role": { "roi_pct_by_role": {
"rgb": { "rgb": {
"x0": 0.166406, "x0": 0.126562,
"y0": 0.82625, "y0": 0.8225,
"x1": 0.228906, "x1": 0.189062,
"y1": 0.99125 "y1": 0.9825
}, },
"re": { "re": {
"x0": 0.132812, "x0": 0.090625,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.194531, "x1": 0.148438,
"y1": 0.995 "y1": 0.985
}, },
"nir": { "nir": {
"x0": 0.195312, "x0": 0.154688,
"y0": 0.83625, "y0": 0.83125,
"x1": 0.260156, "x1": 0.21875,
"y1": 0.99125 "y1": 0.99625
} }
}, },
"roi_list_by_role": { "roi_list_by_role": {
@ -358,13 +430,25 @@
"name": "rgb_legacy_01", "name": "rgb_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.166406, "x0": 0.126562,
"y0": 0.82625, "y0": 0.8225,
"x1": 0.228906, "x1": 0.189062,
"y1": 0.99125 "y1": 0.9825
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:36" "updated_at": "2026-05-12 14:17:12"
},
{
"name": "white_rgb_02",
"enabled": true,
"roi_pct": {
"x0": 0.675,
"y0": 0.83,
"x1": 0.735156,
"y1": 0.985
},
"created_at": "2026-05-12 14:17:13",
"updated_at": "2026-05-12 14:17:23"
} }
], ],
"re": [ "re": [
@ -372,13 +456,25 @@
"name": "re_legacy_01", "name": "re_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.132812, "x0": 0.090625,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.194531, "x1": 0.148438,
"y1": 0.995 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:04" "updated_at": "2026-05-12 14:18:29"
},
{
"name": "white_re_02",
"enabled": true,
"roi_pct": {
"x0": 0.648438,
"y0": 0.83,
"x1": 0.714063,
"y1": 0.99125
},
"created_at": "2026-05-12 14:18:31",
"updated_at": "2026-05-12 14:18:45"
} }
], ],
"nir": [ "nir": [
@ -386,13 +482,25 @@
"name": "nir_legacy_01", "name": "nir_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.195312, "x0": 0.154688,
"y0": 0.83625, "y0": 0.83125,
"x1": 0.260156, "x1": 0.21875,
"y1": 0.99125 "y1": 0.99625
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:56" "updated_at": "2026-05-12 14:19:45"
},
{
"name": "white_nir_02",
"enabled": true,
"roi_pct": {
"x0": 0.713281,
"y0": 0.825,
"x1": 0.775,
"y1": 0.98125
},
"created_at": "2026-05-12 14:19:46",
"updated_at": "2026-05-12 14:19:54"
} }
] ]
} }
@ -540,30 +648,30 @@
"nir" "nir"
], ],
"roi_pct": { "roi_pct": {
"x0": 0.304688, "x0": 0.26875,
"y0": 0.8275, "y0": 0.8175,
"x1": 0.364063, "x1": 0.328906,
"y1": 0.9875 "y1": 0.985
}, },
"target_value": 0.08, "target_value": 0.08,
"weight": 0.7, "weight": 0.7,
"roi_pct_by_role": { "roi_pct_by_role": {
"rgb": { "rgb": {
"x0": 0.304688, "x0": 0.26875,
"y0": 0.8275, "y0": 0.8175,
"x1": 0.364063, "x1": 0.328906,
"y1": 0.9875 "y1": 0.985
}, },
"re": { "re": {
"x0": 0.275, "x0": 0.235156,
"y0": 0.8225, "y0": 0.8175,
"x1": 0.334375, "x1": 0.289062,
"y1": 0.995 "y1": 0.985
}, },
"nir": { "nir": {
"x0": 0.3375, "x0": 0.3,
"y0": 0.8325, "y0": 0.83125,
"x1": 0.4, "x1": 0.360938,
"y1": 0.9925 "y1": 0.9925
} }
}, },
@ -573,13 +681,25 @@
"name": "rgb_legacy_01", "name": "rgb_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.304688, "x0": 0.26875,
"y0": 0.8275, "y0": 0.8175,
"x1": 0.364063, "x1": 0.328906,
"y1": 0.9875 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:26" "updated_at": "2026-05-12 14:16:15"
},
{
"name": "black_rgb_02",
"enabled": true,
"roi_pct": {
"x0": 0.813281,
"y0": 0.8275,
"x1": 0.873437,
"y1": 0.9875
},
"created_at": "2026-05-12 14:16:20",
"updated_at": "2026-05-12 14:16:32"
} }
], ],
"re": [ "re": [
@ -587,13 +707,25 @@
"name": "re_legacy_01", "name": "re_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.275, "x0": 0.235156,
"y0": 0.8225, "y0": 0.8175,
"x1": 0.334375, "x1": 0.289062,
"y1": 0.995 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:15" "updated_at": "2026-05-12 14:17:39"
},
{
"name": "black_re_02",
"enabled": true,
"roi_pct": {
"x0": 0.79375,
"y0": 0.83,
"x1": 0.85625,
"y1": 0.99125
},
"created_at": "2026-05-12 14:17:42",
"updated_at": "2026-05-12 14:17:49"
} }
], ],
"nir": [ "nir": [
@ -601,13 +733,25 @@
"name": "nir_legacy_01", "name": "nir_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.3375, "x0": 0.3,
"y0": 0.8325, "y0": 0.83125,
"x1": 0.4, "x1": 0.360938,
"y1": 0.9925 "y1": 0.9925
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:44" "updated_at": "2026-05-12 14:19:03"
},
{
"name": "black_nir_02",
"enabled": true,
"roi_pct": {
"x0": 0.851562,
"y0": 0.82,
"x1": 0.910156,
"y1": 0.97625
},
"created_at": "2026-05-12 14:19:04",
"updated_at": "2026-05-12 14:19:16"
} }
] ]
} }
@ -621,10 +765,10 @@
"nir" "nir"
], ],
"roi_pct": { "roi_pct": {
"x0": 0.238281, "x0": 0.198437,
"y0": 0.82625, "y0": 0.82625,
"x1": 0.298438, "x1": 0.257812,
"y1": 0.9875 "y1": 0.985
}, },
"target_value": 0.35, "target_value": 0.35,
"target_value_by_role": { "target_value_by_role": {
@ -635,22 +779,22 @@
"weight": 1.0, "weight": 1.0,
"roi_pct_by_role": { "roi_pct_by_role": {
"rgb": { "rgb": {
"x0": 0.238281, "x0": 0.198437,
"y0": 0.82625, "y0": 0.82625,
"x1": 0.298438, "x1": 0.257812,
"y1": 0.9875 "y1": 0.985
}, },
"re": { "re": {
"x0": 0.203125, "x0": 0.163281,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.266406, "x1": 0.221875,
"y1": 0.995 "y1": 0.98625
}, },
"nir": { "nir": {
"x0": 0.266406, "x0": 0.228906,
"y0": 0.83125, "y0": 0.8325,
"x1": 0.332031, "x1": 0.290625,
"y1": 0.99625 "y1": 0.99125
} }
}, },
"roi_list_by_role": { "roi_list_by_role": {
@ -659,13 +803,25 @@
"name": "rgb_legacy_01", "name": "rgb_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.238281, "x0": 0.198437,
"y0": 0.82625, "y0": 0.82625,
"x1": 0.298438, "x1": 0.257812,
"y1": 0.9875 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:18" "updated_at": "2026-05-12 14:16:46"
},
{
"name": "gray_rgb_02",
"enabled": true,
"roi_pct": {
"x0": 0.744531,
"y0": 0.8275,
"x1": 0.804688,
"y1": 0.98625
},
"created_at": "2026-05-12 14:16:48",
"updated_at": "2026-05-12 14:16:59"
} }
], ],
"re": [ "re": [
@ -673,13 +829,25 @@
"name": "re_legacy_01", "name": "re_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.203125, "x0": 0.163281,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.266406, "x1": 0.221875,
"y1": 0.995 "y1": 0.98625
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:55" "updated_at": "2026-05-12 14:18:03"
},
{
"name": "gray_re_02",
"enabled": true,
"roi_pct": {
"x0": 0.722656,
"y0": 0.83,
"x1": 0.7875,
"y1": 0.99125
},
"created_at": "2026-05-12 14:18:05",
"updated_at": "2026-05-12 14:18:15"
} }
], ],
"nir": [ "nir": [
@ -687,13 +855,25 @@
"name": "nir_legacy_01", "name": "nir_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.266406, "x0": 0.228906,
"y0": 0.83125, "y0": 0.8325,
"x1": 0.332031, "x1": 0.290625,
"y1": 0.99625 "y1": 0.99125
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:33" "updated_at": "2026-05-12 14:19:24"
},
{
"name": "gray_nir_02",
"enabled": true,
"roi_pct": {
"x0": 0.782031,
"y0": 0.8225,
"x1": 0.84375,
"y1": 0.98
},
"created_at": "2026-05-12 14:19:25",
"updated_at": "2026-05-12 14:19:30"
} }
] ]
} }
@ -707,31 +887,31 @@
"nir" "nir"
], ],
"roi_pct": { "roi_pct": {
"x0": 0.166406, "x0": 0.126562,
"y0": 0.82625, "y0": 0.8225,
"x1": 0.228906, "x1": 0.189062,
"y1": 0.99125 "y1": 0.9825
}, },
"target_value": 0.82, "target_value": 0.82,
"weight": 0.8, "weight": 0.8,
"roi_pct_by_role": { "roi_pct_by_role": {
"rgb": { "rgb": {
"x0": 0.166406, "x0": 0.126562,
"y0": 0.82625, "y0": 0.8225,
"x1": 0.228906, "x1": 0.189062,
"y1": 0.99125 "y1": 0.9825
}, },
"re": { "re": {
"x0": 0.132812, "x0": 0.090625,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.194531, "x1": 0.148438,
"y1": 0.995 "y1": 0.985
}, },
"nir": { "nir": {
"x0": 0.195312, "x0": 0.154688,
"y0": 0.83625, "y0": 0.83125,
"x1": 0.260156, "x1": 0.21875,
"y1": 0.99125 "y1": 0.99625
} }
}, },
"roi_list_by_role": { "roi_list_by_role": {
@ -740,13 +920,25 @@
"name": "rgb_legacy_01", "name": "rgb_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.166406, "x0": 0.126562,
"y0": 0.82625, "y0": 0.8225,
"x1": 0.228906, "x1": 0.189062,
"y1": 0.99125 "y1": 0.9825
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:53:36" "updated_at": "2026-05-12 14:17:12"
},
{
"name": "white_rgb_02",
"enabled": true,
"roi_pct": {
"x0": 0.675,
"y0": 0.83,
"x1": 0.735156,
"y1": 0.985
},
"created_at": "2026-05-12 14:17:13",
"updated_at": "2026-05-12 14:17:23"
} }
], ],
"re": [ "re": [
@ -754,13 +946,25 @@
"name": "re_legacy_01", "name": "re_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.132812, "x0": 0.090625,
"y0": 0.82625, "y0": 0.8175,
"x1": 0.194531, "x1": 0.148438,
"y1": 0.995 "y1": 0.985
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:04" "updated_at": "2026-05-12 14:18:29"
},
{
"name": "white_re_02",
"enabled": true,
"roi_pct": {
"x0": 0.648438,
"y0": 0.83,
"x1": 0.714063,
"y1": 0.99125
},
"created_at": "2026-05-12 14:18:31",
"updated_at": "2026-05-12 14:18:45"
} }
], ],
"nir": [ "nir": [
@ -768,13 +972,25 @@
"name": "nir_legacy_01", "name": "nir_legacy_01",
"enabled": true, "enabled": true,
"roi_pct": { "roi_pct": {
"x0": 0.195312, "x0": 0.154688,
"y0": 0.83625, "y0": 0.83125,
"x1": 0.260156, "x1": 0.21875,
"y1": 0.99125 "y1": 0.99625
}, },
"created_at": "2026-05-07 14:36:25", "created_at": "2026-05-07 14:36:25",
"updated_at": "2026-05-12 09:54:56" "updated_at": "2026-05-12 14:19:45"
},
{
"name": "white_nir_02",
"enabled": true,
"roi_pct": {
"x0": 0.713281,
"y0": 0.825,
"x1": 0.775,
"y1": 0.98125
},
"created_at": "2026-05-12 14:19:46",
"updated_at": "2026-05-12 14:19:54"
} }
] ]
} }

View File

@ -273,6 +273,13 @@ class RadiometricController:
"re": {"exp": None, "gain": None}, "re": {"exp": None, "gain": None},
} }
self.patch_two_roi_soften_risk = bool(cfg.get("patch_two_roi_soften_risk", True))
self.patch_two_roi_white_risk_percentile = float(cfg.get("patch_two_roi_white_risk_percentile", 75.0))
self.patch_two_roi_other_risk_percentile = float(cfg.get("patch_two_roi_other_risk_percentile", 50.0))
self.patch_white_single_roi_saturation_reject = bool(cfg.get("patch_white_single_roi_saturation_reject", True))
self.patch_white_roi_reject_sat_pct = float(cfg.get("patch_white_roi_reject_sat_pct", 5.0))
self.patch_white_roi_reject_p95 = float(cfg.get("patch_white_roi_reject_p95", 0.995))
def _load_config_json(self, path): def _load_config_json(self, path):
if not path: if not path:
return {} return {}
@ -796,18 +803,67 @@ class RadiometricController:
if self.patch_roi_outlier_reject and len(valid) >= 3: if self.patch_roi_outlier_reject and len(valid) >= 3:
kept = [] kept = []
for r in valid: for r in valid:
p50 = float(r["stats"].get("p50", 0.0)) p50 = float(r["stats"].get("p50", 0.0))
if abs(p50 - median_p50) <= self.patch_roi_max_p50_delta: if abs(p50 - median_p50) <= self.patch_roi_max_p50_delta:
kept.append(r) kept.append(r)
else: else:
rejected.append(r) rejected.append(r)
if kept: if kept:
used = kept used = kept
if rejected: if rejected:
warnings.append(f"roi_outliers_rejected:{len(rejected)}") warnings.append(f"roi_outliers_rejected:{len(rejected)}")
elif self.patch_roi_outlier_reject and len(valid) == 2 and spread_p50 > self.patch_roi_max_p50_delta: elif self.patch_roi_outlier_reject and len(valid) == 2 and spread_p50 > self.patch_roi_max_p50_delta:
# Com apenas 2 ROIs não dá para saber qual é o outlier.
# Então não descartamos nenhuma. Usamos as duas, mas marcamos que
# existe divergência para a etapa de agregação suavizar p95/saturação.
warnings.append(f"roi_p50_spread_high:{spread_p50:.3f}") warnings.append(f"roi_p50_spread_high:{spread_p50:.3f}")
used = valid
patch_type_norm = str(patch_type or "reference").lower()
# ------------------------------------------------------------
# Caso especial: branco com 2 ROIs
# ------------------------------------------------------------
# Se apenas uma ROI branca saturou muito e a outra está limpa,
# tratamos a saturada como reflexo/ROI ruim naquele ciclo.
# Se as duas saturaram, mantém as duas e deixa o controller reduzir.
if (
patch_type_norm == "white"
and self.patch_white_single_roi_saturation_reject
and len(used) == 2
):
clean_rois = []
saturated_rois = []
for r in used:
st = r.get("stats", {}) or {}
roi_sat = float(st.get("sat_pct", 0.0))
roi_p95 = float(st.get("p95", 0.0))
is_bad_white_roi = (
roi_sat > self.patch_white_roi_reject_sat_pct
or roi_p95 >= self.patch_white_roi_reject_p95
)
if is_bad_white_roi:
saturated_rois.append(r)
else:
clean_rois.append(r)
# Só rejeita se exatamente uma saturou e existe uma ROI limpa.
# Se as duas saturaram, é saturação real do branco, então reduz exposição.
if len(saturated_rois) == 1 and len(clean_rois) == 1:
bad_name = str(saturated_rois[0].get("name", "unknown"))
warnings.append(f"white_single_saturated_roi_rejected:{bad_name}")
rejected.extend(saturated_rois)
used = clean_rois
if len(used) < self.patch_roi_min_valid_rois: if len(used) < self.patch_roi_min_valid_rois:
warnings.append(f"valid_roi_count_low:{len(used)}<{self.patch_roi_min_valid_rois}") warnings.append(f"valid_roi_count_low:{len(used)}<{self.patch_roi_min_valid_rois}")
@ -830,9 +886,39 @@ class RadiometricController:
reducer = np.mean if reduce_method == "mean_valid_rois" else np.median reducer = np.mean if reduce_method == "mean_valid_rois" else np.median
# Para brilho/controle usamos redução robusta; para guardas de risco, # ------------------------------------------------------------
# mantemos máximos de p95/saturação. Assim um cartão branco saturado # Agregação robusta de risco para múltiplas ROIs
# ainda força redução de exposição. # ------------------------------------------------------------
# Para o valor de controle usamos redução robusta.
# Para risco de saturação, o código antigo usava sempre o máximo.
# Isso é seguro, mas com 2 cartões pode deixar uma única ROI mandar
# derrubar a exposição inteira. Então suavizamos quando há divergência.
two_roi_disagreement = (
self.patch_two_roi_soften_risk
and self.patch_roi_outlier_reject
and len(valid) == 2
and spread_p50 > self.patch_roi_max_p50_delta
)
patch_type_norm = str(patch_type or "reference").lower()
if two_roi_disagreement:
if patch_type_norm == "white":
risk_percentile = self.patch_two_roi_white_risk_percentile
else:
risk_percentile = self.patch_two_roi_other_risk_percentile
p95_agg = float(np.percentile(p95s, risk_percentile))
sat_agg = float(np.percentile(sats, risk_percentile))
warnings.append(
f"roi_risk_softened_for_2_rois:{patch_type_norm}:p{risk_percentile:.0f}"
)
else:
p95_agg = float(np.max(p95s))
sat_agg = float(np.max(sats))
stats = { stats = {
"valid": True, "valid": True,
"pixels": pixels, "pixels": pixels,
@ -840,8 +926,11 @@ class RadiometricController:
"std": float(reducer(stds)), "std": float(reducer(stds)),
"p05": float(reducer(p05s)), "p05": float(reducer(p05s)),
"p50": float(reducer(p50s)), "p50": float(reducer(p50s)),
"p95": float(np.max(p95s)),
"sat_pct": float(np.max(sats)), # Proteção suavizada quando há exatamente 2 ROIs divergentes.
"p95": p95_agg,
"sat_pct": sat_agg,
"dark_pct": float(reducer(darks)), "dark_pct": float(reducer(darks)),
"roi_reduce_method": reduce_method, "roi_reduce_method": reduce_method,
"roi_count": int(len(roi_results)), "roi_count": int(len(roi_results)),
@ -850,6 +939,7 @@ class RadiometricController:
"roi_rejected_count": int(len(rejected)), "roi_rejected_count": int(len(rejected)),
"roi_p50_values": [float(v) for v in p50_values.tolist()], "roi_p50_values": [float(v) for v in p50_values.tolist()],
"roi_p50_spread": spread_p50, "roi_p50_spread": spread_p50,
"roi_two_roi_disagreement": bool(two_roi_disagreement),
} }
quality = { quality = {
@ -1753,6 +1843,17 @@ class RadiometricController:
"gain_policy": gain_policy, "gain_policy": gain_policy,
} }
def should_apply_exposure(self, old_exp: int, new_exp: int) -> bool:
delta = abs(int(new_exp) - int(old_exp))
# Threshold menor quando a exposição está baixa.
relative_threshold = int(max(8, old_exp * 0.03))
# Mantém limite absoluto máximo para não ficar insensível.
threshold = min(self.exp_apply_threshold_us, relative_threshold)
return delta >= threshold
def apply_control(self, role: str, decision: dict): def apply_control(self, role: str, decision: dict):
role = str(role).lower() role = str(role).lower()
new_exp = int(decision["new_exp"]) new_exp = int(decision["new_exp"])
@ -1771,7 +1872,10 @@ class RadiometricController:
if role == "rgb": if role == "rgb":
responses["awb"] = self.client.svc.set_awb_enable(role=role, enable=False) responses["awb"] = self.client.svc.set_awb_enable(role=role, enable=False)
self._ae_disabled.add(role) self._ae_disabled.add(role)
exp_delta_ok = last["exp"] is None or abs(new_exp - last["exp"]) >= self.exp_apply_threshold_us if last["exp"] is None:
exp_delta_ok = True
else:
exp_delta_ok = self.should_apply_exposure(last["exp"], new_exp)
if force_apply_exposure or exp_delta_ok: if force_apply_exposure or exp_delta_ok:
responses["exposure"] = self.client.svc.set_exposure_time(role=role, exposure_time_us=new_exp) responses["exposure"] = self.client.svc.set_exposure_time(role=role, exposure_time_us=new_exp)
last["exp"] = new_exp last["exp"] = new_exp