Repository navigation
Releases
v1.0.0
Release Notes
v1.0.0
Highlights
🚀 The first official release of pyvisim is out!
Upgrade Notes
- Renamed
set_pca_from_sklearnofVLADEmbedderandFisherVectorEmbeddertoload_pca_model_from_sklearn. The arguments stay the same.
Enhancement Notes
- Truncated an unnecessary markdown cell in notebook
vlad_and_fisher_with_resnet18_deep_features.ipynb.
v0.10.2
Release Notes
v0.10.2
Upgrade Notes
ExternalSearchIndexis removed, and with it the support for indexes of libraries other than FAISS. Pass the FAISS index itself assearch_indextoInMemoryImageEmbeddingStoreand to itsload_from_disk.- A store on a FAISS index is named after the index type, such as
faiss.IndexHNSWFlat, and takes itsspacefrom the index metric.index_paramsand FAISS metrics other thanMETRIC_L2andMETRIC_INNER_PRODUCTraiseValueError. - A store on a FAISS inner-product index now scores by
1 - inner_productlike the built-in"ip"space, so lower is more similar.1 - candidate.scoregives the inner product back.
New Features
-
KReciprocalRerankeraccepts a store on a FAISS index. -
Added
to_single_imagehelper inpyvisim.utils.image_utils, which converts one image into the canonicaluint8array described bydimsandvalue_range. An example is shown below.import numpy as np from pyvisim.utils.image_utils import to_single_image rng = np.random.default_rng(0) # A uint8 image in the NumPy layout (height, width, channels), values in [0, 255] image_hwc = rng.integers(0, 256, size=(64, 80, 3), dtype=np.uint8) # The same pixels as a float image in the PyTorch layout # (channels, height, width), values in [0, 1] image_chw = image_hwc.transpose(2, 0, 1) / 255.0 # dims and value_range describe each input, and both come back as a # uint8 array of shape (64, 80, 3) with values in [0, 255] canonical_hwc = to_single_image(image_hwc, dims="HWC") canonical_chw = to_single_image(image_chw, dims="CHW", value_range=(0.0, 1.0)) print(canonical_chw.shape, canonical_chw.dtype) # (64, 80, 3) uint8 print(np.array_equal(canonical_hwc, canonical_chw)) # True
-
Added
set_pca_from_sklearntoVLADEmbedderandFisherVectorEmbedder. It replaces the embedder's PCA with one created from asklearn.decomposition.PCAestimator, fitted or not.
Enhancement Notes
- The new tutorial
Custom Feature Extractor with RootSIFTunder the classical methods implementsRootSIFTon top of theSIFTofOpenCV, which is the recommended way to useRootSIFTsince it is much faster than the built-inRootSIFT. - The new tutorial
Introduction to VLAD and Fisher Vectorsunder the classical methods trains both embedders onRootSIFTfeatures, once withlearnand once from fittedscikit-learnestimators, which have more training options.
Bug Fixes
- A store saved on an
ExternalSearchIndexnamed"hnsw"was loaded onto a newly built HNSW graph without a warning. Index names now come from the index type, so they cannot collide with a built-in index. - A store saved on a FAISS index and loaded without it falls back to a brute-force scan in the metric of that index, and no longer in
"cosine". VLADEmbedderandFisherVectorEmbeddernow raiseRuntimeErrorfor a fitted PCA whose dimensions do not match the feature extractor or the clustering model, like their other dimension checks.learn(dim_reduction_factor=...)now emits aFutureWarningwhen it replaces a PCA the embedder already has.
v0.10.1
Release Notes
v0.10.1
Upgrade Notes
ExternalSearchIndex.from_faiss_indexno longer takesvectors, so passing them raisesTypeError. For indexes that need the exact vectors for reconstruction, pass them to the constructor instead:ExternalSearchIndex(index, vectors).ExternalSearchIndex.from_faiss_indexnow raisesValueErrorfor an IVF index that has no direct map. Callfaiss.extract_index_ivf(index).make_direct_map()first, or pass the vectors toExternalSearchIndex(index, vectors).- Loading an
InMemoryImageEmbeddingStorefile saved by an earlier version raisesValueError. Build the store from its images again and save it. InMemoryImageEmbeddingStore.save_to_diskignoresembeddingson a store on an HNSW graph and emits aFutureWarning, since the file holds the graph with the embeddings in it.
Performance
-
Building or updating a
BruteForceIndexno longer copies the gallery first, which halves the extra peak memory and saves 14 to 17 % of the time. For 500 000 vectors of dimension 512 the peak went from 1985 MB to 1008 MB, and the build from 726 ms to 605 ms.Specs: 16 threads, 30 GB RAM, Python 3.14.3, NumPy 2.5.3.
-
ExternalSearchIndex.from_faiss_indexno longer keeps a copy of the gallery and reads the vectors from the FAISS index instead. For 500 000 vectors of dimension 512 the adapter's peak memory went from 1952 MB to 0 MB and its steady memory from 977 MB to 0 MB.Specs: 16 threads, 30 GB RAM, Python 3.14.3, NumPy 2.5.3, faiss-cpu 1.15.0.
-
Building or updating an
HnswIndexno longer copies the gallery first, which saves one gallery of peak memory. For 500 000 vectors of dimension 512 the extra peak went from 2097 MB to 1120 MB.Specs: 16 threads, 30 GB RAM, Python 3.14.3, NumPy 2.5.3.
-
A store on an HNSW graph saves the graph and loads it back without building it again. Loading a store of 100 000 embeddings of dimension 512 went from 22.7 s to 0.2 s, and saving it from 2.4 s to 0.2 s.
Specs: 16 threads, 30 GB RAM, Python 3.14.3, NumPy 2.4.6, safetensors 0.8.0.
Bug Fixes
- A loaded HNSW store returns the same neighbors as the store that was saved. Before, the graph was built again on load, which with several threads could return different neighbors.
v0.10.0
Release Notes
v0.10.0
Upgrade Notes
-
The British spelling
neighbouris replaced byneighborthroughout the library. One public name changes with it:InMemoryImageEmbeddingStore.retrieve_top_k_similarnow takesexpansion_neighborswhere it tookexpansion_neighbours. -
The British spelling
serialiseis replaced byserializethroughout the library. Only documentation, docstrings and comments are affected. -
DeepConvFeaturemoved topyvisim.neural_networks.features, sofrom pyvisim.features import DeepConvFeaturenow raisesImportError. If your code imports the class, change the import:# before from pyvisim.features import DeepConvFeature # after from pyvisim.neural_networks.features import DeepConvFeature
-
pyvisim.structuralandpyvisim.pixelwisemoved under the newpyvisim.densepackage. Imports ofSSIM,MSSSIMandPSNRfrom the old paths fail withModuleNotFoundError, so change them topyvisim.dense.structuralandpyvisim.dense.pixelwise:from pyvisim.dense.pixelwise import PSNR from pyvisim.dense.structural import MSSSIM, SSIM
-
pyvisim.features.feature_extractor_from_dictwas removed. Feature extractors are now rebuilt throughFeatureExtractorBase.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
.embedderfiles are unaffected, since the serialized format did not change. -
The first parameter of
FeatureExtractorBase.from_dictwas renamed fromdatatostate. You are affected if you pass it by keyword, andfrom_dict(data=...)becomesfrom_dict(state=...). -
The mapping returned by a feature extractor's
to_dictnow also holds a"format_version"key. -
The
_serialization_confighook ofFeatureExtractorBasewas removed. A custom extractor that overrides it now overrides_stateinstead and returns{"config": ...}, otherwise its arguments are no longer saved. -
InMemoryImageEmbeddingStoreno longer embeds its gallery in the constructor, which affects every store built through it. Callbuild_store()right after the constructor, or passlazy_build=Falseto have the constructor build the store. -
Until
build_store()has run,search,retrieve_top_k_similar,embeddings,embeddings_of,index,dim,to_dictandsave_to_diskraiseRuntimeError. A store that adopts anExternalSearchIndexor is rebuilt byload_from_diskorfrom_dictcomes 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 unknownsearch_index, a rejectedindex_paramsentry, a non-string path and an empty gallery still raise in the constructor. -
pyvisim.neural_networksno longer re-exportsDeepConvFeature, sofrom pyvisim.neural_networks import DeepConvFeaturenow raisesImportError. Import the class frompyvisim.neural_networks.featuresinstead:# before from pyvisim.neural_networks import DeepConvFeature # after from pyvisim.neural_networks.features import DeepConvFeature
-
Importing
pyvisimno longer configures logging, and its loggers are named after their modules (e.g.pyvisim.datasets.datasets) instead ofData_Set,Feature_Extractor,PipelineandSimilarity_Metrics. Configure thepyvisimlogger to see the library's messages. -
from pyvisim import *no longer importsdatasets, which needs thennextra. Access it aspyvisim.datasetsor import it explicitly. -
VLADEmbedderandFisherVectorEmbedderdefault toraise_error_when_pca_incompatible=True, so a clustering model the fitted PCA cannot feed raises aRuntimeErrorinstead of silently dropping the PCA. PassFalseto keep the old behavior. -
pyvisim._base_classesis removed. ImportSimilarityMetric,FeatureExtractorBase,ImageEmbedderBaseandSerializableImageEmbedderfrompyvisim.baseinstead. -
VLADEmbedderandFisherVectorEmbedderno longer takeraise_error_when_pca_incompatible. A clustering model the fitted PCA cannot feed always raises aRuntimeError, so drop the argument. -
VLADEmbedderandFisherVectorEmbedderno longer takeflatten.embedalways returns one flat row per image, so drop the argument. -
Embedders saved by
VLADEmbedderandFisherVectorEmbedderare written under format version 4. Files of version 3 still load, files of version 4 do not load with an olderpyvisim. -
OxfordFlowerDatasetno longer accepts atransformargument. Passing one raisedNotImplementedErrorbefore, so only calls that spelled outtransform=Noneor passedpurposepositionally are affected: drop the argument and passpurposeby keyword. Every item stays the raw RGBuint8array that the embedders and metrics accept, and any preprocessing belongs to a wrappingDataset. -
save_to_diskwrites to the given path as is, and.embedderor.safetensorsis 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 fromSerializerMixinand every serializable class. Subclasses outside the library drop their__file_format__. -
InMemoryImageEmbeddingStore.searchis removed. Code that searched a store by vectors callsstore.index.search(query_vectors, k)instead. -
EmbeddingStoreprotocol class declaresretrieve_top_k_similar(query_images, k)in place ofsearch(query_vectors, k). Custom stores implement the new method. -
pyvisim.image_storeis nowpyvisim.retrieval, split into the subpackagesimage_store(InMemoryImageEmbeddingStore,BruteForceIndex,HnswIndex,ExternalSearchIndex),reranking(KReciprocalReranker) anddata(Candidate), sofrom pyvisim.image_store import ...now raisesModuleNotFoundError. If your code imports frompyvisim.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
vectorsparameter ofInMemoryImageEmbeddingStore.save_to_diskis renamed toembeddings. Code that passesvectors=...passesembeddings=...instead. -
Notebooks are committed without outputs. Run
make strip-notebooksbefore committing one, since the CI rejects notebooks that still carry outputs or execution metadata. -
Every
.embedderfile 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 withsave_to_disk. -
The
__class_key__class attribute is removed fromSerializerMixinand 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_diskand loaded back withload_from_disk, as.safetensorsfiles. InMemoryImageEmbeddingStore.build_storeembeds the gallery and builds the search index. It does nothing on a store that is already built.InMemoryImageEmbeddingStore.is_builtreports whether the store has been built.
Enhancement Notes
-
The new tutorial
Custom Feature Extractor with ORBunder the classical methods shows how to write a feature extractor by inheriting fromFeatureExtractorBase, train aVLADEmbedderwith it and compare two images. -
The
tutorialsdependency group installsopencv-python-headless, which the custom feature extractor tutorial uses forORB. -
FeatureExtractorBasenow owns both halves of its serialization contract:to_dictand the newfrom_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_confighook, asDeepConvFeaturedoes to rebuild its backbone by name. -
FeatureExtractorBase.from_dictforwards its keyword arguments to the_from_confighook, so a custom extractor can take objects its state cannot hold. -
A
DeepConvFeaturebuilt on a user-supplied model or a custom transform can be rebuilt by passing them back asbackbone=andtransform=. AFutureWarningis emitted if the transform differs from the saved one. -
VLADEmbedderandFisherVectorEmbeddertake afeature_extractor_paramsdict infrom_dictandload_from_disk, which is forwarded to the feature extractor. An embedder on aDeepConvFeaturewith a user-supplied model is loaded withfeature_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.
-
OxfordFlowerDatasetreads its.matfiles withscipy.io.loadmatagain. The internal MAT-file readerpyvisim.datasets._matloaderwas removed since SciPy is a runtime dependency. -
Simplified the
[dependency-groups]inpyproject.toml:- Merged the
releasegroup intodocs, sincerenoanddulwichare only used to build the rele...
- Merged the
v0.9.5
Release Notes
v0.9.5
Upgrade Notes
- Binary files for the docs are now added through a pull request against the
assetsbranch instead of a force-push. Contributors who used the old recipe follow the updated "Adding binary files to the docs" section ofCONTRIBUTING.md. - The file format of a serialisable class is now declared on the class itself under one set of names, and the module-level constants that held it are gone.
_FILE_SUFFIXbecame__file_format__,_METADATA_KEYbecame__metadata_key__,_CLASS_KEYbecame__class_key__,_STATE_KEYSbecame__state_keys__, and every per-module format version (_CLUSTERING_EMBEDDER_FILE_FORMAT_VERSION,_NEURAL_EMBEDDER_FORMAT_VERSION,_PIPELINE_FORMAT_VERSION,_CLIP_EMBEDDER_FORMAT_VERSION,_STORE_FORMAT_VERSION) became__format_version__on the class that writes it. Their compatibility table is__compatibility_mapping__. Subclasses outside the library that set any of the old names have to rename them: the old names are no longer read, andSerializerMixinnow raises aTypeErrorat class-definition time when a class that implementsto_dictandfrom_dictleaves one of the required attributes undeclared. pyvisim.serialization.EMBEDDER_METADATA_KEYandpyvisim._base_classes.EMBEDDER_FILE_SUFFIXwere removed. Read them from the embedder class instead, for exampleVLADEmbedder.__metadata_key__andVLADEmbedder.__file_format__.- Subclasses outside the library that implement
_embednow receive one batch as a list of canonicaluint8(H, W[, C])images, withoutdimsandvalue_range, sinceImageEmbedderBase.embednormalizes and batches the input. Drop the two parameters and the batching loop from your_embed. SerializerMixin.to_dictis no longer abstract. It adds the format version and the class name to the mapping returned by the new abstract_statemethod. Subclasses outside the library rename theirto_dictto_stateand drop theformat_versionand class-name entries from it, and__state_keys__no longer lists the class key. Loading a file whose state lacks the format version or the class name raises aValueError.pyvisim.serialization.embedder_to_dictandpyvisim.serialization.embedder_from_dictare removed. Code importing them callsembedder.to_dict()instead, andSerializableImageEmbedder.from_dict(state)to rebuild an embedder of any class from its state.
New Features
- The
KMeans,DiagCovarGaussianMixtureandPCAmodels of the classic embedders can be saved on their own withsave_to_diskand loaded back withload_from_disk, as.safetensorsfiles.
Enhancement Notes
- The
Benchmarksworkflow pushes its barplots todocs/benchmark-barplotsfor review instead of publishing them directly. The newSquash assetsworkflow keepsassetsat a single commit after each merge. - Every embedder rejects an empty input with the same
ValueError("Expected at least one image, got none.") instead of a NumPy concatenation error or an embedder-specific message. - Every
load_from_diskreports an unreadable file the same way, as "File ... is not a valid <suffix> file" followed by the reason. The image store used to raise the bare reason, while the embedders dropped it. - The clustering models of
VLADEmbedderandFisherVectorEmbedderrecord aformat_versionin their part of the.embedderfile. Files written before still load.
v0.9.4
Release Notes
v0.9.4
Highlights
Release notes are now managed with reno. Every contributor adds a note for the change they are making in the same pull request that carries the code, and the notes of a release are published as the body of its GitHub Release.
Upgrade Notes
- Contributors need the new
releasedependency group to add a release note. Runuv sync --group releaseto install it, then create a note withmake release-note NAME=my-change. A pull request touching library code that carries no note fails theCheck Release Notesjob. VLADEmbedder,FisherVectorEmbedder,Pipeline,ContrastiveSiameseNetworkandTripletNeuralNetworknow L2-normalize whatembedreturns. Vectors already stored in anInMemoryImageEmbeddingStorebuilt in the"l2"or"ip"space are on a different scale than newly embedded queries, so rebuild such a store or construct the embedder withnormalize=False. A"cosine"store is unaffected, since it normalizes its vectors anyway.- Subclasses outside the library that implement
embedhave to rename it to_embed:ImageEmbedderBase.embedis now a concrete method that appliesnormalizeto whatever_embedreturns. - The
.embedderfile format carries thenormalizesetting and is bumped to version 3. Files written by earlier versions lack the key and no longer load, so re-save any embedder saved with pyvisim 0.9.3 or older.
New Features
- Every image embedder now takes a
normalizeparameter, whichImageEmbedderBaseowns and all embedders inherit. It defaults toTrue, soembedreturns rows of unit L2 length and two embeddings can be compared with a plain dot product. Passnormalize=Falseto get the raw encoding instead. - macOS is now a supported operating system. The package metadata lists it next to Linux and Windows, and the unit and slow test suites run on
macos-latestin CI.
Enhancement Notes
- The documentation now shows the pyvisim symbol above the title in the sidebar, with a separate variant for the dark theme, and a clickable GitHub icon in the page footer.
CHANGELOG.mdis frozen at0.9.3and is no longer updated. It stays in the repository as the record of every release up to that point. Notes for later releases live inreleasenotes/notesand are rendered onto the GitHub Releases page.
[0.9.3] - 2026-09-09
Added
- Every similarity metric now takes a
batch_sizeargument, exposes it as the
batch_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,TripletNeuralNetworkand
BCESiameseNetworktake 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_neighboursto 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) through
rerank(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 |
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.
Changed
⚠️ Candidateis a frozen dataclass instead of a named tuple, so it no longer
unpacks or indexes: readcandidate.pathandcandidate.score. It lives in
pyvisim.image_store.candidateand is still exported frompyvisim.image_store.- Added backbones resnet34, resnet50, resnet101, resnet152 under
pyvisim.neural_networks.backbones. ⚠️ HnswIndextakesgraph_degree,build_candidatesand
search_candidatesinstead ofm,ef_constructionandef_search, and
exposes them under those names. The old names are gone, inindex_params
too, 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 a
FileNotFoundError. 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
⚠️ 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'sindex_typeparameter is nowsearch_index, which takes
"hnsw",Nonefor a brute-force scan, or anExternalSearchIndex. Its
quantizerparameter is nowspace, taking"cosine"(the default),"l2"
or"ip".⚠️ The scores ofCandidateandsearchare 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.⚠️ Thesearchextra, 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: the
numpy.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) in
tests/neural_networks/test_oxford_flowers_quick.pyand
test_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 serialisable 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_sklearnand
PCA.from_sklearn, plusload_clustering_model_from_sklearnonVLADEncoder
andFisherVectorEncoderto 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.embedder
file 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 (under
pyvisim.structural) metrics as well as their benchmark scripts against
existing implementations underdocs/pixelwise/benchmarksand
docs/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 from
pyvisim.encoderstopyvisim.classic. -
⚠️ The bundled pretrained VLAD and Fisher Vector encoders are removed to make the binary smaller, together
withfrom_pretrained,PretrainedVLAD/PretrainedFisher, the deprecated
weights=argument andKMeansWeights/GMMWeights. Train a vocabulary with
learn()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 from
pyvisim.clusteringtopyvisim.classic._clustering. -
⚠️ Encoder clustering parameters changed: passrnginstead ofrandom_state
insidekmeans_params/gmm_params/pca_params(see
vlad.md and
fisher_vector.md for every accepted key).