Skip to content

Commit 93f17fa

Browse files
committed
add progress_updater support to detect_markers, gaze_to_plane, make_gaze_overlay_video, make_mapped_gaze_video, run_validation for more granular and complete progress output
1 parent 4c93e9f commit 93f17fa

File tree

5 files changed

+83
-21
lines changed

5 files changed

+83
-21
lines changed

src/gazeMapper/process/detect_markers.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from .. import config, episode, marker, naming, plane, process, session, synchronization
1010

1111

12-
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show_visualization=False, visualization_show_rejected_markers=False, **study_settings):
12+
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show_visualization=False, visualization_show_rejected_markers=False, progress_indicator: process_pool.JobProgress=None, **study_settings):
1313
# if show_visualization, each frame is shown in a viewer, overlaid with info about detected markers and planes
1414
# if show_rejected_markers, rejected ArUco marker candidates are also shown in the viewer. Possibly useful for debug
1515
working_dir = pathlib.Path(working_dir)
@@ -29,15 +29,21 @@ def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show
2929
gui.set_show_play_percentage(True)
3030
gui.set_show_action_tooltip(True)
3131

32-
proc_thread = propagating_thread.PropagatingThread(target=do_the_work, args=(working_dir, config_dir, gui, visualization_show_rejected_markers), kwargs=study_settings, cleanup_fun=gui.stop)
32+
proc_thread = propagating_thread.PropagatingThread(target=do_the_work, args=(working_dir, config_dir, gui, visualization_show_rejected_markers, progress_indicator), kwargs=study_settings, cleanup_fun=gui.stop)
3333
proc_thread.start()
3434
gui.start()
3535
proc_thread.join()
3636
else:
37-
do_the_work(working_dir, config_dir, None, False, **study_settings)
37+
do_the_work(working_dir, config_dir, None, False, progress_indicator, **study_settings)
3838

3939

40-
def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: GUI, visualization_show_rejected_markers: bool, **study_settings):
40+
def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: GUI, visualization_show_rejected_markers: bool, progress_indicator: process_pool.JobProgress, **study_settings):
41+
# progress indicator
42+
if progress_indicator is None:
43+
progress_indicator = process_pool.JobProgress(printer=lambda x: print(x))
44+
progress_indicator.set_unit('frames')
45+
progress_indicator.set_start_time_to_now()
46+
4147
# get settings for the study
4248
study_config = config.read_study_config_with_overrides(config_dir, {config.OverrideLevel.Session: working_dir.parent, config.OverrideLevel.Recording: working_dir}, **study_settings)
4349

@@ -102,6 +108,12 @@ def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: GUI, v
102108
colors['rejected_marker_color'] = study_config.mapped_video_rejected_marker_color or (255,0,0)
103109
aruco_manager.set_visualization_colors(**colors)
104110

111+
# prep progress indicator
112+
total = estimator.video_ts.get_last()[0]
113+
progress_indicator.set_total(total)
114+
progress_indicator.set_intervals(int(total/200), int(total/200))
115+
estimator.set_progress_updater(progress_indicator.update)
116+
105117
poses, individual_markers, sync_target_signal = estimator.process_video()
106118

107119
for p in poses:

src/gazeMapper/process/gaze_to_plane.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from .. import config, episode, naming, plane, process, session, synchronization
88

99

10-
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show_visualization=False, show_planes=True, show_only_intervals=True, **study_settings):
10+
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show_visualization=False, show_planes=True, show_only_intervals=True, progress_indicator: process_pool.JobProgress=None, **study_settings):
1111
# if show_visualization, each frame is shown in a viewer, overlaid with info about detected planes and projected gaze
1212
# if show_poster, gaze in space of each plane is also drawn in a separate windows
1313
# if show_only_intervals, only the coded mapping episodes (if available) are shown in the viewer while the rest of the scene video is skipped past
@@ -26,15 +26,21 @@ def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show
2626
gui.set_show_play_percentage(True)
2727
gui.set_show_action_tooltip(True)
2828

29-
proc_thread = propagating_thread.PropagatingThread(target=do_the_work, args=(working_dir, config_dir, gui, show_planes, show_only_intervals), kwargs=study_settings, cleanup_fun=gui.stop)
29+
proc_thread = propagating_thread.PropagatingThread(target=do_the_work, args=(working_dir, config_dir, gui, show_planes, show_only_intervals, progress_indicator), kwargs=study_settings, cleanup_fun=gui.stop)
3030
proc_thread.start()
3131
gui.start()
3232
proc_thread.join()
3333
else:
34-
do_the_work(working_dir, config_dir, None, False, False, **study_settings)
34+
do_the_work(working_dir, config_dir, None, False, False, progress_indicator, **study_settings)
3535

3636

37-
def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: GUI, show_planes: bool, show_only_intervals: bool, **study_settings):
37+
def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: GUI, show_planes: bool, show_only_intervals: bool, progress_indicator: process_pool.JobProgress, **study_settings):
38+
# progress indicator
39+
if progress_indicator is None:
40+
progress_indicator = process_pool.JobProgress(printer=lambda x: print(x))
41+
progress_indicator.set_unit('samples')
42+
progress_indicator.set_start_time_to_now()
43+
3844
# get settings for the study
3945
study_config = config.read_study_config_with_overrides(config_dir, {config.OverrideLevel.Session: working_dir.parent, config.OverrideLevel.Recording: working_dir}, **study_settings)
4046

@@ -77,13 +83,17 @@ def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: GUI, s
7783
head_gazes = gaze_headref.read_dict_from_file(working_dir / gt_naming.gaze_data_fname, processing_intervals if should_load_part else None, ts_column_suffixes=['VOR', ''])[0]
7884
poses = {p:gt_pose.read_dict_from_file(working_dir/f'{naming.plane_pose_prefix}{p}.tsv', mapping_setup[p] if should_load_part else None) for p in mapping_setup}
7985

86+
# prep progress indicator
87+
total = sum(len(head_gazes[f]) for p in poses for f in poses[p] if f in head_gazes)
88+
progress_indicator.set_total(total)
89+
progress_indicator.set_intervals(int(total/200), int(total/200))
8090
# get camera calibration info
8191
camera_params = ocv.CameraParams.read_from_file(working_dir / gt_naming.scene_camera_calibration_fname)
8292

8393
# transform gaze to plane(s)
8494
plane_gazes: dict[str, dict[int,list[gaze_worldref.Gaze]]] = {}
8595
for p in planes:
86-
plane_gazes[p] = gaze_worldref.from_head(poses[p], head_gazes, camera_params)
96+
plane_gazes[p] = gaze_worldref.from_head(poses[p], head_gazes, camera_params, progress_indicator.update)
8797
gaze_worldref.write_dict_to_file(plane_gazes[p], working_dir/f'{naming.world_gaze_prefix}{p}.tsv', skip_missing=True)
8898

8999
# update state

src/gazeMapper/process/make_gaze_overlay_video.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .. import config, process, session
77

88

9-
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show_visualization=False, **study_settings):
9+
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show_visualization=False, progress_indicator: process_pool.JobProgress=None, **study_settings):
1010
# if show_visualization, each frame is shown in a viewer as video is generated
1111
working_dir = pathlib.Path(working_dir)
1212
if config_dir is None:
@@ -25,15 +25,21 @@ def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show
2525
gui.set_show_action_tooltip(True)
2626
gui.set_button_props_for_action(video_player.Action.Quit, 'Stop', tooltip='Interrupt (cut short) the video generation')
2727

28-
proc_thread = propagating_thread.PropagatingThread(target=do_the_work, args=(working_dir, config_dir, gui), kwargs=study_settings, cleanup_fun=gui.stop)
28+
proc_thread = propagating_thread.PropagatingThread(target=do_the_work, args=(working_dir, config_dir, gui, progress_indicator), kwargs=study_settings, cleanup_fun=gui.stop)
2929
proc_thread.start()
3030
gui.start()
3131
proc_thread.join()
3232
else:
33-
do_the_work(working_dir, config_dir, None, **study_settings)
33+
do_the_work(working_dir, config_dir, None, progress_indicator, **study_settings)
3434

3535

36-
def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: video_player.GUI, **study_settings):
36+
def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: video_player.GUI, progress_indicator: process_pool.JobProgress, **study_settings):
37+
# progress indicator
38+
if progress_indicator is None:
39+
progress_indicator = process_pool.JobProgress(printer=lambda x: print(x))
40+
progress_indicator.set_unit('frames')
41+
progress_indicator.set_start_time_to_now()
42+
3743
# get settings for the study
3844
study_config = config.read_study_config_with_overrides(config_dir, {config.OverrideLevel.Session: working_dir.parent, config.OverrideLevel.Recording: working_dir}, **study_settings)
3945

@@ -52,11 +58,16 @@ def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: video_
5258
if gui is not None:
5359
gui.set_show_timeline(True, video_ts, window_id=gui.main_window_id)
5460

61+
# prep progress indicator
62+
total = video_ts.get_last()[0]
63+
progress_indicator.set_total(total)
64+
progress_indicator.set_intervals(int(total/200), int(total/200))
65+
5566
# update state: set to not run so that if we crash or cancel below the task is correctly marked as not run (video files are corrupt)
5667
session.update_action_states(working_dir, process.Action.MAKE_GAZE_OVERLAY_VIDEO, process_pool.State.Not_Run, study_config)
5768

5869
# now run
59-
video_maker.process_video()
70+
video_maker.process_video(progress_indicator.update)
6071

6172
# update state
6273
session.update_action_states(working_dir, process.Action.MAKE_GAZE_OVERLAY_VIDEO, process_pool.State.Completed, study_config)

src/gazeMapper/process/make_mapped_gaze_video.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from .detect_markers import _get_plane_setup, _get_sync_function
2020

2121

22-
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show_visualization=False, **study_settings):
22+
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show_visualization=False, progress_indicator: process_pool.JobProgress=None, **study_settings):
2323
# if show_visualization, the generated video(s) are shown as they are created in a viewer
2424
working_dir = pathlib.Path(working_dir) # working directory of a session, not of a recording
2525
if config_dir is None:
@@ -32,17 +32,23 @@ def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, show
3232
gui = video_player.GUI(use_thread = False)
3333
gui.add_window(working_dir.name)
3434

35-
proc_thread = propagating_thread.PropagatingThread(target=do_the_work, args=(working_dir, config_dir, gui), kwargs=study_settings, cleanup_fun=gui.stop)
35+
proc_thread = propagating_thread.PropagatingThread(target=do_the_work, args=(working_dir, config_dir, gui, progress_indicator), kwargs=study_settings, cleanup_fun=gui.stop)
3636
proc_thread.start()
3737
gui.start()
3838
proc_thread.join()
3939
else:
40-
do_the_work(working_dir, config_dir, None, **study_settings)
40+
do_the_work(working_dir, config_dir, None, progress_indicator, **study_settings)
4141

42-
def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: video_player.GUI, **study_settings):
42+
def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: video_player.GUI, progress_indicator: process_pool.JobProgress, **study_settings):
4343
has_gui = gui is not None
4444
sub_pixel_fac = 8 # for anti-aliased drawing
4545

46+
# progress indicator
47+
if progress_indicator is None:
48+
progress_indicator = process_pool.JobProgress(printer=lambda x: print(x))
49+
progress_indicator.set_unit('frames')
50+
progress_indicator.set_start_time_to_now()
51+
4652
# get settings for the study
4753
study_config = config.read_study_config_with_overrides(config_dir, {config.OverrideLevel.Session: working_dir}, **study_settings)
4854
if not study_config.mapped_video_make_which:
@@ -229,8 +235,6 @@ def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: video_
229235
if sync_target_function is not None:
230236
pose_estimators[rec].register_extra_processing_fun('sync', *sync_target_function)
231237
pose_estimators[rec].show_extra_processing_output = study_config.mapped_video_show_sync_func_output
232-
if study_config.sync_ref_recording and rec!=study_config.sync_ref_recording:
233-
pose_estimators[rec].set_do_report_frames(False)
234238

235239
if rec in study_config.mapped_video_make_which:
236240
pose_estimators[rec].set_visualize_on_frame(True)
@@ -292,6 +296,11 @@ def do_the_work(working_dir: pathlib.Path, config_dir: pathlib.Path, gui: video_
292296
gui.set_show_action_tooltip(True, gui_window_ids[v])
293297
gui.set_button_props_for_action(video_player.Action.Quit, 'Stop', tooltip='Interrupt (cut short) the video generation')
294298

299+
# prep progress indicator
300+
total = videos_ts[lead_vid].get_last()[0]
301+
progress_indicator.set_total(total)
302+
progress_indicator.set_intervals(int(total/200), int(total/200))
303+
295304
# open output video files
296305
for v in write_vids:
297306
# get which pixel format
@@ -444,6 +453,7 @@ def n_digit_timestamp(value_ms: float):
444453
for v in write_vids:
445454
img = Image(plane_buffers=[frame[v].flatten().tobytes()], pix_fmt='bgr24', size=(frame[v].shape[1], frame[v].shape[0]))
446455
vid_writer[v].write_frame(img=img, pts=frame_idx[lead_vid]/vid_info[lead_vid][2])
456+
progress_indicator.update()
447457

448458
# update gui, if any
449459
if has_gui:

src/gazeMapper/process/run_validation.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@
88

99

1010
stopAllProcessing = False
11-
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, **study_settings):
11+
def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path=None, progress_indicator: process_pool.JobProgress=None, **study_settings):
1212
working_dir = pathlib.Path(working_dir)
1313
if config_dir is None:
1414
config_dir = config.guess_config_dir(working_dir)
1515
config_dir = pathlib.Path(config_dir)
1616

17+
# progress indicator
18+
if progress_indicator is None:
19+
progress_indicator = process_pool.JobProgress(printer=lambda x: print(x))
20+
progress_indicator.set_unit('steps')
21+
progress_indicator.set_start_time_to_now()
22+
1723
# get settings for the study
1824
study_config = config.read_study_config_with_overrides(config_dir, {config.OverrideLevel.Session: working_dir.parent, config.OverrideLevel.Recording: working_dir}, **study_settings)
1925
if annotation.Event.Validate not in study_config.planes_per_episode:
@@ -28,6 +34,12 @@ def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, **st
2834
# get interval(s) coded to be analyzed, if any
2935
episodes = episode.list_to_marker_dict(episode.read_list_from_file(working_dir / naming.coding_file))[annotation.Event.Validate]
3036

37+
# prep progress indicator
38+
total = len(planes)*(2+len(episodes)*3)
39+
progress_indicator.set_total(total)
40+
progress_indicator.set_intervals(int(total/200), int(total/200))
41+
progress_indicator.update(n=0) # ensure a complete hover text appears before first processing step is finished
42+
3143
# per plane, run the glassesValidator steps
3244
for p in planes:
3345
plane_def = [pl for pl in study_config.planes if pl.name==p][0]
@@ -51,6 +63,7 @@ def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, **st
5163
I2MC_settings_override=study_config.validate_I2MC_settings,
5264
filename_stem=f'{naming.validation_prefix}{p}_fixations',
5365
plot_limits=plot_limits)
66+
progress_indicator.update()
5467

5568
# assign intervals
5669
for idx,_ in enumerate(episodes):
@@ -70,6 +83,8 @@ def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, **st
7083
fix_file,
7184
do_global_shift=study_config.validate_do_global_shift,
7285
max_dist_fac=study_config.validate_max_dist_fac)
86+
progress_indicator.update()
87+
7388
# plot output
7489
assign_intervals.plot(selected_intervals,
7590
other_intervals,
@@ -81,11 +96,14 @@ def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, **st
8196
iteration=idx,
8297
background_image=background_image,
8398
plot_limits=plot_limits)
99+
progress_indicator.update()
100+
84101
# store output to file
85102
assign_intervals.to_tsv(selected_intervals,
86103
working_dir,
87104
filename_stem=f'{naming.validation_prefix}{p}_fixation_assignment',
88105
iteration=idx)
106+
progress_indicator.update()
89107

90108
compute_offsets.compute(working_dir/f'{naming.world_gaze_prefix}{p}.tsv',
91109
working_dir/f'{naming.plane_pose_prefix}{p}.tsv',
@@ -98,6 +116,7 @@ def run(working_dir: str|pathlib.Path, config_dir: str|pathlib.Path = None, **st
98116
dq_types=study_config.validate_dq_types,
99117
allow_dq_fallback=study_config.validate_allow_dq_fallback,
100118
include_data_loss=study_config.validate_include_data_loss)
119+
progress_indicator.update()
101120

102121
# update state
103122
session.update_action_states(working_dir, process.Action.VALIDATE, process_pool.State.Completed, study_config)

0 commit comments

Comments
 (0)