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..4254a07 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 @@ -266,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) 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()