Skip to content

Commit 9f11879

Browse files
Merge branch 'main' into leonardo-lig-7942-gui-allow-to-create-tag-from-all-samples-fullfilling-the
2 parents cfb3c7c + e14ff9e commit 9f11879

File tree

32 files changed

+1505
-853
lines changed

32 files changed

+1505
-853
lines changed

lightly_studio/src/lightly_studio/api/routes/api/annotation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ def update_annotations(
237237
collection_id=collection_id,
238238
label_name=annotation_update_input.label_name,
239239
bounding_box=annotation_update_input.bounding_box,
240+
segmentation_mask=annotation_update_input.segmentation_mask,
240241
)
241242
for annotation_update_input in annotation_update_inputs
242243
],

lightly_studio/src/lightly_studio/api/routes/api/collection.py

Lines changed: 7 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,15 @@
22

33
from __future__ import annotations
44

5-
from datetime import datetime, timezone
65
from typing import List
76
from uuid import UUID
87

98
from fastapi import APIRouter, Depends, HTTPException, Path, Query
10-
from fastapi.responses import PlainTextResponse
11-
from pydantic import BaseModel
12-
from sqlmodel import Field
139
from typing_extensions import Annotated
1410

1511
from lightly_studio.api.routes.api.status import HTTP_STATUS_NOT_FOUND
1612
from lightly_studio.api.routes.api.validators import Paginated
13+
from lightly_studio.dataset import embedding_utils
1714
from lightly_studio.db_manager import SessionDep
1815
from lightly_studio.models.collection import (
1916
CollectionCreate,
@@ -23,7 +20,6 @@
2320
CollectionViewWithCount,
2421
)
2522
from lightly_studio.resolvers import collection_resolver
26-
from lightly_studio.resolvers.collection_resolver.export import ExportFilter
2723

2824
collection_router = APIRouter()
2925

@@ -124,72 +120,16 @@ def delete_collection(
124120
return {"status": "deleted"}
125121

126122

127-
# TODO(Michal, 09/2025): Move to export.py
128-
class ExportBody(BaseModel):
129-
"""body parameters for including or excluding tag_ids or sample_ids."""
130-
131-
include: ExportFilter | None = Field(
132-
None, description="include filter for sample_ids or tag_ids"
133-
)
134-
exclude: ExportFilter | None = Field(
135-
None, description="exclude filter for sample_ids or tag_ids"
136-
)
137-
138-
139-
# This endpoint should be a GET, however due to the potential huge size
140-
# of sample_ids, it is a POST request to avoid URL length limitations.
141-
# A body with a GET request is supported by fastAPI however it has undefined
142-
# behavior: https://fastapi.tiangolo.com/tutorial/body/
143-
# TODO(Michal, 09/2025): Move to export.py
144-
@collection_router.post(
145-
"/collections/{collection_id}/export",
146-
)
147-
def export_collection_to_absolute_paths(
123+
@collection_router.get("/collections/{collection_id}/has_embeddings")
124+
def has_embeddings(
148125
session: SessionDep,
149126
collection: Annotated[
150127
CollectionTable,
151128
Path(title="collection Id"),
152129
Depends(get_and_validate_collection_id),
153130
],
154-
body: ExportBody,
155-
) -> PlainTextResponse:
156-
"""Export collection from the database."""
157-
# export collection to absolute paths
158-
exported = collection_resolver.export(
159-
session=session,
160-
collection_id=collection.collection_id,
161-
include=body.include,
162-
exclude=body.exclude,
163-
)
164-
165-
# Create a response with the exported data
166-
response = PlainTextResponse("\n".join(exported))
167-
168-
# Add the Content-Disposition header to force download
169-
filename = f"{collection.name}_exported_{datetime.now(timezone.utc)}.txt"
170-
response.headers["Access-Control-Expose-Headers"] = "Content-Disposition"
171-
response.headers["Content-Disposition"] = f"attachment; filename={filename}"
172-
173-
return response
174-
175-
176-
# TODO(Michal, 09/2025): Move to export.py
177-
@collection_router.post(
178-
"/collections/{collection_id}/export/stats",
179-
)
180-
def export_collection_stats(
181-
session: SessionDep,
182-
collection: Annotated[
183-
CollectionTable,
184-
Path(title="collection Id"),
185-
Depends(get_and_validate_collection_id),
186-
],
187-
body: ExportBody,
188-
) -> int:
189-
"""Get statistics about the export query."""
190-
return collection_resolver.get_filtered_samples_count(
191-
session=session,
192-
collection_id=collection.collection_id,
193-
include=body.include,
194-
exclude=body.exclude,
131+
) -> bool:
132+
"""Check if a collection has embeddings."""
133+
return embedding_utils.collection_has_embeddings(
134+
session=session, collection_id=collection.collection_id
195135
)

lightly_studio/src/lightly_studio/api/routes/api/export.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,23 @@
33
from __future__ import annotations
44

55
from collections.abc import Generator
6+
from datetime import datetime, timezone
67
from pathlib import Path as PathlibPath
78
from tempfile import TemporaryDirectory
89

910
from fastapi import APIRouter, Depends, Path
10-
from fastapi.responses import StreamingResponse
11+
from fastapi.responses import PlainTextResponse, StreamingResponse
12+
from pydantic import BaseModel
13+
from sqlmodel import Field
1114
from typing_extensions import Annotated
1215

1316
from lightly_studio.api.routes.api import collection as collection_api
1417
from lightly_studio.core.dataset_query.dataset_query import DatasetQuery
1518
from lightly_studio.db_manager import SessionDep
1619
from lightly_studio.export import export_dataset
1720
from lightly_studio.models.collection import CollectionTable
21+
from lightly_studio.resolvers import collection_resolver
22+
from lightly_studio.resolvers.collection_resolver.export import ExportFilter
1823

1924
export_router = APIRouter(prefix="/collections/{collection_id}", tags=["export"])
2025

@@ -103,6 +108,74 @@ def export_collection_captions(
103108
)
104109

105110

111+
class ExportBody(BaseModel):
112+
"""body parameters for including or excluding tag_ids or sample_ids."""
113+
114+
include: ExportFilter | None = Field(
115+
None, description="include filter for sample_ids or tag_ids"
116+
)
117+
exclude: ExportFilter | None = Field(
118+
None, description="exclude filter for sample_ids or tag_ids"
119+
)
120+
121+
122+
# This endpoint should be a GET, however due to the potential huge size
123+
# of sample_ids, it is a POST request to avoid URL length limitations.
124+
# A body with a GET request is supported by fastAPI however it has undefined
125+
# behavior: https://fastapi.tiangolo.com/tutorial/body/
126+
@export_router.post(
127+
"/export",
128+
)
129+
def export_collection_to_absolute_paths(
130+
session: SessionDep,
131+
collection: Annotated[
132+
CollectionTable,
133+
Path(title="collection Id"),
134+
Depends(collection_api.get_and_validate_collection_id),
135+
],
136+
body: ExportBody,
137+
) -> PlainTextResponse:
138+
"""Export collection from the database."""
139+
# export collection to absolute paths
140+
exported = collection_resolver.export(
141+
session=session,
142+
collection_id=collection.collection_id,
143+
include=body.include,
144+
exclude=body.exclude,
145+
)
146+
147+
# Create a response with the exported data
148+
response = PlainTextResponse("\n".join(exported))
149+
150+
# Add the Content-Disposition header to force download
151+
filename = f"{collection.name}_exported_{datetime.now(timezone.utc)}.txt"
152+
response.headers["Access-Control-Expose-Headers"] = "Content-Disposition"
153+
response.headers["Content-Disposition"] = f"attachment; filename={filename}"
154+
155+
return response
156+
157+
158+
@export_router.post(
159+
"/export/stats",
160+
)
161+
def export_collection_stats(
162+
session: SessionDep,
163+
collection: Annotated[
164+
CollectionTable,
165+
Path(title="collection Id"),
166+
Depends(collection_api.get_and_validate_collection_id),
167+
],
168+
body: ExportBody,
169+
) -> int:
170+
"""Get statistics about the export query."""
171+
return collection_resolver.get_filtered_samples_count(
172+
session=session,
173+
collection_id=collection.collection_id,
174+
include=body.include,
175+
exclude=body.exclude,
176+
)
177+
178+
106179
def _stream_export_file(
107180
temp_dir: TemporaryDirectory[str],
108181
file_path: PathlibPath,

lightly_studio/src/lightly_studio/api/routes/api/features.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
features_router = APIRouter()
1212

1313

14+
# TODO(Michal, 12/2025): Features are currently unused. Remove the endpoint if still not used
15+
# in a couple of months.
1416
@features_router.get("/features")
1517
def get_features() -> list[str]:
1618
"""Get the list of active features in the LightlyStudio app."""

0 commit comments

Comments
 (0)