Changelog¶
Important
This changelog covers the releases up to and including 0.9.3. Every later release has its own page under Release notes.
[0.9.3] - 2026-09-09¶
Added¶
Every similarity metric now takes a
batch_sizeargument, exposes it as thebatch_sizeattribute and takes a new value throughset_batch_size. It defaults to16everywhere;-1turns 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.
VLADEmbedderandFisherVectorEmbeddertake abatch_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_batchextracts 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.DeepConvFeaturepushes a whole batch through its backbone in one forward pass. A customtransformthat keeps the input size leaves the images unstackable, and they are then extracted one at a time as before.ClipEmbedder,ContrastiveSiameseNetwork,TripletNeuralNetworkandBCESiameseNetworktake abatch_sizethat splits their forward passes and bounds the activation memory of each.Pipelinetakes abatch_sizebounding how many images it hands its embedders at a time. Each embedder still applies its own batch size within.InMemoryImageEmbeddingStoreembeds its gallery one batch at a time, sized by thebatch_sizeof the embedder it is given, instead of one image per call.InMemoryImageEmbeddingStoretakes anum_workers(default4) reading and decoding the gallery files on worker threads while the embedder works on the previous batch.InMemoryImageEmbeddingStoretakes anum_prefetch_batches(default4) controlling how many batches of images the reading threads may run ahead of the embedder.InMemoryImageEmbeddingStore.retrieve_top_k_similartakesquery_expansion,expansion_alphaandexpansion_neighborsto 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(inpyvisim.image_store): re-ranks the candidates of a query with the k-reciprocal encoding of Zhong et al. (2017) throughrerank(candidates, top_k), reading their embeddings back from the store.InMemoryImageEmbeddingStore.embeddings_of(paths)andvectors_at(ids)on every index read a few gallery vectors back without decoding the whole gallery.Candidate.arrayreads the matched image as an RGBuint8array on first access and keeps it, andCandidate.clear_bufferdrops it again.scripts/benchmark_reranking.pymeasures 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 |
Changed¶
⚠️
Candidateis a frozen dataclass instead of a named tuple, so it no longer unpacks or indexes: readcandidate.pathandcandidate.score. It lives inpyvisim.image_store.candidateand is still exported frompyvisim.image_store.Added backbones resnet34, resnet50, resnet101, resnet152 under
pyvisim.neural_networks.backbones.⚠️
HnswIndextakesgraph_degree,build_candidatesandsearch_candidatesinstead ofm,ef_constructionandef_search, and exposes them under those names. The old names are gone, inindex_paramstoo, so a store saved by an earlier release cannot be loaded by this one.Every
save_to_diskrejects a destination whose directory does not exist with anOSError, and everyload_from_diskreports a missing file as aFileNotFoundError. The embedders reported both as a safetensors error.⚠️
SSIMandMSSSIMscore 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. Passbatch_size=-1for the previous behaviour.⚠️
PSNRtakesbatch_size=-1instead ofbatch_size=Noneto score the whole input as one batch, and defaults to16rather than to the whole input.⚠️
PSNRraises 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
.embedderformat, so a file written by an earlier release cannot be loaded by this one.
Removed¶
⚠️
DeepConvFeatureno longer appends normalized(x, y)coordinates to its descriptors: thespatial_embeddingargument is gone andoutput_dimis now the channel count of the selected conv layer.⚠️
pyvisim.serialization.save_embedder_stateandload_embedder_state. They only calledsave_state/load_statewith the embedder metadata key, which is now exported asEMBEDDER_METADATA_KEY.
[0.9.2] - 2026-08-26¶
Added¶
HnswIndexandBruteForceIndex(inpyvisim.image_store): an approximate HNSW graph and an exhaustive scan, both compiled into the package and built in cosine space by default.ExternalSearchIndex(inpyvisim.image_store): searches through an index built elsewhere.ExternalSearchIndex.from_faiss_index(index, vectors=None)adapts any FAISS index without FAISS being a dependency of this library.InMemoryImageEmbeddingStore.retrieve_top_k_similarranks the gallery against query images and returnsCandidatematches, both now owned by the store.InMemoryImageEmbeddingStore.save_to_disktakes the galleryvectorsto write, for an index that hands back an approximation of what it was given.InMemoryImageEmbeddingStore.load_from_diskforwards keyword arguments:search_index=...restores a store onto a rebuilt external index, and anything else reaches the embedder.
Changed¶
⚠️ The store’s
index_typeparameter is nowsearch_index, which takes"hnsw",Nonefor a brute-force scan, or anExternalSearchIndex. Itsquantizerparameter is nowspace, taking"cosine"(the default),"l2"or"ip".⚠️ The scores of
Candidateandsearchare distances for the built-in indexes, so lower is more similar. AnExternalSearchIndexreports whatever its own metric produces.The index owns the gallery vectors and the store keeps no second copy, so
store.embeddingsis read-only. Both built-in indexes decode it out of their own storage, which makes every access a fresh copy.Store files are written in a new layout; a store saved by an earlier version cannot be loaded by this one.
Removed¶
⚠️
pyvisim.retrieval(ImageRetriever,ImageIndexand the FAISS-backed IVF indexes) andpyvisim.functional. The store now covers both.⚠️ The
searchextra, along with thefaiss-cpudependency behind it.
[0.9.1] - 2026-08-24¶
Added¶
Added
Triplet Neural Networkunderpyvisim.neural_networkswithTripletLoss.
[0.9.0] - 2026-08-23¶
Fixed¶
PSNR.similarity_scoreaccepts a channel-less grayscale image again: a 2-D array passed with the defaultdims="HWC"raised instead of being read as single-channel, unlikeSSIMand the rest of the library.make test-typesno longer prints aDeprecationWarning: thenumpy.typing.mypy_pluginentry is removed from the mypy configuration.The development interpreter is pinned to Python 3.10 (
.python-version), the project’s minimum supported version and the one every CI job already uses.The
ruff checkCI step no longer fails on import sorting (I001) intests/neural_networks/test_oxford_flowers_quick.pyandtest_oxford_flowers_slow.py.
Added¶
load_from_disknow forwards keyword arguments tofrom_dict, so an embedder can be handed the objects its file cannot hold. The Siamese networks use it for their transform:ContrastiveSiameseNetwork.load_from_disk(path, transform=transform)restores the exact embeddings of a network built with a custom one.The embedders of
pyvisim.neural_networks(ClipEmbedder,ContrastiveSiameseNetwork,BCESiameseNetwork) are now serializable to a safetensors.embedderfile viasave_to_disk/load_from_disk, weights included; a reloaded embedder produces identical embeddings without downloading any pretrained weights.NeuralImageEmbedder(inpyvisim.neural_networks): the shared base for the neural embedders, both aSerializableImageEmbedderand atorch.nn.Module.SiameseNetworkBasenow derives from it, so the Siamese networks and the classic embedders expose the sameembed/similarity_scoresurface.Clustering models can now be built from a fitted scikit-learn estimator:
KMeans.from_sklearn,DiagCovarGaussianMixture.from_sklearnandPCA.from_sklearn, plusload_clustering_model_from_sklearnonVLADEncoderandFisherVectorEncoderto drop one straight into an encoder. Handy for reusing a vocabulary you already trained with scikit-learn.
Changed¶
The Siamese networks now store the
reprof their transform in the.embedderfile and warn onload_from_diskwhen the rebuilt network’s transform differs from it, instead of warning on every save of a custom transform.tqdmandrequestsare no longer runtime dependencies of the core package; they moved into thennextra. Onlypyvisim.datasetsuses them, and that module already requirestorchfrom the same extra.CI restores the Oxford Flowers dataset and the pretrained backbone weights from the GitHub Actions cache instead of re-downloading them on every run; the new
Warm asset cacheworkflow keeps that cache populated onmain.The
similarity_funcregistry inpyvisim._utilsnow maps the metric names straight ontopyvisim.distance.ℹ️ Dropped scikit-learn as a runtime dependency.
Added PSNR (under
pyvisim.pixelwise) and SSIM/MSSSIM (underpyvisim.structural) metrics as well as their benchmark scripts against existing implementations underdocs/pixelwise/benchmarksanddocs/structural/benchmarks.BCESiameseNetwork(inpyvisim.neural_networks): the pair-classifying Siamese variant of Koch, Zemel & Salakhutdinov (2015).The Siamese networks are split along a shared abstract base,
SiameseNetworkBase.Removed the Siamese Network’s train scripts. This is now demonstrated in a notebook in the “examples” repository.
Breaking¶
⚠️
VLADEmbedder,FisherVectorEmbedderandPipelinemoved frompyvisim.encoderstopyvisim.classic.⚠️ The bundled pretrained VLAD and Fisher Vector encoders are removed to make the binary smaller, together with
from_pretrained,PretrainedVLAD/PretrainedFisher, the deprecatedweights=argument andKMeansWeights/GMMWeights. Train a vocabulary withlearn()and persist it withsave_to_disk/load_from_diskinstead.⚠️ “Encoder” is now “embedder” throughout:
ImageEncoderBase->ImageEmbedderBase,VLADEncoder->VLADEmbedder,FisherVectorEncoder->FisherVectorEmbedder, theEncoderprotocol ->Embedder,encode()->embed()andstore.encoder->store.embedder.⚠️ Saved models use the
.embeddersuffix and anembedder_classstate key, so existing.encoderfiles no longer load. Re-save them withsave_to_disk.⚠️
SiameseNeuralNetworkis renamed toContrastiveSiameseNetwork(pyvisim.neural_networks.siamese.siamese_neural_networkis gone; the base class now lives inpyvisim.neural_networks.siamese._base_siamese):from pyvisim.neural_networks import ContrastiveSiameseNetwork model = ContrastiveSiameseNetwork(backbone="resnet18", embedding_dim=128) score = model.similarity_score(image1, image2) # cosine similarity in [-1, 1]
⚠️ The clustering models (
KMeans,DiagCovarGaussianMixture,PCA,ClusteringModelBase) are now internal to the encoders package and moved frompyvisim.clusteringtopyvisim.classic._clustering.⚠️ Encoder clustering parameters changed: pass
rnginstead ofrandom_stateinsidekmeans_params/gmm_params/pca_params(see vlad.md and fisher_vector.md for every accepted key).
[0.8.2]¶
Added¶
SIFTnow exposes the full set of detector parameters (upsampling,n_octaves,n_scales,sigma_min,c_dog,c_edge,n_hist,n_ori, …) as constructor arguments, along with the underlying detector API (detect,extract,detect_and_extractand thekeypoints/descriptors/positions/… attributes).output_dimis nown_hist**2 * n_ori(still128with the defaults).
Changed¶
ℹ️ Removed
OpenCVandtorchaudiofrom the dependency list.SIFTandRootSIFTno longer call OpenCV’scv2.SIFT; they now run the pure NumPy/Cython SIFT implementation vendored from scikit-image (pyvisim/features/_vendored/sift/, compiled viamake build-ext).RootSIFTsubclassesSIFTand only adds the Hellinger-kernel normalization. With OpenCV gone,opencv-python-headlessis removed from the dependencies;scipyreturns as a direct dependency (the vendored implementation uses.
Breaking¶
Some small numerical changes are expected compared to before regarding the
SIFTandRootSIFTcomoutation are expected due to the migration. For the user, no difference in API is observed since only the backend behind these 2 classes change.
[0.8.1]¶
Added¶
Structural similarity metrics (in
pyvisim.structural):SSIM(Wang et al., 2004) and the multi-scaleMSSSIM(Wang et al., 2003), computed by a compiled multithreaded Cython kernel (thread count vianum_workersorPYVISIM_NUM_THREADS) and matching scikit-image / torchmetrics respectively. Both score two image batches into an(N, M)similarity matrix and take abatch_sizeparameter to bound peak memory (-1scores the whole input as one batch):from pyvisim.structural import MSSSIM, SSIM scores = SSIM().similarity_score(image1, image2) # (N, M) matrix in [-1, 1] scores = MSSSIM(batch_size=16).similarity_score(gallery, queries)
New
pyvisim.distancemodule with pyvisim’s own pure-NumPy pairwise metrics:cosine_similarity,euclidean_distancesandmanhattan_distances. They keep scikit-learn’s numerical tricks (float64 upcast and the dot-product expansion for Euclidean, zero-safe norms divided out of the result in place for cosine, chunked broadcasting with a configurableworking_memory_bytesbudget for Manhattan) and are verified against the scikit-learn reference in the test suite, includingslow-marked stress tests on a 100000 x 10000 gallery (size overridable viaPYVISIM_TEST_LARGE_ROWS/PYVISIM_TEST_LARGE_FEATURES).
Changed¶
The distance metrics behind
similarity_funcno longer wrapsklearn.metrics.pairwise; they now resolve to the implementations inpyvisim.distance. Same names, same results.Rolled out lib’s own
.matloader to replacescipy.io.loadmat, so that thescipydependency could be dropped completely. Added test to verify that the new loader loads the same data asscipy.io.loadmaton the Oxford-102 Flowers dataset.read_image_rgbin_utilsnow usesPillowto open instead ofcv2.imreadas plan to be as little dependent on OpenCV as possible.CLIP moved from
pyvisim.classicintopyvisim.neural_networksand dropped the open_clip dependency entirely. The newClipEmbedderruns pyvisim’s own implementation of the CLIP image towers (Vision Transformer and modified ResNet) and loads pretrained safetensors weights from the Hugging Face Hub — verified numerically equivalent to open_clip’s image embeddings. Variant names and pretrained tags follow open_clip: 67 (variant, tag) combinations across 30 variant names are supported (every open_clip variant with a standard CLIP image tower and open_clip-format safetensors on the Hub), fromRN50andViT-B-32up toViT-g-14/ViT-bigG-14, with weights by OpenAI, LAION, DataComp and Meta (MetaCLIP, incl. MetaCLIP-2 worldwide). Enumerate them withpyvisim.neural_networks.clip.available_variants()/available_pretrained(variant). Only the image tower is loaded, always infloat32; QuickGELU-trained checkpoints (like all"openai"ones) automatically get the QuickGELU activation. Downloads are integrity-checked by huggingface_hub and land in the standard Hugging Face cache (~/.cache/huggingface/hub), so weights already pulled via open_clip’s Hub downloads are reused.from pyvisim.neural_networks import ClipEmbedder embedder = ClipEmbedder("ViT-B-32", pretrained="openai") # "ViT-B/32" works too embeddings = embedder.embed(images) # (num_images, 512); L2-normalized by default score = embedder.similarity_score(image1, image2)
Breaking¶
⚠️
CLIPEncoderis gone. UseClipEmbedderinstead: the method isembed()(likeSiameseNeuralNetwork), notencode(), and it takes open_clip-stylevariantandpretrainedarguments (ClipEmbedder("ViT-B-32", pretrained="openai")). CLIP.encoderfiles can no longer be loaded; just construct the embedder with the variant you want.⚠️ The
nnextra no longer installsopen_clip_torch; it now installshuggingface_hub(for the checkpoint downloads) instead. If your own code importsopen_clip, install it yourself.
[0.8.0] - 2026-07-04¶
Added¶
Siamese network for image similarity (in
pyvisim.neural_networks), replacing the earlier sketch.SiameseNeuralNetworkwraps a ResNet-18 backbone plus a projection head and hands back L2-normalized embeddings, so you can score two images withsimilarity_scoreor pull the raw vectors withembed:from pyvisim.neural_networks import SiameseNeuralNetwork model = SiameseNeuralNetwork(backbone="resnet18", embedding_dim=128) score = model.similarity_score(image1, image2) # cosine similarity in [-1, 1]
Fine-tune it on labelled pairs with
ContrastiveLoss(frompyvisim.neural_networks.losses), or just run the bundled Oxford Flowers training script:python -m pyvisim.neural_networks.scripts.train_siamese_neural_network. Needs thennextra (pip install "pyvisim[nn]").
[0.7.0] - 2026-07-03¶
Added¶
CLIPEncoder(inpyvisim.classic): a pretrained-CLIP image encoder built on open_clip. It maps an image straight to a CLIP embedding, so there’s no feature extractor, clustering model, orlearnstep. Embeddings are L2-normalized by default, and it plugs into the usualsimilarity_score/save_to_disk/load_from_diskmachinery.from pyvisim.classic import CLIPEncoder clip = CLIPEncoder(model_name="ViT-B-32", pretrained="laion2b_s34b_b79k") embeddings = clip.encode(images)
Saving stores only the model identifiers (
model_name,pretrainedtag, etc.), not the weights, so.encoderfiles stay tiny and open_clip re-fetches the weights on load.nnoptional extra (pip install "pyvisim[nn]") now pulls in the whole deep-learning stack:torch,torchvision,torchaudioandopen_clip_torch. It coversDeepConvFeature(VGG16 deep features),CLIPEncoder, and thedatasetsandneural_networksmodules. Everything is imported lazily, so importingpyvisimnever requires it; you only hit the error (with an install hint) the first time you actually build one of these without it installed.searchoptional extra (pip install "pyvisim[search]") that pulls infaiss-cpufor the retrieval / image-store stack:InMemoryImageEmbeddingStore,ImageRetrieverand theImageIndex*classes. faiss is imported lazily too, so you only need it when you build a store or an index.
Breaking¶
⚠️
pip install pyvisimno longer installs torch or faiss. The base install now covers the SIFT/RootSIFT encoders only. Install[nn]for deep features and CLIP,[search]for the image store and retrieval, orpip install "pyvisim[nn,search]"for everything. Heads up: the VGG16 pretrained encoders (OXFORD102_K256_VGG16*) build aDeepConvFeature, so they now need thennextra.
[0.6.0] - 2026-06-20¶
Added¶
InMemoryImageEmbeddingStore(inpyvisim.image_store): the new gallery object. Give it image paths, an encoder, and an index type, and it encodes everything, builds a FAISS index, and searches itself:from pyvisim.image_store import InMemoryImageEmbeddingStore store = InMemoryImageEmbeddingStore( gallery_paths, encoder, "ivf-flat", quantizer="inner_product", index_params={"nlist": 100, "nprobe": 8}, ) results = store.retrieve_top_k_similar(query_images, k=5)
It saves to a single
.safetensorsfile (embeddings, paths, index config and the fully serialized encoder) andload_from_diskrebuilds it without re-encoding.index_typestrings select the index structure:"ivf-flat"and"ivf-pq"work today;"hnsw"and"int8"are sketched for a future release and raiseNotImplementedErrorfor now.Encoders and
Pipelinegainedto_dict/from_dict, and there’s a newEmbeddingStoreprotocol inpyvisim.typing.
Changed¶
retrieve_top_k_similar(query_images, store, k=5)now takes a store and searches through its index.top_k_mapandtop_k_accuracytake a store too, instead of a separate(encoding_map, encoder)pair.ImageRetrievernow wraps a store:ImageRetriever(store).The image indexes take the gallery as
(paths, vectors)rather than a mapping, and the trained FAISS index is now the single owner of the vectors. Read them back withindex.reconstruct()(orstore.embeddings) instead of keeping a second copy.
Breaking¶
⚠️
ImageEncodingMapis gone. Build anInMemoryImageEmbeddingStorefrom your image paths instead of a{path: vector}mapping.⚠️
Encoder.generate_encoding_map(...)andPipeline.generate_encoding_map(...)are removed. Pass the paths straight toInMemoryImageEmbeddingStore.⚠️
retrieve_top_k_similardropped itsdataset/encoder/indexarguments (and the brute-force path); pass a store. The same applies totop_k_map/top_k_accuracy.
[v0.5.1] - 2026-06-19¶
Fixed¶
The method
_from_configofDeepConvFeaturewas using the deprecatedmodelargument instead ofbackbone. This version only fixed that.
[v0.5.0] - 2026-06-19¶
Added¶
New
pyvisim.retrievalpackage for fast similarity search. Wrap anImageEncodingMapin an index (ImageIndexIVFFlatorImageIndexIVFPQ, bothl2orinner_product), then hand it to anImageRetriever:from pyvisim.retrieval import ImageIndexIVFFlat, ImageRetriever index = ImageIndexIVFFlat(encoding_map, quantizer="inner_product", nlist=100) retriever = ImageRetriever(index) results = retriever.retrieve_top_k_similar(query_images, k=5)
New
pyvisim.functionalmodule holdingretrieve_top_k_similarand theCandidate(path, score)result type.
Changed¶
retrieve_top_k_similarnow ranks a whole batch of query images in one shot and returns one rankedlist[Candidate]per query (in input order), so a single call can search many images at once. Pass anindex=to run the search through FAISS instead of brute-force cosine.
Breaking¶
⚠️
retrieve_top_k_similarmoved out ofpyvisim.evalintopyvisim.functional. Update your imports:from pyvisim.functional import retrieve_top_k_similar.⚠️ Its return type changed from
list[tuple[str, float]]tolist[list[Candidate]](one list per query image). Readcandidate.pathandcandidate.scoreinstead of unpacking a tuple.⚠️ The getters for the
pcaandclustering_modelattributes of all encoders are removed (the attributes are now read-only). This is in order to discourage users from mutating the clustering internals, which could break the algorithm completely. Also, once trained, there’s not really a reason to have to mutate those models at all because any different model would be basically wrong for the trained encoder.⚠️ The
ImageEncodingMapdoes not take theEncoderas an argument anymore.
[v0.4.1] - 2026-06-18¶
Added¶
DeepConvFeaturenow takes abackboneargument. Pass"vgg16"to grab a torchvision VGG16 with ImageNet weights, or hand it your owntorch.nn.Module. Leave it out and you still get the default VGG16.
Deprecated¶
The
modelargument ofDeepConvFeatureis deprecated; usebackboneinstead. If you still passmodel, it’s used as the backbone and you’ll get aDeprecationWarning. It’ll be removed in a future release.
[v0.4.0] - 2026-06-18¶
Added¶
from_pretrained()onVLADEncoderandFisherVectorEncoder, plus thePretrainedVLADandPretrainedFisherenums. Pick a bundled encoder and you’re ready to go:VLADEncoder.from_pretrained(PretrainedVLAD.OXFORD102_K256_ROOTSIFT).
Changed¶
Encoders now serialize to a single safetensors
.encoderfile that captures everything: the clustering model, PCA, normalization settings, the feature extractor and the similarity metric.load_from_disk()takes just the path and rebuilds the whole encoder, so there’s nothing else to pass back in.For a
DeepConvFeatureextractor, the default torchvision model is rebuilt on load (only a flag is stored), while a model you supply yourself has its fullstate_dictembedded so your trained weights come back exactly.similarity_funcis now chosen by name:"cosine"(default),"euclidean","l1"or"manhattan".The pretrained Oxford-102 weights ship as
.encoderfiles instead of.pkl, shrinking them from ~144 MB to ~12 MB (the K-Means traininglabels_array is no longer stored).
Removed¶
Dropped
joblibentirely in favor of safetensors.⚠️ You can no longer pass your own similarity function; use one of the four built-in metric names above.
Deprecated¶
Loading pretrained weights via
KMeansWeights/GMMWeights(theweights=argument) is deprecated and will be removed in 1.0.0. Usefrom_pretrained()orload_from_disk()with.encoderfiles instead.
[v0.3.1] - 2026-06-18¶
Changed¶
ImageEncodingMapnow encodes every image up front instead of lazily on first access, which drops the in-memory buffer machinery and simplifies the class.ImageEncodingMap.save_to_disk()/load_from_disk()now use the safetensors format instead of HDF5. Files default to the.safetensorsextension.skip_errorsmoved fromsave_to_disk()to theImageEncodingMapconstructor, since encoding now happens at construction time.
Removed¶
Dropped
h5pyas a dependency; addedsafetensors.Removed
ImageEncodingMap.clear_buffer()(there’s no buffer to clear anymore).
Breaking¶
⚠️ Unreadable or missing images now raise (
FileNotFoundError/ValueError) when the map is built, not on first access. Useskip_errors=Trueto drop them with a warning instead.⚠️ Encoding maps saved with
0.3.0(HDF5) can’t be loaded by0.3.1; re-save them as safetensors.
[v0.3.0] - 2026-06-17¶
Added¶
New encoding map feature for the encoders (#36).
PyPI publishing step in the CI workflow, so releases ship automatically (#37).
Changed¶
Batches now fetch dynamically from PyPI instead of being hardcoded (#38).
Moved the notebooks into a tidier layout (#35).
Fixed¶
Dropped the deprecated
project.licenseTOML table inpyproject.toml(#39).
[v0.2.0] - 2026-06-16¶
Added¶
Clustering models with a fresh public API (#19), plus docs to match (#21).
Public types
ImageInputandMatLike, and you can now pass torch images straight in (#23).Unit tests across the board (#24), including behavioral tests that check VLAD and Fisher Vector encoders return the same vector before and after serialization (#32).
Early sketch of a Siamese neural net (#26).
Changed¶
Migrated tooling to
uv(#5).Added ruff, pre-commit hooks, and a CI pipeline that runs on every PR (#9).
Integrated mypy and cleaned up the type errors (#8, #13).
Now compatible with Python 3.10 through 3.12 (#14, #15).
Refreshed the outdated getting-started notebook (#30).
Fixed¶
VLADEncodernow raises if no descriptor is extracted, instead of failing silently (#11).Use
flatten()instead ofsqueeze()for setid arrays, so single-element arrays behave (#29).
[v0.1.3-alpha] - 2025-01-24¶
Initial alpha release.