ElBeh commited on
Commit
b01f8ec
·
verified ·
1 Parent(s): 941688f

Upload 2 files

Browse files
Files changed (2) hide show
  1. tabs/tab_help.py +19 -0
  2. tabs/tab_videoframes.py +440 -0
tabs/tab_help.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Sat Nov 8 11:58:29 2025
5
+
6
+ @author: standarduser
7
+ """
8
+
9
+
10
+
11
+ import gradio as gr
12
+
13
+
14
+ def create_tab_help(tab_label):
15
+ """create help tab"""
16
+ with gr.TabItem(tab_label):
17
+ gr.Markdown("# Help")
18
+
19
+
tabs/tab_videoframes.py ADDED
@@ -0,0 +1,440 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Created on Sat Nov 8 09:54:54 2025
5
+
6
+ @author: standarduser
7
+ """
8
+ import gradio as gr
9
+ import cv2
10
+ import numpy as np
11
+ from PIL import Image
12
+
13
+ # CSS for box styling
14
+ css = """
15
+ .box {
16
+ border: 2px solid #4CAF50;
17
+ padding: 10px;
18
+ border-radius: 10px;
19
+ background-color: #f9f9f9;
20
+ }
21
+ """
22
+
23
+ def merge_annotations(base_image, annotations, mode, current_frame_idx, global_annotation):
24
+ """Combines base frame with annotations"""
25
+ if base_image is None:
26
+ return None
27
+
28
+ if isinstance(base_image, np.ndarray):
29
+ img = Image.fromarray(base_image)
30
+ else:
31
+ img = base_image.copy()
32
+
33
+ # Mode B: Global annotation
34
+ if mode == "B" and global_annotation is not None:
35
+ img = Image.alpha_composite(img.convert('RGBA'), global_annotation).convert('RGB')
36
+
37
+ # Mode A: Frame-specific annotation
38
+ elif mode == "A" and current_frame_idx in annotations:
39
+ img = Image.alpha_composite(img.convert('RGBA'), annotations[current_frame_idx]).convert('RGB')
40
+
41
+ return img
42
+
43
+ def apply_transformation(frame, transformation, process_image_func):
44
+ """Applies selected transformation to frame"""
45
+ if frame is None or transformation == "None":
46
+ return frame
47
+
48
+ # Convert numpy array to PIL if needed
49
+ if isinstance(frame, np.ndarray):
50
+ pil_frame = Image.fromarray(frame)
51
+ else:
52
+ pil_frame = frame
53
+
54
+ # Call process_image with the frame
55
+ result = process_image_func(pil_frame, transformation)
56
+
57
+ # Extract transformed image from tuple
58
+ if isinstance(result, tuple) and len(result) == 2:
59
+ transformed = result[1]
60
+ else:
61
+ transformed = result
62
+
63
+ # Convert back to numpy array
64
+ if transformed is not None:
65
+ return np.array(transformed)
66
+
67
+ return frame
68
+
69
+ def create_sketchpad_value(base_image, annotations, mode, current_frame_idx, global_annotation, transformation, process_image_func):
70
+ """Creates Sketchpad value (Background + Layers)"""
71
+ if base_image is None:
72
+ return None
73
+
74
+ # Apply transformation first
75
+ transformed_frame = apply_transformation(base_image, transformation, process_image_func)
76
+
77
+ # Prepare base image
78
+ if isinstance(transformed_frame, np.ndarray):
79
+ background = Image.fromarray(transformed_frame)
80
+ else:
81
+ background = transformed_frame.copy()
82
+
83
+ # Extract annotation layer
84
+ annotation_layer = None
85
+ if mode == "B" and global_annotation is not None:
86
+ annotation_layer = global_annotation
87
+ elif mode == "A" and current_frame_idx in annotations:
88
+ annotation_layer = annotations[current_frame_idx]
89
+
90
+ # Create Sketchpad dict
91
+ result = {
92
+ 'background': background,
93
+ 'layers': [annotation_layer] if annotation_layer is not None else [],
94
+ 'composite': None
95
+ }
96
+
97
+ return result
98
+
99
+ def extract_annotation_from_sketch(sketch_data):
100
+ """Extracts only the drawing from Sketchpad data"""
101
+ if sketch_data is None:
102
+ return None
103
+
104
+ if isinstance(sketch_data, dict):
105
+ if 'layers' in sketch_data and len(sketch_data['layers']) > 0:
106
+ drawing = sketch_data['layers'][0]
107
+ if isinstance(drawing, np.ndarray):
108
+ # Check if there are actually drawings
109
+ if len(drawing.shape) == 3 and drawing.shape[2] == 4: # RGBA
110
+ alpha = drawing[:, :, 3]
111
+ if np.any(alpha > 0):
112
+ return Image.fromarray(drawing, 'RGBA')
113
+ return None
114
+ return drawing
115
+ elif 'composite' in sketch_data and sketch_data['composite'] is not None:
116
+ composite = sketch_data['composite']
117
+ if isinstance(composite, np.ndarray):
118
+ return Image.fromarray(composite, 'RGBA')
119
+ return composite
120
+
121
+ return None
122
+
123
+ def create_comparison_slider(frame, transformation, process_image_func):
124
+ """Creates ImageSlider comparison between original and transformed frame"""
125
+ if frame is None:
126
+ return None
127
+
128
+ # Convert to PIL if needed
129
+ if isinstance(frame, np.ndarray):
130
+ original = Image.fromarray(frame)
131
+ else:
132
+ original = frame
133
+
134
+ if transformation == "None":
135
+ return (original, original)
136
+
137
+ # Apply transformation
138
+ transformed_array = apply_transformation(frame, transformation, process_image_func)
139
+
140
+ if isinstance(transformed_array, np.ndarray):
141
+ transformed = Image.fromarray(transformed_array)
142
+ else:
143
+ transformed = transformed_array
144
+
145
+ return (original, transformed)
146
+
147
+
148
+ def update_frame_display(frame_idx, frames, fps, annotations, global_annotation, annotation_mode, transformation, process_image_func):
149
+ """Updates frame display"""
150
+ if not frames or frame_idx >= len(frames):
151
+ return {"background": None, "layers": []}, None, f"Frame {int(frame_idx)+1} / 0", "--:--"
152
+
153
+ # Calculate video time
154
+ if fps > 0:
155
+ current_time = frame_idx / fps
156
+ minutes = int(current_time // 60)
157
+ seconds = current_time % 60
158
+ time_str = f"{minutes:02d}:{seconds:05.2f}"
159
+ else:
160
+ time_str = "--:--"
161
+
162
+ # Load frame
163
+ frame = frames[int(frame_idx)]
164
+
165
+ # Create Sketchpad value with transformation
166
+ sketch_value = create_sketchpad_value(frame, annotations, annotation_mode, int(frame_idx), global_annotation, transformation, process_image_func)
167
+
168
+ # Create comparison slider
169
+ slider_value = create_comparison_slider(frame, transformation, process_image_func)
170
+
171
+ return sketch_value, slider_value, f"Frame {int(frame_idx)+1} / {len(frames)}", time_str
172
+
173
+
174
+ def go_to_prev_frame(current_idx, steps, frames, fps, annotations, global_annotation, annotation_mode, transformation, process_image_func):
175
+ """Goes one frame back"""
176
+ if not frames:
177
+ return 0, {"background": None, "layers": []}, None, "No video loaded", "--:--"
178
+
179
+ new_idx = max(0, int(current_idx) - steps)
180
+ sketch_value, slider_value, info, time_str = update_frame_display(new_idx, frames, fps, annotations, global_annotation, annotation_mode, transformation, process_image_func)
181
+ return new_idx, sketch_value, slider_value, info, time_str
182
+
183
+
184
+ def go_to_next_frame(current_idx, steps, frames, fps, annotations, global_annotation, annotation_mode, transformation, process_image_func):
185
+ """Goes one frame forward"""
186
+ if not frames:
187
+ return 0, {"background": None, "layers": []}, None, "No video loaded", "--:--"
188
+
189
+ new_idx = min(len(frames) - 1, int(current_idx) + steps)
190
+ sketch_value, slider_value, info, time_str = update_frame_display(new_idx, frames, fps, annotations, global_annotation, annotation_mode, transformation, process_image_func)
191
+ return new_idx, sketch_value, slider_value, info, time_str
192
+
193
+
194
+ def load_video_frames(video_path):
195
+ """Loads all frames from a video"""
196
+ if video_path is None:
197
+ return [], 0, gr.update(maximum=0, value=0), "No video loaded", 0, 0, {}, None
198
+
199
+ cap = cv2.VideoCapture(video_path)
200
+ frames = []
201
+ fps = cap.get(cv2.CAP_PROP_FPS)
202
+
203
+ while True:
204
+ ret, frame = cap.read()
205
+ if not ret:
206
+ break
207
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
208
+ frames.append(frame_rgb)
209
+ cap.release()
210
+
211
+ if len(frames) == 0:
212
+ return [], 0, gr.update(maximum=0, value=0), "No frames found", 0, 0, {}, None
213
+
214
+ duration = len(frames) / fps if fps > 0 else 0
215
+
216
+ return (
217
+ frames,
218
+ 0,
219
+ gr.update(maximum=len(frames)-1, value=0),
220
+ f"Frame 1 / {len(frames)}",
221
+ duration,
222
+ fps,
223
+ {},
224
+ None
225
+ )
226
+
227
+
228
+ def save_sketch_annotation(sketch_data, mode, current_frame_idx, annotations, global_annotation):
229
+ """Saves drawing from Sketchpad"""
230
+ annotation_img = extract_annotation_from_sketch(sketch_data)
231
+
232
+ if annotation_img is None:
233
+ return annotations, global_annotation
234
+
235
+ new_annotations = annotations.copy() if annotations else {}
236
+ new_global = global_annotation
237
+
238
+ if mode == "A":
239
+ new_annotations[current_frame_idx] = annotation_img
240
+ else: # Mode B
241
+ new_global = annotation_img
242
+
243
+ return new_annotations, new_global
244
+
245
+
246
+ def clear_annotations(mode, annotations, global_annotation):
247
+ """Deletes annotations depending on mode"""
248
+ if mode == "A":
249
+ return {}, global_annotation
250
+ else: # Mode B
251
+ return annotations, None
252
+
253
+
254
+ def create_tab_videoframes(tab_label, process_image):
255
+ """Creates a tab for video frame processing"""
256
+ with gr.TabItem(tab_label):
257
+ video_frames = gr.State([])
258
+ current_frame_idx = gr.State(0)
259
+ video_duration = gr.State(0)
260
+ video_fps = gr.State(0)
261
+ frame_annotations = gr.State({})
262
+ global_annotation = gr.State(None)
263
+ annotation_mode = gr.State("A")
264
+ selected_transformation = gr.State("None")
265
+
266
+
267
+ # Row 1: raw video
268
+ with gr.Accordion("Video Input", open=True):
269
+ with gr.Row():
270
+ with gr.Column():
271
+ video_input = gr.Video(label="Upload video", height=600, sources=['upload'], scale=1)
272
+
273
+
274
+ with gr.Row():
275
+ gr.Markdown("---")
276
+
277
+ # Row 2: video annotations
278
+ with gr.Row():
279
+ with gr.Column(scale=6):
280
+ with gr.Tabs():
281
+ with gr.TabItem("Comparison"):
282
+ comparison_slider = gr.ImageSlider(
283
+ label="Original vs Transformed",
284
+ height=600
285
+ )
286
+ with gr.TabItem("Annotations"):
287
+ with gr.Row():
288
+ radio_mode = gr.Radio(
289
+ choices=[("Per Frame", "A"), ("Global", "B")],
290
+ value="A",
291
+ label="Annotation Mode",
292
+ info="Per Frame: Drawings for each frame separately | Global: One drawing over all frames",
293
+ scale=3
294
+ )
295
+ btn_clear_annotations = gr.Button("Clear Annotations", variant="stop", scale=1, size="sm")
296
+
297
+ with gr.Row():
298
+ sketch_output = gr.Sketchpad(
299
+ label="Video Frame (drawing enabled)",
300
+ height=600,
301
+ brush=gr.Brush(
302
+ colors=["#FF0000", "#00FF00", "#7a7990", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF", "#FFFFFF", "#000000"],
303
+ default_size=3
304
+ ),
305
+ type="numpy",
306
+ scale=2
307
+ )
308
+ with gr.Column(scale=1, min_width=1):
309
+
310
+
311
+ frame_info = gr.Textbox(label="Frame Info", value="No video loaded", interactive=False, scale=2,)
312
+ video_time_display = gr.Textbox(label="Video Time", value="--:--", interactive=False, scale=1)
313
+ gr.Markdown("---")
314
+ radio_transformation = gr.Radio(
315
+ choices=[
316
+ "None",
317
+ "Laplacian High-Pass",
318
+ "FFT Spectrum",
319
+ "Error Level Analysis",
320
+ "Wavelet Decomposition",
321
+ "Noise Extraction",
322
+ "YCbCr Channels",
323
+ "Gradient Magnitude",
324
+ "Histogram Stretching"
325
+ ],
326
+ value="None",
327
+ label="Frame Transformation",
328
+ info="Apply analysis filters to the current frame"
329
+ )
330
+
331
+ # with gr.Row():
332
+ # with gr.Column():
333
+ # frame_info = gr.Textbox(label="Frame Info", value="No video loaded", interactive=False, scale=2)
334
+ # video_time_display = gr.Textbox(label="Video Time", value="--:--", interactive=False, scale=1)
335
+
336
+ # Row: Frame navigation
337
+ with gr.Row():
338
+
339
+ gr.Markdown("---")
340
+
341
+ with gr.Row():
342
+ btn_prev10_frame = gr.Button("◀◀ -10", scale=0, min_width=70)
343
+ btn_prev_frame = gr.Button("◀ -1", scale=0, min_width=70)
344
+ frame_slider = gr.Slider(
345
+ minimum=0,
346
+ maximum=100,
347
+ step=1,
348
+ value=0,
349
+ label="Frame Navigation",
350
+ interactive=True,
351
+ scale=20
352
+ )
353
+ btn_next_frame = gr.Button("▶ +1", scale=0, min_width=70)
354
+ btn_next10_frame = gr.Button("▶▶ +10", scale=0, min_width=70)
355
+
356
+ with gr.Row():
357
+ gr.Markdown("---")
358
+
359
+
360
+ # Video Upload
361
+ video_input.change(
362
+ fn=load_video_frames,
363
+ inputs=[video_input],
364
+ outputs=[video_frames, current_frame_idx, frame_slider, frame_info, video_duration, video_fps, frame_annotations, global_annotation]
365
+ ).then(
366
+ fn=lambda idx, frames, fps, annots, glob_annot, mode, trans: update_frame_display(idx, frames, fps, annots, glob_annot, mode, trans, process_image),
367
+ inputs=[current_frame_idx, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation],
368
+ outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
369
+ )
370
+
371
+ # Frame Navigation
372
+ frame_slider.release(
373
+ fn=lambda idx, frames, fps, annots, glob_annot, mode, trans: update_frame_display(idx, frames, fps, annots, glob_annot, mode, trans, process_image),
374
+ inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation],
375
+ outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
376
+ )
377
+
378
+ btn_prev_frame.click(
379
+ fn=lambda idx, frames, fps, annots, glob_annot, mode, trans: go_to_prev_frame(idx, 1, frames, fps, annots, glob_annot, mode, trans, process_image),
380
+ inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation],
381
+ outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
382
+ )
383
+
384
+ btn_next_frame.click(
385
+ fn=lambda idx, frames, fps, annots, glob_annot, mode, trans: go_to_next_frame(idx, 1, frames, fps, annots, glob_annot, mode, trans, process_image),
386
+ inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation],
387
+ outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
388
+ )
389
+
390
+ btn_prev10_frame.click(
391
+ fn=lambda idx, frames, fps, annots, glob_annot, mode, trans: go_to_prev_frame(idx, 10, frames, fps, annots, glob_annot, mode, trans, process_image),
392
+ inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation],
393
+ outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
394
+ )
395
+
396
+ btn_next10_frame.click(
397
+ fn=lambda idx, frames, fps, annots, glob_annot, mode, trans: go_to_next_frame(idx, 10, frames, fps, annots, glob_annot, mode, trans, process_image),
398
+ inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation],
399
+ outputs=[frame_slider, sketch_output, comparison_slider, frame_info, video_time_display]
400
+ )
401
+
402
+ # Sketchpad Change - Saves drawing
403
+ sketch_output.change(
404
+ fn=save_sketch_annotation,
405
+ inputs=[sketch_output, annotation_mode, frame_slider, frame_annotations, global_annotation],
406
+ outputs=[frame_annotations, global_annotation]
407
+ )
408
+
409
+ # Transformation Change
410
+ radio_transformation.change(
411
+ fn=lambda new_trans: new_trans,
412
+ inputs=[radio_transformation],
413
+ outputs=[selected_transformation]
414
+ ).then(
415
+ fn=lambda idx, frames, fps, annots, glob_annot, mode, trans: update_frame_display(idx, frames, fps, annots, glob_annot, mode, trans, process_image),
416
+ inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation],
417
+ outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
418
+ )
419
+
420
+ # Mode Change
421
+ radio_mode.change(
422
+ fn=lambda new_mode: new_mode,
423
+ inputs=[radio_mode],
424
+ outputs=[annotation_mode]
425
+ ).then(
426
+ fn=lambda idx, frames, fps, annots, glob_annot, mode, trans: update_frame_display(idx, frames, fps, annots, glob_annot, mode, trans, process_image),
427
+ inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation],
428
+ outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
429
+ )
430
+
431
+ # Clear Annotations
432
+ btn_clear_annotations.click(
433
+ fn=clear_annotations,
434
+ inputs=[annotation_mode, frame_annotations, global_annotation],
435
+ outputs=[frame_annotations, global_annotation]
436
+ ).then(
437
+ fn=lambda idx, frames, fps, annots, glob_annot, mode, trans: update_frame_display(idx, frames, fps, annots, glob_annot, mode, trans, process_image),
438
+ inputs=[frame_slider, video_frames, video_fps, frame_annotations, global_annotation, annotation_mode, selected_transformation],
439
+ outputs=[sketch_output, comparison_slider, frame_info, video_time_display]
440
+ )