v0.10.0

Upgrade Notes

  • The British spelling neighbour is replaced by neighbor throughout the library. One public name changes with it: InMemoryImageEmbeddingStore.retrieve_top_k_similar now takes expansion_neighbors where it took expansion_neighbours.

  • The British spelling serialise is replaced by serialize throughout the library. Only documentation, docstrings and comments are affected.

  • DeepConvFeature moved to pyvisim.neural_networks.features, so from pyvisim.features import DeepConvFeature now raises ImportError. If your code imports the class, change the import:

    # before
    from pyvisim.features import DeepConvFeature
    
    # after
    from pyvisim.neural_networks.features import DeepConvFeature
    
  • pyvisim.structural and pyvisim.pixelwise moved under the new pyvisim.dense package. Imports of SSIM, MSSSIM and PSNR from the old paths fail with ModuleNotFoundError, so change them to pyvisim.dense.structural and pyvisim.dense.pixelwise:

    from pyvisim.dense.pixelwise import PSNR
    from pyvisim.dense.structural import MSSSIM, SSIM
    
  • pyvisim.features.feature_extractor_from_dict was removed. Feature extractors are now rebuilt through FeatureExtractorBase.from_dict, which takes the same mapping and returns the same extractor. You are affected if you import the function by name:

    # Before
    from pyvisim.features import feature_extractor_from_dict
    
    extractor = feature_extractor_from_dict(data)
    
    # After
    from pyvisim.base import FeatureExtractorBase
    
    extractor = FeatureExtractorBase.from_dict(data)
    

    Saved .embedder files are unaffected, since the serialized format did not change.

  • The first parameter of FeatureExtractorBase.from_dict was renamed from data to state. You are affected if you pass it by keyword, and from_dict(data=...) becomes from_dict(state=...).

  • The mapping returned by a feature extractor’s to_dict now also holds a "format_version" key.

  • The _serialization_config hook of FeatureExtractorBase was removed. A custom extractor that overrides it now overrides _state instead and returns {"config": ...}, otherwise its arguments are no longer saved.

  • InMemoryImageEmbeddingStore no longer embeds its gallery in the constructor, which affects every store built through it. Call build_store() right after the constructor, or pass lazy_build=False to have the constructor build the store.

  • Until build_store() has run, search, retrieve_top_k_similar, embeddings, embeddings_of, index, dim, to_dict and save_to_disk raise RuntimeError. A store that adopts an ExternalSearchIndex or is rebuilt by load_from_disk or from_dict comes back built.

  • build_store() now raises for an unreadable image file and for a gallery none of whose images could be embedded, which the constructor used to report. An unknown search_index, a rejected index_params entry, a non-string path and an empty gallery still raise in the constructor.

  • pyvisim.neural_networks no longer re-exports DeepConvFeature, so from pyvisim.neural_networks import DeepConvFeature now raises ImportError. Import the class from pyvisim.neural_networks.features instead:

    # before
    from pyvisim.neural_networks import DeepConvFeature
    
    # after
    from pyvisim.neural_networks.features import DeepConvFeature
    
  • Importing pyvisim no longer configures logging, and its loggers are named after their modules (e.g. pyvisim.datasets.datasets) instead of Data_Set, Feature_Extractor, Pipeline and Similarity_Metrics. Configure the pyvisim logger to see the library’s messages.

  • from pyvisim import * no longer imports datasets, which needs the nn extra. Access it as pyvisim.datasets or import it explicitly.

  • VLADEmbedder and FisherVectorEmbedder default to raise_error_when_pca_incompatible=True, so a clustering model the fitted PCA cannot feed raises a RuntimeError instead of silently dropping the PCA. Pass False to keep the old behavior.

  • pyvisim._base_classes is removed. Import SimilarityMetric, FeatureExtractorBase, ImageEmbedderBase and SerializableImageEmbedder from pyvisim.base instead.

  • VLADEmbedder and FisherVectorEmbedder no longer take raise_error_when_pca_incompatible. A clustering model the fitted PCA cannot feed always raises a RuntimeError, so drop the argument.

  • VLADEmbedder and FisherVectorEmbedder no longer take flatten. embed always returns one flat row per image, so drop the argument.

  • Embedders saved by VLADEmbedder and FisherVectorEmbedder are written under format version 4. Files of version 3 still load, files of version 4 do not load with an older pyvisim.

  • OxfordFlowerDataset no longer accepts a transform argument. Passing one raised NotImplementedError before, so only calls that spelled out transform=None or passed purpose positionally are affected: drop the argument and pass purpose by keyword. Every item stays the raw RGB uint8 array that the embedders and metrics accept, and any preprocessing belongs to a wrapping Dataset.

  • save_to_disk writes to the given path as is, and .embedder or .safetensors is no longer appended to it. Code that saves under a name without a suffix and loads the file with the suffix added passes the same full file name to both calls.

  • The __file_format__ class attribute is removed from SerializerMixin and every serializable class. Subclasses outside the library drop their __file_format__.

  • InMemoryImageEmbeddingStore.search is removed. Code that searched a store by vectors calls store.index.search(query_vectors, k) instead.

  • EmbeddingStore protocol class declares retrieve_top_k_similar(query_images, k) in place of search(query_vectors, k). Custom stores implement the new method.

  • pyvisim.image_store is now pyvisim.retrieval, split into the subpackages image_store (InMemoryImageEmbeddingStore, BruteForceIndex, HnswIndex, ExternalSearchIndex), reranking (KReciprocalReranker) and data (Candidate), so from pyvisim.image_store import ... now raises ModuleNotFoundError. If your code imports from pyvisim.image_store, change the import:

    # before
    from pyvisim.image_store import (
        Candidate,
        InMemoryImageEmbeddingStore,
        KReciprocalReranker,
    )
    
    # after
    from pyvisim.retrieval.data import Candidate
    from pyvisim.retrieval.image_store import InMemoryImageEmbeddingStore
    from pyvisim.retrieval.reranking import KReciprocalReranker
    
  • The vectors parameter of InMemoryImageEmbeddingStore.save_to_disk is renamed to embeddings. Code that passes vectors=... passes embeddings=... instead.

  • Notebooks are committed without outputs. Run make strip-notebooks before committing one, since the CI rejects notebooks that still carry outputs or execution metadata.

  • Every .embedder file and image store file now names its class under "__class__", and their format versions go up by one. Files saved by 0.9.5 or earlier no longer load, so rebuild the embedder or store and save it again with save_to_disk.

  • The __class_key__ class attribute is removed from SerializerMixin and every serializable class. Subclasses outside the library drop their __class_key__, since the class name is always stored under "__class__".

New Features

  • Feature extractors can be saved on their own with save_to_disk and loaded back with load_from_disk, as .safetensors files.

  • InMemoryImageEmbeddingStore.build_store embeds the gallery and builds the search index. It does nothing on a store that is already built.

  • InMemoryImageEmbeddingStore.is_built reports whether the store has been built.

Enhancement Notes

  • The new tutorial Custom Feature Extractor with ORB under the classical methods shows how to write a feature extractor by inheriting from FeatureExtractorBase, train a VLADEmbedder with it and compare two images.

  • The tutorials dependency group installs opencv-python-headless, which the custom feature extractor tutorial uses for ORB.

  • FeatureExtractorBase now owns both halves of its serialization contract: to_dict and the new from_dict. Subclasses register themselves as they are defined, so a new feature extractor is rebuilt from a serialized description without being added to a hand-maintained table. Extractors that need more than their stored constructor arguments override the _from_config hook, as DeepConvFeature does to rebuild its backbone by name.

  • FeatureExtractorBase.from_dict forwards its keyword arguments to the _from_config hook, so a custom extractor can take objects its state cannot hold.

  • A DeepConvFeature built on a user-supplied model or a custom transform can be rebuilt by passing them back as backbone= and transform=. A FutureWarning is emitted if the transform differs from the saved one.

  • VLADEmbedder and FisherVectorEmbedder take a feature_extractor_params dict in from_dict and load_from_disk, which is forwarded to the feature extractor. An embedder on a DeepConvFeature with a user-supplied model is loaded with feature_extractor_params={"backbone": model}.

  • The tutorial notebooks define their plotting helpers themselves instead of importing them from a shared module, so that a change to a helper no longer executes every notebook again.

  • OxfordFlowerDataset reads its .mat files with scipy.io.loadmat again. The internal MAT-file reader pyvisim.datasets._matloader was removed since SciPy is a runtime dependency.

  • Simplified the [dependency-groups] in pyproject.toml:

    • Merged the release group into docs, since reno and dulwich are only used to build the release notes pages of the documentation.

    • Merged types, dead-code and fmt into a single lint group, since mypy, vulture and ruff are all static analysis tools.

    Contributors running make targets are unaffected. Anyone invoking uv run/uv sync with --group release, --group types, --group dead-code or --group fmt directly should use --group docs or --group lint instead.

  • The tutorial notebooks moved from the separate examples repository into docs/tutorials/notebooks/, together with their plotting helpers.

  • The Siamese, Triplet and VLAD/Fisher Vector tutorials train on a subset of Oxford Flowers, so that they also finish on a CPU.

  • The documentation renders every tutorial notebook as a page with its executed outputs.

  • The Docs workflow also builds pull requests, executes the tutorial notebooks they change and uploads the rendered HTML as the docs-html artifact.

  • The new Tutorials workflow executes every tutorial notebook from scratch every three days and on demand.

  • The clustering helper of the tutorials reports normalized mutual information under nmi instead of adjusted mutual information.

  • The image search tutorial builds its FAISS index from the embeddings of the store instead of embedding the gallery a second time.

  • The pipeline tutorial trains on every twelfth training image, which covers all 102 classes instead of the three the first 500 images belong to.

  • The Image Search and Retrieval Evaluation tutorials embed every fourth training image, and the evaluation queries every fourth validation and test image, so that they also finish on a CPU.

  • GitHub alert blocks such as > [!NOTE] in the tutorial notebooks render as admonitions in the documentation.

  • The tutorials are grouped into the numbered chapters Introduction, Classical methods, Metric learning methods and Image similarity search. Every notebook carries a single top-level heading, which the sidebar lists as 1.1 <heading> under its chapter.

  • make docs renders the tutorial notebooks as they are on disk instead of executing them. make test-notebooks writes the executed notebooks back in place, so that a following make docs shows their outputs.

Bug Fixes

  • A try-except block is added in class OxfordFlowerDataset so that it raises a TypeError when it is indexed with anything that is not an integer, for example a slice such as dataset[:20], instead of failing inside Pillow with an AttributeError about a list object, which made it hard to understand the cause. NumPy integers keep working as indices.

  • Importing pyvisim no longer creates a res/logs folder next to the installed package, which failed on read-only installs.

  • from pyvisim import * works without the nn extra, and submodules such as pyvisim.classic are reachable after a plain import pyvisim.

  • The warning emitted when a clustering model resets an incompatible PCA is a FutureWarning and names raise_error_when_pca_incompatible=True as the way to raise instead.

  • SIFT and RootSIFT keep their constructor arguments when an embedder using them is saved and loaded, instead of coming back with the default arguments.

  • Pipeline([]) raises a ValueError when it is built, instead of failing later inside embed with a NumPy error.

  • FisherVectorEmbedder raises a ValueError for an image that yields no descriptor, like VLADEmbedder does, instead of returning a row of NaNs when that image shares a batch.

  • top_k_map divides each average precision by min(R, k), with R the gallery images sharing the query label, instead of by the relevant images found in the top k. mAP@k values were inflated before and come out lower now.

  • KMeans.fit rejects a feature matrix that is not 2-D or holds no sample, and a refit asks for the configured number of clusters again after SciPy dropped empty clusters on an earlier fit.

  • The source distribution now ships the Cython source of the PSNR kernel, so installing pyvisim from it no longer fails with 'pyvisim/pixelwise/_kernel/_ssd_kernel.pyx' doesn't match any files.

  • InMemoryImageEmbeddingStore.from_dict accepts the state returned by to_dict. Before, it only accepted a state read from a file and raised TypeError otherwise.