# -*- coding: utf-8 -*-
# Author: vkaff
# E-mail: vkaffes@imis.athena-innovation.gr
import numpy as np
from polygon_classification import config
from shapely.geometry import LineString, Point
from shapely.wkt import loads
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
[docs]class Features:
"""This class builds features regarding polygon properties.
See Also
--------
:func:`compute_features`: Details on the implemented features.
"""
def __init__(self):
pass
[docs] def build(self, X):
"""Build features and return them as an ndarray of floats.
Parameters
----------
X: array-like or sparse matrix, shape = [n_samples, n_features]
The train/test input samples.
Returns
-------
fX: ndarray
The computed features to use as input to ML classifiers.
"""
fX = np.asarray(list(map(self.compute_features, X['pst_geom'], X['dian_geom'])), dtype=float)
# print('Before normalization: ', np.amin(fX, axis=0), np.amax(fX, axis=0))
fX = MinMaxScaler().fit_transform(fX)
# fX = StandardScaler().fit_transform(fX)
# fX = RobustScaler().fit_transform(fX)
# print('After normalization: ', np.amin(fX, axis=0), np.amax(fX, axis=0))
if np.any(np.isnan(fX)): print(np.where(np.isnan(fX)))
if not np.any(np.isfinite(fX)): print('infinite')
return fX
[docs] def compute_features(self, poly1, poly2):
"""
This method builds an ndarray of the following features:
* *core*: basic geometric attributes, i.e.,
#. area of each polygon,
#. percentage of coverage/intersection area per polygon,
#. perimeter of each polygon,
#. number of corners of each polygon,
#. average edges' length per corner of each polygon,
#. variance of edges' length per corner of each polygon,
* *extra*: compute additional features on setting :py:attr:`~polygon_classification.config.MLConf.extra_features` to ``True``, i.e.,
1. area of of each polygon convex hull,
#. percentage of coverage/intersection of convex hull area per polygon,
#. distance of centroids of polygons
Parameters
----------
poly1, poly2: str
Input geometric objects, i.e., shapely Polygons.
Returns
-------
:obj:`list`
It returns a list (vector) of features.
"""
f = []
geom1 = loads(poly1)
geom2 = loads(poly2)
# convex hull
convex1 = geom1.convex_hull
convex2 = geom2.convex_hull
# area
area1 = geom1.area
area2 = geom2.area
convex_area1 = convex1.area
convex_area2 = convex2.area
# % coverage
intersect = geom1.intersection(geom2).area
cover1 = intersect / area1
cover2 = intersect / area2
convex_intersect = convex1.intersection(convex2).area
convex_cover1 = convex_intersect / convex_area1
convex_cover2 = convex_intersect / convex_area2
# polygon length
l1 = geom1.length
l2 = geom2.length
coords1 = list(zip(*geom1.exterior.coords.xy))
coords2 = list(zip(*geom2.exterior.coords.xy))
# no of coords
no_coords1 = len(coords1) - 1
no_coords2 = len(coords2) - 1
# calculate the length of each side of the poly
poly1_lengths = [LineString((coords1[i], coords1[i + 1])).length for i in range(len(coords1) - 1)]
poly2_lengths = [LineString((coords2[i], coords2[i + 1])).length for i in range(len(coords2) - 1)]
# avg length per edge
# avg1 = l1 / no_coords1
# avg2 = l2 / no_coords2
avg1 = np.mean(poly1_lengths)
avg2 = np.mean(poly2_lengths)
# std on edge lengths
# std1 = np.std(poly1_lengths)
# std2 = np.std(poly2_lengths)
var1 = np.var(poly1_lengths)
var2 = np.var(poly2_lengths)
# centroid dist
centroid1 = geom1.centroid.coords
centroid2 = geom2.centroid.coords
centroid_dist = Point(centroid1).distance(Point(centroid2))
f = [
area1, area2, cover1, cover2, l1, l2,
no_coords1, no_coords2, avg1, avg2, var1, var2,
]
if config.MLConf.extra_features: f += [convex_area1, convex_area2, convex_cover1, convex_cover2, centroid_dist]
return f