Spaces:
Build error
Build error
Iskaj
commited on
Commit
•
2935ca0
1
Parent(s):
39557de
add changepoint detection to app and figures
Browse files
app.py
CHANGED
@@ -22,6 +22,10 @@ import faiss
|
|
22 |
|
23 |
import shutil
|
24 |
|
|
|
|
|
|
|
|
|
25 |
FPS = 5
|
26 |
MIN_DISTANCE = 4
|
27 |
MAX_DISTANCE = 30
|
@@ -79,6 +83,8 @@ def compute_hashes(clip, fps=FPS):
|
|
79 |
yield {"frame": 1+index*fps, "hash": hashed}
|
80 |
|
81 |
def index_hashes_for_video(url, is_file = False):
|
|
|
|
|
82 |
if not is_file:
|
83 |
filename = download_video_from_url(url)
|
84 |
else:
|
@@ -116,9 +122,8 @@ def get_video_indices(url, target, MIN_DISTANCE = 4):
|
|
116 |
- MIN_DISTANCE: integer representing the minimum distance between hashes on bit-level before its considered a match
|
117 |
"""
|
118 |
# TODO: Fix crash if no matches are found
|
119 |
-
|
120 |
-
|
121 |
-
elif url.endswith('.mp4'):
|
122 |
is_file = True
|
123 |
|
124 |
# Url (short video)
|
@@ -132,6 +137,8 @@ def get_video_indices(url, target, MIN_DISTANCE = 4):
|
|
132 |
return video_index, hash_vectors, target_indices
|
133 |
|
134 |
def compare_videos(video_index, hash_vectors, target_indices, MIN_DISTANCE = 3): # , is_file = False):
|
|
|
|
|
135 |
# The results are returned as a triplet of 1D arrays
|
136 |
# lims, D, I, where result for query i is in I[lims[i]:lims[i+1]]
|
137 |
# (indices of neighbors), D[lims[i]:lims[i+1]] (distances).
|
@@ -149,7 +156,9 @@ def get_decent_distance(url, target, MIN_DISTANCE, MAX_DISTANCE):
|
|
149 |
nr_matches = len(D)
|
150 |
logging.info(f"{(nr_matches/nr_source_frames) * 100.0:.1f}% of frames have a match for distance '{distance}' ({nr_matches} matches for {nr_source_frames} frames)")
|
151 |
if nr_matches >= nr_source_frames:
|
152 |
-
return distance
|
|
|
|
|
153 |
|
154 |
def plot_comparison(lims, D, I, hash_vectors, MIN_DISTANCE = 3):
|
155 |
sns.set_theme()
|
@@ -185,16 +194,22 @@ def plot_comparison(lims, D, I, hash_vectors, MIN_DISTANCE = 3):
|
|
185 |
logging.basicConfig()
|
186 |
logging.getLogger().setLevel(logging.INFO)
|
187 |
|
188 |
-
def plot_multi_comparison(df):
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
sns.
|
193 |
-
sns.
|
194 |
-
sns.
|
195 |
-
|
196 |
-
|
197 |
-
sns.lineplot(data = df, x='
|
|
|
|
|
|
|
|
|
|
|
|
|
198 |
return fig
|
199 |
|
200 |
|
@@ -250,8 +265,27 @@ def get_videomatch_df(url, target, min_distance=MIN_DISTANCE, vanilla_df=False):
|
|
250 |
# Add Offset col that assumes the video is played at the same speed as the other to do a "timeshift"
|
251 |
df['OFFSET'] = df['SOURCE_S'] - df['TARGET_S'] - np.min(df['SOURCE_S'])
|
252 |
df['OFFSET_LIP'] = df['SOURCE_LIP_S'] - df['TARGET_S'] - np.min(df['SOURCE_LIP_S'])
|
|
|
|
|
|
|
253 |
return df
|
254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
255 |
def get_comparison(url, target, MIN_DISTANCE = 4):
|
256 |
""" Function for Gradio to combine all helper functions"""
|
257 |
video_index, hash_vectors, target_indices = get_video_indices(url, target, MIN_DISTANCE = MIN_DISTANCE)
|
@@ -259,34 +293,43 @@ def get_comparison(url, target, MIN_DISTANCE = 4):
|
|
259 |
fig = plot_comparison(lims, D, I, hash_vectors, MIN_DISTANCE = MIN_DISTANCE)
|
260 |
return fig
|
261 |
|
262 |
-
def get_auto_comparison(url, target,
|
263 |
""" Function for Gradio to combine all helper functions"""
|
264 |
distance = get_decent_distance(url, target, MIN_DISTANCE, MAX_DISTANCE)
|
|
|
|
|
265 |
video_index, hash_vectors, target_indices = get_video_indices(url, target, MIN_DISTANCE = distance)
|
266 |
lims, D, I, hash_vectors = compare_videos(video_index, hash_vectors, target_indices, MIN_DISTANCE = distance)
|
267 |
# fig = plot_comparison(lims, D, I, hash_vectors, MIN_DISTANCE = distance)
|
268 |
df = get_videomatch_df(url, target, min_distance=MIN_DISTANCE, vanilla_df=False)
|
269 |
-
|
|
|
270 |
return fig
|
271 |
|
|
|
|
|
272 |
video_urls = ["https://www.dropbox.com/s/8c89a9aba0w8gjg/Ploumen.mp4?dl=1",
|
273 |
"https://www.dropbox.com/s/rzmicviu1fe740t/Bram%20van%20Ojik%20krijgt%20reprimande.mp4?dl=1",
|
274 |
"https://www.dropbox.com/s/wcot34ldmb84071/Baudet%20ontmaskert%20Omtzigt_%20u%20bent%20door%20de%20mand%20gevallen%21.mp4?dl=1",
|
|
|
275 |
"https://www.dropbox.com/s/4ognq8lshcujk43/Plenaire_zaal_20200923132426_Omtzigt.mp4?dl=1"]
|
276 |
|
277 |
index_iface = gr.Interface(fn=lambda url: index_hashes_for_video(url).ntotal,
|
278 |
-
inputs="text",
|
|
|
279 |
examples=video_urls, cache_examples=True)
|
280 |
|
281 |
compare_iface = gr.Interface(fn=get_comparison,
|
282 |
-
inputs=["text", "text", gr.Slider(2, 30, 4, step=2)],
|
|
|
283 |
examples=[[x, video_urls[-1]] for x in video_urls[:-1]])
|
284 |
|
285 |
auto_compare_iface = gr.Interface(fn=get_auto_comparison,
|
286 |
-
inputs=["text", "text"],
|
|
|
287 |
examples=[[x, video_urls[-1]] for x in video_urls[:-1]])
|
288 |
|
289 |
-
iface = gr.TabbedInterface([
|
290 |
|
291 |
if __name__ == "__main__":
|
292 |
import matplotlib
|
@@ -295,5 +338,5 @@ if __name__ == "__main__":
|
|
295 |
logging.basicConfig()
|
296 |
logging.getLogger().setLevel(logging.INFO)
|
297 |
|
298 |
-
iface.launch()
|
299 |
#iface.launch(auth=("test", "test"), share=True, debug=True)
|
|
|
22 |
|
23 |
import shutil
|
24 |
|
25 |
+
from kats.detectors.cusum_detection import CUSUMDetector
|
26 |
+
from kats.detectors.robust_stat_detection import RobustStatDetector
|
27 |
+
from kats.consts import TimeSeriesData
|
28 |
+
|
29 |
FPS = 5
|
30 |
MIN_DISTANCE = 4
|
31 |
MAX_DISTANCE = 30
|
|
|
83 |
yield {"frame": 1+index*fps, "hash": hashed}
|
84 |
|
85 |
def index_hashes_for_video(url, is_file = False):
|
86 |
+
""" Download a video if it is a url, otherwise refer to the file. Secondly index the video
|
87 |
+
using faiss indices and return thi index. """
|
88 |
if not is_file:
|
89 |
filename = download_video_from_url(url)
|
90 |
else:
|
|
|
122 |
- MIN_DISTANCE: integer representing the minimum distance between hashes on bit-level before its considered a match
|
123 |
"""
|
124 |
# TODO: Fix crash if no matches are found
|
125 |
+
is_file = False
|
126 |
+
if url.endswith('.mp4'):
|
|
|
127 |
is_file = True
|
128 |
|
129 |
# Url (short video)
|
|
|
137 |
return video_index, hash_vectors, target_indices
|
138 |
|
139 |
def compare_videos(video_index, hash_vectors, target_indices, MIN_DISTANCE = 3): # , is_file = False):
|
140 |
+
""" Search for matches between the indices of the target video (long video)
|
141 |
+
and the given hash vectors of a video"""
|
142 |
# The results are returned as a triplet of 1D arrays
|
143 |
# lims, D, I, where result for query i is in I[lims[i]:lims[i+1]]
|
144 |
# (indices of neighbors), D[lims[i]:lims[i+1]] (distances).
|
|
|
156 |
nr_matches = len(D)
|
157 |
logging.info(f"{(nr_matches/nr_source_frames) * 100.0:.1f}% of frames have a match for distance '{distance}' ({nr_matches} matches for {nr_source_frames} frames)")
|
158 |
if nr_matches >= nr_source_frames:
|
159 |
+
return distance
|
160 |
+
logging.warning(f"No matches found for any distance between {MIN_DISTANCE} and {MAX_DISTANCE}")
|
161 |
+
return None
|
162 |
|
163 |
def plot_comparison(lims, D, I, hash_vectors, MIN_DISTANCE = 3):
|
164 |
sns.set_theme()
|
|
|
194 |
logging.basicConfig()
|
195 |
logging.getLogger().setLevel(logging.INFO)
|
196 |
|
197 |
+
def plot_multi_comparison(df, change_points):
|
198 |
+
""" From the dataframe plot the current set of plots, where the bottom right is most indicative """
|
199 |
+
fig, ax_arr = plt.subplots(3, 2, figsize=(12, 6), dpi=100, sharex=True)
|
200 |
+
sns.scatterplot(data = df, x='time', y='SOURCE_S', ax=ax_arr[0,0])
|
201 |
+
sns.lineplot(data = df, x='time', y='SOURCE_LIP_S', ax=ax_arr[0,1])
|
202 |
+
sns.scatterplot(data = df, x='time', y='OFFSET', ax=ax_arr[1,0])
|
203 |
+
sns.lineplot(data = df, x='time', y='OFFSET_LIP', ax=ax_arr[1,1])
|
204 |
+
|
205 |
+
# Plot change point as lines
|
206 |
+
sns.lineplot(data = df, x='time', y='OFFSET_LIP', ax=ax_arr[2,1])
|
207 |
+
for x in change_points:
|
208 |
+
cp_time = x.start_time
|
209 |
+
plt.vlines(x=cp_time, ymin=np.min(df['OFFSET_LIP']), ymax=np.max(df['OFFSET_LIP']), colors='red', lw=2)
|
210 |
+
rand_y_pos = np.random.uniform(low=np.min(df['OFFSET_LIP']), high=np.max(df['OFFSET_LIP']), size=None)
|
211 |
+
plt.text(x=cp_time, y=rand_y_pos, s=str(np.round(x.confidence, 2)), color='r', rotation=-0.0, fontsize=14)
|
212 |
+
plt.xticks(rotation=90)
|
213 |
return fig
|
214 |
|
215 |
|
|
|
265 |
# Add Offset col that assumes the video is played at the same speed as the other to do a "timeshift"
|
266 |
df['OFFSET'] = df['SOURCE_S'] - df['TARGET_S'] - np.min(df['SOURCE_S'])
|
267 |
df['OFFSET_LIP'] = df['SOURCE_LIP_S'] - df['TARGET_S'] - np.min(df['SOURCE_LIP_S'])
|
268 |
+
|
269 |
+
# Add time column for plotting
|
270 |
+
df['time'] = pd.to_datetime(df["TARGET_S"], unit='s') # Needs a datetime as input
|
271 |
return df
|
272 |
|
273 |
+
def get_change_points(df, smoothing_window_size=10, method='CUSUM'):
|
274 |
+
tsd = TimeSeriesData(df.loc[:,['time','OFFSET_LIP']])
|
275 |
+
if method.upper() == "CUSUM":
|
276 |
+
detector = CUSUMDetector(tsd)
|
277 |
+
elif method.upper() == "ROBUSTSTAT":
|
278 |
+
detector = RobustStatDetector(tsd)
|
279 |
+
change_points = detector.detector(smoothing_window_size=smoothing_window_size, comparison_window=-2)
|
280 |
+
|
281 |
+
# Print some stats
|
282 |
+
if method.upper() == "CUSUM" and change_points != []:
|
283 |
+
mean_offset_prechange = change_points[0].mu0
|
284 |
+
mean_offset_postchange = change_points[0].mu1
|
285 |
+
jump_s = mean_offset_postchange - mean_offset_prechange
|
286 |
+
print(f"Video jumps {jump_s:.1f}s in time at {mean_offset_prechange:.1f} seconds")
|
287 |
+
return change_points
|
288 |
+
|
289 |
def get_comparison(url, target, MIN_DISTANCE = 4):
|
290 |
""" Function for Gradio to combine all helper functions"""
|
291 |
video_index, hash_vectors, target_indices = get_video_indices(url, target, MIN_DISTANCE = MIN_DISTANCE)
|
|
|
293 |
fig = plot_comparison(lims, D, I, hash_vectors, MIN_DISTANCE = MIN_DISTANCE)
|
294 |
return fig
|
295 |
|
296 |
+
def get_auto_comparison(url, target, smoothing_window_size=10, method="CUSUM"):
|
297 |
""" Function for Gradio to combine all helper functions"""
|
298 |
distance = get_decent_distance(url, target, MIN_DISTANCE, MAX_DISTANCE)
|
299 |
+
if distance == None:
|
300 |
+
raise gr.Error("No matches found!")
|
301 |
video_index, hash_vectors, target_indices = get_video_indices(url, target, MIN_DISTANCE = distance)
|
302 |
lims, D, I, hash_vectors = compare_videos(video_index, hash_vectors, target_indices, MIN_DISTANCE = distance)
|
303 |
# fig = plot_comparison(lims, D, I, hash_vectors, MIN_DISTANCE = distance)
|
304 |
df = get_videomatch_df(url, target, min_distance=MIN_DISTANCE, vanilla_df=False)
|
305 |
+
change_points = get_change_points(df, smoothing_window_size=smoothing_window_size, method=method)
|
306 |
+
fig = plot_multi_comparison(df, change_points)
|
307 |
return fig
|
308 |
|
309 |
+
|
310 |
+
|
311 |
video_urls = ["https://www.dropbox.com/s/8c89a9aba0w8gjg/Ploumen.mp4?dl=1",
|
312 |
"https://www.dropbox.com/s/rzmicviu1fe740t/Bram%20van%20Ojik%20krijgt%20reprimande.mp4?dl=1",
|
313 |
"https://www.dropbox.com/s/wcot34ldmb84071/Baudet%20ontmaskert%20Omtzigt_%20u%20bent%20door%20de%20mand%20gevallen%21.mp4?dl=1",
|
314 |
+
"https://drive.google.com/uc?id=1XW0niHR1k09vPNv1cp6NvdGXe7FHJc1D&export=download",
|
315 |
"https://www.dropbox.com/s/4ognq8lshcujk43/Plenaire_zaal_20200923132426_Omtzigt.mp4?dl=1"]
|
316 |
|
317 |
index_iface = gr.Interface(fn=lambda url: index_hashes_for_video(url).ntotal,
|
318 |
+
inputs="text",
|
319 |
+
outputs="text",
|
320 |
examples=video_urls, cache_examples=True)
|
321 |
|
322 |
compare_iface = gr.Interface(fn=get_comparison,
|
323 |
+
inputs=["text", "text", gr.Slider(2, 30, 4, step=2)],
|
324 |
+
outputs="plot",
|
325 |
examples=[[x, video_urls[-1]] for x in video_urls[:-1]])
|
326 |
|
327 |
auto_compare_iface = gr.Interface(fn=get_auto_comparison,
|
328 |
+
inputs=["text", "text", gr.Slider(1, 50, 10, step=1), gr.Dropdown(choices=["CUSUM", "Robust"], value="CUSUM")],
|
329 |
+
outputs="plot",
|
330 |
examples=[[x, video_urls[-1]] for x in video_urls[:-1]])
|
331 |
|
332 |
+
iface = gr.TabbedInterface([auto_compare_iface, compare_iface, index_iface,], ["AutoCompare", "Compare", "Index"])
|
333 |
|
334 |
if __name__ == "__main__":
|
335 |
import matplotlib
|
|
|
338 |
logging.basicConfig()
|
339 |
logging.getLogger().setLevel(logging.INFO)
|
340 |
|
341 |
+
iface.launch(inbrowser=True, debug=True)
|
342 |
#iface.launch(auth=("test", "test"), share=True, debug=True)
|