lightly-studio 0.3.1__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.
Potentially problematic release.
This version of lightly-studio might be problematic. Click here for more details.
- lightly_studio/__init__.py +11 -0
- lightly_studio/api/__init__.py +0 -0
- lightly_studio/api/app.py +110 -0
- lightly_studio/api/cache.py +77 -0
- lightly_studio/api/db.py +133 -0
- lightly_studio/api/db_tables.py +32 -0
- lightly_studio/api/features.py +7 -0
- lightly_studio/api/routes/api/annotation.py +233 -0
- lightly_studio/api/routes/api/annotation_label.py +90 -0
- lightly_studio/api/routes/api/annotation_task.py +38 -0
- lightly_studio/api/routes/api/classifier.py +387 -0
- lightly_studio/api/routes/api/dataset.py +182 -0
- lightly_studio/api/routes/api/dataset_tag.py +257 -0
- lightly_studio/api/routes/api/exceptions.py +96 -0
- lightly_studio/api/routes/api/features.py +17 -0
- lightly_studio/api/routes/api/metadata.py +37 -0
- lightly_studio/api/routes/api/metrics.py +80 -0
- lightly_studio/api/routes/api/sample.py +196 -0
- lightly_studio/api/routes/api/settings.py +45 -0
- lightly_studio/api/routes/api/status.py +19 -0
- lightly_studio/api/routes/api/text_embedding.py +48 -0
- lightly_studio/api/routes/api/validators.py +17 -0
- lightly_studio/api/routes/healthz.py +13 -0
- lightly_studio/api/routes/images.py +104 -0
- lightly_studio/api/routes/webapp.py +51 -0
- lightly_studio/api/server.py +82 -0
- lightly_studio/core/__init__.py +0 -0
- lightly_studio/core/dataset.py +523 -0
- lightly_studio/core/sample.py +77 -0
- lightly_studio/core/start_gui.py +15 -0
- lightly_studio/dataset/__init__.py +0 -0
- lightly_studio/dataset/edge_embedding_generator.py +144 -0
- lightly_studio/dataset/embedding_generator.py +91 -0
- lightly_studio/dataset/embedding_manager.py +163 -0
- lightly_studio/dataset/env.py +16 -0
- lightly_studio/dataset/file_utils.py +35 -0
- lightly_studio/dataset/loader.py +622 -0
- lightly_studio/dataset/mobileclip_embedding_generator.py +144 -0
- lightly_studio/dist_lightly_studio_view_app/_app/env.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.DenzbfeK.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/LightlyLogo.BNjCIww-.png +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans- +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Bold.DGvYQtcs.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Italic-VariableFont_wdth_wght.B4AZ-wl6.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Regular.DxJTClRG.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-SemiBold.D3TTYgdB.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-VariableFont_wdth_wght.BZBpG5Iz.ttf +0 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.OwPEPQZu.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.b653GmVf.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.T-zjSUd3.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/useFeatureFlags.CV-KWLNP.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/69_IOA4Y.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B2FVR0s0.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B90CZVMX.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B9zumHo5.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BJXwVxaE.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bsi3UGy5.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bu7uvVrG.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bx1xMsFy.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BylOuP6i.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C8I8rFJQ.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CDnpyLsT.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWj6FrbW.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CYgJF_JY.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CcaPhhk3.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CvOmgdoc.js +93 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CxtLVaYz.js +3 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D5-A_Ffd.js +4 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6RI2Zrd.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6su9Aln.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D98V7j6A.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIRAtgl0.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIeogL5L.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DOlTMNyt.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DjUWrjOv.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DjfY96ND.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/H7C68rOM.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/O-EABkf9.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/XO7A28GO.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/hQVEETDE.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/l7KrR96u.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/nAHhluT7.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/r64xT6ao.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/vC4nQVEB.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/x9G_hzyY.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.CjnvpsmS.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.0o1H7wM9.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.XRq_TUwu.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1.B4rNYwVp.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.DfBwOEhN.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/11.CWG1ehzT.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.CwF2_8mP.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.CS4muRY-.js +6 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/3.CWHpKonm.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/4.OUWOLQeV.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.Dm6t9F5W.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/6.Bw5ck4gK.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.CF0EDTR6.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.Cw30LEcV.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.CPu3CiBc.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -0
- lightly_studio/dist_lightly_studio_view_app/apple-touch-icon-precomposed.png +0 -0
- lightly_studio/dist_lightly_studio_view_app/apple-touch-icon.png +0 -0
- lightly_studio/dist_lightly_studio_view_app/favicon.png +0 -0
- lightly_studio/dist_lightly_studio_view_app/index.html +44 -0
- lightly_studio/examples/example.py +23 -0
- lightly_studio/examples/example_metadata.py +338 -0
- lightly_studio/examples/example_selection.py +39 -0
- lightly_studio/examples/example_split_work.py +67 -0
- lightly_studio/examples/example_v2.py +21 -0
- lightly_studio/export_schema.py +18 -0
- lightly_studio/few_shot_classifier/__init__.py +0 -0
- lightly_studio/few_shot_classifier/classifier.py +80 -0
- lightly_studio/few_shot_classifier/classifier_manager.py +663 -0
- lightly_studio/few_shot_classifier/random_forest_classifier.py +489 -0
- lightly_studio/metadata/complex_metadata.py +47 -0
- lightly_studio/metadata/gps_coordinate.py +41 -0
- lightly_studio/metadata/metadata_protocol.py +17 -0
- lightly_studio/metrics/__init__.py +0 -0
- lightly_studio/metrics/detection/__init__.py +0 -0
- lightly_studio/metrics/detection/map.py +268 -0
- lightly_studio/models/__init__.py +1 -0
- lightly_studio/models/annotation/__init__.py +0 -0
- lightly_studio/models/annotation/annotation_base.py +171 -0
- lightly_studio/models/annotation/instance_segmentation.py +56 -0
- lightly_studio/models/annotation/links.py +17 -0
- lightly_studio/models/annotation/object_detection.py +47 -0
- lightly_studio/models/annotation/semantic_segmentation.py +44 -0
- lightly_studio/models/annotation_label.py +47 -0
- lightly_studio/models/annotation_task.py +28 -0
- lightly_studio/models/classifier.py +20 -0
- lightly_studio/models/dataset.py +84 -0
- lightly_studio/models/embedding_model.py +30 -0
- lightly_studio/models/metadata.py +208 -0
- lightly_studio/models/sample.py +180 -0
- lightly_studio/models/sample_embedding.py +37 -0
- lightly_studio/models/settings.py +60 -0
- lightly_studio/models/tag.py +96 -0
- lightly_studio/py.typed +0 -0
- lightly_studio/resolvers/__init__.py +7 -0
- lightly_studio/resolvers/annotation_label_resolver/__init__.py +21 -0
- lightly_studio/resolvers/annotation_label_resolver/create.py +27 -0
- lightly_studio/resolvers/annotation_label_resolver/delete.py +28 -0
- lightly_studio/resolvers/annotation_label_resolver/get_all.py +22 -0
- lightly_studio/resolvers/annotation_label_resolver/get_by_id.py +24 -0
- lightly_studio/resolvers/annotation_label_resolver/get_by_ids.py +25 -0
- lightly_studio/resolvers/annotation_label_resolver/get_by_label_name.py +24 -0
- lightly_studio/resolvers/annotation_label_resolver/names_by_ids.py +25 -0
- lightly_studio/resolvers/annotation_label_resolver/update.py +38 -0
- lightly_studio/resolvers/annotation_resolver/__init__.py +33 -0
- lightly_studio/resolvers/annotation_resolver/count_annotations_by_dataset.py +120 -0
- lightly_studio/resolvers/annotation_resolver/create.py +19 -0
- lightly_studio/resolvers/annotation_resolver/create_many.py +96 -0
- lightly_studio/resolvers/annotation_resolver/delete_annotation.py +45 -0
- lightly_studio/resolvers/annotation_resolver/delete_annotations.py +56 -0
- lightly_studio/resolvers/annotation_resolver/get_all.py +74 -0
- lightly_studio/resolvers/annotation_resolver/get_by_id.py +18 -0
- lightly_studio/resolvers/annotation_resolver/update_annotation_label.py +144 -0
- lightly_studio/resolvers/annotation_resolver/update_bounding_box.py +68 -0
- lightly_studio/resolvers/annotation_task_resolver.py +31 -0
- lightly_studio/resolvers/annotations/__init__.py +1 -0
- lightly_studio/resolvers/annotations/annotations_filter.py +89 -0
- lightly_studio/resolvers/dataset_resolver.py +278 -0
- lightly_studio/resolvers/embedding_model_resolver.py +100 -0
- lightly_studio/resolvers/metadata_resolver/__init__.py +15 -0
- lightly_studio/resolvers/metadata_resolver/metadata_filter.py +163 -0
- lightly_studio/resolvers/metadata_resolver/sample/__init__.py +21 -0
- lightly_studio/resolvers/metadata_resolver/sample/bulk_set_metadata.py +48 -0
- lightly_studio/resolvers/metadata_resolver/sample/get_by_sample_id.py +24 -0
- lightly_studio/resolvers/metadata_resolver/sample/get_metadata_info.py +104 -0
- lightly_studio/resolvers/metadata_resolver/sample/get_value_for_sample.py +27 -0
- lightly_studio/resolvers/metadata_resolver/sample/set_value_for_sample.py +53 -0
- lightly_studio/resolvers/sample_embedding_resolver.py +86 -0
- lightly_studio/resolvers/sample_resolver.py +249 -0
- lightly_studio/resolvers/samples_filter.py +81 -0
- lightly_studio/resolvers/settings_resolver.py +58 -0
- lightly_studio/resolvers/tag_resolver.py +276 -0
- lightly_studio/selection/README.md +6 -0
- lightly_studio/selection/mundig.py +105 -0
- lightly_studio/selection/select.py +96 -0
- lightly_studio/selection/select_via_db.py +93 -0
- lightly_studio/selection/selection_config.py +31 -0
- lightly_studio/services/annotations_service/__init__.py +21 -0
- lightly_studio/services/annotations_service/get_annotation_by_id.py +31 -0
- lightly_studio/services/annotations_service/update_annotation.py +65 -0
- lightly_studio/services/annotations_service/update_annotation_label.py +48 -0
- lightly_studio/services/annotations_service/update_annotations.py +29 -0
- lightly_studio/setup_logging.py +19 -0
- lightly_studio/type_definitions.py +19 -0
- lightly_studio/vendor/ACKNOWLEDGEMENTS +422 -0
- lightly_studio/vendor/LICENSE +31 -0
- lightly_studio/vendor/LICENSE_weights_data +50 -0
- lightly_studio/vendor/README.md +5 -0
- lightly_studio/vendor/__init__.py +1 -0
- lightly_studio/vendor/mobileclip/__init__.py +96 -0
- lightly_studio/vendor/mobileclip/clip.py +77 -0
- lightly_studio/vendor/mobileclip/configs/mobileclip_b.json +18 -0
- lightly_studio/vendor/mobileclip/configs/mobileclip_s0.json +18 -0
- lightly_studio/vendor/mobileclip/configs/mobileclip_s1.json +18 -0
- lightly_studio/vendor/mobileclip/configs/mobileclip_s2.json +18 -0
- lightly_studio/vendor/mobileclip/image_encoder.py +67 -0
- lightly_studio/vendor/mobileclip/logger.py +154 -0
- lightly_studio/vendor/mobileclip/models/__init__.py +10 -0
- lightly_studio/vendor/mobileclip/models/mci.py +933 -0
- lightly_studio/vendor/mobileclip/models/vit.py +433 -0
- lightly_studio/vendor/mobileclip/modules/__init__.py +4 -0
- lightly_studio/vendor/mobileclip/modules/common/__init__.py +4 -0
- lightly_studio/vendor/mobileclip/modules/common/mobileone.py +341 -0
- lightly_studio/vendor/mobileclip/modules/common/transformer.py +451 -0
- lightly_studio/vendor/mobileclip/modules/image/__init__.py +4 -0
- lightly_studio/vendor/mobileclip/modules/image/image_projection.py +113 -0
- lightly_studio/vendor/mobileclip/modules/image/replknet.py +188 -0
- lightly_studio/vendor/mobileclip/modules/text/__init__.py +4 -0
- lightly_studio/vendor/mobileclip/modules/text/repmixer.py +281 -0
- lightly_studio/vendor/mobileclip/modules/text/tokenizer.py +38 -0
- lightly_studio/vendor/mobileclip/text_encoder.py +245 -0
- lightly_studio-0.3.1.dist-info/METADATA +520 -0
- lightly_studio-0.3.1.dist-info/RECORD +219 -0
- lightly_studio-0.3.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Handler for database operations related to annotations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from sqlmodel import Session, col, delete
|
|
8
|
+
|
|
9
|
+
from lightly_studio.models.annotation.annotation_base import (
|
|
10
|
+
AnnotationBaseTable,
|
|
11
|
+
)
|
|
12
|
+
from lightly_studio.models.annotation.links import AnnotationTagLinkTable
|
|
13
|
+
from lightly_studio.resolvers import annotation_resolver
|
|
14
|
+
from lightly_studio.resolvers.annotations.annotations_filter import (
|
|
15
|
+
AnnotationsFilter,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def delete_annotations(
|
|
20
|
+
session: Session,
|
|
21
|
+
annotation_task_ids: list[UUID] | None,
|
|
22
|
+
annotation_label_ids: list[UUID] | None,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""Delete all annotations and their tag links using filters.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
session: Database session.
|
|
28
|
+
annotation_task_ids: List of annotation task IDs to filter by.
|
|
29
|
+
annotation_label_ids: List of annotation label IDs to filter by.
|
|
30
|
+
"""
|
|
31
|
+
# Find annotation_ids to delete
|
|
32
|
+
annotations = annotation_resolver.get_all(
|
|
33
|
+
session,
|
|
34
|
+
filters=AnnotationsFilter(
|
|
35
|
+
annotation_label_ids=annotation_label_ids,
|
|
36
|
+
annotation_task_ids=annotation_task_ids,
|
|
37
|
+
),
|
|
38
|
+
).annotations
|
|
39
|
+
annotation_ids = [annotation.annotation_id for annotation in annotations]
|
|
40
|
+
# TODO(Horatiu, 06/2025): Check if there is a way to delete the links
|
|
41
|
+
# automatically using SQLModel/SQLAlchemy.
|
|
42
|
+
if annotation_ids:
|
|
43
|
+
# Delete tag links first
|
|
44
|
+
session.exec( # type: ignore
|
|
45
|
+
delete(AnnotationTagLinkTable).where(
|
|
46
|
+
col(AnnotationTagLinkTable.annotation_id).in_(annotation_ids)
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
session.commit()
|
|
50
|
+
# Now delete the annotations themselves
|
|
51
|
+
session.exec( # type: ignore
|
|
52
|
+
delete(AnnotationBaseTable).where(
|
|
53
|
+
col(AnnotationBaseTable.annotation_id).in_(annotation_ids)
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
session.commit()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Handler for database operations related to annotations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from sqlmodel import Session, col, func, select
|
|
9
|
+
|
|
10
|
+
from lightly_studio.api.routes.api.validators import Paginated
|
|
11
|
+
from lightly_studio.models.annotation.annotation_base import (
|
|
12
|
+
AnnotationBaseTable,
|
|
13
|
+
)
|
|
14
|
+
from lightly_studio.resolvers.annotations.annotations_filter import (
|
|
15
|
+
AnnotationsFilter,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GetAllAnnotationsResult(BaseModel):
|
|
20
|
+
"""Result of getting all annotations."""
|
|
21
|
+
|
|
22
|
+
annotations: Sequence[AnnotationBaseTable]
|
|
23
|
+
|
|
24
|
+
total_count: int
|
|
25
|
+
|
|
26
|
+
next_cursor: int | None = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_all(
|
|
30
|
+
session: Session,
|
|
31
|
+
pagination: Paginated | None = None,
|
|
32
|
+
filters: AnnotationsFilter | None = None,
|
|
33
|
+
) -> GetAllAnnotationsResult:
|
|
34
|
+
"""Get all annotations from the database.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
session: Database session
|
|
38
|
+
pagination: Optional pagination parameters
|
|
39
|
+
filters: Optional filters to apply to the query
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
List of annotations matching the filters
|
|
43
|
+
"""
|
|
44
|
+
annotations_statement = select(AnnotationBaseTable)
|
|
45
|
+
|
|
46
|
+
annotations_statement = annotations_statement.order_by(
|
|
47
|
+
col(AnnotationBaseTable.created_at).asc(),
|
|
48
|
+
col(AnnotationBaseTable.annotation_id).asc(),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
total_count_statement = select(func.count()).select_from(AnnotationBaseTable)
|
|
52
|
+
|
|
53
|
+
# Apply filters if provided
|
|
54
|
+
if filters is not None:
|
|
55
|
+
annotations_statement = filters.apply(annotations_statement)
|
|
56
|
+
total_count_statement = filters.apply(total_count_statement)
|
|
57
|
+
|
|
58
|
+
# Apply pagination if provided
|
|
59
|
+
if pagination is not None:
|
|
60
|
+
annotations_statement = annotations_statement.offset(pagination.offset).limit(
|
|
61
|
+
pagination.limit
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
total_count = session.exec(total_count_statement).one()
|
|
65
|
+
|
|
66
|
+
next_cursor = None
|
|
67
|
+
if pagination and pagination.offset + pagination.limit < total_count:
|
|
68
|
+
next_cursor = pagination.offset + pagination.limit
|
|
69
|
+
|
|
70
|
+
return GetAllAnnotationsResult(
|
|
71
|
+
annotations=session.exec(annotations_statement).all(),
|
|
72
|
+
total_count=total_count,
|
|
73
|
+
next_cursor=next_cursor,
|
|
74
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Handler for database operations related to annotations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from sqlmodel import Session, select
|
|
8
|
+
|
|
9
|
+
from lightly_studio.models.annotation.annotation_base import (
|
|
10
|
+
AnnotationBaseTable,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_by_id(session: Session, annotation_id: UUID) -> AnnotationBaseTable | None:
|
|
15
|
+
"""Retrieve a single annotation by ID."""
|
|
16
|
+
return session.exec(
|
|
17
|
+
select(AnnotationBaseTable).where(AnnotationBaseTable.annotation_id == annotation_id)
|
|
18
|
+
).one_or_none()
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Module for handling the update of annotation labels in the database."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TypeVar
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from sqlmodel import Session, SQLModel
|
|
9
|
+
|
|
10
|
+
from lightly_studio.models.annotation.annotation_base import (
|
|
11
|
+
AnnotationBaseTable,
|
|
12
|
+
)
|
|
13
|
+
from lightly_studio.models.annotation.instance_segmentation import (
|
|
14
|
+
InstanceSegmentationAnnotationTable,
|
|
15
|
+
)
|
|
16
|
+
from lightly_studio.models.annotation.links import AnnotationTagLinkTable
|
|
17
|
+
from lightly_studio.models.annotation.object_detection import ObjectDetectionAnnotationTable
|
|
18
|
+
from lightly_studio.models.annotation.semantic_segmentation import (
|
|
19
|
+
SemanticSegmentationAnnotationTable,
|
|
20
|
+
)
|
|
21
|
+
from lightly_studio.resolvers import (
|
|
22
|
+
annotation_resolver,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
T = TypeVar("T", bound=SQLModel)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def update_annotation_label(
|
|
29
|
+
session: Session, annotation_id: UUID, annotation_label_id: UUID
|
|
30
|
+
) -> AnnotationBaseTable:
|
|
31
|
+
"""Update the label of an annotation.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
session: Database session for executing the operation.
|
|
35
|
+
annotation_id: UUID of the annotation to update.
|
|
36
|
+
annotation_label_id: UUID of the new label to assign to the annotation.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The updated annotation with the new label assigned.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ValueError: If the annotation is not found.
|
|
43
|
+
"""
|
|
44
|
+
annotation = annotation_resolver.get_by_id(session, annotation_id)
|
|
45
|
+
if not annotation:
|
|
46
|
+
raise ValueError(f"Annotation with ID {annotation_id} not found.")
|
|
47
|
+
|
|
48
|
+
# DuckDB has no "looking ahead" functionality for referenced tables.
|
|
49
|
+
# We need to work around this by deleting the existing and re-inserting
|
|
50
|
+
# Check https://duckdb.org/docs/stable/sql/indexes.html#over-eager-constraint-checking-in-foreign-keys
|
|
51
|
+
|
|
52
|
+
# DuckDB has no "looking ahead" functionality for referenced tables and neither does it support cascading updates. # noqa: E501
|
|
53
|
+
# Herefore we need to delete and re-insert the affected rows.
|
|
54
|
+
# more information can be found in the DuckDB documentation https://duckdb.org/docs/stable/sql/statements/create_table.html.
|
|
55
|
+
try:
|
|
56
|
+
# copy content
|
|
57
|
+
annotation_copy = annotation.model_copy(update={"annotation_label_id": annotation_label_id})
|
|
58
|
+
|
|
59
|
+
annotation_type = annotation_copy.annotation_type
|
|
60
|
+
|
|
61
|
+
annotation_tags = [
|
|
62
|
+
AnnotationTagLinkTable(
|
|
63
|
+
annotation_id=annotation_copy.annotation_id,
|
|
64
|
+
tag_id=tag.tag_id,
|
|
65
|
+
)
|
|
66
|
+
for tag in annotation.tags
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
# we need to create a new annotation details before committing
|
|
70
|
+
# because copy will be gone with the commit
|
|
71
|
+
instance_segmentation = (
|
|
72
|
+
InstanceSegmentationAnnotationTable(
|
|
73
|
+
annotation_id=annotation_copy.annotation_id,
|
|
74
|
+
segmentation_mask=annotation_copy.instance_segmentation_details.segmentation_mask,
|
|
75
|
+
x=annotation_copy.instance_segmentation_details.x,
|
|
76
|
+
y=annotation_copy.instance_segmentation_details.y,
|
|
77
|
+
width=annotation_copy.instance_segmentation_details.width,
|
|
78
|
+
height=annotation_copy.instance_segmentation_details.height,
|
|
79
|
+
)
|
|
80
|
+
if annotation_type == "instance_segmentation"
|
|
81
|
+
and annotation_copy.instance_segmentation_details
|
|
82
|
+
else None
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
object_detection = (
|
|
86
|
+
ObjectDetectionAnnotationTable(
|
|
87
|
+
annotation_id=annotation_copy.annotation_id,
|
|
88
|
+
x=annotation_copy.object_detection_details.x,
|
|
89
|
+
y=annotation_copy.object_detection_details.y,
|
|
90
|
+
width=annotation_copy.object_detection_details.width,
|
|
91
|
+
height=annotation_copy.object_detection_details.height,
|
|
92
|
+
)
|
|
93
|
+
if annotation_type == "object_detection" and annotation_copy.object_detection_details
|
|
94
|
+
else None
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
semantic_segmentation = (
|
|
98
|
+
SemanticSegmentationAnnotationTable(
|
|
99
|
+
annotation_id=annotation_copy.annotation_id,
|
|
100
|
+
segmentation_mask=annotation_copy.semantic_segmentation_details.segmentation_mask,
|
|
101
|
+
)
|
|
102
|
+
if annotation_type == "semantic_segmentation"
|
|
103
|
+
and annotation_copy.semantic_segmentation_details
|
|
104
|
+
else None
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# delete
|
|
108
|
+
annotation_resolver.delete_annotation(session, annotation.annotation_id)
|
|
109
|
+
|
|
110
|
+
new_annotation = AnnotationBaseTable(
|
|
111
|
+
annotation_id=annotation_copy.annotation_id,
|
|
112
|
+
annotation_label_id=annotation_copy.annotation_label_id,
|
|
113
|
+
annotation_type=annotation_copy.annotation_type,
|
|
114
|
+
annotation_task_id=annotation_copy.annotation_task_id,
|
|
115
|
+
confidence=annotation_copy.confidence,
|
|
116
|
+
created_at=annotation_copy.created_at,
|
|
117
|
+
dataset_id=annotation_copy.dataset_id,
|
|
118
|
+
sample_id=annotation_copy.sample_id,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
session.add(new_annotation)
|
|
122
|
+
|
|
123
|
+
if instance_segmentation:
|
|
124
|
+
session.add(instance_segmentation)
|
|
125
|
+
|
|
126
|
+
if object_detection:
|
|
127
|
+
session.add(object_detection)
|
|
128
|
+
|
|
129
|
+
if semantic_segmentation:
|
|
130
|
+
session.add(semantic_segmentation)
|
|
131
|
+
|
|
132
|
+
if annotation_tags:
|
|
133
|
+
session.add_all(annotation_tags)
|
|
134
|
+
|
|
135
|
+
session.commit()
|
|
136
|
+
session.flush()
|
|
137
|
+
|
|
138
|
+
return annotation_copy
|
|
139
|
+
except Exception:
|
|
140
|
+
# Explicit rollback to be safe, then re-raise the original error.
|
|
141
|
+
session.rollback()
|
|
142
|
+
raise
|
|
143
|
+
|
|
144
|
+
return annotation
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Module for handling the update of annotation bounding box coordinates in the database."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from sqlmodel import Session
|
|
9
|
+
|
|
10
|
+
from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
|
|
11
|
+
from lightly_studio.resolvers import annotation_resolver
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class BoundingBoxCoordinates:
|
|
16
|
+
"""Represents bounding box coordinates."""
|
|
17
|
+
|
|
18
|
+
x: int
|
|
19
|
+
y: int
|
|
20
|
+
width: int
|
|
21
|
+
height: int
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def update_bounding_box(
|
|
25
|
+
session: Session,
|
|
26
|
+
annotation_id: UUID,
|
|
27
|
+
coordinates: BoundingBoxCoordinates,
|
|
28
|
+
) -> AnnotationBaseTable:
|
|
29
|
+
"""Update the bounding box coordinates of an annotation.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
session: Database session for executing the operation.
|
|
33
|
+
annotation_id: UUID of the annotation to update.
|
|
34
|
+
coordinates: New bounding box coordinates.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The updated annotation with the new bounding box coordinates.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If the annotation is not found.
|
|
41
|
+
"""
|
|
42
|
+
annotation = annotation_resolver.get_by_id(session, annotation_id)
|
|
43
|
+
if not annotation:
|
|
44
|
+
raise ValueError(f"Annotation with ID {annotation_id} not found.")
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
if annotation.object_detection_details:
|
|
48
|
+
annotation.object_detection_details.x = coordinates.x
|
|
49
|
+
annotation.object_detection_details.y = coordinates.y
|
|
50
|
+
annotation.object_detection_details.width = coordinates.width
|
|
51
|
+
annotation.object_detection_details.height = coordinates.height
|
|
52
|
+
session.add(annotation.object_detection_details)
|
|
53
|
+
|
|
54
|
+
elif annotation.instance_segmentation_details:
|
|
55
|
+
annotation.instance_segmentation_details.x = coordinates.x
|
|
56
|
+
annotation.instance_segmentation_details.y = coordinates.y
|
|
57
|
+
annotation.instance_segmentation_details.width = coordinates.width
|
|
58
|
+
annotation.instance_segmentation_details.height = coordinates.height
|
|
59
|
+
session.add(annotation.instance_segmentation_details)
|
|
60
|
+
else:
|
|
61
|
+
raise ValueError("Annotation type does not support bounding boxes.")
|
|
62
|
+
|
|
63
|
+
session.commit()
|
|
64
|
+
session.refresh(annotation)
|
|
65
|
+
return annotation
|
|
66
|
+
except Exception:
|
|
67
|
+
session.rollback()
|
|
68
|
+
raise
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Resolver for annotation tasks."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional, Sequence
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from sqlmodel import Session, col, select
|
|
7
|
+
|
|
8
|
+
from lightly_studio.models.annotation_task import AnnotationTaskTable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create(session: Session, annotation_task: AnnotationTaskTable) -> AnnotationTaskTable:
|
|
12
|
+
"""Create a new annotation task."""
|
|
13
|
+
session.add(annotation_task)
|
|
14
|
+
session.commit()
|
|
15
|
+
session.refresh(annotation_task)
|
|
16
|
+
return annotation_task
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_by_id(session: Session, annotation_task_id: UUID) -> Optional[AnnotationTaskTable]:
|
|
20
|
+
"""Get an annotation task by ID."""
|
|
21
|
+
statement = select(AnnotationTaskTable).where(
|
|
22
|
+
AnnotationTaskTable.annotation_task_id == annotation_task_id
|
|
23
|
+
)
|
|
24
|
+
return session.exec(statement).first()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_all(session: Session) -> List[AnnotationTaskTable]:
|
|
28
|
+
"""Get all annotation tasks."""
|
|
29
|
+
statement = select(AnnotationTaskTable).order_by(col(AnnotationTaskTable.created_at).asc())
|
|
30
|
+
results: Sequence[AnnotationTaskTable] = session.exec(statement).all()
|
|
31
|
+
return list(results)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Filtering functionality for annotations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
from sqlmodel import col
|
|
9
|
+
|
|
10
|
+
from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
|
|
11
|
+
from lightly_studio.models.annotation_task import AnnotationType
|
|
12
|
+
from lightly_studio.models.sample import SampleTable
|
|
13
|
+
from lightly_studio.models.tag import TagTable
|
|
14
|
+
from lightly_studio.type_definitions import QueryType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AnnotationsFilter(BaseModel):
|
|
18
|
+
"""Handles filtering for annotation queries."""
|
|
19
|
+
|
|
20
|
+
annotation_types: list[AnnotationType] | None = Field(
|
|
21
|
+
default=None,
|
|
22
|
+
description="Types of annotation to filter (e.g., 'object_detection')",
|
|
23
|
+
)
|
|
24
|
+
dataset_ids: list[UUID] | None = Field(default=None, description="List of dataset UUIDs")
|
|
25
|
+
annotation_label_ids: list[UUID] | None = Field(
|
|
26
|
+
default=None, description="List of annotation label UUIDs"
|
|
27
|
+
)
|
|
28
|
+
annotation_tag_ids: list[UUID] | None = Field(default=None, description="List of tag UUIDs")
|
|
29
|
+
sample_tag_ids: list[UUID] | None = Field(
|
|
30
|
+
default=None,
|
|
31
|
+
description="List of sample tag UUIDs to filter annotations by",
|
|
32
|
+
)
|
|
33
|
+
annotation_task_ids: list[UUID] | None = Field(
|
|
34
|
+
default=None, description="List of annotation task UUIDs"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def apply(
|
|
38
|
+
self,
|
|
39
|
+
query: QueryType,
|
|
40
|
+
) -> QueryType:
|
|
41
|
+
"""Apply filters to an annotation query.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
query: The base query to apply filters to
|
|
45
|
+
annotation_table: The SQLModel table class for the annotation type
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The query with filters applied
|
|
49
|
+
"""
|
|
50
|
+
# Filter by dataset
|
|
51
|
+
if self.dataset_ids:
|
|
52
|
+
query = query.where(col(AnnotationBaseTable.dataset_id).in_(self.dataset_ids))
|
|
53
|
+
|
|
54
|
+
# Filter by annotation task
|
|
55
|
+
if self.annotation_task_ids:
|
|
56
|
+
query = query.where(
|
|
57
|
+
col(AnnotationBaseTable.annotation_task_id).in_(self.annotation_task_ids)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Filter by annotation label
|
|
61
|
+
if self.annotation_label_ids:
|
|
62
|
+
query = query.where(
|
|
63
|
+
col(AnnotationBaseTable.annotation_label_id).in_(self.annotation_label_ids)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Filter by annotation tags
|
|
67
|
+
if self.annotation_tag_ids:
|
|
68
|
+
query = (
|
|
69
|
+
query.join(AnnotationBaseTable.tags)
|
|
70
|
+
.where(
|
|
71
|
+
AnnotationBaseTable.tags.any(col(TagTable.tag_id).in_(self.annotation_tag_ids))
|
|
72
|
+
)
|
|
73
|
+
.distinct()
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Filter by sample tags
|
|
77
|
+
if self.sample_tag_ids:
|
|
78
|
+
query = (
|
|
79
|
+
query.join(AnnotationBaseTable.sample)
|
|
80
|
+
.join(SampleTable.tags)
|
|
81
|
+
.where(SampleTable.tags.any(col(TagTable.tag_id).in_(self.sample_tag_ids)))
|
|
82
|
+
.distinct()
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Filter by annotation type
|
|
86
|
+
if self.annotation_types:
|
|
87
|
+
query = query.where(col(AnnotationBaseTable.annotation_type).in_(self.annotation_types))
|
|
88
|
+
|
|
89
|
+
return query
|