import argparse import json import logging from pathlib import Path import torch import yaml from train.test import infer_deepchoice, test_with_baselines from train.train import build_model, build_split_loader, list_split_batches, synchronize_model_dims, train_deepchoice logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") def _load_config(config_path): cfg = yaml.safe_load(Path(config_path).read_text()) return cfg def _to_jsonable(value): if isinstance(value, dict): return {key: _to_jsonable(item) for key, item in value.items()} if isinstance(value, (list, tuple)): return [_to_jsonable(item) for item in value] if hasattr(value, "tolist"): return value.tolist() return value def main(): parser = argparse.ArgumentParser() parser.add_argument("--config", type=Path, default="configs/config_deepchoice_base.yaml") parser.add_argument("--mode", choices=["train", "test", "infer"], default="train") parser.add_argument("--weights_path", default=None) parser.add_argument("--split", default=None) parser.add_argument("--limit_files", type=int, default=None) parser.add_argument("--inference_output", default=None) parser.add_argument("--metrics_output", default=None) parser.add_argument("--comparison_config", default=None) parser.add_argument("--comparison_weights_path", default=None) parser.add_argument("--comparison_field_name", default="best_mlp") args = parser.parse_args() cfg = _load_config(args.config) synchronize_model_dims(cfg) if args.mode == "train": train_deepchoice(cfg, cfg["training"]["output_dir"]) return model = build_model(cfg) if args.weights_path is None: raise ValueError("--weights_path is required for --mode test and --mode infer") weights_path = args.weights_path state_dict = torch.load(weights_path, map_location=cfg["training"]["device"], weights_only=False) model.load_state_dict(state_dict) comparison_model = None comparison_cfg = None if args.comparison_config is not None and args.comparison_weights_path is not None: comparison_cfg = _load_config(args.comparison_config) synchronize_model_dims(comparison_cfg) comparison_model = build_model(comparison_cfg) comparison_state_dict = torch.load( args.comparison_weights_path, map_location=comparison_cfg["training"]["device"], weights_only=False, ) comparison_model.load_state_dict(comparison_state_dict) split_name = args.split or cfg["test"]["split"] split_paths = list_split_batches(cfg, split_name, limit=args.limit_files) if not split_paths: raise FileNotFoundError(f"No test batches found for split '{split_name}' in {cfg['data']['batches_root']}") loader = build_split_loader( cfg, split_name, shuffle=False, limit=args.limit_files, file_batch_size=cfg.get("test", {}).get("file_batch_size", 1), ) if args.mode == "test": metrics = test_with_baselines(model, loader, cfg, n_classes=int(cfg["model"]["num_classes"])) logging.info( "Test split=%s | files=%s | loss=%.4f | mIoU=%.4f | mF1=%.4f | majority mIoU=%.4f mF1=%.4f | hard_vote mIoU=%.4f mF1=%.4f | anyview mIoU=%.4f mF1=%.4f | IoUs=%s", split_name, len(split_paths), metrics["loss"], metrics["miou"], metrics["mf1"], metrics["baselines"]["majority"]["miou"], metrics["baselines"]["majority"]["mf1"], metrics["baselines"]["hard_vote"]["miou"], metrics["baselines"]["hard_vote"]["mf1"], metrics["baselines"]["anyview"]["miou"], metrics["baselines"]["anyview"]["mf1"], metrics["ious"], ) if args.metrics_output is not None: metrics_path = Path(args.metrics_output) metrics_path.parent.mkdir(parents=True, exist_ok=True) metrics_path.write_text(json.dumps(_to_jsonable(metrics), indent=2), encoding="utf-8") return inference_output = args.inference_output or str(Path(cfg["training"]["output_dir"]) / f"inference_{split_name}") result = infer_deepchoice( model, loader, cfg, inference_output, comparison_model=comparison_model, comparison_config=comparison_cfg, comparison_field_name=args.comparison_field_name, ) logging.info( "Inference split=%s | files=%s | samples=%s | accuracy=%.4f | model mIoU=%.4f mF1=%.4f | majority mIoU=%.4f mF1=%.4f | hard_vote mIoU=%.4f mF1=%.4f | anyview mIoU=%.4f mF1=%.4f | output=%s | las_tiles=%s", split_name, len(split_paths), result["num_samples"], result["accuracy"], result["metrics"]["model"]["miou"], result["metrics"]["model"]["mf1"], result["metrics"]["majority"]["miou"], result["metrics"]["majority"]["mf1"], result["metrics"]["hard_vote"]["miou"], result["metrics"]["hard_vote"]["mf1"], result["metrics"]["anyview"]["miou"], result["metrics"]["anyview"]["mf1"], inference_output, len(result["las_paths"]), ) if args.metrics_output is not None: metrics_path = Path(args.metrics_output) metrics_path.parent.mkdir(parents=True, exist_ok=True) metrics_path.write_text(json.dumps(_to_jsonable(result), indent=2), encoding="utf-8") if __name__ == "__main__": main()