Spaces:
Runtime error
Runtime error
oskarastrom
commited on
Commit
β’
128e4f0
1
Parent(s):
997e70d
Compatability with backend
Browse files- .gitignore +2 -0
- app.py +121 -199
- InferenceConfig.py β backend/InferenceConfig.py +28 -2
- aris.py β backend/aris.py +15 -59
- {gradio_scripts β backend}/aws_handler.py +44 -1
- dataloader.py β backend/dataloader.py +12 -9
- inference.py β backend/inference.py +22 -19
- main.py β backend/predict.py +9 -6
- pyDIDSON.py β backend/pyDIDSON.py +1 -1
- pyDIDSON_format.py β backend/pyDIDSON_format.py +0 -0
- uploader.py β backend/uploader.py +0 -7
- visualizer.py β backend/visualizer.py +0 -1
- dump.rdb +0 -0
- {gradio_scripts β frontend}/annotation_editor.js +0 -0
- {gradio_scripts β frontend}/annotation_handler.py +106 -2
- frontend/aris_crop.py +45 -0
- gradio_scripts/file_reader.py β frontend/custom_file_reader.py +0 -0
- {gradio_scripts β frontend}/pdf_handler.py +6 -38
- {gradio_scripts β frontend}/result_ui.py +72 -8
- frontend/state_handler.py +22 -0
- {gradio_scripts β frontend}/upload_ui.py +17 -12
- gradio_scripts/state_handler.py +0 -451
- lib/fish_eye/.gitignore +0 -104
- lib/fish_eye/tracker.py +4 -4
- lib/fish_eye/{bytetrack.py β tracker_bytetrack.py} +0 -0
- lib/fish_eye/{sort.py β tracker_sort.py} +1 -1
- multipage_pdf.pdf +0 -0
- scripts/{infer_aris.py β aris_to_tracks.py} +11 -4
- scripts/{track_detection.py β detection_to_tracks.py} +30 -64
- scripts/detection_to_tracks_eval.py +86 -0
- scripts/{full_detect_frames.py β frames_to_MOT.py} +40 -43
- scripts/{detect_frames.py β frames_to_detections.py} +17 -27
- scripts/{infer_frames.py β frames_to_tracks.py} +45 -62
- scripts/frames_to_tracks_eval.py +78 -0
- scripts/infer_eval.py +0 -47
- scripts/{project_path.py β project_subpath.py} +4 -2
- scripts/track_eval.py +0 -78
.gitignore
CHANGED
@@ -6,6 +6,7 @@ static/tmp.jpg
|
|
6 |
redis-stable/*
|
7 |
user_data/*
|
8 |
models/*
|
|
|
9 |
*.pyc
|
10 |
|
11 |
.ipynb_checkpoints
|
@@ -14,4 +15,5 @@ models/*
|
|
14 |
*.aris
|
15 |
*.log
|
16 |
*.pdf
|
|
|
17 |
*.DS_STORE
|
|
|
6 |
redis-stable/*
|
7 |
user_data/*
|
8 |
models/*
|
9 |
+
tmp/*
|
10 |
*.pyc
|
11 |
|
12 |
.ipynb_checkpoints
|
|
|
15 |
*.aris
|
16 |
*.log
|
17 |
*.pdf
|
18 |
+
*.mp4
|
19 |
*.DS_STORE
|
app.py
CHANGED
@@ -1,23 +1,22 @@
|
|
1 |
import gradio as gr
|
2 |
-
from uploader import save_data_to_dir, create_data_dir, save_data
|
3 |
-
from main import predict_task
|
4 |
-
from gradio_scripts.state_handler import reset_state
|
5 |
import numpy as np
|
6 |
-
from gradio_scripts.aws_handler import upload_file
|
7 |
-
from aris import create_metadata_table
|
8 |
-
from gradio_scripts.annotation_handler import init_frames
|
9 |
import json
|
10 |
from zipfile import ZipFile
|
11 |
import os
|
12 |
-
from gradio_scripts.upload_ui import Upload_Gradio, models
|
13 |
-
from gradio_scripts.result_ui import Result_Gradio, update_result, table_headers, info_headers, js_update_tab_labels
|
14 |
-
from dataloader import create_dataloader_aris
|
15 |
-
from aris import BEAM_WIDTH_DIR
|
16 |
-
from InferenceConfig import InferenceConfig
|
17 |
|
18 |
-
|
|
|
|
|
|
|
|
|
19 |
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
|
22 |
#Initialize State & Result
|
23 |
state = {
|
@@ -28,11 +27,13 @@ state = {
|
|
28 |
'frame_index': 0,
|
29 |
'outputs': [],
|
30 |
'config': None,
|
|
|
31 |
}
|
32 |
result = {}
|
|
|
33 |
|
34 |
-
|
35 |
-
# Called when an Aris file is uploaded for inference
|
36 |
def on_aris_input(
|
37 |
file_list,
|
38 |
model_id,
|
@@ -43,6 +44,9 @@ def on_aris_input(
|
|
43 |
output_formats
|
44 |
):
|
45 |
|
|
|
|
|
|
|
46 |
print(output_formats)
|
47 |
|
48 |
# Reset Result
|
@@ -82,77 +86,6 @@ def on_aris_input(
|
|
82 |
master_tabs: gr.update(selected=1)
|
83 |
}
|
84 |
|
85 |
-
# Called when a result zip file is uploaded for result review
|
86 |
-
def on_result_upload():
|
87 |
-
return {
|
88 |
-
master_tabs: gr.update(selected=1),
|
89 |
-
result_uploader: gr.update(value=str(np.random.rand()))
|
90 |
-
}
|
91 |
-
|
92 |
-
def on_result_upload_finish(zip_list, aris_list):
|
93 |
-
|
94 |
-
if (zip_list == None):
|
95 |
-
zip_list = [("static/example/example_result.zip", None)]
|
96 |
-
aris_path = "static/example/input_file.aris"
|
97 |
-
aris_list = [(aris_path, bytearray(open(aris_path, 'rb').read()))]
|
98 |
-
|
99 |
-
|
100 |
-
reset_state(result, state)
|
101 |
-
state['version'] = WEBAPP_VERSION
|
102 |
-
state['outputs'] = ["Annotated Video", "Manual Marking", "PDF"]
|
103 |
-
|
104 |
-
component_updates = {
|
105 |
-
tab_labeler: gr.update(value = len(zip_list))
|
106 |
-
}
|
107 |
-
|
108 |
-
for i in range(len(zip_list)):
|
109 |
-
|
110 |
-
# Create dir to unzip files
|
111 |
-
dir_name = create_data_dir(str(i))
|
112 |
-
|
113 |
-
# Check aris input
|
114 |
-
if (aris_list):
|
115 |
-
aris_info = aris_list[i]
|
116 |
-
file_name = aris_info[0].split("/")[-1]
|
117 |
-
bytes = aris_info[1]
|
118 |
-
valid, input_path, dir_name = save_data_to_dir(bytes, file_name, dir_name)
|
119 |
-
else:
|
120 |
-
input_path = None
|
121 |
-
|
122 |
-
# Unzip result
|
123 |
-
zip_info = zip_list[i]
|
124 |
-
zip_name = zip_info[0]
|
125 |
-
print(zip_name)
|
126 |
-
with ZipFile(zip_name) as zip_file:
|
127 |
-
ZipFile.extractall(zip_file, path=dir_name)
|
128 |
-
unzipped = os.listdir(dir_name)
|
129 |
-
print(unzipped)
|
130 |
-
|
131 |
-
for file in unzipped:
|
132 |
-
if (file.endswith("_results.mp4")):
|
133 |
-
result["path_video"].append(os.path.join(dir_name, file))
|
134 |
-
elif (file.endswith("_results.json")):
|
135 |
-
result["path_json"].append(os.path.join(dir_name, file))
|
136 |
-
elif (file.endswith("_marking.txt")):
|
137 |
-
result["path_marking"].append(os.path.join(dir_name, file))
|
138 |
-
|
139 |
-
result["aris_input"].append(input_path)
|
140 |
-
with open(result['path_json'][-1]) as f:
|
141 |
-
json_result = json.load(f)
|
142 |
-
result['json_result'].append(json_result)
|
143 |
-
fish_table, fish_info = create_metadata_table(json_result, table_headers, info_headers)
|
144 |
-
result["fish_table"].append(fish_table)
|
145 |
-
result["fish_info"].append(fish_info)
|
146 |
-
|
147 |
-
update = update_result(i, state, result, inference_handler)
|
148 |
-
|
149 |
-
for key in update.keys():
|
150 |
-
component_updates[key] = update[key]
|
151 |
-
|
152 |
-
component_updates.pop(inference_handler)
|
153 |
-
return component_updates
|
154 |
-
|
155 |
-
|
156 |
# Iterative function that performs inference on the next file in line
|
157 |
def infer_next(_, progress=gr.Progress()):
|
158 |
|
@@ -186,7 +119,10 @@ def infer_next(_, progress=gr.Progress()):
|
|
186 |
}
|
187 |
|
188 |
# Send uploaded file to AWS
|
189 |
-
|
|
|
|
|
|
|
190 |
|
191 |
# Do inference
|
192 |
json_result, json_filepath, zip_filepath, video_filepath, marking_filepath = predict_task(
|
@@ -195,10 +131,14 @@ def infer_next(_, progress=gr.Progress()):
|
|
195 |
output_formats = state['outputs'],
|
196 |
gradio_progress = set_progress
|
197 |
)
|
|
|
|
|
|
|
198 |
|
199 |
# Store result for that file
|
200 |
result['json_result'].append(json_result)
|
201 |
result['aris_input'].append(file_path)
|
|
|
202 |
result["path_video"].append(video_filepath)
|
203 |
result["path_zip"].append(zip_filepath)
|
204 |
result["path_json"].append(json_filepath)
|
@@ -218,12 +158,7 @@ def infer_next(_, progress=gr.Progress()):
|
|
218 |
inference_handler: gr.update()
|
219 |
}
|
220 |
|
221 |
-
#
|
222 |
-
def on_result_ready():
|
223 |
-
# Update result tab for last file
|
224 |
-
i = state["index"] - 1
|
225 |
-
return update_result(i, state, result, inference_handler)
|
226 |
-
|
227 |
def cancel_inference():
|
228 |
return {
|
229 |
master_tabs: gr.update(selected=0),
|
@@ -231,117 +166,118 @@ def cancel_inference():
|
|
231 |
components['cancel_btn']: gr.update(visible=False)
|
232 |
}
|
233 |
|
|
|
|
|
|
|
|
|
|
|
234 |
|
235 |
-
# Request loading of animation editor
|
236 |
-
def prepare_annotation(index):
|
237 |
|
238 |
-
state['annotation_index'] = index
|
239 |
-
state['frame_index'] = 0
|
240 |
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
master_tabs: gr.update(selected=2)
|
245 |
-
}
|
246 |
return {
|
247 |
-
|
248 |
-
|
249 |
}
|
250 |
|
251 |
-
|
252 |
-
|
253 |
-
# annotation_progress.change
|
254 |
-
def load_annotation(_, progress=gr.Progress()):
|
255 |
-
global annotation_info, annotation_dataset
|
256 |
-
|
257 |
-
# Get result index
|
258 |
-
result_index = state['annotation_index']
|
259 |
|
260 |
-
|
|
|
|
|
|
|
|
|
261 |
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
|
266 |
-
|
267 |
-
|
|
|
268 |
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
annotation_content = "<p id='annotation_info' style='display:none'>" + json.dumps(annotation_info) + "</p>"
|
274 |
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
279 |
|
280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
281 |
|
282 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
283 |
|
284 |
-
|
285 |
-
annotation_html += "<div id='annotation_header'>"
|
286 |
-
annotation_html += " <h1 id='annotation_frame_nbr'>Frame 0/100</h1>"
|
287 |
-
annotation_html += " <p id='annotation_edited'>(edited)</p>"
|
288 |
-
annotation_html += "</div>"
|
289 |
|
290 |
-
|
291 |
-
|
292 |
-
annotation_html += " <canvas id='canvas' style='width:50%' onmousedown='mouse_down(event)' onmousemove='mouse_move(event)' onmouseup='mouse_up()' onmouseleave='mouse_up()'></canvas>"
|
293 |
-
annotation_html += " <div id='annotation_display' style='width:50%'></div>"
|
294 |
-
annotation_html += "</div>"
|
295 |
|
296 |
-
|
297 |
-
|
298 |
-
annotation_html += "<!--" + str(np.random.rand()) + "-->"
|
299 |
|
300 |
-
return {
|
301 |
-
annotation_editor: gr.update(value=annotation_html, visible=True),
|
302 |
-
annotation_progress: gr.update(visible=False)
|
303 |
-
}
|
304 |
|
305 |
|
306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
307 |
|
308 |
-
|
309 |
-
with demo:
|
310 |
-
with gr.Blocks()
|
311 |
|
312 |
# Title of page + style
|
313 |
gr.HTML(
|
314 |
"""
|
315 |
-
<h1 align="center" style="font-size:xxx-large">Caltech Fisheye</h1>
|
316 |
<style>
|
|
|
317 |
#marking_json thead {
|
318 |
display: none !important;
|
319 |
}
|
|
|
320 |
.selected.svelte-kqij2n {
|
321 |
background: linear-gradient(180deg, #66eecb47, transparent);
|
322 |
}
|
323 |
-
|
324 |
-
left: calc(50% - 100px);
|
325 |
-
position: absolute;
|
326 |
-
width: 200px;
|
327 |
-
text-align: center;
|
328 |
-
font-size: x-large;
|
329 |
-
}
|
330 |
-
#annotation_header {
|
331 |
-
height: 40px;
|
332 |
-
}
|
333 |
-
#annotation_frame_nbr {
|
334 |
-
left: calc(50% - 100px);
|
335 |
-
position: absolute;
|
336 |
-
width: 200px;
|
337 |
-
text-align: center;
|
338 |
-
font-size: x-large;
|
339 |
-
}
|
340 |
-
#annotation_edited {
|
341 |
-
right: 0px;
|
342 |
-
position: absolute;
|
343 |
-
margin-top: 5px;
|
344 |
-
}
|
345 |
</style>
|
346 |
<style id="tab_style"></style>
|
347 |
"""
|
@@ -360,7 +296,6 @@ with demo:
|
|
360 |
# Master Tab for result visualization
|
361 |
with gr.Tab("Result", id=1):
|
362 |
|
363 |
-
|
364 |
# Define annotation progress bar for event listeres, but unrender since it will be displayed later on
|
365 |
result_uploader = gr.HTML("", visible=False)
|
366 |
components['result_uploader'] = result_uploader
|
@@ -369,10 +304,10 @@ with demo:
|
|
369 |
components['annotation_progress'] = annotation_progress
|
370 |
|
371 |
# Draw the gradio components related to visualzing result
|
372 |
-
|
373 |
|
374 |
# Master Tab for annotation editing
|
375 |
-
if enable_annotation_editor:
|
376 |
with gr.Tab("Annotation Editor", id=2):
|
377 |
|
378 |
# Draw the annotation loading bar here
|
@@ -381,21 +316,13 @@ with demo:
|
|
381 |
# Add annotation editor component
|
382 |
annotation_editor = gr.HTML("", visible=False)
|
383 |
|
384 |
-
# Event listener for
|
385 |
-
annotation_progress.change(
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
window.annotation_info = [];
|
392 |
-
return false;
|
393 |
-
}
|
394 |
-
window.annotation_info = window.annotation_info.concat(info)
|
395 |
-
console.log(window.annotation_info)
|
396 |
-
return true;
|
397 |
-
}
|
398 |
-
""")
|
399 |
|
400 |
# Event listener for running javascript defined in 'annotation_editor.js'
|
401 |
# show_annotation
|
@@ -417,19 +344,18 @@ with demo:
|
|
417 |
inference_handler = components['inference_handler']
|
418 |
result_handler = components['result_handler']
|
419 |
tab_labeler = components['tab_labeler']
|
420 |
-
|
421 |
-
|
422 |
inference_comps = [inference_handler, master_tabs, components['cancel_btn']]
|
423 |
|
424 |
# When a file is uploaded to the input, tell the inference_handler to start inference
|
425 |
input.upload(on_aris_input, [input] + components['hyperparams'], inference_comps)
|
|
|
426 |
|
427 |
# When inference handler updates, tell result_handler to show the new result
|
428 |
# Also, add inference_handler as the output in order to have it display the progress
|
429 |
inference_event = inference_handler.change(infer_next, None, [inference_handler, result_handler, tab_labeler])
|
430 |
|
431 |
# Send UI changes based on the new results to the UI_components, and tell the inference_handler to start next inference
|
432 |
-
result_handler.change(on_result_ready, None,
|
433 |
|
434 |
# Cancel and skip buttons
|
435 |
components['cancel_btn'].click(cancel_inference, None, inference_comps, cancels=[inference_event])
|
@@ -439,11 +365,7 @@ with demo:
|
|
439 |
components['result_uploader'].change(
|
440 |
on_result_upload_finish,
|
441 |
[components['result_input'], components['result_aris_input']],
|
442 |
-
|
443 |
)
|
444 |
|
445 |
demo.queue().launch()
|
446 |
-
|
447 |
-
on_result_ready()
|
448 |
-
|
449 |
-
|
|
|
1 |
import gradio as gr
|
|
|
|
|
|
|
2 |
import numpy as np
|
|
|
|
|
|
|
3 |
import json
|
4 |
from zipfile import ZipFile
|
5 |
import os
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
+
from backend.dataloader import create_dataloader_aris
|
8 |
+
from backend.aws_handler import ping_server
|
9 |
+
from backend.predict import predict_task
|
10 |
+
from backend.uploader import save_data_to_dir, create_data_dir, save_data
|
11 |
+
from backend.InferenceConfig import InferenceConfig
|
12 |
|
13 |
+
from frontend.upload_ui import Upload_Gradio, models
|
14 |
+
from frontend.result_ui import Result_Gradio, update_result, create_metadata_table, table_headers, info_headers
|
15 |
+
from frontend.annotation_handler import load_annotation, prepare_annotation, js_store_frame_info, annotation_css
|
16 |
+
from frontend.state_handler import reset_state
|
17 |
+
|
18 |
+
|
19 |
+
WEBAPP_VERSION = "Advanced 1.0"
|
20 |
|
21 |
#Initialize State & Result
|
22 |
state = {
|
|
|
27 |
'frame_index': 0,
|
28 |
'outputs': [],
|
29 |
'config': None,
|
30 |
+
'enable_annotation_editor': False
|
31 |
}
|
32 |
result = {}
|
33 |
+
components = {}
|
34 |
|
35 |
+
# -------------------------------------------- ----- UPLOAD ARIS FILE ------------------------------------------------------
|
36 |
+
# Called when an Aris file is uploaded for inference - calls infer_next
|
37 |
def on_aris_input(
|
38 |
file_list,
|
39 |
model_id,
|
|
|
44 |
output_formats
|
45 |
):
|
46 |
|
47 |
+
if isinstance(file_list, tuple):
|
48 |
+
file_list = [file_list]
|
49 |
+
|
50 |
print(output_formats)
|
51 |
|
52 |
# Reset Result
|
|
|
86 |
master_tabs: gr.update(selected=1)
|
87 |
}
|
88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
# Iterative function that performs inference on the next file in line
|
90 |
def infer_next(_, progress=gr.Progress()):
|
91 |
|
|
|
119 |
}
|
120 |
|
121 |
# Send uploaded file to AWS
|
122 |
+
ping_server(file_name, state)
|
123 |
+
#upload_file(file_path, "fishcounting", "webapp_uploads/files/" + file_name)
|
124 |
+
|
125 |
+
#crop_clip(file_path, 65)
|
126 |
|
127 |
# Do inference
|
128 |
json_result, json_filepath, zip_filepath, video_filepath, marking_filepath = predict_task(
|
|
|
131 |
output_formats = state['outputs'],
|
132 |
gradio_progress = set_progress
|
133 |
)
|
134 |
+
|
135 |
+
# prepare dummy dataloader for visualizations
|
136 |
+
_, dataset = create_dataloader_aris(file_path, num_frames_bg_subtract=0)
|
137 |
|
138 |
# Store result for that file
|
139 |
result['json_result'].append(json_result)
|
140 |
result['aris_input'].append(file_path)
|
141 |
+
result['datasets'].append(dataset)
|
142 |
result["path_video"].append(video_filepath)
|
143 |
result["path_zip"].append(zip_filepath)
|
144 |
result["path_json"].append(json_filepath)
|
|
|
158 |
inference_handler: gr.update()
|
159 |
}
|
160 |
|
161 |
+
# Cancel inference
|
|
|
|
|
|
|
|
|
|
|
162 |
def cancel_inference():
|
163 |
return {
|
164 |
master_tabs: gr.update(selected=0),
|
|
|
166 |
components['cancel_btn']: gr.update(visible=False)
|
167 |
}
|
168 |
|
169 |
+
# Show result
|
170 |
+
def on_result_ready():
|
171 |
+
# Update result tab for last file
|
172 |
+
i = state["index"] - 1
|
173 |
+
return update_result(i, state, result, inference_handler)
|
174 |
|
|
|
|
|
175 |
|
|
|
|
|
176 |
|
177 |
+
# ------------------------------------------------- UPLOAD RESULT FILE -----------------------------------------------------
|
178 |
+
# Called when result file is uploaded for review
|
179 |
+
def on_result_upload():
|
|
|
|
|
180 |
return {
|
181 |
+
master_tabs: gr.update(selected=1),
|
182 |
+
result_uploader: gr.update(value=str(np.random.rand()))
|
183 |
}
|
184 |
|
185 |
+
# Called when result upload is finished processing
|
186 |
+
def on_result_upload_finish(zip_list, aris_list):
|
|
|
|
|
|
|
|
|
|
|
|
|
187 |
|
188 |
+
if (zip_list == None):
|
189 |
+
zip_list = [("static/example/example_result.zip", None)]
|
190 |
+
aris_path = "static/example/input_file.aris"
|
191 |
+
aris_list = [(aris_path, bytearray(open(aris_path, 'rb').read()))]
|
192 |
+
|
193 |
|
194 |
+
reset_state(result, state)
|
195 |
+
state['version'] = WEBAPP_VERSION
|
196 |
+
state['outputs'] = ["Generate Annotated Video", "Generate Manual Marking", "Generate PDF"]
|
197 |
|
198 |
+
component_updates = {
|
199 |
+
tab_labeler: gr.update(value = len(zip_list))
|
200 |
+
}
|
201 |
|
202 |
+
for i in range(len(zip_list)):
|
203 |
+
|
204 |
+
# Create dir to unzip files
|
205 |
+
dir_name = create_data_dir(str(i))
|
|
|
206 |
|
207 |
+
# Check aris input
|
208 |
+
if (aris_list):
|
209 |
+
aris_info = aris_list[i]
|
210 |
+
file_name = aris_info[0].split("/")[-1]
|
211 |
+
bytes = aris_info[1]
|
212 |
+
valid, input_path, dir_name = save_data_to_dir(bytes, file_name, dir_name)
|
213 |
+
_, dataset = create_dataloader_aris(input_path, num_frames_bg_subtract=0)
|
214 |
+
else:
|
215 |
+
input_path = None
|
216 |
+
dataset = None
|
217 |
|
218 |
+
# Unzip result
|
219 |
+
zip_info = zip_list[i]
|
220 |
+
zip_name = zip_info[0]
|
221 |
+
print(zip_name)
|
222 |
+
with ZipFile(zip_name) as zip_file:
|
223 |
+
ZipFile.extractall(zip_file, path=dir_name)
|
224 |
+
unzipped = os.listdir(dir_name)
|
225 |
+
print(unzipped)
|
226 |
|
227 |
+
for file in unzipped:
|
228 |
+
if (file.endswith("_results.mp4")):
|
229 |
+
result["path_video"].append(os.path.join(dir_name, file))
|
230 |
+
elif (file.endswith("_results.json")):
|
231 |
+
result["path_json"].append(os.path.join(dir_name, file))
|
232 |
+
elif (file.endswith("_marking.txt")):
|
233 |
+
result["path_marking"].append(os.path.join(dir_name, file))
|
234 |
+
|
235 |
+
result["aris_input"].append(input_path)
|
236 |
+
result["datasets"].append(dataset)
|
237 |
+
with open(result['path_json'][-1]) as f:
|
238 |
+
json_result = json.load(f)
|
239 |
+
result['json_result'].append(json_result)
|
240 |
+
fish_table, fish_info = create_metadata_table(json_result, table_headers, info_headers)
|
241 |
+
result["fish_table"].append(fish_table)
|
242 |
+
result["fish_info"].append(fish_info)
|
243 |
|
244 |
+
update = update_result(i, state, result, inference_handler)
|
|
|
|
|
|
|
|
|
245 |
|
246 |
+
for key in update.keys():
|
247 |
+
component_updates[key] = update[key]
|
|
|
|
|
|
|
248 |
|
249 |
+
component_updates.pop(inference_handler)
|
250 |
+
return component_updates
|
|
|
251 |
|
|
|
|
|
|
|
|
|
252 |
|
253 |
|
254 |
+
# ------------------------------------------------- ANNOTATION EDITOR -----------------------------------------------------
|
255 |
+
def on_annotation_open(result_index):
|
256 |
+
return prepare_annotation(state, result, result_index)
|
257 |
+
|
258 |
+
def annotate_next(_, progress=gr.Progress()):
|
259 |
+
return load_annotation(state, result, progress)
|
260 |
+
|
261 |
+
|
262 |
|
263 |
+
# -------------------------------------------------- GRADIO ARCHITECTURE ----------------------------------------------------
|
264 |
+
with gr.Blocks() as demo:
|
265 |
+
with gr.Blocks():
|
266 |
|
267 |
# Title of page + style
|
268 |
gr.HTML(
|
269 |
"""
|
270 |
+
<h1 align="center" style="font-size:xxx-large">Caltech Fisheye - Advanced</h1>
|
271 |
<style>
|
272 |
+
/* Disable header of metadata list in result */
|
273 |
#marking_json thead {
|
274 |
display: none !important;
|
275 |
}
|
276 |
+
/* Color of selected tab */
|
277 |
.selected.svelte-kqij2n {
|
278 |
background: linear-gradient(180deg, #66eecb47, transparent);
|
279 |
}
|
280 |
+
""" + annotation_css + """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
281 |
</style>
|
282 |
<style id="tab_style"></style>
|
283 |
"""
|
|
|
296 |
# Master Tab for result visualization
|
297 |
with gr.Tab("Result", id=1):
|
298 |
|
|
|
299 |
# Define annotation progress bar for event listeres, but unrender since it will be displayed later on
|
300 |
result_uploader = gr.HTML("", visible=False)
|
301 |
components['result_uploader'] = result_uploader
|
|
|
304 |
components['annotation_progress'] = annotation_progress
|
305 |
|
306 |
# Draw the gradio components related to visualzing result
|
307 |
+
visualization_components = Result_Gradio(on_annotation_open, components, state)
|
308 |
|
309 |
# Master Tab for annotation editing
|
310 |
+
if state['enable_annotation_editor']:
|
311 |
with gr.Tab("Annotation Editor", id=2):
|
312 |
|
313 |
# Draw the annotation loading bar here
|
|
|
316 |
# Add annotation editor component
|
317 |
annotation_editor = gr.HTML("", visible=False)
|
318 |
|
319 |
+
# Event listener for batch loading of annotation frames
|
320 |
+
annotation_progress.change(
|
321 |
+
annotate_next,
|
322 |
+
annotation_progress,
|
323 |
+
[annotation_editor, annotation_progress],
|
324 |
+
_js=js_store_frame_info
|
325 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
326 |
|
327 |
# Event listener for running javascript defined in 'annotation_editor.js'
|
328 |
# show_annotation
|
|
|
344 |
inference_handler = components['inference_handler']
|
345 |
result_handler = components['result_handler']
|
346 |
tab_labeler = components['tab_labeler']
|
|
|
|
|
347 |
inference_comps = [inference_handler, master_tabs, components['cancel_btn']]
|
348 |
|
349 |
# When a file is uploaded to the input, tell the inference_handler to start inference
|
350 |
input.upload(on_aris_input, [input] + components['hyperparams'], inference_comps)
|
351 |
+
components['inference_btn'].click(on_aris_input, [input] + components['hyperparams'], inference_comps)
|
352 |
|
353 |
# When inference handler updates, tell result_handler to show the new result
|
354 |
# Also, add inference_handler as the output in order to have it display the progress
|
355 |
inference_event = inference_handler.change(infer_next, None, [inference_handler, result_handler, tab_labeler])
|
356 |
|
357 |
# Send UI changes based on the new results to the UI_components, and tell the inference_handler to start next inference
|
358 |
+
result_handler.change(on_result_ready, None, visualization_components + [inference_handler])
|
359 |
|
360 |
# Cancel and skip buttons
|
361 |
components['cancel_btn'].click(cancel_inference, None, inference_comps, cancels=[inference_event])
|
|
|
365 |
components['result_uploader'].change(
|
366 |
on_result_upload_finish,
|
367 |
[components['result_input'], components['result_aris_input']],
|
368 |
+
visualization_components + [tab_labeler]
|
369 |
)
|
370 |
|
371 |
demo.queue().launch()
|
|
|
|
|
|
|
|
InferenceConfig.py β backend/InferenceConfig.py
RENAMED
@@ -47,16 +47,42 @@ class InferenceConfig:
|
|
47 |
def enable_sort_track(self):
|
48 |
self.associative_tracker = TrackerType.NONE
|
49 |
|
50 |
-
def enable_conf_boost(self, power, decay):
|
51 |
self.associative_tracker = TrackerType.CONF_BOOST
|
52 |
self.boost_power = power
|
53 |
self.boost_decay = decay
|
54 |
|
55 |
-
def enable_byte_track(self, low, high):
|
56 |
self.associative_tracker = TrackerType.BYTETRACK
|
57 |
self.byte_low_conf = low
|
58 |
self.byte_high_conf = high
|
59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
def find_model(self, model_list):
|
61 |
print("weights", self.weights)
|
62 |
for model_name in model_list:
|
|
|
47 |
def enable_sort_track(self):
|
48 |
self.associative_tracker = TrackerType.NONE
|
49 |
|
50 |
+
def enable_conf_boost(self, power=2, decay=0.1):
|
51 |
self.associative_tracker = TrackerType.CONF_BOOST
|
52 |
self.boost_power = power
|
53 |
self.boost_decay = decay
|
54 |
|
55 |
+
def enable_byte_track(self, low=0.1, high=0.3):
|
56 |
self.associative_tracker = TrackerType.BYTETRACK
|
57 |
self.byte_low_conf = low
|
58 |
self.byte_high_conf = high
|
59 |
|
60 |
+
def enable_tracker_from_string(self, associativity):
|
61 |
+
if associativity != "":
|
62 |
+
if (associativity.startswith("boost")):
|
63 |
+
conf = associativity.split(":")
|
64 |
+
if len(conf) == 3:
|
65 |
+
self.enable_conf_boost(power=float(conf[1]), decay=float(conf[2]))
|
66 |
+
return True
|
67 |
+
else:
|
68 |
+
print("INVALID PARAMETERS FOR CONFIDENCE BOOST:", associativity)
|
69 |
+
return False
|
70 |
+
|
71 |
+
elif (associativity.startswith("bytetrack")):
|
72 |
+
conf = associativity.split(":")
|
73 |
+
if len(conf) == 3:
|
74 |
+
self.enable_byte_track(low=float(conf[1]), high=float(conf[2]))
|
75 |
+
return True
|
76 |
+
else:
|
77 |
+
print("INVALID PARAMETERS FOR BYTETRACK:", associativity)
|
78 |
+
return False
|
79 |
+
else:
|
80 |
+
print("INVALID ASSOCIATIVITY TYPE:", associativity)
|
81 |
+
return False
|
82 |
+
else:
|
83 |
+
self.enable_sort_track()
|
84 |
+
return True
|
85 |
+
|
86 |
def find_model(self, model_list):
|
87 |
print("weights", self.weights)
|
88 |
for model_name in model_list:
|
aris.py β backend/aris.py
RENAMED
@@ -9,7 +9,6 @@ from PIL import Image
|
|
9 |
from tqdm import tqdm
|
10 |
import datetime
|
11 |
from decimal import Decimal, ROUND_HALF_UP
|
12 |
-
import json
|
13 |
import pytz
|
14 |
from copy import deepcopy
|
15 |
from multiprocessing import Pool
|
@@ -28,7 +27,12 @@ ImageData = namedtuple('ImageData', [
|
|
28 |
'sample_read_rows', 'sample_read_cols', 'image_write_rows', 'image_write_cols'
|
29 |
])
|
30 |
|
31 |
-
def FastARISRead(
|
|
|
|
|
|
|
|
|
|
|
32 |
""" Just read in the ARIS frame, and not the other meta data.
|
33 |
"""
|
34 |
FrameSize = ARIS_data.SamplesPerChannel*ARIS_data.NumRawBeams
|
@@ -61,7 +65,7 @@ def get_info(aris_fp, beam_width_dir=BEAM_WIDTH_DIR):
|
|
61 |
)
|
62 |
return pixel_meter_size * xdim, pixel_meter_size * ydim, aris_frame.framerate
|
63 |
|
64 |
-
def write_frames(aris_fp, out_dir,
|
65 |
"""
|
66 |
Write all frames from an ARIS file to disk, using our 3-channel format:
|
67 |
(raw img, blurred & mean subtracted img, optical flow approximation)
|
@@ -69,7 +73,6 @@ def write_frames(aris_fp, out_dir, cb=None, max_mb=-1, beam_width_dir=BEAM_WIDTH
|
|
69 |
Args:
|
70 |
aris_fp: path to aris file
|
71 |
out_dir: directory for frame extraction. frames will be named 0.jpg, 1.jpg, ... {n}.jpg
|
72 |
-
cb: a callback function for updating progress
|
73 |
max_mb: maximum amount of the file to be processed, in megabytes
|
74 |
beam_width_dir: location of ARIS camera information
|
75 |
bg_out_dir: where to write the background frame; None disables writing
|
@@ -82,9 +85,6 @@ def write_frames(aris_fp, out_dir, cb=None, max_mb=-1, beam_width_dir=BEAM_WIDTH
|
|
82 |
# Load in the ARIS file
|
83 |
ARISdata, aris_frame = pyARIS.DataImport(aris_fp)
|
84 |
|
85 |
-
if cb:
|
86 |
-
cb(2, msg="Decoding ARIS data...")
|
87 |
-
|
88 |
beam_width_data = pyARIS.load_beam_width_data(aris_frame, beam_width_dir=beam_width_dir)[0]
|
89 |
# What is the meter resolution of the smallest sample?
|
90 |
min_pixel_size = pyARIS.get_minimum_pixel_meter_size(aris_frame, beam_width_data)
|
@@ -135,18 +135,18 @@ def write_frames(aris_fp, out_dir, cb=None, max_mb=-1, beam_width_dir=BEAM_WIDTH
|
|
135 |
with tqdm(total=(end_frame-start_frame-1), desc="Extracting frames", ncols=0) as pbar:
|
136 |
# compute info for bg subtraction using first batch
|
137 |
# TODO: make this a sliding window
|
138 |
-
mean_blurred_frame, mean_normalization_value = write_frame_range(ARISdata, image_data, out_dir, clips[0][0], clips[0][1], None, None,
|
139 |
|
140 |
# do rest of batches in parallel
|
141 |
if num_workers > 0:
|
142 |
-
args = [ (ARISdata, image_data, out_dir, start, end, mean_blurred_frame, mean_normalization_value
|
143 |
with Pool(num_workers) as pool:
|
144 |
results = [ pool.apply_async(write_frame_range, arg) for arg in args ]
|
145 |
results = [ r.get() for r in results ] # need this call to block on thread execution
|
146 |
pbar.update(sum([ arg[4] - arg[3] for arg in args ]))
|
147 |
else:
|
148 |
for j, (start, end) in enumerate(clips[1:]):
|
149 |
-
write_frame_range(ARISdata, image_data, out_dir, start, end, mean_blurred_frame, mean_normalization_value,
|
150 |
|
151 |
if bg_out_dir is not None:
|
152 |
bg_img = (mean_blurred_frame * 255).astype(np.uint8)
|
@@ -155,10 +155,10 @@ def write_frames(aris_fp, out_dir, cb=None, max_mb=-1, beam_width_dir=BEAM_WIDTH
|
|
155 |
|
156 |
return pixel_meter_size * xdim, pixel_meter_size * ydim, aris_frame.framerate
|
157 |
|
158 |
-
def write_frame_range(ARISdata, image_data, out_dir, start, end, mean_blurred_frame=None, mean_normalization_value=None,
|
159 |
try:
|
160 |
frames = np.zeros([end-start, image_data.ydim, image_data.xdim], dtype=np.uint8)
|
161 |
-
frames[:, image_data.image_write_rows, image_data.image_write_cols] =
|
162 |
except:
|
163 |
print("Error extracting frames from", ARISdata.filename, "during batch", i)
|
164 |
return
|
@@ -196,12 +196,12 @@ def write_frame_range(ARISdata, image_data, out_dir, start, end, mean_blurred_fr
|
|
196 |
|
197 |
if pbar:
|
198 |
pbar.update(1)
|
199 |
-
if cb:
|
200 |
-
pct = 2 + int( (start+i) / (end_frame - start_frame - 1) * 98)
|
201 |
-
cb(pct, msg=pbar.__str__())
|
202 |
|
203 |
return mean_blurred_frame, mean_normalization_value
|
204 |
|
|
|
|
|
|
|
205 |
def prep_for_mm(json_data):
|
206 |
"""Prepare json results for writing to a manual marking file."""
|
207 |
json_data = deepcopy(json_data)
|
@@ -257,7 +257,6 @@ def prep_for_mm(json_data):
|
|
257 |
|
258 |
return json_data
|
259 |
|
260 |
-
|
261 |
def add_metadata_to_result(aris_fp, json_data, beam_width_dir=BEAM_WIDTH_DIR):
|
262 |
"""
|
263 |
Return:
|
@@ -436,49 +435,6 @@ def add_metadata_to_result(aris_fp, json_data, beam_width_dir=BEAM_WIDTH_DIR):
|
|
436 |
|
437 |
return json_data
|
438 |
|
439 |
-
def create_metadata_table(result, table_headers, info_headers):
|
440 |
-
if 'metadata' in result:
|
441 |
-
metadata = result['metadata']
|
442 |
-
else:
|
443 |
-
metadata = { 'FISH': [] }
|
444 |
-
|
445 |
-
# Calculate detection dropout
|
446 |
-
for fish in metadata['FISH']:
|
447 |
-
count = 0
|
448 |
-
for frame in result['frames'][fish['START_FRAME']:fish['END_FRAME']+1]:
|
449 |
-
for ann in frame['fish']:
|
450 |
-
if ann['fish_id'] == fish['TOTAL']:
|
451 |
-
count += 1
|
452 |
-
fish['DETECTION_DROPOUT'] = 1 - count / (fish['END_FRAME'] + 1 - fish['START_FRAME'])
|
453 |
-
|
454 |
-
# Create fish table
|
455 |
-
table = []
|
456 |
-
for fish in metadata["FISH"]:
|
457 |
-
row = []
|
458 |
-
for header in table_headers:
|
459 |
-
row.append(fish[header])
|
460 |
-
table.append(row)
|
461 |
-
|
462 |
-
if len(metadata["FISH"]) == 0:
|
463 |
-
row = []
|
464 |
-
for header in table_headers:
|
465 |
-
row.append("-")
|
466 |
-
table.append(row)
|
467 |
-
|
468 |
-
# Create info table
|
469 |
-
info = []
|
470 |
-
for field in info_headers:
|
471 |
-
field_name = "**" + field + "**"
|
472 |
-
if field in metadata:
|
473 |
-
info.append([field_name, str(metadata[field])])
|
474 |
-
else:
|
475 |
-
info.append([field_name, ""])
|
476 |
-
if 'hyperparameters' in metadata:
|
477 |
-
for param_name in metadata['hyperparameters']:
|
478 |
-
info.append(['**' + param_name + '**', str(metadata['hyperparameters'][param_name])])
|
479 |
-
|
480 |
-
return table, info
|
481 |
-
|
482 |
def create_manual_marking(results, out_path=None):
|
483 |
"""
|
484 |
Return:
|
|
|
9 |
from tqdm import tqdm
|
10 |
import datetime
|
11 |
from decimal import Decimal, ROUND_HALF_UP
|
|
|
12 |
import pytz
|
13 |
from copy import deepcopy
|
14 |
from multiprocessing import Pool
|
|
|
27 |
'sample_read_rows', 'sample_read_cols', 'image_write_rows', 'image_write_cols'
|
28 |
])
|
29 |
|
30 |
+
def FastARISRead(aris_fp, start_frame, end_frame):
|
31 |
+
ARISdata, aris_frame = pyARIS.DataImport(aris_fp)
|
32 |
+
frames = FastARISExtract(ARISdata, start_frame, end_frame)
|
33 |
+
return frames
|
34 |
+
|
35 |
+
def FastARISExtract(ARIS_data, start_frame, end_frame):
|
36 |
""" Just read in the ARIS frame, and not the other meta data.
|
37 |
"""
|
38 |
FrameSize = ARIS_data.SamplesPerChannel*ARIS_data.NumRawBeams
|
|
|
65 |
)
|
66 |
return pixel_meter_size * xdim, pixel_meter_size * ydim, aris_frame.framerate
|
67 |
|
68 |
+
def write_frames(aris_fp, out_dir, max_mb=-1, beam_width_dir=BEAM_WIDTH_DIR, bg_out_dir=None, num_workers=0):
|
69 |
"""
|
70 |
Write all frames from an ARIS file to disk, using our 3-channel format:
|
71 |
(raw img, blurred & mean subtracted img, optical flow approximation)
|
|
|
73 |
Args:
|
74 |
aris_fp: path to aris file
|
75 |
out_dir: directory for frame extraction. frames will be named 0.jpg, 1.jpg, ... {n}.jpg
|
|
|
76 |
max_mb: maximum amount of the file to be processed, in megabytes
|
77 |
beam_width_dir: location of ARIS camera information
|
78 |
bg_out_dir: where to write the background frame; None disables writing
|
|
|
85 |
# Load in the ARIS file
|
86 |
ARISdata, aris_frame = pyARIS.DataImport(aris_fp)
|
87 |
|
|
|
|
|
|
|
88 |
beam_width_data = pyARIS.load_beam_width_data(aris_frame, beam_width_dir=beam_width_dir)[0]
|
89 |
# What is the meter resolution of the smallest sample?
|
90 |
min_pixel_size = pyARIS.get_minimum_pixel_meter_size(aris_frame, beam_width_data)
|
|
|
135 |
with tqdm(total=(end_frame-start_frame-1), desc="Extracting frames", ncols=0) as pbar:
|
136 |
# compute info for bg subtraction using first batch
|
137 |
# TODO: make this a sliding window
|
138 |
+
mean_blurred_frame, mean_normalization_value = write_frame_range(ARISdata, image_data, out_dir, clips[0][0], clips[0][1], None, None, pbar)
|
139 |
|
140 |
# do rest of batches in parallel
|
141 |
if num_workers > 0:
|
142 |
+
args = [ (ARISdata, image_data, out_dir, start, end, mean_blurred_frame, mean_normalization_value) for (start, end) in clips[1:] ] # TODO: can't pass pbar to thread
|
143 |
with Pool(num_workers) as pool:
|
144 |
results = [ pool.apply_async(write_frame_range, arg) for arg in args ]
|
145 |
results = [ r.get() for r in results ] # need this call to block on thread execution
|
146 |
pbar.update(sum([ arg[4] - arg[3] for arg in args ]))
|
147 |
else:
|
148 |
for j, (start, end) in enumerate(clips[1:]):
|
149 |
+
write_frame_range(ARISdata, image_data, out_dir, start, end, mean_blurred_frame, mean_normalization_value, pbar)
|
150 |
|
151 |
if bg_out_dir is not None:
|
152 |
bg_img = (mean_blurred_frame * 255).astype(np.uint8)
|
|
|
155 |
|
156 |
return pixel_meter_size * xdim, pixel_meter_size * ydim, aris_frame.framerate
|
157 |
|
158 |
+
def write_frame_range(ARISdata, image_data, out_dir, start, end, mean_blurred_frame=None, mean_normalization_value=None, pbar=None):
|
159 |
try:
|
160 |
frames = np.zeros([end-start, image_data.ydim, image_data.xdim], dtype=np.uint8)
|
161 |
+
frames[:, image_data.image_write_rows, image_data.image_write_cols] = FastARISExtract(ARISdata, start, end)[:, image_data.sample_read_rows, image_data.sample_read_cols]
|
162 |
except:
|
163 |
print("Error extracting frames from", ARISdata.filename, "during batch", i)
|
164 |
return
|
|
|
196 |
|
197 |
if pbar:
|
198 |
pbar.update(1)
|
|
|
|
|
|
|
199 |
|
200 |
return mean_blurred_frame, mean_normalization_value
|
201 |
|
202 |
+
|
203 |
+
|
204 |
+
|
205 |
def prep_for_mm(json_data):
|
206 |
"""Prepare json results for writing to a manual marking file."""
|
207 |
json_data = deepcopy(json_data)
|
|
|
257 |
|
258 |
return json_data
|
259 |
|
|
|
260 |
def add_metadata_to_result(aris_fp, json_data, beam_width_dir=BEAM_WIDTH_DIR):
|
261 |
"""
|
262 |
Return:
|
|
|
435 |
|
436 |
return json_data
|
437 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
438 |
def create_manual_marking(results, out_path=None):
|
439 |
"""
|
440 |
Return:
|
{gradio_scripts β backend}/aws_handler.py
RENAMED
@@ -2,9 +2,11 @@ import logging
|
|
2 |
import boto3
|
3 |
from botocore.exceptions import ClientError
|
4 |
import os
|
|
|
|
|
5 |
|
6 |
|
7 |
-
def upload_file(file_name, bucket, object_name=None):
|
8 |
"""Upload a file to an S3 bucket
|
9 |
|
10 |
:param file_name: File to upload
|
@@ -32,4 +34,45 @@ def upload_file(file_name, bucket, object_name=None):
|
|
32 |
except ClientError as e:
|
33 |
logging.error(e)
|
34 |
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
return True
|
|
|
2 |
import boto3
|
3 |
from botocore.exceptions import ClientError
|
4 |
import os
|
5 |
+
from datetime import datetime
|
6 |
+
import torch
|
7 |
|
8 |
|
9 |
+
def upload_file(file_name, bucket="fishcounting", object_name=None):
|
10 |
"""Upload a file to an S3 bucket
|
11 |
|
12 |
:param file_name: File to upload
|
|
|
34 |
except ClientError as e:
|
35 |
logging.error(e)
|
36 |
return False
|
37 |
+
return True
|
38 |
+
|
39 |
+
def ping_server(aris_name, state):
|
40 |
+
"""Upload a notification file to AWS
|
41 |
+
|
42 |
+
:param aris_name: Name of the aris file uploaded
|
43 |
+
:return: True if file was uploaded, else False
|
44 |
+
"""
|
45 |
+
|
46 |
+
file_name = 'tmp/notification.txt'
|
47 |
+
os.makedirs('tmp', exist_ok=True)
|
48 |
+
with open(file_name, 'w') as f:
|
49 |
+
output = f"time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
|
50 |
+
output += f"filename: {aris_name}\n"
|
51 |
+
output += f"app version: {state['version']}\n"
|
52 |
+
output += f"hardware: {torch.cuda.get_device_name() if torch.cuda.is_available() else 'CPU'}\n"
|
53 |
+
if 'CPU_CORES' in os.environ:
|
54 |
+
output += f"location: HuggingFace\n"
|
55 |
+
output += f"nbr cpu cores: {os.environ['CPU_CORES']}\n"
|
56 |
+
output += f"memory: {os.environ['MEMORY']}\n"
|
57 |
+
else:
|
58 |
+
output += f"location: local install"
|
59 |
+
f.write(output)
|
60 |
+
|
61 |
+
# If S3 object_name was not specified, use file_name
|
62 |
+
|
63 |
+
if (not 'AAK_ID' in os.environ) or (not 'ASAK' in os.environ):
|
64 |
+
print('AWS keys not specified. Cancelling sync')
|
65 |
+
return False
|
66 |
+
|
67 |
+
# Upload the file
|
68 |
+
s3_client = boto3.client(
|
69 |
+
's3',
|
70 |
+
aws_access_key_id=os.environ['AAK_ID'],
|
71 |
+
aws_secret_access_key=os.environ['ASAK']
|
72 |
+
)
|
73 |
+
try:
|
74 |
+
response = s3_client.upload_file(file_name, "fishcounting", "webapp_uploads/notifications/" + str(int(datetime.now().timestamp())) + ".txt")
|
75 |
+
except ClientError as e:
|
76 |
+
logging.error(e)
|
77 |
+
return False
|
78 |
return True
|
dataloader.py β backend/dataloader.py
RENAMED
@@ -5,20 +5,19 @@ import cv2
|
|
5 |
import numpy as np
|
6 |
import json
|
7 |
from threading import Lock
|
8 |
-
import struct
|
9 |
from contextlib import contextmanager
|
10 |
import torch
|
11 |
from torch.utils.data import Dataset
|
12 |
-
import torchvision.transforms as T
|
13 |
from PIL import Image
|
|
|
14 |
|
15 |
# assumes yolov5 on sys.path
|
16 |
from lib.yolov5.utils.general import xyxy2xywh
|
17 |
from lib.yolov5.utils.augmentations import letterbox
|
18 |
from lib.yolov5.utils.dataloaders import create_dataloader as create_yolo_dataloader
|
19 |
|
20 |
-
from pyDIDSON import pyDIDSON
|
21 |
-
from aris import
|
22 |
|
23 |
# use this flag to test the difference between direct ARIS dataloading and
|
24 |
# using the jpeg compressed version. very slow. not much difference observed.
|
@@ -29,21 +28,24 @@ TEST_JPG_COMPRESSION = False
|
|
29 |
# Factory(ish) methods for DataLoader creation. Easy entry points to this module.
|
30 |
# # # # # #
|
31 |
|
32 |
-
def create_dataloader_aris(aris_filepath, beam_width_dir, annotations_file, batch_size=32, stride=64, pad=0.5, img_size=896, rank=-1, world_size=1, workers=0,
|
33 |
-
disable_output=False, cache_bg_frames=False):
|
34 |
"""
|
35 |
Get a PyTorch Dataset and DataLoader for ARIS files with (optional) associated fisheye-formatted labels.
|
36 |
"""
|
|
|
|
|
37 |
# Make sure only the first process in DDP process the dataset first, and the following others can use the cache
|
38 |
# this is a no-op for a single-gpu machine
|
39 |
with torch_distributed_zero_first(rank):
|
40 |
dataset = YOLOARISBatchedDataset(aris_filepath, beam_width_dir, annotations_file, stride, pad, img_size,
|
41 |
-
disable_output=disable_output, cache_bg_frames=cache_bg_frames)
|
42 |
|
43 |
|
44 |
batch_size = min(batch_size, len(dataset))
|
45 |
nw = min([os.cpu_count() // world_size, batch_size if batch_size > 1 else 0, workers]) # number of workers
|
46 |
|
|
|
47 |
if not disable_output:
|
48 |
print("dataset size", len(dataset))
|
49 |
print("dataset shape", dataset.shape)
|
@@ -55,6 +57,7 @@ def create_dataloader_aris(aris_filepath, beam_width_dir, annotations_file, batc
|
|
55 |
num_workers=nw,
|
56 |
pin_memory=True,
|
57 |
collate_fn=collate_fn)
|
|
|
58 |
return dataloader, dataset
|
59 |
|
60 |
def create_dataloader_frames(frames_path, batch_size=32, model_stride_max=32,
|
@@ -348,8 +351,8 @@ class YOLOARISBatchedDataset(ARISBatchedDataset):
|
|
348 |
"""An ARIS Dataset that works with YOLOv5 inference."""
|
349 |
|
350 |
def __init__(self, aris_filepath, beam_width_dir, annotations_file, stride=64, pad=0.5, img_size=896, batch_size=32,
|
351 |
-
disable_output=False, cache_bg_frames=False):
|
352 |
-
super().__init__(aris_filepath, beam_width_dir, annotations_file, batch_size, disable_output=disable_output, cache_bg_frames=cache_bg_frames)
|
353 |
|
354 |
# compute shapes for letterbox
|
355 |
aspect_ratio = self.ydim / self.xdim
|
|
|
5 |
import numpy as np
|
6 |
import json
|
7 |
from threading import Lock
|
|
|
8 |
from contextlib import contextmanager
|
9 |
import torch
|
10 |
from torch.utils.data import Dataset
|
|
|
11 |
from PIL import Image
|
12 |
+
from datetime import datetime
|
13 |
|
14 |
# assumes yolov5 on sys.path
|
15 |
from lib.yolov5.utils.general import xyxy2xywh
|
16 |
from lib.yolov5.utils.augmentations import letterbox
|
17 |
from lib.yolov5.utils.dataloaders import create_dataloader as create_yolo_dataloader
|
18 |
|
19 |
+
from backend.pyDIDSON import pyDIDSON
|
20 |
+
from backend.aris import BEAM_WIDTH_DIR
|
21 |
|
22 |
# use this flag to test the difference between direct ARIS dataloading and
|
23 |
# using the jpeg compressed version. very slow. not much difference observed.
|
|
|
28 |
# Factory(ish) methods for DataLoader creation. Easy entry points to this module.
|
29 |
# # # # # #
|
30 |
|
31 |
+
def create_dataloader_aris(aris_filepath, beam_width_dir=BEAM_WIDTH_DIR, annotations_file=None, batch_size=32, stride=64, pad=0.5, img_size=896, rank=-1, world_size=1, workers=0,
|
32 |
+
disable_output=False, cache_bg_frames=False, num_frames_bg_subtract=1000):
|
33 |
"""
|
34 |
Get a PyTorch Dataset and DataLoader for ARIS files with (optional) associated fisheye-formatted labels.
|
35 |
"""
|
36 |
+
|
37 |
+
print('dataset', datetime.now())
|
38 |
# Make sure only the first process in DDP process the dataset first, and the following others can use the cache
|
39 |
# this is a no-op for a single-gpu machine
|
40 |
with torch_distributed_zero_first(rank):
|
41 |
dataset = YOLOARISBatchedDataset(aris_filepath, beam_width_dir, annotations_file, stride, pad, img_size,
|
42 |
+
disable_output=disable_output, cache_bg_frames=cache_bg_frames, num_frames_bg_subtract=num_frames_bg_subtract)
|
43 |
|
44 |
|
45 |
batch_size = min(batch_size, len(dataset))
|
46 |
nw = min([os.cpu_count() // world_size, batch_size if batch_size > 1 else 0, workers]) # number of workers
|
47 |
|
48 |
+
print('dataloader', datetime.now())
|
49 |
if not disable_output:
|
50 |
print("dataset size", len(dataset))
|
51 |
print("dataset shape", dataset.shape)
|
|
|
57 |
num_workers=nw,
|
58 |
pin_memory=True,
|
59 |
collate_fn=collate_fn)
|
60 |
+
print('done', datetime.now())
|
61 |
return dataloader, dataset
|
62 |
|
63 |
def create_dataloader_frames(frames_path, batch_size=32, model_stride_max=32,
|
|
|
351 |
"""An ARIS Dataset that works with YOLOv5 inference."""
|
352 |
|
353 |
def __init__(self, aris_filepath, beam_width_dir, annotations_file, stride=64, pad=0.5, img_size=896, batch_size=32,
|
354 |
+
disable_output=False, cache_bg_frames=False, num_frames_bg_subtract=1000):
|
355 |
+
super().__init__(aris_filepath, beam_width_dir, annotations_file, batch_size, disable_output=disable_output, cache_bg_frames=cache_bg_frames, num_frames_bg_subtract=num_frames_bg_subtract)
|
356 |
|
357 |
# compute shapes for letterbox
|
358 |
aspect_ratio = self.ydim / self.xdim
|
inference.py β backend/inference.py
RENAMED
@@ -5,21 +5,21 @@ from tqdm import tqdm
|
|
5 |
from functools import partial
|
6 |
import numpy as np
|
7 |
import json
|
8 |
-
import time
|
9 |
from unittest.mock import patch
|
10 |
import math
|
|
|
|
|
11 |
|
12 |
# assumes yolov5 on sys.path
|
13 |
from lib.yolov5.models.experimental import attempt_load
|
14 |
from lib.yolov5.utils.torch_utils import select_device
|
15 |
from lib.yolov5.utils.general import clip_boxes, scale_boxes, xywh2xyxy
|
16 |
from lib.yolov5.utils.metrics import box_iou
|
17 |
-
import torch
|
18 |
-
import torchvision
|
19 |
|
20 |
-
from InferenceConfig import InferenceConfig, TrackerType
|
21 |
from lib.fish_eye.tracker import Tracker
|
22 |
-
from lib.fish_eye.
|
|
|
|
|
23 |
|
24 |
|
25 |
### Configuration options
|
@@ -51,56 +51,59 @@ def norm(bbox, w, h):
|
|
51 |
bb[3] /= h
|
52 |
return bb
|
53 |
|
54 |
-
def do_full_inference(dataloader, image_meter_width, image_meter_height, gp=None, config=InferenceConfig()):
|
55 |
|
56 |
# Set up model
|
57 |
model, device = setup_model(config.weights)
|
58 |
|
59 |
# Detect boxes in frames
|
60 |
-
inference, image_shapes, width, height = do_detection(dataloader, model, device, gp=gp)
|
61 |
|
|
|
|
|
|
|
|
|
|
|
62 |
if config.associative_tracker == TrackerType.BYTETRACK:
|
63 |
|
64 |
# Find low confidence detections
|
65 |
-
low_outputs = do_suppression(inference, image_meter_width, width, conf_thres=config.byte_low_conf, iou_thres=config.nms_iou, max_length=config.max_length, gp=gp)
|
66 |
-
low_preds, real_width, real_height = format_predictions(image_shapes, low_outputs, width, height, gp=gp)
|
67 |
|
68 |
# Find high confidence detections
|
69 |
-
high_outputs = do_suppression(inference, image_meter_width, width, conf_thres=config.byte_high_conf, iou_thres=config.nms_iou, max_length=config.max_length, gp=gp)
|
70 |
-
high_preds, real_width, real_height = format_predictions(image_shapes, high_outputs, width, height, gp=gp)
|
71 |
|
72 |
# Perform associative tracking (ByteTrack)
|
73 |
results = do_associative_tracking(
|
74 |
low_preds, high_preds, image_meter_width, image_meter_height,
|
75 |
reverse=False, min_length=config.min_length, min_travel=config.min_travel,
|
76 |
max_age=config.max_age, min_hits=config.min_hits,
|
77 |
-
gp=gp)
|
78 |
else:
|
79 |
|
80 |
# Find confident detections
|
81 |
-
outputs = do_suppression(inference, image_meter_width, width, conf_thres=config.conf_thresh, iou_thres=config.nms_iou, max_length=config.max_length, gp=gp)
|
82 |
|
83 |
if config.associative_tracker == TrackerType.CONF_BOOST:
|
84 |
|
85 |
# Boost confidence based on found confident detections
|
86 |
-
do_confidence_boost(inference, outputs, boost_power=config.boost_power, boost_decay=config.boost_decay, gp=gp)
|
87 |
|
88 |
# Find confident detections from boosted list
|
89 |
-
outputs = do_suppression(inference, image_meter_width, width, conf_thres=config.conf_thresh, iou_thres=config.nms_iou, max_length=config.max_length, gp=gp)
|
90 |
|
91 |
# Format confident detections
|
92 |
-
all_preds, real_width, real_height = format_predictions(image_shapes, outputs, width, height, gp=gp)
|
93 |
|
94 |
# Perform SORT tracking
|
95 |
results = do_tracking(
|
96 |
all_preds, image_meter_width, image_meter_height,
|
97 |
min_length=config.min_length, min_travel=config.min_travel,
|
98 |
max_age=config.max_age, min_hits=config.min_hits,
|
99 |
-
gp=gp)
|
100 |
|
101 |
return results
|
102 |
-
|
103 |
-
|
104 |
|
105 |
|
106 |
|
|
|
5 |
from functools import partial
|
6 |
import numpy as np
|
7 |
import json
|
|
|
8 |
from unittest.mock import patch
|
9 |
import math
|
10 |
+
import torch
|
11 |
+
import torchvision
|
12 |
|
13 |
# assumes yolov5 on sys.path
|
14 |
from lib.yolov5.models.experimental import attempt_load
|
15 |
from lib.yolov5.utils.torch_utils import select_device
|
16 |
from lib.yolov5.utils.general import clip_boxes, scale_boxes, xywh2xyxy
|
17 |
from lib.yolov5.utils.metrics import box_iou
|
|
|
|
|
18 |
|
|
|
19 |
from lib.fish_eye.tracker import Tracker
|
20 |
+
from lib.fish_eye.tracker_bytetrack import Associate
|
21 |
+
|
22 |
+
from backend.InferenceConfig import InferenceConfig, TrackerType
|
23 |
|
24 |
|
25 |
### Configuration options
|
|
|
51 |
bb[3] /= h
|
52 |
return bb
|
53 |
|
54 |
+
def do_full_inference(dataloader, image_meter_width, image_meter_height, gp=None, config=InferenceConfig(), verbose=True):
|
55 |
|
56 |
# Set up model
|
57 |
model, device = setup_model(config.weights)
|
58 |
|
59 |
# Detect boxes in frames
|
60 |
+
inference, image_shapes, width, height = do_detection(dataloader, model, device, gp=gp, verbose=verbose)
|
61 |
|
62 |
+
result = do_full_tracking(inference, image_shapes, image_meter_width, image_meter_height, width, height, config=InferenceConfig(), gp=None, verbose=verbose)
|
63 |
+
|
64 |
+
return result
|
65 |
+
|
66 |
+
def do_full_tracking(inference, image_shapes, image_meter_width, image_meter_height, width, height, config=InferenceConfig(), gp=None, verbose=True):
|
67 |
if config.associative_tracker == TrackerType.BYTETRACK:
|
68 |
|
69 |
# Find low confidence detections
|
70 |
+
low_outputs = do_suppression(inference, image_meter_width, width, conf_thres=config.byte_low_conf, iou_thres=config.nms_iou, max_length=config.max_length, gp=gp, verbose=verbose)
|
71 |
+
low_preds, real_width, real_height = format_predictions(image_shapes, low_outputs, width, height, gp=gp, verbose=verbose)
|
72 |
|
73 |
# Find high confidence detections
|
74 |
+
high_outputs = do_suppression(inference, image_meter_width, width, conf_thres=config.byte_high_conf, iou_thres=config.nms_iou, max_length=config.max_length, gp=gp, verbose=verbose)
|
75 |
+
high_preds, real_width, real_height = format_predictions(image_shapes, high_outputs, width, height, gp=gp, verbose=verbose)
|
76 |
|
77 |
# Perform associative tracking (ByteTrack)
|
78 |
results = do_associative_tracking(
|
79 |
low_preds, high_preds, image_meter_width, image_meter_height,
|
80 |
reverse=False, min_length=config.min_length, min_travel=config.min_travel,
|
81 |
max_age=config.max_age, min_hits=config.min_hits,
|
82 |
+
gp=gp, verbose=verbose)
|
83 |
else:
|
84 |
|
85 |
# Find confident detections
|
86 |
+
outputs = do_suppression(inference, image_meter_width, width, conf_thres=config.conf_thresh, iou_thres=config.nms_iou, max_length=config.max_length, gp=gp, verbose=verbose)
|
87 |
|
88 |
if config.associative_tracker == TrackerType.CONF_BOOST:
|
89 |
|
90 |
# Boost confidence based on found confident detections
|
91 |
+
do_confidence_boost(inference, outputs, boost_power=config.boost_power, boost_decay=config.boost_decay, gp=gp, verbose=verbose)
|
92 |
|
93 |
# Find confident detections from boosted list
|
94 |
+
outputs = do_suppression(inference, image_meter_width, width, conf_thres=config.conf_thresh, iou_thres=config.nms_iou, max_length=config.max_length, gp=gp, verbose=verbose)
|
95 |
|
96 |
# Format confident detections
|
97 |
+
all_preds, real_width, real_height = format_predictions(image_shapes, outputs, width, height, gp=gp, verbose=verbose)
|
98 |
|
99 |
# Perform SORT tracking
|
100 |
results = do_tracking(
|
101 |
all_preds, image_meter_width, image_meter_height,
|
102 |
min_length=config.min_length, min_travel=config.min_travel,
|
103 |
max_age=config.max_age, min_hits=config.min_hits,
|
104 |
+
gp=gp, verbose=verbose)
|
105 |
|
106 |
return results
|
|
|
|
|
107 |
|
108 |
|
109 |
|
main.py β backend/predict.py
RENAMED
@@ -1,11 +1,13 @@
|
|
|
|
|
|
1 |
import os
|
2 |
import torch
|
3 |
from zipfile import ZipFile
|
4 |
|
5 |
-
from aris import create_manual_marking, BEAM_WIDTH_DIR, add_metadata_to_result, prep_for_mm
|
6 |
-
from dataloader import create_dataloader_aris
|
7 |
-
from inference import do_full_inference, json_dump_round_float
|
8 |
-
from visualizer import generate_video_batches
|
9 |
|
10 |
def predict_task(filepath, config, output_formats=[], gradio_progress=None):
|
11 |
"""
|
@@ -32,6 +34,7 @@ def predict_task(filepath, config, output_formats=[], gradio_progress=None):
|
|
32 |
|
33 |
# Create dataloader
|
34 |
if (gradio_progress): gradio_progress(0, "Initializing Dataloader...")
|
|
|
35 |
dataloader, dataset = create_dataloader_aris(filepath, BEAM_WIDTH_DIR, None)
|
36 |
|
37 |
# Extract aris/didson info. Didson does not yet have pixel-meter info
|
@@ -55,11 +58,11 @@ def predict_task(filepath, config, output_formats=[], gradio_progress=None):
|
|
55 |
json_dump_round_float(results, results_filepath)
|
56 |
|
57 |
# Create Manual Marking file
|
58 |
-
if "Manual Marking" in output_formats and dataset.didson.info['version'][3] == 5:
|
59 |
create_manual_marking(results, out_path=marking_filepath)
|
60 |
|
61 |
# Create Annotated Video
|
62 |
-
if "Annotated Video" in output_formats:
|
63 |
generate_video_batches(dataset.didson, results, frame_rate, video_filepath,
|
64 |
image_meter_width=image_meter_width, image_meter_height=image_meter_height, gp=gradio_progress)
|
65 |
|
|
|
1 |
+
import project_path
|
2 |
+
|
3 |
import os
|
4 |
import torch
|
5 |
from zipfile import ZipFile
|
6 |
|
7 |
+
from backend.aris import create_manual_marking, BEAM_WIDTH_DIR, add_metadata_to_result, prep_for_mm
|
8 |
+
from backend.dataloader import create_dataloader_aris
|
9 |
+
from backend.inference import do_full_inference, json_dump_round_float
|
10 |
+
from backend.visualizer import generate_video_batches
|
11 |
|
12 |
def predict_task(filepath, config, output_formats=[], gradio_progress=None):
|
13 |
"""
|
|
|
34 |
|
35 |
# Create dataloader
|
36 |
if (gradio_progress): gradio_progress(0, "Initializing Dataloader...")
|
37 |
+
dataloader, dataset = create_dataloader_aris(filepath, BEAM_WIDTH_DIR, None, num_frames_bg_subtract=0)
|
38 |
dataloader, dataset = create_dataloader_aris(filepath, BEAM_WIDTH_DIR, None)
|
39 |
|
40 |
# Extract aris/didson info. Didson does not yet have pixel-meter info
|
|
|
58 |
json_dump_round_float(results, results_filepath)
|
59 |
|
60 |
# Create Manual Marking file
|
61 |
+
if "Generate Manual Marking" in output_formats and dataset.didson.info['version'][3] == 5:
|
62 |
create_manual_marking(results, out_path=marking_filepath)
|
63 |
|
64 |
# Create Annotated Video
|
65 |
+
if "Generate Annotated Video" in output_formats:
|
66 |
generate_video_batches(dataset.didson, results, frame_rate, video_filepath,
|
67 |
image_meter_width=image_meter_width, image_meter_height=image_meter_height, gp=gradio_progress)
|
68 |
|
pyDIDSON.py β backend/pyDIDSON.py
RENAMED
@@ -17,7 +17,7 @@ import struct
|
|
17 |
from types import SimpleNamespace
|
18 |
|
19 |
import lib.fish_eye.pyARIS as pyARIS
|
20 |
-
from pyDIDSON_format import *
|
21 |
|
22 |
|
23 |
class pyDIDSON:
|
|
|
17 |
from types import SimpleNamespace
|
18 |
|
19 |
import lib.fish_eye.pyARIS as pyARIS
|
20 |
+
from backend.pyDIDSON_format import *
|
21 |
|
22 |
|
23 |
class pyDIDSON:
|
pyDIDSON_format.py β backend/pyDIDSON_format.py
RENAMED
File without changes
|
uploader.py β backend/uploader.py
RENAMED
@@ -1,4 +1,3 @@
|
|
1 |
-
import project_path
|
2 |
import os;
|
3 |
from datetime import datetime;
|
4 |
|
@@ -30,12 +29,6 @@ def save_data_to_dir(bytes, filename, dirname):
|
|
30 |
return False, None, None
|
31 |
|
32 |
return True, filepath, dirname
|
33 |
-
|
34 |
-
|
35 |
-
def allowed_file(filename):
|
36 |
-
"""Only allow an ARIS file to be uploaded."""
|
37 |
-
return '.' in filename and \
|
38 |
-
filename.rsplit('.', 1)[1].lower() in ['aris', 'ddf']
|
39 |
|
40 |
def create_data_dir(identifier = None):
|
41 |
"""Create a (probably) unique directory for a task."""
|
|
|
|
|
1 |
import os;
|
2 |
from datetime import datetime;
|
3 |
|
|
|
29 |
return False, None, None
|
30 |
|
31 |
return True, filepath, dirname
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
|
33 |
def create_data_dir(identifier = None):
|
34 |
"""Create a (probably) unique directory for a task."""
|
visualizer.py β backend/visualizer.py
RENAMED
@@ -1,6 +1,5 @@
|
|
1 |
import project_path
|
2 |
|
3 |
-
import json
|
4 |
import cv2
|
5 |
import numpy as np
|
6 |
from tqdm import tqdm
|
|
|
1 |
import project_path
|
2 |
|
|
|
3 |
import cv2
|
4 |
import numpy as np
|
5 |
from tqdm import tqdm
|
dump.rdb
DELETED
Binary file (2.68 kB)
|
|
{gradio_scripts β frontend}/annotation_editor.js
RENAMED
File without changes
|
{gradio_scripts β frontend}/annotation_handler.py
RENAMED
@@ -1,13 +1,75 @@
|
|
1 |
-
import json
|
2 |
import cv2
|
3 |
import base64
|
|
|
|
|
|
|
4 |
|
5 |
VIDEO_HEIGHT = 700
|
6 |
|
7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
def init_frames(dataset, preds, index, gp=None):
|
9 |
"""Load frames for annotation editing
|
10 |
-
|
11 |
|
12 |
Returns:
|
13 |
list({
|
@@ -19,6 +81,7 @@ def init_frames(dataset, preds, index, gp=None):
|
|
19 |
)
|
20 |
})
|
21 |
"""
|
|
|
22 |
images = dataset.didson.load_frames(start_frame=0, end_frame=1)
|
23 |
|
24 |
# assumes all frames the same size
|
@@ -39,6 +102,7 @@ def init_frames(dataset, preds, index, gp=None):
|
|
39 |
if gp: gp((index + i)/len(preds['frames']), "Extracting Frames")
|
40 |
|
41 |
# Extract frames
|
|
|
42 |
img_raw = dataset.didson.load_frames(start_frame=index+i, end_frame=index+i+1)[0]
|
43 |
image = cv2.resize(cv2.cvtColor(img_raw, cv2.COLOR_GRAY2BGR), (w, h))
|
44 |
retval, buffer = cv2.imencode('.jpg', image)
|
@@ -65,3 +129,43 @@ def init_frames(dataset, preds, index, gp=None):
|
|
65 |
|
66 |
return annotations, end_index
|
67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import cv2
|
2 |
import base64
|
3 |
+
import gradio as gr
|
4 |
+
import json
|
5 |
+
import numpy as np
|
6 |
|
7 |
VIDEO_HEIGHT = 700
|
8 |
|
9 |
|
10 |
+
# annotation_btn.clock - switches to annotation tab and starts load_annotation
|
11 |
+
def prepare_annotation(state, result, result_index):
|
12 |
+
|
13 |
+
state['annotation_index'] = result_index
|
14 |
+
state['frame_index'] = 0
|
15 |
+
|
16 |
+
# output for [annotation_progress, master_tabs]
|
17 |
+
if result["aris_input"][result_index]:
|
18 |
+
return [
|
19 |
+
gr.update(value="<p id='annotation_info' style='display:none'>[]</p><!--" + str(np.random.rand()) + "-->", visible=True),
|
20 |
+
gr.update(selected=2)
|
21 |
+
]
|
22 |
+
return [gr.update(), gr.update()]
|
23 |
+
|
24 |
+
# annotation_progress.change - loads annotation frames in batches - called after prepare_annotation
|
25 |
+
def load_annotation(state, result, progress_bar):
|
26 |
+
|
27 |
+
# Get result index
|
28 |
+
result_index = state['annotation_index']
|
29 |
+
|
30 |
+
set_progress = lambda pct, msg: progress_bar(pct, desc=msg)
|
31 |
+
|
32 |
+
if state['frame_index'] == 0:
|
33 |
+
if set_progress: set_progress(0, "Loading Frames")
|
34 |
+
|
35 |
+
# Check that frames remain to be loaded
|
36 |
+
if state['frame_index'] < len(result['json_result'][result_index]['frames']):
|
37 |
+
|
38 |
+
# load frames and annotation
|
39 |
+
annotation_info, state['frame_index'] = init_frames(result["aris_input"][result_index], result['json_result'][result_index], state['frame_index'], gp=set_progress)
|
40 |
+
|
41 |
+
# save as html element
|
42 |
+
annotation_content = "<p id='annotation_info' style='display:none'>" + json.dumps(annotation_info) + "</p>"
|
43 |
+
|
44 |
+
# output for [annotation_editor, annotation_progress]
|
45 |
+
return [gr.update(), gr.update(value=annotation_content)]
|
46 |
+
|
47 |
+
# If complete, start annotation editor
|
48 |
+
|
49 |
+
annotation_html = ""
|
50 |
+
|
51 |
+
# Header
|
52 |
+
annotation_html += "<div id='annotation_header'>"
|
53 |
+
annotation_html += " <h1 id='annotation_frame_nbr'>Frame 0/100</h1>"
|
54 |
+
annotation_html += " <p id='annotation_edited'>(edited)</p>"
|
55 |
+
annotation_html += "</div>"
|
56 |
+
|
57 |
+
# Annotation Body
|
58 |
+
annotation_html += "<div style='display:flex'>"
|
59 |
+
annotation_html += " <canvas id='canvas' style='width:50%' onmousedown='mouse_down(event)' onmousemove='mouse_move(event)' onmouseup='mouse_up()' onmouseleave='mouse_up()'></canvas>"
|
60 |
+
annotation_html += " <div id='annotation_display' style='width:50%'></div>"
|
61 |
+
annotation_html += "</div>"
|
62 |
+
|
63 |
+
# Dummy objects
|
64 |
+
annotation_html += "<img id='annotation_img' onload='draw()' style='display:none'></img>"
|
65 |
+
annotation_html += "<!--" + str(np.random.rand()) + "-->"
|
66 |
+
|
67 |
+
# output for [annotation_editor, annotation_progress]
|
68 |
+
return [gr.update(value=annotation_html, visible=True), gr.update(visible=False)]
|
69 |
+
|
70 |
+
# called by load_annotation - read frames from dataloader and formats tracks
|
71 |
def init_frames(dataset, preds, index, gp=None):
|
72 |
"""Load frames for annotation editing
|
|
|
73 |
|
74 |
Returns:
|
75 |
list({
|
|
|
81 |
)
|
82 |
})
|
83 |
"""
|
84 |
+
|
85 |
images = dataset.didson.load_frames(start_frame=0, end_frame=1)
|
86 |
|
87 |
# assumes all frames the same size
|
|
|
102 |
if gp: gp((index + i)/len(preds['frames']), "Extracting Frames")
|
103 |
|
104 |
# Extract frames
|
105 |
+
|
106 |
img_raw = dataset.didson.load_frames(start_frame=index+i, end_frame=index+i+1)[0]
|
107 |
image = cv2.resize(cv2.cvtColor(img_raw, cv2.COLOR_GRAY2BGR), (w, h))
|
108 |
retval, buffer = cv2.imencode('.jpg', image)
|
|
|
129 |
|
130 |
return annotations, end_index
|
131 |
|
132 |
+
# javascript code that retrieves the data from load_annotation and saves it to the javascript window
|
133 |
+
js_store_frame_info = """
|
134 |
+
() => {
|
135 |
+
info_string = document.getElementById("annotation_info").innerHTML;
|
136 |
+
info = JSON.parse(info_string);
|
137 |
+
console.log(info)
|
138 |
+
if (info.length == 0) {
|
139 |
+
window.annotation_info = [];
|
140 |
+
return false;
|
141 |
+
}
|
142 |
+
window.annotation_info = window.annotation_info.concat(info)
|
143 |
+
console.log(window.annotation_info)
|
144 |
+
return true;
|
145 |
+
}
|
146 |
+
"""
|
147 |
+
|
148 |
+
annotation_css = """
|
149 |
+
#annotation_frame_nbr {
|
150 |
+
left: calc(50% - 100px);
|
151 |
+
position: absolute;
|
152 |
+
width: 200px;
|
153 |
+
text-align: center;
|
154 |
+
font-size: x-large;
|
155 |
+
}
|
156 |
+
#annotation_header {
|
157 |
+
height: 40px;
|
158 |
+
}
|
159 |
+
#annotation_frame_nbr {
|
160 |
+
left: calc(50% - 100px);
|
161 |
+
position: absolute;
|
162 |
+
width: 200px;
|
163 |
+
text-align: center;
|
164 |
+
font-size: x-large;
|
165 |
+
}
|
166 |
+
#annotation_edited {
|
167 |
+
right: 0px;
|
168 |
+
position: absolute;
|
169 |
+
margin-top: 5px;
|
170 |
+
}
|
171 |
+
"""
|
frontend/aris_crop.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import lib.fish_eye.pyARIS as pyARIS
|
2 |
+
import struct
|
3 |
+
|
4 |
+
def crop_clip(aris_path, num_frames, verbose=False):
|
5 |
+
"""
|
6 |
+
Crop an aris file based on the first *num_frames* frames. Save the new aris files in the tmp folder
|
7 |
+
"""
|
8 |
+
|
9 |
+
# load aris file and extract frame size
|
10 |
+
ARIS_data, frame = pyARIS.DataImport(aris_path)
|
11 |
+
FrameSize = ARIS_data.NumRawBeams*ARIS_data.SamplesPerChannel
|
12 |
+
if verbose: print("True Old", ARIS_data.FrameCount, ARIS_data.StartFrame, ARIS_data.EndFrame)
|
13 |
+
|
14 |
+
# get byte index of the cutoff point
|
15 |
+
frameoffset = (1024+(num_frames*(1024+(FrameSize))))
|
16 |
+
|
17 |
+
# read aris the bytes for the head and frames we want and cast to bytearray
|
18 |
+
data = open(ARIS_data.filename, 'rb')
|
19 |
+
cropped = data.read(frameoffset)
|
20 |
+
array = bytearray(cropped)
|
21 |
+
|
22 |
+
# get old values for important metadata
|
23 |
+
old_frame_count = struct.unpack("I", array[4:8])[0]
|
24 |
+
old_start_frame = struct.unpack("I", array[352:356])[0]
|
25 |
+
old_end_frame = struct.unpack("I", array[356:360])[0]
|
26 |
+
|
27 |
+
if verbose: print("old", old_frame_count, old_start_frame, old_end_frame)
|
28 |
+
|
29 |
+
# set new values
|
30 |
+
array[4:8] = bytearray(struct.pack("I", num_frames))
|
31 |
+
array[352:356] = bytearray(struct.pack("I", old_start_frame))
|
32 |
+
array[356:360] = bytearray(struct.pack("I", old_start_frame + num_frames))
|
33 |
+
if verbose: print("new", array[4:8], array[352:356], array[356:360])
|
34 |
+
|
35 |
+
# cast to bytes
|
36 |
+
cropped = bytes(array)
|
37 |
+
|
38 |
+
# save new aris file
|
39 |
+
with open("tmp/cropped_aris.aris", 'wb') as f:
|
40 |
+
f.write(cropped)
|
41 |
+
|
42 |
+
# load file to check that the frame count, start frame and end frame makes sense
|
43 |
+
if verbose:
|
44 |
+
ARIS_data_2, frame = pyARIS.DataImport("tmp/cropped_aris.aris")
|
45 |
+
print("check", ARIS_data_2.FrameCount, ARIS_data_2.StartFrame, ARIS_data_2.EndFrame)
|
gradio_scripts/file_reader.py β frontend/custom_file_reader.py
RENAMED
File without changes
|
{gradio_scripts β frontend}/pdf_handler.py
RENAMED
@@ -1,26 +1,23 @@
|
|
1 |
import datetime
|
2 |
import numpy as np
|
3 |
from matplotlib.backends.backend_pdf import PdfPages
|
4 |
-
from matplotlib import collections as mc
|
5 |
import matplotlib.pyplot as plt
|
6 |
import math
|
7 |
-
from aris import BEAM_WIDTH_DIR
|
8 |
import cv2
|
9 |
-
|
10 |
-
from dataloader import create_dataloader_aris
|
11 |
-
|
12 |
|
13 |
STANDARD_FIG_SIZE = (16, 9)
|
14 |
-
OUT_PDF_FILE_NAME = '
|
|
|
15 |
|
16 |
|
17 |
-
def make_pdf(i, state, result, table_headers):
|
18 |
|
19 |
fish_info = result["fish_info"][i]
|
20 |
fish_table = result["fish_table"][i]
|
21 |
json_result = result['json_result'][i]
|
|
|
22 |
metadata = json_result['metadata']
|
23 |
-
aris_input = result["aris_input"][i]
|
24 |
|
25 |
with PdfPages(OUT_PDF_FILE_NAME) as pdf:
|
26 |
plt.rcParams['text.usetex'] = False
|
@@ -31,11 +28,6 @@ def make_pdf(i, state, result, table_headers):
|
|
31 |
|
32 |
generate_fish_list(pdf, table_headers, fish_table)
|
33 |
|
34 |
-
|
35 |
-
dataset = None
|
36 |
-
if (aris_input is not None):
|
37 |
-
dataloader, dataset = create_dataloader_aris(aris_input, BEAM_WIDTH_DIR, None)
|
38 |
-
|
39 |
for i, fish in enumerate(json_result['fish']):
|
40 |
calculate_fish_paths(json_result, dataset, i)
|
41 |
|
@@ -173,6 +165,7 @@ def calculate_fish_paths(result, dataset, id):
|
|
173 |
img = None
|
174 |
if (dataset is not None):
|
175 |
|
|
|
176 |
images = dataset.didson.load_frames(start_frame=start_frame, end_frame=start_frame+1)
|
177 |
img = images[0]
|
178 |
|
@@ -358,10 +351,6 @@ def draw_fish_tracks(pdf, result, dataset, id):
|
|
358 |
|
359 |
|
360 |
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
if (dataset is not None):
|
366 |
indices = [start_frame, int(2/3*start_frame + end_frame/3), int(1/3*start_frame + 2/3*end_frame), end_frame]
|
367 |
fig, axs = plt.subplots(2, len(indices), sharex=False, sharey=False, figsize=STANDARD_FIG_SIZE)
|
@@ -377,15 +366,6 @@ def draw_fish_tracks(pdf, result, dataset, id):
|
|
377 |
box = ann['bbox']
|
378 |
frame_index = fi
|
379 |
break
|
380 |
-
|
381 |
-
batch_i = math.floor(frame_index/32)
|
382 |
-
fi = frame_index - batch_i*32
|
383 |
-
batch = dataset[batch_i]
|
384 |
-
(rgb_img, _, shapes) = batch[fi]
|
385 |
-
rgb_img = rgb_img.permute(1, 2, 0)
|
386 |
-
print(type(batch))
|
387 |
-
print(type(rgb_img))
|
388 |
-
print(rgb_img.shape)
|
389 |
|
390 |
print("box", i, box)
|
391 |
if box is not None:
|
@@ -400,18 +380,6 @@ def draw_fish_tracks(pdf, result, dataset, id):
|
|
400 |
axs[0, i].imshow(cropped_img, extent=(cx-s, cx+s, cy-s, cy+s), cmap=plt.colormaps['Greys_r'])
|
401 |
axs[0, i].plot([x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1], color="red")
|
402 |
axs[0, i].set_title('Frame ' + str(frame_index))
|
403 |
-
|
404 |
-
h, w, _ = rgb_img.shape
|
405 |
-
print(w, h)
|
406 |
-
x1, x2, y1, y2 = int(box[0]*w), int(box[2]*w), int(box[1]*h), int(box[3]*h)
|
407 |
-
cx, cy = int((x2 + x1)/2), int((y2 + y1)/2)
|
408 |
-
s = min(int(max(x2 - x1, y2 - y1)*5/2), cx, cy, w-cx, h-cy)
|
409 |
-
print(x1, x2, y1, y2)
|
410 |
-
print(cx, cy, s)
|
411 |
-
cropped_img = rgb_img[cy-s:cy+s, cx-s:cx+s, :]
|
412 |
-
axs[1, i].imshow(cropped_img, extent=(cx-s, cx+s, cy-s, cy+s), cmap=plt.colormaps['Greys_r'])
|
413 |
-
axs[1, i].plot([x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1], color="red")
|
414 |
-
axs[1, i].set_title('Frame ' + str(frame_index))
|
415 |
|
416 |
pdf.savefig(fig)
|
417 |
plt.close(fig)
|
|
|
1 |
import datetime
|
2 |
import numpy as np
|
3 |
from matplotlib.backends.backend_pdf import PdfPages
|
|
|
4 |
import matplotlib.pyplot as plt
|
5 |
import math
|
|
|
6 |
import cv2
|
7 |
+
import os
|
|
|
|
|
8 |
|
9 |
STANDARD_FIG_SIZE = (16, 9)
|
10 |
+
OUT_PDF_FILE_NAME = 'tmp/fisheye_pdf.pdf'
|
11 |
+
os.makedirs('tmp', exist_ok=True)
|
12 |
|
13 |
|
14 |
+
def make_pdf(i, state, result, dataset, table_headers):
|
15 |
|
16 |
fish_info = result["fish_info"][i]
|
17 |
fish_table = result["fish_table"][i]
|
18 |
json_result = result['json_result'][i]
|
19 |
+
dataset = result['datasets'][i]
|
20 |
metadata = json_result['metadata']
|
|
|
21 |
|
22 |
with PdfPages(OUT_PDF_FILE_NAME) as pdf:
|
23 |
plt.rcParams['text.usetex'] = False
|
|
|
28 |
|
29 |
generate_fish_list(pdf, table_headers, fish_table)
|
30 |
|
|
|
|
|
|
|
|
|
|
|
31 |
for i, fish in enumerate(json_result['fish']):
|
32 |
calculate_fish_paths(json_result, dataset, i)
|
33 |
|
|
|
165 |
img = None
|
166 |
if (dataset is not None):
|
167 |
|
168 |
+
|
169 |
images = dataset.didson.load_frames(start_frame=start_frame, end_frame=start_frame+1)
|
170 |
img = images[0]
|
171 |
|
|
|
351 |
|
352 |
|
353 |
|
|
|
|
|
|
|
|
|
354 |
if (dataset is not None):
|
355 |
indices = [start_frame, int(2/3*start_frame + end_frame/3), int(1/3*start_frame + 2/3*end_frame), end_frame]
|
356 |
fig, axs = plt.subplots(2, len(indices), sharex=False, sharey=False, figsize=STANDARD_FIG_SIZE)
|
|
|
366 |
box = ann['bbox']
|
367 |
frame_index = fi
|
368 |
break
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
369 |
|
370 |
print("box", i, box)
|
371 |
if box is not None:
|
|
|
380 |
axs[0, i].imshow(cropped_img, extent=(cx-s, cx+s, cy-s, cy+s), cmap=plt.colormaps['Greys_r'])
|
381 |
axs[0, i].plot([x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1], color="red")
|
382 |
axs[0, i].set_title('Frame ' + str(frame_index))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
383 |
|
384 |
pdf.savefig(fig)
|
385 |
plt.close(fig)
|
{gradio_scripts β frontend}/result_ui.py
RENAMED
@@ -1,8 +1,9 @@
|
|
1 |
import gradio as gr
|
2 |
import numpy as np
|
3 |
-
from gradio_scripts.pdf_handler import make_pdf
|
4 |
import os
|
5 |
|
|
|
|
|
6 |
js_update_tab_labels = """
|
7 |
async () => {
|
8 |
let el_list = document.getElementById("tab_labeler").getElementsByClassName("svelte-1kcgrqr")
|
@@ -39,9 +40,9 @@ def update_result(i, state, result, inference_handler):
|
|
39 |
# Check if inference is done
|
40 |
not_done = state['index'] < state['total']
|
41 |
|
42 |
-
annotation_avaliable =
|
43 |
|
44 |
-
if 'PDF' in state['outputs']:
|
45 |
print("making pdf")
|
46 |
make_pdf(state['index']-1, state, result, table_headers)
|
47 |
print("done pdf")
|
@@ -65,9 +66,14 @@ def update_result(i, state, result, inference_handler):
|
|
65 |
}
|
66 |
|
67 |
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
-
|
70 |
-
def Result_Gradio(prepare_annotation, components):
|
71 |
global tabs, tab_parent, zip_out
|
72 |
|
73 |
# Dummy element to call inference events, this also displays the inference progress
|
@@ -86,9 +92,23 @@ def Result_Gradio(prepare_annotation, components):
|
|
86 |
visual_components = []
|
87 |
|
88 |
# Zip file output
|
89 |
-
zip_out = gr.File(label="ZIP Output", interactive=False)
|
90 |
visual_components.append(zip_out)
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
# Create result tabs
|
94 |
tabs = []
|
@@ -128,4 +148,48 @@ def Result_Gradio(prepare_annotation, components):
|
|
128 |
|
129 |
components['result_tabs'] = tab_parent
|
130 |
|
131 |
-
return visual_components
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
import numpy as np
|
|
|
3 |
import os
|
4 |
|
5 |
+
from frontend.pdf_handler import make_pdf
|
6 |
+
|
7 |
js_update_tab_labels = """
|
8 |
async () => {
|
9 |
let el_list = document.getElementById("tab_labeler").getElementsByClassName("svelte-1kcgrqr")
|
|
|
40 |
# Check if inference is done
|
41 |
not_done = state['index'] < state['total']
|
42 |
|
43 |
+
annotation_avaliable = state['enable_annotation_editor'] and (result["aris_input"][i] is not None)
|
44 |
|
45 |
+
if 'Generate PDF' in state['outputs']:
|
46 |
print("making pdf")
|
47 |
make_pdf(state['index']-1, state, result, table_headers)
|
48 |
print("done pdf")
|
|
|
66 |
}
|
67 |
|
68 |
|
69 |
+
# Auto_download
|
70 |
+
def auto_download_zip(state):
|
71 |
+
if 'Automatically download result' in state['outputs']:
|
72 |
+
return gr.update(value=str(np.random.rand()))
|
73 |
+
else:
|
74 |
+
return gr.update()
|
75 |
|
76 |
+
def Result_Gradio(prepare_annotation, components, state):
|
|
|
77 |
global tabs, tab_parent, zip_out
|
78 |
|
79 |
# Dummy element to call inference events, this also displays the inference progress
|
|
|
92 |
visual_components = []
|
93 |
|
94 |
# Zip file output
|
95 |
+
zip_out = gr.File(label="ZIP Output", elem_id="zip_out", interactive=False)
|
96 |
visual_components.append(zip_out)
|
97 |
+
components['zip_out'] = zip_out
|
98 |
+
|
99 |
+
autodownloader = gr.Text(value="LOADING", visible=False)
|
100 |
+
|
101 |
+
zip_out.change(lambda: auto_download_zip(state), None, autodownloader)
|
102 |
+
autodownloader.change(lambda x: x, autodownloader, None, _js="""
|
103 |
+
() => {
|
104 |
+
zip_out = document.getElementById("zip_out")
|
105 |
+
downloads = zip_out?.getElementsByClassName("download")
|
106 |
+
if (downloads?.length > 0) {
|
107 |
+
downloads[downloads.length-1].children[0].click()
|
108 |
+
}
|
109 |
+
}
|
110 |
+
"""
|
111 |
+
)
|
112 |
|
113 |
# Create result tabs
|
114 |
tabs = []
|
|
|
148 |
|
149 |
components['result_tabs'] = tab_parent
|
150 |
|
151 |
+
return visual_components
|
152 |
+
|
153 |
+
|
154 |
+
def create_metadata_table(result, table_headers, info_headers):
|
155 |
+
if 'metadata' in result:
|
156 |
+
metadata = result['metadata']
|
157 |
+
else:
|
158 |
+
metadata = { 'FISH': [] }
|
159 |
+
|
160 |
+
# Calculate detection dropout
|
161 |
+
for fish in metadata['FISH']:
|
162 |
+
count = 0
|
163 |
+
for frame in result['frames'][fish['START_FRAME']:fish['END_FRAME']+1]:
|
164 |
+
for ann in frame['fish']:
|
165 |
+
if ann['fish_id'] == fish['TOTAL']:
|
166 |
+
count += 1
|
167 |
+
fish['DETECTION_DROPOUT'] = 1 - count / (fish['END_FRAME'] + 1 - fish['START_FRAME'])
|
168 |
+
|
169 |
+
# Create fish table
|
170 |
+
table = []
|
171 |
+
for fish in metadata["FISH"]:
|
172 |
+
row = []
|
173 |
+
for header in table_headers:
|
174 |
+
row.append(fish[header])
|
175 |
+
table.append(row)
|
176 |
+
|
177 |
+
if len(metadata["FISH"]) == 0:
|
178 |
+
row = []
|
179 |
+
for header in table_headers:
|
180 |
+
row.append("-")
|
181 |
+
table.append(row)
|
182 |
+
|
183 |
+
# Create info table
|
184 |
+
info = []
|
185 |
+
for field in info_headers:
|
186 |
+
field_name = "**" + field + "**"
|
187 |
+
if field in metadata:
|
188 |
+
info.append([field_name, str(metadata[field])])
|
189 |
+
else:
|
190 |
+
info.append([field_name, ""])
|
191 |
+
if 'hyperparameters' in metadata:
|
192 |
+
for param_name in metadata['hyperparameters']:
|
193 |
+
info.append(['**' + param_name + '**', str(metadata['hyperparameters'][param_name])])
|
194 |
+
|
195 |
+
return table, info
|
frontend/state_handler.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
def reset_state(result, state):
|
3 |
+
|
4 |
+
# Reset Result
|
5 |
+
result["json_result"] = []
|
6 |
+
result["aris_input"] = []
|
7 |
+
result["datasets"] = []
|
8 |
+
result["path_video"] = []
|
9 |
+
result["path_zip"] = []
|
10 |
+
result["path_json"] = []
|
11 |
+
result["path_marking"] = []
|
12 |
+
result["fish_table"] = []
|
13 |
+
result["fish_info"] = []
|
14 |
+
|
15 |
+
# Reset State
|
16 |
+
state['files'] = []
|
17 |
+
state['index'] = 0
|
18 |
+
state['total'] = 0
|
19 |
+
|
20 |
+
|
21 |
+
|
22 |
+
|
{gradio_scripts β frontend}/upload_ui.py
RENAMED
@@ -1,13 +1,13 @@
|
|
1 |
import gradio as gr
|
2 |
-
from
|
3 |
-
from InferenceConfig import InferenceConfig, TrackerType
|
4 |
-
|
5 |
|
6 |
models = {
|
7 |
'master': 'models/v5m_896_300best.pt',
|
8 |
-
'elwha': 'models/YsEE20.pt',
|
9 |
-
'elwha+kenai_val': 'models/YsEKvE20.pt',
|
10 |
-
'elwha
|
11 |
}
|
12 |
|
13 |
def Upload_Gradio(gradio_components):
|
@@ -38,28 +38,33 @@ def Upload_Gradio(gradio_components):
|
|
38 |
tracker = gr.Dropdown(["None", "Confidence Boost", "ByteTrack"], value=default_tracker, label="Associative Tracking")
|
39 |
hyperparams.append(tracker)
|
40 |
with gr.Row(visible=default_tracker=="Confidence Boost") as track_row:
|
41 |
-
hyperparams.append(gr.Slider(0, 5, value=default_settings.boost_power, label="Boost Power", info=""))
|
42 |
-
hyperparams.append(gr.Slider(0, 1, value=default_settings.boost_decay, label="Boost Decay", info=""))
|
43 |
tracker.change(lambda x: gr.update(visible=(x=="Confidence Boost")), tracker, track_row)
|
44 |
with gr.Row(visible=default_tracker=="ByteTrack") as track_row:
|
45 |
-
hyperparams.append(gr.Slider(0, 1, value=default_settings.byte_low_conf, label="Low Conf Threshold", info=""))
|
46 |
-
hyperparams.append(gr.Slider(0, 1, value=default_settings.byte_high_conf, label="High Conf Threshold", info=""))
|
47 |
tracker.change(lambda x: gr.update(visible=(x=="ByteTrack")), tracker, track_row)
|
48 |
|
49 |
gr.Markdown("Other")
|
50 |
with gr.Row():
|
51 |
hyperparams.append(gr.Slider(0, 3, value=default_settings.min_length, label="Min Length", info="Minimum length of fish (meters) in order for it to count"))
|
52 |
-
hyperparams.append(gr.Slider(0, 3, value=default_settings.max_length, label="Max Length", info="Maximum length of fish (meters) in order for it to count"))
|
53 |
hyperparams.append(gr.Slider(0, 10, value=default_settings.min_travel, label="Min Travel", info="Minimum travel distance of track (meters) in order for it to count"))
|
54 |
|
55 |
gradio_components['hyperparams'] = hyperparams
|
56 |
|
57 |
with gr.Row():
|
58 |
-
hyperparams.append(gr.CheckboxGroup(["Annotated Video", "Manual Marking", "PDF"], label="Output
|
59 |
|
60 |
#Input field for aris submission
|
61 |
gradio_components['input'] = File(file_types=[".aris", ".ddf"], type="binary", label="ARIS Input", file_count="multiple")
|
62 |
|
|
|
|
|
|
|
|
|
|
|
63 |
# Tab - uploading old result files to review
|
64 |
with gr.Tab("Open Result"):
|
65 |
gr.HTML("""
|
|
|
1 |
import gradio as gr
|
2 |
+
from frontend.custom_file_reader import File
|
3 |
+
from backend.InferenceConfig import InferenceConfig, TrackerType
|
4 |
+
import os
|
5 |
|
6 |
models = {
|
7 |
'master': 'models/v5m_896_300best.pt',
|
8 |
+
# 'elwha': 'models/YsEE20.pt',
|
9 |
+
# 'elwha+kenai_val': 'models/YsEKvE20.pt',
|
10 |
+
'elwha': 'models/YsEKtE20.pt',
|
11 |
}
|
12 |
|
13 |
def Upload_Gradio(gradio_components):
|
|
|
38 |
tracker = gr.Dropdown(["None", "Confidence Boost", "ByteTrack"], value=default_tracker, label="Associative Tracking")
|
39 |
hyperparams.append(tracker)
|
40 |
with gr.Row(visible=default_tracker=="Confidence Boost") as track_row:
|
41 |
+
hyperparams.append(gr.Slider(0, 5, value=default_settings.boost_power, label="Boost Power", info="Scalar multiplier for the boost amount"))
|
42 |
+
hyperparams.append(gr.Slider(0, 1, value=default_settings.boost_decay, label="Boost Decay", info="Exponential decay parameter for boost based on frame time difference"))
|
43 |
tracker.change(lambda x: gr.update(visible=(x=="Confidence Boost")), tracker, track_row)
|
44 |
with gr.Row(visible=default_tracker=="ByteTrack") as track_row:
|
45 |
+
hyperparams.append(gr.Slider(0, 1, value=default_settings.byte_low_conf, label="Low Conf Threshold", info="Confidence threshold for the low detection group"))
|
46 |
+
hyperparams.append(gr.Slider(0, 1, value=default_settings.byte_high_conf, label="High Conf Threshold", info="Confidence threshold for the high detection group"))
|
47 |
tracker.change(lambda x: gr.update(visible=(x=="ByteTrack")), tracker, track_row)
|
48 |
|
49 |
gr.Markdown("Other")
|
50 |
with gr.Row():
|
51 |
hyperparams.append(gr.Slider(0, 3, value=default_settings.min_length, label="Min Length", info="Minimum length of fish (meters) in order for it to count"))
|
52 |
+
hyperparams.append(gr.Slider(0, 3, value=default_settings.max_length, label="Max Length", info="Maximum length of fish (meters) in order for it to count. (disable at 0)"))
|
53 |
hyperparams.append(gr.Slider(0, 10, value=default_settings.min_travel, label="Min Travel", info="Minimum travel distance of track (meters) in order for it to count"))
|
54 |
|
55 |
gradio_components['hyperparams'] = hyperparams
|
56 |
|
57 |
with gr.Row():
|
58 |
+
hyperparams.append(gr.CheckboxGroup([("Generate Annotated Video"), ("Generate Manual Marking"), ("Generate PDF"), ("Automatically download result")], label="Output settings", interactive=True, value=["Generate Annotated Video"]))
|
59 |
|
60 |
#Input field for aris submission
|
61 |
gradio_components['input'] = File(file_types=[".aris", ".ddf"], type="binary", label="ARIS Input", file_count="multiple")
|
62 |
|
63 |
+
example_name = "static/example.aris"
|
64 |
+
gradio_components['examples'] = gr.Examples(examples=[[example_name]], inputs=gradio_components['input'])
|
65 |
+
|
66 |
+
gradio_components['inference_btn'] = gr.Button("Run")
|
67 |
+
|
68 |
# Tab - uploading old result files to review
|
69 |
with gr.Tab("Open Result"):
|
70 |
gr.HTML("""
|
gradio_scripts/state_handler.py
DELETED
@@ -1,451 +0,0 @@
|
|
1 |
-
from aris import create_metadata_table
|
2 |
-
import json
|
3 |
-
|
4 |
-
example_metadata = {
|
5 |
-
"FILE_NAME": "static/example_metadata/fisheye",
|
6 |
-
"FRAME_RATE": 6.548702716827393,
|
7 |
-
"UPSTREAM_FISH": 0,
|
8 |
-
"DOWNSTREAM_FISH": 0,
|
9 |
-
"NONDIRECTIONAL_FISH": 14,
|
10 |
-
"TOTAL_FISH": 14,
|
11 |
-
"TOTAL_FRAMES": 644,
|
12 |
-
"EXPECTED_FRAMES": -1,
|
13 |
-
"TOTAL_TIME": "0:01:38",
|
14 |
-
"EXPECTED_TIME": "0:00:00",
|
15 |
-
"UPSTREAM_MOTION": "Right To Left",
|
16 |
-
"COUNT_FILE_NAME": "N/A",
|
17 |
-
"EDITOR_ID": "N/A",
|
18 |
-
"INTENSITY": "0.0 dB",
|
19 |
-
"THRESHOLD": "0.0 dB",
|
20 |
-
"WINDOW_START": 1,
|
21 |
-
"WINDOW_END": 17,
|
22 |
-
"WATER_TEMP": "13 degC",
|
23 |
-
"FISH": [
|
24 |
-
{
|
25 |
-
"FILE": 1,
|
26 |
-
"TOTAL": 1,
|
27 |
-
"FRAME_NUM": 12,
|
28 |
-
"DIR": " N/A",
|
29 |
-
"R": 13.403139282569885,
|
30 |
-
"THETA": 0.1706,
|
31 |
-
"L": 63.739999999999995,
|
32 |
-
"DR": -1,
|
33 |
-
"LDR": -1,
|
34 |
-
"ASPECT": -1,
|
35 |
-
"TIME": "11:54:40",
|
36 |
-
"DATE": "2018-07-09",
|
37 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
38 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
39 |
-
"PAN": None,
|
40 |
-
"TILT": None,
|
41 |
-
"ROLL": 0,
|
42 |
-
"SPECIES": "Unknown",
|
43 |
-
"MOTION": "Running <-->",
|
44 |
-
"Q": -1,
|
45 |
-
"N": -1,
|
46 |
-
"COMMENT": ""
|
47 |
-
}, {
|
48 |
-
"FILE": 1,
|
49 |
-
"TOTAL": 2,
|
50 |
-
"FRAME_NUM": 35,
|
51 |
-
"DIR": " N/A",
|
52 |
-
"R": 13.206211097755432,
|
53 |
-
"THETA": -9.1195,
|
54 |
-
"L": 73.33,
|
55 |
-
"DR": -1,
|
56 |
-
"LDR": -1,
|
57 |
-
"ASPECT": -1,
|
58 |
-
"TIME": "11:54:44",
|
59 |
-
"DATE": "2018-07-09",
|
60 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
61 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
62 |
-
"PAN": None,
|
63 |
-
"TILT": None,
|
64 |
-
"ROLL": 0,
|
65 |
-
"SPECIES": "Unknown",
|
66 |
-
"MOTION": "Running <-->",
|
67 |
-
"Q": -1,
|
68 |
-
"N": -1,
|
69 |
-
"COMMENT": ""
|
70 |
-
}, {
|
71 |
-
"FILE": 1,
|
72 |
-
"TOTAL": 3,
|
73 |
-
"FRAME_NUM": 122,
|
74 |
-
"DIR": " N/A",
|
75 |
-
"R": 13.219339643409729,
|
76 |
-
"THETA": -9.3961,
|
77 |
-
"L": 84.77,
|
78 |
-
"DR": -1,
|
79 |
-
"LDR": -1,
|
80 |
-
"ASPECT": -1,
|
81 |
-
"TIME": "11:54:58",
|
82 |
-
"DATE": "2018-07-09",
|
83 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
84 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
85 |
-
"PAN": None,
|
86 |
-
"TILT": None,
|
87 |
-
"ROLL": 0,
|
88 |
-
"SPECIES": "Unknown",
|
89 |
-
"MOTION": "Running <-->",
|
90 |
-
"Q": -1,
|
91 |
-
"N": -1,
|
92 |
-
"COMMENT": ""
|
93 |
-
}, {
|
94 |
-
"FILE": 1,
|
95 |
-
"TOTAL": 4,
|
96 |
-
"FRAME_NUM": 123,
|
97 |
-
"DIR": "N/A",
|
98 |
-
"R": 12.996154367286682,
|
99 |
-
"THETA": 10.7991,
|
100 |
-
"L": 59.919999999999995,
|
101 |
-
"DR": -1,
|
102 |
-
"LDR": -1,
|
103 |
-
"ASPECT": -1,
|
104 |
-
"TIME": "11:54:58",
|
105 |
-
"DATE": "2018-07-09",
|
106 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
107 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
108 |
-
"PAN": None,
|
109 |
-
"TILT": None,
|
110 |
-
"ROLL": 0,
|
111 |
-
"SPECIES": "Unknown",
|
112 |
-
"MOTION": "Running <-->",
|
113 |
-
"Q": -1,
|
114 |
-
"N": -1,
|
115 |
-
"COMMENT": ""
|
116 |
-
}, {
|
117 |
-
"FILE": 1,
|
118 |
-
"TOTAL": 5,
|
119 |
-
"FRAME_NUM": 130,
|
120 |
-
"DIR": " N/A",
|
121 |
-
"R": 12.484141086769105,
|
122 |
-
"THETA": -8.2654,
|
123 |
-
"L": 70.89999999999999,
|
124 |
-
"DR": -1,
|
125 |
-
"LDR": -1,
|
126 |
-
"ASPECT": -1,
|
127 |
-
"TIME": "11:54:59",
|
128 |
-
"DATE": "2018-07-09",
|
129 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
130 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
131 |
-
"PAN": None,
|
132 |
-
"TILT": None,
|
133 |
-
"ROLL": 0,
|
134 |
-
"SPECIES": "Unknown",
|
135 |
-
"MOTION": "Running <-->",
|
136 |
-
"Q": -1,
|
137 |
-
"N": -1,
|
138 |
-
"COMMENT": ""
|
139 |
-
}, {
|
140 |
-
"FILE": 1,
|
141 |
-
"TOTAL": 6,
|
142 |
-
"FRAME_NUM": 218,
|
143 |
-
"DIR": " N/A",
|
144 |
-
"R": 13.232468189064026,
|
145 |
-
"THETA": -9.3961,
|
146 |
-
"L": 77.25999999999999,
|
147 |
-
"DR": -1,
|
148 |
-
"LDR": -1,
|
149 |
-
"ASPECT": -1,
|
150 |
-
"TIME": "11:55:12",
|
151 |
-
"DATE": "2018-07-09",
|
152 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
153 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
154 |
-
"PAN": None,
|
155 |
-
"TILT": None,
|
156 |
-
"ROLL": 0,
|
157 |
-
"SPECIES": "Unknown",
|
158 |
-
"MOTION": "Running <-->",
|
159 |
-
"Q": -1,
|
160 |
-
"N": -1,
|
161 |
-
"COMMENT": ""
|
162 |
-
}, {
|
163 |
-
"FILE": 1,
|
164 |
-
"TOTAL": 7,
|
165 |
-
"FRAME_NUM": 278,
|
166 |
-
"DIR": " N/A",
|
167 |
-
"R": 13.967666745704651,
|
168 |
-
"THETA": -12.8758,
|
169 |
-
"L": 37.51,
|
170 |
-
"DR": -1,
|
171 |
-
"LDR": -1,
|
172 |
-
"ASPECT": -1,
|
173 |
-
"TIME": "11:55:22",
|
174 |
-
"DATE": "2018-07-09",
|
175 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
176 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
177 |
-
"PAN": None,
|
178 |
-
"TILT": None,
|
179 |
-
"ROLL": 0,
|
180 |
-
"SPECIES": "Unknown",
|
181 |
-
"MOTION": "Running <-->",
|
182 |
-
"Q": -1,
|
183 |
-
"N": -1,
|
184 |
-
"COMMENT": ""
|
185 |
-
}, {
|
186 |
-
"FILE": 1,
|
187 |
-
"TOTAL": 8,
|
188 |
-
"FRAME_NUM": 302,
|
189 |
-
"DIR": " N/A",
|
190 |
-
"R": 13.25872528037262,
|
191 |
-
"THETA": -9.1195,
|
192 |
-
"L": 79.5,
|
193 |
-
"DR": -1,
|
194 |
-
"LDR": -1,
|
195 |
-
"ASPECT": -1,
|
196 |
-
"TIME": "11:55:25",
|
197 |
-
"DATE": "2018-07-09",
|
198 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
199 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
200 |
-
"PAN": None,
|
201 |
-
"TILT": None,
|
202 |
-
"ROLL": 0,
|
203 |
-
"SPECIES": "Unknown",
|
204 |
-
"MOTION": "Running <-->",
|
205 |
-
"Q": -1,
|
206 |
-
"N": -1,
|
207 |
-
"COMMENT": ""
|
208 |
-
}, {
|
209 |
-
"FILE": 1,
|
210 |
-
"TOTAL": 9,
|
211 |
-
"FRAME_NUM": 331,
|
212 |
-
"DIR": " N/A",
|
213 |
-
"R": 13.25872528037262,
|
214 |
-
"THETA": -9.1195,
|
215 |
-
"L": 80.67,
|
216 |
-
"DR": -1,
|
217 |
-
"LDR": -1,
|
218 |
-
"ASPECT": -1,
|
219 |
-
"TIME": "11:55:30",
|
220 |
-
"DATE": "2018-07-09",
|
221 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
222 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
223 |
-
"PAN": None,
|
224 |
-
"TILT": None,
|
225 |
-
"ROLL": 0,
|
226 |
-
"SPECIES": "Unknown",
|
227 |
-
"MOTION": "Running <-->",
|
228 |
-
"Q": -1,
|
229 |
-
"N": -1,
|
230 |
-
"COMMENT": ""
|
231 |
-
}, {
|
232 |
-
"FILE": 1,
|
233 |
-
"TOTAL": 10,
|
234 |
-
"FRAME_NUM": 450,
|
235 |
-
"DIR": " N/A",
|
236 |
-
"R": 13.324368008644104,
|
237 |
-
"THETA": -8.5535,
|
238 |
-
"L": 83.1,
|
239 |
-
"DR": -1,
|
240 |
-
"LDR": -1,
|
241 |
-
"ASPECT": -1,
|
242 |
-
"TIME": "11:55:48",
|
243 |
-
"DATE": "2018-07-09",
|
244 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
245 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
246 |
-
"PAN": None,
|
247 |
-
"TILT": None,
|
248 |
-
"ROLL": 0,
|
249 |
-
"SPECIES": "Unknown",
|
250 |
-
"MOTION": "Running <-->",
|
251 |
-
"Q": -1,
|
252 |
-
"N": -1,
|
253 |
-
"COMMENT": ""
|
254 |
-
}, {
|
255 |
-
"FILE": 1,
|
256 |
-
"TOTAL": 11,
|
257 |
-
"FRAME_NUM": 495,
|
258 |
-
"DIR": " N/A",
|
259 |
-
"R": 13.481910556495666,
|
260 |
-
"THETA": -9.1195,
|
261 |
-
"L": 86.39,
|
262 |
-
"DR": -1,
|
263 |
-
"LDR": -1,
|
264 |
-
"ASPECT": -1,
|
265 |
-
"TIME": "11:55:55",
|
266 |
-
"DATE": "2018-07-09",
|
267 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
268 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
269 |
-
"PAN": None,
|
270 |
-
"TILT": None,
|
271 |
-
"ROLL": 0,
|
272 |
-
"SPECIES": "Unknown",
|
273 |
-
"MOTION": "Running <-->",
|
274 |
-
"Q": -1,
|
275 |
-
"N": -1,
|
276 |
-
"COMMENT": ""
|
277 |
-
}, {
|
278 |
-
"FILE": 1,
|
279 |
-
"TOTAL": 12,
|
280 |
-
"FRAME_NUM": 526,
|
281 |
-
"DIR": " N/A",
|
282 |
-
"R": 13.04866854990387,
|
283 |
-
"THETA": 10.5397,
|
284 |
-
"L": 55.37,
|
285 |
-
"DR": -1,
|
286 |
-
"LDR": -1,
|
287 |
-
"ASPECT": -1,
|
288 |
-
"TIME": "11:56:00",
|
289 |
-
"DATE": "2018-07-09",
|
290 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
291 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
292 |
-
"PAN": None,
|
293 |
-
"TILT": None,
|
294 |
-
"ROLL": 0,
|
295 |
-
"SPECIES": "Unknown",
|
296 |
-
"MOTION": "Running <-->",
|
297 |
-
"Q": -1,
|
298 |
-
"N": -1,
|
299 |
-
"COMMENT": ""
|
300 |
-
}, {
|
301 |
-
"FILE": 1,
|
302 |
-
"TOTAL": 13,
|
303 |
-
"FRAME_NUM": 538,
|
304 |
-
"DIR": " N/A",
|
305 |
-
"R": 13.416267828224182,
|
306 |
-
"THETA": -9.668,
|
307 |
-
"L": 82.38,
|
308 |
-
"DR": -1,
|
309 |
-
"LDR": -1,
|
310 |
-
"ASPECT": -1,
|
311 |
-
"TIME": "11:56:02",
|
312 |
-
"DATE": "2018-07-09",
|
313 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
314 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
315 |
-
"PAN": None,
|
316 |
-
"TILT": None,
|
317 |
-
"ROLL": 0,
|
318 |
-
"SPECIES": "Unknown",
|
319 |
-
"MOTION": "Running <-->",
|
320 |
-
"Q": -1,
|
321 |
-
"N": -1,
|
322 |
-
"COMMENT": ""
|
323 |
-
}, {
|
324 |
-
"FILE": 1,
|
325 |
-
"TOTAL": 14,
|
326 |
-
"FRAME_NUM": 624,
|
327 |
-
"DIR": " N/A",
|
328 |
-
"R": 13.29811091733551,
|
329 |
-
"THETA": -8.8385,
|
330 |
-
"L": 77.44,
|
331 |
-
"DR": -1,
|
332 |
-
"LDR": -1,
|
333 |
-
"ASPECT": -1,
|
334 |
-
"TIME": "11:56:16",
|
335 |
-
"DATE": "2018-07-09",
|
336 |
-
"LATITUDE": "N 00 d 0.00000 m",
|
337 |
-
"LONGITUDE": "E 000 d 0.00000 m",
|
338 |
-
"PAN": None,
|
339 |
-
"TILT": None,
|
340 |
-
"ROLL": 0,
|
341 |
-
"SPECIES": "Unknown",
|
342 |
-
"MOTION": "Running <-->",
|
343 |
-
"Q": -1,
|
344 |
-
"N": -1,
|
345 |
-
"COMMENT": ""
|
346 |
-
}
|
347 |
-
],
|
348 |
-
"DATE": "2018-07-09",
|
349 |
-
"START": "11:54:39",
|
350 |
-
"END": "11:56:18"
|
351 |
-
}
|
352 |
-
|
353 |
-
def load_example_result(result, table_headers, info_headers):
|
354 |
-
fish_table, fish_info = create_metadata_table(example_metadata, table_headers, info_headers)
|
355 |
-
result['path_zip'] = ["static/example/input_file_results.zip"]
|
356 |
-
result['path_video'] = ["static/example/input_file_results.mp4"]
|
357 |
-
result['path_json'] = ["static/example/input_file_results.json"]
|
358 |
-
result['path_marking'] = ["static/example/input_file_marking.txt"]
|
359 |
-
result['fish_table'] = [fish_table]
|
360 |
-
result['fish_info'] = [fish_info]
|
361 |
-
|
362 |
-
|
363 |
-
def reset_state(result, state):
|
364 |
-
|
365 |
-
# Reset Result
|
366 |
-
result["json_result"] = []
|
367 |
-
result["aris_input"] = []
|
368 |
-
result["path_video"] = []
|
369 |
-
result["path_zip"] = []
|
370 |
-
result["path_json"] = []
|
371 |
-
result["path_marking"] = []
|
372 |
-
result["fish_table"] = []
|
373 |
-
result["fish_info"] = []
|
374 |
-
|
375 |
-
# Reset State
|
376 |
-
state['files'] = []
|
377 |
-
state['index'] = 0
|
378 |
-
state['total'] = 0
|
379 |
-
|
380 |
-
|
381 |
-
def convert_json_to_vatic(json_path, vatic_path="static/example/input_file_vatic.xml"):
|
382 |
-
|
383 |
-
xml = '<?xml version="1.0" encoding="utf-8"?>\n';
|
384 |
-
xml += '<annotation>\n';
|
385 |
-
xml += ' <folder>not available</folder>\n';
|
386 |
-
xml += ' <filename>not available</filename>\n';
|
387 |
-
xml += ' <source>\n';
|
388 |
-
xml += ' <type>video</type>\n';
|
389 |
-
xml += ' <sourceImage>vatic frames</sourceImage>\n';
|
390 |
-
xml += ' <sourceAnnotation>vatic</sourceAnnotation>\n';
|
391 |
-
xml += ' </source>\n';
|
392 |
-
|
393 |
-
with open(json_path, 'r') as f:
|
394 |
-
annotation = json.loads(f.read())
|
395 |
-
|
396 |
-
frames = annotation['frames']
|
397 |
-
nbr_frames = len(frames)
|
398 |
-
|
399 |
-
fishes = {}
|
400 |
-
for frame in annotation['frames']:
|
401 |
-
frame_nbr = str(frame['frame_num'])
|
402 |
-
for fish in frame['fish']:
|
403 |
-
track_id = fish['fish_id']
|
404 |
-
if (not track_id in fishes): fishes[track_id] = {'id': track_id, 'frames': []}
|
405 |
-
|
406 |
-
fishes[track_id]['frames'].append({
|
407 |
-
'frame': frame_nbr,
|
408 |
-
'x_min': str(round(fish['bbox'][0]*522)),
|
409 |
-
'y_min': str(round(fish['bbox'][1]*700)),
|
410 |
-
'x_max': str(round(fish['bbox'][2]*522)),
|
411 |
-
'y_max': str(round(fish['bbox'][3]*700)),
|
412 |
-
'visible': str(fish['visible']),
|
413 |
-
'truth': "1"
|
414 |
-
})
|
415 |
-
|
416 |
-
for fish_id in fishes:
|
417 |
-
fish = fishes[fish_id]
|
418 |
-
|
419 |
-
xml += ' <object>\n';
|
420 |
-
xml += ' <name>fish</name>\n';
|
421 |
-
xml += ' <moving>true</moving>\n';
|
422 |
-
xml += ' <action/>\n';
|
423 |
-
xml += ' <verified>0</verified>\n';
|
424 |
-
xml += ' <id>' + str(fish['id']) + '</id>\n';
|
425 |
-
xml += ' <createdFrame>0</createdFrame>\n';
|
426 |
-
xml += ' <startFrame>0</startFrame>\n';
|
427 |
-
xml += ' <endFrame>' + str(nbr_frames - 1 ) + '</endFrame>\n';
|
428 |
-
|
429 |
-
for frame in fish['frames']:
|
430 |
-
xml += ' ';
|
431 |
-
xml += '<polygon>';
|
432 |
-
xml += '<t>' + str(frame['frame']) + '</t>';
|
433 |
-
xml += '<pt><x>' + frame['x_min'] + '</x><y>' + frame['y_min'] + '</y><l>' + frame['truth'] + '</l></pt>';
|
434 |
-
xml += '<pt><x>' + frame['x_min'] + '</x><y>' + frame['y_max'] + '</y><l>' + frame['truth'] + '</l></pt>';
|
435 |
-
xml += '<pt><x>' + frame['x_max'] + '</x><y>' + frame['y_max'] + '</y><l>' + frame['truth'] + '</l></pt>';
|
436 |
-
xml += '<pt><x>' + frame['x_max'] + '</x><y>' + frame['y_min'] + '</y><l>' + frame['truth'] + '</l></pt>';
|
437 |
-
xml += '</polygon>\n';
|
438 |
-
|
439 |
-
xml += ' </object>\n';
|
440 |
-
|
441 |
-
xml += '</annotation>\n';
|
442 |
-
|
443 |
-
if vatic_path:
|
444 |
-
with open(vatic_path, 'w') as f:
|
445 |
-
f.write(xml)
|
446 |
-
return xml
|
447 |
-
|
448 |
-
|
449 |
-
convert_json_to_vatic("static/example/input_file_results.json")
|
450 |
-
|
451 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lib/fish_eye/.gitignore
DELETED
@@ -1,104 +0,0 @@
|
|
1 |
-
# Byte-compiled / optimized / DLL files
|
2 |
-
__pycache__/
|
3 |
-
*.py[cod]
|
4 |
-
*$py.class
|
5 |
-
|
6 |
-
# C extensions
|
7 |
-
*.so
|
8 |
-
|
9 |
-
# Distribution / packaging
|
10 |
-
.Python
|
11 |
-
build/
|
12 |
-
develop-eggs/
|
13 |
-
dist/
|
14 |
-
downloads/
|
15 |
-
eggs/
|
16 |
-
.eggs/
|
17 |
-
lib/
|
18 |
-
lib64/
|
19 |
-
parts/
|
20 |
-
sdist/
|
21 |
-
var/
|
22 |
-
wheels/
|
23 |
-
*.egg-info/
|
24 |
-
.installed.cfg
|
25 |
-
*.egg
|
26 |
-
MANIFEST
|
27 |
-
|
28 |
-
# PyInstaller
|
29 |
-
# Usually these files are written by a python script from a template
|
30 |
-
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
31 |
-
*.manifest
|
32 |
-
*.spec
|
33 |
-
|
34 |
-
# Installer logs
|
35 |
-
pip-log.txt
|
36 |
-
pip-delete-this-directory.txt
|
37 |
-
|
38 |
-
# Unit test / coverage reports
|
39 |
-
htmlcov/
|
40 |
-
.tox/
|
41 |
-
.coverage
|
42 |
-
.coverage.*
|
43 |
-
.cache
|
44 |
-
nosetests.xml
|
45 |
-
coverage.xml
|
46 |
-
*.cover
|
47 |
-
.hypothesis/
|
48 |
-
.pytest_cache/
|
49 |
-
|
50 |
-
# Translations
|
51 |
-
*.mo
|
52 |
-
*.pot
|
53 |
-
|
54 |
-
# Django stuff:
|
55 |
-
*.log
|
56 |
-
local_settings.py
|
57 |
-
db.sqlite3
|
58 |
-
|
59 |
-
# Flask stuff:
|
60 |
-
instance/
|
61 |
-
.webassets-cache
|
62 |
-
|
63 |
-
# Scrapy stuff:
|
64 |
-
.scrapy
|
65 |
-
|
66 |
-
# Sphinx documentation
|
67 |
-
docs/_build/
|
68 |
-
|
69 |
-
# PyBuilder
|
70 |
-
target/
|
71 |
-
|
72 |
-
# Jupyter Notebook
|
73 |
-
.ipynb_checkpoints
|
74 |
-
|
75 |
-
# pyenv
|
76 |
-
.python-version
|
77 |
-
|
78 |
-
# celery beat schedule file
|
79 |
-
celerybeat-schedule
|
80 |
-
|
81 |
-
# SageMath parsed files
|
82 |
-
*.sage.py
|
83 |
-
|
84 |
-
# Environments
|
85 |
-
.env
|
86 |
-
.venv
|
87 |
-
env/
|
88 |
-
venv/
|
89 |
-
ENV/
|
90 |
-
env.bak/
|
91 |
-
venv.bak/
|
92 |
-
|
93 |
-
# Spyder project settings
|
94 |
-
.spyderproject
|
95 |
-
.spyproject
|
96 |
-
|
97 |
-
# Rope project settings
|
98 |
-
.ropeproject
|
99 |
-
|
100 |
-
# mkdocs documentation
|
101 |
-
/site
|
102 |
-
|
103 |
-
# mypy
|
104 |
-
.mypy_cache/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lib/fish_eye/tracker.py
CHANGED
@@ -5,8 +5,8 @@ import json
|
|
5 |
import numpy as np
|
6 |
|
7 |
from fish_length import Fish_Length
|
8 |
-
from lib.fish_eye.
|
9 |
-
from lib.fish_eye.
|
10 |
import lib
|
11 |
|
12 |
class Tracker:
|
@@ -30,13 +30,13 @@ class Tracker:
|
|
30 |
# Match confidence with correct track
|
31 |
conf = 0
|
32 |
min_score = 1000000
|
33 |
-
if type(self.algorithm) == lib.fish_eye.
|
34 |
for det in dets:
|
35 |
score = sum(abs(det[0:4] - track[0:4]))
|
36 |
if (score < min_score):
|
37 |
min_score = score
|
38 |
conf = det[4]
|
39 |
-
elif type(self.algorithm) == lib.fish_eye.
|
40 |
for det in dets[0]:
|
41 |
score = sum(abs(det[0:4] - track[0:4]))
|
42 |
if (score < min_score):
|
|
|
5 |
import numpy as np
|
6 |
|
7 |
from fish_length import Fish_Length
|
8 |
+
from lib.fish_eye.tracker_sort import Sort
|
9 |
+
from lib.fish_eye.tracker_bytetrack import Associate
|
10 |
import lib
|
11 |
|
12 |
class Tracker:
|
|
|
30 |
# Match confidence with correct track
|
31 |
conf = 0
|
32 |
min_score = 1000000
|
33 |
+
if type(self.algorithm) == lib.fish_eye.tracker_sort.Sort:
|
34 |
for det in dets:
|
35 |
score = sum(abs(det[0:4] - track[0:4]))
|
36 |
if (score < min_score):
|
37 |
min_score = score
|
38 |
conf = det[4]
|
39 |
+
elif type(self.algorithm) == lib.fish_eye.tracker_bytetrack.Associate:
|
40 |
for det in dets[0]:
|
41 |
score = sum(abs(det[0:4] - track[0:4]))
|
42 |
if (score < min_score):
|
lib/fish_eye/{bytetrack.py β tracker_bytetrack.py}
RENAMED
File without changes
|
lib/fish_eye/{sort.py β tracker_sort.py}
RENAMED
@@ -214,7 +214,7 @@ class Sort(object):
|
|
214 |
trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
|
215 |
for t in reversed(to_del):
|
216 |
self.trackers.pop(t)
|
217 |
-
matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets,
|
218 |
|
219 |
# update matched trackers with assigned detections
|
220 |
for m in matched:
|
|
|
214 |
trks = np.ma.compress_rows(np.ma.masked_invalid(trks))
|
215 |
for t in reversed(to_del):
|
216 |
self.trackers.pop(t)
|
217 |
+
matched, unmatched_dets, unmatched_trks = associate_detections_to_trackers(dets,trks, self.iou_threshold)
|
218 |
|
219 |
# update matched trackers with assigned detections
|
220 |
for m in matched:
|
multipage_pdf.pdf
DELETED
Binary file (80.6 kB)
|
|
scripts/{infer_aris.py β aris_to_tracks.py}
RENAMED
@@ -1,13 +1,20 @@
|
|
1 |
-
import project_path
|
2 |
import argparse
|
3 |
-
|
4 |
-
|
|
|
|
|
5 |
|
6 |
|
7 |
def main(args):
|
8 |
-
predict_task(args.aris,
|
9 |
|
10 |
def argument_parser():
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
parser = argparse.ArgumentParser()
|
12 |
parser.add_argument("--aris", required=True, help="Path to ARIS file. Required.")
|
13 |
parser.add_argument("--weights", default='../models/v5m_896_300best.pt', help="Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt")
|
|
|
|
|
1 |
import argparse
|
2 |
+
|
3 |
+
import project_subpath
|
4 |
+
from backend.InferenceConfig import InferenceConfig
|
5 |
+
from backend.predict import predict_task
|
6 |
|
7 |
|
8 |
def main(args):
|
9 |
+
predict_task(args.aris, config=InferenceConfig())
|
10 |
|
11 |
def argument_parser():
|
12 |
+
"""
|
13 |
+
Run full inference on an aris file
|
14 |
+
Args:
|
15 |
+
aris (str): Path to ARIS file. Required.
|
16 |
+
weights (str): Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt
|
17 |
+
"""
|
18 |
parser = argparse.ArgumentParser()
|
19 |
parser.add_argument("--aris", required=True, help="Path to ARIS file. Required.")
|
20 |
parser.add_argument("--weights", default='../models/v5m_896_300best.pt', help="Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt")
|
scripts/{track_detection.py β detection_to_tracks.py}
RENAMED
@@ -1,59 +1,38 @@
|
|
1 |
-
import project_path
|
2 |
-
from lib.yolov5.utils.torch_utils import select_device
|
3 |
-
from lib.yolov5.utils.general import clip_boxes, scale_boxes
|
4 |
import argparse
|
5 |
-
from datetime import datetime
|
6 |
import torch
|
7 |
import os
|
8 |
-
from dataloader import create_dataloader_frames_only
|
9 |
-
from inference import setup_model, do_detection, do_suppression, do_confidence_boost, format_predictions, do_tracking, do_associative_tracking
|
10 |
-
from visualizer import generate_video_batches
|
11 |
import json
|
12 |
from tqdm import tqdm
|
13 |
-
import numpy as np
|
14 |
|
|
|
15 |
|
16 |
-
|
|
|
|
|
|
|
|
|
17 |
"""
|
18 |
-
|
19 |
-
- Writes aris frames to dirname(filepath)/frames/{i}.jpg
|
20 |
-
- Writes json output to dirname(filepath)/{filename}_results.json
|
21 |
-
- Writes manual marking to dirname(filepath)/{filename}_marking.txt
|
22 |
-
- Writes video output to dirname(filepath)/{filename}_results.mp4
|
23 |
-
- Zips all results to dirname(filepath)/{filename}_results.zip
|
24 |
Args:
|
25 |
-
|
26 |
-
|
27 |
-
|
|
|
28 |
"""
|
29 |
|
30 |
-
|
31 |
-
if "conf_threshold" not in config: config['conf_threshold'] = 0.3#0.001
|
32 |
-
if "nms_iou" not in config: config['nms_iou'] = 0.3#0.6
|
33 |
-
if "min_length" not in config: config['min_length'] = 0.3
|
34 |
-
if "min_travel" not in config: config['min_travel'] = 0
|
35 |
-
if "max_age" not in config: config['max_age'] = 20
|
36 |
-
if "iou_threshold" not in config: config['iou_threshold'] = 0.01
|
37 |
-
if "min_hits" not in config: config['min_hits'] = 11
|
38 |
-
if "associativity" not in config: config['associativity'] = None
|
39 |
-
|
40 |
-
print(config)
|
41 |
|
42 |
-
|
43 |
-
locations = [
|
44 |
-
"kenai-rightbank"
|
45 |
-
]
|
46 |
-
for loc in locations:
|
47 |
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
|
56 |
-
|
57 |
|
58 |
|
59 |
|
@@ -100,28 +79,14 @@ def track(in_loc_dir, out_loc_dir, metadata_path, seq, config, verbose):
|
|
100 |
image_meter_height = sequence['y_meter_start'] - sequence['y_meter_stop']
|
101 |
|
102 |
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
low_preds, real_width, real_height = format_predictions(image_shapes, low_outputs, width, height, verbose=verbose)
|
107 |
-
|
108 |
-
high_outputs = do_suppression(inference, conf_thres=config['high_conf_threshold'], iou_thres=config['nms_iou'], verbose=verbose)
|
109 |
-
high_preds, real_width, real_height = format_predictions(image_shapes, high_outputs, width, height, verbose=verbose)
|
110 |
-
|
111 |
-
results = do_associative_tracking(low_preds, high_preds, image_meter_width, image_meter_height, reverse=False, min_length=config['min_length'], min_travel=config['min_travel'], max_age=config['max_age'], iou_thres=config['iou_threshold'], min_hits=config['min_hits'], verbose=verbose)
|
112 |
-
else:
|
113 |
-
outputs = do_suppression(inference, conf_thres=config['conf_threshold'], iou_thres=config['nms_iou'], verbose=verbose)
|
114 |
-
|
115 |
-
if config['associativity'] == "boost":
|
116 |
-
|
117 |
-
do_confidence_boost(inference, outputs, boost_power=config['boost_power'], boost_decay=config['boost_decay'], verbose=verbose)
|
118 |
-
|
119 |
-
outputs = do_suppression(inference, conf_thres=config['conf_threshold'], iou_thres=config['nms_iou'], verbose=verbose)
|
120 |
-
|
121 |
-
all_preds, real_width, real_height = format_predictions(image_shapes, outputs, width, height, verbose=verbose)
|
122 |
|
123 |
-
|
|
|
124 |
|
|
|
125 |
mot_rows = []
|
126 |
for frame in results['frames']:
|
127 |
for fish in frame['fish']:
|
@@ -152,9 +117,10 @@ def track(in_loc_dir, out_loc_dir, metadata_path, seq, config, verbose):
|
|
152 |
def argument_parser():
|
153 |
parser = argparse.ArgumentParser()
|
154 |
parser.add_argument("--detections", required=True, help="Path to frame directory. Required.")
|
|
|
155 |
parser.add_argument("--output", required=True, help="Path to output directory. Required.")
|
156 |
-
parser.add_argument("--metadata", required=True, help="Path to
|
157 |
-
parser.add_argument("--tracker", default='tracker', help="
|
158 |
return parser
|
159 |
|
160 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
1 |
import argparse
|
|
|
2 |
import torch
|
3 |
import os
|
|
|
|
|
|
|
4 |
import json
|
5 |
from tqdm import tqdm
|
|
|
6 |
|
7 |
+
import project_subpath
|
8 |
|
9 |
+
from backend.InferenceConfig import InferenceConfig
|
10 |
+
from backend.inference import do_full_tracking
|
11 |
+
|
12 |
+
|
13 |
+
def main(args, config=InferenceConfig(), verbose=True):
|
14 |
"""
|
15 |
+
Convert raw detections to tracks and saves the tracking json result
|
|
|
|
|
|
|
|
|
|
|
16 |
Args:
|
17 |
+
detections (str): path to raw detections directory. Required
|
18 |
+
output (str): where tracking result will be stored. Required
|
19 |
+
metadata (str): path to metadata directory. Required
|
20 |
+
tracker (str): arbitrary name of tracker folder that you want to save trajectories to
|
21 |
"""
|
22 |
|
23 |
+
print("running detections_to_tracks.py with:", config.to_dict())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
+
loc = args.location
|
|
|
|
|
|
|
|
|
26 |
|
27 |
+
in_loc_dir = os.path.join(args.detections, loc)
|
28 |
+
out_loc_dir = os.path.join(args.output, loc, args.tracker, "data")
|
29 |
+
os.makedirs(out_loc_dir, exist_ok=True)
|
30 |
+
metadata_path = os.path.join(args.metadata, loc + ".json")
|
31 |
+
print(in_loc_dir)
|
32 |
+
print(out_loc_dir)
|
33 |
+
print(metadata_path)
|
34 |
|
35 |
+
track_location(in_loc_dir, out_loc_dir, metadata_path, config, verbose)
|
36 |
|
37 |
|
38 |
|
|
|
79 |
image_meter_height = sequence['y_meter_start'] - sequence['y_meter_stop']
|
80 |
|
81 |
|
82 |
+
# assume all images in the sequence have the same shape
|
83 |
+
real_width = image_shapes[0][0][0][1]
|
84 |
+
real_height = image_shapes[0][0][0][0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
|
86 |
+
# perform tracking
|
87 |
+
results = do_full_tracking(inference, image_shapes, image_meter_width, image_meter_height, width, height, config=config, gp=None, verbose=verbose)
|
88 |
|
89 |
+
# write tracking result
|
90 |
mot_rows = []
|
91 |
for frame in results['frames']:
|
92 |
for fish in frame['fish']:
|
|
|
117 |
def argument_parser():
|
118 |
parser = argparse.ArgumentParser()
|
119 |
parser.add_argument("--detections", required=True, help="Path to frame directory. Required.")
|
120 |
+
parser.add_argument("--location", required=True, help="Name of location dir. Required.")
|
121 |
parser.add_argument("--output", required=True, help="Path to output directory. Required.")
|
122 |
+
parser.add_argument("--metadata", required=True, help="Path to metadata directory. Required.")
|
123 |
+
parser.add_argument("--tracker", default='tracker', help="Tracker name.")
|
124 |
return parser
|
125 |
|
126 |
if __name__ == "__main__":
|
scripts/detection_to_tracks_eval.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import argparse
|
2 |
+
import sys, os
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
import project_subpath
|
6 |
+
from backend.InferenceConfig import InferenceConfig
|
7 |
+
|
8 |
+
from detection_to_tracks import main as track
|
9 |
+
|
10 |
+
current_dir = os.path.dirname(os.path.realpath(__file__))
|
11 |
+
pardir = os.path.dirname(current_dir)
|
12 |
+
sys.path.append(os.path.join(pardir, "../caltech-fish-counting/"))
|
13 |
+
from evaluate import evaluate
|
14 |
+
|
15 |
+
class Object(object):
|
16 |
+
pass
|
17 |
+
|
18 |
+
def main(args):
|
19 |
+
"""
|
20 |
+
Perform tracking on a directory of raw detections, saves the tracks, and runs the 'evaluate' script from the 'caltech-fish-counting' repo
|
21 |
+
Args:
|
22 |
+
detection_dir (str): path to raw detection directory
|
23 |
+
weights (str): path to weights
|
24 |
+
conf_threshold (float): confidence cutoff for detection filtering
|
25 |
+
nms_iou (float): non-maximum suppression IOU threshold
|
26 |
+
min_length (float): minimum length of fish in meters in order to count
|
27 |
+
max_length (float): maximum length of fish in meters in order to count. Disable with 0
|
28 |
+
min_travel (float): minimum travel distance in meters of track in order to count
|
29 |
+
max_age (int): aximum time between detections before a fish is forgotten by the tracker
|
30 |
+
min_hits (int): minimum length of track in frames in order to count
|
31 |
+
associativity (str): string representation of tracking method with corresponding hyperparameters separated by ':'
|
32 |
+
verbose (bool): disable or enable logging
|
33 |
+
"""
|
34 |
+
|
35 |
+
infer_args = Object()
|
36 |
+
infer_args.detections = args.detection_dir
|
37 |
+
infer_args.metadata = "../frames/metadata"
|
38 |
+
infer_args.output = "../frames/result_testing"
|
39 |
+
infer_args.tracker = 'tracker'
|
40 |
+
infer_args.location = 'kenai-val'
|
41 |
+
|
42 |
+
config = InferenceConfig(
|
43 |
+
conf_thresh=float(args.conf_threshold),
|
44 |
+
nms_iou=float(args.nms_iou),
|
45 |
+
min_hits=int(args.min_hits),
|
46 |
+
max_age=int(args.max_age),
|
47 |
+
min_length=float(args.min_length),
|
48 |
+
max_length=float(args.max_length),
|
49 |
+
min_travel=float(args.min_travel),
|
50 |
+
)
|
51 |
+
|
52 |
+
config.enable_tracker_from_string(args.associativity)
|
53 |
+
|
54 |
+
print("verbose", args.verbose)
|
55 |
+
|
56 |
+
track(infer_args, config=config, verbose=args.verbose)
|
57 |
+
|
58 |
+
result = evaluate(infer_args.output, "../frames/MOT", "../frames/metadata", infer_args.tracker, True, location=infer_args.location)
|
59 |
+
metrics = result['MotChallenge2DBox']['tracker']['COMBINED_SEQ']['pedestrian']
|
60 |
+
print('HOTA:', np.mean(metrics['HOTA']['HOTA'])*100)
|
61 |
+
print('MOTA:', metrics['CLEAR']['MOTA']*100)
|
62 |
+
print('IDF1:', metrics['Identity']['IDF1']*100)
|
63 |
+
print('nMAE:', metrics['nMAE']['nMAE']*100)
|
64 |
+
print('misscounts:', str(metrics['nMAE']['nMAE_numer']) + "/" + str(metrics['nMAE']['nMAE_denom']))
|
65 |
+
return result
|
66 |
+
|
67 |
+
|
68 |
+
def argument_parser():
|
69 |
+
default = InferenceConfig()
|
70 |
+
parser = argparse.ArgumentParser()
|
71 |
+
parser.add_argument("--detection_dir", default="../frames/detection_storage", help="Path to raw detection directory")
|
72 |
+
parser.add_argument("--weights", default=default.weights, help="Path to weights")
|
73 |
+
parser.add_argument("--conf_threshold", default=default.conf_thresh, help="Confidence cutoff for detection filtering")
|
74 |
+
parser.add_argument("--nms_iou", default=default.nms_iou, help="Non-maximum Suppression IOU threshold")
|
75 |
+
parser.add_argument("--min_length", default=default.min_length, help="Minimum length of fish in meters in order to count")
|
76 |
+
parser.add_argument("--max_length", default=default.max_length, help="Maximum length of fish in meters in order to count. Disable with 0")
|
77 |
+
parser.add_argument("--min_travel", default=default.min_travel, help="Minimum travel distance in meters of track in order to count.")
|
78 |
+
parser.add_argument("--max_age", default=default.max_age, help="Maximum time between detections before a fish is forgotten by the tracker")
|
79 |
+
parser.add_argument("--min_hits", default=default.min_hits, help="Minimum length of track in frames in order to count")
|
80 |
+
parser.add_argument("--associativity", default='', help="String representation of tracking method with corresponding hyperparameters separated by ':'")
|
81 |
+
parser.add_argument("--verbose", action='store_true', help="Disable or enable logging")
|
82 |
+
return parser
|
83 |
+
|
84 |
+
if __name__ == "__main__":
|
85 |
+
args = argument_parser().parse_args()
|
86 |
+
main(args)
|
scripts/{full_detect_frames.py β frames_to_MOT.py}
RENAMED
@@ -1,66 +1,62 @@
|
|
1 |
-
import project_path
|
2 |
-
from lib.yolov5.utils.general import clip_boxes, scale_boxes
|
3 |
import argparse
|
4 |
-
from datetime import datetime
|
5 |
import torch
|
6 |
import os
|
7 |
-
from dataloader import create_dataloader_frames_only
|
8 |
-
from inference import setup_model, do_detection, do_suppression, do_confidence_boost, format_predictions, do_tracking
|
9 |
-
from visualizer import generate_video_batches
|
10 |
import json
|
11 |
from tqdm import tqdm
|
12 |
-
import numpy as np
|
13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
-
|
|
|
16 |
"""
|
17 |
-
|
18 |
-
- Writes aris frames to dirname(filepath)/frames/{i}.jpg
|
19 |
-
- Writes json output to dirname(filepath)/{filename}_results.json
|
20 |
-
- Writes manual marking to dirname(filepath)/{filename}_marking.txt
|
21 |
-
- Writes video output to dirname(filepath)/{filename}_results.mp4
|
22 |
-
- Zips all results to dirname(filepath)/{filename}_results.zip
|
23 |
Args:
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
"""
|
|
|
28 |
print("In task...")
|
29 |
print("Cuda available in task?", torch.cuda.is_available())
|
30 |
|
31 |
-
|
32 |
-
if "conf_threshold" not in config: config['conf_threshold'] = 0.001
|
33 |
-
if "nms_iou" not in config: config['nms_iou'] = 0.6
|
34 |
-
if "min_length" not in config: config['min_length'] = 0.3
|
35 |
-
if "max_age" not in config: config['max_age'] = 20
|
36 |
-
if "iou_threshold" not in config: config['iou_threshold'] = 0.01
|
37 |
-
if "min_hits" not in config: config['min_hits'] = 11
|
38 |
-
|
39 |
-
print(config)
|
40 |
|
41 |
model, device = setup_model(args.weights)
|
42 |
-
|
43 |
-
locations = [
|
44 |
-
"kenai-val"
|
45 |
-
]
|
46 |
-
for loc in locations:
|
47 |
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
|
|
|
|
52 |
|
53 |
-
|
54 |
|
55 |
|
56 |
|
57 |
-
def detect_location(in_loc_dir, out_loc_dir, config, model, device, verbose):
|
58 |
|
59 |
seq_list = os.listdir(in_loc_dir)
|
60 |
|
61 |
with tqdm(total=len(seq_list), desc="...", ncols=0) as pbar:
|
62 |
for seq in seq_list:
|
63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
pbar.update(1)
|
65 |
if (seq.startswith(".")): continue
|
66 |
pbar.set_description("Processing " + seq)
|
@@ -69,12 +65,12 @@ def detect_location(in_loc_dir, out_loc_dir, config, model, device, verbose):
|
|
69 |
out_seq_dir = os.path.join(out_loc_dir, seq)
|
70 |
os.makedirs(out_seq_dir, exist_ok=True)
|
71 |
|
72 |
-
detect_seq(in_seq_dir, out_seq_dir, config, model, device, verbose)
|
73 |
|
74 |
-
def detect_seq(in_seq_dir, out_seq_dir, config, model, device, verbose):
|
75 |
|
76 |
ann_list = []
|
77 |
-
frame_list = detect(in_seq_dir, config, model, device, verbose)
|
78 |
for frame in frame_list:
|
79 |
if frame is not None:
|
80 |
for ann in frame:
|
@@ -89,7 +85,7 @@ def detect_seq(in_seq_dir, out_seq_dir, config, model, device, verbose):
|
|
89 |
with open(os.path.join(out_seq_dir, 'pred.json'), 'w') as f:
|
90 |
f.write(result)
|
91 |
|
92 |
-
def detect(in_dir, config, model, device, verbose):
|
93 |
|
94 |
#progress_log = lambda p, m: 0
|
95 |
|
@@ -98,8 +94,7 @@ def detect(in_dir, config, model, device, verbose):
|
|
98 |
|
99 |
inference, image_shapes, width, height = do_detection(dataloader, model, device, verbose=verbose)
|
100 |
|
101 |
-
|
102 |
-
outputs = do_suppression(inference, conf_thres=config['conf_threshold'], iou_thres=config['nms_iou'], verbose=verbose)
|
103 |
|
104 |
file_names = dataloader.files
|
105 |
frame_list = []
|
@@ -130,6 +125,8 @@ def detect(in_dir, config, model, device, verbose):
|
|
130 |
def argument_parser():
|
131 |
parser = argparse.ArgumentParser()
|
132 |
parser.add_argument("--frames", required=True, help="Path to frame directory. Required.")
|
|
|
|
|
133 |
parser.add_argument("--output", required=True, help="Path to output directory. Required.")
|
134 |
parser.add_argument("--weights", default='models/v5m_896_300best.pt', help="Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt")
|
135 |
return parser
|
|
|
|
|
|
|
1 |
import argparse
|
|
|
2 |
import torch
|
3 |
import os
|
|
|
|
|
|
|
4 |
import json
|
5 |
from tqdm import tqdm
|
|
|
6 |
|
7 |
+
import project_subpath
|
8 |
+
from backend.dataloader import create_dataloader_frames_only
|
9 |
+
from backend.inference import setup_model, do_detection, do_suppression
|
10 |
+
from backend.InferenceConfig import InferenceConfig
|
11 |
+
|
12 |
+
from lib.yolov5.utils.general import clip_boxes, scale_boxes
|
13 |
|
14 |
+
|
15 |
+
def main(args, config=InferenceConfig(), verbose=False):
|
16 |
"""
|
17 |
+
Construct and save MOT format detections from yolov5 based on a frame directory
|
|
|
|
|
|
|
|
|
|
|
18 |
Args:
|
19 |
+
frames (str): path to image directory
|
20 |
+
output (str): where MOT detections will be stored
|
21 |
+
weights (str): path to model weights
|
22 |
"""
|
23 |
+
|
24 |
print("In task...")
|
25 |
print("Cuda available in task?", torch.cuda.is_available())
|
26 |
|
27 |
+
print("Config:", config.to_dict())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
model, device = setup_model(args.weights)
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
+
in_loc_dir = os.path.join(args.frames, args.location)
|
32 |
+
out_loc_dir = os.path.join(args.output, args.location)
|
33 |
+
metadata_path = os.path.join(args.metadata, args.location + ".json")
|
34 |
+
print(in_loc_dir)
|
35 |
+
print(out_loc_dir)
|
36 |
+
print(metadata_path)
|
37 |
|
38 |
+
detect_location(in_loc_dir, out_loc_dir, metadata_path, config, model, device, verbose)
|
39 |
|
40 |
|
41 |
|
42 |
+
def detect_location(in_loc_dir, out_loc_dir, metadata_path, config, model, device, verbose):
|
43 |
|
44 |
seq_list = os.listdir(in_loc_dir)
|
45 |
|
46 |
with tqdm(total=len(seq_list), desc="...", ncols=0) as pbar:
|
47 |
for seq in seq_list:
|
48 |
|
49 |
+
|
50 |
+
image_meter = (-1, -1)
|
51 |
+
with open(metadata_path, 'r') as f:
|
52 |
+
json_object = json.loads(f.read())
|
53 |
+
for sequence in json_object:
|
54 |
+
if sequence['clip_name'] == seq:
|
55 |
+
image_meter = (
|
56 |
+
sequence['x_meter_stop'] - sequence['x_meter_start'],
|
57 |
+
sequence['y_meter_stop'] - sequence['y_meter_start']
|
58 |
+
)
|
59 |
+
|
60 |
pbar.update(1)
|
61 |
if (seq.startswith(".")): continue
|
62 |
pbar.set_description("Processing " + seq)
|
|
|
65 |
out_seq_dir = os.path.join(out_loc_dir, seq)
|
66 |
os.makedirs(out_seq_dir, exist_ok=True)
|
67 |
|
68 |
+
detect_seq(in_seq_dir, out_seq_dir, image_meter, config, model, device, verbose)
|
69 |
|
70 |
+
def detect_seq(in_seq_dir, out_seq_dir, image_meter, config, model, device, verbose):
|
71 |
|
72 |
ann_list = []
|
73 |
+
frame_list = detect(in_seq_dir, image_meter, config, model, device, verbose)
|
74 |
for frame in frame_list:
|
75 |
if frame is not None:
|
76 |
for ann in frame:
|
|
|
85 |
with open(os.path.join(out_seq_dir, 'pred.json'), 'w') as f:
|
86 |
f.write(result)
|
87 |
|
88 |
+
def detect(in_dir, image_meter, config, model, device, verbose):
|
89 |
|
90 |
#progress_log = lambda p, m: 0
|
91 |
|
|
|
94 |
|
95 |
inference, image_shapes, width, height = do_detection(dataloader, model, device, verbose=verbose)
|
96 |
|
97 |
+
outputs = do_suppression(inference, image_meter_width=image_meter[0], image_pixel_width=image_meter[1], conf_thres=config.conf_thresh, iou_thres=config.nms_iou, verbose=verbose)
|
|
|
98 |
|
99 |
file_names = dataloader.files
|
100 |
frame_list = []
|
|
|
125 |
def argument_parser():
|
126 |
parser = argparse.ArgumentParser()
|
127 |
parser.add_argument("--frames", required=True, help="Path to frame directory. Required.")
|
128 |
+
parser.add_argument("--metadata", required=True, help="Path to frame directory. Required.")
|
129 |
+
parser.add_argument("--location", required=True, help="Name of location dir. Required.")
|
130 |
parser.add_argument("--output", required=True, help="Path to output directory. Required.")
|
131 |
parser.add_argument("--weights", default='models/v5m_896_300best.pt', help="Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt")
|
132 |
return parser
|
scripts/{detect_frames.py β frames_to_detections.py}
RENAMED
@@ -1,46 +1,35 @@
|
|
1 |
-
import project_path
|
2 |
-
from lib.yolov5.utils.general import clip_boxes, scale_boxes
|
3 |
import argparse
|
4 |
-
from datetime import datetime
|
5 |
import torch
|
6 |
import os
|
7 |
-
from dataloader import create_dataloader_frames_only
|
8 |
-
from inference import setup_model, do_detection, do_suppression, do_confidence_boost, format_predictions, do_tracking
|
9 |
-
from visualizer import generate_video_batches
|
10 |
import json
|
11 |
from tqdm import tqdm
|
12 |
-
import numpy as np
|
13 |
|
|
|
|
|
|
|
14 |
|
15 |
-
|
|
|
16 |
"""
|
17 |
-
|
18 |
-
- Writes aris frames to dirname(filepath)/frames/{i}.jpg
|
19 |
-
- Writes json output to dirname(filepath)/{filename}_results.json
|
20 |
-
- Writes manual marking to dirname(filepath)/{filename}_marking.txt
|
21 |
-
- Writes video output to dirname(filepath)/{filename}_results.mp4
|
22 |
-
- Zips all results to dirname(filepath)/{filename}_results.zip
|
23 |
Args:
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
"""
|
|
|
|
|
28 |
print("In task...")
|
29 |
print("Cuda available in task?", torch.cuda.is_available())
|
30 |
|
31 |
model, device = setup_model(args.weights)
|
32 |
-
|
33 |
-
locations = [
|
34 |
-
"kenai-rightbank"
|
35 |
-
]
|
36 |
-
for loc in locations:
|
37 |
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
|
43 |
-
|
44 |
|
45 |
|
46 |
|
@@ -82,6 +71,7 @@ def detect(in_seq_dir, out_seq_dir, model, device, verbose):
|
|
82 |
def argument_parser():
|
83 |
parser = argparse.ArgumentParser()
|
84 |
parser.add_argument("--frames", default="../frames/images", help="Path to frame directory. Required.")
|
|
|
85 |
parser.add_argument("--output", default="../frames/detections/detection_storage/", help="Path to output directory. Required.")
|
86 |
parser.add_argument("--weights", default='models/v5m_896_300best.pt', help="Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt")
|
87 |
return parser
|
|
|
|
|
|
|
1 |
import argparse
|
|
|
2 |
import torch
|
3 |
import os
|
|
|
|
|
|
|
4 |
import json
|
5 |
from tqdm import tqdm
|
|
|
6 |
|
7 |
+
import project_subpath
|
8 |
+
from backend.dataloader import create_dataloader_frames_only
|
9 |
+
from backend.inference import setup_model, do_detection
|
10 |
|
11 |
+
|
12 |
+
def main(args, verbose=False):
|
13 |
"""
|
14 |
+
Construct and save raw detections from yolov5 based on a frame directory
|
|
|
|
|
|
|
|
|
|
|
15 |
Args:
|
16 |
+
frames (str): path to image directory
|
17 |
+
output (str): where detections will be stored
|
18 |
+
weights (str): path to model weights
|
19 |
"""
|
20 |
+
|
21 |
+
|
22 |
print("In task...")
|
23 |
print("Cuda available in task?", torch.cuda.is_available())
|
24 |
|
25 |
model, device = setup_model(args.weights)
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
+
in_loc_dir = os.path.join(args.frames, args.location)
|
28 |
+
out_loc_dir = os.path.join(args.output, args.location)
|
29 |
+
print(in_loc_dir)
|
30 |
+
print(out_loc_dir)
|
31 |
|
32 |
+
detect_location(in_loc_dir, out_loc_dir, model, device, verbose)
|
33 |
|
34 |
|
35 |
|
|
|
71 |
def argument_parser():
|
72 |
parser = argparse.ArgumentParser()
|
73 |
parser.add_argument("--frames", default="../frames/images", help="Path to frame directory. Required.")
|
74 |
+
parser.add_argument("--location", default="kenai-val", help="Name of location dir. Required.")
|
75 |
parser.add_argument("--output", default="../frames/detections/detection_storage/", help="Path to output directory. Required.")
|
76 |
parser.add_argument("--weights", default='models/v5m_896_300best.pt', help="Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt")
|
77 |
return parser
|
scripts/{infer_frames.py β frames_to_tracks.py}
RENAMED
@@ -1,70 +1,58 @@
|
|
1 |
-
|
2 |
import argparse
|
3 |
-
from datetime import datetime
|
4 |
import torch
|
5 |
import os
|
6 |
-
from dataloader import create_dataloader_frames_only
|
7 |
-
from inference import setup_model, do_detection, do_suppression, do_confidence_boost, format_predictions, do_tracking
|
8 |
-
from visualizer import generate_video_batches
|
9 |
import json
|
10 |
from tqdm import tqdm
|
11 |
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
-
def main(args, config=
|
14 |
"""
|
15 |
-
|
16 |
-
- Writes aris frames to dirname(filepath)/frames/{i}.jpg
|
17 |
-
- Writes json output to dirname(filepath)/{filename}_results.json
|
18 |
-
- Writes manual marking to dirname(filepath)/{filename}_marking.txt
|
19 |
-
- Writes video output to dirname(filepath)/{filename}_results.mp4
|
20 |
-
- Zips all results to dirname(filepath)/{filename}_results.zip
|
21 |
Args:
|
22 |
-
|
23 |
-
|
24 |
-
|
|
|
25 |
"""
|
|
|
26 |
print("In task...")
|
27 |
print("Cuda available in task?", torch.cuda.is_available())
|
28 |
|
29 |
-
|
30 |
-
if "conf_threshold" not in config: config['conf_threshold'] = 0.3
|
31 |
-
if "nms_iou" not in config: config['nms_iou'] = 0.3
|
32 |
-
if "min_length" not in config: config['min_length'] = 0.3
|
33 |
-
if "max_age" not in config: config['max_age'] = 20
|
34 |
-
if "iou_threshold" not in config: config['iou_threshold'] = 0.01
|
35 |
-
if "min_hits" not in config: config['min_hits'] = 11
|
36 |
-
|
37 |
-
print(config)
|
38 |
|
39 |
dirname = args.frames
|
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 |
-
in_seq_dir = os.path.join(in_loc_dir, seq)
|
67 |
-
infer_seq(in_seq_dir, out_dir, config, seq, model, device, metadata_path, verbose)
|
68 |
|
69 |
def infer_seq(in_dir, out_dir, config, seq_name, model, device, metadata_path, verbose):
|
70 |
|
@@ -94,17 +82,11 @@ def infer_seq(in_dir, out_dir, config, seq_name, model, device, metadata_path, v
|
|
94 |
f.write("ERROR")
|
95 |
return
|
96 |
|
|
|
|
|
97 |
|
98 |
-
|
99 |
-
|
100 |
-
do_confidence_boost(inference, outputs, verbose=verbose)
|
101 |
-
|
102 |
-
new_outputs = do_suppression(inference, conf_thres=config['conf_threshold'], iou_thres=config['nms_iou'], verbose=verbose)
|
103 |
-
|
104 |
-
all_preds, real_width, real_height = format_predictions(image_shapes, new_outputs, width, height)
|
105 |
-
|
106 |
-
results = do_tracking(all_preds, image_meter_width, image_meter_height, min_length=config['min_length'], max_age=config['max_age'], iou_thres=config['iou_threshold'], min_hits=config['min_hits'], verbose=verbose)
|
107 |
-
|
108 |
mot_rows = []
|
109 |
for frame in results['frames']:
|
110 |
for fish in frame['fish']:
|
@@ -137,6 +119,7 @@ def infer_seq(in_dir, out_dir, config, seq_name, model, device, metadata_path, v
|
|
137 |
def argument_parser():
|
138 |
parser = argparse.ArgumentParser()
|
139 |
parser.add_argument("--frames", required=True, help="Path to frame directory. Required.")
|
|
|
140 |
parser.add_argument("--metadata", required=True, help="Path to metadata directory. Required.")
|
141 |
parser.add_argument("--output", required=True, help="Path to output directory. Required.")
|
142 |
parser.add_argument("--weights", default='models/v5m_896_300best.pt', help="Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt")
|
|
|
1 |
+
|
2 |
import argparse
|
|
|
3 |
import torch
|
4 |
import os
|
|
|
|
|
|
|
5 |
import json
|
6 |
from tqdm import tqdm
|
7 |
|
8 |
+
import project_subpath
|
9 |
+
from backend.InferenceConfig import InferenceConfig
|
10 |
+
from backend.dataloader import create_dataloader_frames_only
|
11 |
+
from backend.inference import do_full_tracking, setup_model, do_detection
|
12 |
+
|
13 |
|
14 |
+
def main(args, config=InferenceConfig(), verbose=True):
|
15 |
"""
|
16 |
+
Perform inference on a directory of frames and saves the tracking json result
|
|
|
|
|
|
|
|
|
|
|
17 |
Args:
|
18 |
+
frames (str): Path to frame directory. Required.
|
19 |
+
metadata (str): Path to metadata directory. Required.
|
20 |
+
output (str): Path to output directory. Required.
|
21 |
+
weights (str): Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt
|
22 |
"""
|
23 |
+
|
24 |
print("In task...")
|
25 |
print("Cuda available in task?", torch.cuda.is_available())
|
26 |
|
27 |
+
print("Config:", config.to_dict())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
dirname = args.frames
|
30 |
+
loc = args.location
|
31 |
+
|
32 |
+
in_loc_dir = os.path.join(dirname, loc)
|
33 |
+
out_dir = os.path.join(args.output, loc, "tracker", "data")
|
34 |
+
metadata_path = os.path.join(args.metadata, loc + ".json")
|
35 |
+
os.makedirs(out_dir, exist_ok=True)
|
36 |
+
print(in_loc_dir)
|
37 |
+
print(out_dir)
|
38 |
+
print(metadata_path)
|
39 |
+
|
40 |
+
# run detection + tracking
|
41 |
+
model, device = setup_model(args.weights)
|
42 |
+
|
43 |
+
seq_list = os.listdir(in_loc_dir)
|
44 |
+
idx = 1
|
45 |
+
with tqdm(total=len(seq_list), desc="...", ncols=0) as pbar:
|
46 |
+
for seq in seq_list:
|
47 |
+
pbar.update(1)
|
48 |
+
pbar.set_description("Processing " + seq)
|
49 |
+
if verbose:
|
50 |
+
print(" ")
|
51 |
+
print("(" + str(idx) + "/" + str(len(seq_list)) + ") " + seq)
|
52 |
+
print(" ")
|
53 |
+
idx += 1
|
54 |
+
in_seq_dir = os.path.join(in_loc_dir, seq)
|
55 |
+
infer_seq(in_seq_dir, out_dir, config, seq, model, device, metadata_path, verbose)
|
|
|
|
|
56 |
|
57 |
def infer_seq(in_dir, out_dir, config, seq_name, model, device, metadata_path, verbose):
|
58 |
|
|
|
82 |
f.write("ERROR")
|
83 |
return
|
84 |
|
85 |
+
real_width = image_shapes[0][0][0][1]
|
86 |
+
real_height = image_shapes[0][0][0][0]
|
87 |
|
88 |
+
results = do_full_tracking(inference, image_shapes, image_meter_width, image_meter_height, width, height, config=config, gp=None, verbose=verbose)
|
89 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
mot_rows = []
|
91 |
for frame in results['frames']:
|
92 |
for fish in frame['fish']:
|
|
|
119 |
def argument_parser():
|
120 |
parser = argparse.ArgumentParser()
|
121 |
parser.add_argument("--frames", required=True, help="Path to frame directory. Required.")
|
122 |
+
parser.add_argument("--location", required=True, help="Name of location dir. Required.")
|
123 |
parser.add_argument("--metadata", required=True, help="Path to metadata directory. Required.")
|
124 |
parser.add_argument("--output", required=True, help="Path to output directory. Required.")
|
125 |
parser.add_argument("--weights", default='models/v5m_896_300best.pt', help="Path to saved YOLOv5 weights. Default: ../models/v5m_896_300best.pt")
|
scripts/frames_to_tracks_eval.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import argparse
|
2 |
+
import sys, os
|
3 |
+
import numpy as np
|
4 |
+
|
5 |
+
import project_subpath
|
6 |
+
from frames_to_tracks import main as infer
|
7 |
+
from backend.InferenceConfig import InferenceConfig
|
8 |
+
|
9 |
+
current_dir = os.path.dirname(os.path.realpath(__file__))
|
10 |
+
pardir = os.path.dirname(current_dir)
|
11 |
+
sys.path.append(os.path.join(pardir, "../caltech-fish-counting/"))
|
12 |
+
from evaluate import evaluate
|
13 |
+
|
14 |
+
class Object(object):
|
15 |
+
pass
|
16 |
+
|
17 |
+
def main(args):
|
18 |
+
"""
|
19 |
+
Perform inference on a directory of frames, saves the tracks, and runs the 'evaluate' script from the 'caltech-fish-counting' repo
|
20 |
+
Args:
|
21 |
+
weights (str): path to weights
|
22 |
+
conf_threshold (float): confidence cutoff for detection filtering
|
23 |
+
nms_iou (float): non-maximum suppression IOU threshold
|
24 |
+
min_length (float): minimum length of fish in meters in order to count
|
25 |
+
max_length (float): maximum length of fish in meters in order to count. Disable with 0
|
26 |
+
min_travel (float): minimum travel distance in meters of track in order to count
|
27 |
+
max_age (int): aximum time between detections before a fish is forgotten by the tracker
|
28 |
+
min_hits (int): minimum length of track in frames in order to count
|
29 |
+
associativity (str): string representation of tracking method with corresponding hyperparameters separated by ':'
|
30 |
+
"""
|
31 |
+
|
32 |
+
infer_args = Object()
|
33 |
+
infer_args.metadata = "../frames/metadata"
|
34 |
+
infer_args.frames = "../frames/images"
|
35 |
+
infer_args.location = "kenai-val"
|
36 |
+
infer_args.output = "../frames/result"
|
37 |
+
infer_args.weights = "models/v5m_896_300best.pt"
|
38 |
+
|
39 |
+
config = InferenceConfig(
|
40 |
+
conf_thresh=float(args.conf_threshold),
|
41 |
+
nms_iou=float(args.nms_iou),
|
42 |
+
min_hits=int(args.min_hits),
|
43 |
+
max_age=int(args.max_age),
|
44 |
+
min_length=float(args.min_length),
|
45 |
+
max_length=float(args.max_length),
|
46 |
+
min_travel=float(args.min_travel),
|
47 |
+
)
|
48 |
+
|
49 |
+
config.enable_tracker_from_string(args.associativity)
|
50 |
+
|
51 |
+
infer(infer_args, config=config, verbose=False)
|
52 |
+
|
53 |
+
result = evaluate("../frames/result", "../frames/MOT", "../frames/metadata", "tracker", True, location=infer_args.location)
|
54 |
+
metrics = result['MotChallenge2DBox']['tracker']['COMBINED_SEQ']['pedestrian']
|
55 |
+
print('HOTA:', np.mean(metrics['HOTA']['HOTA'])*100)
|
56 |
+
print('MOTA:', metrics['CLEAR']['MOTA']*100)
|
57 |
+
print('IDF1:', metrics['Identity']['IDF1']*100)
|
58 |
+
print('nMAE:', metrics['nMAE']['nMAE']*100)
|
59 |
+
print('misscounts:', str(metrics['nMAE']['nMAE_numer']) + "/" + str(metrics['nMAE']['nMAE_denom']))
|
60 |
+
return result
|
61 |
+
|
62 |
+
def argument_parser():
|
63 |
+
default = InferenceConfig()
|
64 |
+
parser = argparse.ArgumentParser()
|
65 |
+
parser.add_argument("--weights", default=default.weights, help="Path to weights")
|
66 |
+
parser.add_argument("--conf_threshold", default=default.conf_thresh, help="Confidence cutoff for detection filtering")
|
67 |
+
parser.add_argument("--nms_iou", default=default.nms_iou, help="Non-maximum Suppression IOU threshold")
|
68 |
+
parser.add_argument("--min_length", default=default.min_length, help="Minimum length of fish in meters in order to count")
|
69 |
+
parser.add_argument("--max_length", default=default.max_length, help="Maximum length of fish in meters in order to count. Disable with 0")
|
70 |
+
parser.add_argument("--min_travel", default=default.min_travel, help="Minimum travel distance in meters of track in order to count.")
|
71 |
+
parser.add_argument("--max_age", default=default.max_age, help="Maximum time between detections before a fish is forgotten by the tracker")
|
72 |
+
parser.add_argument("--min_hits", default=default.min_hits, help="Minimum length of track in frames in order to count")
|
73 |
+
parser.add_argument("--associativity", default='', help="String representation of tracking method with corresponding hyperparameters separated by ':'")
|
74 |
+
return parser
|
75 |
+
|
76 |
+
if __name__ == "__main__":
|
77 |
+
args = argument_parser().parse_args()
|
78 |
+
main(args)
|
scripts/infer_eval.py
DELETED
@@ -1,47 +0,0 @@
|
|
1 |
-
import project_path
|
2 |
-
import argparse
|
3 |
-
from infer_frames import main as infer
|
4 |
-
import sys
|
5 |
-
sys.path.append('..')
|
6 |
-
sys.path.append('../caltech-fish-counting')
|
7 |
-
|
8 |
-
from evaluate import evaluate
|
9 |
-
|
10 |
-
class Object(object):
|
11 |
-
pass
|
12 |
-
|
13 |
-
def main(args):
|
14 |
-
|
15 |
-
infer_args = Object()
|
16 |
-
infer_args.metadata = "../frames/metadata"
|
17 |
-
infer_args.frames = "../frames/images"
|
18 |
-
infer_args.output = "../frames/result"
|
19 |
-
infer_args.weights = "models/v5m_896_300best.pt"
|
20 |
-
|
21 |
-
config = {
|
22 |
-
'conf_threshold': float(args.conf_threshold),
|
23 |
-
'nms_iou': float(args.nms_iou),
|
24 |
-
'min_length': float(args.min_length),
|
25 |
-
'max_age': int(args.max_age),
|
26 |
-
'iou_threshold': float(args.iou_threshold),
|
27 |
-
'min_hits': int(args.min_hits)
|
28 |
-
}
|
29 |
-
|
30 |
-
infer(infer_args, config=config, verbose=False)
|
31 |
-
|
32 |
-
evaluate("../frames/result", "../frames/MOT", "../frames/metadata", "tracker", False)
|
33 |
-
|
34 |
-
|
35 |
-
def argument_parser():
|
36 |
-
parser = argparse.ArgumentParser()
|
37 |
-
parser.add_argument("--conf_threshold", default=0.3, help="Config object. Required.")
|
38 |
-
parser.add_argument("--nms_iou", default=0.3, help="Config object. Required.")
|
39 |
-
parser.add_argument("--min_length", default=0.3, help="Config object. Required.")
|
40 |
-
parser.add_argument("--max_age", default=20, help="Config object. Required.")
|
41 |
-
parser.add_argument("--iou_threshold", default=0.01, help="Config object. Required.")
|
42 |
-
parser.add_argument("--min_hits", default=11, help="Config object. Required.")
|
43 |
-
return parser
|
44 |
-
|
45 |
-
if __name__ == "__main__":
|
46 |
-
args = argument_parser().parse_args()
|
47 |
-
main(args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scripts/{project_path.py β project_subpath.py}
RENAMED
@@ -3,6 +3,8 @@ import os
|
|
3 |
|
4 |
current_dir = os.path.dirname(os.path.realpath(__file__))
|
5 |
pardir = os.path.dirname(current_dir)
|
6 |
-
for d in [pardir, current_dir
|
7 |
if d not in sys.path:
|
8 |
-
sys.path.append(d)
|
|
|
|
|
|
3 |
|
4 |
current_dir = os.path.dirname(os.path.realpath(__file__))
|
5 |
pardir = os.path.dirname(current_dir)
|
6 |
+
for d in [pardir, current_dir]:
|
7 |
if d not in sys.path:
|
8 |
+
sys.path.append(d)
|
9 |
+
|
10 |
+
import project_path
|
scripts/track_eval.py
DELETED
@@ -1,78 +0,0 @@
|
|
1 |
-
import project_path
|
2 |
-
import argparse
|
3 |
-
from track_detection import main as track
|
4 |
-
import sys
|
5 |
-
import numpy as np
|
6 |
-
sys.path.append('..')
|
7 |
-
sys.path.append('../caltech-fish-counting')
|
8 |
-
|
9 |
-
from evaluate import evaluate
|
10 |
-
|
11 |
-
class Object(object):
|
12 |
-
pass
|
13 |
-
|
14 |
-
def main(args):
|
15 |
-
|
16 |
-
infer_args = Object()
|
17 |
-
infer_args.detections = args.detection_dir
|
18 |
-
infer_args.metadata = "../frames/metadata"
|
19 |
-
infer_args.output = "../frames/result_testing"
|
20 |
-
infer_args.tracker = 'tracker'
|
21 |
-
|
22 |
-
config = {
|
23 |
-
'conf_threshold': float(args.conf_threshold),
|
24 |
-
'nms_iou': float(args.nms_iou),
|
25 |
-
'min_length': float(args.min_length),
|
26 |
-
'min_travel': float(args.min_travel),
|
27 |
-
'max_age': int(args.max_age),
|
28 |
-
'iou_threshold': float(args.iou_threshold),
|
29 |
-
'min_hits': int(args.min_hits),
|
30 |
-
'associativity': None
|
31 |
-
}
|
32 |
-
|
33 |
-
if args.associativity != "":
|
34 |
-
if (args.associativity.startswith("boost")):
|
35 |
-
config['associativity'] = "boost"
|
36 |
-
conf = args.associativity.split(":")
|
37 |
-
if len(conf) > 1: config['boost_power'] = float(conf[1])
|
38 |
-
if len(conf) > 2: config['boost_decay'] = float(conf[2])
|
39 |
-
elif (args.associativity.startswith("bytetrack")):
|
40 |
-
config['associativity'] = "bytetrack"
|
41 |
-
conf = args.associativity.split(":")
|
42 |
-
if len(conf) > 1: config['low_conf_threshold'] = float(conf[1])
|
43 |
-
if len(conf) > 2: config['high_conf_threshold'] = float(conf[2])
|
44 |
-
else:
|
45 |
-
print("INVALID ASSOCIATIVITY TYPE:", args.associativity)
|
46 |
-
return
|
47 |
-
|
48 |
-
print("verbose", args.verbose)
|
49 |
-
|
50 |
-
track(infer_args, config=config, verbose=args.verbose)
|
51 |
-
|
52 |
-
result = evaluate(infer_args.output, "../frames/MOT", "../frames/metadata", infer_args.tracker, True)
|
53 |
-
metrics = result['MotChallenge2DBox']['tracker']['COMBINED_SEQ']['pedestrian']
|
54 |
-
print('HOTA:', np.mean(metrics['HOTA']['HOTA'])*100)
|
55 |
-
print('MOTA:', metrics['CLEAR']['MOTA']*100)
|
56 |
-
print('IDF1:', metrics['Identity']['IDF1']*100)
|
57 |
-
print('nMAE:', metrics['nMAE']['nMAE']*100)
|
58 |
-
print('misscounts:', str(metrics['nMAE']['nMAE_numer']) + "/" + str(metrics['nMAE']['nMAE_denom']))
|
59 |
-
return result
|
60 |
-
|
61 |
-
|
62 |
-
def argument_parser():
|
63 |
-
parser = argparse.ArgumentParser()
|
64 |
-
parser.add_argument("--detection_dir", default="../frames/detection_storage")
|
65 |
-
parser.add_argument("--conf_threshold", default=0.3, help="Config object. Required.")
|
66 |
-
parser.add_argument("--nms_iou", default=0.3, help="Config object. Required.")
|
67 |
-
parser.add_argument("--min_length", default=0.3, help="Config object. Required.")
|
68 |
-
parser.add_argument("--min_travel", default=0, help="Config object. Required.")
|
69 |
-
parser.add_argument("--max_age", default=20, help="Config object. Required.")
|
70 |
-
parser.add_argument("--iou_threshold", default=0.01, help="Config object. Required.")
|
71 |
-
parser.add_argument("--min_hits", default=11, help="Config object. Required.")
|
72 |
-
parser.add_argument("--associativity", default='', help="Config object. Required.")
|
73 |
-
parser.add_argument("--verbose", action='store_true', help="Config object. Required.")
|
74 |
-
return parser
|
75 |
-
|
76 |
-
if __name__ == "__main__":
|
77 |
-
args = argument_parser().parse_args()
|
78 |
-
main(args)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|