Skip to content

Commit

Permalink
Merge pull request #749 from ramanakumars/temporal_shape_fix
Browse files Browse the repository at this point in the history
adding eps_t parameter to temporal tools for full 3D IoU calculation
  • Loading branch information
CKrawczyk authored Nov 8, 2023
2 parents d038e73 + 8e850d5 commit 7a2b50f
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 69 deletions.
38 changes: 27 additions & 11 deletions panoptes_aggregation/reducers/shape_metric_IoU.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def panoptes_to_geometry(params, shape):
raise ValueError('The IoU metric only works with the following shapes: rectangle, rotateing rectangle, circle, ellipse, or triangle')


def IoU_metric(params1, params2, shape):
def IoU_metric(params1, params2, shape, eps_t=None):
'''Find the Intersection of Union distance between two shapes.
Parameters
Expand All @@ -92,6 +92,9 @@ def IoU_metric(params1, params2, shape):
shape : string
The shape these parameters belong to (see :meth:`panoptes_to_geometry` for
supported shapes)
eps_t : float
For temporal tools, this defines the temporal width of the rectangle.
Two shapes are connected if the displayTime parameters are within eps_t.
Returns
-------
Expand All @@ -105,16 +108,29 @@ def IoU_metric(params1, params2, shape):
intersection = 0
if geo1.intersects(geo2):
intersection = geo1.intersection(geo2).area
union = geo1.union(geo2).area
if union == 0:
# catch divide by zero (i.e. cases when neither shape has an area)
return numpy.inf

if 'temporal' in shape:
# combine the shape IoU with the time difference and normalize
return 0.5 * ((1 - intersection / union) + numpy.abs(params1[-1] - params2[-1]))
# build two boxes in the time domain with width eps_t and height 1
# centered at (t - eps_t / 2, 0.5) and calculate the intersection in time
time_params1 = (params1[-1] - eps_t, 0, eps_t, 1)
time_params2 = (params2[-1] - eps_t, 0, eps_t, 1)
time_geo1 = panoptes_to_geometry(time_params1, 'rectangle')
time_geo2 = panoptes_to_geometry(time_params2, 'rectangle')
time_intersection = 0
if time_geo1.intersects(time_geo2):
time_intersection = time_geo1.intersection(time_geo2).area

intersection = intersection * time_intersection
union = ((geo1.area + geo2.area) * eps_t - intersection)
else:
return 1 - intersection / union
union = geo1.union(geo2).area

if union == 0:
# catch divide by zero (i.e. cases when neither shape has an area)
return numpy.inf

return 1 - intersection / union


def average_bounds(params_list, shape):
Expand Down Expand Up @@ -252,7 +268,7 @@ def scale_shape(params, shape, gamma):
raise ValueError('The IoU metric only works with the following shapes: rectangle, rotateing rectangle, circle, ellipse, or triangle')


def average_shape_IoU(params_list, shape):
def average_shape_IoU(params_list, shape, eps_t=None):
'''Find the average shape and standard deviation from a list of parameters with respect
to the IoU metric.
Expand All @@ -273,11 +289,11 @@ def average_shape_IoU(params_list, shape):
The standard deviation of the input shapes with respect to the IoU metric
'''
def sum_distance(x):
return sum([IoU_metric(x, p, shape)**2 for p in params_list])
return sum([IoU_metric(x, p, shape, eps_t)**2 for p in params_list])
# find shape that minimizes the variance in the IoU metric using bounds
m = scipy.optimize.shgo(
m = scipy.optimize.direct(
sum_distance,
sampling_method='sobol',
locally_biased=False,
bounds=average_bounds(params_list, shape)
)
# find the 1-sigma value
Expand Down
7 changes: 4 additions & 3 deletions panoptes_aggregation/reducers/shape_reducer_dbscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
from .shape_metric import get_shape_metric_and_avg
from .shape_metric_IoU import IoU_metric, average_shape_IoU


DEFAULTS = {
'eps': {'default': 5.0, 'type': float},
'eps_t': {'default': 0.5, 'type': float},
'min_samples': {'default': 3, 'type': int},
'algorithm': {'default': 'auto', 'type': str},
'leaf_size': {'default': 30, 'type': int},
Expand Down Expand Up @@ -67,6 +67,7 @@ def shape_reducer_dbscan(data_by_tool, **kwargs):
* `tool*_clusters_sigma` : The standard deviation of the average shape under the IoU metric
'''
shape = data_by_tool.pop('shape')
eps_t = kwargs.pop('eps_t', None)
shape_params = SHAPE_LUT[shape]
metric_type = kwargs.pop('metric_type', 'euclidean').lower()
symmetric = data_by_tool.pop('symmetric')
Expand All @@ -75,7 +76,7 @@ def shape_reducer_dbscan(data_by_tool, **kwargs):
kwargs['metric'] = metric
elif metric_type == 'iou':
kwargs['metric'] = IoU_metric
kwargs['metric_params'] = {'shape': shape}
kwargs['metric_params'] = {'shape': shape, 'eps_t': eps_t}
avg = average_shape_IoU
else:
raise ValueError('metric_type must be either "euclidean" or "IoU".')
Expand Down Expand Up @@ -104,7 +105,7 @@ def shape_reducer_dbscan(data_by_tool, **kwargs):
if metric_type == 'euclidean':
k_loc = avg(loc[idx])
elif metric_type == 'iou':
k_loc, sigma = avg(loc[idx], shape)
k_loc, sigma = avg(loc[idx], shape, eps_t)
clusters[frame].setdefault('{0}_clusters_sigma'.format(tool), []).append(float(sigma))
for pdx, param in enumerate(shape_params):
clusters[frame].setdefault('{0}_clusters_{1}'.format(tool, param), []).append(float(k_loc[pdx]))
Expand Down
5 changes: 4 additions & 1 deletion panoptes_aggregation/reducers/shape_reducer_hdbscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
DEFAULTS = {
'min_cluster_size': {'default': 5, 'type': int},
'min_samples': {'default': 3, 'type': int},
'eps_t': {'default': 0.5, 'type': float},
'algorithm': {'default': 'best', 'type': str},
'leaf_size': {'default': 40, 'type': int},
'p': {'default': None, 'type': float},
Expand Down Expand Up @@ -72,6 +73,7 @@ def shape_reducer_hdbscan(data_by_tool, **kwargs):
* `tool*_clusters_sigma` : The standard deviation of the average shape under the IoU metric
'''
shape = data_by_tool.pop('shape')
eps_t = kwargs.pop('eps_t', None)
shape_params = SHAPE_LUT[shape]
metric_type = kwargs.pop('metric_type', 'euclidean').lower()
symmetric = data_by_tool.pop('symmetric')
Expand All @@ -81,6 +83,7 @@ def shape_reducer_hdbscan(data_by_tool, **kwargs):
elif metric_type == 'iou':
kwargs['metric'] = IoU_metric
kwargs['shape'] = shape
kwargs['eps_t'] = eps_t
avg = average_shape_IoU
else:
raise ValueError('metric_type must be either "euclidean" or "IoU".')
Expand Down Expand Up @@ -112,7 +115,7 @@ def shape_reducer_hdbscan(data_by_tool, **kwargs):
if metric_type == 'euclidean':
k_loc = avg(loc[idx])
elif metric_type == 'iou':
k_loc, sigma = avg(loc[idx], shape)
k_loc, sigma = avg(loc[idx], shape, eps_t)
clusters[frame].setdefault('{0}_clusters_sigma'.format(tool), []).append(float(sigma))
for pdx, param in enumerate(shape_params):
clusters[frame].setdefault('{0}_clusters_{1}'.format(tool, param), []).append(float(k_loc[pdx]))
Expand Down
6 changes: 4 additions & 2 deletions panoptes_aggregation/reducers/shape_reducer_optics.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

DEFAULTS = {
'min_samples': {'default': 3, 'type': int},
'eps_t': {'default': 0.5, 'type': float},
'min_cluster_size': {'default': 2, 'type': int},
'algorithm': {'default': 'auto', 'type': str},
'leaf_size': {'default': 30, 'type': int},
Expand Down Expand Up @@ -71,6 +72,7 @@ def shape_reducer_optics(data_by_tool, **kwargs):
* `tool*_clusters_sigma` : The standard deviation of the average shape under the IoU metric
'''
shape = data_by_tool.pop('shape')
eps_t = kwargs.pop('eps_t', None)
shape_params = SHAPE_LUT[shape]
metric_type = kwargs.pop('metric_type', 'euclidean').lower()
symmetric = data_by_tool.pop('symmetric')
Expand All @@ -79,7 +81,7 @@ def shape_reducer_optics(data_by_tool, **kwargs):
kwargs['metric'] = metric
elif metric_type == 'iou':
kwargs['metric'] = IoU_metric
kwargs['metric_params'] = {'shape': shape}
kwargs['metric_params'] = {'shape': shape, 'eps_t': eps_t}
avg = average_shape_IoU
else:
raise ValueError('metric_type must be either "euclidean" or "IoU".')
Expand Down Expand Up @@ -110,7 +112,7 @@ def shape_reducer_optics(data_by_tool, **kwargs):
if metric_type == 'euclidean':
k_loc = avg(loc[idx])
elif metric_type == 'iou':
k_loc, sigma = avg(loc[idx], shape)
k_loc, sigma = avg(loc[idx], shape, eps_t)
clusters[frame].setdefault('{0}_clusters_sigma'.format(tool), []).append(float(sigma))
for pdx, param in enumerate(shape_params):
clusters[frame].setdefault('{0}_clusters_{1}'.format(tool, param), []).append(float(k_loc[pdx]))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@
"T0_toolIndex0_y_center": [580.0],
}
},
{
'frame0': {
'T0_toolIndex0_angle': [50],
'T0_toolIndex0_displayTime': [0.5],
'T0_toolIndex0_height': [100],
'T0_toolIndex0_width': [80],
'T0_toolIndex0_x_center': [500],
'T0_toolIndex0_y_center': [580]
},
}
]

kwargs_extra_data = {
Expand All @@ -107,7 +117,8 @@
6,
7,
8,
9
9,
10
]
}

Expand All @@ -122,30 +133,31 @@
(520.0, 510.0, 120.0, 50.0, 10.0, 1.0),
(530.0, 500.0, 150.0, 50.0, 12.0, 0.9),
(350.0, 620.0, 100.0, 80.0, 25.0, 0.6),
(350.0, 580.0, 80.0, 140.0, 20.0, 0.9)
(350.0, 580.0, 80.0, 140.0, 20.0, 0.9),
(500.0, 580.0, 80.0, 100.0, 50.0, 0.5)
]
},
'shape': 'temporalRotateRectangle',
'symmetric': False
}

reduced_data_dbscan = {
"frame0": {
"T0_toolIndex0_cluster_labels": [0, 0, 0, 1, 1, 1, 1, 1, 1],
"T0_toolIndex0_clusters_count": [3, 6],
"T0_toolIndex0_clusters_angle": [9.4, 21.5],
"T0_toolIndex0_clusters_displayTime": [0.1, 0.9],
"T0_toolIndex0_clusters_height": [60.7, 104.5],
"T0_toolIndex0_clusters_sigma": [0.2, 0.5],
"T0_toolIndex0_clusters_width": [138.0, 102.8],
"T0_toolIndex0_clusters_x_center": [502.2, 352.3],
"T0_toolIndex0_clusters_y_center": [503.9, 604.9],
"T0_toolIndex0_temporalRotateRectangle_angle": [10.0, 12.0, 15.0, 30.0, 10.0, 10.0, 12.0, 25.0, 20.0],
"T0_toolIndex0_temporalRotateRectangle_displayTime": [0.1, 0.1, 0.1, 0.7, 0.9, 1.0, 0.9, 0.6, 0.9],
"T0_toolIndex0_temporalRotateRectangle_height": [50.0, 60.0, 60.0, 120.0, 50.0, 50.0, 50.0, 80.0, 140.0],
"T0_toolIndex0_temporalRotateRectangle_width": [150.0, 160.0, 120.0, 110.0, 140.0, 120.0, 150.0, 100.0, 80.0],
"T0_toolIndex0_temporalRotateRectangle_x_center": [510.0, 490.0, 510.0, 360.0, 520.0, 520.0, 530.0, 350.0, 350.0],
"T0_toolIndex0_temporalRotateRectangle_y_center": [500.0, 515.0, 500.0, 570.0, 510.0, 510.0, 500.0, 620.0, 580.0]
'frame0': {
'T0_toolIndex0_cluster_labels': [0, 0, 0, 1, 2, 2, 2, 1, 1, -1],
'T0_toolIndex0_clusters_angle': [10.0, 110.0, 10.0],
'T0_toolIndex0_clusters_count': [3, 3, 3],
'T0_toolIndex0_clusters_displayTime': [0.1, 0.7, 0.9],
'T0_toolIndex0_clusters_height': [61.0, 98.5, 54.5],
'T0_toolIndex0_clusters_sigma': [0.4, 0.7, 0.4],
'T0_toolIndex0_clusters_width': [137.5, 143.3, 136.1],
'T0_toolIndex0_clusters_x_center': [502.9, 358.1, 522.3],
'T0_toolIndex0_clusters_y_center': [504.3, 584.7, 508.1],
'T0_toolIndex0_temporalRotateRectangle_angle': [10.0, 12.0, 15.0, 30.0, 10.0, 10.0, 12.0, 25.0, 20.0, 50.0],
'T0_toolIndex0_temporalRotateRectangle_displayTime': [0.1, 0.1, 0.1, 0.7, 0.9, 1.0, 0.9, 0.6, 0.9, 0.5],
'T0_toolIndex0_temporalRotateRectangle_height': [50.0, 60.0, 60.0, 120.0, 50.0, 50.0, 50.0, 80.0, 140.0, 100.0],
'T0_toolIndex0_temporalRotateRectangle_width': [150.0, 160.0, 120.0, 110.0, 140.0, 120.0, 150.0, 100.0, 80.0, 80.0],
'T0_toolIndex0_temporalRotateRectangle_x_center': [510.0, 490.0, 510.0, 360.0, 520.0, 520.0, 530.0, 350.0, 350.0, 500.0],
'T0_toolIndex0_temporalRotateRectangle_y_center': [500.0, 515.0, 500.0, 570.0, 510.0, 510.0, 500.0, 620.0, 580.0, 580.0]
}
}

Expand All @@ -159,31 +171,32 @@
network_kwargs=kwargs_extra_data,
pkwargs={'shape': 'temporalRotateRectangle'},
kwargs={
'eps': 0.5,
'eps': 0.8,
'min_samples': 2,
'eps_t': 0.5,
'metric_type': 'IoU',
},
test_name='TestShapeReducerTemporalRotateRectangleDbscan',
round=1
)

reduced_data_optics = {
"frame0": {
"T0_toolIndex0_cluster_labels": [0, 0, 0, 2, 1, 1, 1, 2, 2],
"T0_toolIndex0_clusters_angle": [9.4, 9.4, 20.0],
"T0_toolIndex0_clusters_count": [3, 3, 3],
"T0_toolIndex0_clusters_displayTime": [0.1, 0.9, 0.7],
"T0_toolIndex0_clusters_height": [60.7, 51.4, 143.7],
"T0_toolIndex0_clusters_sigma": [0.2, 0.2, 0.3],
"T0_toolIndex0_clusters_width": [138.0, 137.4, 92.1],
"T0_toolIndex0_clusters_x_center": [502.2, 522.5, 355.0],
"T0_toolIndex0_clusters_y_center": [503.9, 509.1, 583.8],
"T0_toolIndex0_temporalRotateRectangle_angle": [10.0, 12.0, 15.0, 30.0, 10.0, 10.0, 12.0, 25.0, 20.0],
"T0_toolIndex0_temporalRotateRectangle_displayTime": [0.1, 0.1, 0.1, 0.7, 0.9, 1.0, 0.9, 0.6, 0.9],
"T0_toolIndex0_temporalRotateRectangle_height": [50.0, 60.0, 60.0, 120.0, 50.0, 50.0, 50.0, 80.0, 140.0],
"T0_toolIndex0_temporalRotateRectangle_width": [150.0, 160.0, 120.0, 110.0, 140.0, 120.0, 150.0, 100.0, 80.0],
"T0_toolIndex0_temporalRotateRectangle_x_center": [510.0, 490.0, 510.0, 360.0, 520.0, 520.0, 530.0, 350.0, 350.0],
"T0_toolIndex0_temporalRotateRectangle_y_center": [500.0, 515.0, 500.0, 570.0, 510.0, 510.0, 500.0, 620.0, 580.0]
'frame0': {
'T0_toolIndex0_cluster_labels': [0, 0, 0, 2, 1, 1, 1, 2, 2, -1],
'T0_toolIndex0_clusters_angle': [10.0, 10.0, 110.0],
'T0_toolIndex0_clusters_count': [3, 3, 3],
'T0_toolIndex0_clusters_displayTime': [0.1, 0.9, 0.7],
'T0_toolIndex0_clusters_height': [61.0, 54.5, 98.5],
'T0_toolIndex0_clusters_sigma': [0.4, 0.4, 0.7],
'T0_toolIndex0_clusters_width': [137.5, 136.1, 143.3],
'T0_toolIndex0_clusters_x_center': [502.9, 522.3, 358.1],
'T0_toolIndex0_clusters_y_center': [504.3, 508.1, 584.7],
'T0_toolIndex0_temporalRotateRectangle_angle': [10.0, 12.0, 15.0, 30.0, 10.0, 10.0, 12.0, 25.0, 20.0, 50.0],
'T0_toolIndex0_temporalRotateRectangle_displayTime': [0.1, 0.1, 0.1, 0.7, 0.9, 1.0, 0.9, 0.6, 0.9, 0.5],
'T0_toolIndex0_temporalRotateRectangle_height': [50.0, 60.0, 60.0, 120.0, 50.0, 50.0, 50.0, 80.0, 140.0, 100.0],
'T0_toolIndex0_temporalRotateRectangle_width': [150.0, 160.0, 120.0, 110.0, 140.0, 120.0, 150.0, 100.0, 80.0, 80.0],
'T0_toolIndex0_temporalRotateRectangle_x_center': [510.0, 490.0, 510.0, 360.0, 520.0, 520.0, 530.0, 350.0, 350.0, 500.0],
'T0_toolIndex0_temporalRotateRectangle_y_center': [500.0, 515.0, 500.0, 570.0, 510.0, 510.0, 500.0, 620.0, 580.0, 580.0]
}
}

Expand All @@ -200,30 +213,31 @@
kwargs={
'min_samples': 2,
'metric_type': 'IoU',
'eps_t': 0.5
},
test_name='TestShapeReducerTemporalRotateRectangleOptics',
round=1
)

reduced_data_hdbscan = {
"frame0": {
"T0_toolIndex0_cluster_labels": [0, 0, 0, 2, 1, 1, 1, 2, 2],
"T0_toolIndex0_clusters_count": [3, 3, 3],
"T0_toolIndex0_cluster_probabilities": [1.0, 0.5, 1.0, 1.0, 1.0, 1.0, 0.6, 0.7, 1.0],
"T0_toolIndex0_clusters_angle": [9.4, 9.4, 20.0],
"T0_toolIndex0_clusters_displayTime": [0.1, 0.9, 0.7],
"T0_toolIndex0_clusters_height": [60.7, 51.4, 143.7],
"T0_toolIndex0_clusters_persistance": [0.5, 0.6, 0.1],
"T0_toolIndex0_clusters_sigma": [0.2, 0.2, 0.3],
"T0_toolIndex0_clusters_width": [138.0, 137.4, 92.1],
"T0_toolIndex0_clusters_x_center": [502.2, 522.5, 355.0],
"T0_toolIndex0_clusters_y_center": [503.9, 509.1, 583.8],
"T0_toolIndex0_temporalRotateRectangle_angle": [10.0, 12.0, 15.0, 30.0, 10.0, 10.0, 12.0, 25.0, 20.0],
"T0_toolIndex0_temporalRotateRectangle_displayTime": [0.1, 0.1, 0.1, 0.7, 0.9, 1.0, 0.9, 0.6, 0.9],
"T0_toolIndex0_temporalRotateRectangle_height": [50.0, 60.0, 60.0, 120.0, 50.0, 50.0, 50.0, 80.0, 140.0],
"T0_toolIndex0_temporalRotateRectangle_width": [150.0, 160.0, 120.0, 110.0, 140.0, 120.0, 150.0, 100.0, 80.0],
"T0_toolIndex0_temporalRotateRectangle_x_center": [510.0, 490.0, 510.0, 360.0, 520.0, 520.0, 530.0, 350.0, 350.0],
"T0_toolIndex0_temporalRotateRectangle_y_center": [500.0, 515.0, 500.0, 570.0, 510.0, 510.0, 500.0, 620.0, 580.0]
'T0_toolIndex0_cluster_labels': [1, 1, 1, 0, 2, 2, 2, 0, 0, 1],
'T0_toolIndex0_cluster_probabilities': [1.0, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 0.9, 1.0, 0.3],
'T0_toolIndex0_clusters_angle': [110.0, 100.1, 10.0],
'T0_toolIndex0_clusters_count': [3, 4, 3],
'T0_toolIndex0_clusters_displayTime': [0.7, 0.1, 0.9],
'T0_toolIndex0_clusters_height': [98.5, 137.8, 54.5],
'T0_toolIndex0_clusters_persistance': [0.1, 0.4, 0.4],
'T0_toolIndex0_clusters_sigma': [0.7, 0.7, 0.4],
'T0_toolIndex0_clusters_width': [143.3, 62.7, 136.1],
'T0_toolIndex0_clusters_x_center': [358.1, 502.9, 522.3],
'T0_toolIndex0_clusters_y_center': [584.7, 505.2, 508.1],
'T0_toolIndex0_temporalRotateRectangle_angle': [10.0, 12.0, 15.0, 30.0, 10.0, 10.0, 12.0, 25.0, 20.0, 50.0],
'T0_toolIndex0_temporalRotateRectangle_displayTime': [0.1, 0.1, 0.1, 0.7, 0.9, 1.0, 0.9, 0.6, 0.9, 0.5],
'T0_toolIndex0_temporalRotateRectangle_height': [50.0, 60.0, 60.0, 120.0, 50.0, 50.0, 50.0, 80.0, 140.0, 100.0],
'T0_toolIndex0_temporalRotateRectangle_width': [150.0, 160.0, 120.0, 110.0, 140.0, 120.0, 150.0, 100.0, 80.0, 80.0],
'T0_toolIndex0_temporalRotateRectangle_x_center': [510.0, 490.0, 510.0, 360.0, 520.0, 520.0, 530.0, 350.0, 350.0, 500.0],
'T0_toolIndex0_temporalRotateRectangle_y_center': [500.0, 515.0, 500.0, 570.0, 510.0, 510.0, 500.0, 620.0, 580.0, 580.0]
}
}

Expand All @@ -242,6 +256,7 @@
'allow_single_cluster': True,
'metric_type': 'IoU',
'min_samples': 1,
'eps_t': 0.5
},
test_name='TestShapeReducerTemporalRotateRectangleHdbscan',
round=1,
Expand Down

0 comments on commit 7a2b50f

Please sign in to comment.