flowdetect 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.3
2
+ Name: flowdetect
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Author: Charles Nichols
6
+ Author-email: Charles Nichols <nik@vizionikmedia.com>
7
+ Requires-Dist: numpy>=2.4.2
8
+ Requires-Dist: opencv-contrib-python>=4.13.0.92
9
+ Requires-Python: >=3.14
10
+ Description-Content-Type: text/markdown
11
+
File without changes
@@ -0,0 +1,25 @@
1
+ [project]
2
+ name = "flowdetect"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Charles Nichols", email = "nik@vizionikmedia.com" }
8
+ ]
9
+ requires-python = ">=3.14"
10
+ dependencies = [
11
+ "numpy>=2.4.2",
12
+ "opencv-contrib-python>=4.13.0.92",
13
+ ]
14
+
15
+ [project.scripts]
16
+ flowdetect = "flowdetect:main"
17
+
18
+ [build-system]
19
+ requires = ["uv_build>=0.10.7,<0.11.0"]
20
+ build-backend = "uv_build"
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "click>=8.3.1",
25
+ ]
@@ -0,0 +1,4 @@
1
+ from .main import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
@@ -0,0 +1,173 @@
1
+ """
2
+ Video Analyzer CLI
3
+
4
+ This application provides tools for analyzing video files using Optical Flow techniques.
5
+ It leverages the Click framework to provide a professional, POSIX-compliant interface.
6
+ """
7
+
8
+ import cv2
9
+ import numpy as np
10
+ import click
11
+ import sys
12
+
13
+ @click.group()
14
+ def cli():
15
+ """
16
+ A professional Command-Line Interface for video motion analysis.
17
+
18
+ This tool provides access to both Sparse and Dense Optical Flow algorithms
19
+ for tracking features and detecting high-motion highlights.
20
+ """
21
+ pass
22
+
23
+ @cli.command(name='track-sparse')
24
+ @click.argument('video_path', type=click.Path(exists=True, readable=True, dir_okay=False))
25
+ def track_sparse(video_path):
26
+ """
27
+ Track specific points across video frames.
28
+
29
+ This command uses Lucas Kanade Sparse Optical Flow to find and track
30
+ the strongest corners in the image. It is highly useful for stabilizing
31
+ shaky footage or tracking a specific subject to keep them centered.
32
+ """
33
+ click.echo(f"Starting Sparse Optical Flow on {video_path}...")
34
+ click.echo("Press 'q' or 'ESC' in the video window to quit.")
35
+
36
+ cap = cv2.VideoCapture(video_path)
37
+
38
+ # Parameters for ShiTomasi corner detection
39
+ feature_params = dict(
40
+ maxCorners=100,
41
+ qualityLevel=0.3,
42
+ minDistance=7,
43
+ blockSize=7
44
+ )
45
+
46
+ # Parameters for Lucas Kanade optical flow
47
+ lk_params = dict(
48
+ winSize=(15, 15),
49
+ maxLevel=2,
50
+ criteria=(cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03)
51
+ )
52
+
53
+ # Create random colors for drawing tracks
54
+ color = np.random.randint(0, 255, (100, 3))
55
+
56
+ ret, old_frame = cap.read()
57
+ if not ret:
58
+ click.echo("Error: Could not read the video file.", err=True)
59
+ sys.exit(1)
60
+
61
+ old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)
62
+ p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params)
63
+
64
+ # Create a mask image for drawing purposes
65
+ mask = np.zeros_like(old_frame)
66
+
67
+ while True:
68
+ ret, frame = cap.read()
69
+ if not ret:
70
+ break
71
+
72
+ frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
73
+
74
+ # Calculate optical flow
75
+ p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
76
+
77
+ # Select good points
78
+ if p1 is not None:
79
+ good_new = p1[st == 1]
80
+ good_old = p0[st == 1]
81
+
82
+ # Draw the tracks
83
+ for i, (new, old) in enumerate(zip(good_new, good_old)):
84
+ a, b = new.ravel()
85
+ c, d = old.ravel()
86
+ # Draw line for motion history
87
+ mask = cv2.line(mask, (int(a), int(b)), (int(c), int(d)), color[i].tolist(), 2)
88
+ # Draw dot for current position
89
+ frame = cv2.circle(frame, (int(a), int(b)), 5, color[i].tolist(), -1)
90
+
91
+ img = cv2.add(frame, mask)
92
+ cv2.imshow('Sparse Optical Flow Tracker', img)
93
+
94
+ k = cv2.waitKey(30) & 0xff
95
+ if k == 27 or k == ord('q'):
96
+ break
97
+
98
+ # Update the previous frame and previous points
99
+ old_gray = frame_gray.copy()
100
+ if p1 is not None:
101
+ p0 = good_new.reshape(-1, 1, 2)
102
+
103
+ cv2.destroyAllWindows()
104
+ cap.release()
105
+ click.echo("Sparse tracking completed.")
106
+
107
+
108
+ @cli.command(name='detect-dense')
109
+ @click.argument('video_path', type=click.Path(exists=True, readable=True, dir_okay=False))
110
+ @click.option('--threshold', '-t', default=5.0, type=click.FloatRange(min=0.0), help='Motion intensity threshold required to flag a highlight.')
111
+ def detect_dense(video_path, threshold):
112
+ """
113
+ Analyze video for global energy highlights.
114
+
115
+ This command calculates motion for every pixel using Farneback Dense Optical Flow.
116
+ It is computationally heavier but allows the system to detect global energy changes
117
+ such as applause, laughter, or rapid movement.
118
+ """
119
+ click.echo(f"Analyzing {video_path} for high motion events (Threshold: {threshold})...")
120
+ click.echo("Press 'q' or 'ESC' in the video window to quit.")
121
+
122
+ cap = cv2.VideoCapture(video_path)
123
+
124
+ ret, frame1 = cap.read()
125
+ if not ret:
126
+ click.echo("Error: Could not read the video file.", err=True)
127
+ sys.exit(1)
128
+
129
+ prvs = cv2.cvtColor(frame1, cv2.COLOR_BGR2GRAY)
130
+ hsv = np.zeros_like(frame1)
131
+ hsv[..., 1] = 255
132
+
133
+ while True:
134
+ ret, frame2 = cap.read()
135
+ if not ret:
136
+ break
137
+
138
+ next_frame = cv2.cvtColor(frame2, cv2.COLOR_BGR2GRAY)
139
+
140
+ # Calculate Dense Optical Flow
141
+ flow = cv2.calcOpticalFlowFarneback(prvs, next_frame, None, 0.5, 3, 15, 3, 5, 1.2, 0)
142
+
143
+ # Convert Cartesian coordinates to Polar coordinates
144
+ mag, ang = cv2.cartToPolar(flow[..., 0], flow[..., 1])
145
+
146
+ # Visualization
147
+ hsv[..., 0] = ang * 180 / np.pi / 2
148
+ hsv[..., 2] = cv2.normalize(mag, None, 0, 255, cv2.NORM_MINMAX)
149
+ bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
150
+
151
+ # Calculate the mean motion intensity of the frame
152
+ avg_motion = np.mean(mag)
153
+ timestamp = cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0
154
+
155
+ if avg_motion > threshold:
156
+ click.echo(f"Highlight Detected at {timestamp:.2f} seconds (Intensity: {avg_motion:.2f})")
157
+ # Draw a red border to visualize the detection
158
+ cv2.rectangle(bgr, (0, 0), (bgr.shape[1], bgr.shape[0]), (0, 0, 255), 10)
159
+
160
+ cv2.imshow('Dense Optical Flow Highlight Detector', bgr)
161
+
162
+ k = cv2.waitKey(30) & 0xff
163
+ if k == 27 or k == ord('q'):
164
+ break
165
+
166
+ prvs = next_frame
167
+
168
+ cap.release()
169
+ cv2.destroyAllWindows()
170
+ click.echo("Highlight detection completed.")
171
+
172
+ if __name__ == "__main__":
173
+ cli()