indico_toolkit 6.14.0__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 (83) hide show
  1. indico_toolkit/__init__.py +24 -0
  2. indico_toolkit/association/__init__.py +15 -0
  3. indico_toolkit/association/association.py +65 -0
  4. indico_toolkit/association/extracted_tokens.py +106 -0
  5. indico_toolkit/association/line_items.py +189 -0
  6. indico_toolkit/association/positioning.py +301 -0
  7. indico_toolkit/association/splitting.py +41 -0
  8. indico_toolkit/auto_populate/__init__.py +3 -0
  9. indico_toolkit/auto_populate/populator.py +362 -0
  10. indico_toolkit/auto_populate/types.py +66 -0
  11. indico_toolkit/auto_review/__init__.py +6 -0
  12. indico_toolkit/auto_review/auto_review_functions.py +123 -0
  13. indico_toolkit/auto_review/auto_reviewer.py +62 -0
  14. indico_toolkit/client.py +35 -0
  15. indico_toolkit/errors.py +32 -0
  16. indico_toolkit/etloutput/__init__.py +210 -0
  17. indico_toolkit/etloutput/cell.py +47 -0
  18. indico_toolkit/etloutput/errors.py +16 -0
  19. indico_toolkit/etloutput/etloutput.py +110 -0
  20. indico_toolkit/etloutput/range.py +30 -0
  21. indico_toolkit/etloutput/table.py +44 -0
  22. indico_toolkit/etloutput/token.py +25 -0
  23. indico_toolkit/indico_wrapper/__init__.py +15 -0
  24. indico_toolkit/indico_wrapper/dataset.py +114 -0
  25. indico_toolkit/indico_wrapper/doc_extraction.py +79 -0
  26. indico_toolkit/indico_wrapper/download.py +193 -0
  27. indico_toolkit/indico_wrapper/indico_wrapper.py +119 -0
  28. indico_toolkit/indico_wrapper/modelop.py +200 -0
  29. indico_toolkit/indico_wrapper/reviewer.py +111 -0
  30. indico_toolkit/indico_wrapper/workflow.py +231 -0
  31. indico_toolkit/metrics/__init__.py +11 -0
  32. indico_toolkit/metrics/compare_ground_truth.py +123 -0
  33. indico_toolkit/metrics/compare_models.py +180 -0
  34. indico_toolkit/metrics/metrics.py +373 -0
  35. indico_toolkit/metrics/plotting.py +102 -0
  36. indico_toolkit/ocr/__init__.py +9 -0
  37. indico_toolkit/ocr/customocr_object.py +42 -0
  38. indico_toolkit/ocr/ondoc_object.py +93 -0
  39. indico_toolkit/ocr/standard_object.py +54 -0
  40. indico_toolkit/pipelines/__init__.py +3 -0
  41. indico_toolkit/pipelines/file_processing.py +157 -0
  42. indico_toolkit/polling/__init__.py +8 -0
  43. indico_toolkit/polling/autoreview.py +204 -0
  44. indico_toolkit/polling/downstream.py +145 -0
  45. indico_toolkit/polling/queries.py +68 -0
  46. indico_toolkit/py.typed +0 -0
  47. indico_toolkit/results/__init__.py +104 -0
  48. indico_toolkit/results/document.py +78 -0
  49. indico_toolkit/results/errors.py +4 -0
  50. indico_toolkit/results/model.py +60 -0
  51. indico_toolkit/results/normalization.py +159 -0
  52. indico_toolkit/results/predictionlist.py +395 -0
  53. indico_toolkit/results/predictions/__init__.py +88 -0
  54. indico_toolkit/results/predictions/box.py +58 -0
  55. indico_toolkit/results/predictions/citation.py +47 -0
  56. indico_toolkit/results/predictions/classification.py +50 -0
  57. indico_toolkit/results/predictions/documentextraction.py +160 -0
  58. indico_toolkit/results/predictions/extraction.py +35 -0
  59. indico_toolkit/results/predictions/formextraction.py +121 -0
  60. indico_toolkit/results/predictions/group.py +29 -0
  61. indico_toolkit/results/predictions/prediction.py +41 -0
  62. indico_toolkit/results/predictions/span.py +43 -0
  63. indico_toolkit/results/predictions/summarization.py +103 -0
  64. indico_toolkit/results/predictions/unbundling.py +51 -0
  65. indico_toolkit/results/result.py +152 -0
  66. indico_toolkit/results/review.py +32 -0
  67. indico_toolkit/results/utilities.py +73 -0
  68. indico_toolkit/retry.py +94 -0
  69. indico_toolkit/snapshots/__init__.py +3 -0
  70. indico_toolkit/snapshots/snapshot.py +340 -0
  71. indico_toolkit/structure/__init__.py +3 -0
  72. indico_toolkit/structure/create_structure.py +232 -0
  73. indico_toolkit/structure/queries.py +119 -0
  74. indico_toolkit/structure/utils.py +16 -0
  75. indico_toolkit/types/__init__.py +12 -0
  76. indico_toolkit/types/classification.py +92 -0
  77. indico_toolkit/types/extractions.py +227 -0
  78. indico_toolkit/types/predictions.py +28 -0
  79. indico_toolkit/types/workflow_object.py +133 -0
  80. indico_toolkit-6.14.0.dist-info/LICENSE +21 -0
  81. indico_toolkit-6.14.0.dist-info/METADATA +144 -0
  82. indico_toolkit-6.14.0.dist-info/RECORD +83 -0
  83. indico_toolkit-6.14.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,373 @@
1
+ import json
2
+ from typing import Dict, List
3
+
4
+ from indico import IndicoClient
5
+
6
+ from ..errors import ToolkitInputError
7
+ from ..indico_wrapper import IndicoWrapper
8
+ from .plotting import Plotting
9
+
10
+ try:
11
+ import pandas as pd
12
+
13
+ _PANDAS_INSTALLED = True
14
+ except ImportError as error:
15
+ _PANDAS_INSTALLED = False
16
+ _IMPORT_ERROR = error
17
+
18
+
19
+ class ExtractionMetrics(IndicoWrapper):
20
+ """
21
+ Example usage:
22
+
23
+ metrics = ExtractionMetrics(client)
24
+ metrics.get_extraction_metrics(MODEL_GROUP_ID)
25
+
26
+ # get a pandas dataframe of field level results
27
+ df = metrics.get_metrics_df()
28
+ print(df.head())
29
+
30
+ # get metrics for a specific span type and/or model ID
31
+ df = metrics.get_metrics_df(span_type="exact", select_model_id=102)
32
+ print(df.head())
33
+
34
+ # write the results to a CSV (can also optionally pass span_type/model ID here as
35
+ well) metrics.to_csv("./my_metrics.pdf")
36
+
37
+ # get an interactive bar plot to visualize model improvement over time
38
+ metrics.bar_plot("./my_bar_plot.html")
39
+ """
40
+
41
+ def __init__(self, client: IndicoClient):
42
+ self.client = client
43
+ self.raw_metrics: List[dict] = None
44
+ self.included_models: List[int] = None
45
+ self.number_of_samples: Dict[int, int] = None
46
+
47
+ def get_metrics(self, model_group_id: int):
48
+ results = self.graphQL_request(METRIC_QUERY, {"modelGroupId": model_group_id})
49
+ if len(results["modelGroups"]["modelGroups"]) == 0:
50
+ raise ToolkitInputError(
51
+ f"There are no models associated with ID: {model_group_id}"
52
+ )
53
+ results = results["modelGroups"]["modelGroups"][0]["models"]
54
+ self.raw_metrics = [
55
+ m
56
+ for m in results
57
+ if isinstance(m["evaluation"]["metrics"]["classMetrics"], list)
58
+ ]
59
+ self.included_models = [model["id"] for model in self.raw_metrics]
60
+ self.number_of_samples = {
61
+ model["id"]: int(json.loads(model["modelInfo"])["number_of_examples"])
62
+ for model in self.raw_metrics
63
+ }
64
+
65
+ def get_metrics_df(
66
+ self, span_type: str = "overlap", select_model_id: int = None
67
+ ) -> "pd.DataFrame":
68
+ """
69
+ Get a dataframe of model metrics for a particular span type
70
+ Args:
71
+ span_type (str): options include 'superset', 'exact', 'overlap' or 'token'
72
+ select_model_id (int): only return metrics for a particular model
73
+ """
74
+ if not _PANDAS_INSTALLED:
75
+ raise RuntimeError(
76
+ "getting a metrics datafram requires additional dependencies: "
77
+ "`pip install indico-toolkit[metrics]`"
78
+ ) from _IMPORT_ERROR
79
+
80
+ if select_model_id:
81
+ if select_model_id not in self.included_models:
82
+ raise ToolkitInputError(
83
+ f"You must select a model ID from: {self.included_models}"
84
+ )
85
+ model_metrics = [
86
+ mod for mod in self.raw_metrics if mod["id"] == select_model_id
87
+ ]
88
+ else:
89
+ model_metrics = self.raw_metrics
90
+ cleaned_metrics = []
91
+ for model in model_metrics:
92
+ class_metrics = model["evaluation"]["metrics"]["classMetrics"]
93
+ for metric in class_metrics:
94
+ scores = [i for i in metric["metrics"] if i["spanType"] == span_type][0]
95
+ scores["field_name"] = metric["name"]
96
+ scores["model_id"] = model["id"]
97
+ scores["number_of_samples"] = self.number_of_samples[model["id"]]
98
+ cleaned_metrics.append(scores)
99
+ df = pd.DataFrame(cleaned_metrics)
100
+ return df.sort_values(by=["field_name", "model_id"], ascending=False)
101
+
102
+ def bar_plot(
103
+ self,
104
+ output_path: str,
105
+ metric: str = "f1Score",
106
+ span_type: str = "overlap",
107
+ plot_title: str = "",
108
+ ids_to_exclude: List[int] = [],
109
+ ):
110
+ """
111
+ Write an html bar plot to disc to compare model IDs within a model group. Will
112
+ also open the plot automatically in your browser, where you will interactive
113
+ functionality and the ability to download a copy as a PNG as well.
114
+
115
+ Args:
116
+ output_path (str): where you want to write plot, e.g. "./myplot.html"
117
+ span_type (str): options include 'superset', 'exact', 'overlap' or 'token'
118
+ metric (str, optional): possible values are 'precision', 'recall',
119
+ 'f1Score', 'falsePositives', 'falseNegatives', 'truePositives'.
120
+ Defaults to "f1Score".
121
+ plot_title (str, optional): Title of the plot. Defaults to "".
122
+ ids_to_exclude (List[int], optional): Model Ids to exclude from plot.
123
+ """
124
+ df = self.get_metrics_df(span_type=span_type)
125
+ if ids_to_exclude:
126
+ df = df.drop(df.loc[df["model_id"].isin(ids_to_exclude)].index)
127
+ model_ids = sorted(list(df["model_id"].unique()))
128
+ field_order = (
129
+ df.loc[df["model_id"] == model_ids[-1]]
130
+ .sort_values(by=metric)["field_name"]
131
+ .tolist()
132
+ )
133
+ df["field_name"] = (
134
+ df["field_name"].astype("category").cat.set_categories(field_order)
135
+ )
136
+ plotting = Plotting()
137
+ for model_id in model_ids:
138
+ sub_df = df.loc[df["model_id"] == model_id].copy()
139
+ sub_df.sort_values(["field_name"], inplace=True)
140
+ plotting.add_barplot_data(
141
+ sub_df["field_name"],
142
+ sub_df[metric],
143
+ name=str(model_id),
144
+ color=None,
145
+ )
146
+ plotting.define_layout(
147
+ yaxis_title=metric, legend_title="Model ID", plot_title=plot_title
148
+ )
149
+ plotting.plot(output_path)
150
+
151
+ def line_plot(
152
+ self,
153
+ output_path: str,
154
+ metric: str = "f1Score",
155
+ span_type: str = "overlap",
156
+ plot_title: str = "",
157
+ ids_to_exclude: List[int] = [],
158
+ fields_to_exclude: List[str] = [],
159
+ ):
160
+ """
161
+ Write an html line plot to disc with # of samples on x-axis, a metric on the
162
+ y-axis and each line representing a distinct field.
163
+
164
+ Will also open the plot automatically in your browser, where you will
165
+ interactive functionality and the ability to download a copy as a PNG as well.
166
+
167
+ Args:
168
+ output_path (str): where you want to write plot, e.g. "./myplot.html"
169
+ span_type (str): options include 'superset', 'exact', 'overlap' or 'token'
170
+ metric (str, optional): possible values are 'precision', 'recall',
171
+ 'f1Score', 'falsePositives', 'falseNegatives', 'truePositives'.
172
+ Defaults to "f1Score".
173
+ plot_title (str, optional): Title of the plot. Defaults to "".
174
+ ids_to_exclude (List[int], optional): Model Ids to exclude from plot.
175
+ fields_to_exclude (List[str], optional): Field Names to exclude from plot.
176
+ """
177
+ df = self.get_metrics_df(span_type=span_type)
178
+ if ids_to_exclude:
179
+ df = df.drop(df.loc[df["model_id"].isin(ids_to_exclude)].index)
180
+ if fields_to_exclude:
181
+ df = df.drop(df.loc[df["field_name"].isin(fields_to_exclude)].index)
182
+ df = df.sort_values(by=["field_name", "number_of_samples", metric])
183
+ plotting = Plotting()
184
+ for field in sorted(df["field_name"].unique()):
185
+ sub_df = df.loc[df["field_name"] == field].copy()
186
+ # ensure only one value per # of samples
187
+ sub_df = sub_df.drop_duplicates(subset=["number_of_samples"])
188
+ plotting.add_line_data(
189
+ sub_df["number_of_samples"],
190
+ sub_df[metric],
191
+ name=field,
192
+ color=None,
193
+ )
194
+ plotting.define_layout(
195
+ xaxis_title="Number of Samples",
196
+ legend_title="Field",
197
+ plot_title=plot_title,
198
+ yaxis_title=metric,
199
+ )
200
+ plotting.plot(output_path)
201
+
202
+ def to_csv(
203
+ self, output_path: str, span_type: str = "overlap", select_model_id: int = None
204
+ ):
205
+ """
206
+ Write a CSV to disc of model metrics for a particular span type and/or model ID
207
+ Args:
208
+ output_path (str): path to write CSV on your system, e.g. "./my_metrics.csv"
209
+ span_type (str): options include 'superset', 'exact', 'overlap' or 'token'
210
+ select_model_id (int): only return metrics for a particular model (OPTIONAL)
211
+ """
212
+ df = self.get_metrics_df(span_type, select_model_id)
213
+ df.to_csv(output_path, index=False)
214
+
215
+
216
+ class UnbundlingMetrics(ExtractionMetrics):
217
+ """
218
+ Example Usage:
219
+
220
+ um = UnbundlingMetrics(client)
221
+ um.get_metrics(1232)
222
+ um.line_plot(
223
+ "./my_metric_plot.html",
224
+ metric="recall",
225
+ title="Insurance Model Recall Improvement",
226
+ )
227
+ """
228
+
229
+ def get_metrics(self, model_group_id: int):
230
+ """
231
+ Collect all metrics available based on a Model Group ID for an Unbundling model
232
+
233
+ Args:
234
+ model_group_id (int): Model Group ID that you're interestd in (available
235
+ within the Explain UI)
236
+ """
237
+ results = self.graphQL_request(METRIC_QUERY, {"modelGroupId": model_group_id})
238
+ if len(results["modelGroups"]["modelGroups"]) == 0:
239
+ raise ToolkitInputError(
240
+ f"There are no models associated with ID: {model_group_id}"
241
+ )
242
+ results = results["modelGroups"]["modelGroups"][0]["models"]
243
+ raw_metrics = []
244
+ included_models = []
245
+ labeled_samples = []
246
+ for r in results:
247
+ model_info = json.loads(r["modelInfo"])
248
+ if (
249
+ "total_number_of_examples" not in model_info
250
+ or "metrics" not in model_info
251
+ ):
252
+ # some dictionaries don't come back with required fields...
253
+ continue
254
+ labeled_samples.append(model_info["total_number_of_examples"])
255
+ included_models.append(r["id"])
256
+ raw_metrics.append(model_info["metrics"]["per_class_metrics"])
257
+ self.raw_metrics = raw_metrics
258
+ self.included_models = included_models
259
+ self.number_of_samples = {
260
+ model_id: samples
261
+ for model_id, samples in zip(included_models, labeled_samples)
262
+ }
263
+
264
+ def get_metrics_df(self) -> "pd.DataFrame":
265
+ if not _PANDAS_INSTALLED:
266
+ raise RuntimeError(
267
+ "getting a metrics dataframe requires additional dependencies: "
268
+ "`pip install indico-toolkit[metrics]`"
269
+ ) from _IMPORT_ERROR
270
+
271
+ cleaned_metrics = []
272
+ for model_id, metrics in zip(self.included_models, self.raw_metrics):
273
+ for class_name in metrics:
274
+ scores = metrics[class_name]["page"]
275
+ scores["field_name"] = class_name
276
+ scores["model_id"] = model_id
277
+ scores["number_of_samples"] = self.number_of_samples[model_id]
278
+ cleaned_metrics.append(scores)
279
+ df = pd.DataFrame(cleaned_metrics)
280
+ return df.sort_values(by=["field_name", "model_id"], ascending=False)
281
+
282
+ def line_plot(
283
+ self,
284
+ output_path: str,
285
+ metric: str = "f1_score",
286
+ plot_title: str = "",
287
+ ids_to_exclude: List[int] = [],
288
+ fields_to_exclude: List[str] = [],
289
+ ):
290
+ """
291
+ Write an html line plot to disc with # of samples on x-axis, a metric on the
292
+ y-axis and each line representing a distinct field.
293
+
294
+ Will also open the plot automatically in your browser, where you will
295
+ interactive functionality and the ability to download a copy as a PNG as well.
296
+
297
+ Args:
298
+ output_path (str): where you want to write plot, e.g. "./myplot.html"
299
+ span_type (str): options include 'superset', 'exact', 'overlap' or 'token'
300
+ metric (str, optional): possible values are 'precision', 'recall',
301
+ 'f1_score', 'false_positives', 'false_negatives', 'true_positives'.
302
+ Defaults to "f1_score".
303
+ plot_title (str, optional): Title of the plot. Defaults to "".
304
+ ids_to_exclude (List[int], optional): Model Ids to exclude from plot.
305
+ fields_to_exclude (List[str], optional): Field Names to exclude from plot.
306
+ """
307
+ df = self.get_metrics_df()
308
+ if ids_to_exclude:
309
+ df = df.drop(df.loc[df["model_id"].isin(ids_to_exclude)].index)
310
+ if fields_to_exclude:
311
+ df = df.drop(df.loc[df["field_name"].isin(fields_to_exclude)].index)
312
+ df = df.sort_values(by=["field_name", "number_of_samples", metric])
313
+ plotting = Plotting()
314
+ for field in sorted(df["field_name"].unique()):
315
+ sub_df = df.loc[df["field_name"] == field].copy()
316
+ # ensure only one value per # of samples
317
+ sub_df = sub_df.drop_duplicates(subset=["number_of_samples"])
318
+ plotting.add_line_data(
319
+ sub_df["number_of_samples"],
320
+ sub_df[metric],
321
+ name=field,
322
+ color=None,
323
+ )
324
+ plotting.define_layout(
325
+ xaxis_title="Number of Samples",
326
+ legend_title="Field",
327
+ plot_title=plot_title,
328
+ yaxis_title=metric,
329
+ )
330
+ plotting.plot(output_path)
331
+
332
+ def bar_plot(self):
333
+ raise NotImplementedError(
334
+ "Bar Plot is not currently implemented for unbundling"
335
+ )
336
+
337
+ def get_extraction_metrics(self, model_group_id: int):
338
+ raise NotImplementedError("Not available for unbundling class")
339
+
340
+
341
+ METRIC_QUERY = """
342
+ query modelGroupMetrics($modelGroupId: Int!){
343
+ modelGroups(
344
+ modelGroupIds: [$modelGroupId]
345
+ ) {
346
+ modelGroups {
347
+ models {
348
+ id
349
+ modelInfo
350
+ evaluation {
351
+ ... on AnnotationEvaluation {
352
+ metrics {
353
+ classMetrics {
354
+ name
355
+ metrics {
356
+ spanType
357
+ precision
358
+ recall
359
+ f1Score
360
+ falsePositives
361
+ falseNegatives
362
+ truePositives
363
+ }
364
+ }
365
+ retrainForMetrics
366
+ }
367
+ }
368
+ }
369
+ }
370
+ }
371
+ }
372
+ }
373
+ """
@@ -0,0 +1,102 @@
1
+ from typing import Iterable, Union
2
+
3
+ try:
4
+ import plotly as py
5
+ import plotly.graph_objects as go
6
+
7
+ _PLOTLY_INSTALLED = True
8
+ except ImportError as error:
9
+ _PLOTLY_INSTALLED = False
10
+ _IMPORT_ERROR = error
11
+
12
+
13
+ class Plotting:
14
+ def __init__(self):
15
+ if not _PLOTLY_INSTALLED:
16
+ raise RuntimeError(
17
+ "plotting metrics requires additional dependencies: "
18
+ "`pip install indico-toolkit[metrics]`"
19
+ ) from _IMPORT_ERROR
20
+
21
+ self._plot_data = []
22
+ self.layout = go.Layout()
23
+
24
+ def add_barplot_data(
25
+ self,
26
+ x_data: Iterable[str],
27
+ y_data: Iterable[Union[int, float]],
28
+ name: str = "",
29
+ color: str = "blue",
30
+ ):
31
+ """
32
+ Add a bar plot object to your data.
33
+ Args:
34
+ x_data (Iterable[str]): Values/Groups that will go on X axis
35
+ y_data (Iterable[Union[int, float]]): Values that will be plotted on y axis
36
+ name (str, optional): If you have multiple bar plot groups, the distinct
37
+ name for this group, e.g. "Model ID: 121". Defaults to "".
38
+ color (str, optional): Choose a color, can be css name or rgb like
39
+ 'rgb(31, 119, 180)'. Defaults to "blue".
40
+ """
41
+ self._plot_data.append(
42
+ go.Bar(x=x_data, y=y_data, name=name, marker=dict(color=color))
43
+ )
44
+
45
+ def add_line_data(
46
+ self,
47
+ x_data: Iterable[str],
48
+ y_data: Iterable[Union[int, float]],
49
+ name: str = "",
50
+ color: str = "blue",
51
+ ):
52
+ """
53
+ Add a bar plot object to your data.
54
+ Args:
55
+ x_data (Iterable[str]): Values/Groups that will go on X axis
56
+ y_data (Iterable[Union[int, float]]): Values that will be plotted on y axis
57
+ name (str, optional): If you have multiple line plot groups, the distinct
58
+ name for this group, e.g. "Model ID: 121". Defaults to "".
59
+ color (str, optional): Choose a color, can be css name or rgb like
60
+ 'rgb(31, 119, 180)'. Defaults to "blue".
61
+ """
62
+ self._plot_data.append(
63
+ go.Scatter(x=x_data, y=y_data, name=name, marker=dict(color=color))
64
+ )
65
+
66
+ def define_layout(
67
+ self,
68
+ yaxis_title: str = "",
69
+ legend_title: str = "",
70
+ plot_title: str = "",
71
+ xaxis_title: str = "",
72
+ ):
73
+ """
74
+ Add labels to your visualization
75
+ Args:
76
+ yaxis_title (str, optional): Title for the y axis, e.g. "F1 Score".
77
+ Defaults to "".
78
+ xaxis_title (str, optional): Title for the x axis. Defaults to "".
79
+ legend_title (str, optional): A title for the plot's legend
80
+ e.g. "Model IDs". Defaults to "".
81
+ plot_title (str, optional): A title above the plot. Defaults to "".
82
+ """
83
+ self.layout = go.Layout(
84
+ title=plot_title,
85
+ yaxis=dict(title=yaxis_title),
86
+ xaxis=dict(title=xaxis_title),
87
+ legend=dict(title=legend_title),
88
+ )
89
+
90
+ def plot(self, path_to_write_plot: str):
91
+ """
92
+ Write an html plot to disc. Will also open the plot automatically in your
93
+ browser, where you will interactive functionality and the ability to download a
94
+ copy as a PNG as well.
95
+
96
+ Args:
97
+ path_to_write_plot (str): where you want to write plot, e.g. "./myplot.html"
98
+ """
99
+ py.offline.plot(
100
+ {"data": self._plot_data, "layout": self.layout},
101
+ filename=path_to_write_plot,
102
+ )
@@ -0,0 +1,9 @@
1
+ from .customocr_object import CustomOcr
2
+ from .ondoc_object import OnDoc
3
+ from .standard_object import StandardOcr
4
+
5
+ __all__ = (
6
+ "CustomOcr",
7
+ "OnDoc",
8
+ "StandardOcr",
9
+ )
@@ -0,0 +1,42 @@
1
+ from typing import List, Union
2
+
3
+
4
+ class CustomOcr:
5
+ """
6
+ CustomOcr is a helper class for the raw preset config OCR results. Enables easy
7
+ extraction of full text and page-level text.
8
+ """
9
+
10
+ def __init__(self, customocr: Union[List[dict], dict]):
11
+ """
12
+ customocr Union[List[dict], dict]: result object from
13
+ indico.queries.DocumentExtraction
14
+ """
15
+ self.customocr = customocr
16
+
17
+ @property
18
+ def full_text(self) -> str:
19
+ """
20
+ Return full document text as string
21
+ """
22
+ if isinstance(self.customocr, dict) and "text" in self.customocr:
23
+ return self.customocr["text"]
24
+ elif isinstance(self.customocr, dict) and "pages" in self.customocr:
25
+ if "text" in self.customocr["pages"][0]:
26
+ return "\n".join(page["text"] for page in self.customocr["pages"])
27
+ elif isinstance(self.customocr, list) and "pages" in self.customocr[0]:
28
+ if "text" in self.customocr[0]["pages"][0]:
29
+ return "\n".join(page["pages"][0]["text"] for page in self.customocr)
30
+ raise RuntimeError("JSON configuration setting does not have full text.")
31
+
32
+ @property
33
+ def page_texts(self) -> List[str]:
34
+ """
35
+ Return list of page-level text
36
+ """
37
+ if isinstance(self.customocr, dict) and "pages" in self.customocr:
38
+ return [page["text"] for page in self.customocr["pages"]]
39
+ elif isinstance(self.customocr, list) and "pages" in self.customocr[0]:
40
+ if "text" in self.customocr[0]["pages"][0]:
41
+ return [page["pages"][0]["text"] for page in self.customocr]
42
+ raise RuntimeError("JSON configuration setting does not have page-level text.")
@@ -0,0 +1,93 @@
1
+ import statistics
2
+ from typing import List
3
+
4
+
5
+ class OnDoc:
6
+ """
7
+ OnDoc is a helper class for the raw "ondocument" preset confid OCR result. Enables
8
+ easy extraction of common datapoints into usable objects. "ondocument" is the
9
+ default extraction config on the Indico platform.
10
+ """
11
+
12
+ def __init__(self, ondoc: List[dict]):
13
+ """
14
+ ondoc {List[dict]}: ondocument result object from
15
+ indico.queries.DocumentExtraction
16
+ """
17
+ self.ondoc = ondoc
18
+
19
+ @property
20
+ def full_text(self) -> str:
21
+ """
22
+ Return full document text as string
23
+ """
24
+ return "\n".join(page["pages"][0]["text"] for page in self.ondoc)
25
+
26
+ @property
27
+ def page_texts(self) -> List[str]:
28
+ """
29
+ Return list of page-level text
30
+ """
31
+ return [page["pages"][0]["text"] for page in self.ondoc]
32
+
33
+ @property
34
+ def page_results(self) -> List[dict]:
35
+ """
36
+ Return list of page-level dictionary result objects
37
+ """
38
+ return [page["pages"][0] for page in self.ondoc]
39
+
40
+ @property
41
+ def block_texts(self) -> List[str]:
42
+ """
43
+ Return list of block-level text
44
+ """
45
+ return [block["text"] for page in self.ondoc for block in page["blocks"]]
46
+
47
+ @property
48
+ def token_objects(self) -> List[dict]:
49
+ """
50
+ Return list of all token objects
51
+ """
52
+ return [token for page in self.ondoc for token in page["tokens"]]
53
+
54
+ @property
55
+ def total_pages(self) -> int:
56
+ return len(self.ondoc)
57
+
58
+ @property
59
+ def total_characters(self) -> int:
60
+ return len(self.full_text)
61
+
62
+ @property
63
+ def total_tokens(self) -> int:
64
+ return len(self.full_text.replace("\n", " ").split())
65
+
66
+ @property
67
+ def page_heights_and_widths(self) -> List[dict]:
68
+ return [i["pages"][0]["size"] for i in self.ondoc]
69
+
70
+ def ocr_confidence(self, metric="mean") -> float:
71
+ """
72
+ Return the OCR confidence (scale: 0 - 100) for all characters in the document
73
+
74
+ metric {str}: options are "mean" or "median"
75
+ """
76
+ if metric not in ("mean", "median"):
77
+ raise RuntimeError(
78
+ f"Metric value must be either mean or median, not '{metric}'"
79
+ )
80
+
81
+ if "confidence" not in self.ondoc[0]["chars"][0].keys():
82
+ raise RuntimeError(
83
+ "You are likely using an old SDK version, confidence is not included"
84
+ )
85
+
86
+ confidence = [
87
+ character["confidence"]
88
+ for page in self.ondoc
89
+ for character in page["chars"]
90
+ ]
91
+ if metric == "mean":
92
+ return statistics.mean(confidence)
93
+ return statistics.median(confidence)
@@ -0,0 +1,54 @@
1
+ from typing import List
2
+
3
+
4
+ class StandardOcr:
5
+ """
6
+ StandardOcr is a helper class for the raw "standard" preset config OCR result.
7
+ Enables easy extraction of common datapoints into usable objects.
8
+ """
9
+
10
+ def __init__(self, standardocr: dict):
11
+ """
12
+ standardocr dict: standard result object from indico.queries.DocumentExtraction
13
+ """
14
+ self.standardocr = standardocr
15
+
16
+ @property
17
+ def full_text(self) -> str:
18
+ """
19
+ Return full document text as string
20
+ """
21
+ return self.standardocr["text"]
22
+
23
+ @property
24
+ def page_texts(self) -> List[str]:
25
+ """
26
+ Return list of page-level text
27
+ """
28
+ return [page["text"] for page in self.standardocr["pages"]]
29
+
30
+ @property
31
+ def page_results(self) -> List[dict]:
32
+ """
33
+ Return list of page-level dictionary result objects
34
+ """
35
+ return [page for page in self.standardocr["pages"]]
36
+
37
+ @property
38
+ def block_texts(self) -> List[str]:
39
+ """
40
+ Return list of block-level text
41
+ """
42
+ return [
43
+ block["text"]
44
+ for page in self.standardocr["pages"]
45
+ for block in page["blocks"]
46
+ ]
47
+
48
+ @property
49
+ def total_pages(self) -> int:
50
+ return len(self.standardocr["pages"])
51
+
52
+ @property
53
+ def total_characters(self) -> int:
54
+ return len(self.full_text)
@@ -0,0 +1,3 @@
1
+ from .file_processing import FileProcessing
2
+
3
+ __all__ = ("FileProcessing",)