Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding eps_t parameter to temporal tools for full 3D IoU calculation #749

Merged
merged 4 commits into from
Nov 8, 2023
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
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