diff --git a/.github/workflows/cpp_linter.yaml b/.github/workflows/cpp_linter.yaml new file mode 100644 index 0000000..3286734 --- /dev/null +++ b/.github/workflows/cpp_linter.yaml @@ -0,0 +1,26 @@ +name: C++ Linters + +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + +jobs: + cpp_linter: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Python Setup + uses: actions/setup-python@v3 + with: + python-version: 3.13 + + - name: Install libraries + run: make python-install-development + + - name: clang-tidy + run: make clang-tidy diff --git a/.github/workflows/cpp_linters.yaml b/.github/workflows/cpp_linters.yaml deleted file mode 100644 index b8be389..0000000 --- a/.github/workflows/cpp_linters.yaml +++ /dev/null @@ -1,34 +0,0 @@ -name: CPPLinters - -on: - push: - branches: - - '**' - pull_request: - branches: - - '**' - -jobs: - cpplinter: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Python Setup - uses: actions/setup-python@v3 - with: - python-version: 3.13 - - - name: Install libraries - run: make python-install-editable - - - name: Install pipx - run: | - sudo apt update - sudo apt install pipx - pipx ensurepath - sudo pipx ensurepath --global - - name: Install cpplint - run: pipx install cpplint - - name: Run cpplint - run: cpplint --filter=-whitespace/line_length,-whitespace/parens ./src/cpp/fast/* diff --git a/.github/workflows/linters.yaml b/.github/workflows/python_linter.yaml similarity index 90% rename from .github/workflows/linters.yaml rename to .github/workflows/python_linter.yaml index e696377..ecc7912 100644 --- a/.github/workflows/linters.yaml +++ b/.github/workflows/python_linter.yaml @@ -1,32 +1,32 @@ -name: Linters - -on: - push: - branches: - - '**' - pull_request: - branches: - - '**' - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Python Setup - uses: actions/setup-python@v3 - with: - python-version: 3.13 - - - name: Install libraries - run: make python-install-development - - - name: mypy - run: make mypy - - - name: ruff - run: make ruff - - - name: flake8 - run: make flake8 +name: Python Linters + +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + +jobs: + python_linter: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Python Setup + uses: actions/setup-python@v3 + with: + python-version: 3.13 + + - name: Install libraries + run: make python-install-development + + - name: mypy + run: make mypy + + - name: ruff + run: make ruff + + - name: flake8 + run: make flake8 diff --git a/Makefile b/Makefile index ec2a6d8..1618561 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,9 @@ python-install-development: python-install-editable: pip3 install -e .[development] +clang-tidy: + clang-tidy $(shell find ./src/cpp/ -name '*.hpp' -o -name '*.cpp') -- -I$(shell python3 -c "import sysconfig; print(sysconfig.get_path('include'))") -I$(shell python3 -c "import numpy; print(numpy.get_include())") + mypy: mypy ./src/python/ @@ -18,4 +21,3 @@ ruff: flake8: flake8 ./src/python/ - diff --git a/README.md b/README.md index 4940244..e92b936 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # CPython-based Extension for Image Manipulation +[![C++ Linters](https://github.com/ForNeus57/advanced-python-programming-project/actions/workflows/cpp_linter.yaml/badge.svg)](https://github.com/ForNeus57/advanced-python-programming-project/actions/workflows/cpp_linter.yaml) +[![Python Linters](https://github.com/ForNeus57/advanced-python-programming-project/actions/workflows/python_linter.yaml/badge.svg)](https://github.com/ForNeus57/advanced-python-programming-project/actions/workflows/python_linter.yaml) + * [CPython-based Extension for Image Manipulation](#cpython-based-extension-for-image-manipulation) * [Team members](#team-members) diff --git a/src/cpp/fast/foo.cpp b/src/cpp/fast/foo.cpp index 2b1f510..0d7b485 100644 --- a/src/cpp/fast/foo.cpp +++ b/src/cpp/fast/foo.cpp @@ -9,31 +9,39 @@ numpy_system(PyObject *self, PyObject *args) int sts; if (!PyArg_ParseTuple(args, "s", &command)) + { return NULL; + } + sts = system(command); return PyLong_FromLong(sts); } static PyObject * -numpy_add(PyObject *self, PyObject *args){ +numpy_add(PyObject *self, PyObject *args) +{ PyArrayObject *arr; PyArg_ParseTuple(args, "O", &arr); - if(PyErr_Occurred()){ + if(PyErr_Occurred()) + { return NULL; } - if(!PyArray_Check(arr) || PyArray_TYPE(arr) != NPY_DOUBLE) { + if(!PyArray_Check(arr) || PyArray_TYPE(arr) != NPY_DOUBLE) + { PyErr_SetString(PyExc_TypeError, "Argument must be a numpy array of type double!"); return NULL; } - double *data = (double *) PyArray_DATA(arr); + double *data = reinterpret_cast(PyArray_DATA(arr)); int64_t size = PyArray_SIZE(arr); - double total=0; - for (int i=0; i < size; i++){ + double total = 0; + for (int i = 0; i < size; i++) + { total += data[i]; } + return PyFloat_FromDouble(total); } @@ -42,13 +50,15 @@ static PyObject *SpamError = NULL; static int numpy_module_exec(PyObject *m) { - if (SpamError != NULL) { + if (SpamError != NULL) + { PyErr_SetString(PyExc_ImportError, "cannot initialize numpy module more than once"); return -1; } SpamError = PyErr_NewException("numpy.error", NULL, NULL); - if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) { + if (PyModule_AddObjectRef(m, "SpamError", SpamError) < 0) + { return -1; } @@ -62,7 +72,7 @@ static PyMethodDef numpy_methods[] = { }; static PyModuleDef_Slot numpy_module_slots[] = { - {Py_mod_exec, (void*) numpy_module_exec}, + {Py_mod_exec, reinterpret_cast(numpy_module_exec)}, {0, NULL} }; diff --git a/src/python/app/command/parser.py b/src/python/app/command/parser.py index 48fe02e..5d06ef8 100644 --- a/src/python/app/command/parser.py +++ b/src/python/app/command/parser.py @@ -34,8 +34,7 @@ def get_parser() -> ArgumentParser: subparser = parser.add_subparsers(required=True, help='Command to be performed on an image') - operation_class: type[Operation] - for operation_class in [Rotate90Operation, IdentityOperation, FlipOperation, BGR2RGBOperation, RollOperation, GrayscaleOperation, HistogramEqualizationOperation]: + for operation_class in available_commands(): operation = operation_class() operation_parser = subparser.add_parser(name=operation_class.name(), @@ -46,6 +45,7 @@ def get_parser() -> ArgumentParser: return parser + def prepare_command(command: Operation) -> Callable[[Namespace], int]: def wrapper(args: Namespace) -> int: @@ -61,3 +61,15 @@ def wrapper(args: Namespace) -> int: return 0 return wrapper + + +def available_commands(): + return [ + Rotate90Operation, + IdentityOperation, + FlipOperation, + BGR2RGBOperation, + RollOperation, + GrayscaleOperation, + HistogramEqualizationOperation, + ] diff --git a/src/python/app/imcli.py b/src/python/app/imcli.py index 0aeeaa4..a6f1346 100644 --- a/src/python/app/imcli.py +++ b/src/python/app/imcli.py @@ -1,3 +1,5 @@ +"""Main entrypoint for imcli program""" + from app.command.parser import get_parser @@ -5,5 +7,6 @@ def main() -> None: args = get_parser().parse_args() return args.func(args) + if __name__ == '__main__': main() diff --git a/src/python/app/io/bmp.py b/src/python/app/io/bmp.py index 44dc20d..8f8cb71 100644 --- a/src/python/app/io/bmp.py +++ b/src/python/app/io/bmp.py @@ -46,7 +46,8 @@ def read_format(self, file: BinaryIO) -> Image: assert panes == 1 assert bits_per_pixel in (1, 4, 8, 16, 24, 32) - compression_method, raw_bitmap_data_size, horizontal_resolution, vertical_resolution, num_colors, num_important_colors = \ + compression_method, raw_bitmap_data_size, horizontal_resolution, vertical_resolution, \ + num_colors, num_important_colors = \ struct.unpack('IIiiII', dib_header_no_size[12:]) assert compression_method == 0 assert raw_bitmap_data_size != 0 @@ -61,7 +62,8 @@ def read_format(self, file: BinaryIO) -> Image: num_colors_end = bits_per_pixel // 8 return Image(data=np.flip( np.flip( - np.frombuffer(bytes(image_bytes), dtype=np.uint8).reshape(image_height, image_width, num_colors_end)[:, :, ::-1], + np.frombuffer(bytes(image_bytes), + dtype=np.uint8).reshape(image_height, image_width, num_colors_end)[:, :, ::-1], axis=0 ), axis=1) diff --git a/src/python/app/io/format_factory.py b/src/python/app/io/format_factory.py index 85ac265..b9373cb 100644 --- a/src/python/app/io/format_factory.py +++ b/src/python/app/io/format_factory.py @@ -1,5 +1,4 @@ import enum -from typing import Self from app.error.unknown_format_exception import UnknownFormatException from app.io.bmp import BMPReader, BMPWriter @@ -11,7 +10,7 @@ class KnownFormat(enum.Enum): BMP = 0 @classmethod - def from_string(cls, data_format: str) -> Self: + def from_string(cls, data_format: str) -> 'KnownFormat': match data_format: case 'bmp': return cls.BMP @@ -24,7 +23,7 @@ def get_available_formats(cls) -> list[str]: return [e.name.lower() for e in cls] @classmethod - def default(cls) -> Self: + def default(cls) -> 'KnownFormat': return KnownFormat.BMP @@ -37,6 +36,7 @@ def get_reader_from_format(data_format: KnownFormat) -> FormatReader: case _: assert False, "unreachable" + def get_writer_from_format(data_format: KnownFormat) -> FormatWriter: match data_format: diff --git a/src/python/app/io/format_reader.py b/src/python/app/io/format_reader.py index 73dd446..e95f6f0 100644 --- a/src/python/app/io/format_reader.py +++ b/src/python/app/io/format_reader.py @@ -1,8 +1,6 @@ from abc import abstractmethod, ABC from typing import BinaryIO -import numpy as np - from app.image.image import Image diff --git a/src/python/app/operation/histogram_equalization.py b/src/python/app/operation/histogram_equalization.py index b203f32..66961e7 100644 --- a/src/python/app/operation/histogram_equalization.py +++ b/src/python/app/operation/histogram_equalization.py @@ -37,4 +37,4 @@ def equalize_chanel(self, image: np.ndarray) -> np.ndarray: cdf = (256 - 1) * cdf / cdf[-1] image_equalized = np.interp(image.flatten(), bins[:-1], cdf) - return image_equalized.reshape(image.shape) \ No newline at end of file + return image_equalized.reshape(image.shape) diff --git a/src/python/app/operation/operation.py b/src/python/app/operation/operation.py index 5e233a7..c605a28 100644 --- a/src/python/app/operation/operation.py +++ b/src/python/app/operation/operation.py @@ -1,8 +1,5 @@ from abc import ABC, abstractmethod from argparse import Namespace, ArgumentParser -from typing import final - -import numpy as np from app.image.image import Image