Skip to content

Commit

Permalink
[gui] Finish region editor
Browse files Browse the repository at this point in the history
Issue: #39
Issue: #58
  • Loading branch information
Breakthrough committed Oct 10, 2023
1 parent b4e830c commit e78b95f
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 234 deletions.
6 changes: 3 additions & 3 deletions dvr_scan/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import dvr_scan
from dvr_scan.cli.config import ConfigRegistry, CHOICE_MAP, USER_CONFIG_FILE_PATH
from dvr_scan.selection_window import Point, RegionValue
from dvr_scan.region import Point, RegionValidator

# Version string shown for the -v/--version CLI argument.
VERSION_STRING = f"""------------------------------------------------
Expand Down Expand Up @@ -306,7 +306,7 @@ def __call__(self, parser, namespace, values: List[str], option_string=None):
if not values:
setattr(namespace, 'region_editor', True)
return
# TODO(v1.6): Re-add backwards compat. for X Y W H rectangles.
# TODO(v1.6): Re-add backwards compatibility for X Y W H rectangles.
raise NotImplementedError()
# Append this ROI to any existing ones, if any.
items = getattr(namespace, 'roi_deprecated', [])
Expand Down Expand Up @@ -346,7 +346,7 @@ def __init__(self,
def __call__(self, parser, namespace, values: List[str], option_string=None):

try:
region = RegionValue(" ".join(values))
region = RegionValidator(" ".join(values))
except ValueError as ex:
message = " ".join(str(arg) for arg in ex.args)
raise (argparse.ArgumentError(
Expand Down
2 changes: 1 addition & 1 deletion dvr_scan/cli/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def run_dvr_scan(settings: ProgramSettings) -> ty.List[ty.Tuple[FrameTimecode, F
duration=settings.get_arg('duration'),
)

# TODO(v1.6): Ensure ROI window respects start time if set.
# TODO(v1.7): Ensure ROI window respects start time if set.
scanner.set_regions(
region_editor=settings.get('region-editor'),
regions=settings.get_arg('regions'),
Expand Down
39 changes: 23 additions & 16 deletions dvr_scan/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import numpy as np

from dvr_scan.subtractor import Subtractor
from dvr_scan.selection_window import Point
from dvr_scan.region import Point

Rectangle = namedtuple("Rectangle", ['x', 'y', 'w', 'h'])

Expand Down Expand Up @@ -53,41 +53,50 @@ def __init__(self, subtractor: Subtractor, frame_size: ty.Tuple[int, int], downs
self._downscale = downscale
self._regions = list(regions) if not regions is None else []
self._mask: np.ndarray = np.ones((0, 0))
self._area: Rectangle = Rectangle(x=0, y=0, w=self._frame_size[0], h=self._frame_size[1])
self._area: ty.Tuple[Point, Point] = (Point(0, 0),
Point(self._frame_size[0] - 1,
self._frame_size[1] - 1))
if self._regions:
# TODO: See if this can be done using a single color channel or in a bitmap
mask = np.zeros((frame_size[1], frame_size[0], 3), dtype=np.uint8)
for shape in self._regions:
points = np.array([shape], np.int32)
mask = cv2.fillPoly(mask, points, color=(255, 255, 255), lineType=cv2.LINE_4)
# True denotes ignored (masked) elements, and False represents unmasked elements (those
# inside any region).
self._mask = np.logical_not(mask.astype(bool))
mask = mask[:, :, 0].astype(bool)
active_pixels = mask.sum()
# False marks unmasked elements (those inside the active region), so we invert the mask.
mask = np.logical_not(mask)
# Calculate subset of frame to use to speed up calculations.
min_x, min_y, max_x, max_y = self._frame_size[0], self._frame_size[1], 0, 0
for shape in self._regions:
for point in shape:
min_x, min_y = min(min_x, point.x), min(min_y, point.y)
max_x, max_y = max(max_x, point.x), max(max_y, point.y)
self._area: Rectangle = Rectangle(x=min_x, y=min_y, w=max_x - min_x, h=max_y - min_y)
logger.debug("Cropping detection area: %s", str(self._area))
self._area = (Point(min_x, min_y), Point(max_x, max_y))
coverage = 100.0 * (active_pixels / float(frame_size[0] * frame_size[1]))
mask = mask[self._area[0].y:self._area[1].y, self._area[0].x:self._area[1].x]
logger.debug(
"Region Mask: area = ("
f"{self._area[0].x},{self._area[0].y}),({self._area[1].x},{self._area[1].y}"
f"), coverage = {coverage:.2f}%")
if self._downscale > 1:
mask = mask[::self._downscale, ::self._downscale]
logger.debug(f"Mask Downscaled: size = {mask.shape[0]}, {mask.shape[1]}")
self._mask = mask

@property
def area(self) -> Rectangle:
"""Area the region of interest covers in the original frame."""
return self._area

@property
def background_mask(self) -> np.ndarray:
raise NotImplementedError()

def _preprocess(self, frame: np.ndarray) -> np.ndarray:
cropped = None
if not self._regions:
cropped = frame
else:
cropped = frame[
self._area.y:self._area.y + self._area.h,
self._area.x:self._area.x + self._area.w,
self._area[0].y:self._area[1].y,
self._area[0].x:self._area[1].x,
]
if self._downscale > 1:
return cropped[::self._downscale, ::self._downscale, :]
Expand All @@ -97,11 +106,9 @@ def _preprocess(self, frame: np.ndarray) -> np.ndarray:
def update(self, frame: np.ndarray) -> ProcessedFrame:
frame = self._preprocess(frame)
subtracted = self._subtractor.apply(frame)

if len(self._regions) <= 1:
if not self._regions:
return ProcessedFrame(
subtracted=subtracted, masked=subtracted, score=np.average(subtracted))

motion_mask = np.ma.array(subtracted, mask=self._mask)
return ProcessedFrame(
subtracted=subtracted,
Expand Down
4 changes: 4 additions & 0 deletions dvr_scan/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,15 @@ def get_filename(path: AnyStr, include_extension: bool) -> AnyStr:


def set_icon(window_name: str, icon_path: str):
if not icon_path:
return
if not IS_WINDOWS:
# TODO: Set icon on Linux/OSX.
return
SendMessage = ctypes.windll.user32.SendMessageW
FindWindow = ctypes.windll.user32.FindWindowW
LoadImage = ctypes.windll.user32.LoadImageW
SetFocus = ctypes.windll.user32.SetFocus
IMAGE_ICON = 1
ICON_SMALL = 1
ICON_BIG = 1
Expand All @@ -146,6 +149,7 @@ def set_icon(window_name: str, icon_path: str):
hIcon = LoadImage(None, icon_path, IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION)
SendMessage(hWnd, WM_SETICON, ICON_SMALL, hIcon)
SendMessage(hWnd, WM_SETICON, ICON_BIG, hIcon)
SetFocus(hWnd)


@contextmanager
Expand Down
Loading

0 comments on commit e78b95f

Please sign in to comment.