From f4f002d9d851ddf73e549a4b497fa93591c33cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Mon, 15 Sep 2025 20:33:09 +0200 Subject: [PATCH 1/2] gh: add dissemination contrib to ego-thales and eliegoudout (github.com/huytransformer/Awesome-Out-Of-Distribution-Detection/pull/15) [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Élie Goudout --- CONTRIBUTORS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 880adbb..d7bc8cd 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -12,7 +12,7 @@ Possible contribution values are: `answering questions`, `bug reports`, `code`, | GitHub username | Contributions | Name (optional) | |-----------------|---------------|-----------------| | Sebastienlejeune | infrastructure | Lejeune, Sébastien | -| ego-thales | bug reports, code, documentation, fixes, ideas, maintenance, pr reviews, testing, tutorials | Goudout, Élie | -| eliegoudout | bug reports, documentation, fixes, maintenance | Goudout, Élie | +| ego-thales | bug reports, code, dissemination, documentation, fixes, ideas, maintenance, pr reviews, testing, tutorials | Goudout, Élie | +| eliegoudout | bug reports, dissemination, documentation, fixes, maintenance | Goudout, Élie | | Lap0u | fixes | Beaurain, Clément | From cbd38fa397abae6440ee12d3629081f9755cc801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Goudout?= Date: Mon, 15 Sep 2025 22:12:18 +0200 Subject: [PATCH 2/2] ci, ruff: add timeout (closes #30), separate linting, factor strategy/setup + fix lint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Élie Goudout --- .github/workflows/ci.yml | 32 ++++++++----------- scio/scores/classification/deepmahalanobis.py | 2 +- scio/scores/classification/dknn.py | 2 +- scio/scores/classification/gram.py | 4 +-- scio/scores/classification/isomax.py | 2 +- scio/scores/classification/jtla.py | 6 ++-- scio/scores/classification/odds.py | 2 +- test/scio/eval/classification/roc/test_roc.py | 6 ++-- .../scores/classification/base/test_base.py | 2 +- 9 files changed, 27 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d99045..2578878 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,23 +7,21 @@ on: branches: [develop, release] jobs: - build: - name: continuous-integration - runs-on: ${{ matrix.os }} + ci: + timeout-minutes: 15 strategy: matrix: + os: ["ubuntu-latest", "windows-latest"] # Remove MacOS (billing + github.com/ThalesGroup/scio/issues/2) python-version: ["3.12", "3.13"] - os: ["ubuntu-latest", "windows-latest", "macos-latest"] - - + runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v4 - - name: Install the latest version of uv and set the python version + - name: Install uv and set python version uses: astral-sh/setup-uv@v6 with: - version: "0.8.9" + version: "0.8.17" python-version: ${{ matrix.python-version }} - name: Run ruff @@ -35,8 +33,13 @@ jobs: run: | uv -v run mypy + - name: Build & Install + run: | + uv -v build + uv -v pip install dist/scio_pypi-1.0.0rc3-py3-none-any.whl + - name: Build docs (Posix) - if: matrix.os != 'windows-latest' && matrix.os != 'macos-latest' # Temporary (github.com/ThalesGroup/scio/issues/2) + if: matrix.os != 'windows-latest' env: SPHINXOPTS: --fail-on-warning run: | @@ -49,13 +52,7 @@ jobs: run: | uv -v run cmd /c "docs\\make.bat" - - name: Build & Install - run: | - uv -v build - uv -v pip install dist/scio_pypi-1.0.0rc3-py3-none-any.whl - - name: Run pytest - if: matrix.os != 'macos-latest' # Temporary (github.com/ThalesGroup/scio/issues/2) id: run-pytest continue-on-error: true run: | @@ -67,13 +64,13 @@ jobs: with: name: pytest-observed-${{ matrix.os }}-py${{ matrix.python-version }} path: test/expected/*/*.observed.* - retention-days: 7 + retention-days: 30 - name: Fail the job if pytest failed if: ${{ steps.run-pytest.outcome == 'failure' }} run: exit 1 - - name: Upload coverage report to Codecov (Ubuntu, python3.13) + - name: Upload coverage report to Codecov if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13' uses: codecov/codecov-action@v5 with: @@ -82,7 +79,6 @@ jobs: # TEMPORARILY DISABLED (https://github.com/ThalesGroup/scio/issues/9) # - name: Run pytest with lowest resolution - # if: matrix.os != 'macos-latest' # Temporary (github.com/ThalesGroup/scio/issues/2) # id: run-pytest-lowest # continue-on-error: true # run: | diff --git a/scio/scores/classification/deepmahalanobis.py b/scio/scores/classification/deepmahalanobis.py index ed9fc6d..a37b32e 100644 --- a/scio/scores/classification/deepmahalanobis.py +++ b/scio/scores/classification/deepmahalanobis.py @@ -300,7 +300,7 @@ def compute_precision(residues: Tensor) -> Tensor: if not precision.isfinite().all(): raise torch.linalg.LinAlgError # pragma: no cover # noqa: TRY301 except torch.linalg.LinAlgError: - data_dim = residues.size(1) + data_dim = residues.shape[1] return torch.full((data_dim, data_dim), torch.nan).to(residues) return precision diff --git a/scio/scores/classification/dknn.py b/scio/scores/classification/dknn.py index ca0fc65..9d1be92 100644 --- a/scio/scores/classification/dknn.py +++ b/scio/scores/classification/dknn.py @@ -61,7 +61,7 @@ def _check_params(self, n_calib: int) -> None: def calibrate(self, calib_data: Tensor, calib_labels: Tensor) -> None: """Calibrate the scoring algorithm with In-Distribution data.""" self.calib_labels = calib_labels - n_samples, n_classes = self.rnet(calib_data).shape # Records activations + n_samples = len(self.rnet(calib_data)) # Records activations all_activations = self.activations() self.indexes = make_indexes(all_activations, metric=self.index_metric) diff --git a/scio/scores/classification/gram.py b/scio/scores/classification/gram.py index a8937de..4a421a0 100644 --- a/scio/scores/classification/gram.py +++ b/scio/scores/classification/gram.py @@ -135,7 +135,7 @@ def calibrate(self, calib_data: Tensor, calib_labels_true: Tensor) -> None: def get_conformity(self, inputs: Tensor) -> tuple[Tensor, Tensor]: """Compute output and associated conformity at inference.""" out = self.rnet(inputs) # Records activations - n_samples, n_classes = out.shape + n_classes = out.shape[1] total_deviations = torch.zeros_like(out) for activations, low_high, expected_deviation in zip( @@ -184,7 +184,7 @@ def layer_stats(self, activations: Tensor) -> Tensor: ) raise ValueError(msg) - n_channels = activations.size(1) if ndim == 4 else 1 # noqa: PLR2004 (magic value 4) + n_channels = activations.shape[1] if ndim == 4 else 1 # noqa: PLR2004 (magic value 4) per_channel = activations.shape[ndim // 2 :].numel() activations_3d = activations.reshape(len(activations), n_channels, per_channel) diff --git a/scio/scores/classification/isomax.py b/scio/scores/classification/isomax.py index 9f267c9..e5c5e35 100644 --- a/scio/scores/classification/isomax.py +++ b/scio/scores/classification/isomax.py @@ -60,7 +60,7 @@ def calibrate(self, calib_data: Tensor, calib_labels: Tensor) -> None: with torch.no_grad(): logits = self.rnet(calib_data, dont_record=True) - n_classes = logits.size(1) + n_classes = logits.shape[1] # Init at 0 like [IsoMax] self.prototypes = torch.zeros( n_classes, diff --git a/scio/scores/classification/jtla.py b/scio/scores/classification/jtla.py index d6bcfad..e8c7fd5 100644 --- a/scio/scores/classification/jtla.py +++ b/scio/scores/classification/jtla.py @@ -167,7 +167,7 @@ def calibrate(self, calib_data: Tensor, calib_labels: Tensor) -> None: all_activations = self.activations() self.calib_labels_true = calib_labels self.calib_labels_pred = out.argmax(1) if self.pred_conditional else None - self.prepare_tests(all_activations, out.size(1)) + self.prepare_tests(all_activations, out.shape[1]) # Next are tensors of shape (n_classes, n_calib_samples, n_layers) (or None) self.calib_tests_true, self.calib_tests_pred = self.run_tests(all_activations) @@ -410,7 +410,7 @@ def aggregate_layers( # Special aK-LPE method if self.layer_aggregation_method == "lpe": - n_calib = self.lpe_true_calib.size(1) + n_calib = self.lpe_true_calib.shape[1] similarity_search = self.index_metric == "ip" normed_true = self.lpe_normalizer(tests_true) @@ -510,7 +510,7 @@ def slicer(n: int, mask: Tensor) -> Tensor: if only_consecutive: return mask.unfold(1, n, 1) - range_n_layers = torch.arange(mask.size(1)).to(device=device) + range_n_layers = torch.arange(mask.shape[1]).to(device=device) combs = torch.combinations(range_n_layers, n) return mask[:, combs] diff --git a/scio/scores/classification/odds.py b/scio/scores/classification/odds.py index 567485f..8720930 100644 --- a/scio/scores/classification/odds.py +++ b/scio/scores/classification/odds.py @@ -68,7 +68,7 @@ def calibrate(self, calib_data: Tensor, calib_labels: Tensor) -> None: self.rng = torch.Generator(calib_data.device).manual_seed(self.rng_seed) logits = self.rnet(calib_data, dont_record=True) - n_samples, n_classes = logits.shape + n_classes = logits.shape[1] self.g_stats_avg = torch.full( (n_classes,) * 2, torch.nan, diff --git a/test/scio/eval/classification/roc/test_roc.py b/test/scio/eval/classification/roc/test_roc.py index c25b0b5..3ee61aa 100644 --- a/test/scio/eval/classification/roc/test_roc.py +++ b/test/scio/eval/classification/roc/test_roc.py @@ -18,13 +18,13 @@ def test_roc_trivial(): @parametrize_bool("label") def test_roc_requires_positive_and_negative(label): """Test that both positive and negative samples are required.""" - with pytest.raises(AssertionError, match="^$"): + with pytest.raises(AssertionError, match=r"^$"): ROC([label], [0]) def test_roc_rejects_nan(): """Test that ``nan`` are forbidden.""" - with pytest.raises(AssertionError, match="^$"): + with pytest.raises(AssertionError, match=r"^$"): ROC([T, F], [0, np.nan]) @@ -34,7 +34,7 @@ def test_roc_allows_only_posinf(sign): if sign > 0: ROC([T, F], [0, I]) else: - with pytest.raises(AssertionError, match="^$"): + with pytest.raises(AssertionError, match=r"^$"): ROC([T, F], [0, -I]) diff --git a/test/scio/scores/classification/base/test_base.py b/test/scio/scores/classification/base/test_base.py index ef0a159..c22dbe6 100644 --- a/test/scio/scores/classification/base/test_base.py +++ b/test/scio/scores/classification/base/test_base.py @@ -24,7 +24,7 @@ def test_raises_on_unexpected_mode(monkeypatch_enum_new_member, score, test_data def test_accepts_unique_conformity(score, class_aggregation, test_data): """Check that the score can output one score only per sample.""" - out, conformity = score(test_data) + conformity = score(test_data)[1] same_conformity_over_classes = (conformity == conformity[:, [0]]).all() assert conformity.shape == (len(test_data), N_CLASSES)