import numpy as np
import cv2
from isimple import get_logger, settings
from isimple.config import extend, ConfigType, Field, validator, BaseConfig
from isimple.core.interface import FilterConfig, FilterInterface, FilterType
from isimple.maths.images import ckernel
from isimple.maths.colors import Color, HsvColor, convert, WRAP
log = get_logger(__name__)
[docs]@extend(ConfigType)
class HsvRangeFilterConfig(FilterConfig):
"""HSV range filter"""
range: HsvColor = Field(default=HsvColor(h=10, s=75, v=75))
color: HsvColor = Field(default=HsvColor())
close: int = Field(default=0)
open: int = Field(default=0)
@property
def ready(self) -> bool:
return self.color != HsvColor()
@property
def c0(self) -> HsvColor:
return self.color - self.range
@property
def c1(self) -> HsvColor:
return self.color + self.range
_resolve_close = validator('close', allow_reuse=True)(BaseConfig._odd_add)
_resolve_open = validator('open', allow_reuse=True)(BaseConfig._odd_add)
[docs]@extend(FilterType)
class HsvRangeFilter(FilterInterface):
"""Filters by a range of hues ~ HSV representation
"""
_config_class = HsvRangeFilterConfig
[docs] def set_filter(self, filter: HsvRangeFilterConfig, color: Color) -> HsvRangeFilterConfig:
color = convert(color, HsvColor)
log.debug(f'Setting filter {filter} ~ color {color}')
filter(color=color)
return filter
[docs] def mean_color(self, filter: HsvRangeFilterConfig) -> Color:
# S and V are arbitrary but work relatively well
# for both overlay & plot colors
return HsvColor(h=filter.color.h, s=255, v=200)
[docs] def filter(self, filter: HsvRangeFilterConfig, img: np.ndarray, mask: np.ndarray = None) -> np.ndarray:
if filter.c0.h > filter.c1.h:
# handle hue wrapping situation with two ranges
c0_a = np.float32(filter.c0.list)
c1_a = np.float32([WRAP-1] + filter.c1.list[1:])
c0_b = np.float32([0] + filter.c0.list[1:])
c1_b = np.float32(filter.c1.list)
binary = cv2.inRange(img, c0_a, c1_a, img) \
+ cv2.inRange(img, c0_b, c1_b, img)
else:
c0 = np.float32(filter.c0.list)
c1 = np.float32(filter.c1.list)
binary = cv2.inRange(img, c0, c1, img)
if filter.close:
binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, ckernel(filter.close))
if filter.open:
binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, ckernel(filter.open))
if mask is not None:
# Mask off again
binary = cv2.bitwise_and(binary, mask)
return binary