Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 47 additions & 14 deletions ftw_tools/inference/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import geopandas as gpd
import numpy as np
import shapely
from fiboa_cli.parquet import create_parquet
from fiboa_cli.registry import Registry
from vecorel_cli.encoding.geoparquet import GeoParquet
from vecorel_cli.vecorel.collection import Collection


def merge_polygons(
Expand Down Expand Up @@ -144,9 +146,8 @@ def postprocess_instance_polygons(
contain_thresh=overlap_contain_threshold,
)

# Convert to hectares
polygons["area"] = polygons.geometry.area * 0.0001
polygons["perimeter"] = polygons.geometry.length
polygons["metrics:area"] = polygons.geometry.area # in m²
polygons["metrics:perimeter"] = polygons.geometry.length # in m

# Convert back to original CRS
polygons.to_crs(src_crs, inplace=True)
Expand All @@ -157,9 +158,29 @@ def postprocess_instance_polygons(
return polygons


def convert_to_fiboa(
polygons: gpd.GeoDataFrame, output: str, timestamp: str | None
) -> gpd.GeoDataFrame:
def features_to_dataframe(features, columns):
"""Convert a list of features to a GeoDataFrame.

Args:
features: The features to convert.
columns: The columns to include in the output GeoDataFrame.

Returns:
The converted GeoDataFrame.
"""
data = []
for feature in features:
row = {}
for col in columns:
if col == "geometry":
row[col] = shapely.geometry.shape(feature["geometry"])
else:
row[col] = feature["properties"].get(col, None)
data.append(row)
return gpd.GeoDataFrame(data, geometry="geometry")


def convert_to_fiboa(polygons: gpd.GeoDataFrame, output: str, timestamp: str | None):
"""Convert polygons to fiboa parquet format.

Args:
Expand All @@ -168,22 +189,34 @@ def convert_to_fiboa(
timestamp: The timestamp of the image.

Returns:
The converted polygons.
Nothing. The converted polygons are written to the output file.
"""
Comment thread
m-mohr marked this conversation as resolved.
polygons["determination_method"] = "auto-imagery"
polygons["determination:method"] = "auto-imagery"

config = collection = {"fiboa_version": "0.2.0"}
columns = ["id", "area", "perimeter", "determination_method", "geometry"]
columns = [
"id",
"geometry",
"metrics:area",
"metrics:perimeter",
"determination:method",
]
extensions = ["https://vecorel.org/geometry-metrics-extension/v0.1.0/schema.yaml"]

if timestamp is not None:
pattern = re.compile(
r"^(\d{4})[:-](\d{2})[:-](\d{2})[T\s](\d{2}):(\d{2}):(\d{2}).*$"
)
if pattern.match(timestamp):
timestamp = re.sub(pattern, r"\1-\2-\3T\4:\5:\6Z", timestamp)
polygons["determination_datetime"] = timestamp
columns.append("determination_datetime")
polygons["determination:datetime"] = timestamp
columns.append("determination:datetime")
else:
print("WARNING: Unable to parse timestamp from TIFFTAG_DATETIME tag.")

create_parquet(polygons, columns, collection, output, config, compression="brotli")
collection = Collection(
Registry.get_default_collection("ftw", extensions=extensions)
)

file = GeoParquet(output)
file.set_collection(collection)
file.write(polygons, properties=columns)
Comment thread
m-mohr marked this conversation as resolved.
33 changes: 17 additions & 16 deletions ftw_tools/postprocess/polygonize.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
import rasterio.features
import shapely.geometry
from affine import Affine
from fiboa_cli.parquet import create_parquet, features_to_dataframe
from pyproj import CRS, Transformer
from rtree import index
from shapely.ops import transform, unary_union
from skimage.morphology import dilation, erosion
from tqdm import tqdm

from ftw_tools.inference.utils import convert_to_fiboa, features_to_dataframe
from ftw_tools.settings import SUPPORTED_POLY_FORMATS_TXT


Expand Down Expand Up @@ -122,8 +122,8 @@ def merge_adjacent_polygons(features, ratio):
u = unary_union([geoms[k] for k in idxs])
props = {
"id": ",".join([ids[k] for k in idxs if ids[k]]),
"area": float(u.area),
"perimeter": float(u.length),
"metrics:area": float(u.area),
"metrics:perimeter": float(u.length),
}
Comment thread
m-mohr marked this conversation as resolved.
out.append({"geometry": shapely.geometry.mapping(u), "properties": props})

Expand Down Expand Up @@ -331,7 +331,11 @@ def polygonize(
rows = []
schema = {
"geometry": "Polygon",
"properties": {"id": "str", "area": "float", "perimeter": "float"},
"properties": {
"id": "str",
"metrics:area": "float",
"metrics:perimeter": "float",
},
}
i = 1
# read the input file as a mask
Expand Down Expand Up @@ -468,9 +472,8 @@ def polygonize(
"geometry": shapely.geometry.mapping(g),
"properties": {
"id": str(i),
"area": g.area
* 0.0001, # Add the area in hectares
"perimeter": g.length, # Add the perimeter in meters
"metrics:area": g.area, # area in m²
"metrics:perimeter": g.length, # perimeter in m
},
}
)
Expand All @@ -481,9 +484,8 @@ def polygonize(
"geometry": shapely.geometry.mapping(geom),
"properties": {
"id": str(i),
"area": area
* 0.0001, # Add the area in hectares
"perimeter": perimeter, # Add the perimeter in meters
"metrics:area": area, # area in m²
"metrics:perimeter": perimeter, # perimeter in m
},
}
)
Expand All @@ -507,18 +509,17 @@ def polygonize(
print("WARNING: Unable to parse timestamp from TIFFTAG_DATETIME tag.")
timestamp = None

config = collection = {"fiboa_version": "0.2.0"}
columns = ["geometry", "determination_method"] + list(
columns = ["geometry", "determination:method"] + list(
schema["properties"].keys()
)
gdf = features_to_dataframe(rows, columns)
gdf.set_crs(original_crs, inplace=True, allow_override=True)
gdf["determination_method"] = "auto-imagery"
gdf["determination:method"] = "auto-imagery"
if timestamp is not None:
gdf["determination_datetime"] = timestamp
columns.append("determination_datetime")
gdf["determination:datetime"] = timestamp
columns.append("determination:datetime")

create_parquet(gdf, columns, collection, out, config, compression="brotli")
convert_to_fiboa(gdf[columns], out, timestamp)
Comment thread
m-mohr marked this conversation as resolved.
else:
print(
"WARNING: The fiboa-compliant GeoParquet output format is recommended for field boundaries."
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ classifiers = [
dependencies = [
"click>=8.2.1,<9",
"dask[distributed]>=2025.5.1",
"fiboa-cli==0.7",
"fiboa-cli>=0.21,<0.22",
"fiona>=1.9,<2",
"geopandas>=0.14,<2",
"kornia>=0.7,<1",
Expand Down
8 changes: 4 additions & 4 deletions tests/test_merge_adjacent.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,10 @@ def test_merge_adjacent_properties():
merged = result[0]
assert "A" in merged["properties"]["id"]
assert "B" in merged["properties"]["id"]
assert "area" in merged["properties"]
assert "perimeter" in merged["properties"]
assert merged["properties"]["area"] > 0
assert merged["properties"]["perimeter"] > 0
assert "metrics:area" in merged["properties"]
assert "metrics:perimeter" in merged["properties"]
assert merged["properties"]["metrics:area"] > 0
assert merged["properties"]["metrics:perimeter"] > 0


def test_merge_adjacent_performance():
Expand Down
Loading