Skip to content

Commit 679f283

Browse files
committed
Merge branch 'remotecontrolling' of https://github.com/DFKI-SignLanguage/RecSync-android into remotecontrolling
2 parents 9d660a9 + f067ff8 commit 679f283

File tree

5 files changed

+71
-15
lines changed

5 files changed

+71
-15
lines changed

PostProcessing/PostProcessVideos.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
from video import extract_frames
1313
from video import rebuild_video
14+
from video import extract_video_info
1415

1516

16-
THRESHOLD_NS = 10 * 1000 * 1000 # millis * micros * nanos
17+
DEFAULT_THRESHOLD_MILLIS = 10
18+
DEFAULT_THRESHOLD_NANOS = DEFAULT_THRESHOLD_MILLIS * 1000 * 1000 # millis * micros * nanos
1719

1820

1921
def scan_session_dir(input_dir: Path) -> Tuple[List[str], List[pd.DataFrame], List[str]]:
@@ -65,14 +67,13 @@ def scan_session_dir(input_dir: Path) -> Tuple[List[str], List[pd.DataFrame], Li
6567
#
6668
#
6769
#
68-
def main(input_dir: Path, output_dir: Path):
70+
def main(input_dir: Path, output_dir: Path, threshold_ns: int):
6971

7072
print(f"Scanning dir {str(input_dir)}...")
7173
clientIDs, df_list, mp4_list = scan_session_dir(input_dir)
7274

7375
n_clients = len(clientIDs)
7476

75-
7677
#
7778
# Print collected info
7879
for i in range(n_clients):
@@ -96,18 +97,21 @@ def main(input_dir: Path, output_dir: Path):
9697
# Find time ranges
9798
min_common, max_common = compute_time_range(repaired_df_list)
9899
# Trim the data frames to the time range
99-
trimmed_dataframes = trim_repaired_into_interval(repaired_df_list, min_common, max_common, THRESHOLD_NS)
100+
trimmed_dataframes = trim_repaired_into_interval(repaired_df_list, min_common, max_common, threshold_ns)
100101

101102
assert len(clientIDs) == len(trimmed_dataframes), f"Expected {len(clientIDs)} trimmed dataframes. Found f{len(trimmed_dataframes)}"
102103

103104
# Check that all the resulting dataframes have the same number of rows
105+
print("Checking if all clients we have the same number of frames in the repaired amd trimmed tables...")
104106
client0ID = clientIDs[0]
105107
client0size = len(trimmed_dataframes[0])
106-
print(f"For client {client0ID}: {client0size} frames")
108+
print(f"Client {client0ID} has {client0size} frames.")
107109
for cID, df in zip(clientIDs[1:], trimmed_dataframes[1:]):
108110
dfsize = len(df)
109111
if client0size != dfsize:
110-
raise Exception(f"For client {cID}: expecting {client0size} frames, found {dfsize}")
112+
raise Exception(f"For client {cID}: expecting {client0size} frames. Found {dfsize}."
113+
f" This might be due to an excessive phase offset during recording."
114+
f" Try to increase the threshold.")
111115

112116
print("Good. All trimmed dataframes have the same number of entries.")
113117

@@ -125,8 +129,10 @@ def main(input_dir: Path, output_dir: Path):
125129
extract_frames(video_file=video_file, timestamps_df=orig_df, output_dir=tmp_dir)
126130

127131
# Reconstruct videos
132+
vinfo = extract_video_info(video_path=video_file)
133+
input_fps = vinfo.fps
128134
video_out_filepath = output_dir / (cID + ".mp4")
129-
rebuild_video(dir=Path(tmp_dir), frames=trimmed_df, outfile=video_out_filepath)
135+
rebuild_video(dir=Path(tmp_dir), frames=trimmed_df, fps=input_fps, outfile=video_out_filepath)
130136
# And save also the CSV
131137
csv_out_filepath = video_out_filepath.with_suffix(".csv")
132138
trimmed_df.to_csv(path_or_buf=csv_out_filepath, header=True, index=False)
@@ -149,16 +155,25 @@ def main(input_dir: Path, output_dir: Path):
149155
"--outfolder", "-o", type=str, help="The folder where the repaired and aligned frames will be stored.",
150156
required=True
151157
)
158+
parser.add_argument(
159+
"--threshold", "-t", type=int, help="The allowed difference in ms between corresponding frames on different videos."
160+
" Increase this is post processing fails with trimmed tables of different sizes."
161+
f" Default is now {DEFAULT_THRESHOLD_MILLIS} ms.",
162+
required=False,
163+
default=DEFAULT_THRESHOLD_MILLIS
164+
)
152165

153166
args = parser.parse_args()
154167

155168
infolder = Path(args.infolder)
156169
outfolder = Path(args.outfolder)
170+
threshold_millis = args.threshold
171+
threshold_nanos = threshold_millis * 1000 * 1000
157172

158173
if not infolder.exists():
159174
raise Exception(f"Input folder '{infolder}' doesn't exist.")
160175

161176
if not outfolder.exists():
162177
raise Exception(f"Output folder '{outfolder}' doesn't exist.")
163178

164-
main(infolder, outfolder)
179+
main(infolder, outfolder, threshold_nanos)

PostProcessing/dataframes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,6 @@ def trim_repaired_into_interval(dfs, min_common, max_common, threshold) -> List[
120120
trimmed_df = df[selection_mask]
121121
trimmed_dataframes.append(trimmed_df)
122122

123+
assert len(trimmed_dataframes) <= len(df)
124+
123125
return trimmed_dataframes

PostProcessing/test_functions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
from video import video_info
1515

16-
from PostProcessVideos import THRESHOLD_NS
16+
from PostProcessVideos import DEFAULT_THRESHOLD_NANOS
1717

1818

1919
RECSYNCH_SESSION_DIR_VAR = "RECSYNCH_SESSION_DIR"
@@ -94,7 +94,7 @@ def test_df_reparation(client_data):
9494

9595
tstamps = repaired_df["timestamp"]
9696
diffs = tstamps.diff().dropna()
97-
assert (diffs <= (time_step + THRESHOLD_NS)).all(), "some timestamps difference is longer than expected"
97+
assert (diffs <= (time_step + DEFAULT_THRESHOLD_NANOS)).all(), "some timestamps difference is longer than expected"
9898

9999

100100
def test_df_trimming(session_data):

PostProcessing/video.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import cv2
22
import os
33
from pathlib import Path
4+
from collections import namedtuple
45

56
import pandas as pd
67
import numpy as np
@@ -35,7 +36,43 @@ def video_info(video_path: str) -> Tuple[int, int, int]:
3536
return video_w, video_h, n_frames
3637

3738

38-
def extract_frames(video_file: str, timestamps_df: pd.DataFrame, output_dir: str):
39+
VideoInfo = namedtuple("VideoInfo", ["width", "height", "n_frames", "fps", "codec"])
40+
41+
42+
def extract_video_info(video_path: str) -> VideoInfo:
43+
"""
44+
Uses the ffmpeg.probe function to retrieve information about a video file.
45+
46+
:param video_path: Path to a valid video file
47+
:return: An instance of a VideoTuple named tuple with self-explaining information.
48+
"""
49+
50+
#
51+
# Fetch video info
52+
info = ffmpeg.probe(video_path)
53+
# Get the list of all video streams
54+
video_streams = [stream for stream in info['streams'] if stream['codec_type'] == 'video']
55+
if len(video_streams) == 0:
56+
raise BaseException("No video streams found in file '{}'".format(video_path))
57+
58+
# retrieve the first stream of type 'video'
59+
info_video = video_streams[0]
60+
61+
framerate_ratio_str = info_video['r_frame_rate']
62+
fps = eval(framerate_ratio_str)
63+
64+
out = VideoInfo(
65+
width=info_video['width'],
66+
height=info_video['height'],
67+
n_frames=int(info_video['nb_frames']),
68+
fps=fps,
69+
codec=info_video['codec_name']
70+
)
71+
72+
return out
73+
74+
75+
def extract_frames(video_file: str, timestamps_df: pd.DataFrame, output_dir: str) -> None:
3976

4077
# Open the video file
4178
cap = cv2.VideoCapture(video_file)
@@ -60,7 +97,7 @@ def extract_frames(video_file: str, timestamps_df: pd.DataFrame, output_dir: str
6097
cap.release()
6198

6299

63-
def extract_frames_ffmpeg(video_file: str, timestamps_df: pd.DataFrame, output_dir: str):
100+
def extract_frames_ffmpeg(video_file: str, timestamps_df: pd.DataFrame, output_dir: str) -> None:
64101
video_w, video_h, _ = video_info(video_file)
65102

66103
ffmpeg_read_process = (
@@ -98,7 +135,7 @@ def extract_frames_ffmpeg(video_file: str, timestamps_df: pd.DataFrame, output_d
98135
ffmpeg_read_process = None
99136

100137

101-
def rebuild_video(dir: Path, frames: pd.DataFrame, outfile: Path) -> None:
138+
def rebuild_video(dir: Path, frames: pd.DataFrame, fps: float, outfile: Path) -> None:
102139

103140
# We don't know the target video size, yet.
104141
frame_width = None
@@ -131,6 +168,7 @@ def rebuild_video(dir: Path, frames: pd.DataFrame, outfile: Path) -> None:
131168
ffmpeg_video_out_process = (
132169
ffmpeg
133170
.input('pipe:', format='rawvideo', pix_fmt='rgb24', s='{}x{}'.format(frame_width, frame_height))
171+
.filter('fps', fps=fps)
134172
# -vf "drawtext=fontfile=Arial.ttf: fontsize=48: text=%{n}: x=(w-tw)/2: y=h-(2*lh): fontcolor=white: box=1: boxcolor=0x00000099"
135173
.drawtext(text="%{n}", escape_text=False,
136174
#x=50, y=50,

remote_control/remote_control.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@ def retranslateUi(self, MainWindow):
222222
self.stop_btn.setText(_translate("MainWindow", "Stop"))
223223
self.status_btn.setText(_translate("MainWindow", "Status"))
224224
self.status_clear_btn.setText(_translate("MainWindow", "X"))
225-
self.status_clear_btn.setStyleSheet('QPushButton {;color: red;}')
225+
self.status_clear_btn.setStyleSheet('QPushButton {;color: #cc2222;}')
226226
self.delete_btn.setText(_translate("MainWindow", "Empty Device"))
227-
self.delete_btn.setStyleSheet('QPushButton {;background-color: red;}')
227+
self.delete_btn.setStyleSheet('QPushButton {;background-color: #cc2222;}')
228228
self.api_input.setPlaceholderText(_translate("MainWindow", "Please enter the api endpoint where you want the files to be uploaded."))
229229
self.download_prefix_text.setPlaceholderText(_translate("MainWindow", " Enter Session Prefix"))
230230
self.download_btn.setText(_translate("MainWindow", "Download"))
@@ -240,4 +240,5 @@ def retranslateUi(self, MainWindow):
240240
rc.setupUi(MainWindow)
241241

242242
MainWindow.show()
243+
243244
sys.exit(app.exec_())

0 commit comments

Comments
 (0)