Spaces:
Configuration error
Configuration error
File size: 7,665 Bytes
cdeafc5 | 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 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | import os
import numpy as np
import geopandas as gpd
import pandas as pd
import rasterio
from rasterio.features import shapes
from sklearn.decomposition import PCA
import logging
from tqdm import tqdm
from shapely.geometry import shape
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Choose one of the following band mappings based on selected bands for PCA
band_mapping_case1 = {2: 0, 4: 1, 5: 2, 6: 3} # For Bands 2, 4, 5, and 6
band_mapping_case2 = {2: 0, 5: 1, 6: 2, 7: 3} # For Bands 2, 5, 6, and 7
# Choose band mapping (adjust as needed)
band_mapping = band_mapping_case1
# Function to read specific band file
def read_band_file(input_directory, scene_name, band_index):
band_filename = f"{scene_name}_SR_B{band_index}.TIF"
band_path = os.path.join(input_directory, band_filename)
if not os.path.exists(band_path):
logging.error(f"File not found: {band_path}")
return None
with rasterio.open(band_path) as src:
return src.read(1), src.transform, src.crs
# Function to stack bands for PCA
def stack_bands(scene_name, input_directory, output_directory, bands):
stacked_bands, transform, crs = [], None, None
for band_index in bands:
result = read_band_file(input_directory, scene_name, band_index)
if result is not None:
band_data, transform, crs = result
stacked_bands.append(band_data)
else:
logging.warning(f"Skipping band {band_index} for scene {scene_name} due to missing file.")
if len(stacked_bands) == len(bands):
stacked_array = np.stack(stacked_bands, axis=-1)
stacked_path = os.path.join(output_directory, f"{scene_name}_stacked.tif")
with rasterio.open(stacked_path, 'w', driver='GTiff', height=stacked_array.shape[0],
width=stacked_array.shape[1], count=len(bands), dtype=stacked_array.dtype,
crs=crs, transform=transform) as dst:
for i in range(len(bands)):
dst.write(stacked_array[..., i], i + 1)
logging.info(f"Stacked bands saved at: {stacked_path}")
return stacked_path
else:
logging.warning(f"Could not stack all bands for scene: {scene_name}")
return None
# Function to perform PCA on stacked bands
def perform_pca(stacked_path):
with rasterio.open(stacked_path) as src:
data = src.read()
reshaped_data = data.reshape(data.shape[0], -1).T
pca = PCA(n_components=min(data.shape[0], data.shape[2]))
pca_results = pca.fit_transform(reshaped_data)
loadings_df = pd.DataFrame(pca.components_.T, columns=[f'PC{i + 1}' for i in range(pca.n_components_)])
return pca_results.reshape(data.shape[1], data.shape[2], -1), loadings_df
# Function to analyze principal components
def analyze_principal_components(loadings, criteria):
selected_pcs = {}
for mineral, bands in criteria.items():
found = False
for pc_index in range(loadings.shape[0]):
if all((loadings[pc_index, band_mapping[band_index]] > 0 if sign == '+' else loadings[pc_index, band_mapping[band_index]] < 0)
for band_index, sign in bands.items() if band_index in band_mapping):
selected_pcs[mineral] = pc_index + 1
logging.info(f"Selected PC for {mineral} is PC{pc_index + 1}.")
found = True
break
if not found:
logging.warning(f"No suitable PC found for {mineral}.")
return selected_pcs
# Function to log PCA statistics
def log_pca_stats(loadings_df, selected_pcs, output_directory, scene_name):
stats = []
for mineral, pc_index in selected_pcs.items():
pc_column = loadings_df[f'PC{pc_index}']
mean_val = pc_column.mean()
std_val = pc_column.std()
logging.info(f"{mineral} - PC{pc_index}: Mean={mean_val}, Std Dev={std_val}")
stats.append({
'Mineral': mineral,
'Selected_PC': f'PC{pc_index}',
'Mean_Loading': mean_val,
'Std_Dev_Loading': std_val,
**{f'Band_{col}': loadings_df.iloc[band_mapping[col], pc_index - 1] for col in band_mapping.keys()}
})
stats_df = pd.DataFrame(stats)
stats_csv_path = os.path.join(output_directory, f"{scene_name}_PCA_Loading_Stats.csv")
stats_df.to_csv(stats_csv_path, index=False)
logging.info(f"PCA loading values and statistics saved at: {stats_csv_path}")
# Function to apply thresholds and convert to vector
def apply_threshold_and_convert_to_vector(data, mean, std_dev, threshold_levels, output_shapefile_base, transform, crs):
for label, multiplier in threshold_levels.items():
threshold = mean + multiplier * std_dev
mask = data > threshold
with rasterio.Env():
shapes_gen = shapes(mask.astype(np.uint8), transform=transform)
polygons = [shape(geom) for geom, val in shapes_gen if val == 1]
shapefile_path = f"{output_shapefile_base}_{label}.shp"
gdf = gpd.GeoDataFrame(geometry=polygons, crs=crs)
gdf.to_file(shapefile_path)
logging.info(f"Shapefile created for threshold {label} at: {shapefile_path}")
# Main function to process a single scene
def process_single_scene(scene_name, input_directory, output_directory, mineral_criteria, threshold_levels):
bands = [2, 4, 5, 6] if band_mapping == band_mapping_case1 else [2, 5, 6, 7]
stacked_path = stack_bands(scene_name, input_directory, output_directory, bands)
if stacked_path is not None:
with rasterio.open(stacked_path) as src:
transform, crs = src.transform, src.crs
pca_results, loadings_df = perform_pca(stacked_path)
selected_pcs = analyze_principal_components(loadings_df.values, mineral_criteria)
log_pca_stats(loadings_df, selected_pcs, output_directory, scene_name)
for mineral, pc_index in selected_pcs.items():
pc_data = pca_results[:, :, pc_index - 1]
mean, std_dev = np.mean(pc_data), np.std(pc_data)
shapefile_path_base = os.path.join(output_directory, f"{scene_name}_{mineral}")
apply_threshold_and_convert_to_vector(pc_data, mean, std_dev, threshold_levels, shapefile_path_base, transform, crs)
# Batch process function for all files in the directory
def batch_process_scenes(input_directory, output_directory, mineral_criteria, threshold_levels):
processed_scenes = set()
tif_files = [file for file in os.listdir(input_directory) if file.endswith('.TIF')]
for file in tqdm(tif_files, desc="Processing scenes"):
scene_name = "_".join(file.split("_")[:-2])
if scene_name not in processed_scenes:
process_single_scene(scene_name, input_directory, output_directory, mineral_criteria, threshold_levels)
processed_scenes.add(scene_name)
# Main execution block for standalone use
if __name__ == "__main__":
input_directory = r'D:\RS\Arabian Shield\Complied data\Data for BR\segregated_files\SR'
output_directory = r'D:\RS\Arabian Shield\Complied data\Processed_PCA'
mineral_criteria = {
'Iron_Oxides': {2: '+', 4: '-'},
'Hydroxyl_Alteration': {2: '+', 5: '+', 6: '-', 7: '+'},
'Limonite': {2: '+', 5: '-'}
}
threshold_levels = {'98%': 3, '95%': 2, '92%': 1}
batch_process_scenes(input_directory, output_directory, mineral_criteria, threshold_levels)
logging.info("Processing completed!")
|