Skip to content

Commit a4858c4

Browse files
committed
Add doctests to motion detection functions
Doctests were added to all major functions in motion_detection.py to provide usage examples and enable easier testing. This improves code reliability and documentation by demonstrating expected input and output for each function.
1 parent fe2a9c9 commit a4858c4

1 file changed

Lines changed: 51 additions & 0 deletions

File tree

computer_vision/motion_detection.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
def create_background_subtractor() -> cv2.BackgroundSubtractor:
2626
"""
2727
Create and return a MOG2 background subtractor with sensible defaults.
28+
29+
Doctest:
30+
>>> subtractor = create_background_subtractor()
31+
>>> hasattr(subtractor, "apply")
32+
True
2833
"""
2934
# history=500, varThreshold=16 are common defaults; detectShadows adds robustness
3035
return cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=True)
@@ -33,6 +38,12 @@ def create_background_subtractor() -> cv2.BackgroundSubtractor:
3338
def preprocess_frame(frame: cv2.Mat) -> cv2.Mat:
3439
"""
3540
Convert to grayscale and apply Gaussian blur to suppress noise.
41+
42+
Doctest:
43+
>>> dummy = np.zeros((10, 10, 3), dtype=np.uint8)
44+
>>> out = preprocess_frame(dummy)
45+
>>> out.shape == (10, 10) and out.dtype == np.uint8
46+
True
3647
"""
3748
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
3849
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
@@ -43,6 +54,16 @@ def frame_difference(prev_gray: cv2.Mat, curr_gray: cv2.Mat) -> cv2.Mat:
4354
"""
4455
Compute absolute difference between consecutive grayscale frames.
4556
Returns a binary motion mask after thresholding and morphology.
57+
58+
Doctest:
59+
>>> a = np.zeros((8, 8), dtype=np.uint8)
60+
>>> b = np.zeros((8, 8), dtype=np.uint8)
61+
>>> b[2:6, 2:6] = 255
62+
>>> mask = frame_difference(a, b)
63+
>>> mask.shape
64+
(8, 8)
65+
>>> mask.dtype == np.uint8
66+
True
4667
"""
4768
diff = cv2.absdiff(prev_gray, curr_gray)
4869
_, thresh = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)
@@ -55,6 +76,15 @@ def frame_difference(prev_gray: cv2.Mat, curr_gray: cv2.Mat) -> cv2.Mat:
5576
def background_subtraction_mask(subtractor: cv2.BackgroundSubtractor, frame: cv2.Mat) -> cv2.Mat:
5677
"""
5778
Apply background subtraction to obtain a motion mask. Includes morphology.
79+
80+
Doctest:
81+
>>> subtractor = create_background_subtractor()
82+
>>> frame = np.zeros((12, 12, 3), dtype=np.uint8)
83+
>>> mask = background_subtraction_mask(subtractor, frame)
84+
>>> mask.shape
85+
(12, 12)
86+
>>> mask.dtype == np.uint8
87+
True
5888
"""
5989
fg_mask = subtractor.apply(frame)
6090
# Remove shadows if present (MOG2 shadows are typically 127)
@@ -68,6 +98,14 @@ def background_subtraction_mask(subtractor: cv2.BackgroundSubtractor, frame: cv2
6898
def annotate_motion(frame: cv2.Mat, motion_mask: cv2.Mat) -> cv2.Mat:
6999
"""
70100
Find contours on the motion mask and draw bounding boxes on the frame.
101+
102+
Doctest:
103+
>>> frame = np.zeros((60, 60, 3), dtype=np.uint8)
104+
>>> mask = np.zeros((60, 60), dtype=np.uint8)
105+
>>> mask[10:40, 10:40] = 255 # large enough to exceed MIN_CONTOUR_AREA
106+
>>> annotated = annotate_motion(frame, mask)
107+
>>> np.any(annotated[..., 1] == 255) # green channel from rectangle
108+
True
71109
"""
72110
contours, _ = cv2.findContours(motion_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
73111
annotated = frame.copy()
@@ -80,6 +118,19 @@ def annotate_motion(frame: cv2.Mat, motion_mask: cv2.Mat) -> cv2.Mat:
80118

81119

82120
def main() -> None:
121+
"""
122+
Run motion detection loop for the configured VIDEO_SOURCE.
123+
124+
Doctest (expect a RuntimeError when pointing to an invalid source):
125+
>>> _prev = VIDEO_SOURCE
126+
>>> VIDEO_SOURCE = "nonexistent_file_does_not_exist.mp4"
127+
>>> try:
128+
... main()
129+
... except RuntimeError as e:
130+
... isinstance(e, RuntimeError)
131+
True
132+
>>> VIDEO_SOURCE = _prev
133+
"""
83134
cap = cv2.VideoCapture(VIDEO_SOURCE)
84135
if not cap.isOpened():
85136
raise RuntimeError("Unable to open video source. Set VIDEO_SOURCE correctly.")

0 commit comments

Comments
 (0)