SS 2025
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import csv
|
||||
import rasterio
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
class ChangeAnalysis():
|
||||
def __init__(self, old_raster_path, new_raster_path, change_raster_path, report_csv_path, report_png_path):
|
||||
|
||||
# Attributes
|
||||
self.old_raster_path = old_raster_path
|
||||
self.new_raster_path = new_raster_path
|
||||
self.change_raster_path = change_raster_path
|
||||
self.report_csv_path = report_csv_path
|
||||
self.report_png_path = report_png_path
|
||||
|
||||
print("Initializing ChangeAnalysis...\n")
|
||||
|
||||
# Checks
|
||||
self._check_resolution()
|
||||
self._check_crs()
|
||||
|
||||
# Calculated
|
||||
self._compute_overlap_window()
|
||||
self._create_change_raster()
|
||||
self.change_matrix_np, self.change_matrix = self._create_change_matrix()
|
||||
self._create_change_report_csv()
|
||||
|
||||
print("Change analysis completed.\n")
|
||||
|
||||
def _print_pixel_size(self, raster_path):
|
||||
with rasterio.open(raster_path) as src:
|
||||
transform = src.transform
|
||||
pixel_width = abs(transform.a)
|
||||
pixel_height = abs(transform.e)
|
||||
print("Pixel size:", pixel_width, "x", pixel_height, "metre\n")
|
||||
|
||||
def _get_pixel_size(self, raster_path):
|
||||
with rasterio.open(raster_path) as src:
|
||||
transform = src.transform
|
||||
pixel_width = abs(transform.a)
|
||||
pixel_height = abs(transform.e)
|
||||
return pixel_width, pixel_height
|
||||
|
||||
def _check_resolution(self):
|
||||
self.pixel_size_old = self._get_pixel_size(self.old_raster_path)
|
||||
self.pixel_size_new = self._get_pixel_size(self.new_raster_path)
|
||||
if self.pixel_size_old != self.pixel_size_new:
|
||||
raise ValueError(f"Pixel sizes of the input rasters do not match. Old raster has resolution of: {
|
||||
self.pixel_size_old} and new raster has a resolution of: {self.pixel_size_new}")
|
||||
else:
|
||||
self.pixel_size = self.pixel_size_old
|
||||
print(f"Pixel resolution check passed: {self.pixel_size}\n")
|
||||
|
||||
def _check_crs(self):
|
||||
with rasterio.open(self.old_raster_path) as src_old, rasterio.open(self.new_raster_path) as src_new:
|
||||
if src_old.crs != src_new.crs:
|
||||
raise ValueError(
|
||||
"Error: Old and new rasters must have the same CRS for cropping.")
|
||||
else:
|
||||
print("CRS check passed.\n")
|
||||
|
||||
def _compute_overlap_window(self):
|
||||
print("Computing overlapping window...")
|
||||
with rasterio.open(self.old_raster_path) as src_old, rasterio.open(self.new_raster_path) as src_new:
|
||||
left = max(src_old.bounds.left, src_new.bounds.left)
|
||||
bottom = max(src_old.bounds.bottom, src_new.bounds.bottom)
|
||||
right = min(src_old.bounds.right, src_new.bounds.right)
|
||||
top = min(src_old.bounds.top, src_new.bounds.top)
|
||||
|
||||
if left >= right or bottom >= top:
|
||||
raise ValueError("No overlapping extent.")
|
||||
|
||||
self.bounds = (left, bottom, right, top)
|
||||
self.window_old = src_old.window(left, bottom, right, top)
|
||||
self.window_new = src_new.window(left, bottom, right, top)
|
||||
print(f"Overlap window calculated: {self.bounds}\n")
|
||||
|
||||
def _create_change_raster(self):
|
||||
print("Creating change raster...")
|
||||
with rasterio.open(self.old_raster_path) as src_old, rasterio.open(self.new_raster_path) as src_new:
|
||||
old_data = src_old.read(1, window=self.window_old)
|
||||
new_data = src_new.read(1, window=self.window_new)
|
||||
|
||||
if old_data.shape != new_data.shape:
|
||||
raise ValueError(
|
||||
"Error: The cropped rasters do not have the same shape.")
|
||||
|
||||
change_data = old_data * 10 + new_data
|
||||
|
||||
out_transform = src_old.window_transform(self.window_old)
|
||||
out_profile = src_old.profile.copy()
|
||||
out_profile.update({
|
||||
'height': old_data.shape[0],
|
||||
'width': old_data.shape[1],
|
||||
'transform': out_transform,
|
||||
'dtype': rasterio.int16,
|
||||
'count': 1,
|
||||
'compress': 'lzw'
|
||||
})
|
||||
|
||||
with rasterio.open(self.change_raster_path, 'w', **out_profile) as dst:
|
||||
dst.write(change_data.astype(rasterio.int16), 1)
|
||||
print(f"Change raster created and saved to: {
|
||||
self.change_raster_path}\n")
|
||||
|
||||
def _create_change_matrix(self):
|
||||
print("Creating change matrix...")
|
||||
with rasterio.open(self.change_raster_path) as dataset:
|
||||
change_data = dataset.read(1)
|
||||
|
||||
flat_data = change_data.flatten()
|
||||
|
||||
nodata = dataset.nodata
|
||||
if nodata is not None:
|
||||
flat_data = flat_data[flat_data != nodata]
|
||||
|
||||
old_classes = flat_data // 10
|
||||
new_classes = flat_data % 10
|
||||
|
||||
matrix = {}
|
||||
|
||||
for old, new in zip(old_classes, new_classes):
|
||||
if (old, new) not in matrix:
|
||||
matrix[(old, new)] = 0
|
||||
matrix[(old, new)] += 1
|
||||
|
||||
# Matrix numpy array
|
||||
unique_old = sorted(set(old_classes))
|
||||
unique_new = sorted(set(new_classes))
|
||||
|
||||
mat_np = np.zeros(
|
||||
(max(unique_old)+1, max(unique_new)+1), dtype=int)
|
||||
|
||||
for (old, new), count in matrix.items():
|
||||
mat_np[old, new] = count
|
||||
print("Change matrix created.\n")
|
||||
return mat_np, matrix
|
||||
|
||||
def _create_change_report_csv(self):
|
||||
print(f"Writing change matrix to CSV: {self.report_csv_path}")
|
||||
with open(self.report_csv_path, mode='w', newline='') as csvfile:
|
||||
writer = csv.writer(csvfile)
|
||||
header = ['Old/New'] + list(range(self.change_matrix_np.shape[1]))
|
||||
writer.writerow(header)
|
||||
|
||||
for old_class, row in enumerate(self.change_matrix_np):
|
||||
writer.writerow([old_class] + list(row))
|
||||
print("CSV report created.\n")
|
||||
@@ -0,0 +1,79 @@
|
||||
import sys
|
||||
import rasterio
|
||||
from rasterio.features import rasterize
|
||||
from rasterio import Affine
|
||||
import geopandas as gpd
|
||||
import numpy as np
|
||||
from sklearn.decomposition import PCA
|
||||
from sklearn.ensemble import RandomForestClassifier
|
||||
|
||||
|
||||
def classification(path_raster, path_training, path_output):
|
||||
raster = rasterio.open(path_raster)
|
||||
bands = raster.read()
|
||||
# print("Bands shape:", bands.shape)
|
||||
meta = raster.meta
|
||||
|
||||
# Flatten spatial dimensions for PCA: (393, 550*572)
|
||||
X = bands.reshape(bands.shape[0], -1).T
|
||||
|
||||
# Apply PCA
|
||||
n_components = 20
|
||||
pca = PCA(n_components=n_components)
|
||||
X_pca = pca.fit_transform(X)
|
||||
|
||||
# Reshape back to image cube: (20, 550, 572)
|
||||
bands_pca = X_pca.T.reshape(n_components, bands.shape[1], bands.shape[2])
|
||||
|
||||
# Training data
|
||||
training = gpd.read_file(path_training)
|
||||
if training.crs != raster.crs:
|
||||
print(f"Reprojecting training data from {
|
||||
training.crs} to {raster.crs}")
|
||||
training = training.to_crs(raster.crs)
|
||||
|
||||
# print(training.columns)
|
||||
|
||||
label_raster = rasterize(
|
||||
[(geom, class_id) for geom, class_id in zip(
|
||||
training.geometry, training.Class_ID)],
|
||||
out_shape=(bands.shape[1], bands.shape[2]),
|
||||
transform=raster.transform,
|
||||
fill=0,
|
||||
dtype='int16'
|
||||
)
|
||||
|
||||
# Flatten bands and labels
|
||||
X_train = bands_pca.reshape(n_components, -1).T
|
||||
y_train = label_raster.flatten()
|
||||
|
||||
# Keep only labeled pixels
|
||||
mask = y_train > 0
|
||||
X_train = X_train[mask]
|
||||
y_train = y_train[mask]
|
||||
|
||||
print("Training samples:", X_train.shape)
|
||||
|
||||
clf = RandomForestClassifier(n_estimators=200, random_state=42)
|
||||
clf.fit(X_train, y_train)
|
||||
print("Classifier trained.")
|
||||
|
||||
# Predict classes for all pixels
|
||||
y_pred = clf.predict(X_pca)
|
||||
classified = y_pred.reshape(bands.shape[1], bands.shape[2])
|
||||
print("Classification completed.")
|
||||
|
||||
# Save classified map
|
||||
meta.update({
|
||||
"count": 1,
|
||||
"dtype": classified.dtype
|
||||
})
|
||||
|
||||
with rasterio.open(path_output, "w", **meta) as dst:
|
||||
dst.write(classified, 1)
|
||||
|
||||
print(f"Classified map saved to: {path_output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
classification(sys.argv[1], sys.argv[2], sys.argv[3])
|
||||
@@ -0,0 +1,21 @@
|
||||
import rasterio
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def get_class_frequency(raster_path, metadata):
|
||||
with rasterio.open(raster_path) as raster:
|
||||
band = raster.read(1)
|
||||
|
||||
nodata = raster.nodata
|
||||
if nodata is not None:
|
||||
band = band[band != nodata]
|
||||
else:
|
||||
band = band[~np.isnan(band)]
|
||||
|
||||
values, counts = np.unique(band, return_counts=True)
|
||||
df = pd.DataFrame({'class_id': values, 'count': counts})
|
||||
df = df.merge(metadata, on='class_id', how='left')
|
||||
df = df[['class_name', 'count']]
|
||||
|
||||
return df
|
||||
@@ -0,0 +1,85 @@
|
||||
import rasterio
|
||||
import geopandas as gpd
|
||||
from sklearn.metrics import confusion_matrix, classification_report
|
||||
import numpy as np
|
||||
|
||||
|
||||
def accuracy_assessment(classified_raster_path, reference_points_path, true_class_field):
|
||||
"""
|
||||
Perform accuracy assessment of classified raster using reference points.
|
||||
|
||||
Parameters:
|
||||
- classified_raster_path: str, path to classified TIFF raster file.
|
||||
- reference_points_path: str, path to reference points vector file (GeoJSON or Shapefile).
|
||||
- true_class_field: str, name of the attribute field in reference data containing true class labels.
|
||||
|
||||
Returns:
|
||||
- dict with confusion matrix, classification report, overall accuracy, and kappa coefficient.
|
||||
"""
|
||||
|
||||
# Load raster
|
||||
raster = rasterio.open(classified_raster_path)
|
||||
|
||||
# Load reference points
|
||||
gdf = gpd.read_file(reference_points_path)
|
||||
|
||||
# Reproject reference points to raster CRS if needed
|
||||
if gdf.crs != raster.crs:
|
||||
gdf = gdf.to_crs(raster.crs)
|
||||
|
||||
# Extract raster values at point locations
|
||||
coords = [(geom.x, geom.y) for geom in gdf.geometry]
|
||||
raster_vals = []
|
||||
for val in raster.sample(coords):
|
||||
if val.size > 0 and val[0] is not None:
|
||||
raster_vals.append(val[0])
|
||||
else:
|
||||
raster_vals.append(np.nan)
|
||||
|
||||
gdf['predicted_class'] = raster_vals
|
||||
|
||||
# Drop points where raster value could not be sampled (e.g., outside raster extent)
|
||||
gdf = gdf.dropna(subset=['predicted_class'])
|
||||
|
||||
# Convert predicted_class to int (if float)
|
||||
gdf['predicted_class'] = gdf['predicted_class'].astype(int)
|
||||
|
||||
y_true = gdf[true_class_field].values
|
||||
y_pred = gdf['predicted_class'].values
|
||||
|
||||
print("Unique classes in y_true:", np.unique(y_true))
|
||||
print("Unique classes in y_pred:", np.unique(y_pred))
|
||||
print("Anzahl Samples:", len(y_true))
|
||||
|
||||
# Compute confusion matrix
|
||||
cm = confusion_matrix(y_true, y_pred)
|
||||
|
||||
# Compute classification report (precision, recall, f1-score)
|
||||
report = classification_report(y_true, y_pred, output_dict=True)
|
||||
|
||||
# Overall accuracy
|
||||
overall_accuracy = np.trace(cm) / np.sum(cm)
|
||||
|
||||
# Kappa coefficient calculation
|
||||
total = np.sum(cm)
|
||||
sum_rows = np.sum(cm, axis=1)
|
||||
sum_cols = np.sum(cm, axis=0)
|
||||
expected_accuracy = np.sum(sum_rows * sum_cols) / (total ** 2)
|
||||
kappa = (overall_accuracy - expected_accuracy) / (1 - expected_accuracy)
|
||||
|
||||
# Print summary
|
||||
print("Confusion Matrix:\n", cm)
|
||||
print(f"Overall Accuracy: {overall_accuracy:.4f}")
|
||||
print(f"Kappa Coefficient: {kappa:.4f}")
|
||||
print("Classification Report (per class):")
|
||||
for cls, metrics in report.items():
|
||||
if cls not in ['accuracy', 'macro avg', 'weighted avg']:
|
||||
print(f"Class {cls}: Precision={metrics['precision']:.3f}, Recall={
|
||||
metrics['recall']:.3f}, F1-score={metrics['f1-score']:.3f}")
|
||||
|
||||
return {
|
||||
'confusion_matrix': cm,
|
||||
'classification_report': report,
|
||||
'overall_accuracy': overall_accuracy,
|
||||
'kappa_coefficient': kappa
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import sys
|
||||
import rasterio
|
||||
from rasterio.mask import mask
|
||||
import geopandas as gpd
|
||||
|
||||
|
||||
def clip_tiff(tiff_path, gpkg_path, output_path):
|
||||
vector = gpd.read_file(gpkg_path)
|
||||
|
||||
with rasterio.open(tiff_path) as src:
|
||||
if vector.crs != src.crs:
|
||||
vector = vector.to_crs(src.crs)
|
||||
|
||||
geometries = [feature["geometry"]
|
||||
for feature in vector.__geo_interface__['features']]
|
||||
|
||||
out_image, out_transform = mask(src, geometries, crop=True)
|
||||
out_meta = src.meta.copy()
|
||||
|
||||
out_meta.update({
|
||||
"driver": "GTiff",
|
||||
"height": out_image.shape[1],
|
||||
"width": out_image.shape[2],
|
||||
"transform": out_transform
|
||||
})
|
||||
|
||||
with rasterio.open(output_path, "w", **out_meta) as dest:
|
||||
dest.write(out_image)
|
||||
|
||||
print(f"Clipped raster saved to: {output_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: python clip_tiff.py <input.tif> <input.gpkg> <output.tif>")
|
||||
sys.exit(1)
|
||||
|
||||
tiff_file = sys.argv[1]
|
||||
gpkg_file = sys.argv[2]
|
||||
output_file = sys.argv[3]
|
||||
|
||||
clip_tiff(tiff_file, gpkg_file, output_file)
|
||||
@@ -0,0 +1,16 @@
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def empty_png(path):
|
||||
|
||||
# Set the size of the image (width, height)
|
||||
width = 800
|
||||
height = 600
|
||||
|
||||
# Create a new transparent image (RGBA mode)
|
||||
transparent_image = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
||||
|
||||
# Save the image as PNG
|
||||
transparent_image.save(path)
|
||||
|
||||
print("Transparent PNG created successfully!")
|
||||
@@ -0,0 +1,72 @@
|
||||
import rasterio
|
||||
import os
|
||||
import csv
|
||||
from rasterio.warp import transform_bounds
|
||||
from pyproj import Geod
|
||||
|
||||
|
||||
def extract_tiff_metadata(tiff_path, output_csv_path):
|
||||
"""
|
||||
Extracts metadata from a TIFF file and writes it to a CSV file.
|
||||
|
||||
:param tiff_path: Path to the TIFF file
|
||||
:param output_csv_path: Path to save the metadata CSV file
|
||||
"""
|
||||
try:
|
||||
with rasterio.open(tiff_path) as dataset:
|
||||
pixel_size_x, pixel_size_y = dataset.res
|
||||
crs = dataset.crs
|
||||
geod = Geod(ellps="WGS84")
|
||||
|
||||
if crs.is_projected:
|
||||
# Units are already linear (e.g., meters)
|
||||
crs_unit = crs.linear_units
|
||||
pixel_area = abs(pixel_size_x * pixel_size_y)
|
||||
pixel_size_x_m = pixel_size_x
|
||||
pixel_size_y_m = pixel_size_y
|
||||
else:
|
||||
# CRS is geographic (degrees), compute approximate linear size
|
||||
bounds = dataset.bounds
|
||||
|
||||
# Approximate pixel size X in meters
|
||||
lon1, lat1 = bounds.left, bounds.bottom
|
||||
lon2, lat2 = bounds.left + pixel_size_x, bounds.bottom
|
||||
distance_x, _, _ = geod.inv(lon1, lat1, lon2, lat2)
|
||||
|
||||
# Approximate pixel size Y in meters
|
||||
lon3, lat3 = bounds.left, bounds.bottom
|
||||
lon4, lat4 = bounds.left, bounds.bottom + pixel_size_y
|
||||
distance_y, _, _ = geod.inv(lon3, lat3, lon4, lat4)
|
||||
|
||||
pixel_size_x_m = abs(distance_x)
|
||||
pixel_size_y_m = abs(distance_y)
|
||||
crs_unit = "meters"
|
||||
pixel_area = pixel_size_x_m * pixel_size_y_m
|
||||
|
||||
meta_info = {
|
||||
"Filename": os.path.basename(tiff_path),
|
||||
"Width (pixels)": dataset.width,
|
||||
"Height (pixels)": dataset.height,
|
||||
"Number of Bands": dataset.count,
|
||||
"Coordinate Reference System (CRS)": str(crs),
|
||||
"Pixel Area": f"{pixel_area:.1f} {crs_unit}²",
|
||||
}
|
||||
|
||||
# Write metadata to CSV file
|
||||
with open(output_csv_path, mode='w', newline='') as csvfile:
|
||||
writer = csv.writer(csvfile)
|
||||
writer.writerow(["Key", "Value"])
|
||||
for key, value in meta_info.items():
|
||||
writer.writerow([key, value])
|
||||
|
||||
print(f"Metadata written to {output_csv_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
tiff_file = "example.tif" # Path to your TIFF file
|
||||
output_file = "metadata_output.csv" # Path to output CSV file
|
||||
extract_tiff_metadata(tiff_file, output_file)
|
||||
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
import sys
|
||||
import matplotlib.pyplot as plt
|
||||
import geopandas as gpd
|
||||
import rasterio
|
||||
from rasterio.plot import show as rioshow
|
||||
|
||||
|
||||
def gpkg2png(input_path, base_path, output_path, title, color):
|
||||
# Load vector data
|
||||
data = gpd.read_file(input_path)
|
||||
|
||||
# Load raster base map
|
||||
with rasterio.open(base_path) as src:
|
||||
base_crs = src.crs
|
||||
raster_data = src.read()
|
||||
|
||||
# Reproject vector data if CRS differs
|
||||
if data.crs != base_crs:
|
||||
data = data.to_crs(base_crs)
|
||||
|
||||
# Set up figure and axes
|
||||
fig, ax = plt.subplots()
|
||||
fig.patch.set_facecolor(color)
|
||||
ax.set_xlabel("Longitude", color="white")
|
||||
ax.set_ylabel("Latitude", color="white")
|
||||
|
||||
ax.ticklabel_format(style='plain', axis='both', useOffset=False)
|
||||
# Plot raster background
|
||||
rioshow(raster_data, transform=src.transform, ax=ax)
|
||||
|
||||
# Plot vector data
|
||||
data.plot(
|
||||
ax=ax,
|
||||
facecolor="none", # Transparent fill
|
||||
edgecolor="white", # White border for circles
|
||||
linewidth=1, # Thickness of circle edges
|
||||
alpha=0.7, # Transparency for the edges
|
||||
marker='o', # Ensure point geometries are circles
|
||||
markersize=100 # Adjust size as needed
|
||||
)
|
||||
# Style adjustments
|
||||
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
|
||||
label.set_color("white")
|
||||
|
||||
ax.set_title(title, color="white")
|
||||
ax.tick_params(color="white")
|
||||
ax.set_facecolor(color)
|
||||
for spine in ax.spines.values():
|
||||
spine.set_color("white")
|
||||
|
||||
plt.savefig(output_path, bbox_inches='tight',
|
||||
facecolor=fig.get_facecolor())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gpkg2png(sys.argv[1], sys.argv[2], sys.argv[3])
|
||||
@@ -0,0 +1,27 @@
|
||||
import geopandas as gpd
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def load_gpkg_to_dataframe(gpkg_path, layer_name=None):
|
||||
"""
|
||||
Load attribute data from a GeoPackage into a pandas DataFrame.
|
||||
|
||||
Parameters:
|
||||
- gpkg_path (str): Path to the GeoPackage file (.gpkg).
|
||||
- layer_name (str, optional): Name of the layer in the GeoPackage.
|
||||
If None, the first layer will be used.
|
||||
|
||||
Returns:
|
||||
- pd.DataFrame: DataFrame containing attribute data (geometry excluded).
|
||||
"""
|
||||
# Load the GeoPackage layer into a GeoDataFrame
|
||||
gdf = gpd.read_file(gpkg_path, layer=layer_name)
|
||||
|
||||
# Drop the geometry column if it exists
|
||||
if 'geometry' in gdf.columns:
|
||||
gdf = gdf.drop(columns='geometry')
|
||||
|
||||
# Convert to pandas DataFrame
|
||||
df = pd.DataFrame(gdf)
|
||||
|
||||
return df
|
||||
@@ -0,0 +1,215 @@
|
||||
from ClassificationAnalysis import get_class_frequency
|
||||
from ChangeAnalysis import ChangeAnalysis
|
||||
from Classification import classification
|
||||
from plot_tiff import plot_tiff
|
||||
from plot_tiff_rgb import plot_tiff_rgb
|
||||
from plot_tiff_2 import plot_tiff_2
|
||||
from plot_table import plot_table
|
||||
from clip_tiff import clip_tiff
|
||||
from extract_tiff_metadata import extract_tiff_metadata
|
||||
from load_gpkg_to_df import load_gpkg_to_dataframe
|
||||
from gpkg2png import gpkg2png
|
||||
from empty_png import empty_png
|
||||
from accuracy_assessment import accuracy_assessment
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def create_accuracy_df(overall_accuracy, kappa):
|
||||
data = {
|
||||
"Metric": ["Overall Accuracy", "Kappa Coefficient"],
|
||||
"Value": [overall_accuracy, kappa]
|
||||
}
|
||||
df = pd.DataFrame(data)
|
||||
return df
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
# empty png
|
||||
empty_png('results/empty.png')
|
||||
|
||||
# Paths
|
||||
golm = "data/RGBI_10cm_2022.tif"
|
||||
|
||||
hyperspectral_2018_raster = "data/Hyperspectral_2018.tiff"
|
||||
hyperspectral_2018_training = "data/training_2018.gpkg"
|
||||
classified_2018_raster = "results/classified_2018.tiff"
|
||||
classified_2018_map = "results/classified_2018.png"
|
||||
classified_2018_table = "results/frequencies_2018.png"
|
||||
|
||||
hyperspectral_2024_raster = "data/Hyperspectral_2024.tif"
|
||||
hyperspectral_2024_training = "data/training_2024.gpkg"
|
||||
classified_2024_raster = "results/classified_2024.tiff"
|
||||
classified_2024_map = "results/classified_2024.png"
|
||||
classified_2024_table = "results/frequencies_2024.png"
|
||||
|
||||
metadata = pd.read_csv("data/metadata.csv")
|
||||
change = "results/change.tiff"
|
||||
report_csv = "results/report.csv"
|
||||
report_png = "results/report.png"
|
||||
|
||||
# Classification
|
||||
classification(
|
||||
hyperspectral_2018_raster,
|
||||
hyperspectral_2018_training,
|
||||
classified_2018_raster
|
||||
)
|
||||
|
||||
classification(
|
||||
hyperspectral_2024_raster,
|
||||
hyperspectral_2024_training,
|
||||
classified_2024_raster
|
||||
)
|
||||
clip_tiff(classified_2018_raster,
|
||||
"data/CampusGolm_Clipper.gpkg", classified_2018_raster)
|
||||
clip_tiff(classified_2024_raster,
|
||||
"data/CampusGolm_Clipper.gpkg", classified_2024_raster)
|
||||
plot_tiff(classified_2018_raster, classified_2018_map,
|
||||
metadata, "2018", '#202228')
|
||||
plot_tiff(classified_2024_raster, classified_2024_map,
|
||||
metadata, "2024", '#202228')
|
||||
|
||||
# Classification accuracy
|
||||
result_2018 = accuracy_assessment(
|
||||
classified_raster_path=classified_2018_raster,
|
||||
reference_points_path='data/validation_2018.gpkg',
|
||||
true_class_field="Class_ID"
|
||||
)
|
||||
|
||||
cm_2018 = result_2018['confusion_matrix']
|
||||
cm_df_2018 = pd.DataFrame(cm_2018, index=metadata['class_name'].to_list(
|
||||
), columns=metadata['class_name'].to_list())
|
||||
plot_table(cm_df_2018, 'results/confusion_matrix_2018.png',
|
||||
'2018', '#202228', True)
|
||||
|
||||
oa_2018 = "{:.2f}".format(result_2018['overall_accuracy'])
|
||||
kp_2018 = "{:.2f}".format(result_2018['kappa_coefficient'])
|
||||
df_acc_2018 = create_accuracy_df(oa_2018, kp_2018)
|
||||
plot_table(df_acc_2018, 'results/accuracy_2018.png', '2018', '#202228')
|
||||
|
||||
result_2024 = accuracy_assessment(
|
||||
classified_raster_path=classified_2024_raster,
|
||||
reference_points_path='data/validation_2018.gpkg',
|
||||
true_class_field="Class_ID"
|
||||
)
|
||||
|
||||
cm_2024 = result_2024['confusion_matrix']
|
||||
cm_df_2024 = pd.DataFrame(cm_2024, index=metadata['class_name'].to_list(
|
||||
), columns=metadata['class_name'].to_list())
|
||||
plot_table(cm_df_2024, 'results/confusion_matrix_2024.png',
|
||||
'2024', '#202228', True)
|
||||
|
||||
oa_2024 = "{:.2f}".format(result_2024['overall_accuracy'])
|
||||
kp_2024 = "{:.2f}".format(result_2024['kappa_coefficient'])
|
||||
df_acc_2024 = create_accuracy_df(oa_2024, kp_2024)
|
||||
plot_table(df_acc_2024, 'results/accuracy_2024.png', '2024', '#202228')
|
||||
|
||||
# Classification Analysis
|
||||
classified_2018_class_frequency = get_class_frequency(
|
||||
classified_2018_raster, metadata)
|
||||
classified_2024_class_frequency = get_class_frequency(
|
||||
classified_2024_raster, metadata)
|
||||
plot_table(classified_2018_class_frequency,
|
||||
classified_2018_table, "2018", "#202228")
|
||||
plot_table(classified_2024_class_frequency,
|
||||
classified_2024_table, "2024", "#202228")
|
||||
|
||||
# Change Analysis
|
||||
change_analysis = ChangeAnalysis(
|
||||
classified_2018_raster, classified_2024_raster, change, report_csv, report_png)
|
||||
clip_tiff(change,
|
||||
"data/CampusGolm_Clipper.gpkg", change)
|
||||
|
||||
report = pd.read_csv(report_csv)
|
||||
plot_table(report, report_png, 'Change Report', '#202228')
|
||||
plot_tiff_2(change, "results/change.png", '', "#202228")
|
||||
# Plot metadata
|
||||
metadata_plot = metadata[["class_name", "class_id"]]
|
||||
metadata_plot = metadata_plot.rename(
|
||||
columns={'class_name': 'Material', 'class_id': 'ID'})
|
||||
plot_table(metadata_plot, "results/metadata.png", "", '#202228')
|
||||
|
||||
# Extract metadata
|
||||
extract_tiff_metadata(hyperspectral_2018_raster, "results/meta_2018.csv")
|
||||
plot_table(pd.read_csv('results/meta_2018.csv'),
|
||||
'results/meta_2018.png', '2018', '#202228')
|
||||
extract_tiff_metadata(hyperspectral_2024_raster, "results/meta_2024.csv")
|
||||
|
||||
plot_table(pd.read_csv('results/meta_2024.csv'),
|
||||
'results/meta_2024.png', '2024', '#202228')
|
||||
# Add training data to metadata
|
||||
training_2018_meta = load_gpkg_to_dataframe(hyperspectral_2018_training)
|
||||
training_2018_meta = training_2018_meta[['name', 'Class_ID']]
|
||||
|
||||
training_2018_meta = (
|
||||
training_2018_meta.groupby(['Class_ID', 'name'])
|
||||
.size()
|
||||
.reset_index(name="count")
|
||||
)
|
||||
training_2018_meta = training_2018_meta.sort_values(
|
||||
by='Class_ID', ascending=True).reset_index(drop=True)
|
||||
|
||||
training_2018_meta = training_2018_meta[['name', 'count']]
|
||||
training_2018_meta = training_2018_meta.rename(
|
||||
columns={"name": 'Material', "count": 'Samples'})
|
||||
plot_table(training_2018_meta,
|
||||
"results/training_2018_meta.png", '2018', '#202228')
|
||||
|
||||
training_2024_meta = load_gpkg_to_dataframe(hyperspectral_2024_training)
|
||||
training_2024_meta = training_2024_meta[['name', 'Class_ID']]
|
||||
|
||||
training_2024_meta = (
|
||||
training_2024_meta.groupby(['Class_ID', 'name'])
|
||||
.size()
|
||||
.reset_index(name="count")
|
||||
)
|
||||
|
||||
training_2024_meta = training_2024_meta.sort_values(
|
||||
by='Class_ID', ascending=True).reset_index(drop=True)
|
||||
|
||||
training_2024_meta = training_2024_meta[['name', 'count']]
|
||||
training_2024_meta = training_2024_meta.rename(
|
||||
columns={"name": 'Material', "count": 'Samples'})
|
||||
plot_table(training_2024_meta,
|
||||
"results/training_2024_meta.png", '2024', '#202228')
|
||||
|
||||
# Plot training data
|
||||
gpkg2png(hyperspectral_2018_training,
|
||||
"data/RGBI_20cm_2021.tiff", "results/training2018.png", '2018', "#202228")
|
||||
gpkg2png(hyperspectral_2024_training,
|
||||
"data/RGBI_20cm_2021.tiff", "results/training2024.png", '2024', "#202228")
|
||||
|
||||
# Diff
|
||||
# Merge
|
||||
classified_difference = pd.merge(
|
||||
classified_2018_class_frequency,
|
||||
classified_2024_class_frequency,
|
||||
on="class_name",
|
||||
how="inner"
|
||||
)
|
||||
|
||||
# Compute difference
|
||||
classified_difference['change'] = (
|
||||
classified_difference['count_y'] - classified_difference['count_x']
|
||||
)
|
||||
|
||||
with open('results/area.txt', 'r') as f:
|
||||
total_area = float(f.read().strip())
|
||||
|
||||
classified_difference['percent_change'] = (
|
||||
(classified_difference['change'] / total_area) * 100
|
||||
).round(2)
|
||||
# Sort by 'change'
|
||||
classified_difference = classified_difference.sort_values(
|
||||
"change", ascending=False
|
||||
)
|
||||
|
||||
classified_difference = classified_difference[[
|
||||
'class_name', 'change', 'percent_change']]
|
||||
classified_difference = classified_difference.rename(
|
||||
columns={"class_name": "Material", "change": "Change in m²", "percent_change": "Change in %"})
|
||||
plot_table(classified_difference, 'results/difference.png', '', "#202228")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,48 @@
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
def plot_table(df, output_path, title, color, print_index=False):
|
||||
fig, ax = plt.subplots()
|
||||
|
||||
fig.patch.set_facecolor(color)
|
||||
fig.tight_layout()
|
||||
ax.axis('off')
|
||||
ax.axis('tight')
|
||||
ax.set_facecolor(color)
|
||||
|
||||
# Prepare cellText and colLabels depending on print_index
|
||||
if print_index:
|
||||
# Insert index as first column
|
||||
cell_text = pd.concat(
|
||||
[df.index.to_frame(index=False), df.reset_index(drop=True)], axis=1).values
|
||||
col_labels = [
|
||||
df.index.name if df.index.name else 'Index'] + list(df.columns)
|
||||
else:
|
||||
cell_text = df.values
|
||||
col_labels = df.columns
|
||||
|
||||
table = ax.table(
|
||||
cellText=cell_text,
|
||||
colLabels=col_labels,
|
||||
cellLoc='center',
|
||||
loc='center'
|
||||
)
|
||||
ax.tick_params(colors="white")
|
||||
ax.set_title(title, color="white")
|
||||
|
||||
for spine in ax.spines.values():
|
||||
spine.set_color("white")
|
||||
|
||||
for key, cell in table.get_celld().items():
|
||||
cell.set_facecolor(color) # use your passed color
|
||||
cell.get_text().set_color("white") # make text white for contrast
|
||||
cell.set_edgecolor("white")
|
||||
|
||||
if key[0] == 0: # header row
|
||||
cell.get_text().set_weight('bold')
|
||||
|
||||
table.scale(1, 2)
|
||||
|
||||
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
||||
plt.close()
|
||||
@@ -0,0 +1,50 @@
|
||||
import os
|
||||
import sys
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.colors as mcolors
|
||||
import matplotlib.patches as mpatches
|
||||
import geopandas as gpd
|
||||
import rasterio
|
||||
from rasterio.plot import show
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def plot_tiff(input_path, output_path, meta, title, color="#00000"):
|
||||
class_names = meta['class_name'].tolist()
|
||||
class_ids = meta['class_id'].tolist()
|
||||
colors = meta['class_color'].tolist()
|
||||
|
||||
cmap = mcolors.ListedColormap(colors)
|
||||
norm = mcolors.BoundaryNorm(class_ids + [max(class_ids)+1], cmap.N)
|
||||
legend_patches = [mpatches.Patch(
|
||||
color=colors[i], label=class_names[i]) for i in range(len(class_names))]
|
||||
|
||||
raster = rasterio.open(input_path)
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
fig.patch.set_facecolor(color)
|
||||
show(raster, ax=ax, cmap=cmap, norm=norm)
|
||||
ax.get_xaxis().get_major_formatter().set_useOffset(False)
|
||||
ax.get_yaxis().get_major_formatter().set_useOffset(False)
|
||||
ax.ticklabel_format(style='plain', axis='both', useOffset=False)
|
||||
|
||||
ax.set_xlabel("Longitude", color="white")
|
||||
ax.set_ylabel("Latitude", color="white")
|
||||
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
|
||||
label.set_color("white")
|
||||
ax.set_title(title, color="white")
|
||||
ax.tick_params(color="white")
|
||||
ax.set_facecolor(color)
|
||||
for spine in ax.spines.values():
|
||||
spine.set_color("white")
|
||||
patches = [mpatches.Patch(color=colors[i], label=class_names[i])
|
||||
for i in range(len(class_names))]
|
||||
ax.legend(handles=patches, loc="upper right",
|
||||
fontsize='small', frameon=True, facecolor=color, # legend background
|
||||
edgecolor=color, # legend border color
|
||||
labelcolor="white", framealpha=1.0)
|
||||
plt.savefig(output_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
plot_tiff(sys.argv[1], sys.argv[2])
|
||||
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
import matplotlib.pyplot as plt
|
||||
from rasterio.plot import show
|
||||
import rasterio
|
||||
|
||||
|
||||
def plot_tiff_2(input_path, output_path, title, background_color="#000000"):
|
||||
# Open the raster
|
||||
raster = rasterio.open(input_path)
|
||||
|
||||
# Use a built-in colormap (e.g., "rainbow")
|
||||
cmap = plt.get_cmap("rainbow")
|
||||
|
||||
# Plot
|
||||
fig, ax = plt.subplots()
|
||||
fig.patch.set_facecolor(background_color)
|
||||
show(raster, ax=ax, cmap=cmap)
|
||||
ax.get_xaxis().get_major_formatter().set_useOffset(False)
|
||||
ax.get_yaxis().get_major_formatter().set_useOffset(False)
|
||||
ax.ticklabel_format(style='plain', axis='both', useOffset=False)
|
||||
|
||||
# Style axes and title
|
||||
ax.set_xlabel("Longitude", color="white")
|
||||
ax.set_ylabel("Latitude", color="white")
|
||||
for label in (ax.get_xticklabels() + ax.get_yticklabels()):
|
||||
label.set_color("white")
|
||||
ax.set_title(title, color="white")
|
||||
ax.tick_params(color="white")
|
||||
ax.set_facecolor(background_color)
|
||||
for spine in ax.spines.values():
|
||||
spine.set_color("white")
|
||||
|
||||
# Save figure
|
||||
plt.savefig(output_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
input_path = sys.argv[1]
|
||||
output_path = sys.argv[2]
|
||||
title = sys.argv[3] if len(sys.argv) > 3 else "Raster Plot"
|
||||
plot_tiff_2(input_path, output_path, title)
|
||||
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
import sys
|
||||
import matplotlib.pyplot as plt
|
||||
import rasterio
|
||||
from rasterio.plot import show
|
||||
|
||||
|
||||
def plot_tiff_rgb(input_path, output_path):
|
||||
with rasterio.open(input_path) as src:
|
||||
rgb = src.read([1, 2, 3])
|
||||
|
||||
# Plot
|
||||
fig, ax = plt.subplots()
|
||||
show(rgb, transform=src.transform, ax=ax)
|
||||
ax.set_xlabel("Longitude")
|
||||
ax.set_ylabel("Latitude")
|
||||
|
||||
# Save the figure
|
||||
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
plot_tiff_rgb(sys.argv[1], sys.argv[2])
|
||||
Reference in New Issue
Block a user