3lc-compute-plugin-sdk 0.2.2__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.
- 3lc_compute_plugin_sdk-0.2.2.dist-info/METADATA +81 -0
- 3lc_compute_plugin_sdk-0.2.2.dist-info/RECORD +28 -0
- 3lc_compute_plugin_sdk-0.2.2.dist-info/WHEEL +4 -0
- 3lc_compute_plugin_sdk-0.2.2.dist-info/licenses/LICENSE +202 -0
- tlc_plugin_sdk/__init__.py +58 -0
- tlc_plugin_sdk/asgi_app.py +103 -0
- tlc_plugin_sdk/contract/plugin-api.d.ts +429 -0
- tlc_plugin_sdk/contract.py +108 -0
- tlc_plugin_sdk/job_context.py +120 -0
- tlc_plugin_sdk/py.typed +0 -0
- tlc_plugin_sdk/shared/__init__.py +11 -0
- tlc_plugin_sdk/shared/alias_override_ui.py +175 -0
- tlc_plugin_sdk/shared/alias_ui.py +198 -0
- tlc_plugin_sdk/shared/aliases.py +227 -0
- tlc_plugin_sdk/shared/config_store.py +140 -0
- tlc_plugin_sdk/shared/config_ui.py +256 -0
- tlc_plugin_sdk/shared/data_source_routes.py +221 -0
- tlc_plugin_sdk/shared/data_source_ui.py +365 -0
- tlc_plugin_sdk/shared/generic_job.py +58 -0
- tlc_plugin_sdk/shared/images.py +350 -0
- tlc_plugin_sdk/shared/job_tracker.py +174 -0
- tlc_plugin_sdk/shared/labels.py +247 -0
- tlc_plugin_sdk/shared/modality.py +551 -0
- tlc_plugin_sdk/shared/model_storage.py +160 -0
- tlc_plugin_sdk/shared/naming.py +84 -0
- tlc_plugin_sdk/shared/ui_inject.py +63 -0
- tlc_plugin_sdk/shared/url_utils.py +112 -0
- tlc_plugin_sdk/worker.py +295 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Copyright 2026 3LC Inc.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Shared helpers for discovering and reading class labels from 3LC tables.
|
|
4
|
+
|
|
5
|
+
Centralizes the label-handling pattern used across plugins. The actual value
|
|
6
|
+
map reading is core SDK API — ``table.get_value_map(path)`` and
|
|
7
|
+
``table.get_simple_value_map(path)`` (which canonicalize on
|
|
8
|
+
``MapElement.internal_name``). What core does not provide, and this module
|
|
9
|
+
centralizes, is *discovering the label value path*: the dot-path to the label
|
|
10
|
+
value differs by modality and table convention:
|
|
11
|
+
|
|
12
|
+
- classification: the label column itself (e.g. ``"label"``)
|
|
13
|
+
- detection: ``"{column}.instances_additional_data.label"`` (tlc 3.x) or
|
|
14
|
+
``"{column}.bb_list.label"`` (legacy tables)
|
|
15
|
+
- segmentation: ``"{column}.instance_properties.label"``
|
|
16
|
+
|
|
17
|
+
Porting to tlc core
|
|
18
|
+
-------------------
|
|
19
|
+
Like ``shared/images.py``, this belongs in the SDK next to
|
|
20
|
+
``Table.get_value_map``. Intended mapping: ``find_label_path`` →
|
|
21
|
+
``Table.find_label_path(column=None)``; ``get_label_map`` /
|
|
22
|
+
``get_label_names`` → thin conveniences over ``Table.get_simple_value_map``.
|
|
23
|
+
Plugins import only from this module, so the port touches exactly this file.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
# Nested sub-paths where instance labels live, in preference order. Verified
|
|
31
|
+
# against tlc 3.1: BoundingBoxes2D produces instances_additional_data.label,
|
|
32
|
+
# SegmentationPolygons produces instance_properties.label; bb_list.label is
|
|
33
|
+
# kept for tables written by older SDKs.
|
|
34
|
+
_INSTANCE_LABEL_SUBPATHS = (
|
|
35
|
+
"instances_additional_data.label",
|
|
36
|
+
"bb_list.label",
|
|
37
|
+
"instance_properties.label",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Conventional instance-column names to probe when no column is given.
|
|
41
|
+
_INSTANCE_COLUMN_CANDIDATES = (
|
|
42
|
+
"bbs",
|
|
43
|
+
"bbs_predicted",
|
|
44
|
+
"segmentations",
|
|
45
|
+
"segmentations_predicted",
|
|
46
|
+
"predicted_segmentations",
|
|
47
|
+
"segmentation",
|
|
48
|
+
"masks",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Conventional plain (classification) label column, tried last.
|
|
52
|
+
_PLAIN_LABEL_COLUMN = "label"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def candidate_label_paths(column: str | None = None) -> list[str]:
|
|
56
|
+
"""Enumerate candidate label value paths, most-specific first.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
column: An explicit column to search under. When given, only that
|
|
60
|
+
column (direct, then nested) is considered; otherwise the
|
|
61
|
+
conventional instance columns and the plain ``label`` column.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Dot-paths suitable for ``table.get_value_map``.
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
paths: list[str] = []
|
|
68
|
+
if column:
|
|
69
|
+
paths.append(column)
|
|
70
|
+
paths.extend(f"{column}.{sub}" for sub in _INSTANCE_LABEL_SUBPATHS)
|
|
71
|
+
return paths
|
|
72
|
+
|
|
73
|
+
for col in _INSTANCE_COLUMN_CANDIDATES:
|
|
74
|
+
paths.extend(f"{col}.{sub}" for sub in _INSTANCE_LABEL_SUBPATHS)
|
|
75
|
+
paths.append(_PLAIN_LABEL_COLUMN)
|
|
76
|
+
return paths
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def find_label_path(table: Any, column: str | None = None) -> str | None:
|
|
80
|
+
"""Find the dot-path to a table's label value map.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
table: A loaded ``tlc.Table``.
|
|
84
|
+
column: Optional column to restrict the search to.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
The first candidate path with a non-empty value map, or ``None``.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
for path in candidate_label_paths(column):
|
|
91
|
+
try:
|
|
92
|
+
if table.get_value_map(path):
|
|
93
|
+
return path
|
|
94
|
+
except Exception:
|
|
95
|
+
continue
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_label_map(table: Any, column: str | None = None, *, path: str | None = None) -> dict[int, str]:
|
|
100
|
+
"""Read a table's label map as ``{class index: internal name}``.
|
|
101
|
+
|
|
102
|
+
Thin wrapper over ``table.get_simple_value_map`` with path discovery.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
table: A loaded ``tlc.Table``.
|
|
106
|
+
column: Optional column to restrict path discovery to.
|
|
107
|
+
path: Explicit value path; skips discovery when given.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
The label map, or ``{}`` if the table has none.
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
label_path = path or find_label_path(table, column)
|
|
114
|
+
if not label_path:
|
|
115
|
+
return {}
|
|
116
|
+
try:
|
|
117
|
+
simple = table.get_simple_value_map(label_path)
|
|
118
|
+
except Exception:
|
|
119
|
+
return {}
|
|
120
|
+
return dict(simple) if simple else {}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_label_names(table: Any, column: str | None = None, *, path: str | None = None) -> list[str]:
|
|
124
|
+
"""Read a table's class names, ordered by class index.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
table: A loaded ``tlc.Table``.
|
|
128
|
+
column: Optional column to restrict path discovery to.
|
|
129
|
+
path: Explicit value path; skips discovery when given.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Class names in index order, or ``[]`` if the table has no label map.
|
|
133
|
+
|
|
134
|
+
"""
|
|
135
|
+
label_map = get_label_map(table, column, path=path)
|
|
136
|
+
return [label_map[k] for k in sorted(label_map)]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_class_name_lookup(table: Any, column: str | None = None, *, path: str | None = None) -> dict[str, str]:
|
|
140
|
+
"""Read a table's label map as a string-keyed lookup table.
|
|
141
|
+
|
|
142
|
+
Keys include both the raw map key (``"1.0"``) and its integer form
|
|
143
|
+
(``"1"``), so callers can index with whichever flavor their data carries.
|
|
144
|
+
This is the shape the insights statistics pipeline consumes.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
table: A loaded ``tlc.Table``.
|
|
148
|
+
column: Optional column to restrict path discovery to.
|
|
149
|
+
path: Explicit value path; skips discovery when given.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
``{class key (str): internal name}``, or ``{}`` if no label map.
|
|
153
|
+
|
|
154
|
+
"""
|
|
155
|
+
label_path = path or find_label_path(table, column)
|
|
156
|
+
if not label_path:
|
|
157
|
+
return {}
|
|
158
|
+
try:
|
|
159
|
+
value_map = table.get_value_map(label_path)
|
|
160
|
+
except Exception:
|
|
161
|
+
return {}
|
|
162
|
+
if not value_map:
|
|
163
|
+
return {}
|
|
164
|
+
|
|
165
|
+
names: dict[str, str] = {}
|
|
166
|
+
for key, element in value_map.items():
|
|
167
|
+
names[str(key)] = _element_name(key, element)
|
|
168
|
+
try:
|
|
169
|
+
names.setdefault(str(int(float(key))), names[str(key)])
|
|
170
|
+
except (ValueError, TypeError):
|
|
171
|
+
pass
|
|
172
|
+
return names
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_display_value_map(table: Any, path: str) -> dict[float, str] | None:
|
|
176
|
+
"""Read a value map as ``{float key: display name}`` for UI purposes.
|
|
177
|
+
|
|
178
|
+
Unlike the label helpers above (which canonicalize on ``internal_name``,
|
|
179
|
+
matching core's ``get_simple_value_map``), this prefers ``display_name``
|
|
180
|
+
— the shape used when presenting categorical columns to users.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
table: A loaded ``tlc.Table``.
|
|
184
|
+
path: Value path (column name or dot-path).
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
The display map, or ``None`` if the path has no value map.
|
|
188
|
+
|
|
189
|
+
"""
|
|
190
|
+
try:
|
|
191
|
+
value_map = table.get_value_map(path)
|
|
192
|
+
except Exception:
|
|
193
|
+
return None
|
|
194
|
+
if not value_map:
|
|
195
|
+
return None
|
|
196
|
+
result: dict[float, str] = {}
|
|
197
|
+
for key, element in value_map.items():
|
|
198
|
+
name = _element_attr(element, "display_name") or _element_name(key, element)
|
|
199
|
+
result[float(key)] = name
|
|
200
|
+
return result or None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def find_label_column(table: Any) -> str | None:
|
|
204
|
+
"""Find a top-level categorical label column in a table, if any.
|
|
205
|
+
|
|
206
|
+
A column qualifies if it carries a value map directly (categorical), or
|
|
207
|
+
as a name-based fallback, contains ``label`` in its name.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
table: A loaded ``tlc.Table``.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
The column name, or ``None``.
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
columns = [str(c) for c in table.columns]
|
|
218
|
+
except Exception:
|
|
219
|
+
return None
|
|
220
|
+
for col in columns:
|
|
221
|
+
try:
|
|
222
|
+
if table.get_value_map(col):
|
|
223
|
+
return col
|
|
224
|
+
except Exception:
|
|
225
|
+
continue
|
|
226
|
+
for col in columns:
|
|
227
|
+
if "label" in col.lower():
|
|
228
|
+
return col
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _element_name(key: Any, element: Any) -> str:
|
|
233
|
+
"""Canonical class name for a value-map element (internal_name first)."""
|
|
234
|
+
return (
|
|
235
|
+
_element_attr(element, "internal_name")
|
|
236
|
+
or _element_attr(element, "display_name")
|
|
237
|
+
or (str(element) if not isinstance(element, dict) else str(key))
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _element_attr(element: Any, attr: str) -> str | None:
|
|
242
|
+
"""Read an attribute from a MapElement or a raw dict element."""
|
|
243
|
+
if isinstance(element, dict):
|
|
244
|
+
value = element.get(attr)
|
|
245
|
+
else:
|
|
246
|
+
value = getattr(element, attr, None)
|
|
247
|
+
return str(value) if value else None
|