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.
@@ -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.")