diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..cf561af --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,23 @@ +# .github/workflows/pre-commit.yml +name: Pre-commit + +on: + pull_request: + branches: ['**'] + push: + branches: ['**'] + +jobs: + pre-commit: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yaml similarity index 97% rename from .github/workflows/python-app.yml rename to .github/workflows/python-app.yaml index b981924..3ed2129 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yaml @@ -21,12 +21,12 @@ jobs: steps: - name: Checkout Repository uses: actions/checkout@v4 - + - name: Install uv uses: astral-sh/setup-uv@v6 - + - name: Sync dependencies run: uv sync --all-extras - + - name: Run tests run: uv run pytest diff --git a/.gitignore b/.gitignore index b7faf40..09be0ef 100644 --- a/.gitignore +++ b/.gitignore @@ -182,9 +182,9 @@ cython_debug/ .abstra/ # Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, +# and can be added to the global gitignore or merged into this file. However, if you prefer, # you could uncomment the following to ignore the entire vscode folder # .vscode/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..a9924e0 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +repos: + # housekeeping ๐Ÿงน + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + args: ['--maxkb=500'] + + # make code pretty ๐Ÿ’„ + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.0 + hooks: + - id: ruff + args: [ --fix ] + - id: ruff-format + + # scanning for leaked secrets ใŠ™๏ธ + - repo: https://github.com/gitleaks/gitleaks + rev: v8.18.2 + hooks: + - id: gitleaks diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..d4eca93 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "ms-python.python", + "charliermarsh.ruff", + "ms-python.pylint" + ], + "unwantedRecommendations": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fcdb532 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll.ruff": "explicit" + } + }, + "python.testing.pytestArgs": [ + "tests" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "files.exclude": { + "**/.mypy_cache": true, + "**/.pytest_cache": true, + "**/.ruff_cache": true + } +} diff --git a/ruff.toml b/ruff.toml index 20ceda6..cfdc5ef 100644 --- a/ruff.toml +++ b/ruff.toml @@ -45,7 +45,7 @@ select = [ # https://docs.astral.sh/ruff/rules/ "W", # PyCodeStyle Warnings "ANN", # flake8-annotations "B", # flake8-bugbear - "TC", # flake8-type-checking + "TCH", # flake8-type-checking "PTH", # flake8-use-pathlib "NPY", # NumPy specific rules "TID", # flake8-tidy-imports diff --git a/src/seaclips/collate.py b/src/seaclips/collate.py index 1cddb86..558f56a 100644 --- a/src/seaclips/collate.py +++ b/src/seaclips/collate.py @@ -4,7 +4,7 @@ # Licensed under the Apache License 2.0. See LICENSE file. from dataclasses import dataclass -from typing import Any +from typing import Any, Union import numpy as np import torch @@ -39,7 +39,7 @@ class SeaClipsBatch: image_infos: List of image metadata dictionaries annotations: List of lists of annotation dictionaries video_infos: List of video metadata dictionaries - reference_frames: (optional) BatchedReferenceFrames instance for reference frames + reference_frames: (optional) BatchedReferenceFrames instance for ref frames """ images: torch.Tensor @@ -60,7 +60,7 @@ def seaclips_collate_fn(batch: list[SeaClipsSample]) -> SeaClipsBatch: batch: List of samples from SeaClipsDataset.__getitem__ Returns: - SeaClipsBatch: Batched data structure containing images, annotations, and metadata. + SeaClipsBatch: Batched data structure containing images, annotations & metadata. Example: >>> from torch.utils.data import DataLoader @@ -161,7 +161,7 @@ def _collate_reference_frames( def _get_image_tensor( - image: Any, target_size: tuple[int, int] | None = None + image: Union[Image.Image, torch.Tensor], target_size: tuple[int, int] | None = None ) -> torch.Tensor: if isinstance(image, Image.Image): image_array = np.array(image) @@ -175,7 +175,7 @@ def _get_image_tensor( return image_tensor # --- 2. Pad to fixed size --- - C, H, W = image_tensor.shape + _, H, W = image_tensor.shape target_h, target_w = target_size if target_h < H or target_w < W: diff --git a/src/seaclips/dataset.py b/src/seaclips/dataset.py index b700e56..a8ce9da 100644 --- a/src/seaclips/dataset.py +++ b/src/seaclips/dataset.py @@ -91,14 +91,14 @@ def _load_metadata(self) -> None: self.videos_idx = {} return - with open(self.config.annotation_path, encoding="utf-8") as f: + with open(self.config.annotation_path, encoding="utf-8") as f: # noqa: PTH123 data = json.load(f) self.videos_data = data["videos"] self.videos_idx = {vid["id"]: vid for vid in self.videos_data} def _build_video_frame_index(self) -> None: - """Build index mapping video_id to sorted list of (frame_id, image_id, idx) tuples.""" + """Build index mapping video_id -> sorted list w (frame_id,image_id,idx).""" self.video_frames_idx = {} for idx, img_id in enumerate(self.image_ids): @@ -138,7 +138,8 @@ def __getitem__(self, idx: int) -> SeaClipsSample: - image_info: Image metadata (id, video_id, frame_id, etc.) - annotations: List of annotation dictionaries with parsed fields - video_info: Video metadata for this frame - - reference_frames: List of ReferenceFrame if load_reference_frames=True else None. + - reference_frames: List of ReferenceFrame if load_reference_frames=True + else None. """ if idx >= len(self): raise IndexError( @@ -198,7 +199,8 @@ def _get_reference_frames( if current_pos is None: raise ValueError( - f"Current index {current_idx} not found in video frames for video_id {video_id}" + f"Current index {current_idx} not found in video \ + frames for video_id {video_id}" ) for offset in self.config.ref_frame_range: @@ -207,7 +209,7 @@ def _get_reference_frames( # Check if reference frame exists if not (0 <= ref_pos < len(video_frames)): ref_pos = current_pos # Use current frame if out of bounds - offset = 0 + offset = 0 # noqa: PLW2901 _, ref_img_id, _ = video_frames[ref_pos] @@ -300,7 +302,7 @@ def filter_by_metadata( """Filter images by metadata attributes. Args: - weather: Filter by weather condition ("sunny", "overcast", "cloudy", "rainy") + weather: Filter by weather ("sunny", "overcast", "cloudy", "rainy") sea_state: Filter by sea state ("smooth", "wavy") camera_sensor: Filter by camera sensor type ("e-CAM", "Axis", "FLIR") water_reflections: Filter by water reflections presence ("yes", "no") diff --git a/src/seaclips/visualization.py b/src/seaclips/visualization.py index cadb514..8cb023a 100644 --- a/src/seaclips/visualization.py +++ b/src/seaclips/visualization.py @@ -5,9 +5,9 @@ import random -import matplotlib.patches as patches import matplotlib.pyplot as plt import numpy as np +from matplotlib import patches from matplotlib.figure import Figure from PIL import Image @@ -145,7 +145,7 @@ def show_sample( return fig - def show_sample_with_references( + def show_sample_with_references( # noqa: PLR0914 self, idx: int, show_labels: bool = True, @@ -218,7 +218,7 @@ def show_sample_with_references( spine.set_linewidth(3) spine.set_visible(True) - for idx in range(n_frames, len(axes)): + for idx in range(n_frames, len(axes)): # noqa: PLR1704 axes[idx].axis("off") video_info = self.dataset.videos_idx[sample.video_info["id"]] @@ -266,7 +266,7 @@ def show_video_sequence( axes = np.array([axes]) axes = axes.flatten() - for idx, (frame, ax) in enumerate(zip(frames_to_show, axes)): + for _, (frame, ax) in enumerate(zip(frames_to_show, axes)): image = frame.image annotations = frame.annotations img_info = frame.image_info @@ -330,7 +330,7 @@ def show_grid( return fig -def show_sample(dataset: SeaClipsDataset, idx: int, **kwargs) -> Figure: +def show_sample(dataset: SeaClipsDataset, idx: int, **kwargs) -> Figure: # noqa: ANN003 """Convenience function to visualize a sample. Args: diff --git a/tests/conftest.py b/tests/conftest.py index fac9d17..1ea23b7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,6 @@ import pytest from PIL import Image - from seaclips.dataset import ReferenceFrame, SeaClipsSample @@ -107,7 +106,7 @@ def dataset_root(tmp_path, sample_coco_data): img.save(split_dir / f"{i:06d}.png") annotation_file = tmp_path / f"seaclips-{split}.json" - with open(annotation_file, "w", encoding="utf-8") as f: + with open(annotation_file, "w", encoding="utf-8") as f: # noqa: PTH123 json.dump(sample_coco_data, f) return tmp_path diff --git a/tests/test_collate.py b/tests/test_collate.py index 4084564..a480579 100644 --- a/tests/test_collate.py +++ b/tests/test_collate.py @@ -1,8 +1,6 @@ """Tests for collate functions.""" -import pytest import torch - from seaclips.collate import seaclips_collate_fn diff --git a/tests/test_config.py b/tests/test_config.py index eef11c2..81226c5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,7 +4,6 @@ from pathlib import Path import pytest - from seaclips.config import SeaClipsConfig diff --git a/tests/test_dataset.py b/tests/test_dataset.py index 74bd228..afcd29b 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -3,7 +3,6 @@ import pytest from PIL import Image - from seaclips.config import SeaClipsConfig from seaclips.dataset import SeaClipsDataset