This commit is contained in:
Joaquin Gottlebe
2025-07-30 15:57:48 +02:00
parent e49fcfac46
commit a639c34cee
273 changed files with 20151 additions and 0 deletions
@@ -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
+215
View File
@@ -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])