aissemble-inference-core 1.5.0rc3__py3-none-any.whl

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 (26) hide show
  1. aissemble_inference_core/__init__.py +19 -0
  2. aissemble_inference_core/client/__init__.py +23 -0
  3. aissemble_inference_core/client/builder/__init__.py +36 -0
  4. aissemble_inference_core/client/builder/inference_builder.py +178 -0
  5. aissemble_inference_core/client/builder/object_detection_builder.py +190 -0
  6. aissemble_inference_core/client/builder/raw_inference_builder.py +95 -0
  7. aissemble_inference_core/client/builder/summarization_builder.py +213 -0
  8. aissemble_inference_core/client/inference_client.py +158 -0
  9. aissemble_inference_core/client/oip_adapter.py +211 -0
  10. aissemble_inference_core/client/predictor.py +75 -0
  11. aissemble_inference_core/client/registry.py +201 -0
  12. aissemble_inference_core/client/results/__init__.py +29 -0
  13. aissemble_inference_core/client/results/object_detection_result.py +155 -0
  14. aissemble_inference_core/client/results/summarization_result.py +78 -0
  15. aissemble_inference_core/client/translator.py +57 -0
  16. aissemble_inference_core/client/translators/__init__.py +34 -0
  17. aissemble_inference_core/client/translators/_image_utils.py +75 -0
  18. aissemble_inference_core/client/translators/_tensor_utils.py +89 -0
  19. aissemble_inference_core/client/translators/object_detection_translator.py +212 -0
  20. aissemble_inference_core/client/translators/summarization_translator.py +147 -0
  21. aissemble_inference_core/client/translators/tensorflow_object_detection_translator.py +231 -0
  22. aissemble_inference_core-1.5.0rc3.dist-info/METADATA +71 -0
  23. aissemble_inference_core-1.5.0rc3.dist-info/RECORD +26 -0
  24. aissemble_inference_core-1.5.0rc3.dist-info/WHEEL +4 -0
  25. aissemble_inference_core-1.5.0rc3.dist-info/entry_points.txt +5 -0
  26. aissemble_inference_core-1.5.0rc3.dist-info/licenses/LICENSE.txt +201 -0
@@ -0,0 +1,201 @@
1
+ ###
2
+ # #%L
3
+ # aiSSEMBLE::Open Inference Protocol::Core
4
+ # %%
5
+ # Copyright (C) 2024 Booz Allen Hamilton Inc.
6
+ # %%
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ # #L%
19
+ ###
20
+ """Module registry for dynamic OIP module discovery.
21
+
22
+ This module provides a registry that discovers and loads OIP modules
23
+ via Python entry points. Modules can register:
24
+ - Runtimes (MLServer-compatible model runtimes)
25
+ - Translators (OIP protocol translators)
26
+ - Builders (task-specific inference builders)
27
+
28
+ Entry point groups:
29
+ - inference.runtimes: Model runtime implementations
30
+ - inference.translators: Data format translators
31
+ - inference.builders: Inference builder implementations
32
+ """
33
+
34
+ import importlib.metadata
35
+ import logging
36
+ from typing import Any, Dict, List, Type
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ class ModuleRegistry:
42
+ """Registry for dynamically discovered OIP modules.
43
+
44
+ This registry uses Python entry points to discover installed modules.
45
+ Modules register themselves by declaring entry points in their
46
+ pyproject.toml files.
47
+
48
+ Example pyproject.toml entry points:
49
+ [project.entry-points."inference.runtimes"]
50
+ yolo = "aissemble_inference_yolo:YOLORuntime"
51
+
52
+ [project.entry-points."inference.translators"]
53
+ yolo = "aissemble_inference_yolo:YOLOTranslator"
54
+
55
+ Usage:
56
+ registry = ModuleRegistry.instance()
57
+ registry.discover()
58
+
59
+ # Get a specific runtime
60
+ runtime_class = registry.get_runtime("yolo")
61
+
62
+ # List all available modules
63
+ available = registry.list_available()
64
+ """
65
+
66
+ _instance: "ModuleRegistry | None" = None
67
+
68
+ def __init__(self):
69
+ """Initialize an empty registry."""
70
+ self._runtimes: Dict[str, Type[Any]] = {}
71
+ self._translators: Dict[str, Type[Any]] = {}
72
+ self._builders: Dict[str, Type[Any]] = {}
73
+ self._loaded = False
74
+
75
+ @classmethod
76
+ def instance(cls) -> "ModuleRegistry":
77
+ """Get the singleton registry instance.
78
+
79
+ Returns:
80
+ The global ModuleRegistry instance
81
+ """
82
+ if cls._instance is None:
83
+ cls._instance = cls()
84
+ return cls._instance
85
+
86
+ @classmethod
87
+ def reset(cls) -> None:
88
+ """Reset the singleton instance (primarily for testing)."""
89
+ cls._instance = None
90
+
91
+ def discover(self) -> None:
92
+ """Load all installed OIP modules via entry points.
93
+
94
+ This method scans for entry points in the following groups:
95
+ - inference.runtimes: Model runtime implementations
96
+ - inference.translators: Data format translators
97
+ - inference.builders: Inference builder implementations
98
+
99
+ Entry points are loaded lazily - the actual class is only
100
+ imported when accessed via get_* methods.
101
+ """
102
+ if self._loaded:
103
+ return
104
+
105
+ entry_point_groups = [
106
+ ("inference.runtimes", self._runtimes),
107
+ ("inference.translators", self._translators),
108
+ ("inference.builders", self._builders),
109
+ ]
110
+
111
+ for group, registry in entry_point_groups:
112
+ try:
113
+ eps = importlib.metadata.entry_points(group=group)
114
+ for ep in eps:
115
+ try:
116
+ registry[ep.name] = ep.load()
117
+ logger.debug(f"Loaded {group} entry point: {ep.name}")
118
+ except Exception as e:
119
+ logger.warning(
120
+ f"Failed to load {group} entry point '{ep.name}': {e}"
121
+ )
122
+ except Exception as e:
123
+ logger.warning(f"Failed to scan entry points for group '{group}': {e}")
124
+
125
+ self._loaded = True
126
+
127
+ def get_runtime(self, name: str) -> Type[Any] | None:
128
+ """Get a runtime class by name.
129
+
130
+ Args:
131
+ name: The registered name of the runtime
132
+
133
+ Returns:
134
+ The runtime class, or None if not found
135
+ """
136
+ self.discover()
137
+ return self._runtimes.get(name)
138
+
139
+ def get_translator(self, name: str) -> Type[Any] | None:
140
+ """Get a translator class by name.
141
+
142
+ Args:
143
+ name: The registered name of the translator
144
+
145
+ Returns:
146
+ The translator class, or None if not found
147
+ """
148
+ self.discover()
149
+ return self._translators.get(name)
150
+
151
+ def get_builder(self, name: str) -> Type[Any] | None:
152
+ """Get a builder class by name.
153
+
154
+ Args:
155
+ name: The registered name of the builder
156
+
157
+ Returns:
158
+ The builder class, or None if not found
159
+ """
160
+ self.discover()
161
+ return self._builders.get(name)
162
+
163
+ def list_available(self) -> Dict[str, List[str]]:
164
+ """List all discovered modules.
165
+
166
+ Returns:
167
+ Dictionary mapping category to list of available module names
168
+ """
169
+ self.discover()
170
+ return {
171
+ "runtimes": list(self._runtimes.keys()),
172
+ "translators": list(self._translators.keys()),
173
+ "builders": list(self._builders.keys()),
174
+ }
175
+
176
+ def register_runtime(self, name: str, runtime_class: Type[Any]) -> None:
177
+ """Manually register a runtime class.
178
+
179
+ Args:
180
+ name: Name to register the runtime under
181
+ runtime_class: The runtime class to register
182
+ """
183
+ self._runtimes[name] = runtime_class
184
+
185
+ def register_translator(self, name: str, translator_class: Type[Any]) -> None:
186
+ """Manually register a translator class.
187
+
188
+ Args:
189
+ name: Name to register the translator under
190
+ translator_class: The translator class to register
191
+ """
192
+ self._translators[name] = translator_class
193
+
194
+ def register_builder(self, name: str, builder_class: Type[Any]) -> None:
195
+ """Manually register a builder class.
196
+
197
+ Args:
198
+ name: Name to register the builder under
199
+ builder_class: The builder class to register
200
+ """
201
+ self._builders[name] = builder_class
@@ -0,0 +1,29 @@
1
+ ###
2
+ # #%L
3
+ # aiSSEMBLE::Open Inference Protocol::Core
4
+ # %%
5
+ # Copyright (C) 2024 Booz Allen Hamilton Inc.
6
+ # %%
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ # #L%
19
+ ###
20
+ from aissemble_inference_core.client.results.object_detection_result import (
21
+ BoundingBox,
22
+ Detection,
23
+ ObjectDetectionResult,
24
+ )
25
+ from aissemble_inference_core.client.results.summarization_result import (
26
+ SummarizationResult,
27
+ )
28
+
29
+ __all__ = ["BoundingBox", "Detection", "ObjectDetectionResult", "SummarizationResult"]
@@ -0,0 +1,155 @@
1
+ ###
2
+ # #%L
3
+ # aiSSEMBLE::Open Inference Protocol::Core
4
+ # %%
5
+ # Copyright (C) 2024 Booz Allen Hamilton Inc.
6
+ # %%
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ # #L%
19
+ ###
20
+ from dataclasses import dataclass
21
+ from typing import Any
22
+
23
+
24
+ @dataclass
25
+ class BoundingBox:
26
+ """Represents a bounding box for object detection.
27
+
28
+ Attributes:
29
+ x1: Left x-coordinate
30
+ y1: Top y-coordinate
31
+ x2: Right x-coordinate
32
+ y2: Bottom y-coordinate
33
+ """
34
+
35
+ x1: float
36
+ y1: float
37
+ x2: float
38
+ y2: float
39
+
40
+ @property
41
+ def width(self) -> float:
42
+ """Calculate the width of the bounding box."""
43
+ return self.x2 - self.x1
44
+
45
+ @property
46
+ def height(self) -> float:
47
+ """Calculate the height of the bounding box."""
48
+ return self.y2 - self.y1
49
+
50
+ @property
51
+ def area(self) -> float:
52
+ """Calculate the area of the bounding box."""
53
+ return self.width * self.height
54
+
55
+ @property
56
+ def center(self) -> tuple[float, float]:
57
+ """Calculate the center point of the bounding box."""
58
+ return ((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2)
59
+
60
+
61
+ @dataclass
62
+ class Detection:
63
+ """Represents a single detected object.
64
+
65
+ Attributes:
66
+ bbox: Bounding box coordinates
67
+ label: Class label of the detected object
68
+ confidence: Confidence score (0-1)
69
+ label_id: Optional numeric ID for the label
70
+ """
71
+
72
+ bbox: BoundingBox
73
+ label: str
74
+ confidence: float
75
+ label_id: int | None = None
76
+
77
+
78
+ @dataclass
79
+ class ObjectDetectionResult:
80
+ """Value object representing the result of an object detection inference.
81
+
82
+ Attributes:
83
+ detections: List of detected objects
84
+ image_width: Width of the input image
85
+ image_height: Height of the input image
86
+ """
87
+
88
+ detections: list[Detection]
89
+ image_width: int
90
+ image_height: int
91
+
92
+ def filter_by_confidence(self, threshold: float) -> "ObjectDetectionResult":
93
+ """Filter detections by confidence threshold.
94
+
95
+ Args:
96
+ threshold: Minimum confidence score (0-1)
97
+
98
+ Returns:
99
+ New ObjectDetectionResult with filtered detections
100
+ """
101
+ filtered = [d for d in self.detections if d.confidence >= threshold]
102
+ return ObjectDetectionResult(
103
+ detections=filtered,
104
+ image_width=self.image_width,
105
+ image_height=self.image_height,
106
+ )
107
+
108
+ def filter_by_label(self, labels: list[str]) -> "ObjectDetectionResult":
109
+ """Filter detections by label.
110
+
111
+ Args:
112
+ labels: List of labels to keep
113
+
114
+ Returns:
115
+ New ObjectDetectionResult with filtered detections
116
+ """
117
+ filtered = [d for d in self.detections if d.label in labels]
118
+ return ObjectDetectionResult(
119
+ detections=filtered,
120
+ image_width=self.image_width,
121
+ image_height=self.image_height,
122
+ )
123
+
124
+ def draw_on_image(self, image: Any) -> Any:
125
+ """Draw bounding boxes on the image.
126
+
127
+ Args:
128
+ image: PIL Image or numpy array
129
+
130
+ Returns:
131
+ Image with drawn bounding boxes
132
+
133
+ Raises:
134
+ ImportError: If PIL is not installed
135
+ """
136
+ try:
137
+ from PIL import Image, ImageDraw
138
+ except ImportError as e:
139
+ raise ImportError(
140
+ "PIL (Pillow) is required for drawing. Install with: pip install Pillow"
141
+ ) from e
142
+
143
+ if not isinstance(image, Image.Image):
144
+ image = Image.fromarray(image)
145
+
146
+ draw = ImageDraw.Draw(image)
147
+ for detection in self.detections:
148
+ bbox = detection.bbox
149
+ draw.rectangle(
150
+ [(bbox.x1, bbox.y1), (bbox.x2, bbox.y2)], outline="red", width=2
151
+ )
152
+ text = f"{detection.label}: {detection.confidence:.2f}"
153
+ draw.text((bbox.x1, bbox.y1 - 10), text, fill="red")
154
+
155
+ return image
@@ -0,0 +1,78 @@
1
+ ###
2
+ # #%L
3
+ # aiSSEMBLE::Open Inference Protocol::Core
4
+ # %%
5
+ # Copyright (C) 2024 Booz Allen Hamilton Inc.
6
+ # %%
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ # #L%
19
+ ###
20
+ """Value objects for text summarization results."""
21
+
22
+ from dataclasses import dataclass
23
+
24
+
25
+ @dataclass
26
+ class SummarizationResult:
27
+ """Result of a text summarization inference.
28
+
29
+ This class encapsulates the output from a summarization model, including
30
+ the generated summary text and optional metadata about the operation.
31
+
32
+ Attributes:
33
+ summary: The generated summary text
34
+ original_length: Character count of the original input text
35
+ summary_length: Character count of the generated summary
36
+ model_name: Optional name of the model that generated the summary
37
+ input_tokens: Optional count of input tokens (for token-based models)
38
+ output_tokens: Optional count of output tokens (for token-based models)
39
+ """
40
+
41
+ summary: str
42
+ original_length: int
43
+ summary_length: int
44
+ model_name: str | None = None
45
+ input_tokens: int | None = None
46
+ output_tokens: int | None = None
47
+
48
+ @property
49
+ def compression_ratio(self) -> float:
50
+ """Calculate the compression ratio (original length / summary length).
51
+
52
+ Returns:
53
+ Ratio of original to summary length. Higher values indicate more compression.
54
+ Returns 0.0 if summary is empty.
55
+
56
+ Example:
57
+ >>> result = SummarizationResult(
58
+ ... summary="Short summary",
59
+ ... original_length=1000,
60
+ ... summary_length=100
61
+ ... )
62
+ >>> result.compression_ratio
63
+ 10.0
64
+ """
65
+ if self.summary_length == 0:
66
+ return 0.0
67
+ return self.original_length / self.summary_length
68
+
69
+ def __repr__(self) -> str:
70
+ """Return a detailed string representation."""
71
+ return (
72
+ f"SummarizationResult("
73
+ f"summary={self.summary[:50]!r}..., "
74
+ f"original_length={self.original_length}, "
75
+ f"summary_length={self.summary_length}, "
76
+ f"compression_ratio={self.compression_ratio:.2f}x"
77
+ f")"
78
+ )
@@ -0,0 +1,57 @@
1
+ ###
2
+ # #%L
3
+ # aiSSEMBLE::Open Inference Protocol::Core
4
+ # %%
5
+ # Copyright (C) 2024 Booz Allen Hamilton Inc.
6
+ # %%
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ # #L%
19
+ ###
20
+ from abc import ABC, abstractmethod
21
+ from typing import Generic, TypeVar
22
+
23
+ from aissemble_inference_core.client.oip_adapter import OipRequest, OipResponse
24
+
25
+ I = TypeVar("I")
26
+ O = TypeVar("O")
27
+
28
+
29
+ class Translator(ABC, Generic[I, O]):
30
+ """
31
+ Directly inspired by DJL, this is the ONLY class that knows about the OIP json schema,
32
+ tensor shapes, inputs, parameters, framing, etc. It is intended to be pluggable and
33
+ testable in isolation. It also provides tolerable reader behavior, where unknown
34
+ fields can be ignored.
35
+ """
36
+
37
+ @abstractmethod
38
+ def preprocess(self, input: I) -> OipRequest:
39
+ """
40
+ Preprocesses the input to create an OipRequest.
41
+
42
+ :param input: The input data of type I.
43
+ :return: An OipRequest object.
44
+ """
45
+ # TODO: Implement OipRequest creation
46
+ pass
47
+
48
+ @abstractmethod
49
+ def postprocess(self, response: OipResponse) -> O:
50
+ """
51
+ Postprocesses the OipResponse to produce the output.
52
+
53
+ :param response: The OipResponse object.
54
+ :return: The output data of type O.
55
+ """
56
+ # TODO: Implement OipResponse creation
57
+ pass
@@ -0,0 +1,34 @@
1
+ ###
2
+ # #%L
3
+ # aiSSEMBLE::Open Inference Protocol::Core
4
+ # %%
5
+ # Copyright (C) 2024 Booz Allen Hamilton Inc.
6
+ # %%
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ # #L%
19
+ ###
20
+ from aissemble_inference_core.client.translators.object_detection_translator import (
21
+ DefaultObjectDetectionTranslator,
22
+ )
23
+ from aissemble_inference_core.client.translators.summarization_translator import (
24
+ DefaultSummarizationTranslator,
25
+ )
26
+ from aissemble_inference_core.client.translators.tensorflow_object_detection_translator import (
27
+ TensorFlowObjectDetectionTranslator,
28
+ )
29
+
30
+ __all__ = [
31
+ "DefaultObjectDetectionTranslator",
32
+ "DefaultSummarizationTranslator",
33
+ "TensorFlowObjectDetectionTranslator",
34
+ ]
@@ -0,0 +1,75 @@
1
+ ###
2
+ # #%L
3
+ # aiSSEMBLE::Open Inference Protocol::Core
4
+ # %%
5
+ # Copyright (C) 2024 Booz Allen Hamilton Inc.
6
+ # %%
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ # #L%
19
+ ###
20
+ """Shared utilities for image preprocessing in translators."""
21
+
22
+ import base64
23
+ from io import BytesIO
24
+ from typing import Any
25
+
26
+
27
+ def encode_image_for_oip(input_data: Any) -> tuple[str, int, int]:
28
+ """Encode image to base64 string for OIP transport.
29
+
30
+ Accepts various input formats:
31
+ - PIL Image
32
+ - numpy array
33
+ - file path (string)
34
+ - bytes
35
+
36
+ Args:
37
+ input_data: Image in various formats
38
+
39
+ Returns:
40
+ Tuple of (base64_string, width, height)
41
+
42
+ Raises:
43
+ ImportError: If PIL is not installed
44
+ ValueError: If input type is not supported
45
+ """
46
+ try:
47
+ from PIL import Image
48
+ except ImportError as e:
49
+ raise ImportError(
50
+ "PIL (Pillow) is required for image handling. "
51
+ "Install with: pip install Pillow"
52
+ ) from e
53
+
54
+ if isinstance(input_data, str):
55
+ image = Image.open(input_data)
56
+ elif isinstance(input_data, bytes):
57
+ image = Image.open(BytesIO(input_data))
58
+ elif hasattr(input_data, "mode"):
59
+ image = input_data
60
+ else:
61
+ import numpy as np
62
+
63
+ if isinstance(input_data, np.ndarray):
64
+ image = Image.fromarray(input_data)
65
+ else:
66
+ raise ValueError(f"Unsupported input type: {type(input_data)}")
67
+
68
+ width, height = image.size
69
+
70
+ buffer = BytesIO()
71
+ image.save(buffer, format="PNG")
72
+ image_bytes = buffer.getvalue()
73
+ encoded = base64.b64encode(image_bytes).decode("utf-8")
74
+
75
+ return encoded, width, height