import json import argparse import os from datetime import datetime def now_str(): return datetime.now().strftime("%Y-%m-%d %H:%M:%S") def load_json(path): with open(path, "r", encoding="utf-8") as f: return json.load(f) def rel_or_abs(path): """ Mantém o caminho como veio, mas normaliza separadores. Isso evita quebrar projetos Windows/Linux e deixa o module_params legível. """ if path is None: return None return str(path).replace("\\", "/") def build_flatfield_config(flatfield_json_path, flatfield_data): """ Espera o JSON gerado pelo flatfield_calibration_tool_v2.py. Estrutura esperada: schema: multispec_flatfield_v1 outputs.npz: calibration/flatfield_maps_v1.npz channels: ["R", "G", "B", "RE", "NIR"] maps.CH.gain_key / flat_norm_key / ... """ if not isinstance(flatfield_data, dict): return { "enabled": False, "reason": "flatfield_json ausente ou inválido", } outputs = flatfield_data.get("outputs", {}) or {} maps = flatfield_data.get("maps", {}) or {} npz_path = outputs.get("npz") if not npz_path: # Fallback: tenta deduzir pelo nome do json. base, _ = os.path.splitext(flatfield_json_path) npz_path = base + ".npz" channels = flatfield_data.get("channels") or ["R", "G", "B", "RE", "NIR"] channel_maps = {} for ch in channels: m = maps.get(ch, {}) or {} channel_maps[ch] = { "gain_key": m.get("gain_key", f"gain_{ch}"), "flat_norm_key": m.get("flat_norm_key", f"flat_norm_{ch}"), "white_median_key": m.get("white_median_key", f"white_median_{ch}"), "dark_median_key": m.get("dark_median_key", f"dark_median_{ch}"), "shape": m.get("shape"), "gain_min": m.get("gain_min"), "gain_max": m.get("gain_max"), "gain_mean": m.get("gain_mean"), "gain_std": m.get("gain_std"), } return { "enabled": True, "schema": flatfield_data.get("schema", "multispec_flatfield_v1"), "created_at": flatfield_data.get("created_at"), "json_file": rel_or_abs(flatfield_json_path), "npz_file": rel_or_abs(npz_path), "apply_before_fusion": True, "apply_after_decode": True, "apply_space": "native_camera_space", "map_type": "gain", "formula": "channel_corrected = max(channel_linear - dark, 0) * gain_map", "channels": channels, "channel_maps": channel_maps, "exp_gain_correct_during_flat_capture": bool(flatfield_data.get("exp_gain_correct", False)), "smooth_ksize": flatfield_data.get("smooth_ksize"), "min_gain": flatfield_data.get("min_gain"), "max_gain": flatfield_data.get("max_gain"), "notes": flatfield_data.get("notes", ""), } def main(): parser = argparse.ArgumentParser( description="Monta o module_params.json unificando calibração de câmera, fusão, radiometria e flat-field.", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument("--camera_json", default="calibration/sensor_calibration.json") parser.add_argument("--fusion_json", default="calibration/manual_offsets.json") parser.add_argument("--radiometric_json", default="calibration/radiometric_config.json") parser.add_argument("--radiometric_profile", default="global_scene_mode", choices=["global_scene_mode", "three_reference_patches_mode"]) parser.add_argument("--flatfield_json", default="calibration/flatfield_maps_v1.json") parser.add_argument("--disable_flatfield", action="store_true") parser.add_argument("--out", default="calibration/module_params.json") args = parser.parse_args() cam_data = load_json(args.camera_json) fusion_data = load_json(args.fusion_json) radiometric_data = {} if args.radiometric_json: radiometric_data = load_json(args.radiometric_json) flatfield_data = {} flatfield_config = { "enabled": False, "reason": "flatfield não informado ou arquivo inexistente", } if not args.disable_flatfield and args.flatfield_json and os.path.isfile(args.flatfield_json): flatfield_data = load_json(args.flatfield_json) flatfield_config = build_flatfield_config(args.flatfield_json, flatfield_data) elif args.disable_flatfield: flatfield_config = { "enabled": False, "reason": "desabilitado via --disable_flatfield", } # ========================= # CAMERA SETTINGS # ========================= camera_settings = cam_data.get("camera_settings") if not isinstance(camera_settings, dict): raise RuntimeError("camera_json sem camera_settings válido") # ========================= # RGB CALIBRATION # ========================= rgb_calibration = cam_data.get("rgb_calibration") if not isinstance(rgb_calibration, dict): rgb_calibration = { "enabled": False, "gains": { "R": 1.0, "G": 1.0, "B": 1.0, } } # ========================= # RADIOMETRIC # ========================= radiometric_config = radiometric_data.get(args.radiometric_profile, {}).get("radiometric_config") if not isinstance(radiometric_config, dict): radiometric_config = cam_data.get("radiometric_config") if not isinstance(radiometric_config, dict): radiometric_config = { "enabled": True, "interval_s": 0.5, "verbose": True, "metering_mode": "global", "spectral_control_mode": "shared", "global_roi_pct": { "x0": 0.08, "y0": 0.08, "x1": 0.92, "y1": 0.92 }, "control_metric": "p50", "target_value": 0.40, "deadband": 0.04, "p95_limit": 0.94, "saturation_limit_pct": 1.0, "alpha": 0.18, "exp_step_gain": 0.55, "prefer_exposure": True, "exp_min_us": 100, "exp_max_us": 80000, "gain_min": 1.0, "gain_max": 4.0, "apply_same_spectral_to_both": True, "spectral_roles": ["re", "nir"] } # ========================= # FUSION CONFIG # ========================= fusion_config = { "alignment_mode": fusion_data.get("alignment_mode", "manual_affine"), "baseline_mm": fusion_data.get("baseline_mm", 75.0), "reference_camera": fusion_data.get("reference_camera", "rgb"), "manual_offsets": fusion_data.get("manual_offsets", {}), "homographies": fusion_data.get("homographies", {}), "crop_valid_common": fusion_data.get("crop_valid_common", True), "resize_after_crop": fusion_data.get("resize_after_crop", True), "target_size": fusion_data.get("target_size", None), } # ========================= # MODULE PARAMS FINAL # ========================= module_params = { "schema": "multispec_module_params_v3", "saved_at": now_str(), "frame_type": cam_data.get("frame_type", fusion_data.get("frame_type", "RAW_BRUTO")), "capture_mode_requested": cam_data.get("capture_mode_requested", "AUTO"), "capture_mode_effective": cam_data.get("capture_mode_effective", "AUTO"), "raw_policy": cam_data.get("raw_policy", "allow_single"), "sensor_width": cam_data.get("sensor_width", fusion_data.get("sensor_width")), "sensor_height": cam_data.get("sensor_height", fusion_data.get("sensor_height")), "bayer_pattern": cam_data.get("bayer_pattern", fusion_data.get("bayer_pattern", "GBRG")), "camera_settings": camera_settings, "fusion_config": fusion_config, "radiometric_config": radiometric_config, "rgb_calibration": rgb_calibration, "flatfield_config": flatfield_config, } with open(args.out, "w", encoding="utf-8") as f: json.dump(module_params, f, ensure_ascii=False, indent=2) print(f"[OK] module_params gerado em: {args.out}") if flatfield_config.get("enabled"): print(f"[OK] flatfield habilitado: {flatfield_config.get('npz_file')}") else: print(f"[WARN] flatfield desabilitado: {flatfield_config.get('reason')}") if __name__ == "__main__": main()