lightly-studio 0.3.2__py3-none-any.whl → 0.3.4__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 (163) hide show
  1. lightly_studio/__init__.py +1 -1
  2. lightly_studio/api/app.py +8 -4
  3. lightly_studio/api/db_tables.py +0 -3
  4. lightly_studio/api/routes/api/annotation.py +26 -0
  5. lightly_studio/api/routes/api/annotations/__init__.py +7 -0
  6. lightly_studio/api/routes/api/annotations/create_annotation.py +52 -0
  7. lightly_studio/api/routes/api/caption.py +30 -0
  8. lightly_studio/api/routes/api/dataset.py +3 -5
  9. lightly_studio/api/routes/api/embeddings2d.py +136 -0
  10. lightly_studio/api/routes/api/export.py +73 -0
  11. lightly_studio/api/routes/api/metadata.py +57 -1
  12. lightly_studio/api/routes/api/selection.py +87 -0
  13. lightly_studio/core/add_samples.py +138 -9
  14. lightly_studio/core/dataset.py +174 -63
  15. lightly_studio/core/dataset_query/dataset_query.py +5 -0
  16. lightly_studio/dataset/env.py +4 -0
  17. lightly_studio/dataset/file_utils.py +13 -2
  18. lightly_studio/dataset/loader.py +2 -62
  19. lightly_studio/dataset/mobileclip_embedding_generator.py +3 -2
  20. lightly_studio/db_manager.py +10 -4
  21. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.B3oFNb6O.css +1 -0
  22. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/2.CkOblLn7.css +1 -0
  23. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/Samples.CIbricz7.css +1 -0
  24. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.7Ma7YdVg.css +1 -0
  25. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/{useFeatureFlags.CV-KWLNP.css → _layout.CefECEWA.css} +1 -1
  26. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/transform.2jKMtOWG.css +1 -0
  27. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/-DXuGN29.js +1 -0
  28. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Ccq4ZD0B.js → B7302SU7.js} +1 -1
  29. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BeWf8-vJ.js +1 -0
  30. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bqz7dyEC.js +1 -0
  31. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C1FmrZbK.js +1 -0
  32. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{DRZO-E-T.js → CSCQddQS.js} +1 -1
  33. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CZGpyrcA.js +1 -0
  34. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CfQ4mGwl.js +1 -0
  35. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CiaNZCBa.js +1 -0
  36. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cqo0Vpvt.js +417 -0
  37. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cy4fgWTG.js +1 -0
  38. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D5w4xp5l.js +1 -0
  39. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DD63uD-T.js +1 -0
  40. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DQ8aZ1o-.js +3 -0
  41. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Df3aMO5B.js → DSxvnAMh.js} +1 -1
  42. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D_JuJOO3.js +20 -0
  43. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D_ynJAfY.js +2 -0
  44. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Dafy4oEQ.js +1 -0
  45. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{BqBqV92V.js → Dj4O-5se.js} +1 -1
  46. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DmjAI-UV.js +1 -0
  47. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Dug7Bq1S.js +1 -0
  48. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Dv5BSBQG.js +1 -0
  49. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DzBTnFhV.js +1 -0
  50. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DzX_yyqb.js +1 -0
  51. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Frwd2CjB.js +1 -0
  52. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/H4l0JFh9.js +1 -0
  53. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/H60ATh8g.js +2 -0
  54. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/qIv1kPyv.js +1 -0
  55. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/sLqs1uaK.js +20 -0
  56. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/u-it74zV.js +96 -0
  57. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.BPc0HQPq.js +2 -0
  58. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.SNvc2nrm.js +1 -0
  59. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.5jT7P06o.js +1 -0
  60. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1.Cdy-7S5q.js +1 -0
  61. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.C_uoESTX.js +1 -0
  62. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.DcO8wIAc.js +1 -0
  63. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.BIldfkxL.js +1012 -0
  64. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{3.w9g4AcAx.js → 3.BC9z_TWM.js} +1 -1
  65. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{4.BBI8KwnD.js → 4.D8X_Ch5n.js} +1 -1
  66. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.CAXhxJu6.js +39 -0
  67. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{6.CrbkRPam.js → 6.DRA5Ru_2.js} +1 -1
  68. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.WVBsruHQ.js +1 -0
  69. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.BuKUrCEN.js +20 -0
  70. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.CUIn1yCR.js +1 -0
  71. lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/clustering.worker-DKqeLtG0.js +2 -0
  72. lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/search.worker-vNSty3B0.js +1 -0
  73. lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -1
  74. lightly_studio/dist_lightly_studio_view_app/index.html +15 -14
  75. lightly_studio/examples/example.py +4 -0
  76. lightly_studio/examples/example_coco.py +4 -0
  77. lightly_studio/examples/example_coco_caption.py +24 -0
  78. lightly_studio/examples/example_metadata.py +4 -1
  79. lightly_studio/examples/example_selection.py +4 -0
  80. lightly_studio/examples/example_split_work.py +4 -0
  81. lightly_studio/examples/example_yolo.py +4 -0
  82. lightly_studio/export/export_dataset.py +73 -0
  83. lightly_studio/export/lightly_studio_label_input.py +120 -0
  84. lightly_studio/few_shot_classifier/classifier_manager.py +5 -26
  85. lightly_studio/metadata/compute_typicality.py +67 -0
  86. lightly_studio/models/annotation/annotation_base.py +11 -12
  87. lightly_studio/models/caption.py +73 -0
  88. lightly_studio/models/dataset.py +1 -2
  89. lightly_studio/models/metadata.py +1 -1
  90. lightly_studio/models/sample.py +2 -2
  91. lightly_studio/resolvers/annotation_label_resolver/__init__.py +2 -1
  92. lightly_studio/resolvers/annotation_label_resolver/get_all.py +15 -0
  93. lightly_studio/resolvers/annotation_resolver/__init__.py +2 -3
  94. lightly_studio/resolvers/annotation_resolver/create_many.py +3 -3
  95. lightly_studio/resolvers/annotation_resolver/delete_annotation.py +1 -1
  96. lightly_studio/resolvers/annotation_resolver/delete_annotations.py +7 -3
  97. lightly_studio/resolvers/annotation_resolver/get_by_id.py +19 -1
  98. lightly_studio/resolvers/annotation_resolver/update_annotation_label.py +0 -1
  99. lightly_studio/resolvers/annotations/annotations_filter.py +1 -11
  100. lightly_studio/resolvers/caption_resolver.py +80 -0
  101. lightly_studio/resolvers/dataset_resolver.py +4 -7
  102. lightly_studio/resolvers/metadata_resolver/__init__.py +2 -2
  103. lightly_studio/resolvers/metadata_resolver/sample/__init__.py +3 -3
  104. lightly_studio/resolvers/metadata_resolver/sample/bulk_update_metadata.py +46 -0
  105. lightly_studio/resolvers/samples_filter.py +18 -10
  106. lightly_studio/selection/mundig.py +7 -10
  107. lightly_studio/selection/selection_config.py +4 -1
  108. lightly_studio/services/annotations_service/__init__.py +8 -0
  109. lightly_studio/services/annotations_service/create_annotation.py +63 -0
  110. lightly_studio/services/annotations_service/delete_annotation.py +22 -0
  111. lightly_studio/type_definitions.py +2 -0
  112. {lightly_studio-0.3.2.dist-info → lightly_studio-0.3.4.dist-info}/METADATA +231 -41
  113. {lightly_studio-0.3.2.dist-info → lightly_studio-0.3.4.dist-info}/RECORD +114 -104
  114. lightly_studio/api/routes/api/annotation_task.py +0 -37
  115. lightly_studio/api/routes/api/metrics.py +0 -76
  116. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.DenzbfeK.css +0 -1
  117. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.BBm0IWdq.css +0 -1
  118. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.BNTuXSAe.css +0 -1
  119. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.T-zjSUd3.css +0 -1
  120. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/2O287xak.js +0 -3
  121. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/7YNGEs1C.js +0 -1
  122. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BBoGk9hq.js +0 -1
  123. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BRnH9v23.js +0 -92
  124. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bg1Y5eUZ.js +0 -1
  125. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C0JiMuYn.js +0 -1
  126. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C98Hk3r5.js +0 -1
  127. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CG0dMCJi.js +0 -1
  128. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cpy-nab_.js +0 -1
  129. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Crk-jcvV.js +0 -1
  130. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cs31G8Qn.js +0 -1
  131. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CsKrY2zA.js +0 -1
  132. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cur71c3O.js +0 -1
  133. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CzgC3GFB.js +0 -1
  134. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D8GZDMNN.js +0 -1
  135. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DFRh-Spp.js +0 -1
  136. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DcGCxgpH.js +0 -1
  137. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DkR_EZ_B.js +0 -1
  138. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DqUGznj_.js +0 -1
  139. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/H7C68rOM.js +0 -1
  140. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/KpAtIldw.js +0 -1
  141. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/M1Q1F7bw.js +0 -4
  142. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/OH7-C_mc.js +0 -1
  143. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/gLNdjSzu.js +0 -1
  144. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/i0ZZ4z06.js +0 -1
  145. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.BI-EA5gL.js +0 -2
  146. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.CcsRl3cZ.js +0 -1
  147. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.BbO4Zc3r.js +0 -1
  148. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1._I9GR805.js +0 -1
  149. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.J2RBFrSr.js +0 -1
  150. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.Cmqj25a-.js +0 -1
  151. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.C45iKJHA.js +0 -6
  152. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.huHuxdiF.js +0 -1
  153. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.FomEdhD6.js +0 -1
  154. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.Cb_ADSLk.js +0 -1
  155. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.CajIG5ce.js +0 -1
  156. lightly_studio/metrics/__init__.py +0 -0
  157. lightly_studio/metrics/detection/__init__.py +0 -0
  158. lightly_studio/metrics/detection/map.py +0 -268
  159. lightly_studio/models/annotation_task.py +0 -28
  160. lightly_studio/resolvers/annotation_resolver/create.py +0 -19
  161. lightly_studio/resolvers/annotation_task_resolver.py +0 -31
  162. lightly_studio/resolvers/metadata_resolver/sample/bulk_set_metadata.py +0 -48
  163. {lightly_studio-0.3.2.dist-info → lightly_studio-0.3.4.dist-info}/WHEEL +0 -0
@@ -33,20 +33,15 @@ class SampleFilter(BaseModel):
33
33
  annotation_label_ids: Optional[List[UUID]] = None
34
34
  tag_ids: Optional[List[UUID]] = None
35
35
  metadata_filters: Optional[List[MetadataFilter]] = None
36
+ sample_ids: Optional[List[UUID]] = None
36
37
 
37
38
  def apply(self, query: QueryType) -> QueryType:
38
39
  """Apply the filters to the given query."""
40
+ if self.sample_ids:
41
+ query = query.where(col(SampleTable.sample_id).in_(self.sample_ids))
42
+
39
43
  # Apply dimension-based filters to the query.
40
- if self.width:
41
- if self.width.min is not None:
42
- query = query.where(SampleTable.width >= self.width.min)
43
- if self.width.max is not None:
44
- query = query.where(SampleTable.width <= self.width.max)
45
- if self.height:
46
- if self.height.min is not None:
47
- query = query.where(SampleTable.height >= self.height.min)
48
- if self.height.max is not None:
49
- query = query.where(SampleTable.height <= self.height.max)
44
+ query = self._apply_dimension_filters(query)
50
45
 
51
46
  # Apply annotation label filters to the query.
52
47
  if self.annotation_label_ids:
@@ -79,3 +74,16 @@ class SampleFilter(BaseModel):
79
74
  metadata_join_condition=SampleMetadataTable.sample_id == SampleTable.sample_id,
80
75
  )
81
76
  return query
77
+
78
+ def _apply_dimension_filters(self, query: QueryType) -> QueryType:
79
+ if self.width:
80
+ if self.width.min is not None:
81
+ query = query.where(SampleTable.width >= self.width.min)
82
+ if self.width.max is not None:
83
+ query = query.where(SampleTable.width <= self.width.max)
84
+ if self.height:
85
+ if self.height.min is not None:
86
+ query = query.where(SampleTable.height >= self.height.min)
87
+ if self.height.max is not None:
88
+ query = query.where(SampleTable.height <= self.height.max)
89
+ return query
@@ -10,29 +10,26 @@ from typing import Iterable
10
10
  # Or remove the type ignore once typing stubs were added manually.
11
11
  import lightly_mundig # type: ignore[import-untyped]
12
12
  import numpy as np
13
- from environs import Env
13
+
14
+ from lightly_studio.dataset.env import LIGHTLY_STUDIO_LICENSE_KEY
14
15
 
15
16
 
16
17
  class Mundig:
17
- """Python wrapper for the Mundig selection algorithm.
18
+ """Python interface for the Mundig selection algorithm.
18
19
 
19
20
  This class provides a Python interface to the lightly_mundig Rust library
20
- for sample selection.
21
+ for sample selection. It allows combining different selection strategies
22
+ such as diversity and weighting.
21
23
  """
22
24
 
23
25
  def __init__(self) -> None:
24
26
  """Initialize the Mundig selection interface."""
25
- # Read LIGHTLY_STUDIO_LICENSE_KEY with .env file support
26
- env = Env()
27
- env.read_env()
28
- license_key = env.str("LIGHTLY_STUDIO_LICENSE_KEY", default=None)
29
- if license_key is None:
27
+ if LIGHTLY_STUDIO_LICENSE_KEY is None:
30
28
  raise ValueError(
31
29
  "LIGHTLY_STUDIO_LICENSE_KEY environment variable is not set. "
32
30
  "Please set it to your LightlyStudio license key."
33
31
  )
34
-
35
- self.mundig = lightly_mundig.Selection(token=license_key)
32
+ self.mundig = lightly_mundig.Selection(token=LIGHTLY_STUDIO_LICENSE_KEY)
36
33
 
37
34
  self.n_input_samples: int | None = None
38
35
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from typing import Literal, Sequence
5
6
  from uuid import UUID
6
7
 
7
8
  from pydantic import BaseModel
@@ -13,7 +14,7 @@ class SelectionConfig(BaseModel):
13
14
  dataset_id: UUID
14
15
  n_samples_to_select: int
15
16
  selection_result_tag_name: str
16
- strategies: list[SelectionStrategy]
17
+ strategies: Sequence[SelectionStrategy]
17
18
 
18
19
 
19
20
  class SelectionStrategy(BaseModel):
@@ -25,10 +26,12 @@ class SelectionStrategy(BaseModel):
25
26
  class EmbeddingDiversityStrategy(SelectionStrategy):
26
27
  """Selection strategy based on embedding diversity."""
27
28
 
29
+ strategy_name: Literal["diversity"] = "diversity"
28
30
  embedding_model_name: str | None
29
31
 
30
32
 
31
33
  class MetadataWeightingStrategy(SelectionStrategy):
32
34
  """Selection strategy based on metadata weighting."""
33
35
 
36
+ strategy_name: Literal["weights"] = "weights"
34
37
  metadata_key: str
@@ -1,5 +1,11 @@
1
1
  """Services for annotations operations."""
2
2
 
3
+ from lightly_studio.services.annotations_service.create_annotation import (
4
+ create_annotation,
5
+ )
6
+ from lightly_studio.services.annotations_service.delete_annotation import (
7
+ delete_annotation,
8
+ )
3
9
  from lightly_studio.services.annotations_service.get_annotation_by_id import (
4
10
  get_annotation_by_id,
5
11
  )
@@ -17,6 +23,8 @@ from lightly_studio.services.annotations_service.update_annotations import (
17
23
  )
18
24
 
19
25
  __all__ = [
26
+ "create_annotation",
27
+ "delete_annotation",
20
28
  "get_annotation_by_id",
21
29
  "update_annotation",
22
30
  "update_annotation_bounding_box",
@@ -0,0 +1,63 @@
1
+ """Create annotation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from pydantic import BaseModel
8
+ from sqlmodel import Session
9
+
10
+ from lightly_studio.models.annotation.annotation_base import (
11
+ AnnotationBaseTable,
12
+ AnnotationCreate,
13
+ AnnotationType,
14
+ )
15
+ from lightly_studio.resolvers import annotation_resolver
16
+
17
+
18
+ class AnnotationCreateParams(BaseModel):
19
+ """Input model for create annotation service."""
20
+
21
+ annotation_label_id: UUID
22
+ annotation_type: AnnotationType
23
+ dataset_id: UUID
24
+ sample_id: UUID
25
+
26
+ x: int | None = None
27
+ y: int | None = None
28
+ width: int | None = None
29
+ height: int | None = None
30
+
31
+ segmentation_mask: list[int] | None = None
32
+
33
+
34
+ def create_annotation(session: Session, annotation: AnnotationCreateParams) -> AnnotationBaseTable:
35
+ """Create a new annotation.
36
+
37
+ Args:
38
+ session: Database session for executing the operation.
39
+ annotation: Annotation data to create.
40
+
41
+ Returns:
42
+ The retrieved annotation.
43
+ """
44
+ annotation_create = AnnotationCreate(
45
+ **annotation.model_dump(),
46
+ )
47
+ new_annotation_ids = annotation_resolver.create_many(
48
+ session=session,
49
+ annotations=[annotation_create],
50
+ )
51
+
52
+ if not new_annotation_ids:
53
+ raise ValueError("Failed to create annotation.")
54
+
55
+ created_annotation = annotation_resolver.get_by_id(
56
+ session=session,
57
+ annotation_id=new_annotation_ids[0],
58
+ )
59
+
60
+ if created_annotation is None:
61
+ raise ValueError(f"Failed to create annotation: {annotation}")
62
+
63
+ return created_annotation
@@ -0,0 +1,22 @@
1
+ """Delete an annotation by its ID."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlmodel import Session
8
+
9
+ from lightly_studio.resolvers import annotation_resolver
10
+
11
+
12
+ def delete_annotation(session: Session, annotation_id: UUID) -> None:
13
+ """Delete an annotation by its ID.
14
+
15
+ Args:
16
+ session: Database session for executing the operation.
17
+ annotation_id: ID of the annotation to delete.
18
+
19
+ Raises:
20
+ ValueError: If the annotation with the given ID is not found.
21
+ """
22
+ annotation_resolver.delete_annotation(session=session, annotation_id=annotation_id)
@@ -2,6 +2,7 @@
2
2
 
3
3
  from pathlib import Path
4
4
  from typing import TypeVar, Union
5
+ from uuid import UUID
5
6
 
6
7
  from sqlmodel.sql.expression import SelectOfScalar
7
8
 
@@ -14,6 +15,7 @@ QueryType = TypeVar(
14
15
  SelectOfScalar[AnnotationBaseTable],
15
16
  SelectOfScalar[SampleTable],
16
17
  SelectOfScalar[int],
18
+ SelectOfScalar[UUID],
17
19
  )
18
20
 
19
21
  PathLike = Union[str, Path]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightly-studio
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: LightlyStudio is a lightweight, fast, and easy-to-use data exploration tool for data scientists and engineers.
5
5
  Classifier: Operating System :: MacOS :: MacOS X
6
6
  Classifier: Operating System :: Microsoft :: Windows
@@ -10,7 +10,8 @@ Classifier: Programming Language :: Python :: 3.8
10
10
  Classifier: Programming Language :: Python :: 3.9
11
11
  Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
- Requires-Python: >=3.8
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Python: <3.13,>=3.8
14
15
  Requires-Dist: annotated-types==0.7.0
15
16
  Requires-Dist: duckdb-engine<0.17,>=0.15.0
16
17
  Requires-Dist: duckdb<1.3,>=1.2.2
@@ -20,16 +21,23 @@ Requires-Dist: fastapi>=0.115.5
20
21
  Requires-Dist: faster-coco-eval>=1.6.5
21
22
  Requires-Dist: fsspec>=2023.1.0
22
23
  Requires-Dist: labelformat>=0.1.7
23
- Requires-Dist: lightly-mundig==0.1.3
24
+ Requires-Dist: lightly-mundig==0.1.4
24
25
  Requires-Dist: open-clip-torch>=2.20.0
26
+ Requires-Dist: pyarrow>=17.0.0
25
27
  Requires-Dist: python-multipart>=0.0.20
26
28
  Requires-Dist: scikit-learn==1.3.2
27
29
  Requires-Dist: sqlmodel>=0.0.22
28
- Requires-Dist: torchmetrics>=1.5.2
29
30
  Requires-Dist: tqdm>=4.65.0
30
31
  Requires-Dist: typing-extensions>=4.12.2
31
32
  Requires-Dist: uvicorn>=0.32.1
32
33
  Requires-Dist: xxhash>=3.5.0
34
+ Provides-Extra: cloud-storage
35
+ Requires-Dist: adlfs>=2023.1.0; extra == 'cloud-storage'
36
+ Requires-Dist: gcsfs>=2023.1.0; extra == 'cloud-storage'
37
+ Requires-Dist: s3fs>=2023.1.0; extra == 'cloud-storage'
38
+ Provides-Extra: lightly-edge
39
+ Requires-Dist: lightly-edge-sdk>=1.0.1b2; extra == 'lightly-edge'
40
+ Requires-Dist: opencv-python; extra == 'lightly-edge'
33
41
  Description-Content-Type: text/markdown
34
42
 
35
43
  <div align="center">
@@ -53,7 +61,7 @@ Description-Content-Type: text/markdown
53
61
 
54
62
  We at **[Lightly](https://lightly.ai)** created **LightlyStudio**, an open-source tool
55
63
  designed to supercharge your data curation workflows for computer vision datasets. Explore
56
- your data, visualize annotations and crops, tag samples, and export curated lists to improve
64
+ your data, visualize captions, annotations and crops, tag samples, and export curated lists to improve
57
65
  your machine learning pipelines. And much more!
58
66
 
59
67
  LightlyStudio runs entirely locally on your machine, keeping your data private. It consists
@@ -65,7 +73,7 @@ Using LightlyStudio typically involves these steps:
65
73
 
66
74
  1. **Index Your Dataset:** Run a Python script using the `lightly_studio` library to process your local dataset (images and annotations) and save metadata into a local `lightly_studio.db` file.
67
75
  2. **Launch the UI:** The script then starts a local web server.
68
- 3. **Explore & Curate:** Use the UI to visualize images, annotations, and object crops. Filter and search your data (experimental text search available). Apply tags to interesting samples (e.g., "mislabeled", "review").
76
+ 3. **Explore & Curate:** Use the UI to visualize images, annotations, captions, and object crops. Filter and search your data (experimental text search available). Apply tags to interesting samples (e.g., "mislabeled", "review").
69
77
  4. **Export Curated Data:** Export information (like filenames) for your tagged samples from the UI to use downstream.
70
78
  5. **Stop the Server:** Close the terminal running the script (Ctrl+C) when done.
71
79
 
@@ -104,7 +112,7 @@ The library is OS-independent and works on Windows, Linux, and macOS.
104
112
 
105
113
  ```shell
106
114
  # 1. Create and activate a virtual environment (Recommended)
107
- # On Linux/macOS:
115
+ # On Linux/MacOS:
108
116
  python3 -m venv venv
109
117
  source venv/bin/activate
110
118
 
@@ -113,41 +121,44 @@ python -m venv venv
113
121
  .\venv\Scripts\activate
114
122
 
115
123
  # 2. Install LightlyStudio
116
- pip install lightly_studio
124
+ pip install lightly-studio
117
125
  ```
118
126
 
119
127
  ## **Quickstart**
120
128
 
121
- Download the dataset and run a quickstart script to load your dataset and launch the app.
122
-
123
- ### YOLO Object Detection
124
-
125
- To run an example using a yolo dataset, clone the example repository and run the example script from below:
129
+ Download example datasets by cloning the example repository:
126
130
 
127
131
  ```shell
128
132
  git clone https://github.com/lightly-ai/dataset_examples dataset_examples
129
133
  ```
130
134
 
131
- **`example_yolo.py` script to explore the dataset:**
135
+ ### YOLO Object Detection
136
+
137
+ To run an example using a YOLO dataset, create a file named `example_yolo.py` with the
138
+ following contents in the same directory that contains the `dataset_examples/` folder:
132
139
 
133
140
  ```python
134
- from pathlib import Path
141
+ # example_yolo.py
135
142
 
136
143
  import lightly_studio as ls
137
144
 
138
- data_yaml_path = Path(__file__).resolve().parent / "data.yaml"
139
-
140
145
  # Create a dataset and add the samples from the yolo format
141
146
  dataset = ls.Dataset.create()
142
147
  dataset.add_samples_from_yolo(
143
- data_yaml=data_yaml_path,
144
- input_split="test",
148
+ data_yaml="dataset_examples/road_signs_yolo/data.yaml",
145
149
  )
146
150
 
147
151
  # Start the UI application on the port 8001.
148
152
  ls.start_gui()
153
+ ```
154
+
155
+ Run the script:
149
156
 
150
157
  ```
158
+ python example_yolo.py
159
+ ```
160
+
161
+ When you are done, stop the app by pressing Ctrl+C in the terminal.
151
162
 
152
163
  <details>
153
164
  <summary>The YOLO format details:</summary>
@@ -183,26 +194,20 @@ Where coordinates are normalized between 0 and 1.
183
194
 
184
195
  ### COCO Instance Segmentation
185
196
 
186
- To run an instance segmentation example using a COCO dataset, clone the example repository and run the example script from below:
187
-
188
- ```shell
189
- git clone https://github.com/lightly-ai/dataset_examples dataset_examples
190
- ```
191
-
192
- **`example_coco.py` script to explore the dataset:**
197
+ To run an instance segmentation example using a COCO dataset, create a file named
198
+ `example_coco.py` with the following contents in the same directory that contains
199
+ the `dataset_examples/` folder:
193
200
 
194
201
  ```python
195
- from pathlib import Path
202
+ # example_coco.py
196
203
 
197
204
  import lightly_studio as ls
198
205
 
199
- current_dir = Path(__file__).resolve().parent
200
-
201
206
  # Create a dataset and add the samples from the coco format
202
207
  dataset = ls.Dataset.create()
203
208
  dataset.add_samples_from_coco(
204
- annotations_json=current_dir / "instances_train2017.json",
205
- images_path=current_dir / "images",
209
+ annotations_json="dataset_examples/coco_subset_128_images/instances_train2017.json",
210
+ images_path="dataset_examples/coco_subset_128_images/images",
206
211
  annotation_type=ls.AnnotationType.INSTANCE_SEGMENTATION,
207
212
  )
208
213
 
@@ -210,6 +215,14 @@ dataset.add_samples_from_coco(
210
215
  ls.start_gui()
211
216
  ```
212
217
 
218
+ Run the script:
219
+
220
+ ```
221
+ python example_coco.py
222
+ ```
223
+
224
+ When you are done, stop the app by pressing Ctrl+C in the terminal.
225
+
213
226
  <details>
214
227
  <summary>The COCO format details:</summary>
215
228
 
@@ -230,6 +243,56 @@ COCO uses a single JSON file containing all annotations. The format consists of
230
243
 
231
244
  </details>
232
245
 
246
+ ### COCO Captions
247
+
248
+ To run a caption example using a COCO dataset, create a file named
249
+ `example_coco_captions.py` with the following contents in the same directory that contains
250
+ the `dataset_examples/` folder:
251
+
252
+ ```python
253
+ # example_coco_captions.py
254
+
255
+ import lightly_studio as ls
256
+
257
+ # Create a dataset and add the samples from the coco format
258
+ dataset = ls.Dataset.create()
259
+ dataset.add_samples_from_coco_caption(
260
+ annotations_json="dataset_examples/coco_subset_128_images/captions_train2017.json",
261
+ images_path="dataset_examples/coco_subset_128_images/images",
262
+ )
263
+
264
+ # Start the UI application on the port 8001.
265
+ ls.start_gui()
266
+ ```
267
+
268
+ Run the script:
269
+
270
+ ```
271
+ python example_coco_captions.py
272
+ ```
273
+
274
+ Now you can inspect samples with their assigned captions in the app. When you are done,
275
+ stop the app by pressing Ctrl+C in the terminal.
276
+
277
+ <details>
278
+ <summary>The COCO format details:</summary>
279
+
280
+ ```
281
+ coco_subset_128_images/
282
+ ├── images/
283
+ │ ├── image1.jpg
284
+ │ ├── image2.jpg
285
+ │ └── ...
286
+ └── captions_train2017.json # Single JSON file containing all captions
287
+ ```
288
+
289
+ COCO uses a single JSON file containing all captions. The format consists of three main components:
290
+
291
+ - Images: Defines metadata for each image in the dataset.
292
+ - Annotations: Defines the captions.
293
+
294
+ </details>
295
+
233
296
  ## 🔍 How It Works
234
297
 
235
298
  1. Your **Python script** uses the `lightly_studio` **Dataset**.
@@ -253,6 +316,53 @@ dataset.add_samples_from_path(path="/path/to/image_dataset")
253
316
  ls.start_gui()
254
317
  ```
255
318
 
319
+ #### ☁️ Cloud Storage Support
320
+
321
+ #### Installation with Cloud Storage Support
322
+
323
+ ```shell
324
+ pip install lightly-studio[cloud-storage]
325
+ ```
326
+
327
+ #### Example: Loading Dataset from Cloud Storage
328
+
329
+ ```python
330
+ import lightly_studio as ls
331
+
332
+ dataset = ls.Dataset.create()
333
+
334
+ # Load dataset from S3
335
+ dataset.add_samples_from_path(path="s3://my-bucket/path/to/images/")
336
+
337
+ # You can use glob pattern in the file path
338
+ dataset.add_samples_from_path(path="s3://my-bucket/path/to/images/**/*.jpg") # matches all .jpg files recursively
339
+
340
+ # Load dataset from gcs
341
+ dataset.add_samples_from_path(path="gs://path/to/images/")
342
+
343
+ ls.start_gui()
344
+ ```
345
+
346
+ **Note**: Currently, cloud storage support is limited to loading images only. Annotation files (YOLO labels, COCO JSON files, etc.) cannot be loaded directly from cloud storage paths.
347
+
348
+ #### Authentication
349
+
350
+ **Important**: Cloud storage authentication must be configured before running LightlyStudio. The application relies on your existing cloud storage credentials and will not prompt for authentication.
351
+
352
+ #### AWS S3
353
+
354
+ You can use either of the following two options:
355
+
356
+ - **Set environment variables manually**: Set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` (LightlyStudio uses `s3fs` under the hood to connect to S3)
357
+ - **Authenticate using AWS CLI**: Run `aws configure` (this will automatically set the environment variables that LightlyStudio can access)
358
+
359
+ #### Google Cloud Storage
360
+
361
+ You can use either of the following two options:
362
+
363
+ - **Set environment variable manually**: Set `GOOGLE_APPLICATION_CREDENTIALS` pointing to your service account key file (LightlyStudio uses `gcsfs` under the hood to connect to GCS)
364
+ - **Authenticate using gcloud CLI**: Run `gcloud auth application-default login` (this will automatically set the environment variables that LightlyStudio can access)
365
+
256
366
  #### Load Images With Annotations
257
367
 
258
368
  The `Dataset` currently supports:
@@ -268,7 +378,6 @@ import lightly_studio as ls
268
378
  dataset = ls.Dataset.create()
269
379
  dataset.add_samples_from_yolo(
270
380
  data_yaml="my_yolo_dataset/data.yaml",
271
- input_split="val",
272
381
  )
273
382
 
274
383
  ls.start_gui()
@@ -564,6 +673,16 @@ In some use cases, one might want to assign a tag to the samples that are the re
564
673
  query.add_tag("tag_name")
565
674
  ```
566
675
 
676
+ #### Export Samples
677
+
678
+ Currently, exporting to the COCO object detection format is supported and only annotations
679
+ of type object detection are exported. The following example exports the samples in the query
680
+ to a COCO JSON file named `coco_export.json`:
681
+
682
+ ```py
683
+ query.export().to_coco_object_detections()
684
+ ```
685
+
567
686
  ### Examples
568
687
 
569
688
  #### Add Custom Metadata
@@ -625,7 +744,11 @@ samples.
625
744
  Set the `LIGHTLY_STUDIO_LICENSE_KEY` environment variable before using selection features:
626
745
 
627
746
  ```bash
747
+ # On Linux/MacOS
628
748
  export LIGHTLY_STUDIO_LICENSE_KEY="license_key_here"
749
+
750
+ # On Windows (PowerShell)
751
+ $env:LIGHTLY_STUDIO_LICENSE_KEY="license_key_here"
629
752
  ```
630
753
 
631
754
  Alternatively, set it inside your Python script:
@@ -635,7 +758,15 @@ import os
635
758
  os.environ["LIGHTLY_STUDIO_LICENSE_KEY"] = "license_key_here"
636
759
  ```
637
760
 
638
- The selection can be configured directly from a `DatasetQuery`. The example below showcases a simple case of selecting diverse samples.
761
+ Or in a `.env` file:
762
+
763
+ ```
764
+ LIGHTLY_STUDIO_LICENSE_KEY="license_key_here"
765
+ ```
766
+
767
+ #### Diversity Selection
768
+
769
+ Diversity selection can be configured directly from a `DatasetQuery`. The example below showcases a simple case of selecting diverse samples.
639
770
 
640
771
  ```py
641
772
  import lightly_studio as ls
@@ -653,6 +784,57 @@ dataset.query().selection().diverse(
653
784
  ls.start_gui()
654
785
  ```
655
786
 
787
+ #### Metadata Weighting Selection
788
+
789
+ You can select samples based on the values of a metadata field. The example below showcases a simple case of selecting samples with the highest metadata value.
790
+
791
+ ```py
792
+ import lightly_studio as ls
793
+
794
+ # Load your dataset
795
+ dataset = ls.Dataset.load_or_create()
796
+ dataset.add_samples_from_path(path="/path/to/image_dataset")
797
+ # Compute and store 'typicality' metadata.
798
+ dataset.compute_typicality_metadata(metadata_name="typicality")
799
+
800
+ # Select the 5 samples with the highest 'typicality' scores.
801
+ dataset.query().selection().metadata_weighting(
802
+ n_samples_to_select=5,
803
+ selection_result_tag_name="metadata_weighting_selection",
804
+ metadata_key="typicality",
805
+ )
806
+ ```
807
+
808
+ #### Selection Based on Multiple Strategies
809
+
810
+ You can configure multiple strategies, the selection takes into account all of them at the same time, weighted by the `strength` parameter.
811
+
812
+ ```py
813
+ import lightly_studio as ls
814
+ from lightly_studio.selection.selection_config import (
815
+ MetadataWeightingStrategy,
816
+ EmbeddingDiversityStrategy,
817
+ )
818
+
819
+ # Load your dataset
820
+ dataset = ls.Dataset.load_or_create()
821
+ dataset.add_samples_from_path(path="/path/to/image_dataset")
822
+ # Compute typicality and store it as `typicality` metadata
823
+ dataset.compute_typicality_metadata(metadata_name="typicality")
824
+
825
+ # Select 10 samples by combining typicality and diversity, diversity having double the strength.
826
+ dataset.query().selection().multi_strategies(
827
+ n_samples_to_select=10,
828
+ selection_result_tag_name="multi_strategy_selection",
829
+ selection_strategies=[
830
+ MetadataWeightingStrategy(metadata_key="typicality", strength=1.0),
831
+ EmbeddingDiversityStrategy(embedding_model_name="my_model_name", strength=2.0),
832
+ ],
833
+ )
834
+ ```
835
+
836
+ #### Exporting Selected Samples
837
+
656
838
  The selected sample paths can be exported via the GUI, or by a script:
657
839
 
658
840
  ```py
@@ -671,19 +853,27 @@ with open("export.txt", "w") as f:
671
853
 
672
854
  ## 📚 **FAQ**
673
855
 
674
- ### Are the datasets persistent?
856
+ ### Does LightlyStudio persist the datasets?
675
857
 
676
- Yes, the information about datasets is persistent and stored in the db file. You can see it after the dataset is processed.
677
- If you rerun the loader it will create a new dataset representing the same dataset, keeping the previous dataset information untouched.
858
+ Yes, the information about datasets is persisted in a database file. You can see inspect
859
+ it after the dataset is processed. Use `Dataset.load()` to load a dataset from a pre-existing
860
+ database.
678
861
 
679
862
  ### Can I change the database path?
680
863
 
681
- Not yet. The database is stored in the working directory by default.
864
+ Yes, the database can be selected as follows:
865
+ ```py
866
+ import lightly_studio as ls
867
+
868
+ ls.db_manager.connect(db_file="custom.db")
869
+ ```
682
870
 
683
- ### Can I launch in another Python script or do I have to do it in the same script?
871
+ ### Can I use LightlyStudio from two scripts in parallel?
684
872
 
685
- It is possible to use only one script at the same time because we lock the db file for the duration of the script.
873
+ Only one script can be run at one time as the app uses a database lock for data integrity.
686
874
 
687
- ### Can I change the API backend port?
875
+ ### Can I change the API backend host and port?
688
876
 
689
- Yes. To change the port set the LIGHTLY_STUDIO_PORT variable to your preffered value. If at runtime the port is unavailable it will try to set it to a random value.
877
+ Yes, by setting environment variables. For the host set the LIGHTLY_STUDIO_HOST variable,
878
+ to change the port set the LIGHTLY_STUDIO_PORT variable. Note that if the port is unavailable
879
+ at runtime the app uses a random port number.