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.
- indico_toolkit/__init__.py +24 -0
- indico_toolkit/association/__init__.py +15 -0
- indico_toolkit/association/association.py +65 -0
- indico_toolkit/association/extracted_tokens.py +106 -0
- indico_toolkit/association/line_items.py +189 -0
- indico_toolkit/association/positioning.py +301 -0
- indico_toolkit/association/splitting.py +41 -0
- indico_toolkit/auto_populate/__init__.py +3 -0
- indico_toolkit/auto_populate/populator.py +362 -0
- indico_toolkit/auto_populate/types.py +66 -0
- indico_toolkit/auto_review/__init__.py +6 -0
- indico_toolkit/auto_review/auto_review_functions.py +123 -0
- indico_toolkit/auto_review/auto_reviewer.py +62 -0
- indico_toolkit/client.py +35 -0
- indico_toolkit/errors.py +32 -0
- indico_toolkit/etloutput/__init__.py +210 -0
- indico_toolkit/etloutput/cell.py +47 -0
- indico_toolkit/etloutput/errors.py +16 -0
- indico_toolkit/etloutput/etloutput.py +110 -0
- indico_toolkit/etloutput/range.py +30 -0
- indico_toolkit/etloutput/table.py +44 -0
- indico_toolkit/etloutput/token.py +25 -0
- indico_toolkit/indico_wrapper/__init__.py +15 -0
- indico_toolkit/indico_wrapper/dataset.py +114 -0
- indico_toolkit/indico_wrapper/doc_extraction.py +79 -0
- indico_toolkit/indico_wrapper/download.py +193 -0
- indico_toolkit/indico_wrapper/indico_wrapper.py +119 -0
- indico_toolkit/indico_wrapper/modelop.py +200 -0
- indico_toolkit/indico_wrapper/reviewer.py +111 -0
- indico_toolkit/indico_wrapper/workflow.py +231 -0
- indico_toolkit/metrics/__init__.py +11 -0
- indico_toolkit/metrics/compare_ground_truth.py +123 -0
- indico_toolkit/metrics/compare_models.py +180 -0
- indico_toolkit/metrics/metrics.py +373 -0
- indico_toolkit/metrics/plotting.py +102 -0
- indico_toolkit/ocr/__init__.py +9 -0
- indico_toolkit/ocr/customocr_object.py +42 -0
- indico_toolkit/ocr/ondoc_object.py +93 -0
- indico_toolkit/ocr/standard_object.py +54 -0
- indico_toolkit/pipelines/__init__.py +3 -0
- indico_toolkit/pipelines/file_processing.py +157 -0
- indico_toolkit/polling/__init__.py +8 -0
- indico_toolkit/polling/autoreview.py +204 -0
- indico_toolkit/polling/downstream.py +145 -0
- indico_toolkit/polling/queries.py +68 -0
- indico_toolkit/py.typed +0 -0
- indico_toolkit/results/__init__.py +104 -0
- indico_toolkit/results/document.py +78 -0
- indico_toolkit/results/errors.py +4 -0
- indico_toolkit/results/model.py +60 -0
- indico_toolkit/results/normalization.py +159 -0
- indico_toolkit/results/predictionlist.py +395 -0
- indico_toolkit/results/predictions/__init__.py +88 -0
- indico_toolkit/results/predictions/box.py +58 -0
- indico_toolkit/results/predictions/citation.py +47 -0
- indico_toolkit/results/predictions/classification.py +50 -0
- indico_toolkit/results/predictions/documentextraction.py +160 -0
- indico_toolkit/results/predictions/extraction.py +35 -0
- indico_toolkit/results/predictions/formextraction.py +121 -0
- indico_toolkit/results/predictions/group.py +29 -0
- indico_toolkit/results/predictions/prediction.py +41 -0
- indico_toolkit/results/predictions/span.py +43 -0
- indico_toolkit/results/predictions/summarization.py +103 -0
- indico_toolkit/results/predictions/unbundling.py +51 -0
- indico_toolkit/results/result.py +152 -0
- indico_toolkit/results/review.py +32 -0
- indico_toolkit/results/utilities.py +73 -0
- indico_toolkit/retry.py +94 -0
- indico_toolkit/snapshots/__init__.py +3 -0
- indico_toolkit/snapshots/snapshot.py +340 -0
- indico_toolkit/structure/__init__.py +3 -0
- indico_toolkit/structure/create_structure.py +232 -0
- indico_toolkit/structure/queries.py +119 -0
- indico_toolkit/structure/utils.py +16 -0
- indico_toolkit/types/__init__.py +12 -0
- indico_toolkit/types/classification.py +92 -0
- indico_toolkit/types/extractions.py +227 -0
- indico_toolkit/types/predictions.py +28 -0
- indico_toolkit/types/workflow_object.py +133 -0
- indico_toolkit-6.14.0.dist-info/LICENSE +21 -0
- indico_toolkit-6.14.0.dist-info/METADATA +144 -0
- indico_toolkit-6.14.0.dist-info/RECORD +83 -0
- 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,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)
|