diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..563b87d --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,8 @@ +name: Ruff +on: [push, pull_request] +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: chartboost/ruff-action@v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 5ad2cc3..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Tests -on: - - push - - pull_request -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest] - python-version: ["3.10"] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox tox-gh-actions - - name: Test with tox - run: tox diff --git a/DESCRIPTION.md b/DESCRIPTION.md index 3b0f290..eee33e8 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -1,3 +1,4 @@ +# Traffic Light Detector ## Likvidēt cilvēka reakcijas laika ietekmi uz satiksmes sastrēgumu realizāciju ar datorsistēmu, kas balstīta tiešā norobežota krustojuma izmaiņu komunikācijas metodikā, maršruta posmā, kas aprīkots ar gaismas signalizācijas satiksmes regulatoru. > Reduce traffic congestion effects due to human reaction times at intersections with traffic lights by utilizing a computer system based on direct communication  method of changes in the circumscribed route intersection. diff --git a/README.md b/README.md index dbe5fe0..17487bd 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Traffic Light Detector Traffic Light recognition and color detection -![Tests](https://github.com/kristoferssolo/Traffic-Light-Detector/actions/workflows/tests.yml/badge.svg) +![Tests](https://github.com/kristoferssolo/Traffic-Light-Detector/actions/workflows/ruff.yml/badge.svg) ## Description -See [DESCRIPTION.md](./DESCRIPTION.md) +See [DESCRIPTION.md](./DESCRIPTION.md) [lv] ![Red light](./media/red.jpg) @@ -29,5 +29,4 @@ pip install . Replace `` with your camera number specified by the operating system. Probably `0` or `1`, but can be higher. ## To Do -- [ ] Write unit tests. - [ ] Create/find better traffic light model for better traffic light recognition. diff --git a/main.py b/main.py index fbd2723..3c3dd97 100755 --- a/main.py +++ b/main.py @@ -3,11 +3,9 @@ import argparse from loguru import logger -from TrafficLightDetector.paths import IMAGES_IN_PATH, create_dirs -from TrafficLightDetector.traffic_light_camera import \ - TrafficLightDetectorCamera -from TrafficLightDetector.traffic_light_images import \ - TrafficLightDetectorImages +from TrafficLightDetector.paths import create_dirs, IMAGES_IN_PATH +from TrafficLightDetector.traffic_light_camera import TrafficLightDetectorCamera +from TrafficLightDetector.traffic_light_images import TrafficLightDetectorImages def pos_int(string: str) -> int: @@ -37,11 +35,7 @@ group.add_argument( metavar="int", help="Reads camera inputs to determine traffic light color. (Default: %(default)s)", ) -parser.add_argument( - "-s", - "--sound", - action="store_true" -) +parser.add_argument("-s", "--sound", action="store_true") @logger.catch @@ -54,7 +48,9 @@ def main(args) -> None: image.draw() if args.camera is not None: - camera = TrafficLightDetectorCamera(args.camera, sound=args.sound) # Change number if webcam didn't detect + camera = TrafficLightDetectorCamera( + args.camera, sound=args.sound + ) # Change number if webcam didn't detect camera.enable() diff --git a/pyproject.toml b/pyproject.toml index 7e1be6d..a5ea5f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,32 @@ [build-system] -requires = ["setuptools>=42.0", "wheel"] +requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" -[tool.pytest.ini_options] -addopts = "--cov=TrafficLightDetector" -testpaths = ["tests"] +[project] +name = "TrafficLightDetector" +version = "0.1.0" +description = "Reduce traffic congestion effects due to human reaction times at intersections with traffic lights by utilizing a computer system based on direct communication method of changes in the circumscribed route intersection." +authors = [ + { name = "Kristofers Solo", email = "dev@kristofers.xyz" }, + { name = "Alan Alexander Cerna" }, +] +keywords = ["detection", "traffic light"] +readme = "README.md" +requires-python = ">=3.10" +license = { text = "GPLv3" } +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3:10", + "Programming Language :: Python :: 3:11", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: Linux", +] +dependencies = ["loguru==0.7", "opencv-python~=4.8", "magicsound==0.0.5"] + +[project.urls] +"Source" = "https://github.com/kristoferssolo/Traffic-Light-Detector" +"Description" = "https://github.com/kristoferssolo/Traffic-Light-Detector/blob/main/DESCRIPTION.md" +"Bug Tracker" = "https://github.com/kristoferssolo/Traffic-Light-Detector/issues" [tool.mypy] check_untyped_defs = true @@ -19,3 +41,10 @@ warn_redundant_casts = true warn_return_any = true warn_unreachable = true warn_unused_configs = true + + +[tool.ruff] +line-length = 160 + +[tool.ruff.flake8-quotes] +docstring-quotes = "double" diff --git a/requirements.txt b/requirements.txt index 4dccefb..7a8a2d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -loguru==0.6.0 -opencv-python==4.6.0.66 -playsound==1.3.0 +loguru==0.7.* +opencv-python==4.8.* +magicsound==0.0.* diff --git a/requirements_dev.txt b/requirements_dev.txt index 14597cb..d874ab9 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,5 +1,2 @@ -flake8==6.0.0 -mypy==0.991 -pytest-cov==4.0.0 -pytest==7.2.0 -tox==3.27.1 +mypy==1.5.* +ruff==0.0.* diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 41787ab..0000000 --- a/setup.cfg +++ /dev/null @@ -1,34 +0,0 @@ -[metadata] -name = TrafficLightDetector -description = Reduce traffic congestion effects due to human reaction times at intersections with traffic lights by utilizing a computer system based on direct communication method of changes in the circumscribed route intersection. -author = Kristofers Solo -license = MIT -license_files = LICENSE -platforms = unix, linux, osx, cygwin, win32 -classifiers = - Programming Language :: Python :: 3.10 - -[options] -packages = TrafficLightDetector -install_requires = - loguru>=0.6.0 - opencv-python>=4.6.0.66 - playsound>=1.3.0 - -python_requires = >=3.10 -package_dir = =src -zip_safe = no - -[options.extras_require] -testing = - flake8>=6.0.0 - mypy>=0.991 - pytest-cov>=4.0.0 - pytest>=7.2.0 - tox>=3.27.1 - -[options.package_data] -detector = py.typed - -[flake8] -max-line-length = 160 diff --git a/setup.py b/setup.py deleted file mode 100644 index 7f1a176..0000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -from setuptools import setup - -if __name__ == "__main__": - setup() diff --git a/src/TrafficLightDetector/color.py b/src/TrafficLightDetector/color.py index 5ea5ddc..2ba9a35 100644 --- a/src/TrafficLightDetector/color.py +++ b/src/TrafficLightDetector/color.py @@ -2,7 +2,6 @@ from typing import NamedTuple import cv2 import numpy as np -from loguru import logger class ColorRange(NamedTuple): @@ -10,24 +9,40 @@ class ColorRange(NamedTuple): color2: tuple[int, int, int] -@logger.catch class ColorAttributes: - def __init__(self, name: str, color: tuple[int, int, int], lowers: ColorRange, - uppers: ColorRange, hsv: cv2.cvtColor, minDist: int, param2: int) -> None: + def __init__( + self, + name: str, + color: tuple[int, int, int], + lowers: ColorRange, + uppers: ColorRange, + hsv: np.ndarray, + minDist: int, + param2: int, + ) -> None: self.name = name self.color = color # set mask - masks: list[cv2.inRange] = [] + masks = [] for lower, upper in zip(lowers, uppers): masks.append(cv2.inRange(hsv, lower, upper)) + self.mask = masks[0] if len(masks) > 1: for mask in masks: self.mask = cv2.add(self.mask, mask) # set circle - self.circle = cv2.HoughCircles(self.mask, cv2.HOUGH_GRADIENT, 1, minDist=minDist, param1=50, - param2=param2, minRadius=0, maxRadius=30) + self.circle = cv2.HoughCircles( + self.mask, + cv2.HOUGH_GRADIENT, + 1, + minDist=minDist, + param1=50, + param2=param2, + minRadius=0, + maxRadius=30, + ) if self.circle is not None: self.circle = np.uint16(np.around(self.circle)) diff --git a/src/TrafficLightDetector/paths.py b/src/TrafficLightDetector/paths.py index 29a176d..f69f9a6 100644 --- a/src/TrafficLightDetector/paths.py +++ b/src/TrafficLightDetector/paths.py @@ -17,7 +17,13 @@ PATHS = (LOGS_PATH, IMAGES_IN_PATH, IMAGES_OUT_PATH, SOUND_PATH) log_level = "DEBUG" if BASE_PATH.joinpath("debug").exists() else "INFO" # Set up logging -logger.add(LOGS_PATH.joinpath("detection.log"), format="{time} | {level} | {message}", level=log_level, rotation="1 MB", compression="zip") +logger.add( + LOGS_PATH.joinpath("detection.log"), + format="{time} | {level} | {message}", + level=log_level, + rotation="1 MB", + compression="zip", +) @logger.catch diff --git a/src/TrafficLightDetector/traffic_light_camera.py b/src/TrafficLightDetector/traffic_light_camera.py index 0ba336e..1dbe19c 100644 --- a/src/TrafficLightDetector/traffic_light_camera.py +++ b/src/TrafficLightDetector/traffic_light_camera.py @@ -1,12 +1,11 @@ import cv2 -from playsound import playsound +from magicsound import magicsound from TrafficLightDetector.paths import SOUND_PATH from TrafficLightDetector.traffic_light_detector import TrafficLightDetector class TrafficLightDetectorCamera(TrafficLightDetector): - def __init__(self, source: int, sound: bool = False) -> None: self.video_capture = cv2.VideoCapture(source) self.sound = sound @@ -29,4 +28,4 @@ class TrafficLightDetectorCamera(TrafficLightDetector): def _make_sound(self) -> None: if self.signal_color == "GREEN": - playsound(str(SOUND_PATH.joinpath("move.mp3"))) + magicsound(str(SOUND_PATH.joinpath("move.mp3"))) diff --git a/src/TrafficLightDetector/traffic_light_detector.py b/src/TrafficLightDetector/traffic_light_detector.py index 854bd4c..f316255 100644 --- a/src/TrafficLightDetector/traffic_light_detector.py +++ b/src/TrafficLightDetector/traffic_light_detector.py @@ -28,9 +28,27 @@ class TrafficLightDetector: self.roi = self.image if roi is None else roi self.size = self.roi.shape if roi is not None else self.image.shape hsv = cv2.cvtColor(self.image if roi is None else self.roi, cv2.COLOR_BGR2HSV) - self.red = ColorAttributes("RED", self.RED, self.RED_LOWER, self.RED_UPPER, hsv, minDist=80, param2=10) - self.yellow = ColorAttributes("YELLOW", self.YELLOW, self.YELLOW_LOWER, self.YELLOW_UPPER, hsv, minDist=60, param2=10) - self.green = ColorAttributes("GREEN", self.GREEN, self.GREEN_LOWER, self.GREEN_UPPER, hsv, minDist=30, param2=5) + self.red = ColorAttributes( + "RED", self.RED, self.RED_LOWER, self.RED_UPPER, hsv, minDist=80, param2=10 + ) + self.yellow = ColorAttributes( + "YELLOW", + self.YELLOW, + self.YELLOW_LOWER, + self.YELLOW_UPPER, + hsv, + minDist=60, + param2=10, + ) + self.green = ColorAttributes( + "GREEN", + self.GREEN, + self.GREEN_LOWER, + self.GREEN_UPPER, + hsv, + minDist=30, + param2=5, + ) self.colors = [self.red, self.yellow, self.green] self.detect_traffic_lights = detectTrafficLights self.signal_color = "" @@ -39,8 +57,10 @@ class TrafficLightDetector: gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY) # draw rectangle around traffic lights for x, y, width, height in self.CASCADE.detectMultiScale(gray, 1.2, 5): - cv2.rectangle(self.image, (x, y), (x + width, y + height), (255, 0, 0), self.BOUNDARY) - roi = self.image[y:y + height, x:x + width] + cv2.rectangle( + self.image, (x, y), (x + width, y + height), (255, 0, 0), self.BOUNDARY + ) + roi = self.image[y : y + height, x : x + width] self._set_image(self.image, roi) self._draw_circle() @@ -49,20 +69,46 @@ class TrafficLightDetector: if color.circle is None: continue for value in color.circle[0, :]: - if value[0] > self.size[1] or value[1] > self.size[0] or value[1] > self.size[0] * self.BOUNDARY: + if ( + value[0] > self.size[1] + or value[1] > self.size[0] + or value[1] > self.size[0] * self.BOUNDARY + ): continue h, s = 0, 0 for i in range(-self.RADIUS, self.RADIUS): for j in range(-self.RADIUS, self.RADIUS): - if (value[1] + i) >= self.size[0] or (value[0] + j) >= self.size[1]: + if (value[1] + i) >= self.size[0] or ( + value[0] + j + ) >= self.size[1]: continue h += color.mask[value[1] + i, value[0] + j] s += 1 if h / s > 100: - cv2.circle(self.roi if self.detect_traffic_lights else self.image, (value[0], value[1]), value[2] + 10, color.color, 2) # draws circle - cv2.circle(color.mask, (value[0], value[1]), value[2] + 30, (255, 255, 255), 2) + cv2.circle( + self.roi if self.detect_traffic_lights else self.image, + (value[0], value[1]), + value[2] + 10, + color.color, + 2, + ) # draws circle + cv2.circle( + color.mask, + (value[0], value[1]), + value[2] + 30, + (255, 255, 255), + 2, + ) if self.TEXT: - cv2.putText(self.roi if self.detect_traffic_lights else self.image, color.name, - (value[0], value[1]), self.FONT, 1, color.color, 2, cv2.LINE_AA) # draws text + cv2.putText( + self.roi if self.detect_traffic_lights else self.image, + color.name, + (value[0], value[1]), + self.FONT, + 1, + color.color, + 2, + cv2.LINE_AA, + ) # draws text self.signal_color = color.name diff --git a/src/TrafficLightDetector/traffic_light_images.py b/src/TrafficLightDetector/traffic_light_images.py index f891bbd..265027c 100644 --- a/src/TrafficLightDetector/traffic_light_images.py +++ b/src/TrafficLightDetector/traffic_light_images.py @@ -7,7 +7,6 @@ from TrafficLightDetector.traffic_light_detector import TrafficLightDetector class TrafficLightDetectorImages(TrafficLightDetector): - def __init__(self, path: Path) -> None: self.path = path self._set_image(cv2.imread(str(self.path))) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_package.py b/tests/test_package.py deleted file mode 100644 index 1ea4655..0000000 --- a/tests/test_package.py +++ /dev/null @@ -1,10 +0,0 @@ -import unittest - - -class TestTask(unittest.TestCase): - def test_nothing(self): - pass - - -if __name__ == "__main__": - unittest.main() diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 184efe8..0000000 --- a/tox.ini +++ /dev/null @@ -1,27 +0,0 @@ -[tox] -minversion = 3.8.0 -envlist = py310, flake8, mypy -isolated_build = true - -[gh-actions] -python = - 3.10: py310, mypy, flake8 - -[testenv] -setenv = - PYTHONPATH = {toxinidir} -deps = - -r{toxinidir}/requirements_dev.txt -commands = - pytest --basetemp={envtmpdir} - -[testenv:flake8] -basepython = python3.10 -deps = flake8 -commands = flake8 src tests - -[testenv:mypy] -basepython = python3.10 -deps = - -r{toxinidir}/requirements_dev.txt -commands = mypy src