feat: improve CLI UX with smart defaults and auto-output generation
- Add default yrow=8 when no mode specified - Make --output optional, auto-generate to results/ folder - Add 4-char UUID and threshold to auto-generated filenames - Auto-append .jpg extension when no extension provided - Rotate row mode output 90° clockwise for proper orientation - Move debug mode outputs to results/ folder - Add uuid module for unique filename generation Example outputs: - results/video_a3f2_t0_01.jpg (auto-generated) - results/video_7c91_t0_05_changes.png (debug mode)
This commit is contained in:
parent
b0be7d9d6e
commit
8da27a1ead
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@
|
|||||||
*.mp4
|
*.mp4
|
||||||
*.avi
|
*.avi
|
||||||
.venv/
|
.venv/
|
||||||
|
results/
|
||||||
#demos
|
#demos
|
||||||
!demo_changes.png
|
!demo_changes.png
|
||||||
!demo.jpg
|
!demo.jpg
|
||||||
|
|||||||
43
main.py
43
main.py
@ -12,6 +12,7 @@ import cv2
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
def calculate_line_difference(line1, line2):
|
def calculate_line_difference(line1, line2):
|
||||||
@ -132,8 +133,8 @@ def analyze_changes_only(video_path, x_column=None, y_row=None, debug_output=Non
|
|||||||
cap.release()
|
cap.release()
|
||||||
|
|
||||||
if debug_output:
|
if debug_output:
|
||||||
# Generate change graph
|
# Generate change graph (debug_output is now a Path object)
|
||||||
graph_path = f"{debug_output}_changes.png"
|
graph_path = debug_output.parent / f"{debug_output.stem}_changes.png"
|
||||||
generate_change_graph(changes, graph_path)
|
generate_change_graph(changes, graph_path)
|
||||||
|
|
||||||
# Generate statistics
|
# Generate statistics
|
||||||
@ -157,7 +158,7 @@ def analyze_changes_only(video_path, x_column=None, y_row=None, debug_output=Non
|
|||||||
return changes
|
return changes
|
||||||
|
|
||||||
|
|
||||||
def extract_column_strip(video_path, x_column, output_path, change_threshold=0.01):
|
def extract_column_strip(video_path, x_column, output_path, change_threshold=0.005):
|
||||||
"""
|
"""
|
||||||
Extract vertical strip at x_column from each frame of the video.
|
Extract vertical strip at x_column from each frame of the video.
|
||||||
Only include frames where the change exceeds the threshold.
|
Only include frames where the change exceeds the threshold.
|
||||||
@ -310,11 +311,14 @@ def extract_row_strip(video_path, y_row, output_path, change_threshold=0.01):
|
|||||||
# Convert list to numpy array
|
# Convert list to numpy array
|
||||||
strip_image = np.stack(significant_rows, axis=0)
|
strip_image = np.stack(significant_rows, axis=0)
|
||||||
|
|
||||||
|
# Rotate clockwise 90 degrees for row mode
|
||||||
|
strip_image = cv2.rotate(strip_image, cv2.ROTATE_90_CLOCKWISE)
|
||||||
|
|
||||||
print(f"Original frames: {total_frames}")
|
print(f"Original frames: {total_frames}")
|
||||||
print(f"Included frames: {included_frames}")
|
print(f"Included frames: {included_frames}")
|
||||||
print(f"Skipped frames: {skipped_frames}")
|
print(f"Skipped frames: {skipped_frames}")
|
||||||
print(f"Compression ratio: {skipped_frames/total_frames:.1%}")
|
print(f"Compression ratio: {skipped_frames/total_frames:.1%}")
|
||||||
print(f"Output dimensions: {strip_image.shape}")
|
print(f"Output dimensions: {strip_image.shape} (rotated 90° CW)")
|
||||||
print(f"Saving to: {output_path}")
|
print(f"Saving to: {output_path}")
|
||||||
|
|
||||||
# Save the strip image
|
# Save the strip image
|
||||||
@ -341,13 +345,12 @@ def main():
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--yrow",
|
"--yrow",
|
||||||
type=int,
|
type=int,
|
||||||
help="Extract horizontal line at y-coordinate (row mode)"
|
help="Extract horizontal line at y-coordinate (row mode, default: 8)"
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--output",
|
"--output",
|
||||||
required=True,
|
help="Output image file path (default: results/<input_name>.jpg)"
|
||||||
help="Output image file path"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@ -376,9 +379,10 @@ def main():
|
|||||||
print("Error: Cannot specify both --xcolumn and --yrow. Choose one mode.")
|
print("Error: Cannot specify both --xcolumn and --yrow. Choose one mode.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Default to yrow=8 if neither mode specified
|
||||||
if args.xcolumn is None and args.yrow is None:
|
if args.xcolumn is None and args.yrow is None:
|
||||||
print("Error: Must specify either --xcolumn or --yrow.")
|
args.yrow = 8
|
||||||
sys.exit(1)
|
print(f"Using default: --yrow={args.yrow}")
|
||||||
|
|
||||||
# Validate coordinates
|
# Validate coordinates
|
||||||
if args.xcolumn is not None and args.xcolumn < 0:
|
if args.xcolumn is not None and args.xcolumn < 0:
|
||||||
@ -394,7 +398,24 @@ def main():
|
|||||||
print("Error: --threshold must be between 0 and 1")
|
print("Error: --threshold must be between 0 and 1")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Generate output path
|
||||||
|
if args.output:
|
||||||
output_path = Path(args.output)
|
output_path = Path(args.output)
|
||||||
|
# Add .jpg extension if no extension provided
|
||||||
|
if not output_path.suffix:
|
||||||
|
output_path = output_path.with_suffix('.jpg')
|
||||||
|
print(f"No extension specified, using: {output_path}")
|
||||||
|
else:
|
||||||
|
# Auto-generate output path in results folder with UUID
|
||||||
|
results_dir = Path("results")
|
||||||
|
results_dir.mkdir(exist_ok=True)
|
||||||
|
# Generate 4-character UUID prefix
|
||||||
|
uuid_prefix = uuid.uuid4().hex[:4]
|
||||||
|
# Include threshold in filename
|
||||||
|
threshold_str = f"t{args.threshold}".replace(".", "_")
|
||||||
|
output_filename = f"{video_path.stem}_{uuid_prefix}_{threshold_str}.jpg"
|
||||||
|
output_path = results_dir / output_filename
|
||||||
|
print(f"No output specified, using: {output_path}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if args.debug:
|
if args.debug:
|
||||||
@ -403,10 +424,10 @@ def main():
|
|||||||
|
|
||||||
if args.xcolumn is not None:
|
if args.xcolumn is not None:
|
||||||
print(f"Column mode: Analyzing vertical line at x={args.xcolumn}")
|
print(f"Column mode: Analyzing vertical line at x={args.xcolumn}")
|
||||||
analyze_changes_only(video_path, x_column=args.xcolumn, debug_output=output_path.stem)
|
analyze_changes_only(video_path, x_column=args.xcolumn, debug_output=output_path)
|
||||||
else:
|
else:
|
||||||
print(f"Row mode: Analyzing horizontal line at y={args.yrow}")
|
print(f"Row mode: Analyzing horizontal line at y={args.yrow}")
|
||||||
analyze_changes_only(video_path, y_row=args.yrow, debug_output=output_path.stem)
|
analyze_changes_only(video_path, y_row=args.yrow, debug_output=output_path)
|
||||||
|
|
||||||
print("Change analysis completed successfully!")
|
print("Change analysis completed successfully!")
|
||||||
else:
|
else:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user