From 000704a3dfe31701b6450f4ea137bbd1d9880a25 Mon Sep 17 00:00:00 2001 From: MarvinKweyu Date: Sat, 17 Aug 2024 19:12:26 +0300 Subject: [PATCH 01/10] add readthedocs config docs: update version --- .readthedocs.yml | 16 ++++++++++++++++ CHANGES.rst | 10 ++++++++++ setup.py | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 .readthedocs.yml diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..8fdd975 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,16 @@ +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.12" + + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/conf.py + +formats: + - pdf + - epub \ No newline at end of file diff --git a/CHANGES.rst b/CHANGES.rst index bda38bf..7508a2b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,16 @@ ColorDetect Changelog ===================== +.. _1.6.3: +1.6.3 (17-08-2024) +================== +Fix +--------- + +- Docs configuration for build + + + .. _1.6.2: 1.6.2 (15-08-2024) ================== diff --git a/setup.py b/setup.py index 5399abc..a811c05 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="ColorDetect", - version="1.6.2", + version="1.6.3", author="Marvin Kweyu", author_email="mkweyu1@gmail.com", description="Detect and recognize colors in images or video", From c471a33e47e4ee466640df4f97e7cd89d63d82fe Mon Sep 17 00:00:00 2001 From: MarvinKweyu Date: Sat, 17 Aug 2024 20:08:35 +0300 Subject: [PATCH 02/10] docs: update readthedocs comment --- .readthedocs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 8fdd975..dc806e9 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,6 +1,5 @@ version: 2 -# Set the OS, Python version and other tools you might need build: os: ubuntu-22.04 tools: From f9eccc67e2e84850bbf1e169d1d415948ea16bf6 Mon Sep 17 00:00:00 2001 From: MarvinKweyu Date: Sat, 17 Aug 2024 21:06:55 +0300 Subject: [PATCH 03/10] fix: move tests to git workflows --- .circleci/config.yml | 20 -------------------- .github/workflows/package-tests.yml | 5 +---- README.md | 4 ++-- 3 files changed, 3 insertions(+), 26 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 70caa9e..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: 2 -jobs: - build: - machine: - image: ubuntu-2004:2024.04.4 - steps: - - checkout - - run: - name: Install python dependencies - command: | - sudo -S apt-get install build-essential cmake -y - pip3 install virtualenv - virtualenv -p python3 .venv - source .venv/bin/activate - pip install -r requirements/requirements-dev.txt - - run: - name: Running tests - command: | - source .venv/bin/activate - pytest diff --git a/.github/workflows/package-tests.yml b/.github/workflows/package-tests.yml index f1f1e86..e5b99c8 100644 --- a/.github/workflows/package-tests.yml +++ b/.github/workflows/package-tests.yml @@ -1,7 +1,4 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python - -name: Python package +name: Package tests on: push: diff --git a/README.md b/README.md index 9046498..b0b362a 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ ![Lint workflow](https://github.com/MarvinKweyu/ColorDetect/actions/workflows/lint.yml/badge.svg?branch=master) [![PyPI version](https://badge.fury.io/py/ColorDetect.svg)](https://pypi.org/project/ColorDetect/) -[![Python](https://img.shields.io/badge/python-3.6%7C3.7%7C3.8%7C3.9-green)](https://pypi.org/project/ColorDetect/) -[![CircleCI](https://circleci.com/gh/MarvinKweyu/ColorDetect.svg?style=svg)](https://circleci.com/gh/MarvinKweyu/ColorDetect) +[![Python](https://img.shields.io/badge/python->=3.6-green)](https://pypi.org/project/ColorDetect/) +![Package tests](https://github.com/MarvinKweyu/ColorDetect/actions/workflows/package-tests.yml/badge.svg?branch=master) [![Downloads](https://pepy.tech/badge/colordetect)](https://pypi.org/project/ColorDetect/) [![Documentation Status](https://readthedocs.org/projects/colordetect/badge/?version=master)](https://colordetect.readthedocs.io/en/master/) From cd91bf732f3e0288fe3ab44cff80509d71e1c87e Mon Sep 17 00:00:00 2001 From: MarvinKweyu Date: Sat, 17 Aug 2024 21:12:38 +0300 Subject: [PATCH 04/10] chore: update tests for 3.11 --- .github/workflows/package-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package-tests.yml b/.github/workflows/package-tests.yml index e5b99c8..09eb999 100644 --- a/.github/workflows/package-tests.yml +++ b/.github/workflows/package-tests.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.11"] steps: - uses: actions/checkout@v4 From 1e486118056c8712944a6046679043fcedc0f1a7 Mon Sep 17 00:00:00 2001 From: MarvinKweyu Date: Sat, 17 Aug 2024 21:44:00 +0300 Subject: [PATCH 05/10] docs: update docs --- docs/conf.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d27e75e..92d0143 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ author = "Marvin Kweyu" # The full version, including alpha/beta/rc tags -release = "1.6.0" +release = "1.6.3" # -- General configuration --------------------------------------------------- @@ -63,7 +63,8 @@ "github_user": "MarvinKweyu", "github_repo": "ColorDetect", "github_button": True, - # "show_related": True, + "github_type": "star", + "show_related": True, } # Add any paths that contain custom static files (such as style sheets) here, From 7c9eb15a2dd6447cb5498107e46c570813ed521c Mon Sep 17 00:00:00 2001 From: Marvin Kweyu Date: Sat, 28 Feb 2026 15:51:43 +0300 Subject: [PATCH 06/10] feat: upgrade to support later python versions (#77) --- .github/workflows/python-publish.yml | 4 +-- CONTRIBUTING.rst | 12 +++++--- pyproject.toml | 41 ++++++++++++++++++++++++++++ setup.py | 31 --------------------- tests/conftest.py | 4 +-- tests/test_functional.py | 4 +-- tests/test_unit.py | 2 -- 7 files changed, 55 insertions(+), 43 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 4e1ef42..9b97706 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -21,11 +21,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install build twine - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel + python -m build twine upload dist/* diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 2e4905b..80f702b 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -9,11 +9,15 @@ The source code, located at `the ColorDetect project page `_ for dependency management. +Install it via:: - python3 -m venv .venv - .venv/bin/activate - pip install -r requirements/requirements-dev.txt + curl -LsSf https://astral.sh/uv/install.sh | sh + +Then set up the project:: + + uv venv + uv pip install -e ".[dev]" pre-commit install diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..271594f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "ColorDetect" +version = "1.6.3" +authors = [ + {name = "Marvin Kweyu", email = "mkweyu1@gmail.com"}, +] +description = "Detect and recognize colors in images or video" +readme = "README.md" +license = {text = "MIT"} +requires-python = ">=3.6" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: POSIX :: Linux", +] +dependencies = [ + "numpy>=1.18.1", + "matplotlib>=3.2.1", + "opencv-python>=4.2.0.32", + "scikit-learn>=0.22.2.post1", + "webcolors>=1.11.1", +] + +[project.urls] +Homepage = "https://github.com/MarvinKweyu/ColorDetect" + +[project.optional-dependencies] +dev = [ + "pytest", + "flake8", + "black", + "isort", + "pre-commit", +] + +[tool.setuptools.packages.find] +where = ["."] diff --git a/setup.py b/setup.py deleted file mode 100644 index a811c05..0000000 --- a/setup.py +++ /dev/null @@ -1,31 +0,0 @@ -import setuptools - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="ColorDetect", - version="1.6.3", - author="Marvin Kweyu", - author_email="mkweyu1@gmail.com", - description="Detect and recognize colors in images or video", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/MarvinKweyu/ColorDetect", - packages=setuptools.find_packages(), - include_package_data=True, - license="MIT", - install_requires=[ - "numpy>=1.18.1", - "matplotlib>=3.2.1", - "opencv-python>=4.2.0.32", - "scikit-learn>=0.22.2.post1", - "webcolors>=1.11.1", - ], - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: POSIX :: Linux", - ], - python_requires=">=3.6", -) diff --git a/tests/conftest.py b/tests/conftest.py index a341e0b..8b0c95e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import os -from distutils import dir_util +import shutil import pytest @@ -42,6 +42,6 @@ def datadir(tmpdir, request): test_dir, _ = os.path.splitext(filename) if os.path.isdir(test_dir): - dir_util.copy_tree(test_dir, bytes(tmpdir)) + shutil.copytree(test_dir, str(tmpdir), dirs_exist_ok=True) return tmpdir diff --git a/tests/test_functional.py b/tests/test_functional.py index 6de785a..54c4dfb 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -3,7 +3,6 @@ test whether text is written to the image """ -import imghdr import mimetypes from pathlib import Path @@ -24,7 +23,8 @@ def test_argument_is_image(image): """ Ensure that the argument parsed is an image """ - assert type(imghdr.what(Path(image))) is str + mime_type, _ = mimetypes.guess_type(str(image)) + assert mime_type is not None and mime_type.startswith("image/") def test_video_passed_is_valid_video(video): diff --git a/tests/test_unit.py b/tests/test_unit.py index 92595f8..1adcc0d 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -3,11 +3,9 @@ From the developers perspective """ -import mimetypes import os from pathlib import Path -import cv2 import pytest from ..colordetect import ColorDetect, VideoColor, col_share From bbe1d41df2aad1dc5c3168a1a0af5c64e64fafbc Mon Sep 17 00:00:00 2001 From: Marvin Kweyu Date: Sat, 28 Feb 2026 15:57:19 +0300 Subject: [PATCH 07/10] docs: update changelog --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7508a2b..b6ef396 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,15 @@ ColorDetect Changelog ===================== + +.. _1.6.4: +1.6.4 (28-02-2026) +================== +Feat +--------- + +- Migrate to pyproject.toml from using setup + .. _1.6.3: 1.6.3 (17-08-2024) ================== From 7add1d638f952d610e4508a6a63d36760e1caec4 Mon Sep 17 00:00:00 2001 From: Marvin Kweyu Date: Sat, 28 Feb 2026 16:03:51 +0300 Subject: [PATCH 08/10] fix: release tag --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 92d0143..69b2cef 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ author = "Marvin Kweyu" # The full version, including alpha/beta/rc tags -release = "1.6.3" +release = "1.6.4" # -- General configuration --------------------------------------------------- From f80646ec31836ad88c637a50fc8d1d9c6d3d3b29 Mon Sep 17 00:00:00 2001 From: Marvin Kweyu Date: Sat, 28 Feb 2026 16:09:58 +0300 Subject: [PATCH 09/10] docs: update system version --- CHANGES.rst | 7 +++++++ docs/conf.py | 2 +- pyproject.toml | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index b6ef396..0945288 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,13 @@ ColorDetect Changelog ===================== +.. _1.6.5: +1.6.5 (28-02-2026) +================== +Fix +--------- + +- System versioning and development contact .. _1.6.4: 1.6.4 (28-02-2026) diff --git a/docs/conf.py b/docs/conf.py index 69b2cef..0344d52 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ author = "Marvin Kweyu" # The full version, including alpha/beta/rc tags -release = "1.6.4" +release = "1.6.5" # -- General configuration --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 271594f..ea1e8e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,9 +4,9 @@ build-backend = "setuptools.build_meta" [project] name = "ColorDetect" -version = "1.6.3" +version = "1.6.5" authors = [ - {name = "Marvin Kweyu", email = "mkweyu1@gmail.com"}, + {name = "Marvin Kweyu", email = "hello@marvinkweyu.net"}, ] description = "Detect and recognize colors in images or video" readme = "README.md" From a1c0b7c0c9590d314eab6f65e217534e00341779 Mon Sep 17 00:00:00 2001 From: Marvin Kweyu Date: Fri, 28 Aug 2026 15:22:34 +0300 Subject: [PATCH 10/10] refac: update package struct for new packaging (#81) * refac: update package struct for new packaging * fix: lint pipeline * docs: update docs * fix: lint errors --- .flake8 | 1 + .github/workflows/lint.yml | 8 +- CHANGES.rst | 15 +++- __init__.py | 0 docs/conf.py | 2 +- docs/videocolor.rst | 2 +- pyproject.toml | 10 +-- {colordetect => src/colordetect}/__init__.py | 0 {colordetect => src/colordetect}/col_share.py | 0 .../colordetect}/color_detect.py | 23 +++-- .../colordetect}/video_color_detect.py | 88 +++++++++---------- tests/test_functional.py | 2 +- tests/test_unit.py | 4 +- 13 files changed, 83 insertions(+), 72 deletions(-) delete mode 100644 __init__.py rename {colordetect => src/colordetect}/__init__.py (100%) rename {colordetect => src/colordetect}/col_share.py (100%) rename {colordetect => src/colordetect}/color_detect.py (95%) rename {colordetect => src/colordetect}/video_color_detect.py (67%) diff --git a/.flake8 b/.flake8 index ef97a3b..939b9af 100644 --- a/.flake8 +++ b/.flake8 @@ -3,3 +3,4 @@ max-line-length = 100 max-complexity = 18 select = B,C,E,F,W,T4,B9 + exclude = .git,__pycache__,.venv,venv,.tox,build,dist,*.egg-info,docs/_build diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index caef45b..3d92dd9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.9] + python-version: [3.11] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -21,6 +21,6 @@ jobs: pip install -r requirements/requirements-dev.txt - name: Check code format and style run: | - isort **/*.py -c -v - black --check colordetect/ tests/ - flake8 colordetect/ tests/ + isort src/colordetect/ tests/ docs/conf.py -c -v + black --check src/colordetect/ tests/ + flake8 src/colordetect/ tests/ diff --git a/CHANGES.rst b/CHANGES.rst index 0945288..75cf8e3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,15 @@ ColorDetect Changelog ===================== + +.. _1.6.6: +1.6.6 (28-08-2026) +================== +Feat +--------- + +- Migration to recommended packaging + .. _1.6.5: 1.6.5 (28-02-2026) ================== @@ -53,7 +62,7 @@ Fix Features --------- -- Perform color recognition on a video at a specific time +- Perform color recognition on a video at a specific time - Extract image from video at a specific time @@ -65,12 +74,12 @@ Docs ---- - Update contribution readme with pre-commit configuration. - + Fix --- - Linting of code - + .. _1.5.0: 1.5.0 (11-08-2021) ======================= diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/docs/conf.py b/docs/conf.py index 0344d52..cb36276 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -23,7 +23,7 @@ author = "Marvin Kweyu" # The full version, including alpha/beta/rc tags -release = "1.6.5" +release = "1.6.6" # -- General configuration --------------------------------------------------- diff --git a/docs/videocolor.rst b/docs/videocolor.rst index 07bf156..db8a920 100644 --- a/docs/videocolor.rst +++ b/docs/videocolor.rst @@ -57,7 +57,7 @@ We could, **alternatively**, handle the saving ourselves and go as below: >>> image.write_color_count(font_color=(255,255,255)) - >>> image.save_image(location='path/to/directory/of/choice', filename='filenameofchoice.jpg') + >>> image.save_image(location='path/to/directory/of/choice', file_name='filenameofchoice.jpg') diff --git a/pyproject.toml b/pyproject.toml index ea1e8e0..3407a6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [build-system] -requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" +requires = ["uv_build"] +build-backend = "uv_build" [project] name = "ColorDetect" -version = "1.6.5" +version = "1.6.6" authors = [ {name = "Marvin Kweyu", email = "hello@marvinkweyu.net"}, ] @@ -37,5 +37,5 @@ dev = [ "pre-commit", ] -[tool.setuptools.packages.find] -where = ["."] +[tool.pytest.ini_options] +pythonpath = ["src"] diff --git a/colordetect/__init__.py b/src/colordetect/__init__.py similarity index 100% rename from colordetect/__init__.py rename to src/colordetect/__init__.py diff --git a/colordetect/col_share.py b/src/colordetect/col_share.py similarity index 100% rename from colordetect/col_share.py rename to src/colordetect/col_share.py diff --git a/colordetect/color_detect.py b/src/colordetect/color_detect.py similarity index 95% rename from colordetect/color_detect.py rename to src/colordetect/color_detect.py index 90758f1..ef1a98b 100644 --- a/colordetect/color_detect.py +++ b/src/colordetect/color_detect.py @@ -160,15 +160,15 @@ def get_segmented_image( segmented = cv2.bitwise_and(self.image_original, self.image_original, mask=mask) - for i in range(len(mask)): - for j in range(len(mask[i])): - if mask[i][j] != 0: - output_image[i][j] = self.image_original[i][j] + output_image[mask != 0] = self.image_original[mask != 0] return output_image, gray, segmented, mask def get_color_count( - self, color_count: int = 5, color_format: str = "human_readable" + self, + color_count: int = 5, + color_format: str = "human_readable", + max_pixels: int = 10_000, ) -> dict: """ .. _get_color_count: @@ -188,6 +188,10 @@ def get_color_count( * rgb - rgb(255, 255, 0) for yellow * hex - #FFFF00 for yellow * human_readable - yellow for yellow + max_pixels: int + Maximum number of pixels sampled for clustering. Reduces memory and + CPU usage on large images with negligible impact on color accuracy. + Set to None to disable downsampling. :return: color description """ @@ -198,8 +202,11 @@ def get_color_count( # convert image from BGR to RGB for better accuracy rgb = cv2.cvtColor(self.image, cv2.COLOR_BGR2RGB) - reshape = rgb.reshape((rgb.shape[0] * rgb.shape[1], 3)) - cluster = KMeans(n_clusters=color_count).fit(reshape) + reshape = rgb.reshape((-1, 3)) + if max_pixels is not None and len(reshape) > max_pixels: + indices = np.random.choice(len(reshape), max_pixels, replace=False) + reshape = reshape[indices] + cluster = KMeans(n_clusters=color_count, n_init=10).fit(reshape) unique_colors = self._find_unique_colors(cluster, cluster.cluster_centers_) @@ -252,7 +259,7 @@ def _find_unique_colors(self, cluster, centroids) -> dict: # Get the number of different clusters, create histogram, and normalize labels = np.arange(0, len(np.unique(cluster.labels_)) + 1) - (hist, _) = np.histogram(cluster.labels_, bins=labels) + hist, _ = np.histogram(cluster.labels_, bins=labels) hist = hist.astype("float") hist /= hist.sum() diff --git a/colordetect/video_color_detect.py b/src/colordetect/video_color_detect.py similarity index 67% rename from colordetect/video_color_detect.py rename to src/colordetect/video_color_detect.py index 49b0472..7c46567 100644 --- a/colordetect/video_color_detect.py +++ b/src/colordetect/video_color_detect.py @@ -9,16 +9,13 @@ >>> from colordetect import VideoColor >>> user_video = VideoColor("") # where frame_color_count is the target most dominant colors to be found. Default set to 5 ->>> colors = user_video.get_video_frames(frame_color_count=7) +>>> colors = user_video.get_video_frames(frame_color_count=7) >>> colors -# alternatively shorten the dictionary to get a specific number of sorted colors from the whole lot +# shorten the result to the N most dominant colors across the whole video >>> from colordetect import col_share >>> top_colors = col_share.sort_order(object_description=colors, key_count=8) """ -import datetime -import sys - import cv2 from . import col_share @@ -31,7 +28,6 @@ class VideoColor: """ def __init__(self, video): - # super().__init__(video) self.video_file = cv2.VideoCapture(video) self.color_description = {} @@ -51,12 +47,14 @@ def get_video_frames( ---------- frame_color_count: int The number of most dominant colors to be obtained from a single frame - color_format:str + color_format: str The format to return the color in. Options * hsv - (60°,100%,100%) * rgb - rgb(255, 255, 0) for yellow * hex - #FFFF00 for yellow + progress: bool + Show a progress bar during processing. Default False. :return: color_description dictionary """ if not isinstance(frame_color_count, int): @@ -74,30 +72,29 @@ def get_video_frames( count = 0 total_frame_count = self.video_file.get(cv2.CAP_PROP_FRAME_COUNT) - while self.video_file.isOpened(): - # how often to extract colors from video frames. Defaults to every 1 second - (success, image) = self._get_frame(time=count * 1000) - if not success: - break # Video is complete - image_object = ColorDetect(image) - colors = image_object.get_color_count( - color_count=frame_color_count, color_format=color_format - ) - # merge dictionaries as they are created - self.color_description = {**self.color_description, **colors} - count += 1 - current_frame_num = self.video_file.get(cv2.CAP_PROP_POS_FRAMES) + try: + while self.video_file.isOpened(): + success, image = self._get_frame(time=count * 1000) + if not success: + break + colors = ColorDetect(image).get_color_count( + color_count=frame_color_count, color_format=color_format + ) + self.color_description.update(colors) + count += 1 + if progress: + col_share.progress_bar( + position=self.video_file.get(cv2.CAP_PROP_POS_FRAMES), + total_length=total_frame_count, + ) if progress: + # Flush bar to 100% for any trailing millis skipped at the end col_share.progress_bar( - position=current_frame_num, total_length=total_frame_count + position=total_frame_count, total_length=total_frame_count ) - if progress: - col_share.progress_bar( - position=total_frame_count, total_length=total_frame_count - ) # Cater for video with extra millis at the end that don't sum upto a full sec, and are thus skipped + finally: + self.video_file.release() - self.video_file.release() - print("\n") return self.color_description def _get_frame(self, time: int = 1000) -> tuple: @@ -112,13 +109,10 @@ def _get_frame(self, time: int = 1000) -> tuple: time: int Time to get color from in parsed image - :return: () + :return: (success, image) """ - # read file every x time in milliseconds self.video_file.set(cv2.CAP_PROP_POS_MSEC, time) - success, image = self.video_file.read() - - return (success, image) + return self.video_file.read() def get_time_frame_color( self, color_count: int = 5, color_format: str = "rgb", time: int = 1000 @@ -136,7 +130,7 @@ def get_time_frame_color( Time to get color from in video in milliseconds color_count: int Number of colors to return at the given time frame - color_format:str + color_format: str The format to return the color in. Options * hsv - (60°,100%,100%) @@ -157,23 +151,23 @@ def get_time_frame_color( if time < 1: raise ValueError("Cannot give negative time to extract color from") - if self._get_video_length() < time: + video_length = self._get_video_length() + if video_length < time: raise ValueError( - f"The time given is longer than the video parsed. Provided {time} while length of video: {self._get_video_length()}" + f"The time given is longer than the video parsed. Provided {time} while length of video: {video_length}" ) - (success, image) = self._get_frame(time) - if success: - image_object = ColorDetect(image) - colors = image_object.get_color_count( - color_count=color_count, color_format=color_format - ) - - self.color_description = colors - - self.video_file.release() + try: + success, image = self._get_frame(time) + if success: + image_object = ColorDetect(image) + self.color_description = image_object.get_color_count( + color_count=color_count, color_format=color_format + ) + finally: + self.video_file.release() - return (image_object, self.color_description) + return image_object, self.color_description def _get_video_length(self) -> int: """ @@ -182,7 +176,7 @@ def _get_video_length(self) -> int: ---------------- get the length of a video - return: the length of a video + return: the length of a video in milliseconds """ frames = self.video_file.get(cv2.CAP_PROP_FRAME_COUNT) fps = self.video_file.get(cv2.CAP_PROP_FPS) diff --git a/tests/test_functional.py b/tests/test_functional.py index 54c4dfb..73f6967 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -8,7 +8,7 @@ import matplotlib.colors as mcolors -from ..colordetect import ColorDetect +from colordetect import ColorDetect def test_existence_of_image_vid_path(image, video): diff --git a/tests/test_unit.py b/tests/test_unit.py index 1adcc0d..5640ed4 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -8,7 +8,7 @@ import pytest -from ..colordetect import ColorDetect, VideoColor, col_share +from colordetect import ColorDetect, VideoColor, col_share def test_image_vid_parsed_to_class(image, video): @@ -177,7 +177,7 @@ def test_get_time_frame_color_returns_video_colors_at_given_time(video): """ user_video = VideoColor(video) - (image, colors_at_time) = user_video.get_time_frame_color(time=10000) + image, colors_at_time = user_video.get_time_frame_color(time=10000) assert isinstance(image, ColorDetect) assert isinstance(colors_at_time, dict)