diff --git a/.github/collect_env.py b/.github/collect_env.py index f3303b313..95e39d5a0 100644 --- a/.github/collect_env.py +++ b/.github/collect_env.py @@ -226,10 +226,7 @@ def get_os(run_lambda): def get_env_info(): run_lambda = run - if HOLOCRON_AVAILABLE: - holocron_str = holocron.__version__ - else: - holocron_str = "N/A" + holocron_str = holocron.__version__ if HOLOCRON_AVAILABLE else "N/A" if TORCH_AVAILABLE: torch_str = torch.__version__ @@ -237,10 +234,7 @@ def get_env_info(): else: torch_str = cuda_available_str = "N/A" - if TORCHVISION_AVAILABLE: - torchvision_str = torchvision.__version__ - else: - torchvision_str = "N/A" + torchvision_str = torchvision.__version__ if TORCHVISION_AVAILABLE else "N/A" return SystemEnv( holocron_version=holocron_str, @@ -273,14 +267,14 @@ def get_env_info(): def pretty_str(envinfo): def replace_nones(dct, replacement="Could not collect"): - for key in dct.keys(): + for key in dct: if dct[key] is not None: continue dct[key] = replacement return dct def replace_bools(dct, true="Yes", false="No"): - for key in dct.keys(): + for key in dct: if dct[key] is True: dct[key] = true elif dct[key] is False: diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index de1368552..7493241b4 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -32,7 +32,7 @@ jobs: pip install -e . --upgrade - name: Import package run: python -c "import holocron; print(holocron.__version__)" - + pypi: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index 9dfed6d33..e0a75c79d 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -15,14 +15,13 @@ jobs: python: [3.9] steps: - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} architecture: x64 - name: Run ruff run: | - pip install ruff==0.1.0 + pip install ruff==0.1.11 ruff --version ruff check --diff . @@ -34,8 +33,7 @@ jobs: python: [3.9] steps: - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v1 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} architecture: x64 @@ -53,7 +51,7 @@ jobs: mypy --version mypy - black: + ruff-format: runs-on: ${{ matrix.os }} strategy: matrix: @@ -61,18 +59,17 @@ jobs: python: [3.9] steps: - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} architecture: x64 - - name: Run black + - name: Run ruff run: | - pip install "black==23.3.0" - black --version - black --check --diff . + pip install ruff==0.1.11 + ruff --version + ruff format --check --diff . - bandit: + precommit-hooks: runs-on: ${{ matrix.os }} strategy: matrix: @@ -80,13 +77,13 @@ jobs: python: [3.9] steps: - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} architecture: x64 - - name: Run bandit + - name: Run ruff run: | - pip install bandit[toml] - bandit --version - bandit -r . -c pyproject.toml + pip install pre-commit + pre-commit install + pre-commit --version + pre-commit run --all-files diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 413583cc4..dce037f00 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -51,7 +51,7 @@ jobs: with: flags: unittests fail_ci_if_error: true - + api: runs-on: ${{ matrix.os }} strategy: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 29f5d5b58..6dccf61ff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,28 +2,28 @@ default_language_version: python: python3.9 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: + - id: check-added-large-files - id: check-ast + - id: check-case-conflict + - id: check-json + - id: check-merge-conflict + - id: check-symlinks + - id: check-toml + - id: check-xml - id: check-yaml exclude: .conda - - id: check-toml - - id: check-json - - id: check-added-large-files - - id: end-of-file-fixer - - id: trailing-whitespace - id: debug-statements language_version: python3 - - id: check-merge-conflict + - id: end-of-file-fixer - id: no-commit-to-branch args: ['--branch', 'main'] - - repo: https://github.com/psf/black - rev: 23.3.0 - hooks: - - id: black + - id: trailing-whitespace - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.1.0' + rev: 'v0.1.11' hooks: - id: ruff args: - --fix + - id: ruff-format diff --git a/Makefile b/Makefile index 0bee9de80..81bffa1de 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,12 @@ # this target runs checks on all files quality: + ruff format --check . ruff check . mypy - black --check . - bandit -r . -c pyproject.toml # this target runs checks on all files and potentially modifies some of them style: - black . + ruff format . ruff --fix . # Run tests for the library diff --git a/api/app/config.py b/api/app/config.py index f7bba490b..d4507aaf1 100644 --- a/api/app/config.py +++ b/api/app/config.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/api/app/main.py b/api/app/main.py index 9a0addc88..20fc0134e 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/api/app/routes/classification.py b/api/app/routes/classification.py index 53cabbec8..a484e9af6 100644 --- a/api/app/routes/classification.py +++ b/api/app/routes/classification.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -12,8 +12,8 @@ router = APIRouter() -@router.post("/", response_model=ClsCandidate, status_code=status.HTTP_200_OK, summary="Perform image classification") -async def classify(file: UploadFile = File(...)): +@router.post("/", status_code=status.HTTP_200_OK, summary="Perform image classification") +async def classify(file: UploadFile = File(...)) -> ClsCandidate: """Runs holocron vision model to analyze the input image""" probs = classify_image(decode_image(file.file.read())) diff --git a/api/app/schemas.py b/api/app/schemas.py index 80608d153..e0999d930 100644 --- a/api/app/schemas.py +++ b/api/app/schemas.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/api/app/vision.py b/api/app/vision.py index 0080e4ef3..3c35ae75a 100644 --- a/api/app/vision.py +++ b/api/app/vision.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/api/docker-compose.yml b/api/docker-compose.yml index 65d34e2f7..31e4d9e7b 100644 --- a/api/docker-compose.yml +++ b/api/docker-compose.yml @@ -16,7 +16,7 @@ services: - "traefik.http.routers.api.service=backend@docker" - "traefik.http.routers.api.tls={}" - "traefik.http.services.backend.loadbalancer.server.port=8050" - + traefik: image: traefik:v2.9.6 volumes: diff --git a/demo/app.py b/demo/app.py index 668369e84..96f22d01c 100644 --- a/demo/app.py +++ b/demo/app.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/docs/source/conf.py b/docs/source/conf.py index d7f3a0cc9..ee6c1927b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -22,7 +22,7 @@ from tabulate import tabulate -sys.path.insert(0, Path().resolve().parent.parent) +sys.path.insert(0, Path().cwd().parent.parent) import holocron @@ -217,10 +217,12 @@ def inject_checkpoint_metadata(app, what, name, obj, options, lines): # Loading Meta meta = field.value.meta - table.append(("url", f"`link <{meta.url}>`__")) - table.append(("sha256", meta.sha256[:16])) - table.append(("size", f"{meta.size / 1024**2:.1f}MB")) - table.append(("num_params", f"{meta.num_params / 1e6:.1f}M")) + table.extend(( + ("url", f"`link <{meta.url}>`__"), + ("sha256", meta.sha256[:16]), + ("size", f"{meta.size / 1024 ** 2:.1f}MB"), + ("num_params", f"{meta.num_params / 1000000.0:.1f}M"), + )) # Wrap the text max_visible = 3 v = meta.categories @@ -311,9 +313,7 @@ def add_ga_javascript(app, pagename, templatename, context, doctree): gtag('js', new Date()); gtag('config', '{0}'); - """.format( - app.config.googleanalytics_id - ) + """.format(app.config.googleanalytics_id) context["metatags"] = metatags diff --git a/holocron/models/checkpoints.py b/holocron/models/checkpoints.py index 8ae092ecc..42898b163 100644 --- a/holocron/models/checkpoints.py +++ b/holocron/models/checkpoints.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023, François-Guillaume Fernandez. +# Copyright (C) 2023-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/models/classification/convnext.py b/holocron/models/classification/convnext.py index e20c1a189..040df8174 100644 --- a/holocron/models/classification/convnext.py +++ b/holocron/models/classification/convnext.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -46,7 +46,7 @@ class LayerScale(nn.Module): def __init__(self, chans: int, scale: float = 1e-6) -> None: super().__init__() - self.register_parameter("weight", nn.Parameter(scale * torch.ones(chans))) + self.register_parameter("weight", nn.Parameter(scale * torch.ones(chans))) # type: ignore[arg-type] def forward(self, x: torch.Tensor) -> torch.Tensor: return x * self.weight.reshape(1, -1, *((1,) * (x.ndim - 2))) @@ -168,19 +168,17 @@ def __init__( block_idx += _num_blocks super().__init__( - OrderedDict( - [ - ("features", nn.Sequential(*_layers)), - ("pool", GlobalAvgPool2d(flatten=True)), - ( - "head", - nn.Sequential( - nn.LayerNorm(planes[-1], eps=1e-6), - nn.Linear(planes[-1], num_classes), - ), + OrderedDict([ + ("features", nn.Sequential(*_layers)), + ("pool", GlobalAvgPool2d(flatten=True)), + ( + "head", + nn.Sequential( + nn.LayerNorm(planes[-1], eps=1e-6), + nn.Linear(planes[-1], num_classes), ), - ] - ) + ), + ]) ) # Init all layers diff --git a/holocron/models/classification/darknet.py b/holocron/models/classification/darknet.py index a7855da2a..61cd13b04 100644 --- a/holocron/models/classification/darknet.py +++ b/holocron/models/classification/darknet.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -43,36 +43,32 @@ def __init__( in_chans = [stem_channels] + [_layout[-1] for _layout in layout[:-1]] super().__init__( - OrderedDict( - [ - ( - "stem", - nn.Sequential( - *conv_sequence( - in_channels, - stem_channels, - act_layer, - norm_layer, - drop_layer, - conv_layer, - kernel_size=7, - padding=3, - stride=2, - bias=(norm_layer is None), - ) - ), + OrderedDict([ + ( + "stem", + nn.Sequential( + *conv_sequence( + in_channels, + stem_channels, + act_layer, + norm_layer, + drop_layer, + conv_layer, + kernel_size=7, + padding=3, + stride=2, + bias=(norm_layer is None), + ) ), - ( - "layers", - nn.Sequential( - *[ - self._make_layer([_in_chans, *planes], act_layer, norm_layer, drop_layer, conv_layer) - for _in_chans, planes in zip(in_chans, layout) - ] - ), - ), - ] - ) + ), + ( + "layers", + nn.Sequential(*[ + self._make_layer([_in_chans, *planes], act_layer, norm_layer, drop_layer, conv_layer) + for _in_chans, planes in zip(in_chans, layout) + ]), + ), + ]) ) init_module(self, "leaky_relu") @@ -118,18 +114,14 @@ def __init__( conv_layer: Optional[Callable[..., nn.Module]] = None, ) -> None: super().__init__( - OrderedDict( - [ - ( - "features", - DarknetBodyV1( - layout, in_channels, stem_channels, act_layer, norm_layer, drop_layer, conv_layer - ), - ), - ("pool", GlobalAvgPool2d(flatten=True)), - ("classifier", nn.Linear(layout[2][-1], num_classes)), - ] - ) + OrderedDict([ + ( + "features", + DarknetBodyV1(layout, in_channels, stem_channels, act_layer, norm_layer, drop_layer, conv_layer), + ), + ("pool", GlobalAvgPool2d(flatten=True)), + ("classifier", nn.Linear(layout[2][-1], num_classes)), + ]) ) init_module(self, "leaky_relu") diff --git a/holocron/models/classification/darknetv2.py b/holocron/models/classification/darknetv2.py index 21efb41e5..c82405b33 100644 --- a/holocron/models/classification/darknetv2.py +++ b/holocron/models/classification/darknetv2.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -50,37 +50,33 @@ def __init__( in_chans = [stem_channels] + [_layout[0] for _layout in layout[:-1]] super().__init__( - OrderedDict( - [ - ( - "stem", - nn.Sequential( - *conv_sequence( - in_channels, - stem_channels, - act_layer, - norm_layer, - drop_layer, - conv_layer, - kernel_size=3, - padding=1, - bias=(norm_layer is None), - ) - ), + OrderedDict([ + ( + "stem", + nn.Sequential( + *conv_sequence( + in_channels, + stem_channels, + act_layer, + norm_layer, + drop_layer, + conv_layer, + kernel_size=3, + padding=1, + bias=(norm_layer is None), + ) ), - ( - "layers", - nn.Sequential( - *[ - self._make_layer( - num_blocks, _in_chans, out_chans, act_layer, norm_layer, drop_layer, conv_layer - ) - for _in_chans, (out_chans, num_blocks) in zip(in_chans, layout) - ] - ), - ), - ] - ) + ), + ( + "layers", + nn.Sequential(*[ + self._make_layer( + num_blocks, _in_chans, out_chans, act_layer, norm_layer, drop_layer, conv_layer + ) + for _in_chans, (out_chans, num_blocks) in zip(in_chans, layout) + ]), + ), + ]) ) self.passthrough = passthrough @@ -167,18 +163,16 @@ def __init__( conv_layer: Optional[Callable[..., nn.Module]] = None, ) -> None: super().__init__( - OrderedDict( - [ - ( - "features", - DarknetBodyV2( - layout, in_channels, stem_channels, False, act_layer, norm_layer, drop_layer, conv_layer - ), + OrderedDict([ + ( + "features", + DarknetBodyV2( + layout, in_channels, stem_channels, False, act_layer, norm_layer, drop_layer, conv_layer ), - ("classifier", nn.Conv2d(layout[-1][0], num_classes, 1)), - ("pool", GlobalAvgPool2d(flatten=True)), - ] - ) + ), + ("classifier", nn.Conv2d(layout[-1][0], num_classes, 1)), + ("pool", GlobalAvgPool2d(flatten=True)), + ]) ) init_module(self, "leaky_relu") diff --git a/holocron/models/classification/darknetv3.py b/holocron/models/classification/darknetv3.py index 1fca6fb5d..3c7972483 100644 --- a/holocron/models/classification/darknetv3.py +++ b/holocron/models/classification/darknetv3.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -90,37 +90,33 @@ def __init__( in_chans = [stem_channels] + [_layout[0] for _layout in layout[:-1]] super().__init__( - OrderedDict( - [ - ( - "stem", - nn.Sequential( - *conv_sequence( - in_channels, - stem_channels, - act_layer, - norm_layer, - drop_layer, - conv_layer, - kernel_size=3, - padding=1, - bias=(norm_layer is None), - ) - ), + OrderedDict([ + ( + "stem", + nn.Sequential( + *conv_sequence( + in_channels, + stem_channels, + act_layer, + norm_layer, + drop_layer, + conv_layer, + kernel_size=3, + padding=1, + bias=(norm_layer is None), + ) ), - ( - "layers", - nn.Sequential( - *[ - self._make_layer( - num_blocks, _in_chans, out_chans, act_layer, norm_layer, drop_layer, conv_layer - ) - for _in_chans, (out_chans, num_blocks) in zip(in_chans, layout) - ] - ), - ), - ] - ) + ), + ( + "layers", + nn.Sequential(*[ + self._make_layer( + num_blocks, _in_chans, out_chans, act_layer, norm_layer, drop_layer, conv_layer + ) + for _in_chans, (out_chans, num_blocks) in zip(in_chans, layout) + ]), + ), + ]) ) self.num_features = num_features @@ -146,12 +142,10 @@ def _make_layer( stride=2, bias=(norm_layer is None), ) - layers.extend( - [ - ResBlock(out_planes, out_planes // 2, act_layer, norm_layer, drop_layer, conv_layer) - for _ in range(num_blocks) - ] - ) + layers.extend([ + ResBlock(out_planes, out_planes // 2, act_layer, norm_layer, drop_layer, conv_layer) + for _ in range(num_blocks) + ]) return nn.Sequential(*layers) @@ -184,18 +178,14 @@ def __init__( conv_layer: Optional[Callable[..., nn.Module]] = None, ) -> None: super().__init__( - OrderedDict( - [ - ( - "features", - DarknetBodyV3( - layout, in_channels, stem_channels, 1, act_layer, norm_layer, drop_layer, conv_layer - ), - ), - ("pool", GlobalAvgPool2d(flatten=True)), - ("classifier", nn.Linear(layout[-1][0], num_classes)), - ] - ) + OrderedDict([ + ( + "features", + DarknetBodyV3(layout, in_channels, stem_channels, 1, act_layer, norm_layer, drop_layer, conv_layer), + ), + ("pool", GlobalAvgPool2d(flatten=True)), + ("classifier", nn.Linear(layout[-1][0], num_classes)), + ]) ) init_module(self, "leaky_relu") diff --git a/holocron/models/classification/darknetv4.py b/holocron/models/classification/darknetv4.py index e5f5d1f91..cc13dffd4 100644 --- a/holocron/models/classification/darknetv4.py +++ b/holocron/models/classification/darknetv4.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -137,37 +137,31 @@ def __init__( in_chans = [stem_channels] + [_layout[0] for _layout in layout[:-1]] super().__init__( - OrderedDict( - [ - ( - "stem", - nn.Sequential( - *conv_sequence( - in_channels, - stem_channels, - act_layer, - norm_layer, - drop_layer, - conv_layer, - kernel_size=3, - padding=1, - bias=(norm_layer is None), - ) - ), - ), - ( - "stages", - nn.Sequential( - *[ - CSPStage( - _in_chans, out_chans, num_blocks, act_layer, norm_layer, drop_layer, conv_layer - ) - for _in_chans, (out_chans, num_blocks) in zip(in_chans, layout) - ] - ), + OrderedDict([ + ( + "stem", + nn.Sequential( + *conv_sequence( + in_channels, + stem_channels, + act_layer, + norm_layer, + drop_layer, + conv_layer, + kernel_size=3, + padding=1, + bias=(norm_layer is None), + ) ), - ] - ) + ), + ( + "stages", + nn.Sequential(*[ + CSPStage(_in_chans, out_chans, num_blocks, act_layer, norm_layer, drop_layer, conv_layer) + for _in_chans, (out_chans, num_blocks) in zip(in_chans, layout) + ]), + ), + ]) ) self.num_features = num_features @@ -202,25 +196,23 @@ def __init__( conv_layer: Optional[Callable[..., nn.Module]] = None, ) -> None: super().__init__( - OrderedDict( - [ - ( - "features", - DarknetBodyV4( - layout, - in_channels, - stem_channels, - num_features, - act_layer, - norm_layer, - drop_layer, - conv_layer, - ), + OrderedDict([ + ( + "features", + DarknetBodyV4( + layout, + in_channels, + stem_channels, + num_features, + act_layer, + norm_layer, + drop_layer, + conv_layer, ), - ("pool", GlobalAvgPool2d(flatten=True)), - ("classifier", nn.Linear(layout[-1][0], num_classes)), - ] - ) + ), + ("pool", GlobalAvgPool2d(flatten=True)), + ("classifier", nn.Linear(layout[-1][0], num_classes)), + ]) ) init_module(self, "leaky_relu") diff --git a/holocron/models/classification/mobileone.py b/holocron/models/classification/mobileone.py index f4087de36..56a1442f3 100644 --- a/holocron/models/classification/mobileone.py +++ b/holocron/models/classification/mobileone.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -47,22 +47,20 @@ def __init__( *conv_sequence(channels, channels, kernel_size=1, stride=stride, norm_layer=norm_layer, groups=channels) ), ) - _layers.extend( - [ - nn.Sequential( - *conv_sequence( - channels, - channels, - kernel_size=3, - padding=1, - stride=stride, - norm_layer=norm_layer, - groups=channels, - ) + _layers.extend([ + nn.Sequential( + *conv_sequence( + channels, + channels, + kernel_size=3, + padding=1, + stride=stride, + norm_layer=norm_layer, + groups=channels, ) - for _ in range(num_blocks) - ] - ) + ) + for _ in range(num_blocks) + ]) super().__init__(_layers) def forward(self, x: Tensor) -> Tensor: @@ -111,12 +109,10 @@ def __init__( if norm_layer is None: norm_layer = nn.BatchNorm2d _layers = [norm_layer(out_channels)] if out_channels == in_channels else [] - _layers.extend( - [ - nn.Sequential(*conv_sequence(in_channels, out_channels, kernel_size=1, norm_layer=norm_layer)) - for _ in range(num_blocks) - ] - ) + _layers.extend([ + nn.Sequential(*conv_sequence(in_channels, out_channels, kernel_size=1, norm_layer=norm_layer)) + for _ in range(num_blocks) + ]) super().__init__(_layers) def forward(self, x: Tensor) -> Tensor: @@ -207,24 +203,20 @@ def __init__( # Stride & channel changes _stage = [MobileOneBlock(in_planes, _planes, overparam_factor, 2, act_layer, norm_layer)] # Depth - _stage.extend( - [ - MobileOneBlock(_planes, _planes, overparam_factor, 1, act_layer, norm_layer) - for _ in range(_num_blocks - 1) - ] - ) + _stage.extend([ + MobileOneBlock(_planes, _planes, overparam_factor, 1, act_layer, norm_layer) + for _ in range(_num_blocks - 1) + ]) in_planes = _planes _layers.append(nn.Sequential(*_stage)) super().__init__( - OrderedDict( - [ - ("features", nn.Sequential(*_layers)), - ("pool", GlobalAvgPool2d(flatten=True)), - ("head", nn.Linear(in_planes, num_classes)), - ] - ) + OrderedDict([ + ("features", nn.Sequential(*_layers)), + ("pool", GlobalAvgPool2d(flatten=True)), + ("head", nn.Linear(in_planes, num_classes)), + ]) ) # Init all layers diff --git a/holocron/models/classification/pyconv_resnet.py b/holocron/models/classification/pyconv_resnet.py index 893ee61e3..a664f0ac5 100644 --- a/holocron/models/classification/pyconv_resnet.py +++ b/holocron/models/classification/pyconv_resnet.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/models/classification/repvgg.py b/holocron/models/classification/repvgg.py index 0a1e56b6c..c28db61fb 100644 --- a/holocron/models/classification/repvgg.py +++ b/holocron/models/classification/repvgg.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021-2023, François-Guillaume Fernandez. +# Copyright (C) 2021-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -52,16 +52,14 @@ def __init__( if act_layer is None: act_layer = nn.ReLU(inplace=True) - self.branches: Union[nn.Conv2d, nn.ModuleList] = nn.ModuleList( - [ - nn.Sequential( - *conv_sequence(inplanes, planes, None, norm_layer, kernel_size=3, padding=1, stride=stride), - ), - nn.Sequential( - *conv_sequence(inplanes, planes, None, norm_layer, kernel_size=1, padding=0, stride=stride), - ), - ] - ) + self.branches: Union[nn.Conv2d, nn.ModuleList] = nn.ModuleList([ + nn.Sequential( + *conv_sequence(inplanes, planes, None, norm_layer, kernel_size=3, padding=1, stride=stride), + ), + nn.Sequential( + *conv_sequence(inplanes, planes, None, norm_layer, kernel_size=1, padding=0, stride=stride), + ), + ]) self.activation = act_layer @@ -71,11 +69,7 @@ def __init__( self.branches.append(norm_layer(planes)) def forward(self, x: torch.Tensor) -> torch.Tensor: - if isinstance(self.branches, nn.Conv2d): - out = self.branches(x) - else: - out = sum(branch(x) for branch in self.branches) - + out = self.branches(x) if isinstance(self.branches, nn.Conv2d) else sum(branch(x) for branch in self.branches) return self.activation(out) def reparametrize(self) -> None: @@ -158,13 +152,11 @@ def __init__( _stages.append(nn.Sequential(*_layers)) super().__init__( - OrderedDict( - [ - ("features", nn.Sequential(*_stages)), - ("pool", GlobalAvgPool2d(flatten=True)), - ("head", nn.Linear(chans[-1], num_classes)), - ] - ) + OrderedDict([ + ("features", nn.Sequential(*_stages)), + ("pool", GlobalAvgPool2d(flatten=True)), + ("head", nn.Linear(chans[-1], num_classes)), + ]) ) # Init all layers init.init_module(self, nonlinearity="relu") @@ -181,7 +173,6 @@ def _repvgg( checkpoint: Union[Checkpoint, None], progress: bool, num_blocks: List[int], - out_chans: List[int], a: float, b: float, **kwargs: Any, @@ -236,7 +227,7 @@ def repvgg_a0( checkpoint, RepVGG_A0_Checkpoint.DEFAULT.value, ) - return _repvgg(checkpoint, progress, [1, 2, 4, 14, 1], [64, 64, 128, 256, 512], 0.75, 2.5, **kwargs) + return _repvgg(checkpoint, progress, [1, 2, 4, 14, 1], 0.75, 2.5, **kwargs) class RepVGG_A1_Checkpoint(Enum): @@ -284,7 +275,7 @@ def repvgg_a1( checkpoint, RepVGG_A1_Checkpoint.DEFAULT.value, ) - return _repvgg(checkpoint, progress, [1, 2, 4, 14, 1], [64, 64, 128, 256, 512], 1, 2.5, **kwargs) + return _repvgg(checkpoint, progress, [1, 2, 4, 14, 1], 1, 2.5, **kwargs) class RepVGG_A2_Checkpoint(Enum): @@ -332,7 +323,7 @@ def repvgg_a2( checkpoint, RepVGG_A2_Checkpoint.DEFAULT.value, ) - return _repvgg(checkpoint, progress, [1, 2, 4, 14, 1], [64, 64, 128, 256, 512], 1.5, 2.75, **kwargs) + return _repvgg(checkpoint, progress, [1, 2, 4, 14, 1], 1.5, 2.75, **kwargs) class RepVGG_B0_Checkpoint(Enum): @@ -380,7 +371,7 @@ def repvgg_b0( checkpoint, RepVGG_B0_Checkpoint.DEFAULT.value, ) - return _repvgg(checkpoint, progress, [1, 4, 6, 16, 1], [64, 64, 128, 256, 512], 1, 2.5, **kwargs) + return _repvgg(checkpoint, progress, [1, 4, 6, 16, 1], 1, 2.5, **kwargs) class RepVGG_B1_Checkpoint(Enum): @@ -428,7 +419,7 @@ def repvgg_b1( checkpoint, RepVGG_B1_Checkpoint.DEFAULT.value, ) - return _repvgg(checkpoint, progress, [1, 4, 6, 16, 1], [64, 64, 128, 256, 512], 2, 4, **kwargs) + return _repvgg(checkpoint, progress, [1, 4, 6, 16, 1], 2, 4, **kwargs) class RepVGG_B2_Checkpoint(Enum): @@ -476,7 +467,7 @@ def repvgg_b2( checkpoint, RepVGG_B2_Checkpoint.DEFAULT.value, ) - return _repvgg(checkpoint, progress, [1, 4, 6, 16, 1], [64, 64, 128, 256, 512], 2.5, 5, **kwargs) + return _repvgg(checkpoint, progress, [1, 4, 6, 16, 1], 2.5, 5, **kwargs) def repvgg_b3( @@ -502,4 +493,4 @@ def repvgg_b3( checkpoint, None, ) - return _repvgg(checkpoint, progress, [1, 4, 6, 16, 1], [64, 64, 128, 256, 512], 3, 5, **kwargs) + return _repvgg(checkpoint, progress, [1, 4, 6, 16, 1], 3, 5, **kwargs) diff --git a/holocron/models/classification/res2net.py b/holocron/models/classification/res2net.py index 1c138df82..dbfe7e2ea 100644 --- a/holocron/models/classification/res2net.py +++ b/holocron/models/classification/res2net.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -39,25 +39,23 @@ def __init__( self.scale = scale self.width = planes // scale - self.conv = nn.ModuleList( - [ - nn.Sequential( - *conv_sequence( - self.width, - self.width, - act_layer, - norm_layer, - drop_layer, - kernel_size=3, - stride=stride, - padding=1, - groups=groups, - bias=(norm_layer is None), - ) + self.conv = nn.ModuleList([ + nn.Sequential( + *conv_sequence( + self.width, + self.width, + act_layer, + norm_layer, + drop_layer, + kernel_size=3, + stride=stride, + padding=1, + groups=groups, + bias=(norm_layer is None), ) - for _ in range(max(1, scale - 1)) - ] - ) + ) + for _ in range(max(1, scale - 1)) + ]) if downsample: self.downsample = nn.AvgPool2d(kernel_size=3, stride=stride, padding=1) @@ -67,13 +65,10 @@ def __init__( def forward(self, x: torch.Tensor) -> torch.Tensor: # Split the channel dimension into groups of self.width channels split_x = torch.split(x, self.width, 1) - out = [] + out: List[torch.Tensor] = [] for idx, layer in enumerate(self.conv): # If downsampled, don't add previous branch - if idx == 0 or self.downsample is not None: - _res = split_x[idx] - else: - _res = out[-1] + split_x[idx] + _res = split_x[idx] if idx == 0 or self.downsample is not None else out[-1] + split_x[idx] out.append(layer(_res)) # Use the last chunk as shortcut connection if self.scale > 1: diff --git a/holocron/models/classification/resnet.py b/holocron/models/classification/resnet.py index ba6faf636..74adcb059 100644 --- a/holocron/models/classification/resnet.py +++ b/holocron/models/classification/resnet.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -338,13 +338,11 @@ def __init__( stride = 2 super().__init__( - OrderedDict( - [ - ("features", nn.Sequential(*_layers)), - ("pool", GlobalAvgPool2d(flatten=True)), - ("head", nn.Linear(num_repeats * in_planes, num_classes)), - ] - ) + OrderedDict([ + ("features", nn.Sequential(*_layers)), + ("pool", GlobalAvgPool2d(flatten=True)), + ("head", nn.Linear(num_repeats * in_planes, num_classes)), + ]) ) # Init all layers @@ -421,22 +419,20 @@ def _make_layer( **block_args, ) ] - layers.extend( - [ - block( - block.expansion * planes, - planes, - 1, - None, - base_width=width_per_group, - act_layer=act_layer, - norm_layer=norm_layer, - drop_layer=drop_layer, - **block_args, - ) - for _ in range(num_blocks - 1) - ] - ) + layers.extend([ + block( + block.expansion * planes, + planes, + 1, + None, + base_width=width_per_group, + act_layer=act_layer, + norm_layer=norm_layer, + drop_layer=drop_layer, + **block_args, + ) + for _ in range(num_blocks - 1) + ]) return nn.Sequential(*layers) diff --git a/holocron/models/classification/rexnet.py b/holocron/models/classification/rexnet.py index 7aaa29cc4..14c81ceed 100644 --- a/holocron/models/classification/rexnet.py +++ b/holocron/models/classification/rexnet.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -218,13 +218,11 @@ def __init__( ) super().__init__( - OrderedDict( - [ - ("features", nn.Sequential(*_layers)), - ("pool", GlobalAvgPool2d(flatten=True)), - ("head", nn.Sequential(nn.Dropout(dropout_ratio), nn.Linear(pen_channels, num_classes))), - ] - ) + OrderedDict([ + ("features", nn.Sequential(*_layers)), + ("pool", GlobalAvgPool2d(flatten=True)), + ("head", nn.Sequential(nn.Dropout(dropout_ratio), nn.Linear(pen_channels, num_classes))), + ]) ) # Init all layers diff --git a/holocron/models/classification/sknet.py b/holocron/models/classification/sknet.py index 35fcfeeda..f566254b3 100644 --- a/holocron/models/classification/sknet.py +++ b/holocron/models/classification/sknet.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -85,25 +85,23 @@ def __init__( **kwargs: Any, ) -> None: super().__init__() - self.path_convs = nn.ModuleList( - [ - nn.Sequential( - *conv_sequence( - in_channels, - out_channels, - act_layer, - norm_layer, - drop_layer, - kernel_size=3, - bias=(norm_layer is None), - dilation=idx + 1, - padding=idx + 1, - **kwargs, - ) + self.path_convs = nn.ModuleList([ + nn.Sequential( + *conv_sequence( + in_channels, + out_channels, + act_layer, + norm_layer, + drop_layer, + kernel_size=3, + bias=(norm_layer is None), + dilation=idx + 1, + padding=idx + 1, + **kwargs, ) - for idx in range(m) - ] - ) + ) + for idx in range(m) + ]) self.sa = SoftAttentionLayer(out_channels, sa_ratio, m, act_layer, norm_layer, drop_layer) def forward(self, x: torch.Tensor) -> torch.Tensor: diff --git a/holocron/models/classification/tridentnet.py b/holocron/models/classification/tridentnet.py index 5dcf5e738..c10df0db2 100644 --- a/holocron/models/classification/tridentnet.py +++ b/holocron/models/classification/tridentnet.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -38,10 +38,7 @@ def forward(self, x: torch.Tensor) -> torch.Tensor: raise ValueError("expected number of channels of input tensor to be a multiple of `num_branches`.") # Dilation for each chunk - if self.dilation[0] == 1: - dilations = [1] * self.num_branches - else: - dilations = [1 + idx for idx in range(self.num_branches)] + dilations = [1] * self.num_branches if self.dilation[0] == 1 else [1 + idx for idx in range(self.num_branches)] # Use shared weight to apply the convolution out = torch.cat( @@ -78,7 +75,6 @@ def __init__( act_layer: Optional[nn.Module] = None, norm_layer: Optional[Callable[[int], nn.Module]] = None, drop_layer: Optional[Callable[..., nn.Module]] = None, - conv_layer: Optional[Callable[..., nn.Module]] = None, **kwargs: Any, ) -> None: if norm_layer is None: @@ -102,6 +98,7 @@ def __init__( stride=1, bias=(norm_layer is None), dilation=1, + **kwargs, ), *conv_sequence( width, @@ -117,6 +114,7 @@ def __init__( groups=groups, bias=(norm_layer is None), dilation=3, + **kwargs, ), *conv_sequence( width, @@ -130,6 +128,7 @@ def __init__( stride=1, bias=(norm_layer is None), dilation=1, + **kwargs, ), ], downsample, diff --git a/holocron/models/detection/yolo.py b/holocron/models/detection/yolo.py index 662ae702a..0cceb15c4 100644 --- a/holocron/models/detection/yolo.py +++ b/holocron/models/detection/yolo.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -70,10 +70,10 @@ def _compute_losses( gt_labels = [t["labels"] for t in target] # GT xmin, ymin, xmax, ymax - if not all(torch.all(boxes >= 0) and torch.all(boxes <= 1) for boxes in gt_boxes): + if not all(torch.all(boxes >= 0) and torch.all(boxes <= 1) for boxes in gt_boxes): # type: ignore[call-overload] raise ValueError("Ground truth boxes are expected to have values between 0 and 1.") - b, h, w, _, num_classes = pred_scores.shape + b, h, w, _, _ = pred_scores.shape # Convert from (xcenter, ycenter, w, h) to (xmin, ymin, xmax, ymax) pred_xyxy = self.to_isoboxes(pred_boxes, (h, w), clamp=False) pred_xy = (pred_xyxy[..., [0, 1]] + pred_xyxy[..., [2, 3]]) / 2 @@ -133,6 +133,15 @@ def _compute_losses( @staticmethod def to_isoboxes(b_coords: Tensor, grid_shape: Tuple[int, int], clamp: bool = False) -> Tensor: + """Converts xywh boxes to xyxy format. + + Args: + b_coords: tensor of shape (..., 4) where the last dimension is xcenter,ycenter,w,h + grid_shape: the size of the grid + clamp: whether the coords should be clamped to the extreme values + Returns: + tensor with the boxes using relative coords + """ # Cell offset c_x = torch.arange(grid_shape[1], dtype=torch.float, device=b_coords.device) c_y = torch.arange(grid_shape[0], dtype=torch.float, device=b_coords.device) diff --git a/holocron/models/detection/yolov2.py b/holocron/models/detection/yolov2.py index 398613d74..3c74280ab 100644 --- a/holocron/models/detection/yolov2.py +++ b/holocron/models/detection/yolov2.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -63,9 +63,13 @@ def __init__( if anchors is None: # cf. https://github.com/pjreddie/darknet/blob/master/cfg/yolov2-voc.cfg#L242 anchors = ( - torch.tensor( - [[1.3221, 1.73145], [3.19275, 4.00944], [5.05587, 8.09892], [9.47112, 4.84053], [11.2364, 10.0071]] - ) + torch.tensor([ # type: ignore[assignment] + [1.3221, 1.73145], + [3.19275, 4.00944], + [5.05587, 8.09892], + [9.47112, 4.84053], + [11.2364, 10.0071], + ]) / 13 ) @@ -127,7 +131,7 @@ def __init__( ) # Each box has P_objectness, 4 coords, and score for each class - self.head = nn.Conv2d(layout[-1][0], anchors.shape[0] * (5 + num_classes), 1) + self.head = nn.Conv2d(layout[-1][0], anchors.shape[0] * (5 + num_classes), 1) # type: ignore[union-attr] # Register losses self.register_buffer("anchors", anchors) @@ -145,6 +149,15 @@ def num_anchors(self) -> int: @staticmethod def to_isoboxes(b_coords: Tensor, grid_shape: Tuple[int, int], clamp: bool = False) -> Tensor: + """Converts xywh boxes to xyxy format. + + Args: + b_coords: tensor of shape (..., 4) where the last dimension is xcenter,ycenter,w,h + grid_shape: the size of the grid + clamp: whether the coords should be clamped to the extreme values + Returns: + tensor with the boxes using relative coords + """ xy = b_coords[..., :2] wh = b_coords[..., 2:] pred_xyxy = torch.cat((xy - wh / 2, xy + wh / 2), dim=-1).reshape(*b_coords.shape) @@ -235,7 +248,7 @@ def forward( b_coords, b_o, b_scores, - out.shape[-2:], # type: ignore[arg-type] + out.shape[-2:], self.rpn_nms_thresh, self.box_score_thresh, ) diff --git a/holocron/models/detection/yolov4.py b/holocron/models/detection/yolov4.py index a7e0237e2..3bfe357a2 100644 --- a/holocron/models/detection/yolov4.py +++ b/holocron/models/detection/yolov4.py @@ -1,9 +1,9 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast import torch import torch.nn as nn @@ -277,7 +277,7 @@ def _format_outputs(self, output: Tensor) -> Tuple[Tensor, Tensor, Tensor]: c_x = torch.arange(w, dtype=torch.float32, device=output.device).reshape(1, 1, -1, 1) c_y = torch.arange(h, dtype=torch.float32, device=output.device).reshape(1, -1, 1, 1) - b_xy = self.scale_xy * torch.sigmoid(output[..., :2]) - 0.5 * (self.scale_xy - 1) + b_xy = cast(Tensor, self.scale_xy * torch.sigmoid(output[..., :2]) - 0.5 * (self.scale_xy - 1)) b_xy[..., 0].add_(c_x) b_xy[..., 1].add_(c_y) b_xy[..., 0].div_(w) @@ -455,7 +455,7 @@ def __init__( # cf. https://github.com/AlexeyAB/darknet/blob/master/cfg/yolov4.cfg#L1143 if anchors is None: anchors = ( - torch.tensor( + torch.tensor( # type: ignore[assignment] [ [[12, 16], [19, 36], [40, 28]], [[36, 75], [76, 55], [72, 146]], @@ -468,8 +468,8 @@ def __init__( elif not isinstance(anchors, torch.Tensor): anchors = torch.tensor(anchors, dtype=torch.float32) - if anchors.shape[0] != 3: - raise AssertionError(f"The number of anchors is expected to be 3. received: {anchors.shape[0]}") + if anchors.shape[0] != 3: # type: ignore[union-attr] + raise AssertionError(f"The number of anchors is expected to be 3. received: {anchors.shape[0]}") # type: ignore[union-attr] super().__init__() @@ -480,7 +480,7 @@ def __init__( *conv_sequence(256, (5 + num_classes) * 3, None, None, None, conv_layer, kernel_size=1, bias=True), ) - self.yolo1 = YoloLayer(anchors[0], num_classes=num_classes, scale_xy=1.2) + self.yolo1 = YoloLayer(anchors[0], num_classes=num_classes, scale_xy=1.2) # type: ignore[index] self.pre_head2 = nn.Sequential( *conv_sequence( @@ -536,7 +536,7 @@ def __init__( *conv_sequence(512, (5 + num_classes) * 3, None, None, None, conv_layer, kernel_size=1, bias=True), ) - self.yolo2 = YoloLayer(anchors[1], num_classes=num_classes, scale_xy=1.1) + self.yolo2 = YoloLayer(anchors[1], num_classes=num_classes, scale_xy=1.1) # type: ignore[index] self.pre_head3 = nn.Sequential( *conv_sequence( @@ -598,7 +598,7 @@ def __init__( *conv_sequence(1024, (5 + num_classes) * 3, None, None, None, conv_layer, kernel_size=1, bias=True), ) - self.yolo3 = YoloLayer(anchors[2], num_classes=num_classes, scale_xy=1.05) + self.yolo3 = YoloLayer(anchors[2], num_classes=num_classes, scale_xy=1.05) # type: ignore[index] init_module(self, "leaky_relu") # Zero init self.head1[-1].weight.data.zero_() @@ -638,7 +638,7 @@ def forward( ] return detections - return {k: y1[k] + y2[k] + y3[k] for k in y1.keys()} + return {k: y1[k] + y2[k] + y3[k] for k in y1} class YOLOv4(nn.Module): diff --git a/holocron/models/presets.py b/holocron/models/presets.py index d573a7a3a..1c66f6156 100644 --- a/holocron/models/presets.py +++ b/holocron/models/presets.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/models/segmentation/unet.py b/holocron/models/segmentation/unet.py index 962cae737..4ab85a7e3 100644 --- a/holocron/models/segmentation/unet.py +++ b/holocron/models/segmentation/unet.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -44,16 +44,14 @@ def down_path( conv_layer: Optional[Callable[..., nn.Module]] = None, ) -> nn.Sequential: layers: List[nn.Module] = [nn.MaxPool2d(2)] if downsample else [] - layers.extend( - [ - *conv_sequence( - in_chan, out_chan, act_layer, norm_layer, drop_layer, conv_layer, kernel_size=3, padding=padding - ), - *conv_sequence( - out_chan, out_chan, act_layer, norm_layer, drop_layer, conv_layer, kernel_size=3, padding=padding - ), - ] - ) + layers.extend([ + *conv_sequence( + in_chan, out_chan, act_layer, norm_layer, drop_layer, conv_layer, kernel_size=3, padding=padding + ), + *conv_sequence( + out_chan, out_chan, act_layer, norm_layer, drop_layer, conv_layer, kernel_size=3, padding=padding + ), + ]) return nn.Sequential(*layers) @@ -129,13 +127,11 @@ def __init__( _pool = True super().__init__( - OrderedDict( - [ - ("features", nn.Sequential(*_layers)), - ("pool", GlobalAvgPool2d(flatten=True)), - ("head", nn.Linear(layout[-1], num_classes)), - ] - ) + OrderedDict([ + ("features", nn.Sequential(*_layers)), + ("pool", GlobalAvgPool2d(flatten=True)), + ("head", nn.Linear(layout[-1], num_classes)), + ]) ) init_module(self, "relu") @@ -350,9 +346,7 @@ def __init__( self.upsample: Optional[nn.Sequential] = None if final_upsampling: self.upsample = nn.Sequential( - *conv_sequence( - chans[0], chans[0] * 2**2, act_layer, norm_layer, drop_layer, conv_layer, kernel_size=1 - ), + *conv_sequence(chans[0], chans[0] * 2**2, act_layer, norm_layer, drop_layer, conv_layer, kernel_size=1), nn.PixelShuffle(upscale_factor=2), ) diff --git a/holocron/models/segmentation/unet3p.py b/holocron/models/segmentation/unet3p.py index 6e494291f..dc1d90d3a 100644 --- a/holocron/models/segmentation/unet3p.py +++ b/holocron/models/segmentation/unet3p.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -39,23 +39,19 @@ def __init__( # Get UNet depth depth = len(e_chans) + 1 + len(d_chans) # Downsample = max pooling + conv for channel reduction - self.downsamples = nn.ModuleList( - [ - nn.Sequential(nn.MaxPool2d(2 ** (len(e_chans) - idx)), nn.Conv2d(e_chan, base_chan, 3, padding=1)) - for idx, e_chan in enumerate(e_chans) - ] - ) + self.downsamples = nn.ModuleList([ + nn.Sequential(nn.MaxPool2d(2 ** (len(e_chans) - idx)), nn.Conv2d(e_chan, base_chan, 3, padding=1)) + for idx, e_chan in enumerate(e_chans) + ]) self.skip = nn.Conv2d(skip_chan, base_chan, 3, padding=1) if len(e_chans) > 0 else nn.Identity() # Upsample = bilinear interpolation + conv for channel reduction - self.upsamples = nn.ModuleList( - [ - nn.Sequential( - nn.Upsample(scale_factor=2 ** (idx + 1), mode="bilinear", align_corners=True), - nn.Conv2d(d_chan, base_chan, 3, padding=1), - ) - for idx, d_chan in enumerate(d_chans) - ] - ) + self.upsamples = nn.ModuleList([ + nn.Sequential( + nn.Upsample(scale_factor=2 ** (idx + 1), mode="bilinear", align_corners=True), + nn.Conv2d(d_chan, base_chan, 3, padding=1), + ) + for idx, d_chan in enumerate(d_chans) + ]) self.block = nn.Sequential( *conv_sequence( diff --git a/holocron/models/segmentation/unetpp.py b/holocron/models/segmentation/unetpp.py index 0dec02c31..3f4117782 100644 --- a/holocron/models/segmentation/unetpp.py +++ b/holocron/models/segmentation/unetpp.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2023, François-Guillaume Fernandez. +# Copyright (C) 2020-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -73,12 +73,10 @@ def __init__( _layout = [layout[-1]] + layout[1:][::-1] for left_chan, up_chan, num_cells in zip(layout[::-1], _layout, range(1, len(layout) + 1)): self.decoder.append( - nn.ModuleList( - [ - UpPath(left_chan + up_chan, left_chan, True, 1, act_layer, norm_layer, drop_layer, conv_layer) - for _ in range(num_cells) - ] - ) + nn.ModuleList([ + UpPath(left_chan + up_chan, left_chan, True, 1, act_layer, norm_layer, drop_layer, conv_layer) + for _ in range(num_cells) + ]) ) # Classifier @@ -154,21 +152,19 @@ def __init__( _layout = [layout[-1]] + layout[1:][::-1] for left_chan, up_chan, num_cells in zip(layout[::-1], _layout, range(1, len(layout) + 1)): self.decoder.append( - nn.ModuleList( - [ - UpPath( - up_chan + (idx + 1) * left_chan, - left_chan, - True, - 1, - act_layer, - norm_layer, - drop_layer, - conv_layer, - ) - for idx in range(num_cells) - ] - ) + nn.ModuleList([ + UpPath( + up_chan + (idx + 1) * left_chan, + left_chan, + True, + 1, + act_layer, + norm_layer, + drop_layer, + conv_layer, + ) + for idx in range(num_cells) + ]) ) # Classifier diff --git a/holocron/models/utils.py b/holocron/models/utils.py index 2ef9e9e5a..30b8f40f0 100644 --- a/holocron/models/utils.py +++ b/holocron/models/utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/nn/functional.py b/holocron/nn/functional.py index 1608e4cbe..10f9ff79a 100644 --- a/holocron/nn/functional.py +++ b/holocron/nn/functional.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -37,8 +37,8 @@ def hard_mish(x: Tensor, inplace: bool = False) -> Tensor: output tensor """ if inplace: - return x.mul_(0.5 * (x + 2).clamp(min=0, max=2)) - return 0.5 * x * (x + 2).clamp(min=0, max=2) + return x.mul_(0.5 * (x + 2).clamp(min=0, max=2)) # type: ignore[attr-defined] + return 0.5 * x * (x + 2).clamp(min=0, max=2) # type: ignore[attr-defined] def nl_relu(x: Tensor, beta: float = 1.0, inplace: bool = False) -> Tensor: @@ -53,7 +53,7 @@ def nl_relu(x: Tensor, beta: float = 1.0, inplace: bool = False) -> Tensor: """ if inplace: return torch.log(F.relu_(x).mul_(beta).add_(1), out=x) - return torch.log(1 + beta * F.relu(x)) + return torch.log(1 + beta * F.relu(x)) # type: ignore[arg-type] def focal_loss( @@ -92,11 +92,11 @@ def focal_loss( pt = logpt.exp() # Weight - if weight is not None: + if isinstance(weight, Tensor): # Tensor type if weight.type() != x.data.type(): weight = weight.type_as(x.data) - logpt = weight.gather(0, target.data.view(-1)) * logpt + logpt = weight.gather(0, target.data.view(-1)) * logpt # type: ignore[union-attr] # Loss loss = cast(Tensor, -1 * (1 - pt) ** gamma * logpt) @@ -177,7 +177,7 @@ def multilabel_cross_entropy( # Tensor type if weight.type() != x.data.type(): weight = weight.type_as(x.data) - logpt = logpt * weight.view(1, -1, *([1] * (x.ndim - 2))) + logpt = logpt * weight.view(1, -1, *([1] * (x.ndim - 2))) # type: ignore[union-attr] # CE Loss loss = -target * logpt @@ -243,7 +243,7 @@ def complement_cross_entropy( # Tensor type if weight.type() != x.data.type(): weight = weight.type_as(x.data) - loss = loss * weight.view(1, -1, *([1] * (x.ndim - 2))) + loss = loss * weight.view(1, -1, *([1] * (x.ndim - 2))) # type: ignore[union-attr] # Loss reduction if reduction == "sum": @@ -484,7 +484,7 @@ def dropblock2d(x: Tensor, drop_prob: float, block_size: int, inplace: bool = Fa gamma = drop_prob / block_size**2 # Sample a mask for the centers of blocks that will be dropped - mask = (torch.rand((x.shape[0], *x.shape[2:]), device=x.device) <= gamma).to(dtype=x.dtype) + mask = (torch.rand((x.shape[0], *x.shape[2:]), device=x.device) <= gamma).to(dtype=x.dtype) # type: ignore[attr-defined] # Expand zero positions to block size mask = 1 - F.max_pool2d(mask, kernel_size=(block_size, block_size), stride=(1, 1), padding=block_size // 2) @@ -525,7 +525,7 @@ def dice_loss( torch.Tensor: loss reduced with `reduction` method """ inter = gamma * (x * target).flatten(2).sum((0, 2)) - cardinality = (x + gamma * target).flatten(2).sum((0, 2)) + cardinality = (x + gamma * target).flatten(2).sum((0, 2)) # type: ignore[attr-defined] dice_coeff = (inter + eps) / (cardinality + eps) @@ -536,7 +536,7 @@ def dice_loss( # Tensor type if weight.type() != x.data.type(): weight = weight.type_as(x.data) - loss = 1 - (1 + 1 / gamma) * (weight * dice_coeff).sum() / weight.sum() + loss = 1 - (1 + 1 / gamma) * (weight * dice_coeff).sum() / weight.sum() # type: ignore[union-attr] return loss @@ -584,14 +584,14 @@ def poly_loss( loss = cast(Tensor, -1 * logpt + eps * (1 - logpt.exp())) # Weight - if weight is not None: + if isinstance(weight, Tensor): # Tensor type if weight.type() != x.data.type(): weight = weight.type_as(x.data) if target.ndim == x.ndim - 1: - loss = weight.gather(0, target.data.view(-1)) * loss + loss = weight.gather(0, target.data.view(-1)) * loss # type: ignore[union-attr] else: - loss = weight.reshape(1, -1) * loss + loss = weight.reshape(1, -1) * loss # type: ignore[union-attr] # Ignore index (set loss contribution to 0) if target.ndim == x.ndim - 1: diff --git a/holocron/nn/init.py b/holocron/nn/init.py index d7acc6c16..896df5c38 100644 --- a/holocron/nn/init.py +++ b/holocron/nn/init.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/nn/modules/activation.py b/holocron/nn/modules/activation.py index dbe1e8fd9..1143fcd92 100644 --- a/holocron/nn/modules/activation.py +++ b/holocron/nn/modules/activation.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/nn/modules/attention.py b/holocron/nn/modules/attention.py index 30e0b09f6..e01fb6637 100644 --- a/holocron/nn/modules/attention.py +++ b/holocron/nn/modules/attention.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/nn/modules/conv.py b/holocron/nn/modules/conv.py index c4df99249..9bdef4a25 100644 --- a/holocron/nn/modules/conv.py +++ b/holocron/nn/modules/conv.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -400,18 +400,16 @@ def __init__( **kwargs: Any, ) -> None: if num_levels == 1: - super().__init__( - [ - nn.Conv2d( - in_channels, - out_channels, - kernel_size, - padding=padding, - groups=groups[0] if isinstance(groups, list) else 1, - **kwargs, - ) - ] - ) + super().__init__([ + nn.Conv2d( + in_channels, + out_channels, + kernel_size, + padding=padding, + groups=groups[0] if isinstance(groups, list) else 1, + **kwargs, + ) + ]) else: exp2 = int(math.log2(num_levels)) reminder = num_levels - 2**exp2 @@ -428,12 +426,10 @@ def __init__( raise ValueError("The argument `group` is expected to be a list of integer of size `num_levels`.") paddings = [padding + idx for idx in range(num_levels)] - super().__init__( - [ - nn.Conv2d(in_channels, out_chan, k_size, padding=padding, groups=group, **kwargs) - for out_chan, k_size, padding, group in zip(out_chans, k_sizes, paddings, groups) - ] - ) + super().__init__([ + nn.Conv2d(in_channels, out_chan, k_size, padding=padding, groups=group, **kwargs) + for out_chan, k_size, padding, group in zip(out_chans, k_sizes, paddings, groups) + ]) self.num_levels = num_levels def forward(self, x: Tensor) -> Tensor: diff --git a/holocron/nn/modules/downsample.py b/holocron/nn/modules/downsample.py index 0b051afb6..2fa35ff0c 100644 --- a/holocron/nn/modules/downsample.py +++ b/holocron/nn/modules/downsample.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/nn/modules/dropblock.py b/holocron/nn/modules/dropblock.py index 3b83c91a7..d082b3832 100644 --- a/holocron/nn/modules/dropblock.py +++ b/holocron/nn/modules/dropblock.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/nn/modules/lambda_layer.py b/holocron/nn/modules/lambda_layer.py index 9c3b9b526..a0dd4e02d 100644 --- a/holocron/nn/modules/lambda_layer.py +++ b/holocron/nn/modules/lambda_layer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/nn/modules/loss.py b/holocron/nn/modules/loss.py index 02a7519bf..3e6e66eb9 100644 --- a/holocron/nn/modules/loss.py +++ b/holocron/nn/modules/loss.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/ops/boxes.py b/holocron/ops/boxes.py index 8c6a4fe92..f460ab75d 100644 --- a/holocron/ops/boxes.py +++ b/holocron/ops/boxes.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/optim/adabelief.py b/holocron/optim/adabelief.py index 8e8288dd2..c99aafb56 100644 --- a/holocron/optim/adabelief.py +++ b/holocron/optim/adabelief.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/optim/adamp.py b/holocron/optim/adamp.py index 792ec9e1b..cbaa72be1 100644 --- a/holocron/optim/adamp.py +++ b/holocron/optim/adamp.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/optim/adan.py b/holocron/optim/adan.py index 7c15599c2..bd7c08260 100644 --- a/holocron/optim/adan.py +++ b/holocron/optim/adan.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/optim/lamb.py b/holocron/optim/lamb.py index 01ae6d5a2..350f6adf7 100644 --- a/holocron/optim/lamb.py +++ b/holocron/optim/lamb.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -128,10 +128,7 @@ def step(self, closure: Optional[Callable[[], float]] = None) -> Optional[float] update_norm = update.pow(2).sum().sqrt() phi_p = p_norm.clamp(*self.scale_clip) # Compute the local LR - if phi_p == 0 or update_norm == 0: - local_lr = 1 - else: - local_lr = phi_p / update_norm + local_lr = 1 if phi_p == 0 or update_norm == 0 else phi_p / update_norm state["local_lr"] = local_lr diff --git a/holocron/optim/lars.py b/holocron/optim/lars.py index 723253056..55ec75a25 100644 --- a/holocron/optim/lars.py +++ b/holocron/optim/lars.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -117,10 +117,7 @@ def step(self, closure: Optional[Callable[[], float]] = None) -> Optional[float] d_p.add_(p.data, alpha=weight_decay) denom.add_(p_norm, alpha=weight_decay) # Compute the local LR - if p_norm == 0 or denom == 0: - local_lr = 1 - else: - local_lr = p_norm / denom + local_lr = 1 if p_norm == 0 or denom == 0 else p_norm / denom if momentum == 0: p.data.add_(d_p, alpha=-group["lr"] * local_lr) @@ -131,10 +128,7 @@ def step(self, closure: Optional[Callable[[], float]] = None) -> Optional[float] else: momentum_buffer = param_state["momentum_buffer"] momentum_buffer.mul_(momentum).add_(d_p, alpha=1 - dampening) - if nesterov: - d_p = d_p.add(momentum_buffer, alpha=momentum) - else: - d_p = momentum_buffer + d_p = d_p.add(momentum_buffer, alpha=momentum) if nesterov else momentum_buffer p.data.add_(d_p, alpha=-group["lr"] * local_lr) self.state[p]["momentum_buffer"] = momentum_buffer diff --git a/holocron/optim/ralars.py b/holocron/optim/ralars.py index 5917fc8bf..6e62c8567 100644 --- a/holocron/optim/ralars.py +++ b/holocron/optim/ralars.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -131,10 +131,7 @@ def step(self, closure: Optional[Callable[[], float]] = None) -> Optional[float] update_norm = update.pow(2).sum().sqrt() phi_p = p_norm.clamp(*self.scale_clip) # Compute the local LR - if phi_p == 0 or update_norm == 0: - local_lr = 1 - else: - local_lr = phi_p / update_norm + local_lr = 1 if phi_p == 0 or update_norm == 0 else phi_p / update_norm state["local_lr"] = local_lr diff --git a/holocron/optim/tadam.py b/holocron/optim/tadam.py index 8aab64c33..94a877432 100644 --- a/holocron/optim/tadam.py +++ b/holocron/optim/tadam.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -71,7 +71,7 @@ def __init__( raise ValueError(f"Invalid beta parameter at index 0: {betas[0]}") if not 0.0 <= betas[1] < 1.0: raise ValueError(f"Invalid beta parameter at index 1: {betas[1]}") - if not 0.0 <= weight_decay: + if not weight_decay >= 0.0: raise ValueError("Invalid weight_decay value: {}".format(weight_decay)) defaults = {"lr": lr, "betas": betas, "eps": eps, "weight_decay": weight_decay, "amsgrad": amsgrad, "dof": dof} super().__init__(params, defaults) diff --git a/holocron/optim/wrapper.py b/holocron/optim/wrapper.py index 473e66b7b..fa737efe4 100644 --- a/holocron/optim/wrapper.py +++ b/holocron/optim/wrapper.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/trainer/classification.py b/holocron/trainer/classification.py index 2d19ae8da..ba5ab8ad9 100644 --- a/holocron/trainer/classification.py +++ b/holocron/trainer/classification.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/trainer/core.py b/holocron/trainer/core.py index bc02874d1..a2c20cabc 100644 --- a/holocron/trainer/core.py +++ b/holocron/trainer/core.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -252,11 +252,11 @@ def _reset_opt(self, lr: float, norm_weight_decay: Optional[float] = None) -> No self.optimizer.zero_grad() @torch.inference_mode() - def evaluate(self): # type: ignore[no-untyped-def] + def evaluate(self): # type: ignore[no-untyped-def] # noqa: ANN201 raise NotImplementedError @staticmethod - def _eval_metrics_str(eval_metrics): # type: ignore[no-untyped-def] + def _eval_metrics_str(eval_metrics) -> str: # type: ignore[no-untyped-def] # noqa: ANN001 raise NotImplementedError def _reset_scheduler(self, lr: float, num_epochs: int, sched_type: str = "onecycle", **kwargs: Any) -> None: diff --git a/holocron/trainer/detection.py b/holocron/trainer/detection.py index 7afe7527a..0f9d91b34 100644 --- a/holocron/trainer/detection.py +++ b/holocron/trainer/detection.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -22,14 +22,14 @@ def assign_iou(gt_boxes: Tensor, pred_boxes: Tensor, iou_threshold: float = 0.5) assign_unique = torch.unique(iou.indices[gt_kept]) # Filter if iou.indices[gt_kept].shape[0] == assign_unique.shape[0]: - return torch.arange(gt_boxes.shape[0])[gt_kept], iou.indices[gt_kept] # type: ignore[return-value] + return torch.arange(gt_boxes.shape[0])[gt_kept], iou.indices[gt_kept] gt_indices, pred_indices = [], [] for pred_idx in assign_unique: selection = iou.values[gt_kept][iou.indices[gt_kept] == pred_idx].argmax() gt_indices.append(torch.arange(gt_boxes.shape[0])[gt_kept][selection].item()) pred_indices.append(iou.indices[gt_kept][selection].item()) - return gt_indices, pred_indices # type: ignore[return-value] + return gt_indices, pred_indices class DetectionTrainer(Trainer): diff --git a/holocron/trainer/segmentation.py b/holocron/trainer/segmentation.py index f39ec242e..2c0449865 100644 --- a/holocron/trainer/segmentation.py +++ b/holocron/trainer/segmentation.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/trainer/utils.py b/holocron/trainer/utils.py index fee8cc04f..de3644442 100644 --- a/holocron/trainer/utils.py +++ b/holocron/trainer/utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/transforms/interpolation.py b/holocron/transforms/interpolation.py index 3833eb0d5..2fff652f3 100644 --- a/holocron/transforms/interpolation.py +++ b/holocron/transforms/interpolation.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -117,7 +117,7 @@ class RandomZoomOut(nn.Module): the resized image """ - def __init__(self, size: Tuple[int, int], scale: Tuple[float, float] = (0.5, 1.0), **kwargs: Any): + def __init__(self, size: Tuple[int, int], scale: Tuple[float, float] = (0.5, 1.0), **kwargs: Any) -> None: if not isinstance(size, (tuple, list)) or len(size) != 2 or any(s <= 0 for s in size): raise ValueError("size is expected to be a sequence of 2 positive integers") if len(scale) != 2 or scale[0] > scale[1]: @@ -134,10 +134,7 @@ def get_params(self, image: Union[Image.Image, torch.Tensor]) -> Tuple[int, int] _aratio = h / w # Preserve the aspect ratio _tratio = self.size[0] / self.size[1] - if _tratio > _aratio: - _max_area = self.size[1] ** 2 * _aratio - else: - _max_area = self.size[0] ** 2 / _aratio + _max_area = self.size[1] ** 2 * _aratio if _tratio > _aratio else self.size[0] ** 2 / _aratio _area = _max_area * _scale _w = int(round(sqrt(_area / _aratio))) diff --git a/holocron/utils/data/collate.py b/holocron/utils/data/collate.py index 04bad70b3..5e7ace907 100644 --- a/holocron/utils/data/collate.py +++ b/holocron/utils/data/collate.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/holocron/utils/misc.py b/holocron/utils/misc.py index b3025f6ff..8bb9118f8 100644 --- a/holocron/utils/misc.py +++ b/holocron/utils/misc.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -6,7 +6,7 @@ import multiprocessing as mp from math import sqrt from multiprocessing.pool import ThreadPool -from typing import Any, Callable, Optional, Sequence, Tuple, TypeVar +from typing import Any, Callable, Iterable, Optional, Sequence, Tuple, TypeVar import matplotlib.pyplot as plt import numpy as np @@ -26,7 +26,7 @@ def parallel( num_threads: Optional[int] = None, progress: bool = False, **kwargs: Any, -) -> Sequence[Out]: +) -> Iterable[Out]: """Performs parallel tasks by leveraging multi-threading. >>> from holocron.utils.misc import parallel @@ -44,16 +44,10 @@ def parallel( """ num_threads = num_threads if isinstance(num_threads, int) else min(16, mp.cpu_count()) if num_threads < 2: - if progress: - results = list(map(func, tqdm(arr, total=len(arr), **kwargs))) - else: - results = map(func, arr) # type: ignore[assignment] + results = list(map(func, tqdm(arr, total=len(arr), **kwargs))) if progress else map(func, arr) else: with ThreadPool(num_threads) as tp: - if progress: - results = list(tqdm(tp.imap(func, arr), total=len(arr), **kwargs)) - else: - results = tp.map(func, arr) + results = list(tqdm(tp.imap(func, arr), total=len(arr), **kwargs)) if progress else tp.map(func, arr) return results diff --git a/pyproject.toml b/pyproject.toml index aef7a58d4..f6deda558 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,12 +56,10 @@ training = [ "codecarbon>=2.0.0,<3.0.0", ] quality = [ - "ruff==0.1.0", - "mypy==1.5.1", + "ruff==0.1.11", + "mypy==1.8.0", "types-tqdm", - "black==23.3.0", - "bandit[toml]>=1.7.0,<1.8.0", - "pre-commit>=2.17.0,<3.0.0", + "pre-commit>=3.0.0,<4.0.0", ] docs = [ "sphinx>=3.0.0,!=3.5.0", @@ -83,12 +81,10 @@ dev = [ "pytest>=7.2.0", "coverage[toml]>=4.5.4", # style - "ruff==0.1.0", - "mypy==1.6.1", + "ruff==0.1.11", + "mypy==1.8.0", "types-tqdm", - "black==23.3.0", - "bandit[toml]>=1.7.0,<1.8.0", - "pre-commit>=2.17.0,<3.0.0", + "pre-commit>=3.0.0,<4.0.0", # docs "sphinx>=3.0.0,!=3.5.0", "furo>=2022.3.4", @@ -137,6 +133,16 @@ select = [ "T20", # flake8-print "PT", # flake8-pytest-style "LOG", # flake8-logging + "SIM", # flake8-simplify + "YTT", # flake8-2020 + "ANN", # flake8-annotations + "ASYNC", # flake8-async + "BLE", # flake8-blind-except + "A", # flake8-builtins + "ICN", # flake8-import-conventions + "PIE", # flake8-pie + "ARG", # flake8-unused-arguments + "FURB", # refurb ] ignore = [ "E501", # line too long, handled by black @@ -147,29 +153,41 @@ ignore = [ "F403", # star imports "E731", # lambda assignment "C416", # list comprehension to list() + "ANN101", # missing type annotations on self + "ANN102", # missing type annotations on cls + "ANN002", # missing type annotations on *args + "ANN003", # missing type annotations on **kwargs "PT011", # pytest.raises must have a match pattern "N812", # Lowercase imported as non-lowercase + "ISC001", # implicit string concatenation (handled by format) + "ANN401", # Dynamically typed expressions (typing.Any) are disallowed ] exclude = [".git"] line-length = 120 target-version = "py39" preview = true +[tool.ruff.format] +quote-style = "double" +indent-style = "space" + [tool.ruff.per-file-ignores] "**/__init__.py" = ["I001", "F401", "CPY001"] -"scripts/**.py" = ["D", "T201"] -".github/**.py" = ["D", "T201", "S602"] -"docs/**.py" = ["E402", "D103"] -"tests/**.py" = ["D103", "CPY001", "S101"] -"demo/**.py" = ["D103"] +"scripts/**.py" = ["D", "T201", "ANN"] +".github/**.py" = ["D", "T201", "S602", "ANN"] +"docs/**.py" = ["E402", "D103", "ANN", "A001", "ARG001"] +"tests/**.py" = ["D103", "CPY001", "S101", "ANN"] +"demo/**.py" = ["D103", "ANN"] "api/**.py" = ["D103"] -"api/tests/**.py" = ["CPY001", "S101"] -"references/**.py" = ["D101", "D103", "T201"] +"api/app/main.py" = ["ANN"] +"api/tests/**.py" = ["CPY001", "S101", "ANN", "ARG001"] +"references/**.py" = ["D101", "D103", "T201", "ANN"] "setup.py" = ["T201"] "holocron/nn/modules/**.py" = ["N806"] "holocron/models/classification/**.py" = ["N801"] -"holocron/models/*/**.py" = ["D101"] -"holocron/nn/functional.py" = ["N802"] +"holocron/models/*/**.py" = ["D101", "ARG"] +"holocron/nn/functional.py" = ["N802", "ARG"] +"holocron/trainer/**.py" = ["ARG"] [tool.ruff.flake8-quotes] docstring-quotes = "double" @@ -190,6 +208,7 @@ no_implicit_optional = true check_untyped_defs = true implicit_reexport = false disallow_untyped_defs = true +explicit_package_bases = true [[tool.mypy.overrides]] module = [ @@ -200,11 +219,3 @@ module = [ "huggingface_hub.*", ] ignore_missing_imports = true - -[tool.black] -line-length = 120 -target-version = ['py39'] - -[tool.bandit] -exclude_dirs = [".github/collect_env.py"] -skips = ["B101"] diff --git a/references/README.md b/references/README.md index 0016ae4f9..f440f76a0 100644 --- a/references/README.md +++ b/references/README.md @@ -7,7 +7,7 @@ This section is specific to train computer vision models. ### Prerequisites -Python 3.6 (or higher) and [pip](https://pip.pypa.io/en/stable/) & [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) are required to install Holocron. +Python 3.8 (or higher) and [pip](https://pip.pypa.io/en/stable/) & [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) are required to install Holocron. ### Developer mode diff --git a/references/classification/train.py b/references/classification/train.py index 4fb51af5f..9bfffcda8 100644 --- a/references/classification/train.py +++ b/references/classification/train.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -97,33 +97,29 @@ def main(args): if args.dataset.lower() == "imagenette": train_set = ImageFolder( Path(args.data_path).joinpath("train"), - T.Compose( - [ - T.RandomResizedCrop(args.train_crop_size, scale=(0.3, 1.0), interpolation=interpolation), - T.RandomHorizontalFlip(), - A.TrivialAugmentWide(interpolation=interpolation), - T.PILToTensor(), - T.ConvertImageDtype(torch.float32), - normalize, - T.RandomErasing(p=args.random_erase, scale=(0.02, 0.2), value="random"), - ] - ), + T.Compose([ + T.RandomResizedCrop(args.train_crop_size, scale=(0.3, 1.0), interpolation=interpolation), + T.RandomHorizontalFlip(), + A.TrivialAugmentWide(interpolation=interpolation), + T.PILToTensor(), + T.ConvertImageDtype(torch.float32), + normalize, + T.RandomErasing(p=args.random_erase, scale=(0.02, 0.2), value="random"), + ]), ) else: cifar_version = CIFAR100 if args.dataset.lower() == "cifar100" else CIFAR10 train_set = cifar_version( args.data_path, True, - T.Compose( - [ - T.RandomHorizontalFlip(), - A.TrivialAugmentWide(interpolation=interpolation), - T.PILToTensor(), - T.ConvertImageDtype(torch.float32), - normalize, - T.RandomErasing(p=args.random_erase, value="random"), - ] - ), + T.Compose([ + T.RandomHorizontalFlip(), + A.TrivialAugmentWide(interpolation=interpolation), + T.PILToTensor(), + T.ConvertImageDtype(torch.float32), + normalize, + T.RandomErasing(p=args.random_erase, value="random"), + ]), download=True, ) @@ -164,15 +160,13 @@ def main(args): if args.dataset.lower() == "imagenette": val_set = ImageFolder( Path(args.data_path).joinpath("val"), - T.Compose( - [ - T.Resize(args.val_resize_size, interpolation=interpolation), - T.CenterCrop(args.val_crop_size), - T.PILToTensor(), - T.ConvertImageDtype(torch.float32), - normalize, - ] - ), + T.Compose([ + T.Resize(args.val_resize_size, interpolation=interpolation), + T.CenterCrop(args.val_crop_size), + T.PILToTensor(), + T.ConvertImageDtype(torch.float32), + normalize, + ]), ) else: cifar_version = CIFAR100 if args.dataset.lower() == "cifar100" else CIFAR10 diff --git a/references/clean_checkpoint.py b/references/clean_checkpoint.py index 62d4bc493..6400172c6 100644 --- a/references/clean_checkpoint.py +++ b/references/clean_checkpoint.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/references/detection/train.py b/references/detection/train.py index f48813124..0734db4e8 100644 --- a/references/detection/train.py +++ b/references/detection/train.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -113,18 +113,16 @@ def main(args): args.data_path, image_set="train", download=True, - transforms=Compose( - [ - VOCTargetTransform(VOC_CLASSES), - Resize((args.img_size, args.img_size), interpolation=interpolation_mode), - RandomHorizontalFlip(), - convert_to_relative if args.source == "holocron" else lambda x, y: (x, y), - ImageTransform(T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.1, hue=0.02)), - ImageTransform(T.PILToTensor()), - ImageTransform(T.ConvertImageDtype(torch.float32)), - ImageTransform(normalize), - ] - ), + transforms=Compose([ + VOCTargetTransform(VOC_CLASSES), + Resize((args.img_size, args.img_size), interpolation=interpolation_mode), + RandomHorizontalFlip(), + convert_to_relative if args.source == "holocron" else lambda x, y: (x, y), + ImageTransform(T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.1, hue=0.02)), + ImageTransform(T.PILToTensor()), + ImageTransform(T.ConvertImageDtype(torch.float32)), + ImageTransform(normalize), + ]), ) # Suggest size @@ -160,16 +158,14 @@ def main(args): args.data_path, image_set="val", download=True, - transforms=Compose( - [ - VOCTargetTransform(VOC_CLASSES), - Resize((args.img_size, args.img_size), interpolation=interpolation_mode), - convert_to_relative if args.source == "holocron" else lambda x, y: (x, y), - ImageTransform(T.PILToTensor()), - ImageTransform(T.ConvertImageDtype(torch.float32)), - ImageTransform(normalize), - ] - ), + transforms=Compose([ + VOCTargetTransform(VOC_CLASSES), + Resize((args.img_size, args.img_size), interpolation=interpolation_mode), + convert_to_relative if args.source == "holocron" else lambda x, y: (x, y), + ImageTransform(T.PILToTensor()), + ImageTransform(T.ConvertImageDtype(torch.float32)), + ImageTransform(normalize), + ]), ) val_loader = torch.utils.data.DataLoader( diff --git a/references/detection/transforms.py b/references/detection/transforms.py index aebaa9c23..e450180e8 100644 --- a/references/detection/transforms.py +++ b/references/detection/transforms.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/references/segmentation/train.py b/references/segmentation/train.py index 8a80c2c98..4642df895 100644 --- a/references/segmentation/train.py +++ b/references/segmentation/train.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. @@ -130,16 +130,14 @@ def main(args): args.data_path, image_set="train", download=True, - transforms=Compose( - [ - RandomResize(min_size, max_size, interpolation_mode), - RandomCrop(crop_size), - RandomHorizontalFlip(0.5), - ImageTransform(T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.1, hue=0.02)), - ToTensor(), - ImageTransform(normalize), - ] - ), + transforms=Compose([ + RandomResize(min_size, max_size, interpolation_mode), + RandomCrop(crop_size), + RandomHorizontalFlip(0.5), + ImageTransform(T.ColorJitter(brightness=0.3, contrast=0.3, saturation=0.1, hue=0.02)), + ToTensor(), + ImageTransform(normalize), + ]), ) # Suggest size @@ -174,9 +172,11 @@ def main(args): args.data_path, image_set="val", download=True, - transforms=Compose( - [Resize((crop_size, crop_size), interpolation_mode), ToTensor(), ImageTransform(normalize)] - ), + transforms=Compose([ + Resize((crop_size, crop_size), interpolation_mode), + ToTensor(), + ImageTransform(normalize), + ]), ) val_loader = torch.utils.data.DataLoader( diff --git a/references/segmentation/transforms.py b/references/segmentation/transforms.py index 3be1f445b..f96fe4c2b 100644 --- a/references/segmentation/transforms.py +++ b/references/segmentation/transforms.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/scripts/eval_latency.py b/scripts/eval_latency.py index caedcec9d..9b3a27454 100644 --- a/scripts/eval_latency.py +++ b/scripts/eval_latency.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/scripts/export_to_onnx.py b/scripts/export_to_onnx.py index d3402719b..b9f87912a 100644 --- a/scripts/export_to_onnx.py +++ b/scripts/export_to_onnx.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022-2023, François-Guillaume Fernandez. +# Copyright (C) 2022-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/setup.py b/setup.py index 5a94ce84f..a5444ed36 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2023, François-Guillaume Fernandez. +# Copyright (C) 2019-2024, François-Guillaume Fernandez. # This program is licensed under the Apache License 2.0. # See LICENSE or go to for full license details. diff --git a/tests/test_nn_loss.py b/tests/test_nn_loss.py index fa3d229cd..d329378a8 100644 --- a/tests/test_nn_loss.py +++ b/tests/test_nn_loss.py @@ -27,20 +27,14 @@ def _test_loss_function(loss_fn, same_loss=0.0, multi_label=False): # Check that class rescaling works x = torch.rand(num_batches, num_classes, requires_grad=True) - if multi_label: - target = torch.rand(x.shape) - else: - target = (num_classes * torch.rand(num_batches)).to(torch.long) + target = torch.rand(x.shape) if multi_label else (num_classes * torch.rand(num_batches)).to(torch.long) weights = torch.ones(num_classes) assert loss_fn(x, target).item() == loss_fn(x, target, weight=weights).item() # Check that ignore_index works assert loss_fn(x, target).item() == loss_fn(x, target, ignore_index=num_classes).item() # Ignore an index we are certain to be in the target - if multi_label: - ignore_index = torch.unique(target.argmax(dim=1))[0].item() - else: - ignore_index = torch.unique(target)[0].item() + ignore_index = torch.unique(target.argmax(dim=1))[0].item() if multi_label else torch.unique(target)[0].item() assert loss_fn(x, target).item() != loss_fn(x, target, ignore_index=ignore_index) # Check backprop loss = loss_fn(x, target, ignore_index=0)