diff --git a/README.md b/README.md index 83fc6c0..f7a6b85 100644 --- a/README.md +++ b/README.md @@ -22,20 +22,22 @@ Windows builds are also available on [the Downloads page](https://www.dvr-scan.c ## Quickstart -![example](https://raw.githubusercontent.com/Breakthrough/DVR-Scan/main/docs/assets/bounding-box.gif) - Scan `video.mp4` (separate clips for each event): dvr-scan -i video.mp4 -Only scan a region of interest (select with mouse): +Only scan a region of interest ([see user guide](https://dvr-scan.readthedocs.io/en/develop/guide/) or hit `H` for controls): dvr-scan -i video.mp4 -roi +![example of region editor](https://raw.githubusercontent.com/Breakthrough/DVR-Scan/main/docs/assets/region-editor-multiple.jpg) + Draw boxes around motion: dvr-scan -i video.mp4 -bb +![example of bounding boxes](https://raw.githubusercontent.com/Breakthrough/DVR-Scan/main/docs/assets/bounding-box.gif) + Use `ffmpeg` to extract events: dvr-scan -i video.mp4 -m ffmpeg @@ -44,5 +46,5 @@ See [the documentation](docs.md) for a complete list of all command-line and con ------------------------------------------------ -Copyright © 2016-2022 Brandon Castellano. All rights reserved. +Copyright © 2016-2023 Brandon Castellano. All rights reserved. Licensed under BSD 2-Clause (see the LICENSE file for details). diff --git a/docs/assets/region-editor-mask.jpg b/docs/assets/region-editor-mask.jpg index 3b5a7f0..bf71b76 100644 Binary files a/docs/assets/region-editor-mask.jpg and b/docs/assets/region-editor-mask.jpg differ diff --git a/docs/assets/region-editor-multiple.jpg b/docs/assets/region-editor-multiple.jpg index 1354e11..17b094e 100644 Binary files a/docs/assets/region-editor-multiple.jpg and b/docs/assets/region-editor-multiple.jpg differ diff --git a/docs/assets/region-editor-region.jpg b/docs/assets/region-editor-region.jpg index a126eab..2792ef1 100644 Binary files a/docs/assets/region-editor-region.jpg and b/docs/assets/region-editor-region.jpg differ diff --git a/docs/docs.md b/docs/docs.md index 3cb7250..ed2ab19 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -153,9 +153,36 @@ Detection can be limited to specific regions of the frame. This can be done inte Regions are specified as a list of points (X, Y) forming a closed polygon (shape with at least 3 points). For example, a triangle is defined as `0 0 100 0 50 50`. Multiple regions can be combined, and the result can also be saved to a file using `-s/--save-region`. -!!! tip "Setting a region of interest improves scanning performance." +!!! tip "Setting a smaller region of interest can improve scanning performance." + + *
-r, --region-editor
Display region editor before processing. Press `H` to show controls in your terminal. See [the user guide](guide.md#region-editor) for more details. + +``` +dvr-scan -i video.mp4 -r +``` + + + *
-a, --add-region
Add a region to the scan. Regions are defined by a list of points (min. 3) that enclose the scanning region. + +``` +dvr-scan -i video.mp4 -a 50 50 100 50 75 75 +``` + + + *
-s, --save-region
Save region data for this scan. Include all regions added or loaded via command line, and any edits made with the region editor if launched. + +``` +dvr-scan -i video.mp4 -r -s regions.txt +``` + + + *
-R, --load-region
Load region data an existing file. + +``` +dvr-scan -i video.mp4 -R regions.txt +``` + -TODO - This feature is under development in v1.6. More to come here. ------------------------------------------------ diff --git a/docs/guide.md b/docs/guide.md index 958771f..c884b20 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -59,18 +59,60 @@ This should show a window that appears similar to: Press `H` to print a list of all controls. Press `S` to save the current regions to a file, or `O` to load existing ones. Regions can also be set, saved, and loaded [from the command line](docs.md#regions). Regions from the command line also appear in the the region editor, and any edits will be applied before processing. -When you are satisfied with the region, press space bar or enter/return to start processing the video. Hit escape at any time to quit the program. +When you are satisfied with the region, press **space bar or enter to start processing** the video. You can press **escape to quit** the program without saving. ### Regions -You can use the left mouse button to add a new point to the current region, or drag existing points. Points can be deleted by middle clicking (or right-click on Windows). This allows you to create complex shapes, such as: +Regions are a set of points creating a closed shape. A rectangle will be created by default for you to modify. + +You can use the left mouse button to add a new point (keyboard: `A`) and right/middle mouse button (keyboard `X`) to delete a point. This allows you to create complex shapes, such as: example of non-rectangular region -You can hit `M` to view a cutout of the active mask: +Regions can be created by pressing `Shift + A` and deleted by pressing `Shift + X`. +or to click-and-drag an existing point. -example of region mask +example of region mask -A new region can be created by pressing `A`, and the selected region can be deleted by pressing `X`. Regions can be selected using the number keys `1`-`9`, or by pressing `k`/`l` to select the previous/next region. Note that only one region can be active at a time (you must select an existing shape to modify it). +Regions can be selected using the number keys `1`-`9`, or by pressing `k`/`l` to select the previous/next region. Note that only one region can be active at a time (you must select an existing shape to modify it). -example of region mask +Lastly, you can hit `M` to view a cutout of the active mask: + +example of region mask + +### Controls + +#### Editor + +| Command | Keyboard | Alt | +|--|--|--| +| Mask On/Off | `M` | | +| Start Scan | Space,
Enter | | +| Quit | Escape | | +| Save | `S` | | +| Load | `O` | | +| Undo | `Z` | :fontawesome-brands-windows:`Ctrl + Z`| +| Redo | `Y` | :fontawesome-brands-windows:`Ctrl + Y`| +| Print Points | `C` | | + +#### Regions + +| Command | Keyboard | Mouse | +|--|--|--| +| Add Point | `A` | Left | +| Delete Point | `X` | :fontawesome-brands-windows:Right
:fontawesome-brands-linux:Middle | +| Add Region | `Shift + A` | | +| Delete Region | `Shift + X` | | +| Select Region | `1` - `9` | | +| Next Region | `L` | | +| Previous Region | `K` | | + +#### Display + +| Command | Keyboard | Mouse | +|--|--|--| +| Downscale Increase | `W` | | +| Downscale Decrease | `E` | | +| Antialiasing | `Q` | | +| Window Mode | `R` | | +| Display Menu | | :fontawesome-brands-linux:Right | diff --git a/docs/index.md b/docs/index.md index f16a4e3..a8a3908 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ hide: !!! success "Latest Version: 1.5.1 (August 15, 2022)" -
[:fontawesome-solid-download:   Download](download.md){ .md-button #download-button }[:fontawesome-solid-book: User Guide](user_guide.md){ .md-button #changelog-button }[:fontawesome-solid-bars:   Documentation](docs.md){ .md-button #documentation-button }[:fontawesome-solid-gear:   Resources](changelog.md){ .md-button #quickstart-button }
+
[:fontawesome-solid-download:   Download](download.md){ .md-button #download-button }[:fontawesome-solid-book: User Guide](guide.md){ .md-button #changelog-button }[:fontawesome-solid-bars:   Documentation](docs.md){ .md-button #documentation-button }[:fontawesome-solid-gear:   Resources](changelog.md){ .md-button #quickstart-button }
------------------------------------------------------ @@ -21,22 +21,26 @@ DVR-Scan is a command-line application that **automatically detects motion event ## :fontawesome-solid-person-running:Quickstart -overlay example - Scan `video.mp4` (separate clips for each event): dvr-scan -i video.mp4 -Only scan a region of interest (select with mouse): +Only scan a region of interest ([see user guide](guide.md#region-editor) or hit `H` for controls): + + dvr-scan -i video.mp4 -r - dvr-scan -i video.mp4 -roi +overlay example Draw boxes around motion: dvr-scan -i video.mp4 -bb +overlay example + Use `ffmpeg` to extract events: dvr-scan -i video.mp4 -m ffmpeg -See [the documentation](docs.md) for a complete list of all command-line and configuration file options which can be set. You can also type `dvr-scan --help` for an overview of command line options. Some program options can also be set [using a config file](docs.md#config-file). +Once installed, see [the user guide](guide.md) to get started, try one of the examples above, or type `dvr-scan --help`. Press `Ctrl + C` to stop processing at any time. + +See the [documentation](docs.md) for a complete description of all [command-line](docs.md#dvr-scan-options) and [config file](docs.md#config-file) settings. diff --git a/dvr-scan.cfg b/dvr-scan.cfg index 1f20720..3be2e36 100644 --- a/dvr-scan.cfg +++ b/dvr-scan.cfg @@ -75,7 +75,7 @@ # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # Type of background subtraction to use, one of: MOG2, CNT, MOG2_CUDA -# bg-subtractor = MOG2 +#bg-subtractor = MOG2 # Threshold representing amount of motion in a frame (or the ROI, if set) for # a motion event to be triggered. Lower values require less movement, and are @@ -87,6 +87,10 @@ # from 3, 0 to disable, or -1 to auto-set using video resolution. #kernel-size = -1 +# Region file to limit detection area. Region files can be created using the +# -r/--region-editor or -s/--save-region flags. +#region-file = roi.txt + # Integer factor to shrink video before processing. Values <= 1 have no effect. #downscale-factor = 0 diff --git a/dvr_scan/__main__.py b/dvr_scan/__main__.py index cb098de..1874c5f 100644 --- a/dvr_scan/__main__.py +++ b/dvr_scan/__main__.py @@ -26,6 +26,7 @@ EXIT_SUCCESS: int = 0 EXIT_ERROR: int = 1 + def main(): """Main entry-point for DVR-Scan.""" settings = parse_settings() diff --git a/dvr_scan/cli/__init__.py b/dvr_scan/cli/__init__.py index e8b385b..093520b 100644 --- a/dvr_scan/cli/__init__.py +++ b/dvr_scan/cli/__init__.py @@ -353,6 +353,8 @@ def __call__(self, parser, namespace, values: List[str], option_string=None): self, message if message else RegionAction.DEFAULT_ERROR_MESSAGE)) from ex # Append this ROI to any existing ones, if any. + # TODO(v1.7): Audit uses of the 'regions' constant for -a/--add-region, replace with a named + # constant where possible. items = getattr(namespace, 'regions', []) items += [region.value] setattr(namespace, 'regions', items) diff --git a/dvr_scan/cli/controller.py b/dvr_scan/cli/controller.py index 82353ec..c3e3a3c 100644 --- a/dvr_scan/cli/controller.py +++ b/dvr_scan/cli/controller.py @@ -41,22 +41,26 @@ def __init__(self, args: argparse.Namespace, config: ConfigRegistry): self._args = args self._config = config + @property + def config(self) -> ConfigRegistry: + return self._config + def get_arg(self, arg: str) -> ty.Optional[ty.Any]: """Get setting specified via command line argument, if any.""" arg_name = arg.replace('-', '_') return getattr(self._args, arg_name) if hasattr(self._args, arg_name) else None - def get(self, option: str, arg: ty.Optional[str] = None) -> ty.Union[str, int, float, bool]: + def get(self, option: str) -> ty.Union[str, int, float, bool]: """Get setting based on following resolution order: 1. Argument specified via command line. 2. Option set in the active config file (either explicit with -c/--config, or the dvr-scan.cfg file in the user's settings folder). 3. Default value specified in the config map (`dvr_scan.cli.config.CONFIG_MAP`). """ - arg_val = self.get_arg(option if arg is None else arg) + arg_val = self.get_arg(option) if arg_val is not None: return arg_val - return self._config.get_value(option) + return self.config.get_value(option) def _preprocess_args(args): @@ -269,11 +273,18 @@ def run_dvr_scan(settings: ProgramSettings) -> ty.List[ty.Tuple[FrameTimecode, F duration=settings.get_arg('duration'), ) - # TODO(v1.7): Ensure ROI window respects start time if set. + # If the user specified some regions on the command line, ignore the load-regions setting in the + # config file. + load_region = settings.get_arg('load-region') + if load_region is None: + load_region = settings.config.get_value('load-region', ignore_default=True) + if not settings.get_arg('regions') is None: + load_region = None + scanner.set_regions( - region_editor=settings.get('region-editor'), + region_editor=settings.get_arg('region-editor'), regions=settings.get_arg('regions'), - load_region=settings.get('load-region'), + load_region=load_region, save_region=settings.get_arg('save-region'), ) diff --git a/dvr_scan/region.py b/dvr_scan/region.py index db10191..ffee663 100644 --- a/dvr_scan/region.py +++ b/dvr_scan/region.py @@ -12,7 +12,6 @@ """Handles detection region processing.""" from collections import namedtuple -from contextlib import contextmanager from copy import deepcopy from dataclasses import dataclass from logging import getLogger @@ -32,6 +31,12 @@ _WINDOW_NAME = "DVR-Scan: Select ROI" """Title given to the ROI selection window.""" +KEYCODE_ESCAPE = ord('\x1b') +KEYCODE_RETURN = ord('\r') +KEYCODE_SPACE = ord(' ') +KEYCODE_WINDOWS_UNDO = 26 +KEYCODE_WINDOWS_REDO = 25 + # TODO(v1.6): Figure out how to properly get icon path in the package. The folder will be different # in the final Windows build, may have to check if this is a frozen instance or not. Also need to @@ -118,29 +123,31 @@ class SelectionWindowSettings: # TODO(v1.7): Move more of these to SelectionWindowSettings. MIN_NUM_POINTS = 3 MAX_HISTORY_SIZE = 1024 -MAX_DOWNSCALE_FACTOR = 100 +MIN_DOWNSCALE_FACTOR = 1 +MAX_DOWNSCALE_FACTOR = 50 MAX_UPDATE_RATE_NORMAL = 20 MAX_UPDATE_RATE_DRAGGING = 5 HOVER_DISPLAY_DISTANCE = 260**2 MAX_DOWNSCALE_AA_LEVEL = 4 -# TODO(v1.6): Need to add keyboard alternate for adding/deleting point from current shape. -KEYBIND_REGION_ADD = 'a' -KEYBIND_REGION_DELETE = 'x' -KEYBIND_REGION_NEXT = 'l' -KEYBIND_REGION_PREV = 'k' -KEYBIND_UNDO = 'z' -KEYBIND_REDO = 'y' -KEYBIND_MASK = 'm' -KEYBIND_TOGGLE_AA = 'q' -KEYBIND_WINDOW_MODE = 'r' +KEYBIND_BREAKPOINT = 'b' KEYBIND_DOWNSCALE_INC = 'w' KEYBIND_DOWNSCALE_DEC = 'e' -KEYBIND_OUTPUT_LIST = 'c' KEYBIND_HELP = 'h' -KEYBIND_BREAKPOINT = 'b' KEYBIND_LOAD = 'o' +KEYBIND_MASK = 'm' +KEYBIND_OUTPUT_LIST = 'c' +KEYBIND_POINT_ADD = 'a' +KEYBIND_POINT_DELETE = 'x' +KEYBIND_REGION_ADD = 'A' +KEYBIND_REGION_DELETE = 'X' +KEYBIND_REGION_NEXT = 'l' +KEYBIND_REGION_PREVIOUS = 'k' +KEYBIND_REDO = 'y' +KEYBIND_TOGGLE_AA = 'q' KEYBIND_SAVE = 's' +KEYBIND_UNDO = 'z' +KEYBIND_WINDOW_MODE = 'r' def control_handle_radius(scale: int): @@ -178,31 +185,32 @@ def show_controls(): # Right click is disabled on Linux/OSX due to a context manager provided by the UI framework # showing up when right clicking. _WINDOWS_ONLY = 'Right, ' if IS_WINDOWS else '' + logger.info(f"""ROI Window Controls: Editor: - Preview Key: {KEYBIND_MASK} + Mask On/Off Key: {str(KEYBIND_MASK).upper()} Start Scan Key: Space, Enter Quit Key: Escape - Save Key: {KEYBIND_SAVE} - Load Key: {KEYBIND_LOAD} - Undo Key: {KEYBIND_UNDO} - Redo Key: {KEYBIND_REDO} - Print Points Key: {KEYBIND_OUTPUT_LIST} + Save Key: {str(KEYBIND_SAVE).upper()} + Load Key: {str(KEYBIND_LOAD).upper()} + Undo Key: {str(KEYBIND_UNDO).upper()} + Redo Key: {str(KEYBIND_REDO).upper()} + Print Points Key: {str(KEYBIND_OUTPUT_LIST).upper()} Regions: - Add Point Mouse: Left - Delete Point Mouse: {_WINDOWS_ONLY}Middle - Add Region Key: {KEYBIND_REGION_ADD} - Delete Region Key: {KEYBIND_REGION_DELETE} + Add Point Key: {str(KEYBIND_POINT_ADD).upper()}, Mouse: Left + Delete Point Key: {str(KEYBIND_POINT_DELETE).upper()}, Mouse: {_WINDOWS_ONLY}Middle + Add Region Key: Shift + {str(KEYBIND_REGION_ADD).upper()} + Delete Region Key: Shift + {str(KEYBIND_REGION_DELETE).upper()} Select Region Key: 1 - 9 - Next Region Key: {KEYBIND_REGION_NEXT} - Previous Region Key: {KEYBIND_REGION_PREV} + Next Region Key: {str(KEYBIND_REGION_NEXT).upper()} + Previous Region Key: {str(KEYBIND_REGION_PREVIOUS).upper()} Display: - Downscale +/- Key: {KEYBIND_DOWNSCALE_INC}(+), {KEYBIND_DOWNSCALE_DEC} (-) - Antialiasing Key: {KEYBIND_TOGGLE_AA} - Window Mode Key: {KEYBIND_WINDOW_MODE} + Downscale +/- Key: {str(KEYBIND_DOWNSCALE_INC).upper()}(+), {str(KEYBIND_DOWNSCALE_DEC).upper()} (-) + Antialiasing Key: {str(KEYBIND_TOGGLE_AA).upper()} + Window Mode Key: {str(KEYBIND_WINDOW_MODE).upper()} """) @@ -458,81 +466,73 @@ def _init_window(self): cv2.imshow(_WINDOW_NAME, mat=self._frame) cv2.setMouseCallback(_WINDOW_NAME, on_mouse=self._handle_mouse_input) + def _breakpoint(self): + if self._debug_mode: + breakpoint() + + def _create_keymap(self) -> ty.Dict[int, ty.Callable]: + return { + KEYBIND_BREAKPOINT: lambda: self._breakpoint, + KEYBIND_DOWNSCALE_INC: lambda: self._adjust_downscale(1), + KEYBIND_DOWNSCALE_DEC: lambda: self._adjust_downscale(-1), + KEYBIND_HELP: lambda: show_controls(), + KEYBIND_LOAD: lambda: self._load(), + KEYBIND_MASK: lambda: self._toggle_mask(), + KEYBIND_OUTPUT_LIST: lambda: self._emit_points(), + KEYBIND_POINT_ADD: lambda: self._add_point(), + KEYBIND_POINT_DELETE: lambda: self._delete_point(), + KEYBIND_REGION_ADD: lambda: self._add_region(), + KEYBIND_REGION_DELETE: lambda: self._delete_region(), + KEYBIND_REGION_NEXT: lambda: self._next_region(), + KEYBIND_REGION_PREVIOUS: lambda: self._prev_region(), + KEYBIND_REDO: lambda: self._redo(), + KEYBIND_TOGGLE_AA: lambda: self._toggle_antialiasing(), + KEYBIND_SAVE: lambda: self._save(), + KEYBIND_UNDO: lambda: self._undo(), + KEYBIND_WINDOW_MODE: lambda: self._toggle_window_mode(), + chr(KEYCODE_WINDOWS_REDO): lambda: self._redo(), + chr(KEYCODE_WINDOWS_UNDO): lambda: self._undo(), + } + def run(self, warn_if_notkinter: bool) -> bool: - logger.debug("Creating window for frame (scale = %d)", self._scale) - self._init_window() - check_tkinter_support(warn_if_notkinter) - set_icon(_WINDOW_NAME, _ICON_PATH) - regions_valid = False - logger.info(f"Region editor active. Press {KEYBIND_HELP} to show controls.") - while True: - if not cv2.getWindowProperty(_WINDOW_NAME, cv2.WND_PROP_VISIBLE): - logger.debug("Main window closed.") - break - self._draw() - key = cv2.waitKey( - MAX_UPDATE_RATE_NORMAL if not self._dragging else MAX_UPDATE_RATE_DRAGGING) & 0xFF - # TODO: Map keybinds to callbacks rather than handle each case explicitly. - if key == 27: - break - elif key in (ord(' '), 13): - regions_valid = True - break - elif key == ord(KEYBIND_BREAKPOINT) and self._debug_mode: - breakpoint() - elif key == ord(KEYBIND_TOGGLE_AA): - self._settings.use_aa = not self._settings.use_aa - self._redraw = True - logger.debug("AA: %s", "ON" if self._settings.use_aa else "OFF") - if self._scale >= MAX_DOWNSCALE_AA_LEVEL: - logger.warning("AA is disabled due to current scale factor.") - elif key == ord(KEYBIND_DOWNSCALE_INC): - if self._scale < MAX_DOWNSCALE_FACTOR: - self._scale += 1 - self._rescale() - elif key == ord(KEYBIND_DOWNSCALE_DEC): - if self._scale > 1: - self._scale = max(1, self._scale - 1) - self._rescale() - elif key == ord(KEYBIND_UNDO): - self._undo() - elif key == ord(KEYBIND_REDO): - self._redo() - elif key == ord(KEYBIND_OUTPUT_LIST): - self._emit_points() - elif key == ord(KEYBIND_MASK): - self._toggle_mask() - elif key == ord(KEYBIND_WINDOW_MODE): - cv2.destroyWindow(_WINDOW_NAME) - if self._settings.window_mode == cv2.WINDOW_KEEPRATIO: - self._settings.window_mode = cv2.WINDOW_AUTOSIZE - else: - self._settings.window_mode = cv2.WINDOW_KEEPRATIO - logger.debug( - "Window Mode: %s", "KEEPRATIO" - if self._settings.window_mode == cv2.WINDOW_KEEPRATIO else "AUTOSIZE") - self._init_window() - - elif key == ord(KEYBIND_REGION_ADD): - self._add_region() - elif key == ord(KEYBIND_REGION_DELETE): - self._delete_region() - elif key == ord(KEYBIND_REGION_NEXT): - self._next_region() - elif key == ord(KEYBIND_REGION_PREV): - self._prev_region() - - elif key == ord(KEYBIND_HELP): - show_controls() - elif key == ord(KEYBIND_LOAD): - self._load() - elif key == ord(KEYBIND_SAVE): - self._save() - elif key >= ord('0') and key <= ord('9'): - self._select_region((key - ord('1')) % 10) - - cv2.destroyAllWindows() - return regions_valid + try: + logger.debug("Creating window for frame (scale = %d)", self._scale) + self._init_window() + check_tkinter_support(warn_if_notkinter) + set_icon(_WINDOW_NAME, _ICON_PATH) + regions_valid = False + logger.info(f"Region editor active. Press {KEYBIND_HELP} to show controls.") + keyboard_callbacks = self._create_keymap() + while True: + if not cv2.getWindowProperty(_WINDOW_NAME, cv2.WND_PROP_VISIBLE): + logger.debug("Main window closed.") + break + self._draw() + key = cv2.waitKey(MAX_UPDATE_RATE_NORMAL + if not self._dragging else MAX_UPDATE_RATE_DRAGGING) & 0xFF + if key == KEYCODE_ESCAPE: + break + elif key in (KEYCODE_SPACE, KEYCODE_RETURN): + regions_valid = True + break + elif key >= ord('0') and key <= ord('9'): + self._select_region((key - ord('1')) % 10) + elif chr(key) in keyboard_callbacks: + keyboard_callbacks[chr(key)]() + elif key != 0xFF: + print("Unhandled key: %s" % str(key)) + return regions_valid + + finally: + cv2.destroyAllWindows() + + def _adjust_downscale(self, amount: int): + # scale is clamped to MIN_DOWNSCALE_FACTOR/MAX_DOWNSCALE_FACTOR. + scale = self._scale + amount + self._scale = ( + MIN_DOWNSCALE_FACTOR if scale < MIN_DOWNSCALE_FACTOR else + scale if scale < MAX_DOWNSCALE_FACTOR else MAX_DOWNSCALE_FACTOR) + self._rescale() def _save(self): if not HAS_TKINTER: @@ -589,7 +589,7 @@ def _load(self): self._active_shape = 0 if len(self._regions) > 0 else None def _delete_point(self): - if not self._hover_point is None: + if not self._hover_point is None and not self._dragging: if len(self.active_region) > MIN_NUM_POINTS: hover = self._hover_point x, y = self.active_region[hover] @@ -601,11 +601,29 @@ def _delete_point(self): logger.error("Cannot remove point, shape must have at least 3 points.") self._dragging = False + def _toggle_antialiasing(self): + self._settings.use_aa = not self._settings.use_aa + self._redraw = True + logger.debug("AA: %s", "ON" if self._settings.use_aa else "OFF") + if self._scale >= MAX_DOWNSCALE_AA_LEVEL: + logger.warning("AA is disabled due to current scale factor.") + def _toggle_mask(self): self._settings.mask_source = not self._settings.mask_source logger.debug("Masking: %s", "ON" if self._settings.mask_source else "OFF") self._redraw = True + def _toggle_window_mode(self): + cv2.destroyWindow(_WINDOW_NAME) + if self._settings.window_mode == cv2.WINDOW_KEEPRATIO: + self._settings.window_mode = cv2.WINDOW_AUTOSIZE + else: + self._settings.window_mode = cv2.WINDOW_KEEPRATIO + logger.debug( + "Window Mode: %s", + "KEEPRATIO" if self._settings.window_mode == cv2.WINDOW_KEEPRATIO else "AUTOSIZE") + self._init_window() + def _add_point(self) -> bool: if not self._nearest_points is None: insert_pos = (1 + self._nearest_points[0] if self._nearest_points[0] diff --git a/dvr_scan/scanner.py b/dvr_scan/scanner.py index 2454eb1..e647302 100644 --- a/dvr_scan/scanner.py +++ b/dvr_scan/scanner.py @@ -480,7 +480,8 @@ def _handle_regions(self) -> bool: for shape in self._regions] if self._region_editor: self._logger.info("Selecting area of interest:") - # TODO: We should process this frame. + # TODO(v1.7): Ensure ROI window respects start time if set. + # TODO(v1.7): We should process this frame (right now it gets skipped). frame_for_crop = self._input.read() scale_factor = 1 screen_bounds = get_min_screen_bounds() @@ -854,9 +855,6 @@ def _draw_overlays( if not self._metrics_overlay is None: to_display = "Frame: %04d\nScore: %3.2f" % (timecode.get_frames(), frame_score) self._metrics_overlay.draw(frame, text=to_display) - # TODO(v1.6): The bounding box overlay does not draw correctly on masks when an ROI - # is defined due to the coordinate shift being incorrectly applied. Allow passing `shift` - # on each frame to the `draw` function to make it explicit what shift is used where. if not self._bounding_box is None and not bounding_box is None: self._bounding_box.draw(frame, bounding_box)