nv-ingest-api 2025.4.18.dev20250418__py3-none-any.whl → 2025.4.20.dev20250420__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.

Potentially problematic release.


This version of nv-ingest-api might be problematic. Click here for more details.

Files changed (153) hide show
  1. nv_ingest_api/__init__.py +0 -3
  2. nv_ingest_api/{internal/primitives → primitives}/control_message_task.py +0 -4
  3. nv_ingest_api/{internal/primitives → primitives}/ingest_control_message.py +2 -5
  4. {nv_ingest_api-2025.4.18.dev20250418.dist-info → nv_ingest_api-2025.4.20.dev20250420.dist-info}/METADATA +1 -1
  5. nv_ingest_api-2025.4.20.dev20250420.dist-info/RECORD +9 -0
  6. {nv_ingest_api-2025.4.18.dev20250418.dist-info → nv_ingest_api-2025.4.20.dev20250420.dist-info}/WHEEL +1 -1
  7. nv_ingest_api/interface/__init__.py +0 -215
  8. nv_ingest_api/interface/extract.py +0 -972
  9. nv_ingest_api/interface/mutate.py +0 -154
  10. nv_ingest_api/interface/store.py +0 -218
  11. nv_ingest_api/interface/transform.py +0 -382
  12. nv_ingest_api/interface/utility.py +0 -200
  13. nv_ingest_api/internal/enums/__init__.py +0 -3
  14. nv_ingest_api/internal/enums/common.py +0 -494
  15. nv_ingest_api/internal/extract/__init__.py +0 -3
  16. nv_ingest_api/internal/extract/audio/__init__.py +0 -3
  17. nv_ingest_api/internal/extract/audio/audio_extraction.py +0 -149
  18. nv_ingest_api/internal/extract/docx/__init__.py +0 -5
  19. nv_ingest_api/internal/extract/docx/docx_extractor.py +0 -205
  20. nv_ingest_api/internal/extract/docx/engines/__init__.py +0 -0
  21. nv_ingest_api/internal/extract/docx/engines/docxreader_helpers/__init__.py +0 -3
  22. nv_ingest_api/internal/extract/docx/engines/docxreader_helpers/docx_helper.py +0 -122
  23. nv_ingest_api/internal/extract/docx/engines/docxreader_helpers/docxreader.py +0 -895
  24. nv_ingest_api/internal/extract/image/__init__.py +0 -3
  25. nv_ingest_api/internal/extract/image/chart_extractor.py +0 -353
  26. nv_ingest_api/internal/extract/image/image_extractor.py +0 -204
  27. nv_ingest_api/internal/extract/image/image_helpers/__init__.py +0 -3
  28. nv_ingest_api/internal/extract/image/image_helpers/common.py +0 -403
  29. nv_ingest_api/internal/extract/image/infographic_extractor.py +0 -253
  30. nv_ingest_api/internal/extract/image/table_extractor.py +0 -344
  31. nv_ingest_api/internal/extract/pdf/__init__.py +0 -3
  32. nv_ingest_api/internal/extract/pdf/engines/__init__.py +0 -19
  33. nv_ingest_api/internal/extract/pdf/engines/adobe.py +0 -484
  34. nv_ingest_api/internal/extract/pdf/engines/llama.py +0 -243
  35. nv_ingest_api/internal/extract/pdf/engines/nemoretriever.py +0 -597
  36. nv_ingest_api/internal/extract/pdf/engines/pdf_helpers/__init__.py +0 -146
  37. nv_ingest_api/internal/extract/pdf/engines/pdfium.py +0 -603
  38. nv_ingest_api/internal/extract/pdf/engines/tika.py +0 -96
  39. nv_ingest_api/internal/extract/pdf/engines/unstructured_io.py +0 -426
  40. nv_ingest_api/internal/extract/pdf/pdf_extractor.py +0 -74
  41. nv_ingest_api/internal/extract/pptx/__init__.py +0 -5
  42. nv_ingest_api/internal/extract/pptx/engines/__init__.py +0 -0
  43. nv_ingest_api/internal/extract/pptx/engines/pptx_helper.py +0 -799
  44. nv_ingest_api/internal/extract/pptx/pptx_extractor.py +0 -187
  45. nv_ingest_api/internal/mutate/__init__.py +0 -3
  46. nv_ingest_api/internal/mutate/deduplicate.py +0 -110
  47. nv_ingest_api/internal/mutate/filter.py +0 -133
  48. nv_ingest_api/internal/primitives/__init__.py +0 -0
  49. nv_ingest_api/internal/primitives/nim/__init__.py +0 -8
  50. nv_ingest_api/internal/primitives/nim/default_values.py +0 -15
  51. nv_ingest_api/internal/primitives/nim/model_interface/__init__.py +0 -3
  52. nv_ingest_api/internal/primitives/nim/model_interface/cached.py +0 -274
  53. nv_ingest_api/internal/primitives/nim/model_interface/decorators.py +0 -56
  54. nv_ingest_api/internal/primitives/nim/model_interface/deplot.py +0 -270
  55. nv_ingest_api/internal/primitives/nim/model_interface/helpers.py +0 -275
  56. nv_ingest_api/internal/primitives/nim/model_interface/nemoretriever_parse.py +0 -238
  57. nv_ingest_api/internal/primitives/nim/model_interface/paddle.py +0 -462
  58. nv_ingest_api/internal/primitives/nim/model_interface/parakeet.py +0 -367
  59. nv_ingest_api/internal/primitives/nim/model_interface/text_embedding.py +0 -132
  60. nv_ingest_api/internal/primitives/nim/model_interface/vlm.py +0 -152
  61. nv_ingest_api/internal/primitives/nim/model_interface/yolox.py +0 -1400
  62. nv_ingest_api/internal/primitives/nim/nim_client.py +0 -344
  63. nv_ingest_api/internal/primitives/nim/nim_model_interface.py +0 -81
  64. nv_ingest_api/internal/primitives/tracing/__init__.py +0 -0
  65. nv_ingest_api/internal/primitives/tracing/latency.py +0 -69
  66. nv_ingest_api/internal/primitives/tracing/logging.py +0 -96
  67. nv_ingest_api/internal/primitives/tracing/tagging.py +0 -197
  68. nv_ingest_api/internal/schemas/__init__.py +0 -3
  69. nv_ingest_api/internal/schemas/extract/__init__.py +0 -3
  70. nv_ingest_api/internal/schemas/extract/extract_audio_schema.py +0 -130
  71. nv_ingest_api/internal/schemas/extract/extract_chart_schema.py +0 -135
  72. nv_ingest_api/internal/schemas/extract/extract_docx_schema.py +0 -124
  73. nv_ingest_api/internal/schemas/extract/extract_image_schema.py +0 -124
  74. nv_ingest_api/internal/schemas/extract/extract_infographic_schema.py +0 -128
  75. nv_ingest_api/internal/schemas/extract/extract_pdf_schema.py +0 -218
  76. nv_ingest_api/internal/schemas/extract/extract_pptx_schema.py +0 -124
  77. nv_ingest_api/internal/schemas/extract/extract_table_schema.py +0 -129
  78. nv_ingest_api/internal/schemas/message_brokers/__init__.py +0 -3
  79. nv_ingest_api/internal/schemas/message_brokers/message_broker_client_schema.py +0 -23
  80. nv_ingest_api/internal/schemas/message_brokers/request_schema.py +0 -34
  81. nv_ingest_api/internal/schemas/message_brokers/response_schema.py +0 -19
  82. nv_ingest_api/internal/schemas/meta/__init__.py +0 -3
  83. nv_ingest_api/internal/schemas/meta/base_model_noext.py +0 -11
  84. nv_ingest_api/internal/schemas/meta/ingest_job_schema.py +0 -237
  85. nv_ingest_api/internal/schemas/meta/metadata_schema.py +0 -221
  86. nv_ingest_api/internal/schemas/mutate/__init__.py +0 -3
  87. nv_ingest_api/internal/schemas/mutate/mutate_image_dedup_schema.py +0 -16
  88. nv_ingest_api/internal/schemas/store/__init__.py +0 -3
  89. nv_ingest_api/internal/schemas/store/store_embedding_schema.py +0 -28
  90. nv_ingest_api/internal/schemas/store/store_image_schema.py +0 -30
  91. nv_ingest_api/internal/schemas/transform/__init__.py +0 -3
  92. nv_ingest_api/internal/schemas/transform/transform_image_caption_schema.py +0 -15
  93. nv_ingest_api/internal/schemas/transform/transform_image_filter_schema.py +0 -17
  94. nv_ingest_api/internal/schemas/transform/transform_text_embedding_schema.py +0 -25
  95. nv_ingest_api/internal/schemas/transform/transform_text_splitter_schema.py +0 -22
  96. nv_ingest_api/internal/store/__init__.py +0 -3
  97. nv_ingest_api/internal/store/embed_text_upload.py +0 -236
  98. nv_ingest_api/internal/store/image_upload.py +0 -232
  99. nv_ingest_api/internal/transform/__init__.py +0 -3
  100. nv_ingest_api/internal/transform/caption_image.py +0 -205
  101. nv_ingest_api/internal/transform/embed_text.py +0 -496
  102. nv_ingest_api/internal/transform/split_text.py +0 -157
  103. nv_ingest_api/util/__init__.py +0 -0
  104. nv_ingest_api/util/control_message/__init__.py +0 -0
  105. nv_ingest_api/util/control_message/validators.py +0 -47
  106. nv_ingest_api/util/converters/__init__.py +0 -0
  107. nv_ingest_api/util/converters/bytetools.py +0 -78
  108. nv_ingest_api/util/converters/containers.py +0 -65
  109. nv_ingest_api/util/converters/datetools.py +0 -90
  110. nv_ingest_api/util/converters/dftools.py +0 -127
  111. nv_ingest_api/util/converters/formats.py +0 -64
  112. nv_ingest_api/util/converters/type_mappings.py +0 -27
  113. nv_ingest_api/util/detectors/__init__.py +0 -5
  114. nv_ingest_api/util/detectors/language.py +0 -38
  115. nv_ingest_api/util/exception_handlers/__init__.py +0 -0
  116. nv_ingest_api/util/exception_handlers/converters.py +0 -72
  117. nv_ingest_api/util/exception_handlers/decorators.py +0 -223
  118. nv_ingest_api/util/exception_handlers/detectors.py +0 -74
  119. nv_ingest_api/util/exception_handlers/pdf.py +0 -116
  120. nv_ingest_api/util/exception_handlers/schemas.py +0 -68
  121. nv_ingest_api/util/image_processing/__init__.py +0 -5
  122. nv_ingest_api/util/image_processing/clustering.py +0 -260
  123. nv_ingest_api/util/image_processing/processing.py +0 -179
  124. nv_ingest_api/util/image_processing/table_and_chart.py +0 -449
  125. nv_ingest_api/util/image_processing/transforms.py +0 -407
  126. nv_ingest_api/util/logging/__init__.py +0 -0
  127. nv_ingest_api/util/logging/configuration.py +0 -31
  128. nv_ingest_api/util/message_brokers/__init__.py +0 -3
  129. nv_ingest_api/util/message_brokers/simple_message_broker/__init__.py +0 -9
  130. nv_ingest_api/util/message_brokers/simple_message_broker/broker.py +0 -465
  131. nv_ingest_api/util/message_brokers/simple_message_broker/ordered_message_queue.py +0 -71
  132. nv_ingest_api/util/message_brokers/simple_message_broker/simple_client.py +0 -451
  133. nv_ingest_api/util/metadata/__init__.py +0 -5
  134. nv_ingest_api/util/metadata/aggregators.py +0 -469
  135. nv_ingest_api/util/multi_processing/__init__.py +0 -8
  136. nv_ingest_api/util/multi_processing/mp_pool_singleton.py +0 -194
  137. nv_ingest_api/util/nim/__init__.py +0 -56
  138. nv_ingest_api/util/pdf/__init__.py +0 -3
  139. nv_ingest_api/util/pdf/pdfium.py +0 -427
  140. nv_ingest_api/util/schema/__init__.py +0 -0
  141. nv_ingest_api/util/schema/schema_validator.py +0 -10
  142. nv_ingest_api/util/service_clients/__init__.py +0 -3
  143. nv_ingest_api/util/service_clients/client_base.py +0 -86
  144. nv_ingest_api/util/service_clients/kafka/__init__.py +0 -3
  145. nv_ingest_api/util/service_clients/redis/__init__.py +0 -0
  146. nv_ingest_api/util/service_clients/redis/redis_client.py +0 -823
  147. nv_ingest_api/util/service_clients/rest/__init__.py +0 -0
  148. nv_ingest_api/util/service_clients/rest/rest_client.py +0 -531
  149. nv_ingest_api/util/string_processing/__init__.py +0 -51
  150. nv_ingest_api-2025.4.18.dev20250418.dist-info/RECORD +0 -152
  151. /nv_ingest_api/{internal → primitives}/__init__.py +0 -0
  152. {nv_ingest_api-2025.4.18.dev20250418.dist-info → nv_ingest_api-2025.4.20.dev20250420.dist-info}/licenses/LICENSE +0 -0
  153. {nv_ingest_api-2025.4.18.dev20250418.dist-info → nv_ingest_api-2025.4.20.dev20250420.dist-info}/top_level.txt +0 -0
@@ -1,274 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES.
2
- # All rights reserved.
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
-
6
- import base64
7
- import io
8
- import logging
9
- import PIL.Image as Image
10
- from typing import Any, Dict, Optional, List
11
-
12
- import numpy as np
13
-
14
- from nv_ingest_api.internal.primitives.nim import ModelInterface
15
- from nv_ingest_api.util.image_processing.transforms import base64_to_numpy
16
-
17
- logger = logging.getLogger(__name__)
18
-
19
-
20
- class CachedModelInterface(ModelInterface):
21
- """
22
- An interface for handling inference with a Cached model, supporting both gRPC and HTTP
23
- protocols, including batched input.
24
- """
25
-
26
- def name(self) -> str:
27
- """
28
- Get the name of the model interface.
29
-
30
- Returns
31
- -------
32
- str
33
- The name of the model interface ("Cached").
34
- """
35
- return "Cached"
36
-
37
- def prepare_data_for_inference(self, data: Dict[str, Any]) -> Dict[str, Any]:
38
- """
39
- Decode base64-encoded images into NumPy arrays, storing them in `data["image_arrays"]`.
40
-
41
- Parameters
42
- ----------
43
- data : dict of str -> Any
44
- The input data containing either:
45
- - "base64_image": a single base64-encoded image, or
46
- - "base64_images": a list of base64-encoded images.
47
-
48
- Returns
49
- -------
50
- dict of str -> Any
51
- The updated data dictionary with decoded image arrays stored in
52
- "image_arrays", where each array has shape (H, W, C).
53
-
54
- Raises
55
- ------
56
- KeyError
57
- If neither 'base64_image' nor 'base64_images' is provided.
58
- ValueError
59
- If 'base64_images' is provided but is not a list.
60
- """
61
- if "base64_images" in data:
62
- base64_list = data["base64_images"]
63
- if not isinstance(base64_list, list):
64
- raise ValueError("The 'base64_images' key must contain a list of base64-encoded strings.")
65
- data["image_arrays"] = [base64_to_numpy(img) for img in base64_list]
66
-
67
- elif "base64_image" in data:
68
- # Fallback to single image case; wrap it in a list to keep the interface consistent
69
- data["image_arrays"] = [base64_to_numpy(data["base64_image"])]
70
-
71
- else:
72
- raise KeyError("Input data must include 'base64_image' or 'base64_images' with base64-encoded images.")
73
-
74
- return data
75
-
76
- def format_input(self, data: Dict[str, Any], protocol: str, max_batch_size: int, **kwargs) -> Any:
77
- """
78
- Format input data for the specified protocol ("grpc" or "http"), handling batched images.
79
- Additionally, returns batched data that coalesces the original image arrays and their dimensions
80
- in the same order as provided.
81
-
82
- Parameters
83
- ----------
84
- data : dict of str -> Any
85
- The input data dictionary, expected to contain "image_arrays" (a list of np.ndarray).
86
- protocol : str
87
- The protocol to use, "grpc" or "http".
88
- max_batch_size : int
89
- The maximum number of images per batch.
90
-
91
- Returns
92
- -------
93
- tuple
94
- A tuple (formatted_batches, formatted_batch_data) where:
95
- - For gRPC: formatted_batches is a list of NumPy arrays, each of shape (B, H, W, C)
96
- with B <= max_batch_size.
97
- - For HTTP: formatted_batches is a list of JSON-serializable dict payloads.
98
- - In both cases, formatted_batch_data is a list of dicts with the keys:
99
- "image_arrays": the list of original np.ndarray images for that batch, and
100
- "image_dims": a list of (height, width) tuples for each image in the batch.
101
-
102
- Raises
103
- ------
104
- KeyError
105
- If "image_arrays" is missing in the data dictionary.
106
- ValueError
107
- If the protocol is invalid, or if no valid images are found.
108
- """
109
- if "image_arrays" not in data:
110
- raise KeyError("Expected 'image_arrays' in data. Make sure prepare_data_for_inference was called.")
111
-
112
- image_arrays = data["image_arrays"]
113
- # Compute dimensions for each image.
114
- image_dims = [(img.shape[0], img.shape[1]) for img in image_arrays]
115
-
116
- # Helper: chunk a list into sublists of length up to chunk_size.
117
- def chunk_list(lst: list, chunk_size: int) -> List[list]:
118
- return [lst[i : i + chunk_size] for i in range(0, len(lst), chunk_size)]
119
-
120
- if protocol == "grpc":
121
- logger.debug("Formatting input for gRPC Cached model (batched).")
122
- batched_images = []
123
- for arr in image_arrays:
124
- # Expand from (H, W, C) to (1, H, W, C) if needed
125
- if arr.ndim == 3:
126
- arr = np.expand_dims(arr, axis=0)
127
- batched_images.append(arr.astype(np.float32))
128
-
129
- if not batched_images:
130
- raise ValueError("No valid images found for gRPC formatting.")
131
-
132
- # Chunk the processed images, original arrays, and dimensions.
133
- batched_image_chunks = chunk_list(batched_images, max_batch_size)
134
- orig_chunks = chunk_list(image_arrays, max_batch_size)
135
- dims_chunks = chunk_list(image_dims, max_batch_size)
136
-
137
- batched_inputs = []
138
- formatted_batch_data = []
139
- for proc_chunk, orig_chunk, dims_chunk in zip(batched_image_chunks, orig_chunks, dims_chunks):
140
- # Concatenate along the batch dimension => shape (B, H, W, C)
141
- batched_input = np.concatenate(proc_chunk, axis=0)
142
- batched_inputs.append(batched_input)
143
- formatted_batch_data.append({"image_arrays": orig_chunk, "image_dims": dims_chunk})
144
- return batched_inputs, formatted_batch_data
145
-
146
- elif protocol == "http":
147
- logger.debug("Formatting input for HTTP Cached model (batched).")
148
- content_list: List[Dict[str, Any]] = []
149
- for arr in image_arrays:
150
- # Convert to uint8 if needed, then to PIL Image and base64-encode it.
151
- if arr.dtype != np.uint8:
152
- arr = (arr * 255).astype(np.uint8)
153
- image_pil = Image.fromarray(arr)
154
- buffered = io.BytesIO()
155
- image_pil.save(buffered, format="PNG")
156
- base64_img = base64.b64encode(buffered.getvalue()).decode("utf-8")
157
- image_item = {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_img}"}}
158
- content_list.append(image_item)
159
-
160
- # Chunk the content list, original arrays, and dimensions.
161
- content_chunks = chunk_list(content_list, max_batch_size)
162
- orig_chunks = chunk_list(image_arrays, max_batch_size)
163
- dims_chunks = chunk_list(image_dims, max_batch_size)
164
-
165
- payload_batches = []
166
- formatted_batch_data = []
167
- for chunk, orig_chunk, dims_chunk in zip(content_chunks, orig_chunks, dims_chunks):
168
- message = {"content": chunk}
169
- payload = {"messages": [message]}
170
- payload_batches.append(payload)
171
- formatted_batch_data.append({"image_arrays": orig_chunk, "image_dims": dims_chunk})
172
- return payload_batches, formatted_batch_data
173
-
174
- else:
175
- raise ValueError("Invalid protocol specified. Must be 'grpc' or 'http'.")
176
-
177
- def parse_output(self, response: Any, protocol: str, data: Optional[Dict[str, Any]] = None, **kwargs: Any) -> Any:
178
- """
179
- Parse the output from the Cached model's inference response.
180
-
181
- Parameters
182
- ----------
183
- response : Any
184
- The raw response from the model inference.
185
- protocol : str
186
- The protocol used ("grpc" or "http").
187
- data : dict of str -> Any, optional
188
- Additional input data (unused here, but available for consistency).
189
- **kwargs : Any
190
- Additional keyword arguments for future compatibility.
191
-
192
- Returns
193
- -------
194
- Any
195
- The parsed output data (e.g., list of strings), depending on the protocol.
196
-
197
- Raises
198
- ------
199
- ValueError
200
- If the protocol is invalid.
201
- RuntimeError
202
- If the HTTP response is not as expected (missing 'data' key).
203
- """
204
- if protocol == "grpc":
205
- logger.debug("Parsing output from gRPC Cached model (batched).")
206
- parsed: List[str] = []
207
- # Assume `response` is iterable, each element a list/array of byte strings
208
- for single_output in response:
209
- joined_str = " ".join(o.decode("utf-8") for o in single_output)
210
- parsed.append(joined_str)
211
- return parsed
212
-
213
- elif protocol == "http":
214
- logger.debug("Parsing output from HTTP Cached model (batched).")
215
- if not isinstance(response, dict):
216
- raise RuntimeError("Expected JSON/dict response for HTTP, got something else.")
217
- if "data" not in response or not response["data"]:
218
- raise RuntimeError("Unexpected response format: 'data' key missing or empty.")
219
-
220
- contents: List[str] = []
221
- for item in response["data"]:
222
- # Each "item" might have a "content" key
223
- content = item.get("content", "")
224
- contents.append(content)
225
-
226
- return contents
227
-
228
- else:
229
- raise ValueError("Invalid protocol specified. Must be 'grpc' or 'http'.")
230
-
231
- def process_inference_results(self, output: Any, protocol: str, **kwargs: Any) -> Any:
232
- """
233
- Process inference results for the Cached model.
234
-
235
- Parameters
236
- ----------
237
- output : Any
238
- The raw output from the model.
239
- protocol : str
240
- The inference protocol used ("grpc" or "http").
241
- **kwargs : Any
242
- Additional parameters for post-processing (not used here).
243
-
244
- Returns
245
- -------
246
- Any
247
- The processed inference results, which here is simply returned as-is.
248
- """
249
- # For Cached model, we simply return what we parsed (e.g., a list of strings or a single string)
250
- return output
251
-
252
- def _extract_content_from_nim_response(self, json_response: Dict[str, Any]) -> Any:
253
- """
254
- Extract content from the JSON response of a NIM (HTTP) API request.
255
-
256
- Parameters
257
- ----------
258
- json_response : dict of str -> Any
259
- The JSON response from the NIM API.
260
-
261
- Returns
262
- -------
263
- Any
264
- The extracted content from the response.
265
-
266
- Raises
267
- ------
268
- RuntimeError
269
- If the response format is unexpected (missing 'data' or empty).
270
- """
271
- if "data" not in json_response or not json_response["data"]:
272
- raise RuntimeError("Unexpected response format: 'data' key is missing or empty.")
273
-
274
- return json_response["data"][0]["content"]
@@ -1,56 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES.
2
- # All rights reserved.
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- import logging
6
- from functools import wraps
7
- from multiprocessing import Lock
8
- from multiprocessing import Manager
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
- # Create a shared manager and lock for thread-safe access
13
- manager = Manager()
14
- global_cache = manager.dict()
15
- lock = Lock()
16
-
17
-
18
- def multiprocessing_cache(max_calls):
19
- """
20
- A decorator that creates a global cache shared between multiple processes.
21
- The cache is invalidated after `max_calls` number of accesses.
22
-
23
- Args:
24
- max_calls (int): The number of calls after which the cache is cleared.
25
-
26
- Returns:
27
- function: The decorated function with global cache and invalidation logic.
28
- """
29
-
30
- def decorator(func):
31
- call_count = manager.Value("i", 0) # Shared integer for call counting
32
-
33
- @wraps(func)
34
- def wrapper(*args, **kwargs):
35
- key = (func.__name__, args, frozenset(kwargs.items()))
36
-
37
- with lock:
38
- call_count.value += 1
39
-
40
- if call_count.value > max_calls:
41
- global_cache.clear()
42
- call_count.value = 0
43
-
44
- if key in global_cache:
45
- return global_cache[key]
46
-
47
- result = func(*args, **kwargs)
48
-
49
- with lock:
50
- global_cache[key] = result
51
-
52
- return result
53
-
54
- return wrapper
55
-
56
- return decorator
@@ -1,270 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2024, NVIDIA CORPORATION & AFFILIATES.
2
- # All rights reserved.
3
- # SPDX-License-Identifier: Apache-2.0
4
-
5
- from typing import Dict, Any, Optional, List
6
-
7
- import numpy as np
8
- import logging
9
-
10
- from nv_ingest_api.internal.primitives.nim import ModelInterface
11
- from nv_ingest_api.util.image_processing.transforms import base64_to_numpy
12
-
13
- logger = logging.getLogger(__name__)
14
-
15
-
16
- class DeplotModelInterface(ModelInterface):
17
- """
18
- An interface for handling inference with a Deplot model, supporting both gRPC and HTTP protocols,
19
- now updated to handle multiple base64 images ('base64_images').
20
- """
21
-
22
- def name(self) -> str:
23
- """
24
- Get the name of the model interface.
25
-
26
- Returns
27
- -------
28
- str
29
- The name of the model interface ("Deplot").
30
- """
31
- return "Deplot"
32
-
33
- def prepare_data_for_inference(self, data: Dict[str, Any]) -> Dict[str, Any]:
34
- """
35
- Prepare input data by decoding one or more base64-encoded images into NumPy arrays.
36
-
37
- Parameters
38
- ----------
39
- data : dict
40
- The input data containing either 'base64_image' (single image)
41
- or 'base64_images' (multiple images).
42
-
43
- Returns
44
- -------
45
- dict
46
- The updated data dictionary with 'image_arrays': a list of decoded NumPy arrays.
47
- """
48
-
49
- # Handle a single base64_image or multiple base64_images
50
- if "base64_images" in data:
51
- base64_list = data["base64_images"]
52
- if not isinstance(base64_list, list):
53
- raise ValueError("The 'base64_images' key must contain a list of base64-encoded strings.")
54
- image_arrays = [base64_to_numpy(b64) for b64 in base64_list]
55
-
56
- elif "base64_image" in data:
57
- # Fallback for single image
58
- image_arrays = [base64_to_numpy(data["base64_image"])]
59
- else:
60
- raise KeyError("Input data must include 'base64_image' or 'base64_images'.")
61
-
62
- data["image_arrays"] = image_arrays
63
-
64
- return data
65
-
66
- def format_input(self, data: Dict[str, Any], protocol: str, max_batch_size: int, **kwargs) -> Any:
67
- """
68
- Format input data for the specified protocol (gRPC or HTTP) for Deplot.
69
- For HTTP, we now construct multiple messages—one per image batch—along with
70
- corresponding batch data carrying the original image arrays and their dimensions.
71
-
72
- Parameters
73
- ----------
74
- data : dict of str -> Any
75
- The input data dictionary, expected to contain "image_arrays" (a list of np.ndarray).
76
- protocol : str
77
- The protocol to use, "grpc" or "http".
78
- max_batch_size : int
79
- The maximum number of images per batch.
80
- kwargs : dict
81
- Additional parameters to pass to the payload preparation (for HTTP).
82
-
83
- Returns
84
- -------
85
- tuple
86
- (formatted_batches, formatted_batch_data) where:
87
- - For gRPC: formatted_batches is a list of NumPy arrays, each of shape (B, H, W, C)
88
- with B <= max_batch_size.
89
- - For HTTP: formatted_batches is a list of JSON-serializable payload dicts.
90
- - In both cases, formatted_batch_data is a list of dicts containing:
91
- "image_arrays": the list of original np.ndarray images for that batch, and
92
- "image_dims": a list of (height, width) tuples for each image in the batch.
93
-
94
- Raises
95
- ------
96
- KeyError
97
- If "image_arrays" is missing in the data dictionary.
98
- ValueError
99
- If the protocol is invalid, or if no valid images are found.
100
- """
101
- if "image_arrays" not in data:
102
- raise KeyError("Expected 'image_arrays' in data. Call prepare_data_for_inference first.")
103
-
104
- image_arrays = data["image_arrays"]
105
- # Compute image dimensions from each image array.
106
- image_dims = [(img.shape[0], img.shape[1]) for img in image_arrays]
107
-
108
- # Helper function: chunk a list into sublists of length <= chunk_size.
109
- def chunk_list(lst: list, chunk_size: int) -> List[list]:
110
- return [lst[i : i + chunk_size] for i in range(0, len(lst), chunk_size)]
111
-
112
- if protocol == "grpc":
113
- logger.debug("Formatting input for gRPC Deplot model (potentially batched).")
114
- processed = []
115
- for arr in image_arrays:
116
- # Ensure each image has shape (1, H, W, C)
117
- if arr.ndim == 3:
118
- arr = np.expand_dims(arr, axis=0)
119
- arr = arr.astype(np.float32)
120
- arr /= 255.0 # Normalize to [0,1]
121
- processed.append(arr)
122
-
123
- if not processed:
124
- raise ValueError("No valid images found for gRPC formatting.")
125
-
126
- formatted_batches = []
127
- formatted_batch_data = []
128
- proc_chunks = chunk_list(processed, max_batch_size)
129
- orig_chunks = chunk_list(image_arrays, max_batch_size)
130
- dims_chunks = chunk_list(image_dims, max_batch_size)
131
-
132
- for proc_chunk, orig_chunk, dims_chunk in zip(proc_chunks, orig_chunks, dims_chunks):
133
- # Concatenate along the batch dimension to form a single input.
134
- batched_input = np.concatenate(proc_chunk, axis=0)
135
- formatted_batches.append(batched_input)
136
- formatted_batch_data.append({"image_arrays": orig_chunk, "image_dims": dims_chunk})
137
- return formatted_batches, formatted_batch_data
138
-
139
- elif protocol == "http":
140
- logger.debug("Formatting input for HTTP Deplot model (multiple messages).")
141
- if "base64_images" in data:
142
- base64_list = data["base64_images"]
143
- else:
144
- base64_list = [data["base64_image"]]
145
-
146
- formatted_batches = []
147
- formatted_batch_data = []
148
- b64_chunks = chunk_list(base64_list, max_batch_size)
149
- orig_chunks = chunk_list(image_arrays, max_batch_size)
150
- dims_chunks = chunk_list(image_dims, max_batch_size)
151
-
152
- for b64_chunk, orig_chunk, dims_chunk in zip(b64_chunks, orig_chunks, dims_chunks):
153
- payload = self._prepare_deplot_payload(
154
- base64_list=b64_chunk,
155
- max_tokens=kwargs.get("max_tokens", 500),
156
- temperature=kwargs.get("temperature", 0.5),
157
- top_p=kwargs.get("top_p", 0.9),
158
- )
159
- formatted_batches.append(payload)
160
- formatted_batch_data.append({"image_arrays": orig_chunk, "image_dims": dims_chunk})
161
- return formatted_batches, formatted_batch_data
162
-
163
- else:
164
- raise ValueError("Invalid protocol specified. Must be 'grpc' or 'http'.")
165
-
166
- def parse_output(self, response: Any, protocol: str, data: Optional[Dict[str, Any]] = None, **kwargs) -> Any:
167
- """
168
- Parse the model's inference response.
169
- """
170
- if protocol == "grpc":
171
- logger.debug("Parsing output from gRPC Deplot model (batched).")
172
- # Each batch element might be returned as a list of bytes. Combine or keep separate as needed.
173
- results = []
174
- for item in response:
175
- # If item is [b'...'], decode and join
176
- if isinstance(item, list):
177
- joined_str = " ".join(o.decode("utf-8") for o in item)
178
- results.append(joined_str)
179
- else:
180
- # single bytes or str
181
- val = item.decode("utf-8") if isinstance(item, bytes) else str(item)
182
- results.append(val)
183
- return results # Return a list of strings, one per image.
184
-
185
- elif protocol == "http":
186
- logger.debug("Parsing output from HTTP Deplot model.")
187
- return self._extract_content_from_deplot_response(response)
188
- else:
189
- raise ValueError("Invalid protocol specified. Must be 'grpc' or 'http'.")
190
-
191
- def process_inference_results(self, output: Any, protocol: str, **kwargs) -> Any:
192
- """
193
- Process inference results for the Deplot model.
194
-
195
- Parameters
196
- ----------
197
- output : Any
198
- The raw output from the model.
199
- protocol : str
200
- The protocol used for inference (gRPC or HTTP).
201
-
202
- Returns
203
- -------
204
- Any
205
- The processed inference results.
206
- """
207
-
208
- # For Deplot, the output is the chart content as a string
209
- return output
210
-
211
- @staticmethod
212
- def _prepare_deplot_payload(
213
- base64_list: list,
214
- max_tokens: int = 500,
215
- temperature: float = 0.5,
216
- top_p: float = 0.9,
217
- ) -> Dict[str, Any]:
218
- """
219
- Prepare an HTTP payload for Deplot that includes one message per image,
220
- matching the original single-image style:
221
-
222
- messages = [
223
- {
224
- "role": "user",
225
- "content": "Generate ... <img src=\"data:image/png;base64,...\" />"
226
- },
227
- {
228
- "role": "user",
229
- "content": "Generate ... <img src=\"data:image/png;base64,...\" />"
230
- },
231
- ...
232
- ]
233
-
234
- If your backend expects multiple messages in a single request, this keeps
235
- the same structure as the single-image code repeated N times.
236
- """
237
- messages = []
238
- # Note: deplot NIM currently only supports a single message per request
239
- for b64_img in base64_list:
240
- messages.append(
241
- {
242
- "role": "user",
243
- "content": (
244
- "Generate the underlying data table of the figure below: "
245
- f'<img src="data:image/png;base64,{b64_img}" />'
246
- ),
247
- }
248
- )
249
-
250
- payload = {
251
- "model": "google/deplot",
252
- "messages": messages, # multiple user messages now
253
- "max_tokens": max_tokens,
254
- "stream": False,
255
- "temperature": temperature,
256
- "top_p": top_p,
257
- }
258
- return payload
259
-
260
- @staticmethod
261
- def _extract_content_from_deplot_response(json_response: Dict[str, Any]) -> Any:
262
- """
263
- Extract content from the JSON response of a Deplot HTTP API request.
264
- The original code expected a single choice with a single textual content.
265
- """
266
- if "choices" not in json_response or not json_response["choices"]:
267
- raise RuntimeError("Unexpected response format: 'choices' key is missing or empty.")
268
-
269
- # If the service only returns one textual result, we return that one.
270
- return json_response["choices"][0]["message"]["content"]