From 93a65c3ccf6dee1bf22eabd40ce0986f493d7937 Mon Sep 17 00:00:00 2001 From: goatzilla Date: Tue, 9 Jul 2024 19:33:46 -0500 Subject: [PATCH 1/8] Add property to pull position_ms (presentation time) from input stream. --- dvr_scan/video_joiner.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dvr_scan/video_joiner.py b/dvr_scan/video_joiner.py index 6e2c5f7..e30728a 100644 --- a/dvr_scan/video_joiner.py +++ b/dvr_scan/video_joiner.py @@ -84,6 +84,10 @@ def position(self) -> FrameTimecode: """Current position of the video including presentation time of the current frame.""" return self._position + 1 + @property + def position_ms(self) -> float: + return self._cap.position_ms + def read(self, decode: bool = True) -> Optional[numpy.ndarray]: """Read/decode the next frame.""" next = self._cap.read(decode=decode) From 649cb3ae927855efcbc9db8809bc3e1d78b906f6 Mon Sep 17 00:00:00 2001 From: goatzilla Date: Tue, 9 Jul 2024 19:34:55 -0500 Subject: [PATCH 2/8] Add use-pts option to directly use timestamps for event times. The previous default behavior of calculating time from frame numbers should be preserved. The switch kind of smears alternate timekeeping through the scanner to fetch position_ms from the video source and stuff it into MotionEvent so the rest of the code should still behave the same. Might need more testing; was unsure about some of the frame_skip bits. Fixes Breakthrough/DVR-Scan#168 --- dvr_scan/cli/__init__.py | 7 ++++ dvr_scan/cli/config.py | 1 + dvr_scan/cli/controller.py | 1 + dvr_scan/scanner.py | 66 ++++++++++++++++++++++++++++++-------- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/dvr_scan/cli/__init__.py b/dvr_scan/cli/__init__.py index fc89bc4..376cda8 100644 --- a/dvr_scan/cli/__init__.py +++ b/dvr_scan/cli/__init__.py @@ -648,6 +648,13 @@ def get_cli_parser(user_config: ConfigRegistry): (', '.join(CHOICE_MAP['verbosity']), user_config.get_help_string('verbosity'))), ) + parser.add_argument( + '--use-pts', + action='store_true', + default=False, + help=('Use OpenCV provided presentation timestamp instead of calculated version.'), + ) + parser.add_argument( '--debug', action='store_true', diff --git a/dvr_scan/cli/config.py b/dvr_scan/cli/config.py index d4d6b6b..c25255d 100644 --- a/dvr_scan/cli/config.py +++ b/dvr_scan/cli/config.py @@ -315,6 +315,7 @@ def from_config(config_value: str, default: 'RGBValue') -> 'RGBValue': 'min-event-length': TimecodeValue('0.1s'), 'time-before-event': TimecodeValue('1.5s'), 'time-post-event': TimecodeValue('2.0s'), + 'use-pts' : False, # Detection Parameters 'bg-subtractor': 'MOG2', diff --git a/dvr_scan/cli/controller.py b/dvr_scan/cli/controller.py index 80aee22..54549d5 100644 --- a/dvr_scan/cli/controller.py +++ b/dvr_scan/cli/controller.py @@ -282,6 +282,7 @@ def run_dvr_scan(settings: ProgramSettings) -> ty.List[ty.Tuple[FrameTimecode, F min_event_len=settings.get('min-event-length'), time_pre_event=settings.get('time-before-event'), time_post_event=settings.get('time-post-event'), + use_pts=settings.get('use-pts'), ) scanner.set_video_time( diff --git a/dvr_scan/scanner.py b/dvr_scan/scanner.py index b8a236e..85f1590 100644 --- a/dvr_scan/scanner.py +++ b/dvr_scan/scanner.py @@ -245,6 +245,7 @@ def __init__(self, self._min_event_len = None # -l/--min-event-length self._pre_event_len = None # -tb/--time-before-event self._post_event_len = None # -tp/--time-post-event + self._use_pts = None # --use_pts # Region Parameters (set_region) self._region_editor = False # -w/--region-window @@ -426,12 +427,14 @@ def set_regions(self, def set_event_params(self, min_event_len: Union[int, float, str] = '0.1s', time_pre_event: Union[int, float, str] = '1.5s', - time_post_event: Union[int, float, str] = '2s'): + time_post_event: Union[int, float, str] = '2s', + use_pts: bool = False): """Set motion event parameters.""" assert self._input.framerate is not None self._min_event_len = FrameTimecode(min_event_len, self._input.framerate) self._pre_event_len = FrameTimecode(time_pre_event, self._input.framerate) self._post_event_len = FrameTimecode(time_post_event, self._input.framerate) + self._use_pts = use_pts def set_video_time(self, start_time: Optional[Union[int, float, str]] = None, @@ -559,7 +562,11 @@ def scan(self) -> Optional[DetectionResult]: # Seek to starting position if required. if self._start_time is not None: self._input.seek(self._start_time) - start_frame = self._input.position.frame_num + + if not self._use_pts: + start_frame = self._input.position.frame_num + else: + start_frame_ms = self._input.position_ms # Show ROI selection window if required. if not self._handle_regions(): @@ -607,13 +614,20 @@ def scan(self) -> Optional[DetectionResult]: # shifting the event start time. Instead of using `-l`/`--min-event-len` directly, we # need to compensate for rounding errors when we corrected it for frame skip. This is # important as this affects the number of frames we consider for the actual motion event. - start_event_shift: int = ( - self._pre_event_len.frame_num + min_event_len * (self._frame_skip + 1)) + if not self._use_pts: + start_event_shift: int = ( + self._pre_event_len.frame_num + min_event_len * (self._frame_skip + 1)) + else: + start_event_shift_ms: float = ( + (self._pre_event_len.get_seconds() + self._min_event_len.get_seconds()) * 1000) # Length of buffer we require in memory to keep track of all frames required for -l and -tb. buff_len = pre_event_len + min_event_len event_end = self._input.position - last_frame_above_threshold = 0 + if not self._use_pts: + last_frame_above_threshold = 0 + else: + last_frame_above_threshold_ms = 0 if self._bounding_box: self._bounding_box.set_corrections( @@ -696,7 +710,10 @@ def scan(self) -> Optional[DetectionResult]: # If this frame still has motion, reset the post-event window. if above_threshold: num_frames_post_event = 0 - last_frame_above_threshold = frame.timecode.frame_num + if not self._use_pts: + last_frame_above_threshold = frame.timecode.frame_num + else: + last_frame_above_threshold_ms = self._input.position_ms # Otherwise, we wait until the post-event window has passed before ending # this motion event and start looking for a new one. # @@ -710,10 +727,17 @@ def scan(self) -> Optional[DetectionResult]: # the post event length time. We also need to compensate for the number # of frames that we skipped that could have had motion. # We also add 1 to include the presentation duration of the last frame. - event_end = FrameTimecode( - 1 + last_frame_above_threshold + self._post_event_len.frame_num + - self._frame_skip, self._input.framerate) - assert event_end.frame_num >= event_start.frame_num + if not self._use_pts: + event_end = FrameTimecode( + 1 + last_frame_above_threshold + self._post_event_len.frame_num + + self._frame_skip, self._input.framerate) + assert event_end.frame_num >= event_start.frame_num + else: + event_end = FrameTimecode( + (last_frame_above_threshold_ms / 1000) + + self._post_event_len.get_seconds(), self._input.framerate) + #print("event end %f start %f" % (event_end.get_seconds(), event_start.get_seconds())) + assert event_end.get_seconds() >= event_start.get_seconds() event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: encode_queue.put(MotionEvent(start=event_start, end=event_end)) @@ -749,9 +773,18 @@ def scan(self) -> Optional[DetectionResult]: num_frames_post_event = 0 frames_since_last_event = frame.timecode.frame_num - event_end.frame_num last_frame_above_threshold = frame.timecode.frame_num - shift_amount = min(frames_since_last_event, start_event_shift) - shifted_start = max(start_frame, frame.timecode.frame_num + 1 - shift_amount) - event_start = FrameTimecode(shifted_start, self._input.framerate) + + if not self._use_pts: + shift_amount = min(frames_since_last_event, start_event_shift) + shifted_start = max(start_frame, frame.timecode.frame_num + 1 - shift_amount) + event_start = FrameTimecode(shifted_start, self._input.framerate) + else: + ms_since_last_event = self._input.position_ms - (event_end.get_seconds() * 1000) + last_frame_above_threshold_ms = self._input.position_ms + # TODO: not sure all of this is actually necessary? + shift_amount_ms = min(ms_since_last_event, start_event_shift_ms) + shifted_start_ms = max(start_frame_ms, self._input.position_ms - shift_amount_ms) + event_start = FrameTimecode(shifted_start_ms / 1000, self._input.framerate) # Send buffered frames to encode thread. for encode_frame in buffered_frames: # We have to be careful here. Since we're putting multiple items @@ -780,7 +813,12 @@ def scan(self) -> Optional[DetectionResult]: # compute the duration and ending timecode and add it to the event list. if in_motion_event and not self._stop.is_set(): # curr_pos already includes the presentation duration of the frame. - event_end = FrameTimecode(self._input.position.frame_num, self._input.framerate) + if not self._use_pts: + event_end = FrameTimecode(self._input.position.frame_num, self._input.framerate) + else: + event_end = FrameTimecode( + (self._input.position_ms / 1000) + self._post_event_len.get_seconds(), + self._input.framerate) event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: encode_queue.put(MotionEvent(start=event_start, end=event_end)) From 99cf72358144762ecd2d3c9ab166bab6009bc029 Mon Sep 17 00:00:00 2001 From: goatzilla Date: Tue, 9 Jul 2024 21:48:48 -0500 Subject: [PATCH 3/8] Fix end-of-stream problem when using opencv timestamps. position_ms seems to return 0 after reading past the end of a stream. --- dvr_scan/scanner.py | 3 +-- dvr_scan/video_joiner.py | 9 +++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/dvr_scan/scanner.py b/dvr_scan/scanner.py index 85f1590..aad8408 100644 --- a/dvr_scan/scanner.py +++ b/dvr_scan/scanner.py @@ -736,7 +736,6 @@ def scan(self) -> Optional[DetectionResult]: event_end = FrameTimecode( (last_frame_above_threshold_ms / 1000) + self._post_event_len.get_seconds(), self._input.framerate) - #print("event end %f start %f" % (event_end.get_seconds(), event_start.get_seconds())) assert event_end.get_seconds() >= event_start.get_seconds() event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: @@ -817,7 +816,7 @@ def scan(self) -> Optional[DetectionResult]: event_end = FrameTimecode(self._input.position.frame_num, self._input.framerate) else: event_end = FrameTimecode( - (self._input.position_ms / 1000) + self._post_event_len.get_seconds(), + (self._input.last_position_ms / 1000) + self._post_event_len.get_seconds(), self._input.framerate) event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: diff --git a/dvr_scan/video_joiner.py b/dvr_scan/video_joiner.py index e30728a..f48fd28 100644 --- a/dvr_scan/video_joiner.py +++ b/dvr_scan/video_joiner.py @@ -53,6 +53,7 @@ def __init__(self, paths: Union[AnyStr, List[AnyStr]]): # Initialize position now that the framerate is valid. self._position: FrameTimecode = FrameTimecode(0, self.framerate) self._last_cap_pos: FrameTimecode = FrameTimecode(0, self.framerate) + self._last_position_ms: float = 0 @property def paths(self) -> List[AnyStr]: @@ -88,8 +89,16 @@ def position(self) -> FrameTimecode: def position_ms(self) -> float: return self._cap.position_ms + @property + def last_position_ms(self) -> float: + return self._last_position_ms + def read(self, decode: bool = True) -> Optional[numpy.ndarray]: """Read/decode the next frame.""" + + # I get the feeling when you run off the end of a file the position_ms returns 0. + self._last_position_ms = self._cap.position_ms + next = self._cap.read(decode=decode) if next is False: if (self._curr_cap_index + 1) < len(self._paths): From 7b7818146c86a5bc073bd6cd748f2db88f6363b9 Mon Sep 17 00:00:00 2001 From: goatzilla Date: Wed, 10 Jul 2024 05:11:58 -0500 Subject: [PATCH 4/8] Revert "Fix end-of-stream problem when using opencv timestamps." This reverts commit 99cf72358144762ecd2d3c9ab166bab6009bc029. --- dvr_scan/scanner.py | 3 ++- dvr_scan/video_joiner.py | 9 --------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/dvr_scan/scanner.py b/dvr_scan/scanner.py index aad8408..85f1590 100644 --- a/dvr_scan/scanner.py +++ b/dvr_scan/scanner.py @@ -736,6 +736,7 @@ def scan(self) -> Optional[DetectionResult]: event_end = FrameTimecode( (last_frame_above_threshold_ms / 1000) + self._post_event_len.get_seconds(), self._input.framerate) + #print("event end %f start %f" % (event_end.get_seconds(), event_start.get_seconds())) assert event_end.get_seconds() >= event_start.get_seconds() event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: @@ -816,7 +817,7 @@ def scan(self) -> Optional[DetectionResult]: event_end = FrameTimecode(self._input.position.frame_num, self._input.framerate) else: event_end = FrameTimecode( - (self._input.last_position_ms / 1000) + self._post_event_len.get_seconds(), + (self._input.position_ms / 1000) + self._post_event_len.get_seconds(), self._input.framerate) event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: diff --git a/dvr_scan/video_joiner.py b/dvr_scan/video_joiner.py index f48fd28..e30728a 100644 --- a/dvr_scan/video_joiner.py +++ b/dvr_scan/video_joiner.py @@ -53,7 +53,6 @@ def __init__(self, paths: Union[AnyStr, List[AnyStr]]): # Initialize position now that the framerate is valid. self._position: FrameTimecode = FrameTimecode(0, self.framerate) self._last_cap_pos: FrameTimecode = FrameTimecode(0, self.framerate) - self._last_position_ms: float = 0 @property def paths(self) -> List[AnyStr]: @@ -89,16 +88,8 @@ def position(self) -> FrameTimecode: def position_ms(self) -> float: return self._cap.position_ms - @property - def last_position_ms(self) -> float: - return self._last_position_ms - def read(self, decode: bool = True) -> Optional[numpy.ndarray]: """Read/decode the next frame.""" - - # I get the feeling when you run off the end of a file the position_ms returns 0. - self._last_position_ms = self._cap.position_ms - next = self._cap.read(decode=decode) if next is False: if (self._curr_cap_index + 1) < len(self._paths): From 01df90d1e7ceb9ba06a3fef8ff60541f93a0bb25 Mon Sep 17 00:00:00 2001 From: goatzilla Date: Wed, 10 Jul 2024 05:16:41 -0500 Subject: [PATCH 5/8] Move (most) position_ms calls to _decode_thread. --- dvr_scan/scanner.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/dvr_scan/scanner.py b/dvr_scan/scanner.py index 85f1590..4b7f49b 100644 --- a/dvr_scan/scanner.py +++ b/dvr_scan/scanner.py @@ -665,6 +665,7 @@ def scan(self) -> Optional[DetectionResult]: if frame is None: break assert frame.frame_bgr is not None + pts = frame.timecode.get_seconds() * 1000 frame_size = (frame.frame_bgr.shape[1], frame.frame_bgr.shape[0]) if frame_size != self._input.resolution: time = frame.timecode @@ -713,7 +714,7 @@ def scan(self) -> Optional[DetectionResult]: if not self._use_pts: last_frame_above_threshold = frame.timecode.frame_num else: - last_frame_above_threshold_ms = self._input.position_ms + last_frame_above_threshold_ms = pts # Otherwise, we wait until the post-event window has passed before ending # this motion event and start looking for a new one. # @@ -779,11 +780,11 @@ def scan(self) -> Optional[DetectionResult]: shifted_start = max(start_frame, frame.timecode.frame_num + 1 - shift_amount) event_start = FrameTimecode(shifted_start, self._input.framerate) else: - ms_since_last_event = self._input.position_ms - (event_end.get_seconds() * 1000) - last_frame_above_threshold_ms = self._input.position_ms + ms_since_last_event = pts - (event_end.get_seconds() * 1000) + last_frame_above_threshold_ms = pts # TODO: not sure all of this is actually necessary? shift_amount_ms = min(ms_since_last_event, start_event_shift_ms) - shifted_start_ms = max(start_frame_ms, self._input.position_ms - shift_amount_ms) + shifted_start_ms = max(start_frame_ms, pts - shift_amount_ms) event_start = FrameTimecode(shifted_start_ms / 1000, self._input.framerate) # Send buffered frames to encode thread. for encode_frame in buffered_frames: @@ -817,7 +818,7 @@ def scan(self) -> Optional[DetectionResult]: event_end = FrameTimecode(self._input.position.frame_num, self._input.framerate) else: event_end = FrameTimecode( - (self._input.position_ms / 1000) + self._post_event_len.get_seconds(), + (pts / 1000) + self._post_event_len.get_seconds(), self._input.framerate) event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: @@ -858,8 +859,12 @@ def _decode_thread(self, decode_queue: queue.Queue): # self._input.position points to the time at the end of the current frame (i.e. the # first frame has a frame_num of 1), so we correct that for presentation time. assert self._input.position.frame_num > 0 - presentation_time = FrameTimecode( - timecode=self._input.position.frame_num - 1, fps=self._input.framerate) + if not self._use_pts: + presentation_time = FrameTimecode( + timecode=self._input.position.frame_num - 1, fps=self._input.framerate) + else: + presentation_time = FrameTimecode( + self._input.position_ms / 1000, self._input.framerate) if not self._stop.is_set(): decode_queue.put(DecodeEvent(frame_bgr, presentation_time)) From feff96aacf781fba16677cd66b357321c9f05144 Mon Sep 17 00:00:00 2001 From: goatzilla Date: Wed, 10 Jul 2024 17:31:15 -0500 Subject: [PATCH 6/8] Cleanup code formatting and extraneous print --- dvr_scan/cli/config.py | 2 +- dvr_scan/scanner.py | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/dvr_scan/cli/config.py b/dvr_scan/cli/config.py index c25255d..0a2d440 100644 --- a/dvr_scan/cli/config.py +++ b/dvr_scan/cli/config.py @@ -315,7 +315,7 @@ def from_config(config_value: str, default: 'RGBValue') -> 'RGBValue': 'min-event-length': TimecodeValue('0.1s'), 'time-before-event': TimecodeValue('1.5s'), 'time-post-event': TimecodeValue('2.0s'), - 'use-pts' : False, + 'use-pts': False, # Detection Parameters 'bg-subtractor': 'MOG2', diff --git a/dvr_scan/scanner.py b/dvr_scan/scanner.py index 4b7f49b..a54cba4 100644 --- a/dvr_scan/scanner.py +++ b/dvr_scan/scanner.py @@ -734,10 +734,9 @@ def scan(self) -> Optional[DetectionResult]: self._frame_skip, self._input.framerate) assert event_end.frame_num >= event_start.frame_num else: - event_end = FrameTimecode( - (last_frame_above_threshold_ms / 1000) + - self._post_event_len.get_seconds(), self._input.framerate) - #print("event end %f start %f" % (event_end.get_seconds(), event_start.get_seconds())) + event_end = FrameTimecode((last_frame_above_threshold_ms / 1000) + + self._post_event_len.get_seconds(), + self._input.framerate) assert event_end.get_seconds() >= event_start.get_seconds() event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: @@ -777,7 +776,8 @@ def scan(self) -> Optional[DetectionResult]: if not self._use_pts: shift_amount = min(frames_since_last_event, start_event_shift) - shifted_start = max(start_frame, frame.timecode.frame_num + 1 - shift_amount) + shifted_start = max(start_frame, + frame.timecode.frame_num + 1 - shift_amount) event_start = FrameTimecode(shifted_start, self._input.framerate) else: ms_since_last_event = pts - (event_end.get_seconds() * 1000) @@ -817,9 +817,8 @@ def scan(self) -> Optional[DetectionResult]: if not self._use_pts: event_end = FrameTimecode(self._input.position.frame_num, self._input.framerate) else: - event_end = FrameTimecode( - (pts / 1000) + self._post_event_len.get_seconds(), - self._input.framerate) + event_end = FrameTimecode((pts / 1000) + self._post_event_len.get_seconds(), + self._input.framerate) event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: encode_queue.put(MotionEvent(start=event_start, end=event_end)) @@ -863,8 +862,8 @@ def _decode_thread(self, decode_queue: queue.Queue): presentation_time = FrameTimecode( timecode=self._input.position.frame_num - 1, fps=self._input.framerate) else: - presentation_time = FrameTimecode( - self._input.position_ms / 1000, self._input.framerate) + presentation_time = FrameTimecode(self._input.position_ms / 1000, + self._input.framerate) if not self._stop.is_set(): decode_queue.put(DecodeEvent(frame_bgr, presentation_time)) From 6068e0fe2ed4d4da0d055652a93f546135bfd0e4 Mon Sep 17 00:00:00 2001 From: goatzilla Date: Mon, 15 Jul 2024 22:29:01 -0500 Subject: [PATCH 7/8] Use exact PTS for events at end-of-file, comparable to frame_num behavior --- dvr_scan/scanner.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dvr_scan/scanner.py b/dvr_scan/scanner.py index a54cba4..bdaf7f1 100644 --- a/dvr_scan/scanner.py +++ b/dvr_scan/scanner.py @@ -817,8 +817,7 @@ def scan(self) -> Optional[DetectionResult]: if not self._use_pts: event_end = FrameTimecode(self._input.position.frame_num, self._input.framerate) else: - event_end = FrameTimecode((pts / 1000) + self._post_event_len.get_seconds(), - self._input.framerate) + event_end = FrameTimecode((pts / 1000), self._input.framerate) event_list.append(MotionEvent(start=event_start, end=event_end)) if self._output_mode != OutputMode.SCAN_ONLY: encode_queue.put(MotionEvent(start=event_start, end=event_end)) From d328cb5570767c7284dab14fd326f30abed08f49 Mon Sep 17 00:00:00 2001 From: goatzilla Date: Mon, 15 Jul 2024 22:31:12 -0500 Subject: [PATCH 8/8] Add test for use_pts. PTS uses a slightly different time than the frame_num which is shifted by 1, causing a slight slide in the expected events. --- tests/test_scan_context.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_scan_context.py b/tests/test_scan_context.py index 43543e8..162c8d0 100644 --- a/tests/test_scan_context.py +++ b/tests/test_scan_context.py @@ -32,6 +32,8 @@ # Similar to ARM, the CUDA version gives slightly different results. CUDA_EVENT_TOLERANCE = 1 +PTS_EVENT_TOLERANCE = 1 + # ROI within the frame used for the test case (see traffic_camera.txt for details). TRAFFIC_CAMERA_ROI = [ Point(631, 532), @@ -102,6 +104,17 @@ def test_scan_context(traffic_camera_video): compare_event_lists(event_list, TRAFFIC_CAMERA_EVENTS, EVENT_FRAME_TOLERANCE) +def test_scan_context_use_pts(traffic_camera_video): + """Test scanner 'use_pts' option to change how timekeeping is done.""" + scanner = MotionScanner([traffic_camera_video]) + scanner.set_detection_params() + scanner.set_regions(regions=[TRAFFIC_CAMERA_ROI]) + scanner.set_event_params(min_event_len=4, time_pre_event=0, use_pts=True) + event_list = scanner.scan().event_list + event_list = [(event.start.frame_num, event.end.frame_num) for event in event_list] + compare_event_lists(event_list, TRAFFIC_CAMERA_EVENTS, PTS_EVENT_TOLERANCE) + + @pytest.mark.skipif(not SubtractorCudaMOG2.is_available(), reason="CUDA module not available.") def test_scan_context_cuda(traffic_camera_video): """ Test functionality of MotionScanner with the DetectorType.MOG2_CUDA. """