Preserve aspect ratio in false-positive review sheets
This commit is contained in:
@@ -141,7 +141,7 @@ def require_dependencies() -> dict[str, Any]:
|
||||
import rasterio
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
from pyproj import Geod, Transformer
|
||||
from shapely.geometry import box, shape
|
||||
from shapely.geometry import Polygon, box, shape
|
||||
from shapely.ops import transform
|
||||
except ImportError as exc:
|
||||
raise SystemExit(
|
||||
@@ -156,6 +156,7 @@ def require_dependencies() -> dict[str, Any]:
|
||||
"Geod": Geod,
|
||||
"Transformer": Transformer,
|
||||
"box": box,
|
||||
"Polygon": Polygon,
|
||||
"shape": shape,
|
||||
"transform": transform,
|
||||
}
|
||||
@@ -212,15 +213,23 @@ def draw_geometry(
|
||||
draw: Any,
|
||||
geometry: Any,
|
||||
inverse_transform: Any,
|
||||
scale_x: float,
|
||||
scale_y: float,
|
||||
scale: float,
|
||||
offset_x: float,
|
||||
offset_y: float,
|
||||
crop_left: float,
|
||||
crop_top: float,
|
||||
color: tuple[int, int, int],
|
||||
) -> None:
|
||||
for ring in polygon_rings(geometry):
|
||||
points = []
|
||||
for x, y in ring.coords:
|
||||
column, row = inverse_transform * (x, y)
|
||||
points.append((column * scale_x, row * scale_y))
|
||||
points.append(
|
||||
(
|
||||
(column - crop_left) * scale + offset_x,
|
||||
(row - crop_top) * scale + offset_y,
|
||||
)
|
||||
)
|
||||
if len(points) >= 2:
|
||||
draw.line(points, fill=color, width=2, joint="curve")
|
||||
|
||||
@@ -239,18 +248,44 @@ def render_card(
|
||||
Transformer = dependencies["Transformer"]
|
||||
shape = dependencies["shape"]
|
||||
transform_geometry = dependencies["transform"]
|
||||
box = dependencies["box"]
|
||||
Polygon = dependencies["Polygon"]
|
||||
|
||||
header_height = 88
|
||||
with rasterio.open(record["resolved_source_tile_path"]) as source:
|
||||
pixels = normalize_raster(source.read(), numpy)
|
||||
image = Image.fromarray(pixels, mode="RGB").resize(
|
||||
(thumb_size, thumb_size), Image.Resampling.BILINEAR
|
||||
bbox = record["bbox_json"]
|
||||
if (
|
||||
float(bbox["x_min"]) < 0
|
||||
or float(bbox["y_min"]) < 0
|
||||
or float(bbox["x_max"]) > source.width
|
||||
or float(bbox["y_max"]) > source.height
|
||||
):
|
||||
raise SystemExit(
|
||||
f"Detection bbox_json exceeds source tile dimensions: {record['candidate_feature_id']}"
|
||||
)
|
||||
bbox_width = float(bbox["x_max"]) - float(bbox["x_min"])
|
||||
bbox_height = float(bbox["y_max"]) - float(bbox["y_min"])
|
||||
crop_width = min(source.width, max(128, math.ceil(bbox_width * 4)))
|
||||
crop_height = min(source.height, max(128, math.ceil(bbox_height * 4)))
|
||||
center_x = (float(bbox["x_min"]) + float(bbox["x_max"])) / 2
|
||||
center_y = (float(bbox["y_min"]) + float(bbox["y_max"])) / 2
|
||||
crop_left = max(0, min(source.width - crop_width, round(center_x - crop_width / 2)))
|
||||
crop_top = max(0, min(source.height - crop_height, round(center_y - crop_height / 2)))
|
||||
crop_right = crop_left + crop_width
|
||||
crop_bottom = crop_top + crop_height
|
||||
image = Image.fromarray(pixels, mode="RGB").crop(
|
||||
(crop_left, crop_top, crop_right, crop_bottom)
|
||||
)
|
||||
scale = min(thumb_size / image.width, thumb_size / image.height)
|
||||
render_width = max(1, round(image.width * scale))
|
||||
render_height = max(1, round(image.height * scale))
|
||||
image = image.resize((render_width, render_height), Image.Resampling.BILINEAR)
|
||||
image_offset_x = (thumb_size - render_width) // 2
|
||||
image_offset_y = (thumb_size - render_height) // 2
|
||||
card = Image.new(
|
||||
"RGB", (thumb_size, thumb_size + header_height), color=(242, 245, 247)
|
||||
)
|
||||
card.paste(image, (0, header_height))
|
||||
card.paste(image, (image_offset_x, header_height + image_offset_y))
|
||||
draw = ImageDraw.Draw(card)
|
||||
font = ImageFont.load_default()
|
||||
draw.rectangle((0, 0, thumb_size, header_height), fill=(22, 29, 38))
|
||||
@@ -279,29 +314,33 @@ def render_card(
|
||||
(6, 72), "green ref | blue miss", fill=(235, 238, 241), font=font
|
||||
)
|
||||
|
||||
scale_x = thumb_size / source.width
|
||||
scale_y = thumb_size / source.height
|
||||
bbox = record["bbox_json"]
|
||||
draw.rectangle(
|
||||
(
|
||||
max(0, float(bbox["x_min"]) * scale_x),
|
||||
header_height + max(0, float(bbox["y_min"]) * scale_y),
|
||||
min(thumb_size - 1, float(bbox["x_max"]) * scale_x),
|
||||
header_height + min(thumb_size - 1, float(bbox["y_max"]) * scale_y),
|
||||
),
|
||||
outline=(231, 76, 60),
|
||||
width=3,
|
||||
candidate_box = (
|
||||
(float(bbox["x_min"]) - crop_left) * scale + image_offset_x,
|
||||
header_height
|
||||
+ (float(bbox["y_min"]) - crop_top) * scale
|
||||
+ image_offset_y,
|
||||
(float(bbox["x_max"]) - crop_left) * scale + image_offset_x,
|
||||
header_height
|
||||
+ (float(bbox["y_max"]) - crop_top) * scale
|
||||
+ image_offset_y,
|
||||
)
|
||||
|
||||
overlay_count = 0
|
||||
if source.crs:
|
||||
transformer = Transformer.from_crs("EPSG:4326", source.crs, always_xy=True)
|
||||
bounds = box(*source.bounds)
|
||||
crop_bounds = Polygon(
|
||||
[
|
||||
source.transform * (crop_left, crop_top),
|
||||
source.transform * (crop_right, crop_top),
|
||||
source.transform * (crop_right, crop_bottom),
|
||||
source.transform * (crop_left, crop_bottom),
|
||||
]
|
||||
)
|
||||
overlay = Image.new("RGBA", (thumb_size, thumb_size), (0, 0, 0, 0))
|
||||
overlay_draw = ImageDraw.Draw(overlay)
|
||||
for reference in references:
|
||||
geometry = transform_geometry(transformer.transform, shape(reference["geometry"]))
|
||||
if geometry.is_empty or not geometry.intersects(bounds):
|
||||
if geometry.is_empty or not geometry.intersects(crop_bounds):
|
||||
continue
|
||||
color = (
|
||||
(39, 174, 96)
|
||||
@@ -312,12 +351,16 @@ def render_card(
|
||||
overlay_draw,
|
||||
geometry,
|
||||
~source.transform,
|
||||
scale_x,
|
||||
scale_y,
|
||||
scale,
|
||||
image_offset_x,
|
||||
image_offset_y,
|
||||
crop_left,
|
||||
crop_top,
|
||||
color,
|
||||
)
|
||||
overlay_count += 1
|
||||
card.paste(overlay, (0, header_height), overlay)
|
||||
draw.rectangle(candidate_box, outline=(231, 76, 60), width=3)
|
||||
return card, overlay_count
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user