File size: 13,363 Bytes
588ce8d
 
 
70b8d29
 
 
 
 
588ce8d
70b8d29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
afeb582
588ce8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
afeb582
 
588ce8d
 
 
afeb582
588ce8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3e8b98f
588ce8d
 
 
afeb582
588ce8d
 
 
 
7b5103c
588ce8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3e8b98f
588ce8d
 
 
 
 
 
 
 
7b5103c
588ce8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
690e199
 
588ce8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70b8d29
588ce8d
 
 
70b8d29
 
 
 
 
 
 
588ce8d
 
 
 
 
 
 
 
 
 
 
 
 
2098c98
 
588ce8d
70b8d29
588ce8d
 
 
 
 
 
 
 
 
 
2098c98
 
 
 
 
 
 
 
 
 
 
 
 
588ce8d
 
 
 
 
2098c98
588ce8d
2098c98
588ce8d
 
2098c98
588ce8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import numpy as np
import matplotlib.pyplot as plt

import numpy as np 
def mask_to_boxes(mask):
    """ Convert a boolean (Height x Width) mask into a (N x 4) array of NON-OVERLAPPING bounding boxes
    surrounding "islands of truth" in the mask.  Boxes indicate the (Left, Top, Right, Bottom) bounds
    of each island, with Right and Bottom being NON-INCLUSIVE (ie they point to the indices AFTER the island).

    This algorithm (Downright Boxing) does not necessarily put separate connected components into
    separate boxes.

    You can "cut out" the island-masks with
        boxes = mask_to_boxes(mask)
        island_masks = [mask[t:b, l:r] for l, t, r, b in boxes]
    """
    max_ix = max(s+1 for s in mask.shape)   # Use this to represent background
    # These arrays will be used to carry the "box start" indices down and to the right.
    x_ixs = np.full(mask.shape, fill_value=max_ix)
    y_ixs = np.full(mask.shape, fill_value=max_ix)

    # Propagate the earliest x-index in each segment to the bottom-right corner of the segment
    for i in range(mask.shape[0]):
        x_fill_ix = max_ix
        for j in range(mask.shape[1]):
            above_cell_ix = x_ixs[i-1, j] if i>0 else max_ix
            still_active = mask[i, j] or ((x_fill_ix != max_ix) and (above_cell_ix != max_ix))
            x_fill_ix = min(x_fill_ix, j, above_cell_ix) if still_active else max_ix
            x_ixs[i, j] = x_fill_ix

    # Propagate the earliest y-index in each segment to the bottom-right corner of the segment
    for j in range(mask.shape[1]):
        y_fill_ix = max_ix
        for i in range(mask.shape[0]):
            left_cell_ix = y_ixs[i, j-1] if j>0 else max_ix
            still_active = mask[i, j] or ((y_fill_ix != max_ix) and (left_cell_ix != max_ix))
            y_fill_ix = min(y_fill_ix, i, left_cell_ix) if still_active else max_ix
            y_ixs[i, j] = y_fill_ix

    # Find the bottom-right corners of each segment
    new_xstops = np.diff((x_ixs != max_ix).astype(np.int32), axis=1, append=False)==-1
    new_ystops = np.diff((y_ixs != max_ix).astype(np.int32), axis=0, append=False)==-1
    corner_mask = new_xstops & new_ystops
    y_stops, x_stops = np.array(np.nonzero(corner_mask))

    # Extract the boxes, getting the top-right corners from the index arrays
    x_starts = x_ixs[y_stops, x_stops]
    y_starts = y_ixs[y_stops, x_stops]
    ltrb_boxes = np.hstack([x_starts[:, None], y_starts[:, None], x_stops[:, None]+1, y_stops[:, None]+1])
    return ltrb_boxes


def get_top_predictions(prediction = None, threshold = 0.8):
    if prediction is None:
        return None, None
    else:
        sorted_scores_ids = prediction.pred_instances.scores.argsort()[::-1]
        sorted_scores = prediction.pred_instances.scores[sorted_scores_ids]
        sorted_predictions = prediction.pred_instances.labels[sorted_scores_ids]
        return {'pred_above_thresh': sorted_predictions[sorted_scores > threshold], 
                'pred_above_thresh_id': sorted_scores_ids[sorted_scores > threshold],
                'pred_above_thresh_scores': sorted_scores[sorted_scores > threshold],
                'pred_above_thresh_bboxes': prediction.pred_instances['bboxes'][sorted_scores_ids][sorted_scores > threshold]}
    
def add_class_labels(top_pred = {}, class_labels = None):
    if class_labels == None:
        print('No class labels provided, returning original dictionary')
        return top_pred
    else:
        top_pred['pred_above_thresh_labels'] = [class_labels[x].lower() for x in top_pred['pred_above_thresh']]
        #print(top_pred['pred_above_thresh_labels'])
        #print(top_pred['pred_above_thresh_scores'])
        top_pred['any_detection'] = len(top_pred['pred_above_thresh_labels']) > 0
        if top_pred['any_detection']:
            # Get shark / human / unknown vectors
            top_pred['is_shark'] = np.array([1 if 'shark' in x and 'shadow' not in x else 0 for x in top_pred['pred_above_thresh_labels']])
            top_pred['is_human'] = np.array([1 if 'person' in x else 1 if 'surfer' in x else 0 for x in top_pred['pred_above_thresh_labels']])
            top_pred['is_unknown'] = np.array([1 if 'unidentifiable' in x else 0 for x in top_pred['pred_above_thresh_labels']])
            # Get shark / human / unknown  numbers of detections
            top_pred['shark_n'] = np.sum(top_pred['is_shark'])
            top_pred['human_n'] = np.sum(top_pred['is_human'])
            top_pred['unknown_n'] = np.sum(top_pred['is_unknown'])
        else:
            # Get shark / human / unknown vectors
            top_pred['is_shark'] = None
            top_pred['is_human'] = None
            top_pred['is_unknown'] = None
            # Get shark / human / unknown  numbers of detections
            top_pred['shark_n'] = 0
            top_pred['human_n'] = 0
            top_pred['unknown_n'] = 0
        return top_pred

def add_class_sizes(top_pred = {}, class_sizes = None):
    size_list = []
    shark_size_list = []
    if top_pred['any_detection']:
        for tmp_pred in top_pred['pred_above_thresh_labels']:
                tmp_class_sizes = class_sizes[tmp_pred.lower()]
                if tmp_class_sizes == None:
                    size_list.append(None)
                    continue 
                else:
                    size_list.append(tmp_class_sizes['feet'])

                if 'shark' in tmp_pred.lower() and 'shadow' not in tmp_pred.lower() :
                    shark_size_list.append(np.mean(tmp_class_sizes['feet']))

        top_pred['pred_above_thresh_sizes'] = size_list

        if top_pred['shark_n'] > 0 and len(shark_size_list) > 0:
            top_pred['biggest_shark_size'] = np.max(shark_size_list)
        else:
            top_pred['biggest_shark_size'] = None
    else:
        top_pred['pred_above_thresh_sizes'] = None
        top_pred['biggest_shark_size'] = None
    return top_pred

def add_class_weights(top_pred = {}, class_weights = None):
    weight_list = []
    shark_weight_list = []
    if top_pred['any_detection']:
        for tmp_pred in top_pred['pred_above_thresh_labels']:
                tmp_class_weights = class_weights[tmp_pred.lower()]
                if tmp_class_weights == None:
                    weight_list.append(None)
                    continue
                else:
                    weight_list.append(tmp_class_weights['pounds'])

                if 'shark' in tmp_pred.lower():
                    shark_weight_list.append(np.mean(tmp_class_weights['pounds']))

        top_pred['pred_above_thresh_weights'] = weight_list

        if top_pred['shark_n'] > 0 and len(shark_weight_list) > 0:
            top_pred['biggest_shark_weight'] = np.max(shark_weight_list)
        else:
            top_pred['biggest_shark_weight'] = None
    else:
        top_pred['pred_above_thresh_weights'] = None
        top_pred['biggest_shark_weight'] = None
    return top_pred

# Sizes
def get_min_distance_shark_person(top_pred, class_sizes = None, dangerous_distance = 100):
    min_dist = 99999
    dist_calculated = False
    # Calculate distance for every pairing of human and shark
    # and accumulate the min distance
    for i, tmp_shark in enumerate(top_pred['is_shark']):
        for j, tmp_person in enumerate(top_pred['is_human']):
            if tmp_shark == 1 and tmp_person == 1:
                dist_calculated = True
                #print(top_pred['pred_above_thresh_bboxes'][i])
                #print(top_pred['pred_above_thresh_bboxes'][j])
                tmp_dist_feed = _calculate_dist_estimate(top_pred['pred_above_thresh_bboxes'][i], 
                                                         top_pred['pred_above_thresh_bboxes'][j], 
                                                         [top_pred['pred_above_thresh_labels'][i], top_pred['pred_above_thresh_labels'][j]],
                                                         class_sizes,
                                                         measurement = 'feet')
                #print(tmp_dist_feed)
                min_dist = min(min_dist, tmp_dist_feed)
            else:
                pass
    return {'min_dist': str(round(min_dist,1)) + ' feet' if dist_calculated else '', 
            'any_dist_calculated': dist_calculated, 
            'dangerous_dist': min_dist < dangerous_distance}

def _calculate_dist_estimate(bbox1, bbox2, labels, class_sizes = None, measurement = 'feet'):
    if class_sizes[labels[0]] == None or class_sizes[labels[1]] == None:
        return 9999
    class_feet_size_mean = np.array([class_sizes[labels[0]][measurement][0], 
                                     class_sizes[labels[1]][measurement][0]]).mean()
    box_pixel_size_mean = np.array([np.linalg.norm(bbox1[[0, 1]] - bbox1[[2, 3]]), 
                                    np.linalg.norm(bbox2[[0, 1]] - bbox2[[2, 3]])]).mean()
    
    # Calculate the max size of the two boxes
    box_center_1 = np.array([(bbox1[2] - bbox1[0])/2 + bbox1[0], 
                             (bbox1[3] - bbox1[1])/2 + bbox1[1]])
    box_center_2 = np.array([(bbox2[2] - bbox2[0])/2 + bbox2[0], 
                             (bbox2[3] - bbox2[1])/2 + bbox2[1]])
    
    # Return ratio distance
    return np.linalg.norm(box_center_1 - box_center_2) / box_pixel_size_mean * class_feet_size_mean

# bboxes info!
# 1 x1 (left, lower pixel number)
# 2 y1 (top , lower pixel number)
# 3 x2 (right, higher pixel number)
# 4 y2 (bottom, higher pixel number)


def process_results_for_plot(predictions = None, threshold = 0.5, classes = None,
                             class_sizes = None, dangerous_distance = 100):
    
    # Attempt to create bounding boxes for humans(surfers) detected.  
    # mask = predictions.pred_panoptic_seg.sem_seg[0]
    # print(np.unique(mask))
    # mask = mask >2000
    # bboxes = mask_to_boxes(mask)
    # bboxes = [box for box in bboxes if box[2]*box[3]>100]
    
    top_pred = get_top_predictions(predictions, threshold = threshold)
    top_pred = add_class_labels(top_pred, class_labels = classes)
    top_pred = add_class_sizes(top_pred, class_sizes = class_sizes)
    top_pred = add_class_weights(top_pred, class_weights = class_sizes)
    
    if len(top_pred['pred_above_thresh']) > 0:
        min_dist = get_min_distance_shark_person(top_pred, class_sizes = class_sizes)
    else:
        min_dist = {'any_dist_calculated': False,
                    'min_dist': '',
                    'dangerous_dist': False}

    return {'min_dist_str': min_dist['min_dist'], 
            'shark_suspected': top_pred['shark_n'] > 0,
            'human_suspected': top_pred['human_n'] > 0,
            'shark_n': top_pred['shark_n'],
            #'human_n': max(top_pred['human_n'],len(bboxes)-top_pred['shark_n']),
            'human_n': top_pred['human_n'],
            'human_and_shark': (top_pred['shark_n'] > 0) and (top_pred['human_n'] > 0),
            'dangerous_dist': min_dist['dangerous_dist'],
            'dist_calculated': min_dist['any_dist_calculated'],
            'biggest_shark_size': '' if top_pred['biggest_shark_size'] == None else str(round(top_pred['biggest_shark_size'],1)) + ' feet',
            'biggest_shark_weight': '' if top_pred['biggest_shark_weight'] == None else str(round(top_pred['biggest_shark_weight'],1)) + ' pounds',
            }

def prediction_dashboard(top_pred = None):
    # Bullet points:
    if top_pred['shark_sighted'] > 0:
        shark_suspected = 'Shark Sighted !'
    elif top_pred['shark_suspected'] > 0:
        shark_suspected = 'Shark Suspected !'
    else:
        shark_suspected = 'No Sharks ...'

    if top_pred['human_sighted'] > 0:
        human_suspected = 'Human Sighted !'
    elif top_pred['human_suspected'] > 0:
        human_suspected = 'Human Suspected !'
    else:
        human_suspected = 'No Humans ...'

    shark_size_estimate = 'Biggest shark size: ' + str(top_pred['biggest_shark_size'])
    shark_weight_estimate = 'Biggest shark weight: ' + str(top_pred['biggest_shark_weight'])

    danger_level = 'Danger Level: ' 
    danger_level += 'High' if top_pred['dangerous_dist_confirmed'] else 'Low'

    danger_color = 'orangered' if top_pred['dangerous_dist_confirmed'] else 'yellowgreen'

    # Create a list of strings to plot
    strings = [shark_suspected, human_suspected, shark_size_estimate, shark_weight_estimate, danger_level]

    # Create a figure and axis
    fig, ax = plt.subplots()
    fig.set_facecolor((35/255,40/255,54/255))

    # Hide axes
    ax.axis('off')

    # Position for starting to place text, starting from top
    y_pos = 0.7

    # Iterate through list and place each item as text on the plot
    for s in strings:
        if 'danger' in s.lower():
            ax.text(0.05, y_pos, s, transform=ax.transAxes, fontsize=16, color=danger_color)
        else:
            ax.text(0.05, y_pos, s, transform=ax.transAxes, fontsize=16, color=(0, 204/255, 153/255))
        y_pos -= 0.1  # move down for next item

    # plt.tight_layout()
    # If we haven't already shown or saved the plot, then we need to
    # draw the figure first...
    fig.canvas.draw();

    # Now we can save it to a numpy array.
    data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
    data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))
    plt.close()
    #plt.savefig('tmp.png', format='png')
    return data #plt.show()