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.
- smqtk_classifier-0.20.0/LICENSE.txt +27 -0
- smqtk_classifier-0.20.0/PKG-INFO +55 -0
- smqtk_classifier-0.20.0/README.md +21 -0
- smqtk_classifier-0.20.0/pyproject.toml +103 -0
- smqtk_classifier-0.20.0/smqtk_classifier/__init__.py +9 -0
- smqtk_classifier-0.20.0/smqtk_classifier/_defaults.py +8 -0
- smqtk_classifier-0.20.0/smqtk_classifier/classification_element_factory.py +116 -0
- smqtk_classifier-0.20.0/smqtk_classifier/classify_descriptor_collection.py +298 -0
- smqtk_classifier-0.20.0/smqtk_classifier/exceptions.py +29 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/__init__.py +0 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classification_element/__init__.py +0 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classification_element/file.py +125 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classification_element/memory.py +77 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classification_element/postgres.py +263 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor/__init__.py +0 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor/classify_index_label_descriptor.py +63 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor_supervised/__init__.py +0 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor_supervised/libsvm.py +420 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor_supervised/sklearn_logistic_regression.py +84 -0
- smqtk_classifier-0.20.0/smqtk_classifier/impls/classify_descriptor_supervised/sklearn_svm.py +232 -0
- smqtk_classifier-0.20.0/smqtk_classifier/interfaces/__init__.py +0 -0
- smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classification_element.py +264 -0
- smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classify_descriptor.py +399 -0
- smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classify_descriptor_supervised.py +75 -0
- smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classify_image.py +60 -0
- smqtk_classifier-0.20.0/smqtk_classifier/interfaces/classify_image_supervised.py +73 -0
- 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
|
+
"""
|
|
File without changes
|
|
File without changes
|