lightly-studio 0.3.1__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.
- lightly_studio/__init__.py +4 -4
- lightly_studio/api/app.py +7 -5
- lightly_studio/api/db_tables.py +0 -3
- lightly_studio/api/routes/api/annotation.py +32 -16
- lightly_studio/api/routes/api/annotation_label.py +2 -5
- lightly_studio/api/routes/api/annotations/__init__.py +7 -0
- lightly_studio/api/routes/api/annotations/create_annotation.py +52 -0
- lightly_studio/api/routes/api/classifier.py +2 -5
- lightly_studio/api/routes/api/dataset.py +5 -8
- lightly_studio/api/routes/api/dataset_tag.py +2 -3
- lightly_studio/api/routes/api/embeddings2d.py +104 -0
- lightly_studio/api/routes/api/export.py +73 -0
- lightly_studio/api/routes/api/metadata.py +2 -4
- lightly_studio/api/routes/api/sample.py +5 -13
- lightly_studio/api/routes/api/selection.py +87 -0
- lightly_studio/api/routes/api/settings.py +2 -6
- lightly_studio/api/routes/images.py +6 -6
- lightly_studio/core/add_samples.py +374 -0
- lightly_studio/core/dataset.py +272 -400
- lightly_studio/core/dataset_query/boolean_expression.py +67 -0
- lightly_studio/core/dataset_query/dataset_query.py +216 -0
- lightly_studio/core/dataset_query/field.py +113 -0
- lightly_studio/core/dataset_query/field_expression.py +79 -0
- lightly_studio/core/dataset_query/match_expression.py +23 -0
- lightly_studio/core/dataset_query/order_by.py +79 -0
- lightly_studio/core/dataset_query/sample_field.py +28 -0
- lightly_studio/core/dataset_query/tags_expression.py +46 -0
- lightly_studio/core/sample.py +159 -32
- lightly_studio/core/start_gui.py +35 -0
- lightly_studio/dataset/edge_embedding_generator.py +13 -8
- lightly_studio/dataset/embedding_generator.py +2 -3
- lightly_studio/dataset/embedding_manager.py +74 -6
- lightly_studio/dataset/env.py +4 -0
- lightly_studio/dataset/file_utils.py +13 -2
- lightly_studio/dataset/fsspec_lister.py +275 -0
- lightly_studio/dataset/loader.py +49 -84
- lightly_studio/dataset/mobileclip_embedding_generator.py +9 -6
- lightly_studio/db_manager.py +145 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.CA_CXIBb.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.DS78jgNY.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/index.BVs_sZj9.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/transform.D487hwJk.css +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/6t3IJ0vQ.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{D6su9Aln.js → 8NsknIT2.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{x9G_hzyY.js → BND_-4Kp.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{BylOuP6i.js → BdfTHw61.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{DOlTMNyt.js → BfHVnyNT.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BjkP1AHA.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BuuNVL9G.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{O-EABkf9.js → BzKGpnl4.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CCx7Ho51.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{l7KrR96u.js → CH6P3X75.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{D5-A_Ffd.js → CR2upx_Q.js} +2 -2
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWPZrTTJ.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{C8I8rFJQ.js → Cs1XmhiF.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{CDnpyLsT.js → CwPowJfP.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CxFKfZ9T.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Cxevwdid.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{DjfY96ND.js → D4whDBUi.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6r9vr07.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DA6bFLPR.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DEgUu98i.js +3 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DGTPl6Gk.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DKGxBSlK.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DQXoLcsF.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DQe_kdRt.js +92 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DcY4jgG3.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Bu7uvVrG.js → RmD8FzRo.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/V-MnMC1X.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/{Bsi3UGy5.js → keKYsoph.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.BVr6DYqP.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.u7zsVvqp.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.Da2agmdd.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{1.B4rNYwVp.js → 1.B11tVRJV.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.l30Zud4h.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.CgKPGcAP.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.C8HLK8mj.js +857 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{3.CWHpKonm.js → 3.CLvg3QcJ.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{4.OUWOLQeV.js → 4.BQhDtXUI.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.-6XqWX5G.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/6.uBV1Lhat.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.BXsgoQZh.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.BkbcnUs8.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/{9.CPu3CiBc.js → 9.Bkrv-Vww.js} +1 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/clustering.worker-DKqeLtG0.js +2 -0
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/search.worker-vNSty3B0.js +1 -0
- lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -1
- lightly_studio/dist_lightly_studio_view_app/index.html +14 -14
- lightly_studio/examples/example.py +13 -12
- lightly_studio/examples/example_coco.py +13 -0
- lightly_studio/examples/example_metadata.py +83 -98
- lightly_studio/examples/example_selection.py +7 -19
- lightly_studio/examples/example_split_work.py +12 -36
- lightly_studio/examples/{example_v2.py → example_yolo.py} +3 -4
- lightly_studio/export/export_dataset.py +65 -0
- lightly_studio/export/lightly_studio_label_input.py +120 -0
- lightly_studio/few_shot_classifier/classifier_manager.py +5 -26
- lightly_studio/metadata/compute_typicality.py +67 -0
- lightly_studio/models/annotation/annotation_base.py +18 -20
- lightly_studio/models/annotation/instance_segmentation.py +8 -8
- lightly_studio/models/annotation/object_detection.py +4 -4
- lightly_studio/models/dataset.py +6 -2
- lightly_studio/models/sample.py +10 -3
- lightly_studio/resolvers/annotation_label_resolver/__init__.py +2 -1
- lightly_studio/resolvers/annotation_label_resolver/get_all.py +15 -0
- lightly_studio/resolvers/annotation_resolver/__init__.py +2 -3
- lightly_studio/resolvers/annotation_resolver/create_many.py +3 -3
- lightly_studio/resolvers/annotation_resolver/delete_annotation.py +1 -1
- lightly_studio/resolvers/annotation_resolver/delete_annotations.py +7 -3
- lightly_studio/resolvers/annotation_resolver/get_by_id.py +19 -1
- lightly_studio/resolvers/annotation_resolver/update_annotation_label.py +0 -1
- lightly_studio/resolvers/annotations/annotations_filter.py +1 -11
- lightly_studio/resolvers/dataset_resolver.py +10 -0
- lightly_studio/resolvers/embedding_model_resolver.py +22 -0
- lightly_studio/resolvers/sample_resolver.py +53 -9
- lightly_studio/resolvers/tag_resolver.py +23 -0
- lightly_studio/selection/mundig.py +7 -10
- lightly_studio/selection/select.py +55 -46
- lightly_studio/selection/select_via_db.py +23 -19
- lightly_studio/selection/selection_config.py +10 -4
- lightly_studio/services/annotations_service/__init__.py +12 -0
- lightly_studio/services/annotations_service/create_annotation.py +63 -0
- lightly_studio/services/annotations_service/delete_annotation.py +22 -0
- lightly_studio/services/annotations_service/update_annotation.py +21 -32
- lightly_studio/services/annotations_service/update_annotation_bounding_box.py +36 -0
- lightly_studio-0.3.3.dist-info/METADATA +814 -0
- {lightly_studio-0.3.1.dist-info → lightly_studio-0.3.3.dist-info}/RECORD +130 -113
- lightly_studio/api/db.py +0 -133
- lightly_studio/api/routes/api/annotation_task.py +0 -38
- lightly_studio/api/routes/api/metrics.py +0 -80
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.DenzbfeK.css +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.OwPEPQZu.css +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.b653GmVf.css +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.T-zjSUd3.css +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B2FVR0s0.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B9zumHo5.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BJXwVxaE.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bx1xMsFy.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CcaPhhk3.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CvOmgdoc.js +0 -93
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CxtLVaYz.js +0 -3
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6RI2Zrd.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D98V7j6A.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIRAtgl0.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DjUWrjOv.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/XO7A28GO.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/hQVEETDE.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/nAHhluT7.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/r64xT6ao.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/vC4nQVEB.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.CjnvpsmS.js +0 -2
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.0o1H7wM9.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.XRq_TUwu.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.DfBwOEhN.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.CwF2_8mP.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.CS4muRY-.js +0 -6
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.Dm6t9F5W.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/6.Bw5ck4gK.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.CF0EDTR6.js +0 -1
- lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.Cw30LEcV.js +0 -1
- lightly_studio/metrics/detection/__init__.py +0 -0
- lightly_studio/metrics/detection/map.py +0 -268
- lightly_studio/models/annotation_task.py +0 -28
- lightly_studio/resolvers/annotation_resolver/create.py +0 -19
- lightly_studio/resolvers/annotation_task_resolver.py +0 -31
- lightly_studio-0.3.1.dist-info/METADATA +0 -520
- /lightly_studio/{metrics → core/dataset_query}/__init__.py +0 -0
- /lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/{OpenSans- → OpenSans-Medium.DVUZMR_6.ttf} +0 -0
- {lightly_studio-0.3.1.dist-info → lightly_studio-0.3.3.dist-info}/WHEEL +0 -0
lightly_studio/__init__.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Set up logging before importing any other modules.
|
|
2
2
|
# Add noqa to silence unused import and unsorted imports linter warnings.
|
|
3
3
|
from . import setup_logging # noqa: F401 I001
|
|
4
|
-
|
|
5
|
-
# TODO (Jonas 08/25): This will be removed as soon as the new interface is used in the examples
|
|
6
|
-
from lightly_studio.dataset.loader import DatasetLoader
|
|
7
4
|
from lightly_studio.core.dataset import Dataset
|
|
8
5
|
from lightly_studio.core.start_gui import start_gui
|
|
9
6
|
|
|
7
|
+
# TODO (Jonas 08/25): This will be removed as soon as the new interface is used in the examples
|
|
8
|
+
from lightly_studio.dataset.loader import DatasetLoader
|
|
9
|
+
from lightly_studio.models.annotation.annotation_base import AnnotationType
|
|
10
10
|
|
|
11
|
-
__all__ = ["Dataset", "DatasetLoader", "start_gui"]
|
|
11
|
+
__all__ = ["AnnotationType", "Dataset", "DatasetLoader", "start_gui"]
|
lightly_studio/api/app.py
CHANGED
|
@@ -11,19 +11,20 @@ from fastapi.routing import APIRoute
|
|
|
11
11
|
from sqlmodel import Session
|
|
12
12
|
from typing_extensions import Annotated
|
|
13
13
|
|
|
14
|
-
from lightly_studio
|
|
14
|
+
from lightly_studio import db_manager
|
|
15
15
|
from lightly_studio.api.routes import healthz, images, webapp
|
|
16
16
|
from lightly_studio.api.routes.api import (
|
|
17
17
|
annotation,
|
|
18
18
|
annotation_label,
|
|
19
|
-
annotation_task,
|
|
20
19
|
classifier,
|
|
21
20
|
dataset,
|
|
22
21
|
dataset_tag,
|
|
22
|
+
embeddings2d,
|
|
23
|
+
export,
|
|
23
24
|
features,
|
|
24
25
|
metadata,
|
|
25
|
-
metrics,
|
|
26
26
|
sample,
|
|
27
|
+
selection,
|
|
27
28
|
settings,
|
|
28
29
|
text_embedding,
|
|
29
30
|
)
|
|
@@ -84,16 +85,17 @@ api_router = APIRouter(prefix="/api", tags=["api"])
|
|
|
84
85
|
|
|
85
86
|
api_router.include_router(dataset.dataset_router)
|
|
86
87
|
api_router.include_router(dataset_tag.tag_router)
|
|
88
|
+
api_router.include_router(export.export_router)
|
|
87
89
|
api_router.include_router(sample.samples_router)
|
|
88
90
|
api_router.include_router(annotation_label.annotations_label_router)
|
|
89
91
|
api_router.include_router(annotation.annotations_router)
|
|
90
92
|
api_router.include_router(text_embedding.text_embedding_router)
|
|
91
|
-
api_router.include_router(annotation_task.router)
|
|
92
93
|
api_router.include_router(settings.settings_router)
|
|
93
94
|
api_router.include_router(classifier.classifier_router)
|
|
95
|
+
api_router.include_router(embeddings2d.embeddings2d_router)
|
|
94
96
|
api_router.include_router(features.features_router)
|
|
95
97
|
api_router.include_router(metadata.metadata_router)
|
|
96
|
-
api_router.include_router(
|
|
98
|
+
api_router.include_router(selection.selection_router)
|
|
97
99
|
|
|
98
100
|
|
|
99
101
|
app.include_router(api_router)
|
lightly_studio/api/db_tables.py
CHANGED
|
@@ -6,9 +6,6 @@ from lightly_studio.models.annotation.annotation_base import (
|
|
|
6
6
|
from lightly_studio.models.annotation_label import (
|
|
7
7
|
AnnotationLabelTable, # noqa: F401, required for SQLModel to work properly
|
|
8
8
|
)
|
|
9
|
-
from lightly_studio.models.annotation_task import (
|
|
10
|
-
AnnotationTaskTable, # noqa: F401, required for SQLModel to work properly
|
|
11
|
-
)
|
|
12
9
|
from lightly_studio.models.dataset import (
|
|
13
10
|
DatasetTable, # noqa: F401, required for SQLModel to work properly
|
|
14
11
|
)
|
|
@@ -7,16 +7,16 @@ from uuid import UUID
|
|
|
7
7
|
from fastapi import APIRouter, Body, Depends, HTTPException, Path
|
|
8
8
|
from fastapi.params import Query
|
|
9
9
|
from pydantic import BaseModel
|
|
10
|
-
from sqlmodel import Session
|
|
11
10
|
from typing_extensions import Annotated
|
|
12
11
|
|
|
13
|
-
from lightly_studio.api.
|
|
12
|
+
from lightly_studio.api.routes.api import annotations as annotations_module
|
|
14
13
|
from lightly_studio.api.routes.api.dataset import get_and_validate_dataset_id
|
|
15
14
|
from lightly_studio.api.routes.api.status import (
|
|
16
15
|
HTTP_STATUS_CREATED,
|
|
17
16
|
HTTP_STATUS_NOT_FOUND,
|
|
18
17
|
)
|
|
19
18
|
from lightly_studio.api.routes.api.validators import Paginated, PaginatedWithCursor
|
|
19
|
+
from lightly_studio.db_manager import SessionDep
|
|
20
20
|
from lightly_studio.models.annotation.annotation_base import (
|
|
21
21
|
AnnotationBaseTable,
|
|
22
22
|
AnnotationDetailsView,
|
|
@@ -27,6 +27,7 @@ from lightly_studio.resolvers import annotation_resolver, tag_resolver
|
|
|
27
27
|
from lightly_studio.resolvers.annotation_resolver.get_all import (
|
|
28
28
|
GetAllAnnotationsResult,
|
|
29
29
|
)
|
|
30
|
+
from lightly_studio.resolvers.annotation_resolver.update_bounding_box import BoundingBoxCoordinates
|
|
30
31
|
from lightly_studio.resolvers.annotations.annotations_filter import (
|
|
31
32
|
AnnotationsFilter,
|
|
32
33
|
)
|
|
@@ -36,7 +37,7 @@ from lightly_studio.services.annotations_service.update_annotation import (
|
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
annotations_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["annotations"])
|
|
39
|
-
|
|
40
|
+
annotations_router.include_router(annotations_module.create_annotation_router)
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
@annotations_router.get("/annotations/count")
|
|
@@ -134,11 +135,8 @@ class AnnotationUpdateInput(BaseModel):
|
|
|
134
135
|
|
|
135
136
|
annotation_id: UUID
|
|
136
137
|
dataset_id: UUID
|
|
137
|
-
label_name: str | None
|
|
138
|
-
|
|
139
|
-
y: int | None = None
|
|
140
|
-
width: int | None = None
|
|
141
|
-
height: int | None = None
|
|
138
|
+
label_name: str | None = None
|
|
139
|
+
bounding_box: BoundingBoxCoordinates | None = None
|
|
142
140
|
|
|
143
141
|
|
|
144
142
|
@annotations_router.put("/annotations/{annotation_id}")
|
|
@@ -161,10 +159,7 @@ def update_annotation(
|
|
|
161
159
|
annotation_id=annotation_id,
|
|
162
160
|
dataset_id=dataset_id,
|
|
163
161
|
label_name=annotation_update_input.label_name,
|
|
164
|
-
|
|
165
|
-
y=annotation_update_input.y,
|
|
166
|
-
width=annotation_update_input.width,
|
|
167
|
-
height=annotation_update_input.height,
|
|
162
|
+
bounding_box=annotation_update_input.bounding_box,
|
|
168
163
|
),
|
|
169
164
|
)
|
|
170
165
|
|
|
@@ -188,10 +183,7 @@ def update_annotations(
|
|
|
188
183
|
annotation_id=annotation_update_input.annotation_id,
|
|
189
184
|
dataset_id=dataset_id,
|
|
190
185
|
label_name=annotation_update_input.label_name,
|
|
191
|
-
|
|
192
|
-
y=annotation_update_input.y,
|
|
193
|
-
width=annotation_update_input.width,
|
|
194
|
-
height=annotation_update_input.height,
|
|
186
|
+
bounding_box=annotation_update_input.bounding_box,
|
|
195
187
|
)
|
|
196
188
|
for annotation_update_input in annotation_update_inputs
|
|
197
189
|
],
|
|
@@ -231,3 +223,27 @@ def remove_tag_from_annotation(
|
|
|
231
223
|
raise HTTPException(status_code=HTTP_STATUS_NOT_FOUND, detail=f"Tag {tag_id} not found")
|
|
232
224
|
|
|
233
225
|
return True
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@annotations_router.delete("/annotations/{annotation_id}")
|
|
229
|
+
def delete_annotation(
|
|
230
|
+
session: SessionDep,
|
|
231
|
+
# We need dataset_id because generator doesn't include it
|
|
232
|
+
# actuall path for this route is /datasets/{dataset_id}/annotations/{annotation_id}
|
|
233
|
+
dataset_id: Annotated[ # noqa: ARG001
|
|
234
|
+
UUID,
|
|
235
|
+
Path(title="Dataset Id", description="The ID of the dataset"),
|
|
236
|
+
],
|
|
237
|
+
annotation_id: Annotated[
|
|
238
|
+
UUID, Path(title="Annotation ID", description="ID of the annotation to delete")
|
|
239
|
+
],
|
|
240
|
+
) -> dict[str, str]:
|
|
241
|
+
"""Delete an annotation from the database."""
|
|
242
|
+
try:
|
|
243
|
+
annotations_service.delete_annotation(session=session, annotation_id=annotation_id)
|
|
244
|
+
return {"status": "deleted"}
|
|
245
|
+
except ValueError as e:
|
|
246
|
+
raise HTTPException(
|
|
247
|
+
status_code=HTTP_STATUS_NOT_FOUND,
|
|
248
|
+
detail="Annotation not found",
|
|
249
|
+
) from e
|
|
@@ -4,15 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
|
-
from fastapi import APIRouter,
|
|
8
|
-
from sqlmodel import Session
|
|
9
|
-
from typing_extensions import Annotated
|
|
7
|
+
from fastapi import APIRouter, HTTPException
|
|
10
8
|
|
|
11
|
-
from lightly_studio.api.db import get_session
|
|
12
9
|
from lightly_studio.api.routes.api.status import (
|
|
13
10
|
HTTP_STATUS_CREATED,
|
|
14
11
|
HTTP_STATUS_NOT_FOUND,
|
|
15
12
|
)
|
|
13
|
+
from lightly_studio.db_manager import SessionDep
|
|
16
14
|
from lightly_studio.models.annotation_label import (
|
|
17
15
|
AnnotationLabelCreate,
|
|
18
16
|
AnnotationLabelTable,
|
|
@@ -20,7 +18,6 @@ from lightly_studio.models.annotation_label import (
|
|
|
20
18
|
from lightly_studio.resolvers import annotation_label_resolver
|
|
21
19
|
|
|
22
20
|
annotations_label_router = APIRouter()
|
|
23
|
-
SessionDep = Annotated[Session, Depends(get_session)]
|
|
24
21
|
|
|
25
22
|
|
|
26
23
|
@annotations_label_router.post(
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Create annotation route."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, Path
|
|
8
|
+
from fastapi.params import Body
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from typing_extensions import Annotated
|
|
11
|
+
|
|
12
|
+
from lightly_studio.db_manager import SessionDep
|
|
13
|
+
from lightly_studio.models.annotation.annotation_base import (
|
|
14
|
+
AnnotationBaseTable,
|
|
15
|
+
AnnotationType,
|
|
16
|
+
AnnotationView,
|
|
17
|
+
)
|
|
18
|
+
from lightly_studio.services import annotations_service
|
|
19
|
+
from lightly_studio.services.annotations_service.create_annotation import AnnotationCreateParams
|
|
20
|
+
|
|
21
|
+
create_annotation_router = APIRouter()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class AnnotationCreateInput(BaseModel):
|
|
25
|
+
"""API interface to create annotation."""
|
|
26
|
+
|
|
27
|
+
annotation_label_id: UUID
|
|
28
|
+
annotation_type: AnnotationType
|
|
29
|
+
sample_id: UUID
|
|
30
|
+
x: int | None = None
|
|
31
|
+
y: int | None = None
|
|
32
|
+
width: int | None = None
|
|
33
|
+
height: int | None = None
|
|
34
|
+
segmentation_mask: list[int] | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@create_annotation_router.post(
|
|
38
|
+
"/annotations",
|
|
39
|
+
response_model=AnnotationView,
|
|
40
|
+
)
|
|
41
|
+
def create_annotation(
|
|
42
|
+
dataset_id: Annotated[UUID, Path(title="Dataset Id", description="The ID of the dataset")],
|
|
43
|
+
session: SessionDep,
|
|
44
|
+
create_annotation_input: Annotated[AnnotationCreateInput, Body()],
|
|
45
|
+
) -> AnnotationBaseTable:
|
|
46
|
+
"""Create a new annotation."""
|
|
47
|
+
return annotations_service.create_annotation(
|
|
48
|
+
session=session,
|
|
49
|
+
annotation=AnnotationCreateParams(
|
|
50
|
+
dataset_id=dataset_id, **create_annotation_input.model_dump()
|
|
51
|
+
),
|
|
52
|
+
)
|
|
@@ -6,13 +6,11 @@ import io
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from uuid import UUID
|
|
8
8
|
|
|
9
|
-
from fastapi import APIRouter,
|
|
9
|
+
from fastapi import APIRouter, UploadFile
|
|
10
10
|
from fastapi.responses import StreamingResponse
|
|
11
11
|
from pydantic import BaseModel
|
|
12
|
-
from sqlmodel import Session
|
|
13
|
-
from typing_extensions import Annotated
|
|
14
12
|
|
|
15
|
-
from lightly_studio.
|
|
13
|
+
from lightly_studio.db_manager import SessionDep
|
|
16
14
|
from lightly_studio.few_shot_classifier.classifier import (
|
|
17
15
|
ExportType,
|
|
18
16
|
)
|
|
@@ -22,7 +20,6 @@ from lightly_studio.few_shot_classifier.classifier_manager import (
|
|
|
22
20
|
from lightly_studio.models.classifier import EmbeddingClassifier
|
|
23
21
|
|
|
24
22
|
classifier_router = APIRouter()
|
|
25
|
-
SessionDep = Annotated[Session, Depends(get_session)]
|
|
26
23
|
|
|
27
24
|
|
|
28
25
|
class GetNegativeSamplesRequest(BaseModel):
|
|
@@ -9,12 +9,12 @@ from uuid import UUID
|
|
|
9
9
|
from fastapi import APIRouter, Depends, HTTPException, Path, Query
|
|
10
10
|
from fastapi.responses import PlainTextResponse
|
|
11
11
|
from pydantic import BaseModel
|
|
12
|
-
from sqlmodel import Field
|
|
12
|
+
from sqlmodel import Field
|
|
13
13
|
from typing_extensions import Annotated
|
|
14
14
|
|
|
15
|
-
from lightly_studio.api.db import get_session
|
|
16
15
|
from lightly_studio.api.routes.api.status import HTTP_STATUS_NOT_FOUND
|
|
17
16
|
from lightly_studio.api.routes.api.validators import Paginated
|
|
17
|
+
from lightly_studio.db_manager import SessionDep
|
|
18
18
|
from lightly_studio.models.dataset import (
|
|
19
19
|
DatasetCreate,
|
|
20
20
|
DatasetTable,
|
|
@@ -26,7 +26,6 @@ from lightly_studio.resolvers.dataset_resolver import (
|
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
dataset_router = APIRouter()
|
|
29
|
-
SessionDep = Annotated[Session, Depends(get_session)]
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
def get_and_validate_dataset_id(
|
|
@@ -109,6 +108,7 @@ def delete_dataset(
|
|
|
109
108
|
return {"status": "deleted"}
|
|
110
109
|
|
|
111
110
|
|
|
111
|
+
# TODO(Michal, 09/2025): Move to export.py
|
|
112
112
|
class ExportBody(BaseModel):
|
|
113
113
|
"""body parameters for including or excluding tag_ids or sample_ids."""
|
|
114
114
|
|
|
@@ -124,6 +124,7 @@ class ExportBody(BaseModel):
|
|
|
124
124
|
# of sample_ids, it is a POST request to avoid URL length limitations.
|
|
125
125
|
# A body with a GET request is supported by fastAPI however it has undefined
|
|
126
126
|
# behavior: https://fastapi.tiangolo.com/tutorial/body/
|
|
127
|
+
# TODO(Michal, 09/2025): Move to export.py
|
|
127
128
|
@dataset_router.post(
|
|
128
129
|
"/datasets/{dataset_id}/export",
|
|
129
130
|
)
|
|
@@ -156,11 +157,7 @@ def export_dataset_to_absolute_paths(
|
|
|
156
157
|
return response
|
|
157
158
|
|
|
158
159
|
|
|
159
|
-
|
|
160
|
-
Endpoint to export samples from a dataset.
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
# TODO(Michal, 09/2025): Move to export.py
|
|
164
161
|
@dataset_router.post(
|
|
165
162
|
"/datasets/{dataset_id}/export/stats",
|
|
166
163
|
)
|
|
@@ -8,10 +8,9 @@ from uuid import UUID
|
|
|
8
8
|
from fastapi import APIRouter, Depends, HTTPException, Path, Query
|
|
9
9
|
from pydantic import BaseModel
|
|
10
10
|
from sqlalchemy.exc import IntegrityError
|
|
11
|
-
from sqlmodel import Field
|
|
11
|
+
from sqlmodel import Field
|
|
12
12
|
from typing_extensions import Annotated
|
|
13
13
|
|
|
14
|
-
from lightly_studio.api.db import get_session
|
|
15
14
|
from lightly_studio.api.routes.api.dataset import get_and_validate_dataset_id
|
|
16
15
|
from lightly_studio.api.routes.api.status import (
|
|
17
16
|
HTTP_STATUS_CONFLICT,
|
|
@@ -19,6 +18,7 @@ from lightly_studio.api.routes.api.status import (
|
|
|
19
18
|
HTTP_STATUS_NOT_FOUND,
|
|
20
19
|
)
|
|
21
20
|
from lightly_studio.api.routes.api.validators import Paginated
|
|
21
|
+
from lightly_studio.db_manager import SessionDep
|
|
22
22
|
from lightly_studio.models.dataset import DatasetTable
|
|
23
23
|
from lightly_studio.models.tag import (
|
|
24
24
|
TagCreate,
|
|
@@ -31,7 +31,6 @@ from lightly_studio.models.tag import (
|
|
|
31
31
|
from lightly_studio.resolvers import tag_resolver
|
|
32
32
|
|
|
33
33
|
tag_router = APIRouter()
|
|
34
|
-
SessionDep = Annotated[Session, Depends(get_session)]
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
@tag_router.post(
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Routes delivering 2D embeddings for visualization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pyarrow as pa
|
|
9
|
+
from fastapi import APIRouter, HTTPException, Response
|
|
10
|
+
from numpy.typing import NDArray
|
|
11
|
+
from pyarrow import ipc
|
|
12
|
+
from sklearn.manifold import TSNE
|
|
13
|
+
from sqlmodel import select
|
|
14
|
+
|
|
15
|
+
from lightly_studio.db_manager import SessionDep
|
|
16
|
+
from lightly_studio.models.dataset import DatasetTable
|
|
17
|
+
from lightly_studio.models.embedding_model import EmbeddingModelTable
|
|
18
|
+
from lightly_studio.resolvers import sample_embedding_resolver
|
|
19
|
+
|
|
20
|
+
embeddings2d_router = APIRouter()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@embeddings2d_router.get("/embeddings2d/tsne")
|
|
24
|
+
def get_embeddings2d__tsne(session: SessionDep) -> Response:
|
|
25
|
+
"""Return 2D embeddings serialized as an Arrow stream."""
|
|
26
|
+
# TODO(Malte, 09/2025): Support choosing the dataset via API parameter.
|
|
27
|
+
dataset = session.exec(select(DatasetTable).limit(1)).first()
|
|
28
|
+
if dataset is None:
|
|
29
|
+
raise HTTPException(status_code=404, detail="No dataset configured.")
|
|
30
|
+
|
|
31
|
+
# TODO(Malte, 09/2025): Support choosing the embedding model via API parameter.
|
|
32
|
+
embedding_model = session.exec(
|
|
33
|
+
select(EmbeddingModelTable)
|
|
34
|
+
.where(EmbeddingModelTable.dataset_id == dataset.dataset_id)
|
|
35
|
+
.limit(1)
|
|
36
|
+
).first()
|
|
37
|
+
if embedding_model is None:
|
|
38
|
+
raise HTTPException(status_code=404, detail="No embedding model configured.")
|
|
39
|
+
|
|
40
|
+
# TODO(Malte, 09/2025): Support choosing a subset of samples via API parameter.
|
|
41
|
+
embeddings = sample_embedding_resolver.get_all_by_dataset_id(
|
|
42
|
+
session=session,
|
|
43
|
+
dataset_id=dataset.dataset_id,
|
|
44
|
+
embedding_model_id=embedding_model.embedding_model_id,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
embedding_values = np.asarray([e.embedding for e in embeddings], dtype=np.float32)
|
|
48
|
+
embedding_values_tsne = _calculate_tsne_embeddings(embedding_values)
|
|
49
|
+
x = embedding_values_tsne[:, 0]
|
|
50
|
+
y = embedding_values_tsne[:, 1]
|
|
51
|
+
|
|
52
|
+
# TODO(Malte, 09/2025): Save the 2D-embeddings in the database to avoid recomputing
|
|
53
|
+
# them on every request.
|
|
54
|
+
|
|
55
|
+
# TODO(Malte, 09/2025): Include a sample identifier in the returned payload.
|
|
56
|
+
table = pa.table(
|
|
57
|
+
{
|
|
58
|
+
"x": pa.array(x, type=pa.float32()),
|
|
59
|
+
"y": pa.array(y, type=pa.float32()),
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
buffer = io.BytesIO()
|
|
64
|
+
with ipc.new_stream(buffer, table.schema) as writer:
|
|
65
|
+
writer.write_table(table)
|
|
66
|
+
buffer.seek(0)
|
|
67
|
+
|
|
68
|
+
return Response(
|
|
69
|
+
content=buffer.getvalue(),
|
|
70
|
+
media_type="application/vnd.apache.arrow.stream",
|
|
71
|
+
headers={
|
|
72
|
+
"Content-Disposition": "inline; filename=embeddings2d.arrow",
|
|
73
|
+
"Content-Type": "application/vnd.apache.arrow.stream",
|
|
74
|
+
"X-Content-Type-Options": "nosniff",
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _calculate_tsne_embeddings(embedding_values: NDArray[np.float32]) -> NDArray[np.float32]:
|
|
80
|
+
# TODO(Malte, 10/2025): Switch to a better and faster projection method than
|
|
81
|
+
# scikit-learn's TSNE.
|
|
82
|
+
# See https://linear.app/lightly/issue/LIG-7678/embedding-plot-investigate-fasterandbetter-2d-computation-options
|
|
83
|
+
n_samples = embedding_values.shape[0]
|
|
84
|
+
# For 0, 1 or 2 samples we hard-code deterministic coordinates.
|
|
85
|
+
if n_samples == 0:
|
|
86
|
+
return np.zeros((0, 2), dtype=np.float32)
|
|
87
|
+
if n_samples == 1:
|
|
88
|
+
return np.asarray([[0.0, 0.0]], dtype=np.float32)
|
|
89
|
+
if n_samples == 2: # noqa: PLR2004
|
|
90
|
+
return np.asarray([[0.0, 0.0], [1.0, 1.0]], dtype=np.float32)
|
|
91
|
+
|
|
92
|
+
# Copied from lightly-core:
|
|
93
|
+
# https://github.com/lightly-ai/lightly-core/blob/b738952516e916eba42fdd28498491ff18df5c1e/appv2/packages/queueworker/src/jobs/embeddings2d/function-source/main.py#L179-L186
|
|
94
|
+
embeddings_2d: NDArray[np.float32] = TSNE(
|
|
95
|
+
init="pca", # changed in https://github.com/scikit-learn/scikit-learn/issues/18018
|
|
96
|
+
learning_rate="auto", # changed in https://github.com/scikit-learn/scikit-learn/issues/18018
|
|
97
|
+
n_components=2,
|
|
98
|
+
# Perplexity must be _less_ than the number of entries. 30 is the default value.
|
|
99
|
+
# https://scikit-learn.org/stable/modules/generated/sklearn.manifold.TSNE.html
|
|
100
|
+
perplexity=min(30.0, float(n_samples - 1)),
|
|
101
|
+
# Make the computation deterministic.
|
|
102
|
+
random_state=0,
|
|
103
|
+
).fit_transform(embedding_values)
|
|
104
|
+
return embeddings_2d
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""API routes for exporting dataset annotation tasks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Generator
|
|
6
|
+
from pathlib import Path as PathlibPath
|
|
7
|
+
from tempfile import TemporaryDirectory
|
|
8
|
+
|
|
9
|
+
from fastapi import APIRouter, Depends, Path
|
|
10
|
+
from fastapi.responses import StreamingResponse
|
|
11
|
+
from typing_extensions import Annotated
|
|
12
|
+
|
|
13
|
+
from lightly_studio.api.routes.api import dataset as dataset_api
|
|
14
|
+
from lightly_studio.core.dataset_query.dataset_query import DatasetQuery
|
|
15
|
+
from lightly_studio.db_manager import SessionDep
|
|
16
|
+
from lightly_studio.export import export_dataset
|
|
17
|
+
from lightly_studio.models.dataset import DatasetTable
|
|
18
|
+
|
|
19
|
+
export_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["export"])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@export_router.get("/export/annotations")
|
|
23
|
+
def export_dataset_annotations(
|
|
24
|
+
dataset: Annotated[
|
|
25
|
+
DatasetTable,
|
|
26
|
+
Path(title="Dataset Id"),
|
|
27
|
+
Depends(dataset_api.get_and_validate_dataset_id),
|
|
28
|
+
],
|
|
29
|
+
session: SessionDep,
|
|
30
|
+
) -> StreamingResponse:
|
|
31
|
+
"""Export dataset annotations for an object detection task in COCO format."""
|
|
32
|
+
# Query to export - all samples in the dataset.
|
|
33
|
+
dataset_query = DatasetQuery(dataset=dataset, session=session)
|
|
34
|
+
|
|
35
|
+
# Create the export in a temporary directory. We cannot use a context manager
|
|
36
|
+
# because the directory should be deleted only after the file has finished streaming.
|
|
37
|
+
temp_dir = TemporaryDirectory()
|
|
38
|
+
output_path = PathlibPath(temp_dir.name) / "coco_export.json"
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
export_dataset.to_coco_object_detections(
|
|
42
|
+
session=session,
|
|
43
|
+
samples=dataset_query,
|
|
44
|
+
output_json=output_path,
|
|
45
|
+
)
|
|
46
|
+
except Exception:
|
|
47
|
+
temp_dir.cleanup()
|
|
48
|
+
# Reraise.
|
|
49
|
+
raise
|
|
50
|
+
|
|
51
|
+
return StreamingResponse(
|
|
52
|
+
content=_stream_export_file(
|
|
53
|
+
temp_dir=temp_dir,
|
|
54
|
+
file_path=output_path,
|
|
55
|
+
),
|
|
56
|
+
media_type="application/json",
|
|
57
|
+
headers={
|
|
58
|
+
"Access-Control-Expose-Headers": "Content-Disposition",
|
|
59
|
+
"Content-Disposition": f"attachment; filename={output_path.name}",
|
|
60
|
+
},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _stream_export_file(
|
|
65
|
+
temp_dir: TemporaryDirectory[str],
|
|
66
|
+
file_path: PathlibPath,
|
|
67
|
+
) -> Generator[bytes, None, None]:
|
|
68
|
+
"""Stream the export file and clean up the temporary directory afterwards."""
|
|
69
|
+
try:
|
|
70
|
+
with file_path.open("rb") as file:
|
|
71
|
+
yield from file
|
|
72
|
+
finally:
|
|
73
|
+
temp_dir.cleanup()
|
|
@@ -5,18 +5,16 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import List
|
|
6
6
|
from uuid import UUID
|
|
7
7
|
|
|
8
|
-
from fastapi import APIRouter,
|
|
9
|
-
from sqlmodel import Session
|
|
8
|
+
from fastapi import APIRouter, Path
|
|
10
9
|
from typing_extensions import Annotated
|
|
11
10
|
|
|
12
|
-
from lightly_studio.
|
|
11
|
+
from lightly_studio.db_manager import SessionDep
|
|
13
12
|
from lightly_studio.models.metadata import MetadataInfoView
|
|
14
13
|
from lightly_studio.resolvers.metadata_resolver.sample.get_metadata_info import (
|
|
15
14
|
get_all_metadata_keys_and_schema,
|
|
16
15
|
)
|
|
17
16
|
|
|
18
17
|
metadata_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["metadata"])
|
|
19
|
-
SessionDep = Annotated[Session, Depends(get_session)]
|
|
20
18
|
|
|
21
19
|
|
|
22
20
|
@metadata_router.get("/metadata/info", response_model=List[MetadataInfoView])
|
|
@@ -6,16 +6,15 @@ from uuid import UUID
|
|
|
6
6
|
|
|
7
7
|
from fastapi import APIRouter, Depends, HTTPException, Path, Query
|
|
8
8
|
from pydantic import BaseModel, Field
|
|
9
|
-
from sqlmodel import Session
|
|
10
9
|
from typing_extensions import Annotated
|
|
11
10
|
|
|
12
|
-
from lightly_studio.api.db import get_session
|
|
13
11
|
from lightly_studio.api.routes.api.dataset import get_and_validate_dataset_id
|
|
14
12
|
from lightly_studio.api.routes.api.status import (
|
|
15
13
|
HTTP_STATUS_CREATED,
|
|
16
14
|
HTTP_STATUS_NOT_FOUND,
|
|
17
15
|
)
|
|
18
16
|
from lightly_studio.api.routes.api.validators import Paginated
|
|
17
|
+
from lightly_studio.db_manager import SessionDep
|
|
19
18
|
from lightly_studio.models.dataset import DatasetTable
|
|
20
19
|
from lightly_studio.models.sample import (
|
|
21
20
|
SampleCreate,
|
|
@@ -27,12 +26,12 @@ from lightly_studio.resolvers import (
|
|
|
27
26
|
sample_resolver,
|
|
28
27
|
tag_resolver,
|
|
29
28
|
)
|
|
29
|
+
from lightly_studio.resolvers.sample_resolver import GetAllSamplesByDatasetIdResult
|
|
30
30
|
from lightly_studio.resolvers.samples_filter import (
|
|
31
31
|
SampleFilter,
|
|
32
32
|
)
|
|
33
33
|
|
|
34
34
|
samples_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["samples"])
|
|
35
|
-
SessionDep = Annotated[Session, Depends(get_session)]
|
|
36
35
|
|
|
37
36
|
|
|
38
37
|
@samples_router.post("/samples", response_model=SampleView)
|
|
@@ -60,7 +59,7 @@ def read_samples(
|
|
|
60
59
|
session: SessionDep,
|
|
61
60
|
dataset_id: Annotated[UUID, Path(title="Dataset Id")],
|
|
62
61
|
body: ReadSamplesRequest,
|
|
63
|
-
) ->
|
|
62
|
+
) -> GetAllSamplesByDatasetIdResult:
|
|
64
63
|
"""Retrieve a list of samples from the database with optional filtering.
|
|
65
64
|
|
|
66
65
|
Args:
|
|
@@ -71,22 +70,15 @@ def read_samples(
|
|
|
71
70
|
Returns:
|
|
72
71
|
A list of filtered samples.
|
|
73
72
|
"""
|
|
74
|
-
|
|
73
|
+
return sample_resolver.get_all_by_dataset_id(
|
|
75
74
|
session=session,
|
|
76
75
|
dataset_id=dataset_id,
|
|
77
|
-
|
|
78
|
-
offset=body.pagination.offset if body.pagination else 0,
|
|
79
|
-
limit=body.pagination.limit if body.pagination else 10,
|
|
76
|
+
pagination=body.pagination,
|
|
80
77
|
filters=body.filters,
|
|
81
78
|
text_embedding=body.text_embedding,
|
|
82
79
|
sample_ids=body.sample_ids,
|
|
83
80
|
)
|
|
84
81
|
|
|
85
|
-
return SampleViewsWithCount(
|
|
86
|
-
data=result.samples,
|
|
87
|
-
total_count=result.total_count,
|
|
88
|
-
)
|
|
89
|
-
|
|
90
82
|
|
|
91
83
|
@samples_router.get("/samples/dimensions")
|
|
92
84
|
def get_sample_dimensions(
|