lightly-studio 0.3.3__py3-none-any.whl → 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of lightly-studio might be problematic. Click here for more details.
- lightly_studio/api/app.py +2 -0
- lightly_studio/api/features.py +3 -5
- lightly_studio/api/routes/api/caption.py +30 -0
- lightly_studio/api/routes/api/dataset_tag.py +10 -0
- lightly_studio/api/routes/api/embeddings2d.py +42 -39
- lightly_studio/api/routes/api/metadata.py +57 -1
- lightly_studio/core/add_samples.py +138 -0
- lightly_studio/core/dataset.py +232 -18
- lightly_studio/core/dataset_query/__init__.py +14 -0
- lightly_studio/core/sample.py +33 -1
- lightly_studio/dataset/loader.py +2 -8
- lightly_studio/db_manager.py +14 -6
- lightly_studio/dist_lightly_studio_view_app/_app/env.js +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.CN4hnTks.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/2.CkOblLn7.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/Samples.C0_eo9eP.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/{useFeatureFlags.CV-KWLNP.css → _layout.CefECEWA.css} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.kFFGI0zL.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/transform.sLzR40om.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{6t3IJ0vQ.js → BOmrKuMn.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Cs1XmhiF.js → BPpOWbDa.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BaFFwDFr.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BiGQqqJP.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BrNKoXwc.js +20 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BsaJCCG_.js +96 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BtXGzlpP.js +20 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C1FmrZbK.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C3xJX0nD.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CANX9QXL.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CAPx0Bfm.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CP9M7pei.js +39 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWuDkrMZ.js +436 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/ChlxSwqI.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cj4nZbtb.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/ClzkJBWk.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CpbA3HU7.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D8ZGoCPm.js +3 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DMJzr1NB.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{BdfTHw61.js → DNJnBfHs.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{keKYsoph.js → DUtlYNuP.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DVxjPOJB.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DmGM9V9Q.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DoEId1MK.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DthpwYR_.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DyIcJj6J.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/SiegjVo0.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{BfHVnyNT.js → WEyXQRi6.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/gBp1tBnA.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/xQhUoIl9.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.Y-sSoz5q.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.CvxVp0Cu.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.0Fm6E-5B.js +4 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1.DB-0vkHb.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.vaUePh5k.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/11.7i7ljNVT.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/13.9qy3WtZv.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{2.C8HLK8mj.js → 2.Drwwdm7A.js} +267 -111
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{3.CLvg3QcJ.js → 3.D3X_-Wan.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{4.BQhDtXUI.js → 4.C9TqY3tA.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.iRw6HCWX.js +39 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{6.uBV1Lhat.js → 6.fqfYR7dB.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.C7gMM-gk.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.C4v1w-oS.js +20 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.DbHcSiMn.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -1
- lightly_studio/dist_lightly_studio_view_app/index.html +15 -14
- lightly_studio/examples/example.py +4 -0
- lightly_studio/examples/example_coco.py +4 -0
- lightly_studio/examples/example_coco_caption.py +24 -0
- lightly_studio/examples/example_metadata.py +4 -1
- lightly_studio/examples/example_selection.py +4 -0
- lightly_studio/examples/example_split_work.py +4 -0
- lightly_studio/examples/example_yolo.py +4 -0
- lightly_studio/export/export_dataset.py +11 -3
- lightly_studio/metadata/compute_typicality.py +1 -1
- lightly_studio/models/caption.py +74 -0
- lightly_studio/models/dataset.py +1 -2
- lightly_studio/models/metadata.py +1 -1
- lightly_studio/models/sample.py +9 -2
- lightly_studio/models/settings.py +5 -0
- lightly_studio/resolvers/caption_resolver.py +80 -0
- lightly_studio/resolvers/dataset_resolver.py +6 -11
- lightly_studio/resolvers/metadata_resolver/__init__.py +2 -2
- lightly_studio/resolvers/metadata_resolver/sample/__init__.py +3 -3
- lightly_studio/resolvers/metadata_resolver/sample/bulk_update_metadata.py +46 -0
- lightly_studio/resolvers/sample_resolver.py +1 -0
- lightly_studio/resolvers/samples_filter.py +18 -10
- lightly_studio/resolvers/settings_resolver.py +3 -0
- lightly_studio/resolvers/twodim_embedding_resolver.py +29 -0
- lightly_studio/selection/__init__.py +1 -0
- lightly_studio/selection/mundig.py +41 -0
- lightly_studio/type_definitions.py +2 -0
- lightly_studio-0.4.0.dist-info/METADATA +78 -0
- {lightly_studio-0.3.3.dist-info → lightly_studio-0.4.0.dist-info}/RECORD +96 -88
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.CA_CXIBb.css +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.DS78jgNY.css +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/index.BVs_sZj9.css +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/transform.D487hwJk.css +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/8NsknIT2.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BND_-4Kp.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BjkP1AHA.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BuuNVL9G.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BzKGpnl4.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CCx7Ho51.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CH6P3X75.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CR2upx_Q.js +0 -4
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWPZrTTJ.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CwPowJfP.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CxFKfZ9T.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cxevwdid.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D4whDBUi.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6r9vr07.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DA6bFLPR.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DEgUu98i.js +0 -3
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DGTPl6Gk.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DKGxBSlK.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DQXoLcsF.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DQe_kdRt.js +0 -92
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DcY4jgG3.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/H7C68rOM.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/RmD8FzRo.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/V-MnMC1X.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.BVr6DYqP.js +0 -2
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.u7zsVvqp.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.Da2agmdd.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1.B11tVRJV.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.l30Zud4h.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.CgKPGcAP.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.-6XqWX5G.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.BXsgoQZh.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.BkbcnUs8.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.Bkrv-Vww.js +0 -1
- lightly_studio/resolvers/metadata_resolver/sample/bulk_set_metadata.py +0 -48
- lightly_studio/selection/README.md +0 -6
- lightly_studio-0.3.3.dist-info/METADATA +0 -814
- /lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{11.CWG1ehzT.js → 12.CWG1ehzT.js} +0 -0
- {lightly_studio-0.3.3.dist-info → lightly_studio-0.4.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Resolvers for caption."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
from sqlmodel import Session, col, func, select
|
|
10
|
+
|
|
11
|
+
from lightly_studio.api.routes.api.validators import Paginated
|
|
12
|
+
from lightly_studio.models.caption import CaptionCreate, CaptionTable
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GetAllCaptionsResult(BaseModel):
|
|
16
|
+
"""Result wrapper for caption listings."""
|
|
17
|
+
|
|
18
|
+
captions: Sequence[CaptionTable]
|
|
19
|
+
total_count: int
|
|
20
|
+
next_cursor: int | None = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_many(session: Session, captions: Sequence[CaptionCreate]) -> list[CaptionTable]:
|
|
24
|
+
"""Create many captions in bulk.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
session: Database session
|
|
28
|
+
captions: The captions to create
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
The created captions
|
|
32
|
+
"""
|
|
33
|
+
if not captions:
|
|
34
|
+
return []
|
|
35
|
+
|
|
36
|
+
db_captions = [CaptionTable.model_validate(caption) for caption in captions]
|
|
37
|
+
session.bulk_save_objects(db_captions)
|
|
38
|
+
session.commit()
|
|
39
|
+
return db_captions
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_all(
|
|
43
|
+
session: Session,
|
|
44
|
+
dataset_id: UUID,
|
|
45
|
+
pagination: Paginated | None = None,
|
|
46
|
+
) -> GetAllCaptionsResult:
|
|
47
|
+
"""Get all captions from the database.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
session: Database session
|
|
51
|
+
dataset_id: dataset_id parameter to filter the query
|
|
52
|
+
pagination: Optional pagination parameters
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of captions matching the filters, total number of captions, next cursor (pagination)
|
|
56
|
+
"""
|
|
57
|
+
query = select(CaptionTable).order_by(
|
|
58
|
+
col(CaptionTable.created_at).asc(),
|
|
59
|
+
col(CaptionTable.caption_id).asc(),
|
|
60
|
+
)
|
|
61
|
+
count_query = select(func.count()).select_from(CaptionTable)
|
|
62
|
+
|
|
63
|
+
query = query.where(CaptionTable.dataset_id == dataset_id)
|
|
64
|
+
count_query = count_query.where(CaptionTable.dataset_id == dataset_id)
|
|
65
|
+
|
|
66
|
+
if pagination is not None:
|
|
67
|
+
query = query.offset(pagination.offset).limit(pagination.limit)
|
|
68
|
+
|
|
69
|
+
captions = session.exec(query).all()
|
|
70
|
+
total_count = session.exec(count_query).one()
|
|
71
|
+
|
|
72
|
+
next_cursor: int | None = None
|
|
73
|
+
if pagination and pagination.offset + pagination.limit < total_count:
|
|
74
|
+
next_cursor = pagination.offset + pagination.limit
|
|
75
|
+
|
|
76
|
+
return GetAllCaptionsResult(
|
|
77
|
+
captions=captions,
|
|
78
|
+
total_count=total_count,
|
|
79
|
+
next_cursor=next_cursor,
|
|
80
|
+
)
|
|
@@ -41,6 +41,9 @@ class ExportFilter(BaseModel):
|
|
|
41
41
|
|
|
42
42
|
def create(session: Session, dataset: DatasetCreate) -> DatasetTable:
|
|
43
43
|
"""Create a new dataset in the database."""
|
|
44
|
+
existing = get_by_name(session=session, name=dataset.name)
|
|
45
|
+
if existing:
|
|
46
|
+
raise ValueError(f"Dataset with name '{dataset.name}' already exists.")
|
|
44
47
|
db_dataset = DatasetTable.model_validate(dataset)
|
|
45
48
|
session.add(db_dataset)
|
|
46
49
|
session.commit()
|
|
@@ -69,12 +72,7 @@ def get_by_id(session: Session, dataset_id: UUID) -> DatasetTable | None:
|
|
|
69
72
|
|
|
70
73
|
def get_by_name(session: Session, name: str) -> DatasetTable | None:
|
|
71
74
|
"""Retrieve a single dataset by name."""
|
|
72
|
-
|
|
73
|
-
if len(datasets) == 0:
|
|
74
|
-
return None
|
|
75
|
-
if len(datasets) > 1:
|
|
76
|
-
raise ValueError(f"Cannot retrieve a dataset, found multiple with name '{name}'.")
|
|
77
|
-
return datasets[0]
|
|
75
|
+
return session.exec(select(DatasetTable).where(DatasetTable.name == name)).one_or_none()
|
|
78
76
|
|
|
79
77
|
|
|
80
78
|
def update(session: Session, dataset_id: UUID, dataset_data: DatasetCreate) -> DatasetTable:
|
|
@@ -84,7 +82,6 @@ def update(session: Session, dataset_id: UUID, dataset_data: DatasetCreate) -> D
|
|
|
84
82
|
raise ValueError(f"Dataset ID was not found '{dataset_id}'.")
|
|
85
83
|
|
|
86
84
|
dataset.name = dataset_data.name
|
|
87
|
-
dataset.directory = dataset_data.directory
|
|
88
85
|
dataset.updated_at = datetime.now(timezone.utc)
|
|
89
86
|
|
|
90
87
|
session.commit()
|
|
@@ -232,10 +229,8 @@ def _build_export_query( # noqa: C901
|
|
|
232
229
|
raise ValueError("Invalid include or export filter combination.")
|
|
233
230
|
|
|
234
231
|
|
|
235
|
-
# TODO:
|
|
236
|
-
#
|
|
237
|
-
# https://linear.app/lightly/issue/LIG-6196/figure-out-architecture-follow-up-changes-in-python-package
|
|
238
|
-
# TODO: this should be abstracted to allow different export formats.
|
|
232
|
+
# TODO(Michal, 10/2025): Consider moving the export logic to a separate service.
|
|
233
|
+
# This is a legacy code from the initial implementation of the export feature.
|
|
239
234
|
def export(
|
|
240
235
|
session: Session,
|
|
241
236
|
dataset_id: UUID,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
"""Metadata resolver module."""
|
|
2
2
|
|
|
3
3
|
from lightly_studio.resolvers.metadata_resolver.sample import (
|
|
4
|
-
|
|
4
|
+
bulk_update_metadata,
|
|
5
5
|
get_by_sample_id,
|
|
6
6
|
get_value_for_sample,
|
|
7
7
|
set_value_for_sample,
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
__all__ = [
|
|
11
|
-
"
|
|
11
|
+
"bulk_update_metadata",
|
|
12
12
|
"get_by_sample_id",
|
|
13
13
|
"get_value_for_sample",
|
|
14
14
|
"set_value_for_sample",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Resolvers for metadata operations."""
|
|
2
2
|
|
|
3
|
-
from .
|
|
4
|
-
|
|
3
|
+
from .bulk_update_metadata import (
|
|
4
|
+
bulk_update_metadata,
|
|
5
5
|
)
|
|
6
6
|
from .get_by_sample_id import (
|
|
7
7
|
get_by_sample_id,
|
|
@@ -14,7 +14,7 @@ from .set_value_for_sample import (
|
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
|
-
"
|
|
17
|
+
"bulk_update_metadata",
|
|
18
18
|
"get_by_sample_id",
|
|
19
19
|
"get_value_for_sample",
|
|
20
20
|
"set_value_for_sample",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Resolver for operations for setting metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from sqlmodel import Session, col, select
|
|
9
|
+
|
|
10
|
+
from lightly_studio.models.metadata import SampleMetadataTable
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def bulk_update_metadata(
|
|
14
|
+
session: Session,
|
|
15
|
+
sample_metadata: list[tuple[UUID, dict[str, Any]]],
|
|
16
|
+
) -> None:
|
|
17
|
+
"""Bulk insert or update metadata for multiple samples.
|
|
18
|
+
|
|
19
|
+
If a sample does not have metadata, a new metadata row is created.
|
|
20
|
+
If a sample already has metadata, the new key-value pairs are merged with the existing metadata.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
session: The database session.
|
|
24
|
+
sample_metadata: List of (sample_id, metadata_dict) tuples.
|
|
25
|
+
"""
|
|
26
|
+
# TODO(Mihnea, 10/2025): Consider using SQLAlchemy's bulk operations
|
|
27
|
+
# (Session.bulk_insert/update_mappings) if performance becomes a bottleneck.
|
|
28
|
+
if not sample_metadata:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# Get all existing metadata rows for the given sample IDs.
|
|
32
|
+
sample_ids = [s[0] for s in sample_metadata]
|
|
33
|
+
existing_metadata = session.exec(
|
|
34
|
+
select(SampleMetadataTable).where(col(SampleMetadataTable.sample_id).in_(sample_ids))
|
|
35
|
+
).all()
|
|
36
|
+
sample_id_to_existing_metadata = {meta.sample_id: meta for meta in existing_metadata}
|
|
37
|
+
|
|
38
|
+
for sample_id, new_metadata in sample_metadata:
|
|
39
|
+
metadata = sample_id_to_existing_metadata.get(
|
|
40
|
+
sample_id, SampleMetadataTable(sample_id=sample_id)
|
|
41
|
+
)
|
|
42
|
+
for key, value in new_metadata.items():
|
|
43
|
+
metadata.set_value(key, value)
|
|
44
|
+
session.add(metadata)
|
|
45
|
+
|
|
46
|
+
session.commit()
|
|
@@ -109,6 +109,7 @@ def get_all_by_dataset_id( # noqa: PLR0913
|
|
|
109
109
|
joinedload(AnnotationBaseTable.instance_segmentation_details),
|
|
110
110
|
joinedload(AnnotationBaseTable.semantic_segmentation_details),
|
|
111
111
|
),
|
|
112
|
+
selectinload(SampleTable.captions),
|
|
112
113
|
selectinload(SampleTable.tags),
|
|
113
114
|
# Ignore type checker error below as it's a false positive caused by TYPE_CHECKING.
|
|
114
115
|
joinedload(SampleTable.metadata_dict), # type: ignore[arg-type]
|
|
@@ -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
|
-
|
|
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
|
|
@@ -52,6 +52,9 @@ def set_settings(session: Session, settings: SettingView) -> SettingView:
|
|
|
52
52
|
# Update show annotation text labels
|
|
53
53
|
current_settings.show_annotation_text_labels = settings.show_annotation_text_labels
|
|
54
54
|
|
|
55
|
+
# Update sample filename visibility
|
|
56
|
+
current_settings.show_sample_filenames = settings.show_sample_filenames
|
|
57
|
+
|
|
55
58
|
session.commit()
|
|
56
59
|
session.refresh(current_settings)
|
|
57
60
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Handler for getting 2D embeddings from high-dimensional embeddings."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from lightly_mundig import TwoDimEmbedding # type: ignore[import-untyped]
|
|
6
|
+
|
|
7
|
+
from lightly_studio.dataset.env import LIGHTLY_STUDIO_LICENSE_KEY
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# TODO(Malte, 10/2025): Add the get_twodim_embeddings function here that handles the
|
|
11
|
+
# caching in the DB and calls _calculate_2d_embeddings when needed.
|
|
12
|
+
def _calculate_2d_embeddings(embedding_values: list[list[float]]) -> list[tuple[float, float]]:
|
|
13
|
+
n_samples = len(embedding_values)
|
|
14
|
+
# For 0, 1 or 2 samples we hard-code deterministic coordinates.
|
|
15
|
+
if n_samples == 0:
|
|
16
|
+
return []
|
|
17
|
+
if n_samples == 1:
|
|
18
|
+
return [(0.0, 0.0)]
|
|
19
|
+
if n_samples == 2: # noqa: PLR2004
|
|
20
|
+
return [(0.0, 0.0), (1.0, 1.0)]
|
|
21
|
+
|
|
22
|
+
license_key = LIGHTLY_STUDIO_LICENSE_KEY
|
|
23
|
+
if license_key is None:
|
|
24
|
+
raise ValueError(
|
|
25
|
+
"LIGHTLY_STUDIO_LICENSE_KEY environment variable is not set. "
|
|
26
|
+
"Please set it to your LightlyStudio license key."
|
|
27
|
+
)
|
|
28
|
+
embedding_calculator = TwoDimEmbedding(embedding_values, license_key)
|
|
29
|
+
return embedding_calculator.calculate_2d_embedding() # type: ignore[no-any-return]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Selection package."""
|
|
@@ -81,6 +81,47 @@ class Mundig:
|
|
|
81
81
|
self._check_consistent_input_size(weights_ndarray.shape[0])
|
|
82
82
|
self.mundig.add_weighting_strategy(weights=weights_ndarray, strength=strength)
|
|
83
83
|
|
|
84
|
+
def add_class_balancing(
|
|
85
|
+
self,
|
|
86
|
+
class_distributions: Iterable[Iterable[float]],
|
|
87
|
+
target: Iterable[float],
|
|
88
|
+
strength: float = 1.0,
|
|
89
|
+
) -> None:
|
|
90
|
+
"""Add a class balancing strategy.
|
|
91
|
+
|
|
92
|
+
This strategy aims to select a subset of samples such that the
|
|
93
|
+
distribution of classes in the subset is close to the target
|
|
94
|
+
distribution.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
class_distributions:
|
|
98
|
+
First dimension is over all samples, second one is the distribution per sample over
|
|
99
|
+
the classes.
|
|
100
|
+
target:
|
|
101
|
+
The desired target distribution for the classes in the selected subset of samples.
|
|
102
|
+
The length of the target must match the number of classes in the class
|
|
103
|
+
distributions.
|
|
104
|
+
strength:
|
|
105
|
+
The strength of the balancing strategy.
|
|
106
|
+
"""
|
|
107
|
+
# Convert to ndarray with float32 dtype if not already
|
|
108
|
+
if isinstance(class_distributions, np.ndarray) and class_distributions.dtype == np.float32:
|
|
109
|
+
class_distributions_nparray = class_distributions
|
|
110
|
+
else:
|
|
111
|
+
class_distributions_nparray = np.array(class_distributions, dtype=np.float32)
|
|
112
|
+
self._check_consistent_input_size(class_distributions_nparray.shape[0])
|
|
113
|
+
target_nparray = np.array(target, dtype=np.float32)
|
|
114
|
+
if class_distributions_nparray.shape[1] != target_nparray.shape[0]:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"The length of 'target' {target_nparray.shape[0]} doesn't match the width of "
|
|
117
|
+
f"'class_distributions': {class_distributions_nparray.shape[0]}"
|
|
118
|
+
)
|
|
119
|
+
self.mundig.add_balancing_strategy(
|
|
120
|
+
class_distributions=class_distributions_nparray,
|
|
121
|
+
target=target_nparray,
|
|
122
|
+
strength=strength,
|
|
123
|
+
)
|
|
124
|
+
|
|
84
125
|
def _check_consistent_input_size(self, n_input_samples_strategy: int) -> None:
|
|
85
126
|
"""Assert that input samples count is consistent across strategies.
|
|
86
127
|
|
|
@@ -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]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lightly-studio
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: LightlyStudio is a lightweight, fast, and easy-to-use data exploration tool for data scientists and engineers.
|
|
5
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
6
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
7
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Python: <3.13,>=3.8
|
|
15
|
+
Requires-Dist: annotated-types==0.7.0
|
|
16
|
+
Requires-Dist: duckdb-engine<0.17,>=0.15.0
|
|
17
|
+
Requires-Dist: duckdb<1.3,>=1.2.2
|
|
18
|
+
Requires-Dist: environs<12.0.0
|
|
19
|
+
Requires-Dist: eval-type-backport>=0.2.2
|
|
20
|
+
Requires-Dist: fastapi>=0.115.5
|
|
21
|
+
Requires-Dist: faster-coco-eval>=1.6.5
|
|
22
|
+
Requires-Dist: fsspec>=2023.1.0
|
|
23
|
+
Requires-Dist: labelformat>=0.1.7
|
|
24
|
+
Requires-Dist: lightly-mundig==0.1.7
|
|
25
|
+
Requires-Dist: open-clip-torch>=2.20.0
|
|
26
|
+
Requires-Dist: pyarrow>=17.0.0
|
|
27
|
+
Requires-Dist: python-multipart>=0.0.20
|
|
28
|
+
Requires-Dist: scikit-learn==1.3.2
|
|
29
|
+
Requires-Dist: sqlmodel>=0.0.22
|
|
30
|
+
Requires-Dist: tqdm>=4.65.0
|
|
31
|
+
Requires-Dist: typing-extensions>=4.12.2
|
|
32
|
+
Requires-Dist: uvicorn>=0.32.1
|
|
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'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<a href="https://lightly.ai/lightly-studio">
|
|
45
|
+
<img src="https://cdn.prod.website-files.com/62cd5ce03261cba217188442/66dac501a8e9a90495970876_Logo%20dark-short-p-800.png" height="50" alt="LightlyStudio logo" />
|
|
46
|
+
</a>
|
|
47
|
+
</p>
|
|
48
|
+
<p align="center"><strong>Curate, Annotate, and Manage Your Data in LightlyStudio.</strong></p>
|
|
49
|
+
<p align="center">
|
|
50
|
+
<a href="https://pypi.org/project/lightly-studio">
|
|
51
|
+
<img src="https://img.shields.io/pypi/pyversions/lightly-studio" alt="PyPI python" />
|
|
52
|
+
</a>
|
|
53
|
+
<a href="https://pypi.org/project/lightly-studio">
|
|
54
|
+
<img src="https://badge.fury.io/py/lightly-studio.svg" alt="PyPI version" />
|
|
55
|
+
</a>
|
|
56
|
+
<a href="LICENSE">
|
|
57
|
+
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License" />
|
|
58
|
+
</a>
|
|
59
|
+
<a href="https://docs.lightly.ai/studio">
|
|
60
|
+
<img src="https://img.shields.io/badge/Docs-blue" alt="Docs" />
|
|
61
|
+
</a>
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
# Welcome to LightlyStudio!
|
|
67
|
+
|
|
68
|
+
We at **[Lightly](https://lightly.ai)** created **[LightlyStudio](https://www.lightly.ai/lightly-studio)**, an open-source tool designed to unify your data workflows from curation, annotation and management in a single tool. Since we're big fans of Rust we used it to speed things up. You can work with COCO and ImageNet on a Macbook Pro with M1 and 16GB of memory!
|
|
69
|
+
|
|
70
|
+
Explore [LightlyStudio on GitHub](https://github.com/lightly-ai/lightly-studio).
|
|
71
|
+
|
|
72
|
+
Learn more in our [documentation](https://docs.lightly.ai/studio).
|
|
73
|
+
|
|
74
|
+
<p align="center">
|
|
75
|
+
<img alt="LightlyStudio Overview" src="https://storage.googleapis.com/lightly-public/studio/studio_overview.gif" width="70%">
|
|
76
|
+
<br/>
|
|
77
|
+
<em>Curate, Annotate, and Manage Your Data in LightlyStudio.</em>
|
|
78
|
+
</p>
|