smqtk_classifier 0.20.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. smqtk_classifier-0.20.0/LICENSE.txt +27 -0
  2. smqtk_classifier-0.20.0/PKG-INFO +55 -0
  3. smqtk_classifier-0.20.0/README.md +21 -0
  4. smqtk_classifier-0.20.0/pyproject.toml +103 -0
  5. smqtk_classifier-0.20.0/smqtk_classifier/__init__.py +9 -0
  6. smqtk_classifier-0.20.0/smqtk_classifier/_defaults.py +8 -0
  7. smqtk_classifier-0.20.0/smqtk_classifier/classification_element_factory.py +116 -0
  8. smqtk_classifier-0.20.0/smqtk_classifier/classify_descriptor_collection.py +298 -0
  9. smqtk_classifier-0.20.0/smqtk_classifier/exceptions.py +29 -0
  10. smqtk_classifier-0.20.0/smqtk_classifier/impls/__init__.py +0 -0
  11. smqtk_classifier-0.20.0/smqtk_classifier/impls/classification_element/__init__.py +0 -0
  12. smqtk_classifier-0.20.0/smqtk_classifier/impls/classification_element/file.py +125 -0
  13. smqtk_classifier-0.20.0/smqtk_classifier/impls/classification_element/memory.py +77 -0
  14. smqtk_classifier-0.20.0/smqtk_classifier/impls/classification_element/postgres.py +263 -0
  15. smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor/__init__.py +0 -0
  16. smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor/classify_index_label_descriptor.py +63 -0
  17. smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor_supervised/__init__.py +0 -0
  18. smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor_supervised/libsvm.py +420 -0
  19. smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor_supervised/sklearn_logistic_regression.py +84 -0
  20. smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor_supervised/sklearn_svm.py +232 -0
  21. smqtk_classifier-0.20.0/smqtk_classifier/interfaces/__init__.py +0 -0
  22. smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classification_element.py +264 -0
  23. smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classify_descriptor.py +399 -0
  24. smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classify_descriptor_supervised.py +75 -0
  25. smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classify_image.py +60 -0
  26. smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classify_image_supervised.py +73 -0
  27. smqtk_classifier-0.20.0/smqtk_classifier/py.typed +0 -0
@@ -0,0 +1,27 @@
1
+ Copyright 2014-2019 Kitware, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ * Neither name of Kitware, Inc. nor the names of any contributors may be used
15
+ to endorse or promote products derived from this software without specific
16
+ prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
19
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
22
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.1
2
+ Name: smqtk_classifier
3
+ Version: 0.20.0
4
+ Summary: Algorithms, data structures and utilities around performing classificationof inputs.
5
+ Home-page: https://github.com/Kitware/SMQTK-Classifier
6
+ License: BSD-3-Clause
7
+ Author: Kitware, Inc.
8
+ Author-email: smqtk-developers@kitware.com
9
+ Requires-Python: >=3.10,<4.0
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Operating System :: MacOS :: MacOS X
15
+ Classifier: Operating System :: Unix
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Provides-Extra: libsvm
22
+ Provides-Extra: sklearn
23
+ Requires-Dist: libsvm-official (>=3.30,<3.31) ; extra == "libsvm"
24
+ Requires-Dist: numpy (>=1.23.5,<2.0) ; python_version < "3.13"
25
+ Requires-Dist: numpy (>=2.1) ; python_version >= "3.13"
26
+ Requires-Dist: scikit-learn (>=1.6) ; extra == "sklearn"
27
+ Requires-Dist: smqtk-core (>=0.20.0)
28
+ Requires-Dist: smqtk-dataprovider (>=0.19.0)
29
+ Requires-Dist: smqtk-descriptors (>=0.20)
30
+ Project-URL: Documentation, https://smqtk-classifier.readthedocs.io/
31
+ Project-URL: Repository, https://github.com/Kitware/SMQTK-Classifier
32
+ Description-Content-Type: text/markdown
33
+
34
+ # SMQTK - Classifier
35
+
36
+ ## Intent
37
+ This package provides interfaces and implementations around the classification
38
+ of inputs into some form of labeled probabilistic values.
39
+ Additional data structure abstractions are defined here to standardize this
40
+ behavior into common terms.
41
+
42
+ ## Documentation
43
+ You can build the sphinx documentation locally for the most up-to-date
44
+ reference:
45
+ ```bash
46
+ # Install dependencies
47
+ poetry install
48
+ # Navigate to the documentation root.
49
+ cd docs
50
+ # Build the docs.
51
+ poetry run make html
52
+ # Open in your favorite browser!
53
+ firefox _build/html/index.html
54
+ ```
55
+
@@ -0,0 +1,21 @@
1
+ # SMQTK - Classifier
2
+
3
+ ## Intent
4
+ This package provides interfaces and implementations around the classification
5
+ of inputs into some form of labeled probabilistic values.
6
+ Additional data structure abstractions are defined here to standardize this
7
+ behavior into common terms.
8
+
9
+ ## Documentation
10
+ You can build the sphinx documentation locally for the most up-to-date
11
+ reference:
12
+ ```bash
13
+ # Install dependencies
14
+ poetry install
15
+ # Navigate to the documentation root.
16
+ cd docs
17
+ # Build the docs.
18
+ poetry run make html
19
+ # Open in your favorite browser!
20
+ firefox _build/html/index.html
21
+ ```
@@ -0,0 +1,103 @@
1
+ [build-system]
2
+ requires = ["poetry-core>=1.0.0"]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+ ###############################################################################
6
+ [tool.poetry]
7
+ name = "smqtk_classifier"
8
+ # REMEMBER: `distutils.version.*Version` types can be used to compare versions
9
+ # from strings like this.
10
+ # SMQTK prefers to use the strict numbering standard when possible.
11
+ version = "0.20.0"
12
+ description = """\
13
+ Algorithms, data structures and utilities around performing classification\
14
+ of inputs."""
15
+ license = "BSD-3-Clause"
16
+ authors = ["Kitware, Inc. <smqtk-developers@kitware.com>"]
17
+ readme = "README.md"
18
+ repository = "https://github.com/Kitware/SMQTK-Classifier"
19
+ documentation = "https://smqtk-classifier.readthedocs.io/"
20
+ classifiers = [
21
+ 'Development Status :: 3 - Alpha',
22
+ 'Intended Audience :: Developers',
23
+ 'Intended Audience :: Science/Research',
24
+ 'License :: OSI Approved :: BSD License',
25
+ 'Operating System :: MacOS :: MacOS X',
26
+ 'Operating System :: Unix',
27
+ 'Programming Language :: Python :: 3',
28
+ 'Programming Language :: Python :: 3.10',
29
+ 'Programming Language :: Python :: 3.11',
30
+ 'Programming Language :: Python :: 3.12',
31
+ 'Programming Language :: Python :: 3.13',
32
+ ]
33
+
34
+ [tool.poetry.dependencies]
35
+ python = "^3.10"
36
+ numpy = [
37
+ # Without the hinge, Poetry fails to resolve for py3.13. 2.1 is the first version with py3.13 support.
38
+ {version = ">=1.23.5,<2.0", python = "<3.13" },
39
+ {version = ">=2.1", python = ">=3.13" }
40
+ ]
41
+ smqtk-core = ">=0.20.0"
42
+ smqtk-dataprovider = ">=0.19.0"
43
+ smqtk-descriptors = ">=0.20"
44
+ libsvm-official = {version = ">=3.30,<3.31", optional = true}
45
+ scikit-learn = {version = ">=1.6", optional = true}
46
+
47
+ [tool.poetry.extras]
48
+ libsvm = [ "libsvm-official" ]
49
+ sklearn = [ "scikit-learn" ]
50
+
51
+ [tool.poetry.dev-dependencies]
52
+ # CI
53
+ flake8 = ">=6"
54
+ flake8-mutable = ">=1.2"
55
+ mypy = ">=0.991"
56
+ # Docs
57
+ # - Also see: `docs/readthedocs-reqs.txt` for use by RTD
58
+ Sphinx = ">=5.3"
59
+ sphinx-rtd-theme = ">=1.1.1"
60
+ sphinx-argparse = ">=0.4"
61
+ sphinx-prompt = ">=1.5"
62
+ livereload = ">=2.6.3"
63
+ # Testing
64
+ coverage = ">=6.5"
65
+ pytest = ">=7.2"
66
+ pytest-cov = ">=4"
67
+ # Development
68
+ ipython = ">=8.6.0"
69
+
70
+ [tool.poetry.plugins."smqtk_plugins"]
71
+ # ClassificationElement
72
+ "smqtk_classifier.impls.classification_element.file" = "smqtk_classifier.impls.classification_element.file"
73
+ "smqtk_classifier.impls.classification_element.memory" = "smqtk_classifier.impls.classification_element.memory"
74
+ "smqtk_classifier.impls.classification_element.postgres" = "smqtk_classifier.impls.classification_element.postgres"
75
+ # Classify Descriptor
76
+ "smqtk_classifier.impls.classify_descriptor.classify_index_label_descriptor" = "smqtk_classifier.impls.classify_descriptor.classify_index_label_descriptor"
77
+ # Supervised
78
+ "smqtk_classifier.impls.classify_descriptor_supervised.libsvm" = "smqtk_classifier.impls.classify_descriptor_supervised.libsvm"
79
+ "smqtk_classifier.impls.classify_descriptor_supervised.sklearn_logistic_regression" = "smqtk_classifier.impls.classify_descriptor_supervised.sklearn_logistic_regression"
80
+ "smqtk_classifier.impls.classify_descriptor_supervised.sklearn_svm" = "smqtk_classifier.impls.classify_descriptor_supervised.sklearn_svm"
81
+
82
+ ###############################################################################
83
+ [tool.pytest.ini_options]
84
+ addopts = [
85
+ "-lv", # Show local in trace-backs.
86
+ "--doctest-modules", # Increased verbosity.
87
+ "--tb=long", # Trace-back print mode.
88
+ "--cov=./smqtk_classifier", # Cover our package specifically
89
+ "--cov=./tests", # Also cover our tests for dead spots
90
+ "--cov-report=term", # Coverage report to terminal
91
+ "--cov-report=xml:coverage.xml" # For external tool reporting
92
+ ]
93
+ testpaths = [
94
+ "tests",
95
+ "smqtk_classifier",
96
+ ]
97
+ norecursedirs = [
98
+ "tests/data"
99
+ ]
100
+ doctest_optionflags = [
101
+ "NORMALIZE_WHITESPACE",
102
+ "IGNORE_EXCEPTION_DETAIL",
103
+ ]
@@ -0,0 +1,9 @@
1
+ from .interfaces.classification_element import ClassificationElement # noqa: F401
2
+ from .interfaces.classify_descriptor import ClassifyDescriptor # noqa: F401
3
+ from .interfaces.classify_descriptor_supervised import ClassifyDescriptorSupervised # noqa: F401
4
+ from .interfaces.classify_image import ClassifyImage # noqa: F401
5
+ from .interfaces.classify_image_supervised import ClassifyImageSupervised # noqa: F401
6
+
7
+ from .classification_element_factory import ClassificationElementFactory # noqa: F401
8
+ from .classify_descriptor_collection import ClassifyDescriptorCollection # noqa: F401
9
+ from .classify_descriptor_collection import ClassifierCollection # noqa: F401
@@ -0,0 +1,8 @@
1
+ from smqtk_classifier.classification_element_factory import ClassificationElementFactory
2
+ from smqtk_classifier.impls.classification_element.memory import MemoryClassificationElement
3
+
4
+
5
+ # Default classifier element factory for interfaces.
6
+ DFLT_CLASSIFIER_FACTORY = ClassificationElementFactory(
7
+ MemoryClassificationElement, {}
8
+ )
@@ -0,0 +1,116 @@
1
+ from typing import Any, Dict, Hashable, Type, TypeVar
2
+
3
+ from smqtk_core import Configurable
4
+ from smqtk_core.configuration import (
5
+ cls_conf_from_config_dict,
6
+ cls_conf_to_config_dict,
7
+ make_default_config
8
+ )
9
+ from smqtk_core.dict import merge_dict
10
+
11
+ from smqtk_classifier.interfaces.classification_element import ClassificationElement
12
+
13
+
14
+ C = TypeVar("C", bound=ClassificationElement)
15
+
16
+
17
+ class ClassificationElementFactory (Configurable):
18
+ """
19
+ Factory class for producing ClassificationElement instances of a specified
20
+ type and configuration.
21
+ """
22
+
23
+ # noinspection PyShadowingBuiltins
24
+ def __init__(self, type: Type[C], type_config: Dict[str, Any]):
25
+ """
26
+ Initialize the factory to produce ClassificationElement instances of the
27
+ given type from the given configuration.
28
+
29
+ :param type: Python implementation type of the ClassifierElement to
30
+ produce
31
+ :param type_config: Configuration dictionary that will be passed
32
+ ``from_config`` class method of given ``type``.
33
+ """
34
+ self.type = type
35
+ self.type_config = type_config
36
+
37
+ #
38
+ # Class methods
39
+ #
40
+
41
+ @classmethod
42
+ def get_default_config(cls) -> Dict[str, Any]:
43
+ """
44
+ Generate and return a default configuration dictionary for this class.
45
+ This will be primarily used for generating what the configuration
46
+ dictionary would look like for this class without instantiating it.
47
+
48
+ It is not be guaranteed that the configuration dictionary returned
49
+ from this method is valid for construction of an instance of this class.
50
+
51
+ :return: Default configuration dictionary for the class.
52
+ :rtype: dict
53
+
54
+ """
55
+ return make_default_config(ClassificationElement.get_impls())
56
+
57
+ # We likely do not expect subclasses for this type base, thus it is OK to
58
+ # use direct type reference in Type and return annotations.
59
+ @classmethod
60
+ def from_config(
61
+ cls: Type["ClassificationElementFactory"],
62
+ config_dict: Dict,
63
+ merge_default: bool = True
64
+ ) -> "ClassificationElementFactory":
65
+ """
66
+ Instantiate a new instance of this class given the configuration
67
+ JSON-compliant dictionary encapsulating initialization arguments.
68
+
69
+ This method should not be called via super unless and instance of the
70
+ class is desired.
71
+
72
+ :param config_dict: JSON compliant dictionary encapsulating
73
+ a configuration.
74
+ :param merge_default: Merge the given configuration on top of the
75
+ default provided by ``get_default_config``.
76
+
77
+ :return: Constructed instance from the provided config.
78
+ """
79
+ if merge_default:
80
+ config_dict = merge_dict(cls.get_default_config(), config_dict)
81
+
82
+ ce_type, ce_conf = cls_conf_from_config_dict(
83
+ config_dict, ClassificationElement.get_impls()
84
+ )
85
+ return ClassificationElementFactory(ce_type, ce_conf)
86
+
87
+ def get_config(self) -> Dict[str, Any]:
88
+ return cls_conf_to_config_dict(self.type, self.type_config)
89
+
90
+ # noinspection PyShadowingBuiltins
91
+ def new_classification(self, type: str, uuid: Hashable) -> ClassificationElement:
92
+ """
93
+ Create a new ClassificationElement instance of the configured
94
+ implementation.
95
+
96
+ :param type: Type of classifier. This is usually the name of the
97
+ classifier that generated this result.
98
+ :param uuid: UUID to associate with the classification.
99
+
100
+ :return: New ClassificationElement instance.
101
+ """
102
+ return self.type.from_config(self.type_config, type, uuid)
103
+
104
+ # noinspection PyShadowingBuiltins
105
+ def __call__(self, type: str, uuid: Hashable) -> ClassificationElement:
106
+ """
107
+ Create a new ClassificationElement instance of the configured
108
+ implementation.
109
+
110
+ :param type: Type of classifier. This is usually the name of the
111
+ classifier that generated this result.
112
+ :param uuid: UUID to associate with the classification.
113
+
114
+ :return: New ClassificationElement instance.
115
+ """
116
+ return self.new_classification(type, uuid)
@@ -0,0 +1,298 @@
1
+ import threading
2
+ from typing import Any, Dict, Hashable, Iterable, List, Optional, Mapping, Sequence, Set, Type
3
+ from types import TracebackType
4
+ from warnings import warn
5
+ import numpy as np
6
+
7
+ from smqtk_core.configuration import (
8
+ Configurable,
9
+ make_default_config,
10
+ from_config_dict,
11
+ to_config_dict
12
+ )
13
+ from smqtk_core.dict import merge_dict
14
+ from smqtk_descriptors import DescriptorElement
15
+
16
+ from smqtk_classifier.classification_element_factory import ClassificationElementFactory
17
+ from smqtk_classifier.exceptions import MissingLabelError
18
+ from smqtk_classifier.interfaces.classification_element import ClassificationElement
19
+
20
+ from ._defaults import DFLT_CLASSIFIER_FACTORY
21
+ from .interfaces.classify_descriptor import ClassifyDescriptor
22
+
23
+
24
+ class ClassifyDescriptorCollection (Configurable):
25
+ """
26
+ A collection of descriptively-labeled classifier instances for the purpose
27
+ of applying all stored classifiers to one or more input descriptor
28
+ elements.
29
+
30
+ TODO: [optionally?] map a classification element factory per classifier.
31
+
32
+ :param classifiers: Optional dictionary of semantic label keys and
33
+ Classifier instance values.
34
+ :param labeled_classifiers: Key-word arguments may be provided where
35
+ the key used is considered the semantic label of the provided
36
+ Classifier instance.
37
+ """
38
+
39
+ EXAMPLE_KEY = '__example_label__'
40
+
41
+ def __init__(
42
+ self,
43
+ classifiers: Mapping[str, ClassifyDescriptor] | None = None,
44
+ **labeled_classifiers: ClassifyDescriptor
45
+ ):
46
+ self._label_to_classifier_lock = threading.RLock()
47
+ self._label_to_classifier = {}
48
+
49
+ # Go though classifiers map and key-word arguments, check that values
50
+ # are actually classifiers.
51
+ if classifiers is not None:
52
+ for label, classifier in classifiers.items():
53
+ if not isinstance(classifier, ClassifyDescriptor):
54
+ raise ValueError("Found a non-Classifier instance value "
55
+ "for key '%s'" % label)
56
+ self._label_to_classifier[label] = classifier
57
+
58
+ for label, classifier in labeled_classifiers.items():
59
+ if not isinstance(classifier, ClassifyDescriptor):
60
+ raise ValueError("Found a non-Classifier instance value "
61
+ "for key '%s'" % label)
62
+ elif label in self._label_to_classifier:
63
+ raise ValueError("Duplicate classifier label '%s' provided "
64
+ "in key-word arguments." % label)
65
+ self._label_to_classifier[label] = classifier
66
+
67
+ @classmethod
68
+ def get_default_config(cls) -> Dict[str, Any]:
69
+ c = super(ClassifyDescriptorCollection, cls).get_default_config()
70
+
71
+ # We list the label-classifier mapping on one level, so remove the
72
+ # nested map parameter that can optionally be used in the constructor.
73
+ del c['classifiers']
74
+
75
+ # Add slot of a list of classifier plugin specifications
76
+ c[cls.EXAMPLE_KEY] = make_default_config(ClassifyDescriptor.get_impls())
77
+
78
+ return c
79
+
80
+ # We likely do not expect subclasses for this type base, thus it is OK to
81
+ # use direct type reference in Type and return annotations.
82
+ @classmethod
83
+ def from_config(
84
+ cls,
85
+ config_dict: Dict,
86
+ merge_default: bool = True
87
+ ) -> "ClassifyDescriptorCollection":
88
+ if merge_default:
89
+ config_dict = merge_dict(cls.get_default_config(), config_dict)
90
+
91
+ classifier_map = {}
92
+
93
+ # Copying list of keys so we can update the dictionary as we loop.
94
+ for label in list(config_dict.keys()):
95
+ # Skip the example section.
96
+ if label == cls.EXAMPLE_KEY:
97
+ continue
98
+
99
+ classifier_config = config_dict[label]
100
+ classifier = from_config_dict(classifier_config,
101
+ ClassifyDescriptor.get_impls())
102
+ classifier_map[label] = classifier
103
+
104
+ return cls(classifiers=classifier_map)
105
+
106
+ def get_config(self) -> Dict[str, Any]:
107
+ with self._label_to_classifier_lock:
108
+ c = dict((label, to_config_dict(classifier))
109
+ for label, classifier
110
+ in self._label_to_classifier.items())
111
+ return c
112
+
113
+ def __enter__(self) -> "ClassifyDescriptorCollection":
114
+ """
115
+ :rtype: IqrSession
116
+ """
117
+ self._label_to_classifier_lock.acquire()
118
+ return self
119
+
120
+ def __exit__(
121
+ self,
122
+ exc_type: Optional[Type],
123
+ exc_val: Optional[BaseException],
124
+ exc_tb: Optional[TracebackType]
125
+ ) -> None:
126
+ self._label_to_classifier_lock.release()
127
+
128
+ def size(self) -> int:
129
+ with self._label_to_classifier_lock:
130
+ return len(self._label_to_classifier)
131
+
132
+ __len__ = size
133
+
134
+ def labels(self) -> Set[str]:
135
+ """
136
+ :return: Set of labels for currently collected classifiers.
137
+ """
138
+ with self._label_to_classifier_lock:
139
+ return set(self._label_to_classifier.keys())
140
+
141
+ def add_classifier(self, label: str, classifier: ClassifyDescriptor) -> "ClassifyDescriptorCollection":
142
+ """
143
+ Add a classifier instance with associated descriptive label to this
144
+ collection.
145
+
146
+ :param label: String descriptive label for the classifier.
147
+ :param classifier: Classifier instance to collect.
148
+
149
+ :raises ValueError: Classifier provided is not actually a classifier
150
+ instance, or if the label provided already exists in this
151
+ collection.
152
+
153
+ :return: Self.
154
+ """
155
+ if not isinstance(classifier, ClassifyDescriptor):
156
+ raise ValueError("Not given a Classifier instance (given type"
157
+ " %s)." % type(classifier))
158
+ with self._label_to_classifier_lock:
159
+ if label in self._label_to_classifier:
160
+ raise ValueError("Duplicate label provided: '%s'" % label)
161
+ self._label_to_classifier[label] = classifier
162
+ return self
163
+
164
+ def get_classifier(self, label: str) -> ClassifyDescriptor:
165
+ """
166
+ Get the classifier instance for a given label.
167
+
168
+ :param label: Label of the classifier to get.
169
+
170
+ :raises KeyError: No classifier for the given label.
171
+
172
+ :return: Classifier instance.
173
+ """
174
+ with self._label_to_classifier_lock:
175
+ return self._label_to_classifier[label]
176
+
177
+ def remove_classifier(self, label: str) -> "ClassifyDescriptorCollection":
178
+ """
179
+ Remove a label-classifier pair from this collection.
180
+
181
+ :param label: Label of the classifier to remove.
182
+
183
+ :raises KeyError: The given label does not reference a classifier in
184
+ this collection.
185
+
186
+ :return: Self.
187
+ """
188
+ with self._label_to_classifier_lock:
189
+ del self._label_to_classifier[label]
190
+ return self
191
+
192
+ def labels_to_classifiers(self, labels: Optional[Iterable[str]] = None) -> Dict[str, ClassifyDescriptor]:
193
+ """
194
+ Get a shallow copy mapping of classifiers for the labels given, or for
195
+ all classifiers if no labels were explicitly given.
196
+
197
+ This method is thread-safe and the returned dictionary is separate from
198
+ this class's control. However, the classifier instances are still
199
+ shared.
200
+
201
+ :param None | typing.Iterable[str] labels:
202
+ One or more labels of stored classifiers to retrieve.
203
+ If None, we will consider all stored classifiers.
204
+
205
+ :raises MissingLabelError: Thrown when one or more labels provided do
206
+ not associate to any currently stored classifiers.
207
+
208
+ :return: Dictionary mapping string labels to Classifier instances.
209
+ """
210
+ with self._label_to_classifier_lock:
211
+ if labels is not None:
212
+ labels = list(labels)
213
+ # If we're missing some of the requested labels, complain
214
+ missing_labels = set(labels) - self.labels()
215
+ if missing_labels:
216
+ raise MissingLabelError(missing_labels)
217
+ label2classifier = {label: self._label_to_classifier[label]
218
+ for label in labels}
219
+ else:
220
+ label2classifier = dict(self._label_to_classifier)
221
+ return label2classifier
222
+
223
+ def classify(
224
+ self,
225
+ descriptor: DescriptorElement,
226
+ labels: Optional[Iterable[str]] = None,
227
+ factory: ClassificationElementFactory = DFLT_CLASSIFIER_FACTORY,
228
+ overwrite: bool = False
229
+ ) -> Dict[str, ClassificationElement]:
230
+ """
231
+ Apply all stored classifiers to the given descriptor element.
232
+
233
+ We return a dictionary mapping the label of a stored classifier to the
234
+ classifier element result produced by that classifier via the
235
+ provided classification element factory.
236
+
237
+ :param descriptor: Descriptor element to classify.
238
+ :param labels: One or more labels of stored classifiers to use for
239
+ classifying the given descriptor. If None, use all stored
240
+ classifiers.
241
+ :param factory: Classification element factory.
242
+ :param overwrite: Force re-computation of the classification of the
243
+ input descriptor.
244
+
245
+ :raises smqtk.exceptions.MissingLabelError: Some or all of the
246
+ requested labels are missing.
247
+
248
+ :return: Result dictionary of classifier labels to classification
249
+ elements.
250
+ """
251
+ d_classifications = {}
252
+ label2classifier = self.labels_to_classifiers(labels)
253
+ for label, classifier in label2classifier.items():
254
+ d_classifications[label] = classifier.classify_one_element(
255
+ descriptor, factory=factory, overwrite=overwrite
256
+ )
257
+ return d_classifications
258
+
259
+ def classify_arrays(
260
+ self,
261
+ array_seq: Sequence[np.ndarray],
262
+ labels: Sequence[str] | None = None
263
+ ) -> Dict[str, List[Dict[Hashable, float]]]:
264
+ """
265
+ Apply all stored classifiers to the given iterable or matrix of numpy
266
+ arrays.
267
+
268
+ We return a dictionary mapping the label of a stored classifier to the
269
+ class-confidence map result produced by that classifier's
270
+ `classify_arrays` method.
271
+
272
+ :param Sequence[numpy.ndarray] array_seq:
273
+ Sequence of descriptor vectors, as numpy arrays, to be classified.
274
+ :param Sequence[str] labels:
275
+ One or more labels of stored classifiers to use for classifying the
276
+ given descriptors. If None (the default), use all stored
277
+ classifiers.
278
+
279
+ :raises MissingLabelError: Thrown when one or more labels provided do
280
+ not associate to any currently stored classifiers.
281
+
282
+ :return: Dictionary of result predictions for each classifier.
283
+ :rtype: dict[str, list[dict[collections.abc.Hashable, float]]]
284
+ """
285
+ label2classifier = self.labels_to_classifiers(labels)
286
+ label2pred = {}
287
+ for label, classifier in label2classifier.items():
288
+ label2pred[label] = list(classifier.classify_arrays(array_seq))
289
+ return label2pred
290
+
291
+
292
+ class ClassifierCollection(ClassifyDescriptorCollection):
293
+
294
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
295
+ warn("ClassifierCollection was renamed to "
296
+ "ClassifyDescriptorCollection", category=DeprecationWarning,
297
+ stacklevel=2)
298
+ super().__init__(*args, **kwargs)
@@ -0,0 +1,29 @@
1
+ from typing import Set
2
+
3
+
4
+ class NoClassificationError (Exception):
5
+ """
6
+ When a ClassificationElement has no mapping yet set, but an operation
7
+ required it.
8
+ """
9
+
10
+
11
+ class MissingLabelError(Exception):
12
+ """
13
+ Raised by ClassifyDescriptorCollection.classify when requested classifier
14
+ labels are missing from collection.
15
+ """
16
+ def __init__(self, labels: Set[str]):
17
+ """
18
+ :param labels: The labels missing from the collection
19
+ """
20
+ super(MissingLabelError, self).__init__(labels)
21
+ self.labels = labels
22
+
23
+
24
+ class ExistingModelError(Exception):
25
+ """
26
+ Raised by ClassifyDescriptorSupervised and ClassifyImageSupervised when
27
+ a model already exists in an instance to prevent overwriting and existing
28
+ model
29
+ """