From dee2e74a50790c9027cfb1e9f380d53ea95fbfee Mon Sep 17 00:00:00 2001 From: F-G Fernandez <26927750+frgfm@users.noreply.github.com> Date: Wed, 12 Jul 2023 19:26:09 -0700 Subject: [PATCH] style: Bumps ruff & black, removes isort & pydocstyle (#216) * chore: Updates ruff config * style: Fixes ruff * style: Updates ruff config * style: Adds copyright check * style: Adds rules to ruff * style: Updates precommit * style: Fixes ruff * chore: Removes pydocstyle * style: Fixes typing * chore: Removes isort * style: Fixes black * style: Fixes bandit * chore: Updates deps for mypy * ci: Updates CI * chore: Bumps mypy --- .github/collect_env.py | 4 +- .github/verify_labels.py | 6 ++- .github/workflows/style.yml | 43 +------------------- .pre-commit-config.yaml | 24 +++++------ Makefile | 3 -- demo/app.py | 8 ++-- docs/source/conf.py | 2 +- pyproject.toml | 70 ++++++++++++++++++++------------ scripts/cam_example.py | 3 +- scripts/eval_latency.py | 1 - scripts/eval_perf.py | 1 - setup.py | 1 - tests/conftest.py | 2 +- tests/test_methods_activation.py | 1 - tests/test_methods_core.py | 1 - tests/test_methods_gradient.py | 2 - tests/test_methods_utils.py | 1 - tests/test_metrics.py | 1 - tests/test_utils.py | 1 - torchcam/__init__.py | 2 +- torchcam/methods/_utils.py | 2 - torchcam/methods/activation.py | 19 ++------- torchcam/methods/core.py | 6 +-- torchcam/methods/gradient.py | 12 +----- torchcam/metrics.py | 10 ++--- torchcam/utils.py | 1 - 26 files changed, 81 insertions(+), 146 deletions(-) diff --git a/.github/collect_env.py b/.github/collect_env.py index b2aa8ac0..844d0d84 100644 --- a/.github/collect_env.py +++ b/.github/collect_env.py @@ -136,7 +136,7 @@ def get_cudnn_version(run_lambda): if not files: return None # Alphabetize the result because the order is non-deterministic otherwise - files = list(sorted(files)) + files = sorted(files) if len(files) == 1: return files[0] result = "\n".join(files) @@ -292,7 +292,7 @@ def maybe_start_on_next_line(string): "nvidia_gpu_models", "nvidia_driver_version", ] - all_cuda_fields = dynamic_cuda_fields + ["cudnn_version"] + all_cuda_fields = [*dynamic_cuda_fields, "cudnn_version"] all_dynamic_cuda_fields_missing = all(mutable_dict[field] is None for field in dynamic_cuda_fields) if TORCH_AVAILABLE and not torch.cuda.is_available() and all_dynamic_cuda_fields_missing: for field in all_cuda_fields: diff --git a/.github/verify_labels.py b/.github/verify_labels.py index cd5f2b97..71264a7b 100644 --- a/.github/verify_labels.py +++ b/.github/verify_labels.py @@ -44,7 +44,11 @@ def query_repo(cmd: str, *, accept) -> Any: - response = requests.get(f"https://api.github.com/repos/{GH_ORG}/{GH_REPO}/{cmd}", headers=dict(Accept=accept)) + response = requests.get( + f"https://api.github.com/repos/{GH_ORG}/{GH_REPO}/{cmd}", + headers={"Accept": accept}, + timeout=5, + ) return response.json() diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index bdef1a03..902b9083 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -26,26 +26,6 @@ jobs: ruff --version ruff check --diff . - isort: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - python: [3.8] - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }} - architecture: x64 - - name: Run isort - run: | - pip install isort - isort --version - isort . - if [ -n "$(git status --porcelain --untracked-files=no)" ]; then exit 1; else echo "All clear"; fi - mypy: runs-on: ${{ matrix.os }} strategy: @@ -68,31 +48,12 @@ jobs: run: | python -m pip install --upgrade pip pip install -e . --upgrade - pip install mypy + pip install "mypy==1.4.1" - name: Run mypy run: | mypy --version mypy - pydocstyle: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - python: [3.8] - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python }} - architecture: x64 - - name: Run pydocstyle - run: | - pip install pydocstyle[toml] - pydocstyle --version - pydocstyle - black: runs-on: ${{ matrix.os }} strategy: @@ -108,7 +69,7 @@ jobs: architecture: x64 - name: Run black run: | - pip install "black==22.3.0" + pip install "black==23.3.0" black --version black --check --diff . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e9f6c1bd..55d02fb0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,31 +1,29 @@ +default_language_version: + python: python3.9 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - - id: check-ast - 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-ast + - id: check-json - id: check-merge-conflict - id: no-commit-to-branch args: ['--branch', 'main'] + - id: debug-statements + language_version: python3 - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 23.3.0 hooks: - id: black - - repo: https://github.com/pycqa/isort - rev: 5.10.1 - hooks: - - id: isort - exclude: "(__init__.py)$" - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.0.260' + rev: 'v0.0.278' hooks: - id: ruff - exclude: "(__init__.py)$" + args: + - --fix diff --git a/Makefile b/Makefile index c2f5e788..aff274cc 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,12 @@ # this target runs checks on all files quality: - isort . -c ruff check . mypy - pydocstyle black --check . bandit -r . -c pyproject.toml # this target runs checks on all files and potentially modifies some of them style: - isort . black . ruff --fix . diff --git a/demo/app.py b/demo/app.py index 16f9b479..f09e6dc9 100644 --- a/demo/app.py +++ b/demo/app.py @@ -28,12 +28,12 @@ "convnext_small", ] LABEL_MAP = requests.get( - "https://raw.githubusercontent.com/anishathalye/imagenet-simple-labels/master/imagenet-simple-labels.json" + "https://raw.githubusercontent.com/anishathalye/imagenet-simple-labels/master/imagenet-simple-labels.json", + timeout=10, ).json() def main(): - # Wide mode st.set_page_config(layout="wide") @@ -91,19 +91,17 @@ def main(): ) class_choices = [f"{idx + 1} - {class_name}" for idx, class_name in enumerate(LABEL_MAP)] - class_selection = st.sidebar.selectbox("Class selection", ["Predicted class (argmax)"] + class_choices) + class_selection = st.sidebar.selectbox("Class selection", ["Predicted class (argmax)", *class_choices]) # For newline st.sidebar.write("\n") if st.sidebar.button("Compute CAM"): - if uploaded_file is None: st.sidebar.error("Please upload an image first") else: with st.spinner("Analyzing..."): - # Preprocess image img_tensor = normalize(to_tensor(resize(img, (224, 224))), [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) diff --git a/docs/source/conf.py b/docs/source/conf.py index 4d71a4e0..80ca2b2a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -107,10 +107,10 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] + # Add googleanalytics id # ref: https://github.com/orenhecht/googleanalytics/blob/master/sphinxcontrib/googleanalytics.py def add_ga_javascript(app, pagename, templatename, context, doctree): - metatags = context.get("metatags", "") metatags += """ diff --git a/pyproject.toml b/pyproject.toml index 5546a878..7fd25d80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,11 +45,9 @@ test = [ "torchvision>=0.4.0,<1.0.0", ] quality = [ - "ruff>=0.0.260,<1.0.0", - "isort>=5.7.0", - "mypy>=1.2.0", - "pydocstyle[toml]>=6.0.0", - "black==22.3.0", + "ruff>=0.0.273,<1.0.0", + "mypy==1.4.1", + "black==23.3.0", "bandit[toml]>=1.7.0,<1.8.0", "pre-commit>=2.17.0,<3.0.0", ] @@ -75,11 +73,9 @@ dev = [ "requests>=2.20.0,<3.0.0", "torchvision>=0.4.0,<1.0.0", # style - "ruff>=0.0.260,<1.0.0", - "isort>=5.7.0", - "mypy>=1.2.0", - "pydocstyle[toml]>=6.0.0", - "black==22.3.0", + "ruff>=0.0.273,<1.0.0", + "mypy==1.4.1", + "black==23.3.0", "bandit[toml]>=1.7.0,<1.8.0", "pre-commit>=2.17.0,<3.0.0", # docs @@ -108,18 +104,52 @@ exclude = ["demo*", "docs*", "notebooks*", "scripts*", "tests*"] source = ["torchcam"] [tool.ruff] -ignore = ["E402", "F403", "E731"] -exclude = [".git", "venv*", "build", "docs"] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "D101", "D103", # pydocstyle missing docstring in public function/class + "D201","D202","D207","D208","D214","D215","D300","D301","D417", "D419", # pydocstyle + "F", # pyflakes + "I", # isort + "C", # flake8-comprehensions + "B", # flake8-bugbear + "CPY001", # flake8-copyright + "ISC", # flake8-implicit-str-concat + "PYI", # flake8-pyi + "NPY", # numpy + "PERF", # perflint + "RUF", # ruff specific +] +ignore = [ + "E501", # line too long, handled by black + "B008", # do not perform function calls in argument defaults + "B904", # raise from + "C901", # too complex + "F403", # star imports + "E731", # lambda assignment + "C416", # list comprehension to list() +] +exclude = [".git"] line-length = 120 -target-version = "py38" +target-version = "py39" [tool.ruff.per-file-ignores] -"**/__init__.py" = ["F401"] +"**/__init__.py" = ["I001", "F401", "CPY001"] +"scripts/**.py" = ["D"] +".github/**.py" = ["D"] +"docs/**.py" = ["E402", "D103"] +"tests/**.py" = ["D103", "CPY001"] +"demo/**.py" = ["D103"] [tool.ruff.flake8-quotes] docstring-quotes = "double" +[tool.ruff.isort] +known-first-party = ["app"] +known-third-party = ["torch", "torchvision"] + [tool.mypy] +python_version = "3.8" files = "torchcam/" show_error_codes = true pretty = true @@ -138,19 +168,9 @@ module = [ ] ignore_missing_imports = true -[tool.isort] -line_length = 120 -src_paths = ["torchcam", "tests", "scripts", "docs", "demo", ".github"] -skip_glob = "**/__init__.py" -known_third_party = ["torch", "torchvision"] - -[tool.pydocstyle] -select = "D300,D301,D417" -match = ".*\\.py" - [tool.black] line-length = 120 -target-version = ['py38'] +target-version = ['py39'] [tool.bandit] exclude_dirs = [".github/collect_env.py"] diff --git a/scripts/cam_example.py b/scripts/cam_example.py index b4b5cc6a..d7440d56 100644 --- a/scripts/cam_example.py +++ b/scripts/cam_example.py @@ -23,7 +23,6 @@ def main(args): - if args.device is None: args.device = "cuda:0" if torch.cuda.is_available() else "cpu" @@ -37,7 +36,7 @@ def main(args): # Image if args.img.startswith("http"): - img_path = BytesIO(requests.get(args.img).content) + img_path = BytesIO(requests.get(args.img, timeout=5).content) else: img_path = args.img pil_img = Image.open(img_path, mode="r").convert("RGB") diff --git a/scripts/eval_latency.py b/scripts/eval_latency.py index 1f7472c7..9c0eae0f 100644 --- a/scripts/eval_latency.py +++ b/scripts/eval_latency.py @@ -18,7 +18,6 @@ def main(args): - if args.device is None: args.device = "cuda:0" if torch.cuda.is_available() else "cpu" diff --git a/scripts/eval_perf.py b/scripts/eval_perf.py index 58d30548..79d9ac90 100644 --- a/scripts/eval_perf.py +++ b/scripts/eval_perf.py @@ -23,7 +23,6 @@ def main(args): - if args.device is None: args.device = "cuda:0" if torch.cuda.is_available() else "cpu" diff --git a/setup.py b/setup.py index 3d132f3e..92ad5df4 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,6 @@ if __name__ == "__main__": - print(f"Building wheel {PKG_NAME}-{VERSION}") # Dynamically set the __version__ attribute diff --git a/tests/conftest.py b/tests/conftest.py index 7da91418..66211452 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ def mock_img_tensor(): try: # Get a dog image URL = "https://www.woopets.fr/assets/races/000/066/big-portrait/border-collie.jpg" - response = requests.get(URL) + response = requests.get(URL, timeout=5) # Forward an image pil_img = Image.open(BytesIO(response.content), mode="r").convert("RGB") diff --git a/tests/test_methods_activation.py b/tests/test_methods_activation.py index bbffa007..a581ff1e 100644 --- a/tests/test_methods_activation.py +++ b/tests/test_methods_activation.py @@ -87,7 +87,6 @@ def test_video_cams(cam_name, target_layer, num_samples, output_size, mock_video # Hook the corresponding layer in the model with activation.__dict__[cam_name](model, target_layer, **kwargs) as extractor: - with torch.no_grad(): scores = model(mock_video_tensor) # Use the hooked data to compute activation map diff --git a/tests/test_methods_core.py b/tests/test_methods_core.py index 1674ea11..c7dd6fc6 100644 --- a/tests/test_methods_core.py +++ b/tests/test_methods_core.py @@ -102,7 +102,6 @@ def test_cam_repr(mock_img_model): def test_fuse_cams(): - with pytest.raises(TypeError): core._CAM.fuse_cams(torch.zeros((3, 32, 32))) diff --git a/tests/test_methods_gradient.py b/tests/test_methods_gradient.py index 2cc6e219..cfd41a22 100644 --- a/tests/test_methods_gradient.py +++ b/tests/test_methods_gradient.py @@ -33,7 +33,6 @@ def test_img_cams(cam_name, target_layer, output_size, batch_size, mock_img_tens target_layer = target_layer(model) if callable(target_layer) else target_layer # Hook the corresponding layer in the model with gradient.__dict__[cam_name](model, target_layer) as extractor: - scores = model(mock_img_tensor.repeat((batch_size,) + (1,) * (mock_img_tensor.ndim - 1))) # Use the hooked data to compute activation map _verify_cam(extractor(scores[0].argmax().item(), scores, retain_graph=True)[0], (batch_size, *output_size)) @@ -88,7 +87,6 @@ def test_smoothgradcampp_repr(): def test_layercam_fuse_cams(mock_img_model): - with pytest.raises(TypeError): gradient.LayerCAM.fuse_cams(torch.zeros((3, 32, 32))) diff --git a/tests/test_methods_utils.py b/tests/test_methods_utils.py index cb63ab5c..5947cc88 100644 --- a/tests/test_methods_utils.py +++ b/tests/test_methods_utils.py @@ -25,7 +25,6 @@ def test_locate_candidate_layer(mock_img_model): def test_locate_linear_layer(mock_img_model): - # ResNet-18 mod = resnet18().eval() for p in mod.parameters(): diff --git a/tests/test_metrics.py b/tests/test_metrics.py index 2d0ba7c8..8c5230d6 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -8,7 +8,6 @@ def test_classification_metric(): - model = mobilenet_v3_small(pretrained=False) with LayerCAM(model, "features.12") as extractor: metric = metrics.ClassificationMetric(extractor, partial(torch.softmax, dim=-1)) diff --git a/tests/test_utils.py b/tests/test_utils.py index 701b684c..e2c14ce9 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -5,7 +5,6 @@ def test_overlay_mask(): - img = Image.fromarray(np.zeros((4, 4, 3)).astype(np.uint8)) mask = Image.fromarray(255 * np.ones((4, 4)).astype(np.uint8)) diff --git a/torchcam/__init__.py b/torchcam/__init__.py index 7b5c0aab..16c708b0 100644 --- a/torchcam/__init__.py +++ b/torchcam/__init__.py @@ -1,6 +1,6 @@ from torchcam import methods, metrics, utils try: - from .version import __version__ # noqa: F401 + from .version import __version__ except ImportError: pass diff --git a/torchcam/methods/_utils.py b/torchcam/methods/_utils.py index 40fbf57e..727bc184 100644 --- a/torchcam/methods/_utils.py +++ b/torchcam/methods/_utils.py @@ -22,7 +22,6 @@ def locate_candidate_layer(mod: nn.Module, input_shape: Tuple[int, ...] = (3, 22 Returns: str: the candidate layer for CAM """ - # Set module in eval mode module_mode = mod.training mod.eval() @@ -69,7 +68,6 @@ def locate_linear_layer(mod: nn.Module) -> Optional[str]: Returns: str: the candidate layer """ - candidate_layer = None for layer_name, m in mod.named_modules(): if isinstance(m, nn.Linear): diff --git a/torchcam/methods/activation.py b/torchcam/methods/activation.py index 3af5d3bc..aabb35f1 100644 --- a/torchcam/methods/activation.py +++ b/torchcam/methods/activation.py @@ -54,7 +54,6 @@ def __init__( input_shape: Tuple[int, ...] = (3, 224, 224), **kwargs: Any, ) -> None: - if isinstance(target_layer, list) and len(target_layer) > 1: raise ValueError("base CAM does not support multiple target layers") @@ -82,13 +81,12 @@ def __init__( self._fc_weights = self._fc_weights.view(*self._fc_weights.shape[:2]) @torch.no_grad() - def _get_weights( + def _get_weights( # type: ignore[override] self, class_idx: Union[int, List[int]], *args: Any, ) -> List[Tensor]: """Computes the weight coefficients of the hooked activation maps.""" - # Take the FC weights of the target class if isinstance(class_idx, int): return [self._fc_weights[class_idx, :].unsqueeze(0)] @@ -143,24 +141,21 @@ def __init__( input_shape: Tuple[int, ...] = (3, 224, 224), **kwargs: Any, ) -> None: - super().__init__(model, target_layer, input_shape, **kwargs) # Input hook - self.hook_handles.append(model.register_forward_pre_hook(self._store_input)) + self.hook_handles.append(model.register_forward_pre_hook(self._store_input)) # type: ignore[arg-type] self.bs = batch_size # Ensure ReLU is applied to CAM before normalization self._relu = True def _store_input(self, module: nn.Module, input: Tensor) -> None: """Store model input tensor.""" - if self._hooks_enabled: self._input = input[0].data.clone() @torch.no_grad() def _get_score_weights(self, activations: List[Tensor], class_idx: Union[int, List[int]]) -> List[Tensor]: - b, c = activations[0].shape[:2] # (N * C, I, H, W) scored_inputs = [ @@ -178,7 +173,6 @@ def _get_score_weights(self, activations: List[Tensor], class_idx: Union[int, Li for idx, scored_input in enumerate(scored_inputs): # Process by chunk (GPU RAM limitation) for _idx in range(math.ceil(weights[idx].numel() / self.bs)): - _slice = slice(_idx * self.bs, min((_idx + 1) * self.bs, weights[idx].numel())) # Get the softmax probabilities of the target class # (*, M) @@ -193,13 +187,12 @@ def _get_score_weights(self, activations: List[Tensor], class_idx: Union[int, Li return [torch.softmax(w.view(b, c), -1) for w in weights] @torch.no_grad() - def _get_weights( + def _get_weights( # type: ignore[override] self, class_idx: Union[int, List[int]], *args: Any, ) -> List[Tensor]: """Computes the weight coefficients of the hooked activation maps.""" - self.hook_a: List[Tensor] # type: ignore[assignment] # Normalize the activation @@ -288,7 +281,6 @@ def __init__( input_shape: Tuple[int, ...] = (3, 224, 224), **kwargs: Any, ) -> None: - super().__init__(model, target_layer, batch_size, input_shape, **kwargs) self.num_samples = num_samples @@ -297,7 +289,6 @@ def __init__( @torch.no_grad() def _get_score_weights(self, activations: List[Tensor], class_idx: Union[int, List[int]]) -> List[Tensor]: - b, c = activations[0].shape[:2] # Initialize weights @@ -319,7 +310,6 @@ def _get_score_weights(self, activations: List[Tensor], class_idx: Union[int, Li # Process by chunk (GPU RAM limitation) for _idx in range(math.ceil(weights[idx].numel() / self.bs)): - _slice = slice(_idx * self.bs, min((_idx + 1) * self.bs, weights[idx].numel())) # Get the softmax probabilities of the target class cic = self.model(scored_input[_slice]) - logits[idcs[_slice]] @@ -387,14 +377,12 @@ def __init__( input_shape: Tuple[int, ...] = (3, 224, 224), **kwargs: Any, ) -> None: - super().__init__(model, target_layer, batch_size, input_shape, **kwargs) self.num_samples = num_samples @torch.no_grad() def _get_score_weights(self, activations: List[Tensor], class_idx: Union[int, List[int]]) -> List[Tensor]: - b, c = activations[0].shape[:2] # (N * C, I, H, W) scored_inputs = [ @@ -416,7 +404,6 @@ def _get_score_weights(self, activations: List[Tensor], class_idx: Union[int, Li # Process by chunk (GPU RAM limitation) for _idx in range(math.ceil(weights[idx].numel() / self.bs)): - _slice = slice(_idx * self.bs, min((_idx + 1) * self.bs, weights[idx].numel())) # Get the softmax probabilities of the target class cic = self.model(_coeff * scored_input[_slice]) - logits[idcs[_slice]] diff --git a/torchcam/methods/core.py b/torchcam/methods/core.py index 08d68e81..a515baac 100644 --- a/torchcam/methods/core.py +++ b/torchcam/methods/core.py @@ -35,7 +35,6 @@ def __init__( input_shape: Tuple[int, ...] = (3, 224, 224), enable_hooks: bool = True, ) -> None: - # Obtain a mapping from module name to module instance for each layer in the model self.submodule_dict = dict(model.named_modules()) @@ -133,7 +132,6 @@ def _get_weights(self, class_idx, scores, **kwargs): # type: ignore[no-untyped- def _precheck(self, class_idx: Union[int, List[int]], scores: Optional[Tensor] = None) -> None: """Check for invalid computation cases.""" - for fmap in self.hook_a: # Check that forward has already occurred if not isinstance(fmap, Tensor): @@ -159,7 +157,6 @@ def __call__( normalized: bool = True, **kwargs: Any, ) -> List[Tensor]: - # Integrity check self._precheck(class_idx, scores) @@ -180,13 +177,13 @@ def compute_cams( the list needs to have valid class indices and have a length equal to the batch size. scores: forward output scores of the hooked model of shape (N, K) normalized: whether the CAM should be normalized + kwargs: keyword args of `_get_weights` method Returns: list of class activation maps of shape (N, H, W), one for each hooked layer. If a list of class indices was passed to arg `class_idx`, the k-th element along the batch axis will be the activation map for the k-th element of the input batch for class index equal to the k-th element of `class_idx`. """ - # Get map weight & unsqueeze it weights = self._get_weights(class_idx, scores, **kwargs) @@ -229,7 +226,6 @@ def fuse_cams(cls, cams: List[Tensor], target_shape: Optional[Tuple[int, int]] = Returns: torch.Tensor: fused class activation map """ - if not isinstance(cams, list) or any(not isinstance(elt, Tensor) for elt in cams): raise TypeError("invalid argument type for `cams`") diff --git a/torchcam/methods/gradient.py b/torchcam/methods/gradient.py index 1a4fea12..3fe5d411 100644 --- a/torchcam/methods/gradient.py +++ b/torchcam/methods/gradient.py @@ -30,7 +30,6 @@ def __init__( input_shape: Tuple[int, ...] = (3, 224, 224), **kwargs: Any, ) -> None: - super().__init__(model, target_layer, input_shape, **kwargs) # Ensure ReLU is applied before normalization self._relu = True @@ -51,7 +50,6 @@ def _hook_g(self, module: nn.Module, input: Tuple[Tensor, ...], output: Tensor, def _backprop(self, scores: Tensor, class_idx: Union[int, List[int]], retain_graph: bool = False) -> None: """Backpropagate the loss for a specific output class""" - # Backpropagate to get the gradients on the hooked layer if isinstance(class_idx, int): loss = scores[:, class_idx].sum() @@ -61,7 +59,6 @@ def _backprop(self, scores: Tensor, class_idx: Union[int, List[int]], retain_gra loss.backward(retain_graph=retain_graph) def _get_weights(self, class_idx: Union[int, List[int]], scores: Tensor, **kwargs: Any) -> List[Tensor]: - raise NotImplementedError @@ -99,7 +96,6 @@ class GradCAM(_GradCAM): def _get_weights(self, class_idx: Union[int, List[int]], scores: Tensor, **kwargs: Any) -> List[Tensor]: """Computes the weight coefficients of the hooked activation maps.""" - # Backpropagate self._backprop(scores, class_idx, **kwargs) @@ -153,7 +149,6 @@ def _get_weights( self, class_idx: Union[int, List[int]], scores: Tensor, eps: float = 1e-8, **kwargs: Any ) -> List[Tensor]: """Computes the weight coefficients of the hooked activation maps.""" - # Backpropagate self._backprop(scores, class_idx, **kwargs) self.hook_a: List[Tensor] # type: ignore[assignment] @@ -237,13 +232,12 @@ def __init__( input_shape: Tuple[int, ...] = (3, 224, 224), **kwargs: Any, ) -> None: - super().__init__(model, target_layer, input_shape, **kwargs) # Model scores is not used by the extractor self._score_used = False # Input hook - self.hook_handles.append(model.register_forward_pre_hook(self._store_input)) + self.hook_handles.append(model.register_forward_pre_hook(self._store_input)) # type: ignore[arg-type] # Noise distribution self.num_samples = num_samples self.std = std @@ -253,7 +247,6 @@ def __init__( def _store_input(self, module: nn.Module, input: Tensor) -> None: """Store model input tensor.""" - if self._ihook_enabled: self._input = input[0].data.clone() @@ -261,7 +254,6 @@ def _get_weights( self, class_idx: Union[int, List[int]], scores: Optional[Tensor] = None, eps: float = 1e-8, **kwargs: Any ) -> List[Tensor]: """Computes the weight coefficients of the hooked activation maps.""" - # Disable input update self._ihook_enabled = False # Keep initial activation @@ -343,7 +335,6 @@ def _get_weights( self, class_idx: Union[int, List[int]], scores: Tensor, eps: float = 1e-8, **kwargs: Any ) -> List[Tensor]: """Computes the weight coefficients of the hooked activation maps.""" - # Backpropagate self._backprop(scores, class_idx, **kwargs) @@ -389,7 +380,6 @@ class LayerCAM(_GradCAM): def _get_weights(self, class_idx: Union[int, List[int]], scores: Tensor, **kwargs: Any) -> List[Tensor]: """Computes the weight coefficients of the hooked activation maps.""" - # Backpropagate self._backprop(scores, class_idx, **kwargs) diff --git a/torchcam/metrics.py b/torchcam/metrics.py index 44522705..877c411b 100644 --- a/torchcam/metrics.py +++ b/torchcam/metrics.py @@ -57,7 +57,6 @@ def __init__( cam_extractor: _CAM, logits_fn: Union[Callable[[torch.Tensor], torch.Tensor], None] = None, ) -> None: - self.cam_extractor = cam_extractor self.logits_fn = logits_fn self.reset() @@ -77,7 +76,6 @@ def update( input_tensor: preprocessed input tensor for the model class_idx: class index to focus on (default: index of the top predicted class for each sample) """ - self.cam_extractor.model.eval() probs = self._get_probs(input_tensor) # Take the top preds for the cam @@ -124,10 +122,10 @@ def summary(self) -> Dict[str, float]: if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") - return dict( - avg_drop=self.drop / self.total, - conf_increase=self.increase / self.total, - ) + return { + "avg_drop": self.drop / self.total, + "conf_increase": self.increase / self.total, + } def reset(self) -> None: self.drop = 0.0 diff --git a/torchcam/utils.py b/torchcam/utils.py index 3595cfc2..877b66a1 100644 --- a/torchcam/utils.py +++ b/torchcam/utils.py @@ -31,7 +31,6 @@ def overlay_mask(img: Image.Image, mask: Image.Image, colormap: str = "jet", alp TypeError: when the arguments have invalid types ValueError: when the alpha argument has an incorrect value """ - if not isinstance(img, Image.Image) or not isinstance(mask, Image.Image): raise TypeError("img and mask arguments need to be PIL.Image")