Skip to content

Commit b87860a

Browse files
author
Kevin Kim
committed
Merge branch 'develop' of https://github.com/Labelbox/labelbox-python into kkim/AL-5172
2 parents 63d30eb + 8ffa9da commit b87860a

File tree

13 files changed

+240
-31
lines changed

13 files changed

+240
-31
lines changed

CHANGELOG.md

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,35 @@
11
# Changelog
22

3-
# Version 3.40.0 (YYYY-MM-DD)
3+
# Version 3.40.1 (2023-03-10)
44

5-
## Added
6-
* Insert newest changelogs here
5+
## Fixed
6+
* Fixed issue where calling create_batch() on exported data rows wasn't working
7+
8+
# Version 3.40.0 (2023-03-10)
9+
10+
## Added
11+
* Support Global keys to reference data rows in `Project.create_batch()`, `ModelRun.assign_data_rows_to_split()`
12+
* Support upserting labels via project_id in `model_run.upsert_labels()`
13+
* `media_type_override` param to export_v2
14+
* `last_activity_at` and `label_created_at` params to export_v2
15+
* New client method `is_feature_schema_archived()`
16+
* New client method `unarchive_feature_schema_node()`
17+
* New client method `delete_feature_schema_from_ontology()`
18+
19+
## Changed
20+
* Removed default task names for export_v2
21+
22+
## Fixed
23+
* process_label() for COCO panoptic dataset
24+
25+
## Notebooks
26+
* Updated `annotation_import/pdf.ipynb` with more examples
27+
* Added `integrations/huggingface/huggingface.ipynb`
28+
* Fixed broken links for detectron notebooks in README
29+
* Added Dataset QueueMode during project creation in `integrations/detectron2/coco_object.ipynb`
30+
* Removed metadata and updated ontology in `annotation_import/text.ipynb`
31+
* Removed confidence scores in `annotation_import/image.ipynb`
32+
* Updated custom embedding tutorial links in `basics/data_row_metadata.ipynb`
733

834
# Version 3.39.0 (2023-02-28)
935
## Added
@@ -72,7 +98,6 @@
7298
* Added support for adding metadata by name when creating datarows using `Dataset.create_data_rows()`, `Dataset.create_data_rows_sync()`, and `Dataset.create_data_row()`.
7399
* Example notebooks for auto metrics in models
74100

75-
76101
### Changed
77102
* `Dataset.create_data_rows()` max limit of DataRows increased to 150,000
78103
* Improved error handling for invalid annotation import content

CONTRIB.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,20 @@ Each release should follow the following steps:
6363
6. This will kick off a Github Actions workflow that will:
6464
- Build the library in the [standard way](https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives)
6565
- Upload the distribution archives in the [standard way](https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives)
66-
with credentials for the `labelbox` PyPI user.
66+
- with credentials for the `labelbox` PyPI user.
67+
68+
## Running Jupyter Notebooks
69+
70+
We have plenty of good samples in the _examples_ directory and using them for testing can help us increase our productivity. One way to use jupyter notebooks is to run the jupyter server locally (another way is to use a VSC plugin, not documented here). It works really fast.
71+
72+
Make sure your notebook will use your source code:
73+
1. `ipython profile create`
74+
2. `ipython locate` - will show where the config file is. This is the config file used by the jupyter server, since it runs via ipython
75+
3. Open the file (this should be ipython_config.py and it is usually located in ~/.ipython/profile_default) and add the following line of code:
76+
```
77+
c.InteractiveShellApp.exec_lines = [
78+
'import sys; sys.path.insert(0, "<labelbox-python root folder>")'
79+
]
80+
```
81+
4. Go to the root of your project and run `jupyter notebook` to start the server
82+

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
copyright = '2021, Labelbox'
2222
author = 'Labelbox'
2323

24-
release = '3.39.0'
24+
release = '3.40.1'
2525

2626
# -- General configuration ---------------------------------------------------
2727

labelbox/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "labelbox"
2-
__version__ = "3.39.0"
2+
__version__ = "3.40.1"
33

44
from labelbox.client import Client
55
from labelbox.schema.project import Project

labelbox/client.py

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from labelbox.schema.role import Role
3737
from labelbox.schema.slice import CatalogSlice, ModelSlice
3838
from labelbox.schema.queue_mode import QueueMode
39+
from labelbox.schema.ontology import Ontology, DeleteFeatureFromOntologyResult
3940

4041
from labelbox.schema.media_type import MediaType, get_media_type_validation_error
4142

@@ -44,11 +45,6 @@
4445
_LABELBOX_API_KEY = "LABELBOX_API_KEY"
4546

4647

47-
class DeleteFeatureFromOntologyResult:
48-
archived: bool
49-
deleted: bool
50-
51-
5248
class Client:
5349
""" A Labelbox client.
5450
@@ -1578,6 +1574,49 @@ def get_catalog_slice(self, slice_id) -> CatalogSlice:
15781574
res = self.execute(query_str, {'id': slice_id})
15791575
return Entity.CatalogSlice(self, res['getSavedQuery'])
15801576

1577+
def is_feature_schema_archived(self, ontology_id: str,
1578+
feature_schema_id: str) -> bool:
1579+
"""
1580+
Returns true if a feature schema is archived in the specified ontology, returns false otherwise.
1581+
1582+
Args:
1583+
feature_schema_id (str): The ID of the feature schema
1584+
ontology_id (str): The ID of the ontology
1585+
Returns:
1586+
bool
1587+
"""
1588+
1589+
ontology_endpoint = self.rest_endpoint + "/ontologies/" + urllib.parse.quote(
1590+
ontology_id)
1591+
response = requests.get(
1592+
ontology_endpoint,
1593+
headers=self.headers,
1594+
)
1595+
1596+
if response.status_code == requests.codes.ok:
1597+
feature_schema_nodes = response.json()['featureSchemaNodes']
1598+
tools = feature_schema_nodes['tools']
1599+
classifications = feature_schema_nodes['classifications']
1600+
relationships = feature_schema_nodes['relationships']
1601+
feature_schema_node_list = tools + classifications + relationships
1602+
filtered_feature_schema_nodes = [
1603+
feature_schema_node
1604+
for feature_schema_node in feature_schema_node_list
1605+
if feature_schema_node['featureSchemaId'] == feature_schema_id
1606+
]
1607+
if filtered_feature_schema_nodes:
1608+
return bool(filtered_feature_schema_nodes[0]['archived'])
1609+
else:
1610+
raise labelbox.exceptions.LabelboxError(
1611+
"The specified feature schema was not in the ontology.")
1612+
1613+
elif response.status_code == 404:
1614+
raise labelbox.exceptions.ResourceNotFoundError(
1615+
Ontology, ontology_id)
1616+
else:
1617+
raise labelbox.exceptions.LabelboxError(
1618+
"Failed to get the feature schema archived status.")
1619+
15811620
def get_model_slice(self, slice_id) -> ModelSlice:
15821621
"""
15831622
Fetches a Model Slice by ID.
@@ -1646,3 +1685,30 @@ def delete_feature_schema_from_ontology(
16461685
raise labelbox.exceptions.LabelboxError(
16471686
"Failed to remove feature schema from ontology, message: " +
16481687
str(response.json()['message']))
1688+
1689+
def unarchive_feature_schema_node(self, ontology_id: str,
1690+
root_feature_schema_id: str) -> None:
1691+
"""
1692+
Unarchives a feature schema node in an ontology.
1693+
Only root level feature schema nodes can be unarchived.
1694+
Args:
1695+
ontology_id (str): The ID of the ontology
1696+
root_feature_schema_id (str): The ID of the root level feature schema
1697+
Returns:
1698+
None
1699+
"""
1700+
ontology_endpoint = self.rest_endpoint + "/ontologies/" + urllib.parse.quote(
1701+
ontology_id) + '/feature-schemas/' + urllib.parse.quote(
1702+
root_feature_schema_id) + '/unarchive'
1703+
response = requests.patch(
1704+
ontology_endpoint,
1705+
headers=self.headers,
1706+
)
1707+
if response.status_code == requests.codes.ok:
1708+
if not bool(response.json()['unarchived']):
1709+
raise labelbox.exceptions.LabelboxError(
1710+
"Failed unarchive the feature schema.")
1711+
else:
1712+
raise labelbox.exceptions.LabelboxError(
1713+
"Failed unarchive the feature schema node, message: ",
1714+
response.text)

labelbox/schema/export_params.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import sys
22

33
from typing import Optional
4+
5+
from labelbox.schema.media_type import MediaType
46
if sys.version_info >= (3, 8):
57
from typing import TypedDict
68
else:
@@ -11,6 +13,7 @@ class DataRowParams(TypedDict):
1113
data_row_details: Optional[bool]
1214
metadata_fields: Optional[bool]
1315
attachments: Optional[bool]
16+
media_type_override: Optional[MediaType]
1417

1518

1619
class ProjectExportParams(DataRowParams):

labelbox/schema/model_run.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,14 +335,15 @@ def delete_model_run_data_rows(self, data_row_ids: List[str]):
335335

336336
@experimental
337337
def assign_data_rows_to_split(self,
338-
data_row_ids: List[str],
339-
split: Union[DataSplit, str],
338+
data_row_ids: List[str] = None,
339+
split: Union[DataSplit, str] = None,
340+
global_keys: List[str] = None,
340341
timeout_seconds=120):
341342

342343
split_value = split.value if isinstance(split, DataSplit) else split
343344
valid_splits = DataSplit._member_names_
344345

345-
if split_value not in valid_splits:
346+
if split_value is None or split_value not in valid_splits:
346347
raise ValueError(
347348
f"`split` must be one of : `{valid_splits}`. Found : `{split}`")
348349

@@ -354,7 +355,8 @@ def assign_data_rows_to_split(self,
354355
'data': {
355356
'assignments': [{
356357
'split': split_value,
357-
'dataRowIds': data_row_ids
358+
'dataRowIds': data_row_ids,
359+
'globalKeys': global_keys,
358360
}]
359361
}
360362
},
@@ -521,6 +523,8 @@ def export_v2(self,
521523
"modelRunId": self.uid
522524
},
523525
"params": {
526+
"mediaTypeOverride":
527+
_params.get('media_type_override', None),
524528
"includeAttachments":
525529
_params.get('attachments', False),
526530
"includeMetadata":

labelbox/schema/ontology.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,21 @@
1111
from labelbox.exceptions import InconsistentOntologyException
1212
from labelbox.orm.db_object import DbObject
1313
from labelbox.orm.model import Field, Relationship
14+
import json
1415

1516
FeatureSchemaId: Type[str] = constr(min_length=25, max_length=25)
1617
SchemaId: Type[str] = constr(min_length=25, max_length=25)
1718

1819

20+
class DeleteFeatureFromOntologyResult:
21+
archived: bool
22+
deleted: bool
23+
24+
def __str__(self):
25+
return "<%s %s>" % (self.__class__.__name__.split(".")[-1],
26+
json.dumps(self.__dict__))
27+
28+
1929
class FeatureSchema(DbObject):
2030
name = Field.String("name")
2131
color = Field.String("name")

labelbox/schema/project.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,8 @@ def export_v2(self,
430430
"data_row_details": False,
431431
"project_details": False,
432432
"performance_details": False,
433-
"label_details": False
433+
"label_details": False,
434+
"media_type_override": None
434435
})
435436

436437
_filters = filters or ProjectExportFilters({
@@ -451,6 +452,7 @@ def _get_timezone() -> str:
451452
""" % (mutation_name)
452453

453454
search_query: List[Dict[str, Collection[str]]] = []
455+
media_type_override = _params.get('media_type_override', None)
454456
query_params = {
455457
"input": {
456458
"taskName": task_name,
@@ -462,6 +464,9 @@ def _get_timezone() -> str:
462464
}
463465
},
464466
"params": {
467+
"mediaTypeOverride":
468+
media_type_override.value
469+
if media_type_override is not None else None,
465470
"includeAttachments":
466471
_params.get('attachments', False),
467472
"includeMetadata":
@@ -809,7 +814,7 @@ def create_batch(
809814
"`data_rows` must be DataRow ids or DataRow objects")
810815

811816
if data_rows is not None:
812-
row_count = len(data_rows)
817+
row_count = len(dr_ids)
813818
elif global_keys is not None:
814819
row_count = len(global_keys)
815820
else:

scripts/update_sdk_version.sh

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,21 @@ CHANGELOGS_FILE="$SDK_PATH/CHANGELOG.md"
3838

3939
old_version=$(cat $SDK_PATH/labelbox/__init__.py | grep __version__ | cut -d '=' -f2 | tr -d ' ' | tr -d '"')
4040

41-
echo "New version: $new_version"
42-
echo "Old version: $old_version"
43-
41+
printf "Starting release process! $old_version --> $new_version\n"
4442
escaped_old_version=$(echo "$old_version" | sed "s/\./\\\./g")
4543
escaped_new_version=$(echo "$new_version" | sed "s/\./\\\./g")
4644

4745
sed -i "" "s/$escaped_old_version/$escaped_new_version/" $INIT_FILE
48-
echo "Updated '$INIT_FILE'"
46+
printf "Updated '$INIT_FILE'\n"
4947

5048
sed -i "" "s/$escaped_old_version/$escaped_new_version/" $READTHEDOCS_CONF_FILE
51-
echo "Updated '$READTHEDOCS_CONF_FILE'"
52-
echo "Successfully updated SDK version locally!"
49+
printf "Updated '$READTHEDOCS_CONF_FILE'\n"
50+
printf "Successfully updated SDK version locally!\n"
5351

54-
echo "\nOpening CHANGELOGS file in text editor"
52+
printf "Opening CHANGELOGS file in text editor\n"
5553
open -e $CHANGELOGS_FILE
5654

57-
echo "\nPlease open a PR to finish the release process using the following git commands:"
58-
echo "\ngit add --all"
59-
echo "git commit -m 'Preparing for $new_version release'"
60-
echo "git push origin prep_$new_version"
55+
printf "Please open a PR to finish the release process using the following git commands:\n"
56+
printf "git add --all\n"
57+
printf "git commit -m 'Preparing for $new_version release'\n"
58+
printf "git push origin prep_$new_version\n"

0 commit comments

Comments
 (0)