Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ jobs:
pytest tests/test_io.py && \
pytest tests/test_other.py && \
pytest tests/test_transform.py && \
pytest tests/test_video.py
pytest tests/test_video.py && \
pytest tests/test_processing.py
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
# Changelog
## [1.3.1] - 2025-09-07

### Added
* default line_type value moved to cv3.opt
* updated threshold function with type parameter and relative values support
* new test_threshold_all_types in test_processing.py
* opt functions validation and tests in test_other
* examples in video.py for VideoCapture, VideoWriter, and Video functions

### Changed
* got rid of f-strings for compatibility with early python versions
* got rid of TODOs
* small docs corrections
* replaced Path(...).unlink(missing_ok=True) with if Path(...).exists(): Path(...).unlink() for compatibility

### Deprecated

### Removed

### Fixed

## [1.3.0] - 2025-08-31

### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ with cv3.Window('Result') as window:
This is just a small example of what cv3 can do. Check out the [documentation](https://cv3.readthedocs.io/en/latest/)
for a comprehensive overview of all the improvements and additions that cv3 provides over raw OpenCV.

You can also get acquainted with the features in [demo.ipynb](https://github.com/gorodion/pycv/blob/main/demo.ipynb)
You can also get acquainted with the features in [demo.ipynb](https://github.com/gorodion/cv3/blob/main/demo.ipynb)

## Run tests

Expand Down
31 changes: 24 additions & 7 deletions cv3/_draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,30 @@
'italic': cv2.FONT_ITALIC
}

_THRESHOLD_TYPE_DICT = {
'binary': cv2.THRESH_BINARY,
'binary_inv': cv2.THRESH_BINARY_INV,
'trunc': cv2.THRESH_TRUNC,
'tozero': cv2.THRESH_TOZERO,
'tozero_inv': cv2.THRESH_TOZERO_INV
}


def _line_type_flag_match(flag):
assert flag in _LINE_TYPE_DICT, f'no such flag: "{flag}". Available: {", ".join(_LINE_TYPE_DICT.keys())}'
assert flag in _LINE_TYPE_DICT, 'no such flag: "{}". Available: {}'.format(flag, ", ".join(_LINE_TYPE_DICT.keys()))
return _LINE_TYPE_DICT[flag]


def _font_flag_match(flag):
assert flag in _FONTS_DICT, f'no such flag: "{flag}". Available: {", ".join(_FONTS_DICT.keys())}'
assert flag in _FONTS_DICT, 'no such flag: "{}". Available: {}'.format(flag, ", ".join(_FONTS_DICT.keys()))
return _FONTS_DICT[flag]


def _threshold_type_flag_match(flag):
assert flag in _THRESHOLD_TYPE_DICT, 'no such flag: "{}". Available: {}'.format(flag, ", ".join(_THRESHOLD_TYPE_DICT.keys()))
return _THRESHOLD_TYPE_DICT[flag]



def _handle_poly_pts(img, pts, rel=None):
pts = np.array(pts).reshape(-1)
pts = _relative_handle(img, *pts, rel=rel)
Expand All @@ -82,18 +95,22 @@ def _handle_poly_pts(img, pts, rel=None):

def _draw_decorator(func):
@type_decorator
def wrapper(img, *args, color=None, line_type=cv2.LINE_8, copy=False, **kwargs):
def wrapper(img, *args, color=None, line_type=None, copy=False, **kwargs):
if copy:
img = img.copy()

color = _process_color(color)

if isinstance(line_type, str):
# Handle line_type parameter
if line_type is None:
line_type = opt.LINE_TYPE
elif isinstance(line_type, str):
line_type = _line_type_flag_match(line_type)

kwargs['t'] = round(kwargs.get('t', opt.THICKNESS))
kwargs['line_type'] = line_type

return func(img, *args, color=color, line_type=line_type, **kwargs)
return func(img, *args, color=color, **kwargs)

return wrapper

Expand Down Expand Up @@ -269,7 +286,7 @@ def _marker_flag_match(flag):
'triangle_up': cv2.MARKER_TRIANGLE_UP,
'triangle_down': cv2.MARKER_TRIANGLE_DOWN
}
assert flag in marker_dict or flag in marker_dict.values(), f'no such flag: "{flag}". Available: {", ".join(marker_dict.keys())}'
assert flag in marker_dict or flag in marker_dict.values(), 'no such flag: "{}". Available: {}'.format(flag, ", ".join(marker_dict.keys()))
return marker_dict.get(flag, flag)


Expand Down
4 changes: 2 additions & 2 deletions cv3/_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def _inter_flag_match(flag):
Raises:
AssertionError: If flag is not one of the valid options.
"""
assert flag in _INTER_DICT, f'no such flag: "{flag}". Available: {", ".join(_INTER_DICT.keys())}'
assert flag in _INTER_DICT, 'no such flag: "{}". Available: {}'.format(flag, ", ".join(_INTER_DICT.keys()))
return _INTER_DICT[flag]


Expand All @@ -61,7 +61,7 @@ def _border_flag_match(flag):
Raises:
AssertionError: If flag is not one of the valid options.
"""
assert flag in _BORDER_DICT, f'no such flag: "{flag}". Available: {", ".join(_BORDER_DICT.keys())}'
assert flag in _BORDER_DICT, 'no such flag: "{}". Available: {}'.format(flag, ", ".join(_BORDER_DICT.keys()))
return _BORDER_DICT[flag]


Expand Down
2 changes: 1 addition & 1 deletion cv3/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def _process_color(color):
if color is None:
color = opt.COLOR
if isinstance(color, str):
assert color in COLORS_RGB_DICT, f'No such color: {color}. Available colors: {list(COLORS_RGB_DICT.keys())}'
assert color in COLORS_RGB_DICT, 'No such color: {}. Available colors: {}'.format(color, list(COLORS_RGB_DICT.keys()))
color = COLORS_RGB_DICT[color]
if not opt.RGB:
color = color[::-1]
Expand Down
22 changes: 11 additions & 11 deletions cv3/draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def rectangle(img, x0, y0, x1, y1, mode='xyxy', rel=None, color=None, t=None, li
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the rectangle (default: opt.COLOR).
t: Thickness of the rectangle lines (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
fill (bool, optional): Whether to fill the rectangle. If True, draws a filled rectangle
regardless of thickness. If False, draws an outlined rectangle. If None, uses
the thickness parameter to determine fill behavior. Defaults to None.
Expand Down Expand Up @@ -138,7 +138,7 @@ def polylines(img, pts, is_closed=False, rel=None, color=None, t=None, line_type
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the polylines (default: opt.COLOR).
t: Thickness of the lines (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
copy (bool): Whether to copy the image before drawing (default: False).

Returns:
Expand Down Expand Up @@ -217,7 +217,7 @@ def circle(img, x0, y0, r, rel=None, color=None, t=None, line_type=None, fill=No
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the circle (default: opt.COLOR).
t: Thickness of the circle line. Use -1 or cv2.FILLED for filled circle (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
fill (bool, optional): Whether to fill the circle. If True, draws a filled circle
regardless of thickness. If False, draws an outlined circle. If None, uses
the thickness parameter to determine fill behavior. Defaults to None.
Expand Down Expand Up @@ -305,7 +305,7 @@ def line(img, x0, y0, x1, y1, rel=None, color=None, t=None, line_type=None, copy
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the line (default: opt.COLOR).
t: Thickness of the line (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
copy (bool): Whether to copy the image before drawing (default: False).

Returns:
Expand Down Expand Up @@ -340,7 +340,7 @@ def hline(img, y, rel=None, color=None, t=None, line_type=None, copy=False):
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the line (default: opt.COLOR).
t: Thickness of the line (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
copy (bool): Whether to copy the image before drawing (default: False).

Returns:
Expand Down Expand Up @@ -375,7 +375,7 @@ def vline(img, x, rel=None, color=None, t=None, line_type=None, copy=False):
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the line (default: opt.COLOR).
t: Thickness of the line (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
copy (bool): Whether to copy the image before drawing (default: False).

Returns:
Expand Down Expand Up @@ -422,7 +422,7 @@ def text(img, text_str, x=0.5, y=0.5, font=None, scale=None, flip=False, rel=Non
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the text (default: opt.COLOR).
t: Thickness of the text strokes (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
copy (bool): Whether to copy the image before drawing (default: False).

Returns:
Expand Down Expand Up @@ -467,7 +467,7 @@ def rectangles(img: np.array, rects: List[List], color=None, t=None, line_type=N
of parameters to pass to the rectangle function.
color: Color of the rectangles (default: opt.COLOR).
t: Thickness of the rectangle lines (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
fill (bool, optional): Whether to fill the rectangles. If True, draws filled rectangles
regardless of thickness. If False, draws outlined rectangles. If None, uses
the thickness parameter to determine fill behavior. Defaults to None.
Expand Down Expand Up @@ -572,7 +572,7 @@ def arrow(img, x0, y0, x1, y1, rel=None, color=None, t=None, line_type=None, tip
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the arrow (default: opt.COLOR).
t: Thickness of the arrow (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
tip_length (float, optional): The length of the arrow tip in relation to the arrow length.
Defaults to 0.1.
copy (bool): Whether to copy the image before drawing (default: False).
Expand Down Expand Up @@ -623,7 +623,7 @@ def ellipse(img, x, y, axes_x, axes_y, angle=0, start_angle=0, end_angle=360, re
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the ellipse (default: opt.COLOR).
t: Thickness of the ellipse line. Use -1 or cv2.FILLED for filled ellipse (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
fill (bool, optional): Whether to fill the ellipse. If True, draws a filled ellipse
regardless of thickness. If False, draws an outlined ellipse. If None, uses
the thickness parameter to determine fill behavior. Defaults to None.
Expand Down Expand Up @@ -684,7 +684,7 @@ def marker(img, x, y, marker_type=None, marker_size=None, rel=None, color=None,
rel (bool, optional): Whether to use relative coordinates. Defaults to None.
color: Color of the marker (default: opt.COLOR).
t: Thickness of the marker lines (default: opt.THICKNESS).
line_type: Type of line for drawing (default: cv2.LINE_8).
line_type: Type of line for drawing (default: opt.LINE_TYPE).
copy (bool): Whether to copy the image before drawing (default: False).

Returns:
Expand Down
4 changes: 2 additions & 2 deletions cv3/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def imread(img_path, flag=cv2.IMREAD_COLOR):
if not is_ascii(img_path):
img = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), flag)
if img is None:
raise OSError(f'File was not read: {img_path}')
raise OSError('File was not read: {}'.format(img_path))
if img.ndim == 2:
return img
if opt.RGB:
Expand Down Expand Up @@ -297,7 +297,7 @@ def __init__(self, window_name=None, pos=None, flag=cv2.WINDOW_AUTOSIZE):
>>> window = cv3.Window('My Window', pos=(100, 100))
"""
if window_name is None:
window_name = f'window{Window.__window_count}'
window_name = 'window{}'.format(Window.__window_count)

window_name = str(window_name)
cv2.namedWindow(window_name, flag)
Expand Down
29 changes: 21 additions & 8 deletions cv3/opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
THICKNESS (int): Default line thickness for drawing operations.
COLOR: Default color for drawing operations.
FONT: Default font for text drawing operations.
LINE_TYPE: Default line type for drawing operations.
THRESHOLD_TYPE: Default threshold type for threshold operations.
SCALE (float): Default scale factor for drawing operations.
PT_RADIUS (int): Default point radius for drawing operations.
EXPERIMENTAL (bool): Flag to enable experimental features.
Expand All @@ -27,6 +29,8 @@
SCALE = 1
PT_RADIUS = 1
EXPERIMENTAL = False
LINE_TYPE = cv2.LINE_8
THRESHOLD_TYPE = cv2.THRESH_BINARY

def set_rgb():
"""Set the color format to RGB.
Expand Down Expand Up @@ -60,12 +64,14 @@ def video(fps=None, fourcc=None):
"""
global FPS, FOURCC
if fps is not None:
assert fps > 0, 'default fps must be more 0'
fps = int(fps)
assert fps > 0, 'default fps must be greater than 0'
FPS = fps
if fourcc is not None:
# TODO asserts flags
FOURCC = cv2.VideoWriter_fourcc(*fourcc) if isinstance(fourcc, str) else fourcc

if isinstance(fourcc, str):
assert len(fourcc) == 4, 'if fourcc is str, len(fourcc) must be 4'
fourcc = cv2.VideoWriter_fourcc(*fourcc)
FOURCC = fourcc

def draw(thickness=None, color=None, font=None, pt_radius=None, scale=None, line_type=None):
"""Set default drawing parameters.
Expand All @@ -90,18 +96,25 @@ def draw(thickness=None, color=None, font=None, pt_radius=None, scale=None, line
assert isinstance(color, (str, int, float, np.unsignedinteger, np.floating, np.ndarray, list, tuple))
COLOR = color
if font is not None:
# TODO asserts flags
# Import font values from _draw.py
from ._draw import _FONTS_DICT
assert font in _FONTS_DICT.values(), 'invalid font type'
FONT = font
if pt_radius is not None:
# TODO asserts
assert isinstance(pt_radius, (int, np.unsignedinteger)), 'default pt_radius must be a non-negative integer'
assert pt_radius >= 0, 'default pt_radius must be non-negative'
PT_RADIUS = pt_radius
if scale is not None:
# TODO asserts
assert isinstance(scale, (int, float, np.floating)), 'default scale must be a positive number'
assert scale > 0, 'default scale must be positive'
SCALE = scale
if line_type is not None:
# TODO asserts flags
# Import line type values from _draw.py
from ._draw import _LINE_TYPE_DICT
assert line_type in _LINE_TYPE_DICT.values(), 'invalid line type'
LINE_TYPE = line_type


def set_exp(exp=True):
global EXPERIMENTAL
EXPERIMENTAL = exp
Loading