From b4cfcbbe31fcab97c5055216b949b619cd794745 Mon Sep 17 00:00:00 2001 From: Prajval Arora Date: Tue, 13 Oct 2026 16:56:01 -0700 Subject: [PATCH 1/3] chore: annotate bag of words query inputs Add annotations for constructor image iterables, finite queries, and word-id vectors. No runtime algorithm changes. --- src/machinevisiontoolbox/BagOfWords.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/machinevisiontoolbox/BagOfWords.py b/src/machinevisiontoolbox/BagOfWords.py index 33231f3e..207a75f7 100644 --- a/src/machinevisiontoolbox/BagOfWords.py +++ b/src/machinevisiontoolbox/BagOfWords.py @@ -2,21 +2,32 @@ Visual Bag of Words model for image classification and retrieval. """ +from __future__ import annotations + from collections import Counter -from typing import Any +from collections.abc import Iterable, Iterator +from typing import Any, Protocol import cv2 import numpy as np -from machinevisiontoolbox.ImagePointFeatures import BaseFeature2D from machinevisiontoolbox import Image +from machinevisiontoolbox.ImagePointFeatures import BaseFeature2D + + +class _ImageQueries(Protocol): + """Finite image queries, including lists and lazy file collections.""" + + def __len__(self) -> int: ... + + def __iter__(self) -> Iterator[Image]: ... # TODO: remove top N% and bottom M% of words by frequency class BagOfWords: def __init__( self, - images, + images: Iterable[Image] | BaseFeature2D | None, k: int = 2_000, nstopwords: int = 0, attempts: int = 1, @@ -372,7 +383,7 @@ def _remove_stopwords(self, verbose: bool = True) -> None: # rearrange the cluster centroids self._centroids = self._centroids[map] - def similarity(self, query) -> np.ndarray: + def similarity(self, query: Image | _ImageQueries | np.ndarray) -> np.ndarray: """ Compute similarity between bag of words and query @@ -450,7 +461,7 @@ def similarity(self, query) -> np.ndarray: sim = sim[0, :] return sim - def retrieve(self, images) -> tuple[int, float]: + def retrieve(self, images: Image | _ImageQueries | np.ndarray) -> tuple[int, float]: S = self.similarity(images).ravel() k = np.argmax(S) @@ -483,7 +494,7 @@ def occurrence(self, word: int) -> int: return np.sum(self.words == word) @staticmethod - def _word_freq_vector(words, maxwords: int) -> np.ndarray: + def _word_freq_vector(words: np.ndarray | list[int], maxwords: int) -> np.ndarray: # create columns of the W unique, unique_counts = np.unique(words, return_counts=True) # [w,f] = count_unique(words) From bf8034b20d730f64d725aa0085e707c12354843c Mon Sep 17 00:00:00 2001 From: Prajval Arora Date: Tue, 13 Oct 2026 16:56:26 -0700 Subject: [PATCH 2/3] test: add accepted BagOfWords input typing fixture Exercise feature, image, iterable, finite file collection and NumPy query annotations with Pyright. --- tests/typing/typing/bag_of_words.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/typing/typing/bag_of_words.py diff --git a/tests/typing/typing/bag_of_words.py b/tests/typing/typing/bag_of_words.py new file mode 100644 index 00000000..f0f2934c --- /dev/null +++ b/tests/typing/typing/bag_of_words.py @@ -0,0 +1,20 @@ +import numpy as np + +from machinevisiontoolbox import BagOfWords, FileCollection, Image +from machinevisiontoolbox.ImagePointFeatures import BaseFeature2D + + +def accepted( + bag: BagOfWords, image: Image, features: BaseFeature2D, files: FileCollection +) -> None: + BagOfWords(features) + BagOfWords([image]) + BagOfWords(iter([image])) + BagOfWords(None) + bag.similarity(image) + bag.similarity([image]) + bag.similarity(files) + bag.similarity(np.zeros((2, 1))) + bag.retrieve(image) + bag.retrieve(files) + BagOfWords._word_freq_vector([0, 1], 2) From 4a00e0be4a83b3687f171eaf9ff20b37b8071623 Mon Sep 17 00:00:00 2001 From: Prajval Arora Date: Tue, 13 Oct 2026 16:57:11 -0700 Subject: [PATCH 3/3] chore: place typing fixture in intended directory --- tests/typing/{typing => }/bag_of_words.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/typing/{typing => }/bag_of_words.py (100%) diff --git a/tests/typing/typing/bag_of_words.py b/tests/typing/bag_of_words.py similarity index 100% rename from tests/typing/typing/bag_of_words.py rename to tests/typing/bag_of_words.py