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,340 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from json import JSONDecodeError
|
|
4
|
+
from typing import List, Tuple, Union
|
|
5
|
+
|
|
6
|
+
from ..errors import ToolkitInputError, ToolkitInstantiationError
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import pandas as pd
|
|
10
|
+
|
|
11
|
+
_PANDAS_INSTALLED = True
|
|
12
|
+
except ImportError as error:
|
|
13
|
+
_PANDAS_INSTALLED = False
|
|
14
|
+
_IMPORT_ERROR = error
|
|
15
|
+
|
|
16
|
+
# TODO: add functionality for classification snapshots
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Snapshot:
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
path_to_snapshot: str,
|
|
23
|
+
text_col: str = None,
|
|
24
|
+
label_col: str = None,
|
|
25
|
+
file_name_col: str = None,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Combine and manipulate a Teach Task's snapshot.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
path_to_snapshot (str): path to Snapshot CSV
|
|
32
|
+
text_col (str, optional): Column with text, will be inferred if not
|
|
33
|
+
provided. Defaults to None.
|
|
34
|
+
label_col (str, optional): Column with labels, will be inferred if not
|
|
35
|
+
provided. Defaults to None.
|
|
36
|
+
file_name_col (str, optional): Column with file names, will be inferred if
|
|
37
|
+
not provided. Defaults to None.
|
|
38
|
+
"""
|
|
39
|
+
if not _PANDAS_INSTALLED:
|
|
40
|
+
raise RuntimeError(
|
|
41
|
+
"snapshots require additional dependencies: "
|
|
42
|
+
"`pip install indico-toolkit[snapshots]`"
|
|
43
|
+
) from _IMPORT_ERROR
|
|
44
|
+
|
|
45
|
+
self.path_to_snapshot = path_to_snapshot
|
|
46
|
+
self.df: pd.DataFrame = pd.read_csv(path_to_snapshot)
|
|
47
|
+
self.label_col = label_col
|
|
48
|
+
if label_col is None:
|
|
49
|
+
self._infer_label_col()
|
|
50
|
+
self._convert_col_from_json()
|
|
51
|
+
self.text_col = text_col
|
|
52
|
+
if text_col is None:
|
|
53
|
+
self._infer_text_col()
|
|
54
|
+
self.file_name_col = file_name_col
|
|
55
|
+
if file_name_col is None:
|
|
56
|
+
self._infer_file_name_col()
|
|
57
|
+
|
|
58
|
+
def remove_extraction_labels(self, labels_to_remove: List[str]):
|
|
59
|
+
"""
|
|
60
|
+
Remove unwanted labels from label column
|
|
61
|
+
Args:
|
|
62
|
+
labels_to_remove (List[str]): the labels you'd like to remove
|
|
63
|
+
"""
|
|
64
|
+
updated_predictions = []
|
|
65
|
+
for label_set in self.df[self.label_col]:
|
|
66
|
+
label_set.update(
|
|
67
|
+
{
|
|
68
|
+
"targets": [
|
|
69
|
+
i
|
|
70
|
+
for i in label_set["targets"]
|
|
71
|
+
if i["label"] not in labels_to_remove
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
updated_predictions.append(label_set)
|
|
76
|
+
self.df[self.label_col] = updated_predictions
|
|
77
|
+
|
|
78
|
+
def standardize_column_names(
|
|
79
|
+
self,
|
|
80
|
+
text_col: str = "source",
|
|
81
|
+
label_col: str = "target",
|
|
82
|
+
file_name_col: str = "file_name",
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
To allow merging/appending snapshots, standardize the key column headers
|
|
86
|
+
"""
|
|
87
|
+
self.df.rename(
|
|
88
|
+
mapper={
|
|
89
|
+
self.file_name_col: file_name_col,
|
|
90
|
+
self.text_col: text_col,
|
|
91
|
+
self.label_col: label_col,
|
|
92
|
+
},
|
|
93
|
+
inplace=True,
|
|
94
|
+
errors="raise",
|
|
95
|
+
axis=1,
|
|
96
|
+
)
|
|
97
|
+
self.file_name_col = file_name_col
|
|
98
|
+
self.label_col = label_col
|
|
99
|
+
self.text_col = text_col
|
|
100
|
+
|
|
101
|
+
def drop_unneeded_columns(self, columns_to_drop: List[str] = None):
|
|
102
|
+
"""
|
|
103
|
+
Keep only text, labels, and file name columns or specify columns to drop by
|
|
104
|
+
passing them in as a list.
|
|
105
|
+
"""
|
|
106
|
+
if columns_to_drop:
|
|
107
|
+
self.df.drop(labels=columns_to_drop, axis=1, inplace=True)
|
|
108
|
+
else:
|
|
109
|
+
self.df = self.df[[self.label_col, self.text_col, self.file_name_col]]
|
|
110
|
+
|
|
111
|
+
def append(self, snap_to_add: "Snapshot"):
|
|
112
|
+
"""
|
|
113
|
+
Append the rows from another Snapshot to this snapshot. Ensure column names are
|
|
114
|
+
standardized beforehand.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
snap_to_add (Snapshot): Snapshot to add
|
|
118
|
+
"""
|
|
119
|
+
self._assert_key_column_names_match(snap_to_add)
|
|
120
|
+
self.df = pd.concat([self.df, snap_to_add.df], ignore_index=True)
|
|
121
|
+
|
|
122
|
+
def get_extraction_label_names(self):
|
|
123
|
+
"""
|
|
124
|
+
Return a list of all labeled classes in an extraction snapshot.
|
|
125
|
+
"""
|
|
126
|
+
label_column = self.df[self.label_col].tolist()
|
|
127
|
+
label_set = set()
|
|
128
|
+
for labels in label_column:
|
|
129
|
+
for label in labels["targets"]:
|
|
130
|
+
label_set.add(label["label"])
|
|
131
|
+
return sorted(list(label_set))
|
|
132
|
+
|
|
133
|
+
def merge_by_file_name(
|
|
134
|
+
self,
|
|
135
|
+
snap_to_merge: "Snapshot",
|
|
136
|
+
ensure_identical_text: bool = True,
|
|
137
|
+
):
|
|
138
|
+
"""
|
|
139
|
+
Merge extraction labels for identical files. Merge is 'left' and file names /
|
|
140
|
+
rows only present in 'snap_to_merge' are excluded.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
snap_to_merge (Snapshot): Snapshot you want to merge
|
|
144
|
+
ensure_identical_text (bool, optional): Require document text to be
|
|
145
|
+
identical for common file name. Defaults to True.
|
|
146
|
+
"""
|
|
147
|
+
self._assert_key_column_names_match(snap_to_merge)
|
|
148
|
+
suffix = "_to_merge"
|
|
149
|
+
merged = pd.merge(
|
|
150
|
+
self.df,
|
|
151
|
+
snap_to_merge.df,
|
|
152
|
+
how="left",
|
|
153
|
+
on=self.file_name_col,
|
|
154
|
+
suffixes=(None, suffix),
|
|
155
|
+
)
|
|
156
|
+
updated_labels = []
|
|
157
|
+
unmatched_files = []
|
|
158
|
+
for _, row in merged.iterrows():
|
|
159
|
+
to_merge_label = f"{self.label_col}{suffix}"
|
|
160
|
+
labels = row[self.label_col]
|
|
161
|
+
if isinstance(row[to_merge_label], (dict, list)):
|
|
162
|
+
if ensure_identical_text:
|
|
163
|
+
try:
|
|
164
|
+
assert row[self.text_col] == row[f"{self.text_col}{suffix}"]
|
|
165
|
+
except AssertionError:
|
|
166
|
+
raise ToolkitInputError(
|
|
167
|
+
f"Text from {row[self.file_name_col]} doesn't match"
|
|
168
|
+
)
|
|
169
|
+
labels["targets"].extend(row[to_merge_label]["targets"])
|
|
170
|
+
else:
|
|
171
|
+
unmatched_files.append(row[self.file_name_col])
|
|
172
|
+
updated_labels.append(labels)
|
|
173
|
+
if unmatched_files:
|
|
174
|
+
print(f"No new labels were added for these files: {unmatched_files}")
|
|
175
|
+
merged[self.label_col] = updated_labels
|
|
176
|
+
self.df = merged.drop([i for i in merged.columns if i.endswith(suffix)], axis=1)
|
|
177
|
+
|
|
178
|
+
def to_csv(
|
|
179
|
+
self,
|
|
180
|
+
path: str,
|
|
181
|
+
only_keep_key_columns: bool = True,
|
|
182
|
+
convert_label_col_to_json: bool = True,
|
|
183
|
+
):
|
|
184
|
+
|
|
185
|
+
if only_keep_key_columns:
|
|
186
|
+
self.drop_unneeded_columns()
|
|
187
|
+
if convert_label_col_to_json:
|
|
188
|
+
self._convert_col_to_json(self.label_col)
|
|
189
|
+
self.df.to_csv(path, index=False)
|
|
190
|
+
|
|
191
|
+
def get_all_labeled_text(
|
|
192
|
+
self, label_name: str, return_per_document: bool = False
|
|
193
|
+
) -> Union[List[List[str]], List[str]]:
|
|
194
|
+
"""
|
|
195
|
+
Get all of the text that was tagged for a given label
|
|
196
|
+
Args:
|
|
197
|
+
label_name (str): name of the label
|
|
198
|
+
return_per_document (bool, optional): return a list per document or one list
|
|
199
|
+
with everything. Defaults to False.
|
|
200
|
+
"""
|
|
201
|
+
available_labels = self.get_extraction_label_names()
|
|
202
|
+
if label_name not in available_labels:
|
|
203
|
+
raise ToolkitInputError(
|
|
204
|
+
f"'{label_name}' not present among available labels: {available_labels}"
|
|
205
|
+
)
|
|
206
|
+
all_labeled_text = []
|
|
207
|
+
for text, labels in zip(self.df[self.text_col], self.df[self.label_col]):
|
|
208
|
+
text_found = []
|
|
209
|
+
for lab in labels["targets"]:
|
|
210
|
+
if lab["label"] == label_name:
|
|
211
|
+
for span in lab["spans"]:
|
|
212
|
+
text_found.append(text[span["start"] : span["end"]])
|
|
213
|
+
if return_per_document:
|
|
214
|
+
all_labeled_text.append(text_found)
|
|
215
|
+
else:
|
|
216
|
+
all_labeled_text.extend(text_found)
|
|
217
|
+
return all_labeled_text
|
|
218
|
+
|
|
219
|
+
def split_and_write_to_csv(
|
|
220
|
+
self, output_dir: str, num_splits: int = 5, output_base_name: str = "split_num"
|
|
221
|
+
) -> None:
|
|
222
|
+
"""
|
|
223
|
+
For large files that may face memory constraints, split the file into multiple
|
|
224
|
+
CSVs and write to disk.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
output_dir (str): Location where split files will be written.
|
|
228
|
+
num_splits (int, optional): The number of splits of the CSV. Defaults to 5.
|
|
229
|
+
output_base_name (str, optional): The base name of the split CSVs:
|
|
230
|
+
Defaults to "split_num". So files would be "split_num_1.csv",
|
|
231
|
+
"split_num_2.csv", etc.
|
|
232
|
+
"""
|
|
233
|
+
split_length = self.number_of_samples // num_splits
|
|
234
|
+
rows_taken = 0
|
|
235
|
+
for i in range(1, num_splits + 1):
|
|
236
|
+
if i == num_splits:
|
|
237
|
+
sub_df = self.df.iloc[rows_taken:]
|
|
238
|
+
else:
|
|
239
|
+
sub_df = self.df.iloc[rows_taken : rows_taken + split_length]
|
|
240
|
+
split_file_loc = os.path.join(output_dir, f"{output_base_name}_{i}.csv")
|
|
241
|
+
sub_df.to_csv(split_file_loc, index=False)
|
|
242
|
+
rows_taken += split_length
|
|
243
|
+
print(f"Wrote split {i} of {num_splits}: {split_file_loc}")
|
|
244
|
+
|
|
245
|
+
def update_label_col_format(self, task_type: str = "annotation"):
|
|
246
|
+
"""
|
|
247
|
+
Modifies label column to updated format with spans.
|
|
248
|
+
Args:
|
|
249
|
+
task_type (str): Task type to specifiy df as
|
|
250
|
+
(annotation = extraction, classification = classification)
|
|
251
|
+
|
|
252
|
+
Note: page_num in the span is currently set to None.
|
|
253
|
+
"""
|
|
254
|
+
updated_column = []
|
|
255
|
+
for label_set in self.df[self.label_col]:
|
|
256
|
+
updated_targets = []
|
|
257
|
+
for span in label_set:
|
|
258
|
+
updated_span = {
|
|
259
|
+
"label": span["label"],
|
|
260
|
+
"spans": [
|
|
261
|
+
{"start": span["start"], "page_num": None, "end": span["end"]}
|
|
262
|
+
],
|
|
263
|
+
}
|
|
264
|
+
updated_targets.append(updated_span)
|
|
265
|
+
|
|
266
|
+
updated_column.append({"task_type": task_type, "targets": updated_targets})
|
|
267
|
+
self.df[self.label_col] = updated_column
|
|
268
|
+
|
|
269
|
+
def __eq__(self, other: "Snapshot"):
|
|
270
|
+
"""
|
|
271
|
+
Check if two snapshots can be merged based on common column names
|
|
272
|
+
"""
|
|
273
|
+
return (
|
|
274
|
+
self.label_col == other.label_col
|
|
275
|
+
and self.text_col == other.text_col
|
|
276
|
+
and self.file_name_col == other.file_name_col
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def _convert_col_from_json(self):
|
|
280
|
+
try:
|
|
281
|
+
self.df[self.label_col] = self.df[self.label_col].apply(json.loads)
|
|
282
|
+
except (TypeError, JSONDecodeError):
|
|
283
|
+
if isinstance(self.df[self.label_col].iloc[0], list):
|
|
284
|
+
return # json column already converted
|
|
285
|
+
raise ToolkitInputError(
|
|
286
|
+
f"{self.label_col} doesn't contain valid extraction labels"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
def _assert_key_column_names_match(self, snapshot: "Snapshot"):
|
|
290
|
+
try:
|
|
291
|
+
assert self == snapshot
|
|
292
|
+
except AssertionError:
|
|
293
|
+
raise ToolkitInputError(
|
|
294
|
+
f"Column names don't match for this Snapshot: {snapshot}"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
def _convert_col_to_json(self, column: str):
|
|
298
|
+
self.df[column] = self.df[column].apply(json.dumps)
|
|
299
|
+
|
|
300
|
+
def _infer_text_col(self):
|
|
301
|
+
if "text" in self.df.columns:
|
|
302
|
+
self.text_col = "text"
|
|
303
|
+
elif "document" in self.df.columns:
|
|
304
|
+
self.text_col = "document"
|
|
305
|
+
elif "source" in self.df.columns:
|
|
306
|
+
self.text_col = "source"
|
|
307
|
+
else:
|
|
308
|
+
error_message = self._format_column_error_message("text_col")
|
|
309
|
+
raise ToolkitInstantiationError(error_message)
|
|
310
|
+
|
|
311
|
+
def _infer_label_col(self):
|
|
312
|
+
question_col = [
|
|
313
|
+
col for col in self.df.columns if "question" in col or "target" in col
|
|
314
|
+
]
|
|
315
|
+
if len(question_col) != 1:
|
|
316
|
+
error_message = self._format_column_error_message("label_col")
|
|
317
|
+
raise ToolkitInstantiationError(error_message)
|
|
318
|
+
self.label_col = question_col[0]
|
|
319
|
+
|
|
320
|
+
def _infer_file_name_col(self):
|
|
321
|
+
file_name_col = [col for col in self.df.columns if "file_name" in col]
|
|
322
|
+
if len(file_name_col) != 1:
|
|
323
|
+
error_message = self._format_column_error_message("file_name_col")
|
|
324
|
+
raise ToolkitInstantiationError(error_message)
|
|
325
|
+
self.file_name_col = file_name_col[0]
|
|
326
|
+
|
|
327
|
+
def _format_column_error_message(self, column):
|
|
328
|
+
column_list = list(self.df.columns)
|
|
329
|
+
return f"You must set attribute '{column}' from {column_list}"
|
|
330
|
+
|
|
331
|
+
def __repr__(self):
|
|
332
|
+
return f"Snapshot, label_col: {self.label_col}, loc: {self.path_to_snapshot}"
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def number_of_samples(self) -> int:
|
|
336
|
+
return self.df.shape[0]
|
|
337
|
+
|
|
338
|
+
@property
|
|
339
|
+
def shape(self) -> Tuple[int, int]:
|
|
340
|
+
return self.df.shape
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import tempfile
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from indico.queries import (
|
|
8
|
+
AddModelGroupComponent,
|
|
9
|
+
CreateDataset,
|
|
10
|
+
CreateWorkflow,
|
|
11
|
+
GetDataset,
|
|
12
|
+
GetWorkflow,
|
|
13
|
+
NewLabelsetArguments,
|
|
14
|
+
)
|
|
15
|
+
from indico.types import (
|
|
16
|
+
Dataset,
|
|
17
|
+
OcrEngine,
|
|
18
|
+
TableReadOrder,
|
|
19
|
+
Workflow,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from ..errors import ToolkitInputError
|
|
23
|
+
from .queries import GetExampleIds, GetTeachDetails, LabelTeachTask
|
|
24
|
+
from .utils import ModelTaskType
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Structure:
|
|
28
|
+
def __init__(self, client):
|
|
29
|
+
self.client = client
|
|
30
|
+
|
|
31
|
+
def create_dataset(
|
|
32
|
+
self,
|
|
33
|
+
dataset_name: str,
|
|
34
|
+
files_to_upload: List[str],
|
|
35
|
+
read_api: bool = True,
|
|
36
|
+
**kwargs,
|
|
37
|
+
) -> Dataset:
|
|
38
|
+
"""
|
|
39
|
+
Creates a dataset from a list of files.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
name_of_dataset (str): Name of the created dataset
|
|
43
|
+
file_path (str): Path of the file to copy.
|
|
44
|
+
read_api (bool, optional): OCR Engine used for the dataset.
|
|
45
|
+
Defaults to True=READ_API / False=OMNIPAGE
|
|
46
|
+
Kwargs:
|
|
47
|
+
Advanced OCR settings
|
|
48
|
+
"""
|
|
49
|
+
read_api_settings = {
|
|
50
|
+
"auto_rotate": True,
|
|
51
|
+
"single_column": False,
|
|
52
|
+
"upscale_images": True,
|
|
53
|
+
"languages": ["ENG"],
|
|
54
|
+
}
|
|
55
|
+
omnipage_settings = {
|
|
56
|
+
**read_api_settings,
|
|
57
|
+
"force_render": True,
|
|
58
|
+
"native_layout": False,
|
|
59
|
+
"native_pdf": False,
|
|
60
|
+
"table_read_order": "row",
|
|
61
|
+
}
|
|
62
|
+
for arg in kwargs.keys():
|
|
63
|
+
if read_api:
|
|
64
|
+
read_api_settings.update({arg: kwargs[arg]})
|
|
65
|
+
else:
|
|
66
|
+
if arg == "table_read_order" and kwargs[arg] not in ["row", "column"]:
|
|
67
|
+
raise ToolkitInputError(
|
|
68
|
+
f"Keyword argument {arg} got an unexpected value of "
|
|
69
|
+
f"{kwargs[arg]}, expected value of either 'row' or 'column'"
|
|
70
|
+
)
|
|
71
|
+
omnipage_settings.update({arg: kwargs[arg]})
|
|
72
|
+
|
|
73
|
+
ocr_engine = OcrEngine.READAPI if read_api else OcrEngine.OMNIPAGE
|
|
74
|
+
if read_api:
|
|
75
|
+
ocr_settings = {"read_api_ocr_options": read_api_settings}
|
|
76
|
+
else:
|
|
77
|
+
omnipage_settings["table_read_order"] = (
|
|
78
|
+
TableReadOrder.ROW
|
|
79
|
+
if omnipage_settings["table_read_order"] == "row"
|
|
80
|
+
else TableReadOrder.COLUMN
|
|
81
|
+
)
|
|
82
|
+
ocr_settings = {"omnipage_ocr_options": omnipage_settings}
|
|
83
|
+
|
|
84
|
+
dataset = self.client.call(
|
|
85
|
+
CreateDataset(
|
|
86
|
+
dataset_name,
|
|
87
|
+
files=files_to_upload,
|
|
88
|
+
dataset_type="DOCUMENT",
|
|
89
|
+
ocr_engine=ocr_engine,
|
|
90
|
+
**ocr_settings,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
print(f"Dataset created with ID: {dataset.id}")
|
|
94
|
+
return dataset
|
|
95
|
+
|
|
96
|
+
def create_duplicate_dataset(
|
|
97
|
+
self,
|
|
98
|
+
dataset_name: str,
|
|
99
|
+
file_path: str,
|
|
100
|
+
times_to_copy_files=55,
|
|
101
|
+
read_api=True,
|
|
102
|
+
**kwargs,
|
|
103
|
+
) -> Dataset:
|
|
104
|
+
"""
|
|
105
|
+
Creates a dataset w/ duplicate instances of 1 file, historically used to create
|
|
106
|
+
a spoofed demo.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
file_path (str): Path of the file to copy.
|
|
110
|
+
name_of_dataset (str): Name of the created dataset
|
|
111
|
+
times_to_copy_files (int, optional): Amount of times to copy the file.
|
|
112
|
+
Defaults to 55.
|
|
113
|
+
read_api (bool, optional): OCR Engine used for the dataset.
|
|
114
|
+
Defaults to True=READ_API / False=OMNIPAGE
|
|
115
|
+
Kwargs:
|
|
116
|
+
Advanced OCR settings
|
|
117
|
+
"""
|
|
118
|
+
tempdir = tempfile.TemporaryDirectory()
|
|
119
|
+
try:
|
|
120
|
+
files_to_add = []
|
|
121
|
+
for i in range(times_to_copy_files):
|
|
122
|
+
fpath = os.path.join(tempdir.name, f"sample_{i+1}.pdf")
|
|
123
|
+
shutil.copy(file_path, fpath)
|
|
124
|
+
files_to_add.append(fpath)
|
|
125
|
+
self.create_dataset(
|
|
126
|
+
dataset_name=dataset_name,
|
|
127
|
+
files_to_upload=files_to_add,
|
|
128
|
+
read_api=read_api,
|
|
129
|
+
**kwargs,
|
|
130
|
+
)
|
|
131
|
+
except Exception as e:
|
|
132
|
+
print(e)
|
|
133
|
+
finally:
|
|
134
|
+
tempdir.cleanup()
|
|
135
|
+
|
|
136
|
+
def create_workflow(self, name: str, dataset_id: int) -> Workflow:
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
name (str): Name of the workflow to be created
|
|
141
|
+
dataset_id (int): Dataset ID for the newly created workflow
|
|
142
|
+
"""
|
|
143
|
+
workflow = self.client.call(CreateWorkflow(name=name, dataset_id=dataset_id))
|
|
144
|
+
print(f"Workflow created with ID: {workflow.id}")
|
|
145
|
+
return workflow
|
|
146
|
+
|
|
147
|
+
def add_teach_task(
|
|
148
|
+
self,
|
|
149
|
+
task_name: str,
|
|
150
|
+
labelset_name: str,
|
|
151
|
+
target_names: List[str],
|
|
152
|
+
dataset_id: int,
|
|
153
|
+
workflow_id: int,
|
|
154
|
+
model_type: str = "annotation",
|
|
155
|
+
data_column: str = "document",
|
|
156
|
+
prev_comp_id: int = None,
|
|
157
|
+
**kwargs,
|
|
158
|
+
) -> Workflow:
|
|
159
|
+
"""
|
|
160
|
+
Args:
|
|
161
|
+
task_name (str): Teach task name
|
|
162
|
+
labelset_name (str): Name for created labelset
|
|
163
|
+
target_names (List[str]): List of target (label) names
|
|
164
|
+
dataset_id (int): Dataset ID.
|
|
165
|
+
workflow_id (int): Workflow ID.
|
|
166
|
+
model_type (str, optional): Defaults to annotation.
|
|
167
|
+
Available model types:
|
|
168
|
+
classification
|
|
169
|
+
annotation
|
|
170
|
+
classification_unbundling
|
|
171
|
+
data_column (str, optional): Defaults to "document".
|
|
172
|
+
Kwargs:
|
|
173
|
+
Advanced model training options
|
|
174
|
+
Returns:
|
|
175
|
+
Updated Workflow with newly added model group component.
|
|
176
|
+
"""
|
|
177
|
+
workflow = self.client.call(GetWorkflow(workflow_id))
|
|
178
|
+
dataset = self.client.call(GetDataset(dataset_id))
|
|
179
|
+
model_map = {
|
|
180
|
+
"classification": ModelTaskType.CLASSIFICATION,
|
|
181
|
+
"annotation": ModelTaskType.ANNOTATION,
|
|
182
|
+
"classification_unbundling": ModelTaskType.CLASSIFICATION_UNBUNDLING,
|
|
183
|
+
"classification_multiple": ModelTaskType.CLASSIFICATION_MULTIPLE,
|
|
184
|
+
}
|
|
185
|
+
if model_type not in model_map.keys():
|
|
186
|
+
raise ToolkitInputError(
|
|
187
|
+
f"{model_type} not found. Available options "
|
|
188
|
+
f"include {[model for model in model_map.keys()]}"
|
|
189
|
+
)
|
|
190
|
+
workflow = self.client.call(GetWorkflow(workflow_id))
|
|
191
|
+
if not prev_comp_id:
|
|
192
|
+
prev_comp_id = workflow.component_by_type("INPUT_OCR_EXTRACTION").id
|
|
193
|
+
|
|
194
|
+
column_id = dataset.datacolumn_by_name(data_column).id
|
|
195
|
+
new_labelset = NewLabelsetArguments(
|
|
196
|
+
labelset_name,
|
|
197
|
+
datacolumn_id=column_id,
|
|
198
|
+
task_type=model_map[model_type],
|
|
199
|
+
target_names=target_names,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
workflow = self.client.call(
|
|
203
|
+
AddModelGroupComponent(
|
|
204
|
+
workflow_id=workflow.id,
|
|
205
|
+
name=task_name,
|
|
206
|
+
dataset_id=dataset.id,
|
|
207
|
+
source_column_id=column_id,
|
|
208
|
+
after_component_id=prev_comp_id,
|
|
209
|
+
new_labelset_args=new_labelset,
|
|
210
|
+
model_training_options=json.dumps(kwargs),
|
|
211
|
+
)
|
|
212
|
+
)
|
|
213
|
+
print(
|
|
214
|
+
"Newly created teach task with teach_id: "
|
|
215
|
+
f"{workflow.components[-1].model_group.questionnaire_id}"
|
|
216
|
+
)
|
|
217
|
+
return workflow
|
|
218
|
+
|
|
219
|
+
def get_teach_details(self, teach_task_id: int):
|
|
220
|
+
return self.client.call(GetTeachDetails(teach_task_id=teach_task_id))
|
|
221
|
+
|
|
222
|
+
def get_example_ids(self, model_group_id: int, limit: int):
|
|
223
|
+
return self.client.call(
|
|
224
|
+
GetExampleIds(model_group_id=model_group_id, limit=limit)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def label_teach_task(self, label_set_id: int, labels: dict, model_group_id: int):
|
|
228
|
+
return self.client.call(
|
|
229
|
+
LabelTeachTask(
|
|
230
|
+
label_set_id=label_set_id, labels=labels, model_group_id=model_group_id
|
|
231
|
+
)
|
|
232
|
+
)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from indico.queries import GraphQLRequest
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GetTeachDetails(GraphQLRequest):
|
|
5
|
+
GET_TEACH_DETAILS = """
|
|
6
|
+
query getCrowdlabelQuestionnaire($teach_task_id: Int!) {
|
|
7
|
+
questionnaire(id: $teach_task_id) {
|
|
8
|
+
id
|
|
9
|
+
question {
|
|
10
|
+
id
|
|
11
|
+
type
|
|
12
|
+
text
|
|
13
|
+
modelGroupId
|
|
14
|
+
status
|
|
15
|
+
labelset {
|
|
16
|
+
id
|
|
17
|
+
targetNames {
|
|
18
|
+
id
|
|
19
|
+
name
|
|
20
|
+
position
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, *, teach_task_id: int):
|
|
29
|
+
super().__init__(
|
|
30
|
+
self.GET_TEACH_DETAILS,
|
|
31
|
+
variables={"teach_task_id": teach_task_id},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def process_response(self, response):
|
|
35
|
+
return super().process_response(response)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class GetExampleIds(GraphQLRequest):
|
|
39
|
+
GET_EXAMPLES = """
|
|
40
|
+
query getExamplesList(
|
|
41
|
+
$modelGroupId: Int!,
|
|
42
|
+
$filters: ExampleFilter,
|
|
43
|
+
$skip: Int,
|
|
44
|
+
$before: Int,
|
|
45
|
+
$after: Int,
|
|
46
|
+
$limit: Int,
|
|
47
|
+
$desc: Boolean,
|
|
48
|
+
$orderBy: ExampleOrder
|
|
49
|
+
) {
|
|
50
|
+
modelGroup(modelGroupId: $modelGroupId) {
|
|
51
|
+
id
|
|
52
|
+
pagedExamples(
|
|
53
|
+
filters: $filters,
|
|
54
|
+
skip: $skip,
|
|
55
|
+
before: $before,
|
|
56
|
+
after: $after,
|
|
57
|
+
limit: $limit,
|
|
58
|
+
desc: $desc,
|
|
59
|
+
orderBy:
|
|
60
|
+
$orderBy
|
|
61
|
+
) {
|
|
62
|
+
examples {
|
|
63
|
+
id
|
|
64
|
+
datarowId
|
|
65
|
+
updatedAt
|
|
66
|
+
status
|
|
67
|
+
datafile {
|
|
68
|
+
name
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
pageInfo {
|
|
72
|
+
startCursor
|
|
73
|
+
endCursor
|
|
74
|
+
hasNextPage
|
|
75
|
+
aggregateCount
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, *, model_group_id: int, limit: int):
|
|
83
|
+
super().__init__(
|
|
84
|
+
self.GET_EXAMPLES,
|
|
85
|
+
variables={"modelGroupId": model_group_id, "limit": limit},
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def process_response(self, response):
|
|
89
|
+
return super().process_response(response)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class LabelTeachTask(GraphQLRequest):
|
|
93
|
+
LABEL_TASK = """
|
|
94
|
+
mutation submitQuestionnaireExample
|
|
95
|
+
($labelsetId: Int!,
|
|
96
|
+
$labels: [LabelInput]!,
|
|
97
|
+
$modelGroupId: Int) {
|
|
98
|
+
submitLabelsV2(
|
|
99
|
+
labels: $labels,
|
|
100
|
+
labelsetId: $labelsetId,
|
|
101
|
+
modelGroupId: $modelGroupId) {
|
|
102
|
+
success
|
|
103
|
+
__typename
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, *, label_set_id: int, labels, model_group_id: int):
|
|
109
|
+
super().__init__(
|
|
110
|
+
self.LABEL_TASK,
|
|
111
|
+
variables={
|
|
112
|
+
"labelsetId": label_set_id,
|
|
113
|
+
"labels": labels,
|
|
114
|
+
"modelGroupId": model_group_id,
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def process_response(self, response):
|
|
119
|
+
return super().process_response(response)
|