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.

Files changed (219) hide show
  1. lightly_studio/__init__.py +11 -0
  2. lightly_studio/api/__init__.py +0 -0
  3. lightly_studio/api/app.py +110 -0
  4. lightly_studio/api/cache.py +77 -0
  5. lightly_studio/api/db.py +133 -0
  6. lightly_studio/api/db_tables.py +32 -0
  7. lightly_studio/api/features.py +7 -0
  8. lightly_studio/api/routes/api/annotation.py +233 -0
  9. lightly_studio/api/routes/api/annotation_label.py +90 -0
  10. lightly_studio/api/routes/api/annotation_task.py +38 -0
  11. lightly_studio/api/routes/api/classifier.py +387 -0
  12. lightly_studio/api/routes/api/dataset.py +182 -0
  13. lightly_studio/api/routes/api/dataset_tag.py +257 -0
  14. lightly_studio/api/routes/api/exceptions.py +96 -0
  15. lightly_studio/api/routes/api/features.py +17 -0
  16. lightly_studio/api/routes/api/metadata.py +37 -0
  17. lightly_studio/api/routes/api/metrics.py +80 -0
  18. lightly_studio/api/routes/api/sample.py +196 -0
  19. lightly_studio/api/routes/api/settings.py +45 -0
  20. lightly_studio/api/routes/api/status.py +19 -0
  21. lightly_studio/api/routes/api/text_embedding.py +48 -0
  22. lightly_studio/api/routes/api/validators.py +17 -0
  23. lightly_studio/api/routes/healthz.py +13 -0
  24. lightly_studio/api/routes/images.py +104 -0
  25. lightly_studio/api/routes/webapp.py +51 -0
  26. lightly_studio/api/server.py +82 -0
  27. lightly_studio/core/__init__.py +0 -0
  28. lightly_studio/core/dataset.py +523 -0
  29. lightly_studio/core/sample.py +77 -0
  30. lightly_studio/core/start_gui.py +15 -0
  31. lightly_studio/dataset/__init__.py +0 -0
  32. lightly_studio/dataset/edge_embedding_generator.py +144 -0
  33. lightly_studio/dataset/embedding_generator.py +91 -0
  34. lightly_studio/dataset/embedding_manager.py +163 -0
  35. lightly_studio/dataset/env.py +16 -0
  36. lightly_studio/dataset/file_utils.py +35 -0
  37. lightly_studio/dataset/loader.py +622 -0
  38. lightly_studio/dataset/mobileclip_embedding_generator.py +144 -0
  39. lightly_studio/dist_lightly_studio_view_app/_app/env.js +1 -0
  40. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.DenzbfeK.css +1 -0
  41. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/LightlyLogo.BNjCIww-.png +0 -0
  42. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans- +0 -0
  43. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Bold.DGvYQtcs.ttf +0 -0
  44. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Italic-VariableFont_wdth_wght.B4AZ-wl6.ttf +0 -0
  45. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Regular.DxJTClRG.ttf +0 -0
  46. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-SemiBold.D3TTYgdB.ttf +0 -0
  47. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-VariableFont_wdth_wght.BZBpG5Iz.ttf +0 -0
  48. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.OwPEPQZu.css +1 -0
  49. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.b653GmVf.css +1 -0
  50. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.T-zjSUd3.css +1 -0
  51. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/useFeatureFlags.CV-KWLNP.css +1 -0
  52. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/69_IOA4Y.js +1 -0
  53. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B2FVR0s0.js +1 -0
  54. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B90CZVMX.js +1 -0
  55. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B9zumHo5.js +1 -0
  56. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BJXwVxaE.js +1 -0
  57. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bsi3UGy5.js +1 -0
  58. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bu7uvVrG.js +1 -0
  59. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bx1xMsFy.js +1 -0
  60. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BylOuP6i.js +1 -0
  61. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C8I8rFJQ.js +1 -0
  62. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CDnpyLsT.js +1 -0
  63. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWj6FrbW.js +1 -0
  64. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CYgJF_JY.js +1 -0
  65. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CcaPhhk3.js +1 -0
  66. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CvOmgdoc.js +93 -0
  67. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CxtLVaYz.js +3 -0
  68. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D5-A_Ffd.js +4 -0
  69. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6RI2Zrd.js +1 -0
  70. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6su9Aln.js +1 -0
  71. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D98V7j6A.js +1 -0
  72. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIRAtgl0.js +1 -0
  73. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIeogL5L.js +1 -0
  74. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DOlTMNyt.js +1 -0
  75. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DjUWrjOv.js +1 -0
  76. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DjfY96ND.js +1 -0
  77. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/H7C68rOM.js +1 -0
  78. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/O-EABkf9.js +1 -0
  79. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/XO7A28GO.js +1 -0
  80. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/hQVEETDE.js +1 -0
  81. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/l7KrR96u.js +1 -0
  82. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/nAHhluT7.js +1 -0
  83. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/r64xT6ao.js +1 -0
  84. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/vC4nQVEB.js +1 -0
  85. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/x9G_hzyY.js +1 -0
  86. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.CjnvpsmS.js +2 -0
  87. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.0o1H7wM9.js +1 -0
  88. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.XRq_TUwu.js +1 -0
  89. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1.B4rNYwVp.js +1 -0
  90. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.DfBwOEhN.js +1 -0
  91. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/11.CWG1ehzT.js +1 -0
  92. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.CwF2_8mP.js +1 -0
  93. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.CS4muRY-.js +6 -0
  94. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/3.CWHpKonm.js +1 -0
  95. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/4.OUWOLQeV.js +1 -0
  96. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.Dm6t9F5W.js +1 -0
  97. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/6.Bw5ck4gK.js +1 -0
  98. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.CF0EDTR6.js +1 -0
  99. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.Cw30LEcV.js +1 -0
  100. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.CPu3CiBc.js +1 -0
  101. lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -0
  102. lightly_studio/dist_lightly_studio_view_app/apple-touch-icon-precomposed.png +0 -0
  103. lightly_studio/dist_lightly_studio_view_app/apple-touch-icon.png +0 -0
  104. lightly_studio/dist_lightly_studio_view_app/favicon.png +0 -0
  105. lightly_studio/dist_lightly_studio_view_app/index.html +44 -0
  106. lightly_studio/examples/example.py +23 -0
  107. lightly_studio/examples/example_metadata.py +338 -0
  108. lightly_studio/examples/example_selection.py +39 -0
  109. lightly_studio/examples/example_split_work.py +67 -0
  110. lightly_studio/examples/example_v2.py +21 -0
  111. lightly_studio/export_schema.py +18 -0
  112. lightly_studio/few_shot_classifier/__init__.py +0 -0
  113. lightly_studio/few_shot_classifier/classifier.py +80 -0
  114. lightly_studio/few_shot_classifier/classifier_manager.py +663 -0
  115. lightly_studio/few_shot_classifier/random_forest_classifier.py +489 -0
  116. lightly_studio/metadata/complex_metadata.py +47 -0
  117. lightly_studio/metadata/gps_coordinate.py +41 -0
  118. lightly_studio/metadata/metadata_protocol.py +17 -0
  119. lightly_studio/metrics/__init__.py +0 -0
  120. lightly_studio/metrics/detection/__init__.py +0 -0
  121. lightly_studio/metrics/detection/map.py +268 -0
  122. lightly_studio/models/__init__.py +1 -0
  123. lightly_studio/models/annotation/__init__.py +0 -0
  124. lightly_studio/models/annotation/annotation_base.py +171 -0
  125. lightly_studio/models/annotation/instance_segmentation.py +56 -0
  126. lightly_studio/models/annotation/links.py +17 -0
  127. lightly_studio/models/annotation/object_detection.py +47 -0
  128. lightly_studio/models/annotation/semantic_segmentation.py +44 -0
  129. lightly_studio/models/annotation_label.py +47 -0
  130. lightly_studio/models/annotation_task.py +28 -0
  131. lightly_studio/models/classifier.py +20 -0
  132. lightly_studio/models/dataset.py +84 -0
  133. lightly_studio/models/embedding_model.py +30 -0
  134. lightly_studio/models/metadata.py +208 -0
  135. lightly_studio/models/sample.py +180 -0
  136. lightly_studio/models/sample_embedding.py +37 -0
  137. lightly_studio/models/settings.py +60 -0
  138. lightly_studio/models/tag.py +96 -0
  139. lightly_studio/py.typed +0 -0
  140. lightly_studio/resolvers/__init__.py +7 -0
  141. lightly_studio/resolvers/annotation_label_resolver/__init__.py +21 -0
  142. lightly_studio/resolvers/annotation_label_resolver/create.py +27 -0
  143. lightly_studio/resolvers/annotation_label_resolver/delete.py +28 -0
  144. lightly_studio/resolvers/annotation_label_resolver/get_all.py +22 -0
  145. lightly_studio/resolvers/annotation_label_resolver/get_by_id.py +24 -0
  146. lightly_studio/resolvers/annotation_label_resolver/get_by_ids.py +25 -0
  147. lightly_studio/resolvers/annotation_label_resolver/get_by_label_name.py +24 -0
  148. lightly_studio/resolvers/annotation_label_resolver/names_by_ids.py +25 -0
  149. lightly_studio/resolvers/annotation_label_resolver/update.py +38 -0
  150. lightly_studio/resolvers/annotation_resolver/__init__.py +33 -0
  151. lightly_studio/resolvers/annotation_resolver/count_annotations_by_dataset.py +120 -0
  152. lightly_studio/resolvers/annotation_resolver/create.py +19 -0
  153. lightly_studio/resolvers/annotation_resolver/create_many.py +96 -0
  154. lightly_studio/resolvers/annotation_resolver/delete_annotation.py +45 -0
  155. lightly_studio/resolvers/annotation_resolver/delete_annotations.py +56 -0
  156. lightly_studio/resolvers/annotation_resolver/get_all.py +74 -0
  157. lightly_studio/resolvers/annotation_resolver/get_by_id.py +18 -0
  158. lightly_studio/resolvers/annotation_resolver/update_annotation_label.py +144 -0
  159. lightly_studio/resolvers/annotation_resolver/update_bounding_box.py +68 -0
  160. lightly_studio/resolvers/annotation_task_resolver.py +31 -0
  161. lightly_studio/resolvers/annotations/__init__.py +1 -0
  162. lightly_studio/resolvers/annotations/annotations_filter.py +89 -0
  163. lightly_studio/resolvers/dataset_resolver.py +278 -0
  164. lightly_studio/resolvers/embedding_model_resolver.py +100 -0
  165. lightly_studio/resolvers/metadata_resolver/__init__.py +15 -0
  166. lightly_studio/resolvers/metadata_resolver/metadata_filter.py +163 -0
  167. lightly_studio/resolvers/metadata_resolver/sample/__init__.py +21 -0
  168. lightly_studio/resolvers/metadata_resolver/sample/bulk_set_metadata.py +48 -0
  169. lightly_studio/resolvers/metadata_resolver/sample/get_by_sample_id.py +24 -0
  170. lightly_studio/resolvers/metadata_resolver/sample/get_metadata_info.py +104 -0
  171. lightly_studio/resolvers/metadata_resolver/sample/get_value_for_sample.py +27 -0
  172. lightly_studio/resolvers/metadata_resolver/sample/set_value_for_sample.py +53 -0
  173. lightly_studio/resolvers/sample_embedding_resolver.py +86 -0
  174. lightly_studio/resolvers/sample_resolver.py +249 -0
  175. lightly_studio/resolvers/samples_filter.py +81 -0
  176. lightly_studio/resolvers/settings_resolver.py +58 -0
  177. lightly_studio/resolvers/tag_resolver.py +276 -0
  178. lightly_studio/selection/README.md +6 -0
  179. lightly_studio/selection/mundig.py +105 -0
  180. lightly_studio/selection/select.py +96 -0
  181. lightly_studio/selection/select_via_db.py +93 -0
  182. lightly_studio/selection/selection_config.py +31 -0
  183. lightly_studio/services/annotations_service/__init__.py +21 -0
  184. lightly_studio/services/annotations_service/get_annotation_by_id.py +31 -0
  185. lightly_studio/services/annotations_service/update_annotation.py +65 -0
  186. lightly_studio/services/annotations_service/update_annotation_label.py +48 -0
  187. lightly_studio/services/annotations_service/update_annotations.py +29 -0
  188. lightly_studio/setup_logging.py +19 -0
  189. lightly_studio/type_definitions.py +19 -0
  190. lightly_studio/vendor/ACKNOWLEDGEMENTS +422 -0
  191. lightly_studio/vendor/LICENSE +31 -0
  192. lightly_studio/vendor/LICENSE_weights_data +50 -0
  193. lightly_studio/vendor/README.md +5 -0
  194. lightly_studio/vendor/__init__.py +1 -0
  195. lightly_studio/vendor/mobileclip/__init__.py +96 -0
  196. lightly_studio/vendor/mobileclip/clip.py +77 -0
  197. lightly_studio/vendor/mobileclip/configs/mobileclip_b.json +18 -0
  198. lightly_studio/vendor/mobileclip/configs/mobileclip_s0.json +18 -0
  199. lightly_studio/vendor/mobileclip/configs/mobileclip_s1.json +18 -0
  200. lightly_studio/vendor/mobileclip/configs/mobileclip_s2.json +18 -0
  201. lightly_studio/vendor/mobileclip/image_encoder.py +67 -0
  202. lightly_studio/vendor/mobileclip/logger.py +154 -0
  203. lightly_studio/vendor/mobileclip/models/__init__.py +10 -0
  204. lightly_studio/vendor/mobileclip/models/mci.py +933 -0
  205. lightly_studio/vendor/mobileclip/models/vit.py +433 -0
  206. lightly_studio/vendor/mobileclip/modules/__init__.py +4 -0
  207. lightly_studio/vendor/mobileclip/modules/common/__init__.py +4 -0
  208. lightly_studio/vendor/mobileclip/modules/common/mobileone.py +341 -0
  209. lightly_studio/vendor/mobileclip/modules/common/transformer.py +451 -0
  210. lightly_studio/vendor/mobileclip/modules/image/__init__.py +4 -0
  211. lightly_studio/vendor/mobileclip/modules/image/image_projection.py +113 -0
  212. lightly_studio/vendor/mobileclip/modules/image/replknet.py +188 -0
  213. lightly_studio/vendor/mobileclip/modules/text/__init__.py +4 -0
  214. lightly_studio/vendor/mobileclip/modules/text/repmixer.py +281 -0
  215. lightly_studio/vendor/mobileclip/modules/text/tokenizer.py +38 -0
  216. lightly_studio/vendor/mobileclip/text_encoder.py +245 -0
  217. lightly_studio-0.3.1.dist-info/METADATA +520 -0
  218. lightly_studio-0.3.1.dist-info/RECORD +219 -0
  219. 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,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