Skip to content

Commit 87ee4ed

Browse files
committed
Fix review issues: safe cv2.cuda guard, use_gpu defaults to False, move imports to module level, add blur_faces and pose GPU tests
1 parent 27a32c5 commit 87ee4ed

4 files changed

Lines changed: 39 additions & 12 deletions

File tree

musicalgestures/_blurfaces.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def mg_blurfaces(self,
8080
save_data=True,
8181
data_format='csv',
8282
color=(0, 0, 0),
83-
use_gpu=True,
83+
use_gpu=False,
8484
target_name=None,
8585
overwrite=False):
8686
"""
@@ -102,7 +102,7 @@ def mg_blurfaces(self,
102102
save_data (bool, optional): Whether to save the scaled coordinates of the face mask (time (ms), x1, y1, x2, y2) for each frame to a file. Defaults to True.
103103
data_format (str, optional): Specifies format of blur_faces-data. Accepted values are 'csv', 'tsv' and 'txt'. For multiple output formats, use list, e.g. ['csv', 'txt']. Defaults to 'csv'.
104104
color (tuple, optional): Customized color of the rectangle boxes. Defaults to black (0, 0, 0).
105-
use_gpu (bool, optional): Whether to attempt GPU (CUDA) acceleration for face detection. Falls back to CPU automatically if CUDA is unavailable. Defaults to True.
105+
use_gpu (bool, optional): Whether to attempt GPU (CUDA) acceleration for face detection. Falls back to CPU automatically if CUDA is unavailable. Defaults to False.
106106
target_name (str, optional): Target output name. Defaults to None (which assumes that the input filename with the suffix "_blurred" should be used).
107107
overwrite (bool, optional): Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to False.
108108

musicalgestures/_flow.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from scipy.stats import entropy
88

99
import musicalgestures
10-
from musicalgestures._utils import MgFigure, extract_wav, embed_audio_in_video, MgProgressbar, convert_to_avi, generate_outfilename, ffmpeg_cmd
10+
from musicalgestures._utils import MgFigure, extract_wav, embed_audio_in_video, MgProgressbar, convert_to_avi, generate_outfilename, ffmpeg_cmd, get_cuda_device_count
1111

1212

1313
class Flow:
@@ -47,7 +47,7 @@ def dense(
4747
angle_of_view=0,
4848
scaledown=1,
4949
skip_empty=False,
50-
use_gpu=True,
50+
use_gpu=False,
5151
target_name=None,
5252
overwrite=False):
5353
"""
@@ -69,7 +69,7 @@ def dense(
6969
angle_of_view (int, optional): angle of view of camera, for reporting flow in meters per second. Defaults to 0.
7070
scaledown (int, optional): factor to scaledown frame size of the video. Defaults to 1.
7171
skip_empty (bool, optional): If True, repeats previous frame in the output when encounters an empty frame. Defaults to False.
72-
use_gpu (bool, optional): Whether to attempt GPU (CUDA) acceleration using `cv2.cuda.FarnebackOpticalFlow`. When `True`, falls back to CPU automatically if CUDA is unavailable or the required OpenCV CUDA modules are not installed. When `False`, CPU processing is used unconditionally. Defaults to True.
72+
use_gpu (bool, optional): Whether to attempt GPU (CUDA) acceleration using `cv2.cuda.FarnebackOpticalFlow`. When `True`, falls back to CPU automatically if CUDA is unavailable or the required OpenCV CUDA modules are not installed. When `False`, CPU processing is used unconditionally. Defaults to False.
7373
target_name (str, optional): Target output name for the video. Defaults to None (which assumes that the input filename with the suffix "_flow_dense" should be used).
7474
overwrite (bool, optional): Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to False.
7575
@@ -103,11 +103,10 @@ def dense(
103103
size = (int(width/scaledown), int(height/scaledown))
104104

105105
# Determine whether to use GPU-accelerated Farneback optical flow
106-
from musicalgestures._utils import get_cuda_device_count
107106
_use_gpu = False
108107
farneback_gpu = None
109108
if use_gpu:
110-
if not hasattr(cv2.cuda, 'FarnebackOpticalFlow'):
109+
if not hasattr(cv2, 'cuda') or not hasattr(cv2.cuda, 'FarnebackOpticalFlow'):
111110
print('cv2.cuda.FarnebackOpticalFlow is unavailable (requires opencv-contrib built with CUDA). Switching to CPU for dense optical flow.')
112111
elif get_cuda_device_count() <= 0:
113112
print('OpenCV CUDA backend is unavailable. Switching to CPU for dense optical flow.')
@@ -322,7 +321,7 @@ def sparse(
322321
of_max_level=2,
323322
of_criteria=(cv2.TERM_CRITERIA_EPS |
324323
cv2.TERM_CRITERIA_COUNT, 10, 0.03),
325-
use_gpu=True,
324+
use_gpu=False,
326325
target_name=None,
327326
overwrite=False):
328327
"""
@@ -337,7 +336,7 @@ def sparse(
337336
of_win_size (tuple, optional): Size of the search window at each pyramid level. Defaults to (15, 15).
338337
of_max_level (int, optional): 0-based maximal pyramid level number. If set to 0, pyramids are not used (single level), if set to 1, two levels are used, and so on. If pyramids are passed to input then the algorithm will use as many levels as pyramids have but no more than `maxLevel`. Defaults to 2.
339338
of_criteria (tuple, optional): Specifies the termination criteria of the iterative search algorithm (after the specified maximum number of iterations criteria.maxCount or when the search window moves by less than criteria.epsilon). Defaults to (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03).
340-
use_gpu (bool, optional): Whether to attempt GPU (CUDA) acceleration using `cv2.cuda.SparsePyrLKOpticalFlow`. When `True`, falls back to CPU automatically if CUDA is unavailable or the required OpenCV CUDA modules are not installed. When `False`, CPU processing is used unconditionally. Defaults to True.
339+
use_gpu (bool, optional): Whether to attempt GPU (CUDA) acceleration using `cv2.cuda.SparsePyrLKOpticalFlow`. When `True`, falls back to CPU automatically if CUDA is unavailable or the required OpenCV CUDA modules are not installed. When `False`, CPU processing is used unconditionally. Defaults to False.
341340
target_name (str, optional): Target output name for the video. Defaults to None (which assumes that the input filename with the suffix "_flow_sparse" should be used).
342341
overwrite (bool, optional): Whether to allow overwriting existing files or to automatically increment target filenames to avoid overwriting. Defaults to False.
343342
@@ -370,11 +369,10 @@ def sparse(
370369
length = int(vidcap.get(cv2.CAP_PROP_FRAME_COUNT))
371370

372371
# Determine whether to use GPU-accelerated sparse optical flow
373-
from musicalgestures._utils import get_cuda_device_count
374372
_use_gpu = False
375373
lk_gpu = None
376374
if use_gpu:
377-
if not hasattr(cv2.cuda, 'SparsePyrLKOpticalFlow'):
375+
if not hasattr(cv2, 'cuda') or not hasattr(cv2.cuda, 'SparsePyrLKOpticalFlow'):
378376
print('cv2.cuda.SparsePyrLKOpticalFlow is unavailable (requires opencv-contrib built with CUDA). Switching to CPU for sparse optical flow.')
379377
elif get_cuda_device_count() <= 0:
380378
print('OpenCV CUDA backend is unavailable. Switching to CPU for sparse optical flow.')

musicalgestures/_utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1624,7 +1624,6 @@ def get_cuda_device_count():
16241624
int: Number of available CUDA devices, or 0 if the OpenCV CUDA
16251625
module is unavailable or no devices are detected.
16261626
"""
1627-
import cv2
16281627
try:
16291628
return cv2.cuda.getCudaEnabledDeviceCount()
16301629
except Exception:

tests/test_flow.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,33 @@ def test_returns_int(self):
113113
result = musicalgestures.get_cuda_device_count()
114114
assert isinstance(result, int)
115115
assert result >= 0
116+
117+
118+
class Test_blur_faces_gpu:
119+
def test_use_gpu_false(self, testvideo_avi):
120+
mg = musicalgestures.MgVideo(testvideo_avi)
121+
result = mg.blur_faces(use_gpu=False, overwrite=True)
122+
assert type(result) == musicalgestures.MgVideo
123+
assert os.path.isfile(result.filename) == True
124+
125+
def test_use_gpu_true(self, testvideo_avi):
126+
# use_gpu=True should work (falls back to CPU when CUDA is unavailable)
127+
mg = musicalgestures.MgVideo(testvideo_avi)
128+
result = mg.blur_faces(use_gpu=True, overwrite=True)
129+
assert type(result) == musicalgestures.MgVideo
130+
assert os.path.isfile(result.filename) == True
131+
132+
133+
class Test_pose_gpu:
134+
def test_device_cpu(self, testvideo_avi):
135+
mg = musicalgestures.MgVideo(testvideo_avi)
136+
result = mg.pose(device='cpu', overwrite=True)
137+
assert type(result) == musicalgestures.MgVideo
138+
assert os.path.isfile(result.filename) == True
139+
140+
def test_device_gpu_fallback(self, testvideo_avi):
141+
# device='gpu' should fall back to CPU when CUDA is unavailable
142+
mg = musicalgestures.MgVideo(testvideo_avi)
143+
result = mg.pose(device='gpu', overwrite=True)
144+
assert type(result) == musicalgestures.MgVideo
145+
assert os.path.isfile(result.filename) == True

0 commit comments

Comments
 (0)