oskarastrom commited on
Commit
128e4f0
β€’
1 Parent(s): 997e70d

Compatability with backend

Browse files
Files changed (37) hide show
  1. .gitignore +2 -0
  2. app.py +121 -199
  3. InferenceConfig.py β†’ backend/InferenceConfig.py +28 -2
  4. aris.py β†’ backend/aris.py +15 -59
  5. {gradio_scripts β†’ backend}/aws_handler.py +44 -1
  6. dataloader.py β†’ backend/dataloader.py +12 -9
  7. inference.py β†’ backend/inference.py +22 -19
  8. main.py β†’ backend/predict.py +9 -6
  9. pyDIDSON.py β†’ backend/pyDIDSON.py +1 -1
  10. pyDIDSON_format.py β†’ backend/pyDIDSON_format.py +0 -0
  11. uploader.py β†’ backend/uploader.py +0 -7
  12. visualizer.py β†’ backend/visualizer.py +0 -1
  13. dump.rdb +0 -0
  14. {gradio_scripts β†’ frontend}/annotation_editor.js +0 -0
  15. {gradio_scripts β†’ frontend}/annotation_handler.py +106 -2
  16. frontend/aris_crop.py +45 -0
  17. gradio_scripts/file_reader.py β†’ frontend/custom_file_reader.py +0 -0
  18. {gradio_scripts β†’ frontend}/pdf_handler.py +6 -38
  19. {gradio_scripts β†’ frontend}/result_ui.py +72 -8
  20. frontend/state_handler.py +22 -0
  21. {gradio_scripts β†’ frontend}/upload_ui.py +17 -12
  22. gradio_scripts/state_handler.py +0 -451
  23. lib/fish_eye/.gitignore +0 -104
  24. lib/fish_eye/tracker.py +4 -4
  25. lib/fish_eye/{bytetrack.py β†’ tracker_bytetrack.py} +0 -0
  26. lib/fish_eye/{sort.py β†’ tracker_sort.py} +1 -1
  27. multipage_pdf.pdf +0 -0
  28. scripts/{infer_aris.py β†’ aris_to_tracks.py} +11 -4
  29. scripts/{track_detection.py β†’ detection_to_tracks.py} +30 -64
  30. scripts/detection_to_tracks_eval.py +86 -0
  31. scripts/{full_detect_frames.py β†’ frames_to_MOT.py} +40 -43
  32. scripts/{detect_frames.py β†’ frames_to_detections.py} +17 -27
  33. scripts/{infer_frames.py β†’ frames_to_tracks.py} +45 -62
  34. scripts/frames_to_tracks_eval.py +78 -0
  35. scripts/infer_eval.py +0 -47
  36. scripts/{project_path.py β†’ project_subpath.py} +4 -2
  37. 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
- WEBAPP_VERSION = "1.0"
 
 
 
 
19
 
20
- enable_annotation_editor = False
 
 
 
 
 
 
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
- upload_file(file_path, "fishcounting", "webapp_uploads/" + file_name)
 
 
 
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
- # Show result
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
- if result["aris_input"][index]:
242
- return {
243
- annotation_progress: gr.update(value="<p id='annotation_info' style='display:none'>[]</p><!--" + str(np.random.rand()) + "-->", visible=True),
244
- master_tabs: gr.update(selected=2)
245
- }
246
  return {
247
- annotation_progress: gr.update(),
248
- master_tabs: gr.update()
249
  }
250
 
251
- annotation_info = None
252
- annotation_dataset = None
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
- set_progress = lambda pct, msg: progress(pct, desc=msg)
 
 
 
 
261
 
262
- if state['frame_index'] == 0:
263
- if set_progress: set_progress(0, "Loading Frames")
264
- dataloader, annotation_dataset = create_dataloader_aris(result["aris_input"][result_index], BEAM_WIDTH_DIR, None)
265
 
266
- # Check that frames remain to be loaded
267
- if state['frame_index'] < len(result['json_result'][result_index]['frames']):
 
268
 
269
- # load frames and annotation
270
- annotation_info, state['frame_index'] = init_frames(annotation_dataset, result['json_result'][result_index], state['frame_index'], gp=set_progress)
271
-
272
- # save as html element
273
- annotation_content = "<p id='annotation_info' style='display:none'>" + json.dumps(annotation_info) + "</p>"
274
 
275
- return {
276
- annotation_editor: gr.update(),
277
- annotation_progress: gr.update(value=annotation_content)
278
- }
 
 
 
 
 
 
279
 
280
- # If complete, start annotation editor
 
 
 
 
 
 
 
281
 
282
- annotation_html = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
 
284
- # Header
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
- # Annotation Body
291
- annotation_html += "<div style='display:flex'>"
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
- # Dummy objects
297
- annotation_html += "<img id='annotation_img' onload='draw()' style='display:none'></img>"
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
- components = {}
 
 
 
 
 
 
 
307
 
308
- demo = gr.Blocks()
309
- with demo:
310
- with gr.Blocks() as inner_body:
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
- #annotation_frame_nbr {
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
- vis_components = Result_Gradio(prepare_annotation, components)
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 opening annotation
385
- annotation_progress.change(load_annotation, annotation_progress, [annotation_editor, annotation_progress], _js="""
386
- () => {
387
- info_string = document.getElementById("annotation_info").innerHTML;
388
- info = JSON.parse(info_string);
389
- console.log(info)
390
- if (info.length == 0) {
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, vis_components + [inference_handler])
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
- vis_components + [tab_labeler]
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(ARIS_data, start_frame, end_frame):
 
 
 
 
 
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, cb=None, max_mb=-1, beam_width_dir=BEAM_WIDTH_DIR, bg_out_dir=None, num_workers=0):
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, cb, 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, cb) 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, cb, pbar)
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, cb=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] = FastARISRead(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,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 ImageData
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.bytetrack import Associate
 
 
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 = 'multipage_pdf.pdf'
 
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 = not (result["aris_input"][i] == None)
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 gradio_scripts.file_reader import File
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+kenai_train': 'models/YsEKtE20.pt',
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 formats", interactive=True, value=["Annotated Video", "Manual Marking"]))
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.sort import Sort
9
- from lib.fish_eye.bytetrack import Associate
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.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.bytetrack.Associate:
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, trks, self.iou_threshold)
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
- from predict import predict_task
4
- from datetime import datetime
 
 
5
 
6
 
7
  def main(args):
8
- predict_task(args.aris, weights=args.weights)
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
- def main(args, config={}, verbose=True):
 
 
 
 
17
  """
18
- Main processing task to be run in gradio
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
- filepath (str): path to aris file
26
-
27
- TODO: Separate into subtasks in different queues; have a GPU-only queue.
 
28
  """
29
 
30
- # setup config
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
- in_loc_dir = os.path.join(args.detections, loc)
49
- out_loc_dir = os.path.join(args.output, loc, args.tracker, "data")
50
- os.makedirs(out_loc_dir, exist_ok=True)
51
- metadata_path = os.path.join(args.metadata, loc + ".json")
52
- print(in_loc_dir)
53
- print(out_loc_dir)
54
- print(metadata_path)
55
 
56
- track_location(in_loc_dir, out_loc_dir, metadata_path, config, verbose)
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
- if config['associativity'] == "bytetrack":
104
-
105
- low_outputs = do_suppression(inference, conf_thres=config['low_conf_threshold'], iou_thres=config['nms_iou'], verbose=verbose)
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
- results = do_tracking(all_preds, image_meter_width, image_meter_height, 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)
 
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 output directory. Required.")
157
- parser.add_argument("--tracker", default='tracker', help="Path to output directory. Required.")
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
- def main(args, config={}, verbose=True):
 
16
  """
17
- Main processing task to be run in gradio
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
- filepath (str): path to aris file
25
-
26
- TODO: Separate into subtasks in different queues; have a GPU-only queue.
27
  """
 
28
  print("In task...")
29
  print("Cuda available in task?", torch.cuda.is_available())
30
 
31
- # setup config
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
- in_loc_dir = os.path.join(args.frames, loc)
49
- out_loc_dir = os.path.join(args.output, loc)
50
- print(in_loc_dir)
51
- print(out_loc_dir)
 
 
52
 
53
- detect_location(in_loc_dir, out_loc_dir, config, model, device, verbose)
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
- def main(args, verbose=True):
 
16
  """
17
- Main processing task to be run in gradio
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
- filepath (str): path to aris file
25
-
26
- TODO: Separate into subtasks in different queues; have a GPU-only queue.
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
- in_loc_dir = os.path.join(args.frames, loc)
39
- out_loc_dir = os.path.join(args.output, loc)
40
- print(in_loc_dir)
41
- print(out_loc_dir)
42
 
43
- detect_location(in_loc_dir, out_loc_dir, model, device, verbose)
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
- import project_path
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={}, verbose=True):
14
  """
15
- Main processing task to be run in gradio
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
- filepath (str): path to aris file
23
-
24
- TODO: Separate into subtasks in different queues; have a GPU-only queue.
 
25
  """
 
26
  print("In task...")
27
  print("Cuda available in task?", torch.cuda.is_available())
28
 
29
- # setup config
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
- locations = ["kenai-val"]
42
- for loc in locations:
43
-
44
- in_loc_dir = os.path.join(dirname, loc)
45
- out_dir = os.path.join(args.output, loc, "tracker", "data")
46
- metadata_path = os.path.join(args.metadata, loc + ".json")
47
- os.makedirs(out_dir, exist_ok=True)
48
- print(in_loc_dir)
49
- print(out_dir)
50
- print(metadata_path)
51
-
52
- # run detection + tracking
53
- model, device = setup_model(args.weights)
54
-
55
- seq_list = os.listdir(in_loc_dir)
56
- idx = 1
57
- with tqdm(total=len(seq_list), desc="...", ncols=0) as pbar:
58
- for seq in seq_list:
59
- pbar.update(1)
60
- pbar.set_description("Processing " + seq)
61
- if verbose:
62
- print(" ")
63
- print("(" + str(idx) + "/" + str(len(seq_list)) + ") " + seq)
64
- print(" ")
65
- idx += 1
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
- outputs = do_suppression(inference, conf_thres=config['conf_threshold'], iou_thres=config['nms_iou'], verbose=verbose)
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, os.path.join(pardir, "lib/fish_eye/"), os.path.join(pardir, "lib/"), os.path.join(pardir, "lib/yolov5/")]:
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)