From 7813099b9efdd517996fb54fb7d62e6c0aa2b9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20G=C3=B3mez?= Date: Wed, 13 May 2026 17:50:28 +0200 Subject: [PATCH 1/2] chore(release): prepare v1.3.1 - Bump version to 1.3.1 in pyproject.toml and anomaly_match/__init__.py - Raise cutana minimum to >=0.3.0 in environment.yml, environment_CI.yml, pyproject.toml - Drop obsolete synchronised_loading kwarg from StreamingOrchestrator.init_streaming calls - Add v1.3.1 CHANGELOG entry covering the cutana 0.3.0 compatibility fix and the previously-mirrored safetensors / PyTorch>=2.6 / programmatic-evaluation README changes --- CHANGELOG.MD | 14 ++++++++++++++ anomaly_match/__init__.py | 2 +- environment.yml | 2 +- environment_CI.yml | 2 +- prediction_process_cutana.py | 4 +--- pyproject.toml | 4 ++-- tests/e2e/test_normalisation_consistency.py | 4 ++-- tests/unit/test_checkpoint_io.py | 7 +++---- 8 files changed, 25 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 460fa58..ff72eb9 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -5,6 +5,20 @@ [//]: # (this file, may be copied, modified, propagated, or distributed except according to) [//]: # (the terms contained in the file 'LICENCE.txt'.) +## [v1.3.1] – 2026-05-11 + +### Fixed +- **Cutana 0.3.0 compatibility** by removing the obsolete `synchronised_loading` kwarg from `StreamingOrchestrator.init_streaming` calls +- **Pretrained weight loading** when the repository is cloned without `git-lfs`: fall back to a HuggingFace download instead of failing on LFS pointer files (#7) + +### Changed +- **Security:** replaced pickle-based `torch.save` / `torch.load` checkpointing with `safetensors`, removing the pickle deserialisation risk on untrusted model files (#9) +- **PyTorch minimum** raised to `>=2.6` in the conda environments to pick up upstream CVE fixes (#11) +- **Cutana minimum** raised to `>=0.3.0` in `environment.yml`, `environment_CI.yml`, and `pyproject.toml` + +### Documentation +- **Programmatic Evaluation (Headless Mode)** section added to the README, showing how to run batch predictions through `Session.load_model()` + `Session.evaluate_all_images()` without launching the UI (#10) + ## [v1.3.0] – 2026-02-11 ### Added diff --git a/anomaly_match/__init__.py b/anomaly_match/__init__.py index 6b08686..2c38ee7 100644 --- a/anomaly_match/__init__.py +++ b/anomaly_match/__init__.py @@ -12,7 +12,7 @@ from .utils.print_cfg import print_cfg from .utils.set_log_level import set_log_level -__version__ = "1.3.0" +__version__ = "1.3.1" __all__ = [ "get_default_cfg", diff --git a/environment.yml b/environment.yml index cd31937..3535713 100644 --- a/environment.yml +++ b/environment.yml @@ -33,7 +33,7 @@ dependencies: - pip - pip: - albumentations - - cutana>=0.2.1 + - cutana>=0.3.0 - fitsbolt>=0.2 - opencv-python-headless - safetensors diff --git a/environment_CI.yml b/environment_CI.yml index 6c52521..64a2bc7 100644 --- a/environment_CI.yml +++ b/environment_CI.yml @@ -38,4 +38,4 @@ dependencies: - safetensors - timm - fitsbolt>=0.2 - - cutana>=0.2.1 + - cutana>=0.3.0 diff --git a/prediction_process_cutana.py b/prediction_process_cutana.py index b248721..e75910a 100644 --- a/prediction_process_cutana.py +++ b/prediction_process_cutana.py @@ -210,9 +210,7 @@ def evaluate_images_from_cutana( cutana_orchestrator = cutana.StreamingOrchestrator(cutana_config) - cutana_orchestrator.init_streaming( - batch_size=batch_size, write_to_disk=False, synchronised_loading=False - ) + cutana_orchestrator.init_streaming(batch_size=batch_size, write_to_disk=False) except Exception as e: logger.error(f"Failed to initialize Cutana orchestrator: {e}") raise diff --git a/pyproject.toml b/pyproject.toml index 71703fb..52fe6fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "anomaly_match" -version = "1.3.0" +version = "1.3.1" description = "A tool for anomaly detection in images using semi-supervised and active learning with a GUI" readme = "README.md" license = { file = "LICENSE.txt" } @@ -32,7 +32,7 @@ classifiers = [ dependencies = [ "albumentations", "astropy", - "cutana>=0.2.1", + "cutana>=0.3.0", "dotmap", "fitsbolt>=0.2", "h5py", diff --git a/tests/e2e/test_normalisation_consistency.py b/tests/e2e/test_normalisation_consistency.py index 7d32171..13e5bec 100644 --- a/tests/e2e/test_normalisation_consistency.py +++ b/tests/e2e/test_normalisation_consistency.py @@ -86,7 +86,7 @@ def _run_cutana_normalised(csv_path, fitsbolt_cfg, n_output_channels=3): config.external_fitsbolt_cfg = fitsbolt_cfg orchestrator = cutana.StreamingOrchestrator(config) - orchestrator.init_streaming(batch_size=10, write_to_disk=False, synchronised_loading=False) + orchestrator.init_streaming(batch_size=10, write_to_disk=False) all_cutouts = [] for _ in range(orchestrator.get_batch_count()): @@ -112,7 +112,7 @@ def _extract_raw_cutouts(csv_path, output_dir): config.output_dir = output_dir orchestrator = cutana.StreamingOrchestrator(config) - orchestrator.init_streaming(batch_size=10, write_to_disk=True, synchronised_loading=False) + orchestrator.init_streaming(batch_size=10, write_to_disk=True) for _ in range(orchestrator.get_batch_count()): orchestrator.next_batch() orchestrator.cleanup() diff --git a/tests/unit/test_checkpoint_io.py b/tests/unit/test_checkpoint_io.py index d87bfc8..c8848e9 100644 --- a/tests/unit/test_checkpoint_io.py +++ b/tests/unit/test_checkpoint_io.py @@ -7,11 +7,14 @@ """Unit tests for checkpoint_io: safetensors-based model checkpoint serialization.""" +import json + import numpy as np import pytest import torch from dotmap import DotMap from fitsbolt.normalisation.NormalisationMethod import NormalisationMethod +from safetensors import safe_open from anomaly_match.data_io.checkpoint_io import load_checkpoint, save_checkpoint @@ -228,10 +231,6 @@ def test_no_pickle_in_file(self, tmp_path): def test_metadata_is_plain_json(self, tmp_path): """All metadata in the safetensors header is valid JSON strings.""" - import json - - from safetensors import safe_open - path = save_checkpoint(_make_full_checkpoint(), tmp_path / "model") with safe_open(str(path), framework="pt") as f: metadata = f.metadata() From cdd453347ecf08cb7f0b815436ed80623d8511ee Mon Sep 17 00:00:00 2001 From: Pablo Gomez Date: Thu, 14 May 2026 10:46:48 +0200 Subject: [PATCH 2/2] fix(cutana): surface extension-mismatch hint on empty batches Improves the warning emitted when Cutana returns empty cutouts so users are guided to verify the catalogue extension count matches the training set, instead of failing silently. --- prediction_process_cutana.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/prediction_process_cutana.py b/prediction_process_cutana.py index e75910a..4254a07 100644 --- a/prediction_process_cutana.py +++ b/prediction_process_cutana.py @@ -264,7 +264,11 @@ def evaluate_images_from_cutana( # Handle empty batches (cutana returns [] if all cutouts failed) if isinstance(batch_data, list): if len(batch_data) == 0: - logger.warning(f"Batch {batch_idx} returned empty cutouts (list), skipping") + logger.warning( + f"Batch {batch_idx} returned empty cutouts (list), skipping. " + f"Please ensure that number of extensions in Cutana source " + f"catalogues matches those of the training set." + ) continue # Convert list to numpy array if needed batch_data = np.array(batch_data)