File size: 6,072 Bytes
80e070e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
"""
Band-combination sweep: train + evaluate each combo, save checkpoints, collect
results. Runs on the patches in --data-dir (default all_data/patches). Patch size
is auto-detected by train.py / evaluate.py, so the SAME runner works for the
64 px (30 m) set now and the 128 px (10 m) set later — just point --data-dir at
the new patches.

    python run_experiments.py --gpu 1
    python run_experiments.py --gpu 1 --only f17_proven,f17_standard,f17_optical
    python run_experiments.py --gpu 1 --data-dir all_data/patches --force

Each combo writes  all_data/checkpoint_<name>/  and  all_data/result_<name>/.
Already-finished combos are skipped (resume-friendly) unless --force.
At the end it prints + saves the comparison table (all_data/runs_comparison.csv).
"""

import os
import sys
import argparse
import subprocess

HERE = os.path.dirname(os.path.abspath(__file__))
RUN_GPU = os.path.join(HERE, "run_gpu.sh")

# name -> band list (None = all 35 bands). Edit / extend freely.
COMBOS = {
    # --- three 17-feature variants (the focus); first is the current best 17 ---
    "f17_proven":     ["TPI", "ROUGHNESS", "LST_winter", "NDWI", "LST_summer", "REL_RELIEF",
                       "ASPECT", "LST_anomaly", "ELEV_COP", "B2", "HILLSHADE", "B8A", "NDMI", "B12", "B3", "B4", "B8"],
    "f17_standard":   ["TPI", "SLOPE", "LST_winter", "NDVI", "LST_summer", "REL_RELIEF",
                       "ASPECT", "LST_anomaly", "ELEV_COP", "B2", "HILLSHADE", "B8A", "NDMI", "B11", "B3", "B4", "B8"],
    "f17_optical":    ["B2", "B3", "B4", "B8", "B8A", "B11", "B12", "NDVI", "NDWI", "NDMI", "NDSI", "BSI",
                       "ELEV_COP", "SLOPE", "TPI", "LST_winter", "LST_summer"],
    # --- extra combos to round out the sweep ---
    "f17_terrain":    ["TPI", "ROUGHNESS", "TRI", "SLOPE", "ASPECT", "HILLSHADE", "CURVATURE", "REL_RELIEF",
                       "ELEV_COP", "LST_winter", "LST_summer", "LST_anomaly", "B2", "B4", "B8", "NDWI", "NDVI"],
    "f17_no_thermal": ["TPI", "ROUGHNESS", "SLOPE", "ASPECT", "REL_RELIEF", "HILLSHADE", "CURVATURE",
                       "ELEV_COP", "B2", "B3", "B4", "B8", "B8A", "B11", "B12", "NDWI", "NDVI"],
    "f16_balanced":   ["TPI", "ROUGHNESS", "REL_RELIEF", "ELEV_COP", "SLOPE", "ASPECT", "LST_winter", "LST_summer",
                       "NDWI", "NDVI", "NDMI", "B2", "B3", "B4", "B8", "B12"],
    "f20_rich":       ["TPI", "ROUGHNESS", "LST_winter", "NDWI", "LST_summer", "REL_RELIEF", "ASPECT", "LST_anomaly",
                       "ELEV_COP", "B2", "HILLSHADE", "B8A", "NDMI", "B12", "B3", "B4", "B8", "NDVI", "SLOPE", "B11"],
    "f14_lean":       ["TPI", "ROUGHNESS", "LST_winter", "NDWI", "LST_summer", "REL_RELIEF", "ASPECT",
                       "ELEV_COP", "B2", "B8A", "NDMI", "B12", "B4", "B8"],
    "f12_core":       ["TPI", "ROUGHNESS", "LST_winter", "LST_summer", "REL_RELIEF", "ASPECT",
                       "ELEV_COP", "B2", "B8", "NDWI", "NDMI", "B12"],
    # --- reference baseline (auto-skips if already trained) ---
    "all":            None,
}


def dirs_for(name):
    ck = "all_data/checkpoint" if name == "all" else f"all_data/checkpoint_{name}"
    rs = "all_data/result" if name == "all" else f"all_data/result_{name}"
    return os.path.join(HERE, ck), os.path.join(HERE, rs)


def run_step(script, env, log_path):
    os.makedirs(os.path.dirname(log_path), exist_ok=True)
    with open(log_path, "w") as lf:
        r = subprocess.run([RUN_GPU, script], cwd=HERE, env=env,
                           stdout=lf, stderr=subprocess.STDOUT)
    return r.returncode


def run_one(name, bands, gpu, data_dir, force):
    ckpt_dir, result_dir = dirs_for(name)
    weights = os.path.join(ckpt_dir, "best_model.weights.h5")
    metrics = os.path.join(result_dir, "metrics.json")

    env = os.environ.copy()
    env["CUDA_VISIBLE_DEVICES"] = str(gpu)
    env["DATA_DIR"] = data_dir
    env["RUN_NAME"] = name
    env.pop("BANDS", None)
    if bands is None:
        env.pop("BAND_LIST", None)
    else:
        env["BAND_LIST"] = ",".join(bands)

    nb = "all" if bands is None else len(bands)
    if force or not os.path.exists(weights):
        print(f"\n[TRAIN] {name}  ({nb} bands)  GPU {gpu}")
        if run_step("train.py", env, os.path.join(ckpt_dir, "train_log.txt")) != 0:
            print(f"  TRAIN FAILED -> {ckpt_dir}/train_log.txt")
            return False
    else:
        print(f"\n[skip train] {name}  (weights exist)")

    if force or not os.path.exists(metrics):
        print(f"[EVAL ] {name}")
        if run_step("evaluate.py", env, os.path.join(result_dir, "eval_log.txt")) != 0:
            print(f"  EVAL FAILED -> {result_dir}/eval_log.txt")
            return False
    else:
        print(f"[skip eval ] {name}  (metrics exist)")
    return True


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--gpu", default="1", help="CUDA device index (default 1)")
    ap.add_argument("--data-dir", default="all_data/patches",
                    help="patches root (default all_data/patches)")
    ap.add_argument("--only", default=None,
                    help="comma list of combo names to run (default: all in COMBOS)")
    ap.add_argument("--force", action="store_true",
                    help="re-run even if checkpoint/metrics already exist")
    args = ap.parse_args()

    names = list(COMBOS)
    if args.only:
        wanted = [n.strip() for n in args.only.split(",")]
        bad = [n for n in wanted if n not in COMBOS]
        if bad:
            sys.exit(f"Unknown combo(s): {bad}\nAvailable: {list(COMBOS)}")
        names = wanted

    print(f"Running {len(names)} combo(s) on GPU {args.gpu}, data={args.data_dir}:")
    print("  " + ", ".join(names))
    ok, failed = [], []
    for n in names:
        (ok if run_one(n, COMBOS[n], args.gpu, args.data_dir, args.force) else failed).append(n)

    print(f"\nDone. ok={ok}  failed={failed}")
    print("\n" + "=" * 60 + "\n  RESULTS\n" + "=" * 60)
    subprocess.run([sys.executable, os.path.join(HERE, "results_table.py")], cwd=HERE)


if __name__ == "__main__":
    main()