101 lines
3.1 KiB
Python
101 lines
3.1 KiB
Python
import rasterio
|
||
import numpy as np
|
||
import pandas as pd
|
||
|
||
def map_two_digit_code(code: int) -> str:
|
||
"""
|
||
Returns a string label like 'Water -> Agriculture'
|
||
by interpreting the tens digit as the 'from' class
|
||
and the ones digit as the 'to' class.
|
||
|
||
Example:
|
||
code = 34
|
||
tens_digit = 3 -> 'Forest'
|
||
ones_digit = 4 -> 'Agriculture'
|
||
return 'Forest -> Agriculture'
|
||
"""
|
||
# Define what each digit means
|
||
digit_to_label = {
|
||
1: "Water",
|
||
2: "Urban",
|
||
3: "Forest",
|
||
4: "Agriculture"
|
||
}
|
||
|
||
# Tens digit = 'from' class
|
||
tens_digit = code // 10
|
||
from_label = digit_to_label.get(tens_digit, "Unknown")
|
||
|
||
# Ones digit = 'to' class
|
||
ones_digit = code % 10
|
||
to_label = digit_to_label.get(ones_digit, "Unknown")
|
||
|
||
return f"{from_label} -> {to_label}"
|
||
|
||
def generate_change_detection_report(tif_path: str, output_csv_path: str = None) -> pd.DataFrame:
|
||
"""
|
||
Reads a single-band change-detection GeoTIFF with classes from 11 to 44,
|
||
generates a report of pixel counts, area, and applies a label for every code.
|
||
|
||
:param tif_path: Path to the input GeoTIFF.
|
||
:param output_csv_path: If provided, saves the report as a CSV file.
|
||
:return: A pandas DataFrame with columns: ['Class_Value', 'Pixel_Count', 'Area', 'Label'].
|
||
"""
|
||
# 1. Open the raster
|
||
with rasterio.open(tif_path) as src:
|
||
band = src.read(1)
|
||
transform = src.transform
|
||
|
||
# Compute pixel area
|
||
pixel_width = transform[0]
|
||
pixel_height = -transform[4] # Typically negative
|
||
pixel_area = pixel_width * pixel_height
|
||
|
||
# 2. Count unique class values
|
||
unique_vals, counts = np.unique(band, return_counts=True)
|
||
|
||
# 3. Filter classes from 11 to 44
|
||
valid_mask = (unique_vals >= 11) & (unique_vals <= 44)
|
||
filtered_classes = unique_vals[valid_mask]
|
||
filtered_counts = counts[valid_mask]
|
||
|
||
# 4. Calculate area per class
|
||
areas = filtered_counts * pixel_area
|
||
|
||
# 5. Create a DataFrame
|
||
df_report = pd.DataFrame({
|
||
"Class_Value": filtered_classes,
|
||
"Pixel_Count": filtered_counts,
|
||
"Area": areas
|
||
})
|
||
|
||
# 6. Apply the custom mapping function to get a text label (e.g., 'Water -> Agriculture')
|
||
df_report["Label"] = df_report["Class_Value"].apply(map_two_digit_code)
|
||
|
||
# 7. (Optional) sort by Class_Value
|
||
df_report.sort_values(by="Class_Value", inplace=True)
|
||
|
||
# 8. Optionally save the DataFrame to a CSV file
|
||
if output_csv_path:
|
||
df_report.to_csv(output_csv_path, index=False)
|
||
|
||
return df_report
|
||
|
||
# ---------------
|
||
# Example usage:
|
||
if __name__ == "__main__":
|
||
input_tif = "change_detected/change_detection.tif" # Update path as needed
|
||
report_df = generate_change_detection_report(
|
||
tif_path=input_tif,
|
||
output_csv_path="report/change_detection_report.csv"
|
||
)
|
||
|
||
print("Detailed Report (11–44):")
|
||
print(report_df)
|
||
|
||
# Optional: Summarize area by the Label column
|
||
summary = report_df.groupby("Label")["Area"].sum().reset_index()
|
||
summary.columns = ["Label", "Total_Area"]
|
||
print("\nArea Summary by Label:")
|
||
print(summary)
|