fimeval 0.1.56__py3-none-any.whl → 0.1.57__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fimeval/BenchFIMQuery/__init__.py +5 -0
- fimeval/BenchFIMQuery/access_benchfim.py +761 -0
- fimeval/BenchFIMQuery/utilis.py +269 -0
- fimeval/BuildingFootprint/microsoftBF.py +2 -0
- fimeval/ContingencyMap/evaluationFIM.py +93 -53
- fimeval/__init__.py +4 -0
- fimeval/setup_benchFIM.py +39 -0
- fimeval/utilis.py +49 -0
- {fimeval-0.1.56.dist-info → fimeval-0.1.57.dist-info}/METADATA +34 -16
- fimeval-0.1.57.dist-info/RECORD +21 -0
- fimeval-0.1.56.dist-info/RECORD +0 -17
- {fimeval-0.1.56.dist-info → fimeval-0.1.57.dist-info}/WHEEL +0 -0
- {fimeval-0.1.56.dist-info → fimeval-0.1.57.dist-info}/licenses/LICENSE.txt +0 -0
- {fimeval-0.1.56.dist-info → fimeval-0.1.57.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This utility function contains how to retrieve all the necessary metadata of benchmark FIM
|
|
3
|
+
from the s3 bucket during benchmark FIM querying.
|
|
4
|
+
|
|
5
|
+
Authors: Supath Dhital, sdhital@crimson.ua.edu
|
|
6
|
+
Updated date: 25 Nov, 2025
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
import os, re, json, datetime as dt
|
|
11
|
+
from typing import List, Dict, Any, Optional
|
|
12
|
+
|
|
13
|
+
import urllib.parse
|
|
14
|
+
import boto3
|
|
15
|
+
from botocore import UNSIGNED
|
|
16
|
+
from botocore.config import Config
|
|
17
|
+
|
|
18
|
+
# constants
|
|
19
|
+
BUCKET = "sdmlab"
|
|
20
|
+
CATALOG_KEY = (
|
|
21
|
+
"FIM_Database/FIM_Viz/catalog_core.json" # Path of the json file in the s3 bucket
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# s3 client
|
|
25
|
+
_S3 = boto3.client("s3", config=Config(signature_version=UNSIGNED))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# helpers for direct S3 file links
|
|
29
|
+
def s3_http_url(bucket: str, key: str) -> str:
|
|
30
|
+
"""Build a public-style S3 HTTPS URL."""
|
|
31
|
+
return f"https://{bucket}.s3.amazonaws.com/{urllib.parse.quote(key, safe='/')}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# utils
|
|
35
|
+
_YMD_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
36
|
+
_YMD_COMPACT_RE = re.compile(r"^\d{8}$")
|
|
37
|
+
_YMDH_RE = re.compile(r"^\d{4}-\d{2}-\d{2}[ T]\d{2}$")
|
|
38
|
+
_YMDHMS_RE = re.compile(r"^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2})?$")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _normalize_user_dt(s: str) -> str:
|
|
42
|
+
s = s.strip()
|
|
43
|
+
s = s.replace("/", "-")
|
|
44
|
+
s = re.sub(r"\s+", " ", s)
|
|
45
|
+
return s
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _to_date(s: str) -> dt.date:
|
|
49
|
+
s = _normalize_user_dt(s)
|
|
50
|
+
if _YMD_COMPACT_RE.match(s):
|
|
51
|
+
return dt.datetime.strptime(s, "%Y%m%d").date()
|
|
52
|
+
if _YMD_RE.match(s):
|
|
53
|
+
return dt.date.fromisoformat(s)
|
|
54
|
+
try:
|
|
55
|
+
return dt.datetime.fromisoformat(s).date()
|
|
56
|
+
except Exception:
|
|
57
|
+
m = re.match(r"^(\d{4}-\d{2}-\d{2})[ T](\d{2})$", s)
|
|
58
|
+
if m:
|
|
59
|
+
return dt.datetime.fromisoformat(f"{m.group(1)} {m.group(2)}:00:00").date()
|
|
60
|
+
raise ValueError(f"Bad date format: {s}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _to_hour_or_none(s: str) -> Optional[int]:
|
|
64
|
+
s = _normalize_user_dt(s)
|
|
65
|
+
if _YMD_RE.match(s) or _YMD_COMPACT_RE.match(s):
|
|
66
|
+
return None
|
|
67
|
+
m = re.match(r"^\d{4}-\d{2}-\d{2}[ T](\d{2})$", s)
|
|
68
|
+
if m:
|
|
69
|
+
return int(m.group(1))
|
|
70
|
+
try:
|
|
71
|
+
dt_obj = dt.datetime.fromisoformat(s)
|
|
72
|
+
return dt_obj.hour
|
|
73
|
+
except Exception:
|
|
74
|
+
m2 = re.match(r"^\d{4}-\d{2}-\d{2}T(\d{2})$", s)
|
|
75
|
+
if m2:
|
|
76
|
+
return int(m2.group(1))
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _record_day(rec: Dict[str, Any]) -> Optional[dt.date]:
|
|
81
|
+
ymd = rec.get("date_ymd")
|
|
82
|
+
if isinstance(ymd, str):
|
|
83
|
+
try:
|
|
84
|
+
return dt.date.fromisoformat(ymd)
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
raw = rec.get("date_of_flood")
|
|
88
|
+
if isinstance(raw, str) and len(raw) >= 8:
|
|
89
|
+
try:
|
|
90
|
+
return dt.datetime.strptime(raw[:8], "%Y%m%d").date()
|
|
91
|
+
except Exception:
|
|
92
|
+
return None
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _record_hour_or_none(rec: Dict[str, Any]) -> Optional[int]:
|
|
97
|
+
raw = rec.get("date_of_flood")
|
|
98
|
+
if isinstance(raw, str) and "T" in raw and len(raw) >= 11:
|
|
99
|
+
try:
|
|
100
|
+
return int(raw.split("T", 1)[1][:2])
|
|
101
|
+
except Exception:
|
|
102
|
+
return None
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Printing helpers
|
|
107
|
+
def _pretty_date_for_print(rec: Dict[str, Any]) -> str:
|
|
108
|
+
raw = rec.get("date_of_flood")
|
|
109
|
+
if isinstance(raw, str) and "T" in raw and len(raw) >= 11:
|
|
110
|
+
return f"{raw[:4]}-{raw[4:6]}-{raw[6:8]}T{raw.split('T',1)[1][:2]}"
|
|
111
|
+
ymd = rec.get("date_ymd")
|
|
112
|
+
if isinstance(ymd, str) and _YMD_RE.match(ymd):
|
|
113
|
+
return ymd
|
|
114
|
+
if isinstance(raw, str) and len(raw) >= 8:
|
|
115
|
+
return f"{raw[:4]}-{raw[4:6]}-{raw[6:8]}"
|
|
116
|
+
return "unknown"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _context_str(
|
|
120
|
+
huc8: Optional[str] = None,
|
|
121
|
+
date_input: Optional[str] = None,
|
|
122
|
+
file_name: Optional[str] = None,
|
|
123
|
+
start_date: Optional[str] = None,
|
|
124
|
+
end_date: Optional[str] = None,
|
|
125
|
+
) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Builds a readable context summary for printing headers.
|
|
128
|
+
Example outputs:
|
|
129
|
+
- "HUC 12090301"
|
|
130
|
+
- "HUC 12090301, date '2017-08-30'"
|
|
131
|
+
- "HUC 12090301, range 2017-08-30 to 2017-09-01"
|
|
132
|
+
- "HUC 12090301, file 'PSS_3_0m_20170830T162251_BM.tif'"
|
|
133
|
+
"""
|
|
134
|
+
parts = []
|
|
135
|
+
if huc8:
|
|
136
|
+
parts.append(f"HUC {huc8}")
|
|
137
|
+
if date_input:
|
|
138
|
+
parts.append(f"date '{date_input}'")
|
|
139
|
+
if start_date or end_date:
|
|
140
|
+
if start_date and end_date:
|
|
141
|
+
parts.append(f"range {start_date} to {end_date}")
|
|
142
|
+
elif start_date:
|
|
143
|
+
parts.append(f"from {start_date}")
|
|
144
|
+
elif end_date:
|
|
145
|
+
parts.append(f"until {end_date}")
|
|
146
|
+
if file_name:
|
|
147
|
+
parts.append(f"file '{file_name}'")
|
|
148
|
+
|
|
149
|
+
return ", ".join(parts) if parts else "your filters"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def format_records_for_print(
|
|
153
|
+
records: List[Dict[str, Any]], context: Optional[str] = None
|
|
154
|
+
) -> str:
|
|
155
|
+
if not records:
|
|
156
|
+
ctx = context or "your filters"
|
|
157
|
+
return f"Benchmark FIMs were not matched for {ctx}."
|
|
158
|
+
|
|
159
|
+
header = (
|
|
160
|
+
f"Following are the available benchmark data for {context}:\n"
|
|
161
|
+
if context
|
|
162
|
+
else ""
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
def _is_synthetic_tier_local(r: Dict[str, Any]) -> bool:
|
|
166
|
+
t = str(r.get("tier") or r.get("quality") or "").lower()
|
|
167
|
+
return "tier_4" in t or t.strip() == "4"
|
|
168
|
+
|
|
169
|
+
def _return_period_text_local(r: Dict[str, Any]) -> str:
|
|
170
|
+
rp = (
|
|
171
|
+
r.get("return_period")
|
|
172
|
+
or r.get("return_period_yr")
|
|
173
|
+
or r.get("rp")
|
|
174
|
+
or r.get("rp_years")
|
|
175
|
+
)
|
|
176
|
+
if rp is None:
|
|
177
|
+
return "synthetic flow (return period unknown)"
|
|
178
|
+
try:
|
|
179
|
+
rp_int = int(float(str(rp).strip().replace("yr", "").replace("-year", "")))
|
|
180
|
+
return f"{rp_int}-year synthetic flow"
|
|
181
|
+
except Exception:
|
|
182
|
+
return f"{rp} synthetic flow"
|
|
183
|
+
|
|
184
|
+
blocks: List[str] = []
|
|
185
|
+
for r in records:
|
|
186
|
+
tier = r.get("tier") or r.get("quality") or "Unknown"
|
|
187
|
+
res = r.get("resolution_m")
|
|
188
|
+
res_txt = f"{res}m" if res is not None else "NA"
|
|
189
|
+
fname = r.get("file_name") or "NA"
|
|
190
|
+
|
|
191
|
+
# Build lines with Tier-aware event text
|
|
192
|
+
lines = [f"Data Tier: {tier}"]
|
|
193
|
+
if _is_synthetic_tier_local(r):
|
|
194
|
+
lines.append(f"Return Period: {_return_period_text_local(r)}")
|
|
195
|
+
else:
|
|
196
|
+
date_str = _pretty_date_for_print(r)
|
|
197
|
+
lines.append(f"Benchmark FIM date: {date_str}")
|
|
198
|
+
|
|
199
|
+
lines.extend([
|
|
200
|
+
f"Spatial Resolution: {res_txt}",
|
|
201
|
+
f"Benchmark FIM raster name in DB: {fname}",
|
|
202
|
+
])
|
|
203
|
+
blocks.append("\n".join(lines))
|
|
204
|
+
|
|
205
|
+
return (header + "\n\n".join(blocks)).strip()
|
|
206
|
+
|
|
207
|
+
# S3 and json catalog
|
|
208
|
+
def load_catalog_core() -> Dict[str, Any]:
|
|
209
|
+
obj = _S3.get_object(Bucket=BUCKET, Key=CATALOG_KEY)
|
|
210
|
+
return json.loads(obj["Body"].read().decode("utf-8", "replace"))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _list_prefix(prefix: str) -> List[str]:
|
|
214
|
+
keys: List[str] = []
|
|
215
|
+
paginator = _S3.get_paginator("list_objects_v2")
|
|
216
|
+
for page in paginator.paginate(Bucket=BUCKET, Prefix=prefix):
|
|
217
|
+
for obj in page.get("Contents", []) or []:
|
|
218
|
+
keys.append(obj["Key"])
|
|
219
|
+
return keys
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _download(bucket: str, key: str, dest_path: str) -> str:
|
|
223
|
+
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
|
|
224
|
+
_S3.download_file(bucket, key, dest_path)
|
|
225
|
+
return dest_path
|
|
226
|
+
|
|
227
|
+
# Get the files from s3 bucket
|
|
228
|
+
def _folder_from_record(rec: Dict[str, Any]) -> str:
|
|
229
|
+
s3_key = rec.get("s3_key")
|
|
230
|
+
if not s3_key or "/" not in s3_key:
|
|
231
|
+
raise ValueError("Record lacks s3_key to derive folder")
|
|
232
|
+
return s3_key.rsplit("/", 1)[0] + "/"
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _tif_key_from_record(rec: Dict[str, Any]) -> Optional[str]:
|
|
236
|
+
tif_url = rec.get("tif_url")
|
|
237
|
+
if isinstance(tif_url, str) and ".amazonaws.com/" in tif_url:
|
|
238
|
+
return tif_url.split(".amazonaws.com/", 1)[1]
|
|
239
|
+
fname = rec.get("file_name")
|
|
240
|
+
if not fname:
|
|
241
|
+
return None
|
|
242
|
+
return _folder_from_record(rec) + fname
|
|
243
|
+
|
|
244
|
+
#Download that tif and the boundary file --> need to add building footprint automation as well.
|
|
245
|
+
def download_fim_assets(record: Dict[str, Any], dest_dir: str) -> Dict[str, Any]:
|
|
246
|
+
"""
|
|
247
|
+
Download the .tif (if present) and any .gpkg from the record's folder to dest_dir.
|
|
248
|
+
"""
|
|
249
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
250
|
+
out = {"tif": None, "gpkg_files": []}
|
|
251
|
+
|
|
252
|
+
# TIF
|
|
253
|
+
tif_key = _tif_key_from_record(record)
|
|
254
|
+
if tif_key:
|
|
255
|
+
local = os.path.join(dest_dir, os.path.basename(tif_key))
|
|
256
|
+
if not os.path.exists(local):
|
|
257
|
+
_download(BUCKET, tif_key, local)
|
|
258
|
+
out["tif"] = local
|
|
259
|
+
|
|
260
|
+
# GPKGs (list folder)
|
|
261
|
+
folder = _folder_from_record(record)
|
|
262
|
+
for key in _list_prefix(folder):
|
|
263
|
+
if key.lower().endswith(".gpkg"):
|
|
264
|
+
local = os.path.join(dest_dir, os.path.basename(key))
|
|
265
|
+
if not os.path.exists(local):
|
|
266
|
+
_download(BUCKET, key, local)
|
|
267
|
+
out["gpkg_files"].append(local)
|
|
268
|
+
|
|
269
|
+
return out
|
|
@@ -130,3 +130,5 @@ def BuildingFootprintwithISO(countryISO, ROI, out_dir, geeprojectID=None):
|
|
|
130
130
|
getBuildingFootprintSpark(
|
|
131
131
|
countryISO, ROI, out_dir, tile_size=0.05, projectID=geeprojectID
|
|
132
132
|
)
|
|
133
|
+
|
|
134
|
+
BuildingFootprintwithISO("USA", "/Users/supath/Downloads/S1A_9_6m_20190530T23573_910244W430506N_AOI.gpkg", "/Users/supath/Downloads/AOI", geeprojectID="supathdh")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
import numpy as np
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
import geopandas as gpd
|
|
@@ -12,6 +13,8 @@ from rasterio.io import MemoryFile
|
|
|
12
13
|
from rasterio import features
|
|
13
14
|
from rasterio.mask import mask
|
|
14
15
|
|
|
16
|
+
os.environ["CHECK_DISK_FREE_SPACE"] = "NO"
|
|
17
|
+
|
|
15
18
|
import warnings
|
|
16
19
|
|
|
17
20
|
warnings.filterwarnings("ignore", category=rasterio.errors.ShapeSkipWarning)
|
|
@@ -19,7 +22,8 @@ warnings.filterwarnings("ignore", category=rasterio.errors.ShapeSkipWarning)
|
|
|
19
22
|
from .methods import AOI, smallest_extent, convex_hull, get_smallest_raster_path
|
|
20
23
|
from .metrics import evaluationmetrics
|
|
21
24
|
from .PWBs3 import get_PWB
|
|
22
|
-
from ..utilis import MakeFIMsUniform
|
|
25
|
+
from ..utilis import MakeFIMsUniform, benchmark_name, find_best_boundary
|
|
26
|
+
from ..setup_benchFIM import ensure_benchmark
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
# giving the permission to the folder
|
|
@@ -98,20 +102,18 @@ def evaluateFIM(
|
|
|
98
102
|
|
|
99
103
|
# If method is AOI, and direct shapefile directory is not provided, then it will search for the shapefile in the folder
|
|
100
104
|
if method.__name__ == "AOI":
|
|
101
|
-
#
|
|
105
|
+
# Ubest-matching boundary file, prefer .gpkg from benchFIM downloads
|
|
102
106
|
if shapefile is None:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if file.lower().endswith(ext):
|
|
106
|
-
shapefile = os.path.join(folder, file)
|
|
107
|
-
print(f"Auto-detected shapefile: {shapefile}")
|
|
108
|
-
break
|
|
109
|
-
if shapefile:
|
|
110
|
-
break
|
|
111
|
-
if shapefile is None:
|
|
107
|
+
shapefile_path = find_best_boundary(Path(folder), Path(benchmark_path))
|
|
108
|
+
if shapefile_path is None:
|
|
112
109
|
raise FileNotFoundError(
|
|
113
|
-
"No
|
|
110
|
+
f"No boundary file (.gpkg, .shp, .geojson, .kml) found in {folder}. "
|
|
111
|
+
"Either provide a shapefile path or place a boundary file in the folder."
|
|
114
112
|
)
|
|
113
|
+
shapefile = str(shapefile_path)
|
|
114
|
+
else:
|
|
115
|
+
shapefile = str(shapefile)
|
|
116
|
+
|
|
115
117
|
# Run AOI with the found or provided shapefile
|
|
116
118
|
bounding_geom = AOI(benchmark_path, shapefile, save_dir)
|
|
117
119
|
|
|
@@ -277,8 +279,8 @@ def evaluateFIM(
|
|
|
277
279
|
out_transform1,
|
|
278
280
|
)
|
|
279
281
|
merged = out_image1 + out_image2_resized
|
|
280
|
-
merged[merged==7] = 5
|
|
281
|
-
|
|
282
|
+
merged[merged == 7] = 5
|
|
283
|
+
|
|
282
284
|
# Get Evaluation Metrics
|
|
283
285
|
(
|
|
284
286
|
unique_values,
|
|
@@ -392,13 +394,17 @@ def safe_delete_folder(folder_path):
|
|
|
392
394
|
|
|
393
395
|
def EvaluateFIM(
|
|
394
396
|
main_dir,
|
|
395
|
-
method_name,
|
|
396
|
-
output_dir,
|
|
397
|
+
method_name=None,
|
|
398
|
+
output_dir=None,
|
|
397
399
|
PWB_dir=None,
|
|
398
400
|
shapefile_dir=None,
|
|
399
401
|
target_crs=None,
|
|
400
402
|
target_resolution=None,
|
|
403
|
+
benchmark_dict=None,
|
|
401
404
|
):
|
|
405
|
+
if output_dir is None:
|
|
406
|
+
output_dir = os.path.join(os.getcwd(), "Evaluation_Results")
|
|
407
|
+
|
|
402
408
|
main_dir = Path(main_dir)
|
|
403
409
|
# Read the permanent water bodies
|
|
404
410
|
if PWB_dir is None:
|
|
@@ -414,32 +420,46 @@ def EvaluateFIM(
|
|
|
414
420
|
benchmark_path = None
|
|
415
421
|
candidate_path = []
|
|
416
422
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
candidate_path.append(tif_file)
|
|
423
|
+
for tif_file in tif_files:
|
|
424
|
+
if benchmark_name(tif_file):
|
|
425
|
+
benchmark_path = tif_file
|
|
426
|
+
else:
|
|
427
|
+
candidate_path.append(tif_file)
|
|
423
428
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
429
|
+
if benchmark_path and candidate_path:
|
|
430
|
+
if method_name is None:
|
|
431
|
+
local_method = "AOI"
|
|
432
|
+
|
|
433
|
+
#For single case, if user have explicitly send boundary, use that, else use the boundary from the benchmark FIM evaluation
|
|
434
|
+
if shapefile_dir is not None:
|
|
435
|
+
local_shapefile = shapefile_dir
|
|
428
436
|
else:
|
|
429
|
-
|
|
437
|
+
boundary = find_best_boundary(folder_dir, benchmark_path)
|
|
438
|
+
if boundary is None:
|
|
439
|
+
print(
|
|
440
|
+
f"Skipping {folder_dir.name}: no boundary file found "
|
|
441
|
+
f"and method_name is None (auto-AOI)."
|
|
442
|
+
)
|
|
443
|
+
return
|
|
444
|
+
local_shapefile = str(boundary)
|
|
445
|
+
else:
|
|
446
|
+
local_method = method_name
|
|
447
|
+
local_shapefile = shapefile_dir
|
|
430
448
|
|
|
431
|
-
if benchmark_path and candidate_path:
|
|
432
449
|
print(f"**Flood Inundation Evaluation of {folder_dir.name}**")
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
450
|
+
try:
|
|
451
|
+
Metrics = evaluateFIM(
|
|
452
|
+
benchmark_path,
|
|
453
|
+
candidate_path,
|
|
454
|
+
gdf,
|
|
455
|
+
folder_dir,
|
|
456
|
+
local_method,
|
|
457
|
+
output_dir,
|
|
458
|
+
shapefile=local_shapefile,
|
|
459
|
+
)
|
|
460
|
+
print("\n", Metrics, "\n")
|
|
461
|
+
except Exception as e:
|
|
462
|
+
print(f"Error evaluating {folder_dir.name}: {e}")
|
|
443
463
|
else:
|
|
444
464
|
print(
|
|
445
465
|
f"Skipping {folder_dir.name} as it doesn't have a valid benchmark and candidate configuration."
|
|
@@ -448,34 +468,54 @@ def EvaluateFIM(
|
|
|
448
468
|
# Check if main_dir directly contains tif files
|
|
449
469
|
TIFFfiles_main_dir = list(main_dir.glob("*.tif"))
|
|
450
470
|
if TIFFfiles_main_dir:
|
|
451
|
-
|
|
452
|
-
|
|
471
|
+
|
|
472
|
+
# Ensure benchmark is present if needed
|
|
473
|
+
TIFFfiles_main_dir = ensure_benchmark(
|
|
474
|
+
main_dir, TIFFfiles_main_dir, benchmark_dict
|
|
453
475
|
)
|
|
454
476
|
|
|
455
|
-
# processing folder
|
|
456
477
|
processing_folder = main_dir / "processing"
|
|
457
|
-
|
|
478
|
+
try:
|
|
479
|
+
MakeFIMsUniform(
|
|
480
|
+
main_dir, target_crs=target_crs, target_resolution=target_resolution
|
|
481
|
+
)
|
|
458
482
|
|
|
459
|
-
|
|
460
|
-
|
|
483
|
+
# processing folder
|
|
484
|
+
TIFFfiles = list(processing_folder.glob("*.tif"))
|
|
485
|
+
|
|
486
|
+
process_TIFF(TIFFfiles, main_dir)
|
|
487
|
+
except Exception as e:
|
|
488
|
+
print(f"Error processing {main_dir}: {e}")
|
|
489
|
+
finally:
|
|
490
|
+
safe_delete_folder(processing_folder)
|
|
461
491
|
else:
|
|
462
492
|
for folder in main_dir.iterdir():
|
|
463
493
|
if folder.is_dir():
|
|
464
494
|
tif_files = list(folder.glob("*.tif"))
|
|
465
495
|
|
|
466
496
|
if tif_files:
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
497
|
+
processing_folder = folder / "processing"
|
|
498
|
+
try:
|
|
499
|
+
# Ensure benchmark is present if needed
|
|
500
|
+
tif_files = ensure_benchmark(
|
|
501
|
+
folder, tif_files, benchmark_dict
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
MakeFIMsUniform(
|
|
505
|
+
folder,
|
|
506
|
+
target_crs=target_crs,
|
|
507
|
+
target_resolution=target_resolution,
|
|
508
|
+
)
|
|
472
509
|
|
|
473
|
-
|
|
474
|
-
TIFFfiles = list(processing_folder.glob("*.tif"))
|
|
510
|
+
TIFFfiles = list(processing_folder.glob("*.tif"))
|
|
475
511
|
|
|
476
|
-
|
|
477
|
-
|
|
512
|
+
process_TIFF(TIFFfiles, folder)
|
|
513
|
+
except Exception as e:
|
|
514
|
+
print(f"Error processing folder {folder.name}: {e}")
|
|
515
|
+
finally:
|
|
516
|
+
safe_delete_folder(processing_folder)
|
|
478
517
|
else:
|
|
479
518
|
print(
|
|
480
519
|
f"Skipping {folder.name} as it doesn't contain any tif files."
|
|
481
520
|
)
|
|
521
|
+
|
fimeval/__init__.py
CHANGED
|
@@ -10,6 +10,9 @@ from .utilis import compress_tif_lzw
|
|
|
10
10
|
# Evaluation with Building foorprint module
|
|
11
11
|
from .BuildingFootprint.evaluationwithBF import EvaluationWithBuildingFootprint
|
|
12
12
|
|
|
13
|
+
#Access benchmark FIM module
|
|
14
|
+
from .BenchFIMQuery.access_benchfim import benchFIMquery
|
|
15
|
+
|
|
13
16
|
__all__ = [
|
|
14
17
|
"EvaluateFIM",
|
|
15
18
|
"PrintContingencyMap",
|
|
@@ -17,4 +20,5 @@ __all__ = [
|
|
|
17
20
|
"get_PWB",
|
|
18
21
|
"EvaluationWithBuildingFootprint",
|
|
19
22
|
"compress_tif_lzw",
|
|
23
|
+
"benchFIMquery",
|
|
20
24
|
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This code setup all the case folders whether it has valid benchmark FIM/ which benchmark need to access from catalog and so on.
|
|
3
|
+
Basically It will do everything before going into the actual evaluation process.
|
|
4
|
+
Author: Supath Dhital
|
|
5
|
+
Date updated: 25 Nov, 2025
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .BenchFIMQuery.access_benchfim import benchFIMquery
|
|
10
|
+
from .utilis import benchmark_name
|
|
11
|
+
|
|
12
|
+
def ensure_benchmark(folder_dir, tif_files, benchmark_map):
|
|
13
|
+
"""
|
|
14
|
+
If no local benchmark is found in `tif_files`, and `folder_dir.name`
|
|
15
|
+
exists in `benchmark_map`, download it into this folder using benchFIMquery.
|
|
16
|
+
Returns an updated list of tif files.
|
|
17
|
+
"""
|
|
18
|
+
folder_dir = Path(folder_dir)
|
|
19
|
+
|
|
20
|
+
# If a benchmark/BM tif is already present, just use existing files
|
|
21
|
+
has_benchmark = any(benchmark_name(f) for f in tif_files)
|
|
22
|
+
if has_benchmark or not benchmark_map:
|
|
23
|
+
return tif_files
|
|
24
|
+
|
|
25
|
+
# If folder not in mapping, do nothing
|
|
26
|
+
folder_key = folder_dir.name
|
|
27
|
+
file_name = benchmark_map.get(folder_key)
|
|
28
|
+
if not file_name:
|
|
29
|
+
return tif_files
|
|
30
|
+
|
|
31
|
+
# Download benchmark FIM by filename into this folder
|
|
32
|
+
benchFIMquery(
|
|
33
|
+
file_name=file_name,
|
|
34
|
+
download=True,
|
|
35
|
+
out_dir=str(folder_dir),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Return refreshed tif list
|
|
39
|
+
return list(folder_dir.glob("*.tif"))
|
fimeval/utilis.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
import shutil
|
|
3
4
|
import pyproj
|
|
4
5
|
import rasterio
|
|
@@ -182,3 +183,51 @@ def MakeFIMsUniform(fim_dir, target_crs=None, target_resolution=None):
|
|
|
182
183
|
resample_to_resolution(str(src_path), coarsest_x, coarsest_y)
|
|
183
184
|
else:
|
|
184
185
|
print("All rasters already have the same resolution. No resampling needed.")
|
|
186
|
+
|
|
187
|
+
#Function to find the best boundary file in the folder if multiple boundary files are present
|
|
188
|
+
def find_best_boundary(folder: Path, benchmark_path: Path):
|
|
189
|
+
"""
|
|
190
|
+
Choose the best boundary file in `folder`:
|
|
191
|
+
- prefer .gpkg (from benchFIM downloads),
|
|
192
|
+
- otherwise, pick the file with the most name tokens in common with the benchmark.
|
|
193
|
+
"""
|
|
194
|
+
exts = [".gpkg", ".shp", ".geojson", ".kml"]
|
|
195
|
+
candidates = []
|
|
196
|
+
for ext in exts:
|
|
197
|
+
candidates.extend(folder.glob(f"*{ext}"))
|
|
198
|
+
|
|
199
|
+
if not candidates:
|
|
200
|
+
return None
|
|
201
|
+
if len(candidates) == 1:
|
|
202
|
+
print(f"Auto-detected boundary: {candidates[0]}")
|
|
203
|
+
return candidates[0]
|
|
204
|
+
|
|
205
|
+
bench_tokens = set(
|
|
206
|
+
t for t in re.split(r"[_\-\.\s]+", benchmark_path.stem.lower()) if t
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def score(path: Path):
|
|
210
|
+
name_tokens = set(
|
|
211
|
+
t for t in re.split(r"[_\-\.\s]+", path.stem.lower()) if t
|
|
212
|
+
)
|
|
213
|
+
common = len(bench_tokens & name_tokens)
|
|
214
|
+
bonus = 1 if path.suffix.lower() == ".gpkg" else 0
|
|
215
|
+
return (common, bonus)
|
|
216
|
+
|
|
217
|
+
best = max(candidates, key=score)
|
|
218
|
+
print(f"Auto-detected boundary (best match to benchmark): {best}")
|
|
219
|
+
return best
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
#To test whether the tif is benchmark or not
|
|
223
|
+
def benchmark_name(f: Path) -> bool:
|
|
224
|
+
name = f.stem.lower()
|
|
225
|
+
|
|
226
|
+
# Explicit word
|
|
227
|
+
if "benchmark" in name:
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
# Treating underscores/dashes/dots as separators and look for a 'bm' token
|
|
231
|
+
tokens = re.split(r"[_\-\.\s]+", name)
|
|
232
|
+
return "bm" in tokens
|
|
233
|
+
|