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:
-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.
-
+
-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).
-
+Lastly, you can hit `M` to view a cutout of the active 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
-
-
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
+
Draw boxes around motion:
dvr-scan -i video.mp4 -bb
+
+
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)