lightly-studio 0.3.2__py3-none-any.whl → 0.3.3__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 (115) hide show
  1. lightly_studio/__init__.py +1 -1
  2. lightly_studio/api/app.py +6 -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/dataset.py +3 -5
  8. lightly_studio/api/routes/api/embeddings2d.py +104 -0
  9. lightly_studio/api/routes/api/export.py +73 -0
  10. lightly_studio/api/routes/api/selection.py +87 -0
  11. lightly_studio/core/add_samples.py +0 -9
  12. lightly_studio/core/dataset.py +32 -48
  13. lightly_studio/core/dataset_query/dataset_query.py +5 -0
  14. lightly_studio/dataset/env.py +4 -0
  15. lightly_studio/dataset/file_utils.py +13 -2
  16. lightly_studio/dataset/loader.py +0 -54
  17. lightly_studio/dataset/mobileclip_embedding_generator.py +3 -2
  18. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.CA_CXIBb.css +1 -0
  19. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.DS78jgNY.css +1 -0
  20. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/{SelectableSvgGroup.BNTuXSAe.css → index.BVs_sZj9.css} +1 -1
  21. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/{SelectableSvgGroup.BBm0IWdq.css → transform.D487hwJk.css} +1 -1
  22. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/6t3IJ0vQ.js +1 -0
  23. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{gLNdjSzu.js → 8NsknIT2.js} +1 -1
  24. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Cur71c3O.js → BND_-4Kp.js} +1 -1
  25. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{DRZO-E-T.js → BdfTHw61.js} +1 -1
  26. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{BqBqV92V.js → BfHVnyNT.js} +1 -1
  27. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BjkP1AHA.js +1 -0
  28. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BuuNVL9G.js +1 -0
  29. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{7YNGEs1C.js → BzKGpnl4.js} +1 -1
  30. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{C0JiMuYn.js → CCx7Ho51.js} +1 -1
  31. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{DcGCxgpH.js → CH6P3X75.js} +1 -1
  32. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CR2upx_Q.js +4 -0
  33. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWPZrTTJ.js +1 -0
  34. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Ccq4ZD0B.js → Cs1XmhiF.js} +1 -1
  35. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{OH7-C_mc.js → CwPowJfP.js} +1 -1
  36. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CxFKfZ9T.js +1 -0
  37. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cxevwdid.js +1 -0
  38. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{C98Hk3r5.js → D4whDBUi.js} +1 -1
  39. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6r9vr07.js +1 -0
  40. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{DqUGznj_.js → DA6bFLPR.js} +1 -1
  41. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{2O287xak.js → DEgUu98i.js} +2 -2
  42. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{KpAtIldw.js → DGTPl6Gk.js} +1 -1
  43. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Cs31G8Qn.js → DKGxBSlK.js} +1 -1
  44. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{D8GZDMNN.js → DQXoLcsF.js} +1 -1
  45. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DQe_kdRt.js +92 -0
  46. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DcY4jgG3.js +1 -0
  47. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Crk-jcvV.js → RmD8FzRo.js} +1 -1
  48. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/V-MnMC1X.js +1 -0
  49. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Df3aMO5B.js → keKYsoph.js} +1 -1
  50. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/{app.BI-EA5gL.js → app.BVr6DYqP.js} +2 -2
  51. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.u7zsVvqp.js +1 -0
  52. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.Da2agmdd.js +1 -0
  53. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{1._I9GR805.js → 1.B11tVRJV.js} +1 -1
  54. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{10.J2RBFrSr.js → 10.l30Zud4h.js} +1 -1
  55. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{12.Cmqj25a-.js → 12.CgKPGcAP.js} +1 -1
  56. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.C8HLK8mj.js +857 -0
  57. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{3.w9g4AcAx.js → 3.CLvg3QcJ.js} +1 -1
  58. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{4.BBI8KwnD.js → 4.BQhDtXUI.js} +1 -1
  59. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.-6XqWX5G.js +1 -0
  60. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{6.CrbkRPam.js → 6.uBV1Lhat.js} +1 -1
  61. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{7.FomEdhD6.js → 7.BXsgoQZh.js} +1 -1
  62. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.BkbcnUs8.js +1 -0
  63. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{9.CajIG5ce.js → 9.Bkrv-Vww.js} +1 -1
  64. lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/clustering.worker-DKqeLtG0.js +2 -0
  65. lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/search.worker-vNSty3B0.js +1 -0
  66. lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -1
  67. lightly_studio/dist_lightly_studio_view_app/index.html +14 -14
  68. lightly_studio/export/export_dataset.py +65 -0
  69. lightly_studio/export/lightly_studio_label_input.py +120 -0
  70. lightly_studio/few_shot_classifier/classifier_manager.py +5 -26
  71. lightly_studio/metadata/compute_typicality.py +67 -0
  72. lightly_studio/models/annotation/annotation_base.py +11 -12
  73. lightly_studio/resolvers/annotation_label_resolver/__init__.py +2 -1
  74. lightly_studio/resolvers/annotation_label_resolver/get_all.py +15 -0
  75. lightly_studio/resolvers/annotation_resolver/__init__.py +2 -3
  76. lightly_studio/resolvers/annotation_resolver/create_many.py +3 -3
  77. lightly_studio/resolvers/annotation_resolver/delete_annotation.py +1 -1
  78. lightly_studio/resolvers/annotation_resolver/delete_annotations.py +7 -3
  79. lightly_studio/resolvers/annotation_resolver/get_by_id.py +19 -1
  80. lightly_studio/resolvers/annotation_resolver/update_annotation_label.py +0 -1
  81. lightly_studio/resolvers/annotations/annotations_filter.py +1 -11
  82. lightly_studio/selection/mundig.py +7 -10
  83. lightly_studio/selection/selection_config.py +4 -1
  84. lightly_studio/services/annotations_service/__init__.py +8 -0
  85. lightly_studio/services/annotations_service/create_annotation.py +63 -0
  86. lightly_studio/services/annotations_service/delete_annotation.py +22 -0
  87. {lightly_studio-0.3.2.dist-info → lightly_studio-0.3.3.dist-info}/METADATA +152 -27
  88. {lightly_studio-0.3.2.dist-info → lightly_studio-0.3.3.dist-info}/RECORD +89 -85
  89. lightly_studio/api/routes/api/annotation_task.py +0 -37
  90. lightly_studio/api/routes/api/metrics.py +0 -76
  91. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.DenzbfeK.css +0 -1
  92. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.T-zjSUd3.css +0 -1
  93. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BBoGk9hq.js +0 -1
  94. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BRnH9v23.js +0 -92
  95. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bg1Y5eUZ.js +0 -1
  96. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CG0dMCJi.js +0 -1
  97. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cpy-nab_.js +0 -1
  98. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CsKrY2zA.js +0 -1
  99. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CzgC3GFB.js +0 -1
  100. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DFRh-Spp.js +0 -1
  101. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DkR_EZ_B.js +0 -1
  102. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/M1Q1F7bw.js +0 -4
  103. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/i0ZZ4z06.js +0 -1
  104. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.CcsRl3cZ.js +0 -1
  105. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.BbO4Zc3r.js +0 -1
  106. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.C45iKJHA.js +0 -6
  107. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.huHuxdiF.js +0 -1
  108. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.Cb_ADSLk.js +0 -1
  109. lightly_studio/metrics/__init__.py +0 -0
  110. lightly_studio/metrics/detection/__init__.py +0 -0
  111. lightly_studio/metrics/detection/map.py +0 -268
  112. lightly_studio/models/annotation_task.py +0 -28
  113. lightly_studio/resolvers/annotation_resolver/create.py +0 -19
  114. lightly_studio/resolvers/annotation_task_resolver.py +0 -31
  115. {lightly_studio-0.3.2.dist-info → lightly_studio-0.3.3.dist-info}/WHEEL +0 -0
@@ -3,7 +3,6 @@
3
3
  from lightly_studio.resolvers.annotation_resolver.count_annotations_by_dataset import (
4
4
  count_annotations_by_dataset,
5
5
  )
6
- from lightly_studio.resolvers.annotation_resolver.create import create
7
6
  from lightly_studio.resolvers.annotation_resolver.create_many import create_many
8
7
  from lightly_studio.resolvers.annotation_resolver.delete_annotation import (
9
8
  delete_annotation,
@@ -12,7 +11,7 @@ from lightly_studio.resolvers.annotation_resolver.delete_annotations import (
12
11
  delete_annotations,
13
12
  )
14
13
  from lightly_studio.resolvers.annotation_resolver.get_all import get_all
15
- from lightly_studio.resolvers.annotation_resolver.get_by_id import get_by_id
14
+ from lightly_studio.resolvers.annotation_resolver.get_by_id import get_by_id, get_by_ids
16
15
  from lightly_studio.resolvers.annotation_resolver.update_annotation_label import (
17
16
  update_annotation_label,
18
17
  )
@@ -22,12 +21,12 @@ from lightly_studio.resolvers.annotation_resolver.update_bounding_box import (
22
21
 
23
22
  __all__ = [
24
23
  "count_annotations_by_dataset",
25
- "create",
26
24
  "create_many",
27
25
  "delete_annotation",
28
26
  "delete_annotations",
29
27
  "get_all",
30
28
  "get_by_id",
29
+ "get_by_ids",
31
30
  "update_annotation_label",
32
31
  "update_bounding_box",
33
32
  ]
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from collections.abc import Sequence
6
+ from uuid import UUID
6
7
 
7
8
  from sqlmodel import Session
8
9
 
@@ -24,7 +25,7 @@ from lightly_studio.models.annotation.semantic_segmentation import (
24
25
  def create_many(
25
26
  session: Session,
26
27
  annotations: list[AnnotationCreate],
27
- ) -> Sequence[AnnotationBaseTable]:
28
+ ) -> Sequence[UUID]:
28
29
  """Create many annotations with object detection details in bulk."""
29
30
  # Step 1: Create all base annotations
30
31
  base_annotations = []
@@ -37,7 +38,6 @@ def create_many(
37
38
  db_base_annotation = AnnotationBaseTable(
38
39
  annotation_label_id=annotation_create.annotation_label_id,
39
40
  annotation_type=annotation_create.annotation_type,
40
- annotation_task_id=annotation_create.annotation_task_id,
41
41
  confidence=annotation_create.confidence,
42
42
  dataset_id=annotation_create.dataset_id,
43
43
  sample_id=annotation_create.sample_id,
@@ -93,4 +93,4 @@ def create_many(
93
93
  # Commit everything
94
94
  session.commit()
95
95
 
96
- return base_annotations
96
+ return [annotation.annotation_id for annotation in base_annotations]
@@ -26,7 +26,7 @@ def delete_annotation(
26
26
  annotation_id=annotation_id,
27
27
  )
28
28
  if not annotation:
29
- return
29
+ raise ValueError(f"Annotation {annotation_id} not found")
30
30
  if annotation.object_detection_details:
31
31
  session.delete(annotation.object_detection_details)
32
32
  if annotation.instance_segmentation_details:
@@ -18,14 +18,12 @@ from lightly_studio.resolvers.annotations.annotations_filter import (
18
18
 
19
19
  def delete_annotations(
20
20
  session: Session,
21
- annotation_task_ids: list[UUID] | None,
22
21
  annotation_label_ids: list[UUID] | None,
23
22
  ) -> None:
24
23
  """Delete all annotations and their tag links using filters.
25
24
 
26
25
  Args:
27
26
  session: Database session.
28
- annotation_task_ids: List of annotation task IDs to filter by.
29
27
  annotation_label_ids: List of annotation label IDs to filter by.
30
28
  """
31
29
  # Find annotation_ids to delete
@@ -33,9 +31,15 @@ def delete_annotations(
33
31
  session,
34
32
  filters=AnnotationsFilter(
35
33
  annotation_label_ids=annotation_label_ids,
36
- annotation_task_ids=annotation_task_ids,
37
34
  ),
38
35
  ).annotations
36
+ for annotation in annotations:
37
+ if annotation.object_detection_details:
38
+ session.delete(annotation.object_detection_details)
39
+ if annotation.instance_segmentation_details:
40
+ session.delete(annotation.instance_segmentation_details)
41
+ if annotation.semantic_segmentation_details:
42
+ session.delete(annotation.semantic_segmentation_details)
39
43
  annotation_ids = [annotation.annotation_id for annotation in annotations]
40
44
  # TODO(Horatiu, 06/2025): Check if there is a way to delete the links
41
45
  # automatically using SQLModel/SQLAlchemy.
@@ -2,9 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Sequence
5
6
  from uuid import UUID
6
7
 
7
- from sqlmodel import Session, select
8
+ from sqlmodel import Session, col, select
8
9
 
9
10
  from lightly_studio.models.annotation.annotation_base import (
10
11
  AnnotationBaseTable,
@@ -16,3 +17,20 @@ def get_by_id(session: Session, annotation_id: UUID) -> AnnotationBaseTable | No
16
17
  return session.exec(
17
18
  select(AnnotationBaseTable).where(AnnotationBaseTable.annotation_id == annotation_id)
18
19
  ).one_or_none()
20
+
21
+
22
+ def get_by_ids(session: Session, annotation_ids: Sequence[UUID]) -> Sequence[AnnotationBaseTable]:
23
+ """Retrieve multiple annotations by their IDs.
24
+
25
+ Args:
26
+ session: The database session to use for the query.
27
+ annotation_ids: A list of annotation IDs to retrieve.
28
+
29
+ Returns:
30
+ A list of annotations matching the provided IDs.
31
+ """
32
+ return session.exec(
33
+ select(AnnotationBaseTable).where(
34
+ col(AnnotationBaseTable.annotation_id).in_(annotation_ids)
35
+ )
36
+ ).all()
@@ -111,7 +111,6 @@ def update_annotation_label(
111
111
  annotation_id=annotation_copy.annotation_id,
112
112
  annotation_label_id=annotation_copy.annotation_label_id,
113
113
  annotation_type=annotation_copy.annotation_type,
114
- annotation_task_id=annotation_copy.annotation_task_id,
115
114
  confidence=annotation_copy.confidence,
116
115
  created_at=annotation_copy.created_at,
117
116
  dataset_id=annotation_copy.dataset_id,
@@ -7,8 +7,7 @@ from uuid import UUID
7
7
  from pydantic import BaseModel, Field
8
8
  from sqlmodel import col
9
9
 
10
- from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
11
- from lightly_studio.models.annotation_task import AnnotationType
10
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable, AnnotationType
12
11
  from lightly_studio.models.sample import SampleTable
13
12
  from lightly_studio.models.tag import TagTable
14
13
  from lightly_studio.type_definitions import QueryType
@@ -30,9 +29,6 @@ class AnnotationsFilter(BaseModel):
30
29
  default=None,
31
30
  description="List of sample tag UUIDs to filter annotations by",
32
31
  )
33
- annotation_task_ids: list[UUID] | None = Field(
34
- default=None, description="List of annotation task UUIDs"
35
- )
36
32
 
37
33
  def apply(
38
34
  self,
@@ -51,12 +47,6 @@ class AnnotationsFilter(BaseModel):
51
47
  if self.dataset_ids:
52
48
  query = query.where(col(AnnotationBaseTable.dataset_id).in_(self.dataset_ids))
53
49
 
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
50
  # Filter by annotation label
61
51
  if self.annotation_label_ids:
62
52
  query = query.where(
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightly-studio
3
- Version: 0.3.2
3
+ Version: 0.3.3
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
@@ -20,16 +20,23 @@ Requires-Dist: fastapi>=0.115.5
20
20
  Requires-Dist: faster-coco-eval>=1.6.5
21
21
  Requires-Dist: fsspec>=2023.1.0
22
22
  Requires-Dist: labelformat>=0.1.7
23
- Requires-Dist: lightly-mundig==0.1.3
23
+ Requires-Dist: lightly-mundig==0.1.4
24
24
  Requires-Dist: open-clip-torch>=2.20.0
25
+ Requires-Dist: pyarrow>=17.0.0
25
26
  Requires-Dist: python-multipart>=0.0.20
26
27
  Requires-Dist: scikit-learn==1.3.2
27
28
  Requires-Dist: sqlmodel>=0.0.22
28
- Requires-Dist: torchmetrics>=1.5.2
29
29
  Requires-Dist: tqdm>=4.65.0
30
30
  Requires-Dist: typing-extensions>=4.12.2
31
31
  Requires-Dist: uvicorn>=0.32.1
32
32
  Requires-Dist: xxhash>=3.5.0
33
+ Provides-Extra: cloud-storage
34
+ Requires-Dist: adlfs>=2023.1.0; extra == 'cloud-storage'
35
+ Requires-Dist: gcsfs>=2023.1.0; extra == 'cloud-storage'
36
+ Requires-Dist: s3fs>=2023.1.0; extra == 'cloud-storage'
37
+ Provides-Extra: lightly-edge
38
+ Requires-Dist: lightly-edge-sdk>=1.0.1b2; extra == 'lightly-edge'
39
+ Requires-Dist: opencv-python; extra == 'lightly-edge'
33
40
  Description-Content-Type: text/markdown
34
41
 
35
42
  <div align="center">
@@ -113,42 +120,47 @@ python -m venv venv
113
120
  .\venv\Scripts\activate
114
121
 
115
122
  # 2. Install LightlyStudio
116
- pip install lightly_studio
123
+ pip install lightly-studio
117
124
  ```
118
125
 
119
126
  ## **Quickstart**
120
127
 
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:
128
+ Download example datasets by cloning the example repository:
126
129
 
127
130
  ```shell
128
131
  git clone https://github.com/lightly-ai/dataset_examples dataset_examples
129
132
  ```
130
133
 
131
- **`example_yolo.py` script to explore the dataset:**
134
+ ### YOLO Object Detection
135
+
136
+ To run an example using a YOLO dataset, create a file named `example_yolo.py` with the
137
+ following contents in the same directory that contains the `dataset_examples/` folder:
132
138
 
133
139
  ```python
134
- from pathlib import Path
140
+ # example_yolo.py
135
141
 
142
+ from pathlib import Path
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,
148
+ data_yaml="dataset_examples/road_signs_yolo/data.yaml",
144
149
  input_split="test",
145
150
  )
146
151
 
147
152
  # Start the UI application on the port 8001.
148
153
  ls.start_gui()
154
+ ```
149
155
 
156
+ Run the script:
157
+
158
+ ```
159
+ python example_yolo.py
150
160
  ```
151
161
 
162
+ When you are done, stop the app by pressing Ctrl+C in the terminal.
163
+
152
164
  <details>
153
165
  <summary>The YOLO format details:</summary>
154
166
 
@@ -183,26 +195,21 @@ Where coordinates are normalized between 0 and 1.
183
195
 
184
196
  ### COCO Instance Segmentation
185
197
 
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:**
198
+ To run an instance segmentation example using a COCO dataset, create a file named
199
+ `example_coco.py` with the following contents in the same directory that contains
200
+ the `dataset_examples/` folder:
193
201
 
194
202
  ```python
195
- from pathlib import Path
203
+ # example_coco.py
196
204
 
205
+ from pathlib import Path
197
206
  import lightly_studio as ls
198
207
 
199
- current_dir = Path(__file__).resolve().parent
200
-
201
208
  # Create a dataset and add the samples from the coco format
202
209
  dataset = ls.Dataset.create()
203
210
  dataset.add_samples_from_coco(
204
- annotations_json=current_dir / "instances_train2017.json",
205
- images_path=current_dir / "images",
211
+ annotations_json="dataset_examples/coco_subset_128_images/instances_train2017.json",
212
+ images_path="dataset_examples/coco_subset_128_images/images",
206
213
  annotation_type=ls.AnnotationType.INSTANCE_SEGMENTATION,
207
214
  )
208
215
 
@@ -210,6 +217,14 @@ dataset.add_samples_from_coco(
210
217
  ls.start_gui()
211
218
  ```
212
219
 
220
+ Run the script:
221
+
222
+ ```
223
+ python example_coco.py
224
+ ```
225
+
226
+ When you are done, stop the app by pressing Ctrl+C in the terminal.
227
+
213
228
  <details>
214
229
  <summary>The COCO format details:</summary>
215
230
 
@@ -253,6 +268,53 @@ dataset.add_samples_from_path(path="/path/to/image_dataset")
253
268
  ls.start_gui()
254
269
  ```
255
270
 
271
+ #### ☁️ Cloud Storage Support
272
+
273
+ #### Installation with Cloud Storage Support
274
+
275
+ ```shell
276
+ pip install lightly-studio[cloud-storage]
277
+ ```
278
+
279
+ #### Example: Loading Dataset from Cloud Storage
280
+
281
+ ```python
282
+ import lightly_studio as ls
283
+
284
+ dataset = ls.Dataset.create()
285
+
286
+ # Load dataset from S3
287
+ dataset.add_samples_from_path(path="s3://my-bucket/path/to/images/")
288
+
289
+ # You can use glob pattern in the file path
290
+ dataset.add_samples_from_path(path="s3://my-bucket/path/to/images/**/*.jpg") # matches all .jpg files recursively
291
+
292
+ # Load dataset from gcs
293
+ dataset.add_samples_from_path(path="gs://path/to/images/")
294
+
295
+ ls.start_gui()
296
+ ```
297
+
298
+ **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.
299
+
300
+ #### Authentication
301
+
302
+ **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.
303
+
304
+ #### AWS S3
305
+
306
+ You can use either of the following two options:
307
+
308
+ - **Set environment variables manually**: Set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` (LightlyStudio uses `s3fs` under the hood to connect to S3)
309
+ - **Authenticate using AWS CLI**: Run `aws configure` (this will automatically set the environment variables that LightlyStudio can access)
310
+
311
+ #### Google Cloud Storage
312
+
313
+ You can use either of the following two options:
314
+
315
+ - **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)
316
+ - **Authenticate using gcloud CLI**: Run `gcloud auth application-default login` (this will automatically set the environment variables that LightlyStudio can access)
317
+
256
318
  #### Load Images With Annotations
257
319
 
258
320
  The `Dataset` currently supports:
@@ -564,6 +626,16 @@ In some use cases, one might want to assign a tag to the samples that are the re
564
626
  query.add_tag("tag_name")
565
627
  ```
566
628
 
629
+ #### Export Samples
630
+
631
+ Currently, exporting to the COCO object detection format is supported. The following example shows how to export the samples in the query to a COCO JSON file:
632
+
633
+ ```py
634
+ from pathlib import Path
635
+
636
+ query.export().to_coco_object_detections(Path("coco_export.json"))
637
+ ```
638
+
567
639
  ### Examples
568
640
 
569
641
  #### Add Custom Metadata
@@ -635,7 +707,9 @@ import os
635
707
  os.environ["LIGHTLY_STUDIO_LICENSE_KEY"] = "license_key_here"
636
708
  ```
637
709
 
638
- The selection can be configured directly from a `DatasetQuery`. The example below showcases a simple case of selecting diverse samples.
710
+ #### Diversity Selection
711
+
712
+ Diversity selection can be configured directly from a `DatasetQuery`. The example below showcases a simple case of selecting diverse samples.
639
713
 
640
714
  ```py
641
715
  import lightly_studio as ls
@@ -653,6 +727,57 @@ dataset.query().selection().diverse(
653
727
  ls.start_gui()
654
728
  ```
655
729
 
730
+ #### Metadata Weighting Selection
731
+
732
+ 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.
733
+
734
+ ```py
735
+ import lightly_studio as ls
736
+
737
+ # Load your dataset
738
+ dataset = ls.Dataset.load_or_create()
739
+ dataset.add_samples_from_path(path="/path/to/image_dataset")
740
+ # Compute and store 'typicality' metadata.
741
+ dataset.compute_typicality_metadata(metadata_name="typicality")
742
+
743
+ # Select the 5 samples with the highest 'typicality' scores.
744
+ dataset.query().selection().metadata_weighting(
745
+ n_samples_to_select=5,
746
+ selection_result_tag_name="metadata_weighting_selection",
747
+ metadata_key="typicality",
748
+ )
749
+ ```
750
+
751
+ #### Selection Based on Multiple Strategies
752
+
753
+ You can configure multiple strategies, the selection takes into account all of them at the same time, weighted by the `strength` parameter.
754
+
755
+ ```py
756
+ import lightly_studio as ls
757
+ from lightly_studio.selection.selection_config import (
758
+ MetadataWeightingStrategy,
759
+ EmbeddingDiversityStrategy,
760
+ )
761
+
762
+ # Load your dataset
763
+ dataset = ls.Dataset.load_or_create()
764
+ dataset.add_samples_from_path(path="/path/to/image_dataset")
765
+ # Compute typicality and store it as `typicality` metadata
766
+ dataset.compute_typicality_metadata(metadata_name="typicality")
767
+
768
+ # Select 10 samples by combining typicality and diversity, diversity having double the strength.
769
+ dataset.query().selection().multi_strategies(
770
+ n_samples_to_select=10,
771
+ selection_result_tag_name="multi_strategy_selection",
772
+ selection_strategies=[
773
+ MetadataWeightingStrategy(metadata_key="typicality", strength=1.0),
774
+ EmbeddingDiversityStrategy(embedding_model_name="my_model_name", strength=2.0),
775
+ ],
776
+ )
777
+ ```
778
+
779
+ #### Exporting Selected Samples
780
+
656
781
  The selected sample paths can be exported via the GUI, or by a script:
657
782
 
658
783
  ```py