Skip to content

[0.9.3] - 2026-09-09

Pre-release
Pre-release

Choose a tag to compare

@MechaCritter MechaCritter released this 09 Sep 06:06
· 80 commits to main since this release

Added

  • Every similarity metric now takes a batch_size argument, exposes it as the
    batch_size attribute and takes a new value through set_batch_size. It
    defaults to 16 everywhere; -1 turns the splitting off and processes the
    whole input as one batch, whatever its size.
  • Serializable embedders store their batch size, so a reloaded embedder runs
    with the batch size it was saved with.
  • VLADEmbedder and FisherVectorEmbedder take a batch_size. The images of
    a batch are extracted, reduced, assigned and normalized as one matrix instead
    of one image at a time, and an iterable input stays a stream.
  • FeatureExtractorBase.extract_batch extracts a batch of images and returns
    one feature array per image. Extractors that can do a whole batch in one go
    override it; the default extracts one image at a time.
  • DeepConvFeature pushes a whole batch through its backbone in one forward
    pass. A custom transform that keeps the input size leaves the images
    unstackable, and they are then extracted one at a time as before.
  • ClipEmbedder, ContrastiveSiameseNetwork, TripletNeuralNetwork and
    BCESiameseNetwork take a batch_size that splits their forward passes and
    bounds the activation memory of each.
  • Pipeline takes a batch_size bounding how many images it hands its
    embedders at a time. Each embedder still applies its own batch size within.
  • InMemoryImageEmbeddingStore embeds its gallery one batch at a time, sized by
    the batch_size of the embedder it is given, instead of one image per call.
  • InMemoryImageEmbeddingStore takes a num_workers (default 4) reading and
    decoding the gallery files on worker threads while the embedder works on the
    previous batch.
  • InMemoryImageEmbeddingStore takes a num_prefetch_batches (default 4)
    controlling how many batches of images the reading threads may run ahead of
    the embedder.
  • InMemoryImageEmbeddingStore.retrieve_top_k_similar takes query_expansion,
    expansion_alpha and expansion_neighbours to refine every query with the
    alpha query expansion of Radenović et al. (2019) before the final search. It
    is off by default, since it costs one extra search per query.
  • KReciprocalReranker (in pyvisim.image_store): re-ranks the candidates of a
    query with the k-reciprocal encoding of Zhong et al. (2017) through
    rerank(candidates, top_k), reading their embeddings back from the store.
  • InMemoryImageEmbeddingStore.embeddings_of(paths) and vectors_at(ids) on
    every index read a few gallery vectors back without decoding the whole
    gallery.
  • Candidate.array reads the matched image as an RGB uint8 array on first
    access and keeps it, and Candidate.clear_buffer drops it again.
  • scripts/benchmark_reranking.py measures plain retrieval, alpha query
    expansion and k-reciprocal re-ranking on the Oxford Flower dataset. The
    results are in the README.

Performance

Building a store over all 6149 train images of the Oxford Flower dataset, before
and after the batched gallery build, measured with
a benchmark script on
the CPU with PYVISIM_NUM_THREADS=4. Only the store constructor is timed.

from pyvisim.datasets import OxfordFlowerDataset
from pyvisim.image_store import InMemoryImageEmbeddingStore
from pyvisim.neural_networks import ClipEmbedder

train_dataset = OxfordFlowerDataset()
train_image_paths = train_dataset.image_paths

embedder = ClipEmbedder()

image_store = InMemoryImageEmbeddingStore(
    image_paths=train_image_paths,
    embedder=embedder,
    search_index="hnsw",
    index_params={"m": 16, "ef_construction": 200},
)
Before After
338 s 249 s
from pyvisim.classic import FisherVectorEmbedder
from pyvisim.datasets import OxfordFlowerDataset
from pyvisim.image_store import InMemoryImageEmbeddingStore

train_dataset = OxfordFlowerDataset()
train_image_paths = train_dataset.image_paths

# Fitted beforehand with learn(images, dim_reduction_factor=2)
embedder = FisherVectorEmbedder(n_components=32)

image_store = InMemoryImageEmbeddingStore(
    image_paths=train_image_paths,
    embedder=embedder,
    search_index="hnsw",
    index_params={"m": 16, "ef_construction": 200},
)
Before After
2936 s 2706 s

Removed

  • ⚠️ DeepConvFeature no longer appends normalized (x, y) coordinates to its
    descriptors: the spatial_embedding argument is gone and output_dim is now
    the channel count of the selected conv layer.

Changed

  • ⚠️ Candidate is a frozen dataclass instead of a named tuple, so it no longer
    unpacks or indexes: read candidate.path and candidate.score. It lives in
    pyvisim.image_store.candidate and is still exported from pyvisim.image_store.
  • Added backbones resnet34, resnet50, resnet101, resnet152 under
    pyvisim.neural_networks.backbones.
  • ⚠️ HnswIndex takes graph_degree, build_candidates and
    search_candidates instead of m, ef_construction and ef_search, and
    exposes them under those names. The old names are gone, in index_params
    too, so a store saved by an earlier release cannot be loaded by this one.
  • Every save_to_disk rejects a destination whose directory does not exist
    with an OSError, and every load_from_disk reports a missing file as a
    FileNotFoundError. The embedders reported both as a safetensors error.
  • ⚠️ SSIM and MSSSIM score 16 image pairs per batch instead of two, and
    the neural embedders now embed 16 images per forward pass instead of the
    whole input at once. Pass batch_size=-1 for the previous behaviour.
  • ⚠️ PSNR takes batch_size=-1 instead of batch_size=None to score the
    whole input as one batch, and defaults to 16 rather than to the whole input.
  • ⚠️ PSNR raises on a batch holding no image instead of returning an empty
    score matrix, which is what every other metric already did.
  • ⚠️ The batch size is a required key of the .embedder format, so a file
    written by an earlier release cannot be loaded by this one.

Removed

  • ⚠️ pyvisim.serialization.save_embedder_state and load_embedder_state.
    They only called save_state/load_state with the embedder metadata key,
    which is now exported as EMBEDDER_METADATA_KEY.