fimeval 0.1.43__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/BuildingFootprint/__init__.py +3 -0
- fimeval/BuildingFootprint/evaluationwithBF.py +399 -0
- fimeval/ContingencyMap/PWBs3.py +41 -0
- fimeval/ContingencyMap/__init__.py +6 -0
- fimeval/ContingencyMap/evaluationFIM.py +413 -0
- fimeval/ContingencyMap/methods.py +94 -0
- fimeval/ContingencyMap/metrics.py +44 -0
- fimeval/ContingencyMap/plotevaluationmetrics.py +102 -0
- fimeval/ContingencyMap/printcontingency.py +144 -0
- fimeval/__init__.py +11 -0
- fimeval/utilis.py +182 -0
- fimeval-0.1.43.dist-info/LICENSE.txt +661 -0
- fimeval-0.1.43.dist-info/METADATA +184 -0
- fimeval-0.1.43.dist-info/RECORD +15 -0
- fimeval-0.1.43.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import glob
|
|
3
|
+
import rasterio
|
|
4
|
+
import numpy as np
|
|
5
|
+
from pyproj import Transformer
|
|
6
|
+
from matplotlib.patches import Patch
|
|
7
|
+
from matplotlib import colors as mcolors
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def getContingencyMap(raster_path, method_path):
|
|
12
|
+
# Load the raster
|
|
13
|
+
with rasterio.open(raster_path) as src:
|
|
14
|
+
band1 = src.read(1)
|
|
15
|
+
transform = src.transform
|
|
16
|
+
src_crs = src.crs
|
|
17
|
+
nodata_value = src.nodatavals[0] if src.nodatavals else None
|
|
18
|
+
combined_flood = np.full_like(band1, fill_value=1, dtype=int)
|
|
19
|
+
|
|
20
|
+
# Map pixel values to colors
|
|
21
|
+
combined_flood[band1 == -1] = 0
|
|
22
|
+
combined_flood[band1 == 0] = 1
|
|
23
|
+
combined_flood[band1 == 1] = 2
|
|
24
|
+
combined_flood[band1 == 2] = 3
|
|
25
|
+
combined_flood[band1 == 3] = 4
|
|
26
|
+
combined_flood[band1 == 4] = 5
|
|
27
|
+
|
|
28
|
+
# Handle NoData explicitly, mapping it to "No Data" class (1)
|
|
29
|
+
if nodata_value is not None:
|
|
30
|
+
combined_flood[band1 == nodata_value] = 1
|
|
31
|
+
|
|
32
|
+
rows, cols = np.indices(band1.shape)
|
|
33
|
+
xs, ys = rasterio.transform.xy(transform, rows, cols)
|
|
34
|
+
xs = np.array(xs)
|
|
35
|
+
ys = np.array(ys)
|
|
36
|
+
|
|
37
|
+
dst_crs = "EPSG:4326"
|
|
38
|
+
transformer = Transformer.from_crs(src_crs, dst_crs, always_xy=True)
|
|
39
|
+
flat_xs, flat_ys = xs.ravel(), ys.ravel()
|
|
40
|
+
longitudes, latitudes = transformer.transform(flat_xs, flat_ys)
|
|
41
|
+
xs_dd = np.array(longitudes).reshape(xs.shape)
|
|
42
|
+
ys_dd = np.array(latitudes).reshape(ys.shape)
|
|
43
|
+
|
|
44
|
+
# Define the color map and normalization
|
|
45
|
+
flood_colors = ["black", "white", "grey", "green", "blue", "red"] # 6 classes
|
|
46
|
+
flood_cmap = mcolors.ListedColormap(flood_colors)
|
|
47
|
+
flood_norm = mcolors.BoundaryNorm(
|
|
48
|
+
boundaries=np.arange(-0.5, 6.5, 1), ncolors=len(flood_colors)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Plot the raster with transformed coordinates
|
|
52
|
+
plt.figure(figsize=(12, 11))
|
|
53
|
+
plt.imshow(
|
|
54
|
+
combined_flood,
|
|
55
|
+
cmap=flood_cmap,
|
|
56
|
+
norm=flood_norm,
|
|
57
|
+
interpolation="none",
|
|
58
|
+
extent=(xs_dd.min(), xs_dd.max(), ys_dd.min(), ys_dd.max()),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Create legend patches
|
|
62
|
+
value_labels = {
|
|
63
|
+
0: "Permanent Water",
|
|
64
|
+
1: "No Data",
|
|
65
|
+
2: "True Negative",
|
|
66
|
+
3: "False Positive",
|
|
67
|
+
4: "False Negative",
|
|
68
|
+
5: "True Positive",
|
|
69
|
+
}
|
|
70
|
+
legend_patches = [
|
|
71
|
+
Patch(
|
|
72
|
+
color=flood_colors[i],
|
|
73
|
+
label=value_labels[i],
|
|
74
|
+
edgecolor="black",
|
|
75
|
+
linewidth=1.5,
|
|
76
|
+
)
|
|
77
|
+
for i in range(len(flood_colors))
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# Add legend and labels
|
|
81
|
+
plt.legend(handles=legend_patches, loc="lower left")
|
|
82
|
+
plt.xlabel("Longitude", fontsize=14, fontweight="bold")
|
|
83
|
+
plt.ylabel("Latitude", fontsize=14, fontweight="bold")
|
|
84
|
+
plt.tick_params(axis="both", labelsize=14, width=1.5)
|
|
85
|
+
|
|
86
|
+
# Adjust tick formatting
|
|
87
|
+
x_ticks = np.linspace(xs_dd.min(), xs_dd.max(), 5)
|
|
88
|
+
y_ticks = np.linspace(ys_dd.min(), ys_dd.max(), 5)
|
|
89
|
+
plt.xticks(x_ticks, [f"{abs(tick):.2f}" for tick in x_ticks])
|
|
90
|
+
plt.yticks(y_ticks, [f"{abs(tick):.2f}" for tick in y_ticks])
|
|
91
|
+
plt.legend(handles=legend_patches, loc="lower left")
|
|
92
|
+
plt.xlabel("Longitude", fontsize=14, fontweight="bold")
|
|
93
|
+
plt.ylabel("Latitude", fontsize=14, fontweight="bold")
|
|
94
|
+
plt.tick_params(axis="both", which="both", labelsize="14", width=1.5)
|
|
95
|
+
|
|
96
|
+
# Save the plot in the same directory as the raster with 500 DPI
|
|
97
|
+
plot_dir = os.path.join(method_path, "FinalPlots")
|
|
98
|
+
if not os.path.exists(plot_dir):
|
|
99
|
+
os.makedirs(plot_dir)
|
|
100
|
+
# Base name of the raster file
|
|
101
|
+
base_name = os.path.basename(raster_path).split(".")[0]
|
|
102
|
+
output_path = os.path.join(plot_dir, f"{base_name}.png")
|
|
103
|
+
plt.savefig(output_path, dpi=500, bbox_inches="tight")
|
|
104
|
+
plt.show()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def PrintContingencyMap(main_dir, method_name, out_dir):
|
|
108
|
+
# Check for .tif files directly in main_dir
|
|
109
|
+
tif_files_main = glob.glob(os.path.join(main_dir, "*.tif"))
|
|
110
|
+
|
|
111
|
+
if tif_files_main:
|
|
112
|
+
method_path = os.path.join(out_dir, os.path.basename(main_dir), method_name)
|
|
113
|
+
contingency_path = os.path.join(method_path, "ContingencyMaps")
|
|
114
|
+
|
|
115
|
+
if os.path.exists(contingency_path):
|
|
116
|
+
tif_files = glob.glob(os.path.join(contingency_path, "*.tif"))
|
|
117
|
+
if not tif_files:
|
|
118
|
+
print(f"No Contingency TIFF files found in '{contingency_path}'.")
|
|
119
|
+
else:
|
|
120
|
+
for tif_file in tif_files:
|
|
121
|
+
print(f"****** Printing Contingency Map for {tif_file} ******")
|
|
122
|
+
getContingencyMap(tif_file, method_path)
|
|
123
|
+
|
|
124
|
+
# Traverse all folders in main_dir if no .tif files directly in main_dir
|
|
125
|
+
else:
|
|
126
|
+
for folder in os.listdir(main_dir):
|
|
127
|
+
folder_path = os.path.join(out_dir, folder)
|
|
128
|
+
if os.path.isdir(folder_path):
|
|
129
|
+
method_path = os.path.join(folder_path, method_name)
|
|
130
|
+
contingency_path = os.path.join(method_path, "ContingencyMaps")
|
|
131
|
+
|
|
132
|
+
if os.path.exists(contingency_path):
|
|
133
|
+
tif_files = glob.glob(os.path.join(contingency_path, "*.tif"))
|
|
134
|
+
|
|
135
|
+
if not tif_files:
|
|
136
|
+
print(
|
|
137
|
+
f"No Contingency TIFF files found in '{contingency_path}'."
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
for tif_file in tif_files:
|
|
141
|
+
print(
|
|
142
|
+
f"****** Printing Contingency Map for {tif_file} ******"
|
|
143
|
+
)
|
|
144
|
+
getContingencyMap(tif_file, method_path)
|
fimeval/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#Evaluation modules
|
|
2
|
+
from .ContingencyMap.evaluationFIM import EvaluateFIM
|
|
3
|
+
from .ContingencyMap.printcontingency import PrintContingencyMap
|
|
4
|
+
from .ContingencyMap.plotevaluationmetrics import PlotEvaluationMetrics
|
|
5
|
+
from .ContingencyMap.PWBs3 import get_PWB
|
|
6
|
+
|
|
7
|
+
#Utility modules
|
|
8
|
+
from .utilis import compress_tif_lzw
|
|
9
|
+
|
|
10
|
+
# Evaluation with Building foorprint module
|
|
11
|
+
from .BuildingFootprint.evaluationwithBF import EvaluationWithBuildingFootprint
|
fimeval/utilis.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
import pyproj
|
|
3
|
+
import rasterio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import geopandas as gpd
|
|
6
|
+
from rasterio.warp import calculate_default_transform, reproject, Resampling
|
|
7
|
+
|
|
8
|
+
#Lossless compression to reduce the file size
|
|
9
|
+
def compress_tif_lzw(tif_path):
|
|
10
|
+
# Read original file
|
|
11
|
+
with rasterio.open(tif_path) as src:
|
|
12
|
+
profile = src.profile.copy()
|
|
13
|
+
data = src.read()
|
|
14
|
+
profile.update(compress='lzw')
|
|
15
|
+
|
|
16
|
+
with rasterio.open(tif_path, 'w', **profile) as dst:
|
|
17
|
+
dst.write(data)
|
|
18
|
+
|
|
19
|
+
#Check whether it is a projected CRS
|
|
20
|
+
def is_projected_crs(crs):
|
|
21
|
+
return crs and crs.is_projected
|
|
22
|
+
|
|
23
|
+
#Check if the FIM bounds are within the CONUS
|
|
24
|
+
def is_within_conus(bounds, crs=None):
|
|
25
|
+
CONUS_BBOX = (-125, 24, -66.5, 49.5)
|
|
26
|
+
left, bottom, right, top = bounds
|
|
27
|
+
|
|
28
|
+
if crs and crs.is_projected:
|
|
29
|
+
transformer = pyproj.Transformer.from_crs(crs, "EPSG:4326", always_xy=True)
|
|
30
|
+
left, bottom = transformer.transform(left, bottom)
|
|
31
|
+
right, top = transformer.transform(right, top)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
left >= CONUS_BBOX[0]
|
|
35
|
+
and right <= CONUS_BBOX[2]
|
|
36
|
+
and bottom >= CONUS_BBOX[1]
|
|
37
|
+
and top <= CONUS_BBOX[3]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
#Reproject the FIMs to EPSG:5070 if withinUS and user doesnot define any target CRS, else user need to define it
|
|
41
|
+
def reprojectFIMs(src_path, dst_path, target_crs):
|
|
42
|
+
with rasterio.open(src_path) as src:
|
|
43
|
+
if src.crs != target_crs:
|
|
44
|
+
transform, width, height = calculate_default_transform(
|
|
45
|
+
src.crs, target_crs, src.width, src.height, *src.bounds
|
|
46
|
+
)
|
|
47
|
+
kwargs = src.meta.copy()
|
|
48
|
+
kwargs.update({
|
|
49
|
+
'crs': target_crs,
|
|
50
|
+
'transform': transform,
|
|
51
|
+
'width': width,
|
|
52
|
+
'height': height
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
with rasterio.open(dst_path, 'w', **kwargs) as dst:
|
|
56
|
+
for i in range(1, src.count + 1):
|
|
57
|
+
reproject(
|
|
58
|
+
source=rasterio.band(src, i),
|
|
59
|
+
destination=rasterio.band(dst, i),
|
|
60
|
+
src_transform=src.transform,
|
|
61
|
+
src_crs=src.crs,
|
|
62
|
+
dst_transform=transform,
|
|
63
|
+
dst_crs=target_crs,
|
|
64
|
+
resampling=Resampling.nearest
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
print(f"Source raster is already in {target_crs}. No reprojection needed.")
|
|
68
|
+
shutil.copy(src_path, dst_path)
|
|
69
|
+
compress_tif_lzw(dst_path)
|
|
70
|
+
|
|
71
|
+
#Resample into the coarser resoution amoung all FIMS within the case
|
|
72
|
+
def resample_to_resolution(src_path, x_resolution, y_resolution):
|
|
73
|
+
with rasterio.open(src_path) as src:
|
|
74
|
+
transform = rasterio.transform.from_origin(src.bounds.left, src.bounds.top, x_resolution, y_resolution)
|
|
75
|
+
width = int((src.bounds.right - src.bounds.left) / x_resolution)
|
|
76
|
+
height = int((src.bounds.top - src.bounds.bottom) / y_resolution)
|
|
77
|
+
|
|
78
|
+
kwargs = src.meta.copy()
|
|
79
|
+
kwargs.update({
|
|
80
|
+
'transform': transform,
|
|
81
|
+
'width': width,
|
|
82
|
+
'height': height
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
dst_path = src_path
|
|
86
|
+
with rasterio.open(dst_path, 'w', **kwargs) as dst:
|
|
87
|
+
for i in range(1, src.count + 1):
|
|
88
|
+
reproject(
|
|
89
|
+
source=rasterio.band(src, i),
|
|
90
|
+
destination=rasterio.band(dst, i),
|
|
91
|
+
src_transform=src.transform,
|
|
92
|
+
src_crs=src.crs,
|
|
93
|
+
dst_transform=transform,
|
|
94
|
+
dst_crs=src.crs,
|
|
95
|
+
resampling=Resampling.nearest
|
|
96
|
+
)
|
|
97
|
+
# compress_tif_lzw(dst_path)
|
|
98
|
+
|
|
99
|
+
#Check if the FIMs are in the same CRS or not else do further operation
|
|
100
|
+
def MakeFIMsUniform(fim_dir, target_crs=None, target_resolution=None):
|
|
101
|
+
fim_dir = Path(fim_dir)
|
|
102
|
+
tif_files = list(fim_dir.glob('*.tif'))
|
|
103
|
+
if not tif_files:
|
|
104
|
+
print(f"No TIFF files found in {fim_dir}")
|
|
105
|
+
return
|
|
106
|
+
processing_folder = fim_dir / 'processing'
|
|
107
|
+
processing_folder.mkdir(exist_ok=True)
|
|
108
|
+
|
|
109
|
+
crs_list = []
|
|
110
|
+
projected_status = []
|
|
111
|
+
bounds_list = []
|
|
112
|
+
fims_data = []
|
|
113
|
+
|
|
114
|
+
for tif_path in tif_files:
|
|
115
|
+
try:
|
|
116
|
+
with rasterio.open(tif_path) as src:
|
|
117
|
+
crs_list.append(src.crs)
|
|
118
|
+
projected_status.append(is_projected_crs(src.crs))
|
|
119
|
+
bounds_list.append(src.bounds)
|
|
120
|
+
fims_data.append((src.bounds, src.crs))
|
|
121
|
+
except rasterio.RasterioIOError as e:
|
|
122
|
+
print(f"Error opening {tif_path}: {e}")
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
all_projected = all(projected_status)
|
|
126
|
+
first_crs = crs_list[0] if crs_list else None
|
|
127
|
+
all_same_crs = all(crs == first_crs for crs in crs_list)
|
|
128
|
+
|
|
129
|
+
if not all_projected or (all_projected and not all_same_crs):
|
|
130
|
+
if target_crs:
|
|
131
|
+
print(f"Reprojecting all FIMs to {target_crs}.")
|
|
132
|
+
for src_path in tif_files:
|
|
133
|
+
dst_path = processing_folder / src_path.name
|
|
134
|
+
reprojectFIMs(str(src_path), str(dst_path), target_crs)
|
|
135
|
+
else:
|
|
136
|
+
all_within_conus = all(is_within_conus(bounds_list[i], crs_list[i]) for i in range(len(bounds_list)))
|
|
137
|
+
|
|
138
|
+
if all_within_conus:
|
|
139
|
+
default_target_crs = "EPSG:5070"
|
|
140
|
+
print(f"FIMs are within CONUS, reprojecting all to {default_target_crs} and saving to {processing_folder}")
|
|
141
|
+
for src_path in tif_files:
|
|
142
|
+
dst_path = processing_folder / src_path.name
|
|
143
|
+
reprojectFIMs(str(src_path), str(dst_path), default_target_crs)
|
|
144
|
+
else:
|
|
145
|
+
print("All flood maps are not in the projected CRS or are not in the same projected CRS.\n")
|
|
146
|
+
print("Please provide a target CRS in EPSG format.")
|
|
147
|
+
else:
|
|
148
|
+
for src_path in tif_files:
|
|
149
|
+
dst_path = processing_folder / src_path.name
|
|
150
|
+
shutil.copy(src_path, dst_path)
|
|
151
|
+
compress_tif_lzw(dst_path)
|
|
152
|
+
|
|
153
|
+
# Resolution check and resampling
|
|
154
|
+
processed_tifs = list(processing_folder.glob('*.tif'))
|
|
155
|
+
if processed_tifs:
|
|
156
|
+
resolutions = []
|
|
157
|
+
for tif_path in processed_tifs:
|
|
158
|
+
try:
|
|
159
|
+
with rasterio.open(tif_path) as src:
|
|
160
|
+
resolutions.append(src.res)
|
|
161
|
+
except rasterio.RasterioIOError as e:
|
|
162
|
+
print(f"Error opening {tif_path} in processing folder: {e}")
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
first_resolution = resolutions[0] if resolutions else None
|
|
166
|
+
all_same_resolution = all(res == first_resolution for res in resolutions)
|
|
167
|
+
|
|
168
|
+
if not all_same_resolution:
|
|
169
|
+
if target_resolution is not None:
|
|
170
|
+
for src_path in processed_tifs:
|
|
171
|
+
resample_to_resolution(str(src_path), target_resolution, target_resolution)
|
|
172
|
+
else:
|
|
173
|
+
print("FIMs are in different resolution after projection. \n")
|
|
174
|
+
coarser_x = max(res[0] for res in resolutions)
|
|
175
|
+
coarser_y = max(res[1] for res in resolutions)
|
|
176
|
+
print(f"Using coarser resolution: X={coarser_x}, Y={coarser_y}. Resampling all FIMS to this resolution.")
|
|
177
|
+
for src_path in processed_tifs:
|
|
178
|
+
resample_to_resolution(str(src_path), coarser_x, coarser_y)
|
|
179
|
+
else:
|
|
180
|
+
print("All FIMs in the processing folder have the same resolution.")
|
|
181
|
+
else:
|
|
182
|
+
print("No TIFF files found in the processing folder after CRS standardization.")
|