lightly-studio 0.3.1__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 (219) hide show
  1. lightly_studio/__init__.py +11 -0
  2. lightly_studio/api/__init__.py +0 -0
  3. lightly_studio/api/app.py +110 -0
  4. lightly_studio/api/cache.py +77 -0
  5. lightly_studio/api/db.py +133 -0
  6. lightly_studio/api/db_tables.py +32 -0
  7. lightly_studio/api/features.py +7 -0
  8. lightly_studio/api/routes/api/annotation.py +233 -0
  9. lightly_studio/api/routes/api/annotation_label.py +90 -0
  10. lightly_studio/api/routes/api/annotation_task.py +38 -0
  11. lightly_studio/api/routes/api/classifier.py +387 -0
  12. lightly_studio/api/routes/api/dataset.py +182 -0
  13. lightly_studio/api/routes/api/dataset_tag.py +257 -0
  14. lightly_studio/api/routes/api/exceptions.py +96 -0
  15. lightly_studio/api/routes/api/features.py +17 -0
  16. lightly_studio/api/routes/api/metadata.py +37 -0
  17. lightly_studio/api/routes/api/metrics.py +80 -0
  18. lightly_studio/api/routes/api/sample.py +196 -0
  19. lightly_studio/api/routes/api/settings.py +45 -0
  20. lightly_studio/api/routes/api/status.py +19 -0
  21. lightly_studio/api/routes/api/text_embedding.py +48 -0
  22. lightly_studio/api/routes/api/validators.py +17 -0
  23. lightly_studio/api/routes/healthz.py +13 -0
  24. lightly_studio/api/routes/images.py +104 -0
  25. lightly_studio/api/routes/webapp.py +51 -0
  26. lightly_studio/api/server.py +82 -0
  27. lightly_studio/core/__init__.py +0 -0
  28. lightly_studio/core/dataset.py +523 -0
  29. lightly_studio/core/sample.py +77 -0
  30. lightly_studio/core/start_gui.py +15 -0
  31. lightly_studio/dataset/__init__.py +0 -0
  32. lightly_studio/dataset/edge_embedding_generator.py +144 -0
  33. lightly_studio/dataset/embedding_generator.py +91 -0
  34. lightly_studio/dataset/embedding_manager.py +163 -0
  35. lightly_studio/dataset/env.py +16 -0
  36. lightly_studio/dataset/file_utils.py +35 -0
  37. lightly_studio/dataset/loader.py +622 -0
  38. lightly_studio/dataset/mobileclip_embedding_generator.py +144 -0
  39. lightly_studio/dist_lightly_studio_view_app/_app/env.js +1 -0
  40. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.DenzbfeK.css +1 -0
  41. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/LightlyLogo.BNjCIww-.png +0 -0
  42. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans- +0 -0
  43. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Bold.DGvYQtcs.ttf +0 -0
  44. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Italic-VariableFont_wdth_wght.B4AZ-wl6.ttf +0 -0
  45. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Regular.DxJTClRG.ttf +0 -0
  46. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-SemiBold.D3TTYgdB.ttf +0 -0
  47. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-VariableFont_wdth_wght.BZBpG5Iz.ttf +0 -0
  48. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.OwPEPQZu.css +1 -0
  49. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.b653GmVf.css +1 -0
  50. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.T-zjSUd3.css +1 -0
  51. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/useFeatureFlags.CV-KWLNP.css +1 -0
  52. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/69_IOA4Y.js +1 -0
  53. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B2FVR0s0.js +1 -0
  54. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B90CZVMX.js +1 -0
  55. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B9zumHo5.js +1 -0
  56. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BJXwVxaE.js +1 -0
  57. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bsi3UGy5.js +1 -0
  58. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bu7uvVrG.js +1 -0
  59. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bx1xMsFy.js +1 -0
  60. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BylOuP6i.js +1 -0
  61. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C8I8rFJQ.js +1 -0
  62. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CDnpyLsT.js +1 -0
  63. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWj6FrbW.js +1 -0
  64. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CYgJF_JY.js +1 -0
  65. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CcaPhhk3.js +1 -0
  66. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CvOmgdoc.js +93 -0
  67. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CxtLVaYz.js +3 -0
  68. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D5-A_Ffd.js +4 -0
  69. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6RI2Zrd.js +1 -0
  70. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6su9Aln.js +1 -0
  71. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D98V7j6A.js +1 -0
  72. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIRAtgl0.js +1 -0
  73. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIeogL5L.js +1 -0
  74. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DOlTMNyt.js +1 -0
  75. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DjUWrjOv.js +1 -0
  76. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DjfY96ND.js +1 -0
  77. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/H7C68rOM.js +1 -0
  78. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/O-EABkf9.js +1 -0
  79. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/XO7A28GO.js +1 -0
  80. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/hQVEETDE.js +1 -0
  81. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/l7KrR96u.js +1 -0
  82. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/nAHhluT7.js +1 -0
  83. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/r64xT6ao.js +1 -0
  84. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/vC4nQVEB.js +1 -0
  85. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/x9G_hzyY.js +1 -0
  86. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.CjnvpsmS.js +2 -0
  87. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.0o1H7wM9.js +1 -0
  88. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.XRq_TUwu.js +1 -0
  89. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1.B4rNYwVp.js +1 -0
  90. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.DfBwOEhN.js +1 -0
  91. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/11.CWG1ehzT.js +1 -0
  92. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.CwF2_8mP.js +1 -0
  93. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.CS4muRY-.js +6 -0
  94. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/3.CWHpKonm.js +1 -0
  95. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/4.OUWOLQeV.js +1 -0
  96. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.Dm6t9F5W.js +1 -0
  97. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/6.Bw5ck4gK.js +1 -0
  98. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.CF0EDTR6.js +1 -0
  99. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.Cw30LEcV.js +1 -0
  100. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.CPu3CiBc.js +1 -0
  101. lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -0
  102. lightly_studio/dist_lightly_studio_view_app/apple-touch-icon-precomposed.png +0 -0
  103. lightly_studio/dist_lightly_studio_view_app/apple-touch-icon.png +0 -0
  104. lightly_studio/dist_lightly_studio_view_app/favicon.png +0 -0
  105. lightly_studio/dist_lightly_studio_view_app/index.html +44 -0
  106. lightly_studio/examples/example.py +23 -0
  107. lightly_studio/examples/example_metadata.py +338 -0
  108. lightly_studio/examples/example_selection.py +39 -0
  109. lightly_studio/examples/example_split_work.py +67 -0
  110. lightly_studio/examples/example_v2.py +21 -0
  111. lightly_studio/export_schema.py +18 -0
  112. lightly_studio/few_shot_classifier/__init__.py +0 -0
  113. lightly_studio/few_shot_classifier/classifier.py +80 -0
  114. lightly_studio/few_shot_classifier/classifier_manager.py +663 -0
  115. lightly_studio/few_shot_classifier/random_forest_classifier.py +489 -0
  116. lightly_studio/metadata/complex_metadata.py +47 -0
  117. lightly_studio/metadata/gps_coordinate.py +41 -0
  118. lightly_studio/metadata/metadata_protocol.py +17 -0
  119. lightly_studio/metrics/__init__.py +0 -0
  120. lightly_studio/metrics/detection/__init__.py +0 -0
  121. lightly_studio/metrics/detection/map.py +268 -0
  122. lightly_studio/models/__init__.py +1 -0
  123. lightly_studio/models/annotation/__init__.py +0 -0
  124. lightly_studio/models/annotation/annotation_base.py +171 -0
  125. lightly_studio/models/annotation/instance_segmentation.py +56 -0
  126. lightly_studio/models/annotation/links.py +17 -0
  127. lightly_studio/models/annotation/object_detection.py +47 -0
  128. lightly_studio/models/annotation/semantic_segmentation.py +44 -0
  129. lightly_studio/models/annotation_label.py +47 -0
  130. lightly_studio/models/annotation_task.py +28 -0
  131. lightly_studio/models/classifier.py +20 -0
  132. lightly_studio/models/dataset.py +84 -0
  133. lightly_studio/models/embedding_model.py +30 -0
  134. lightly_studio/models/metadata.py +208 -0
  135. lightly_studio/models/sample.py +180 -0
  136. lightly_studio/models/sample_embedding.py +37 -0
  137. lightly_studio/models/settings.py +60 -0
  138. lightly_studio/models/tag.py +96 -0
  139. lightly_studio/py.typed +0 -0
  140. lightly_studio/resolvers/__init__.py +7 -0
  141. lightly_studio/resolvers/annotation_label_resolver/__init__.py +21 -0
  142. lightly_studio/resolvers/annotation_label_resolver/create.py +27 -0
  143. lightly_studio/resolvers/annotation_label_resolver/delete.py +28 -0
  144. lightly_studio/resolvers/annotation_label_resolver/get_all.py +22 -0
  145. lightly_studio/resolvers/annotation_label_resolver/get_by_id.py +24 -0
  146. lightly_studio/resolvers/annotation_label_resolver/get_by_ids.py +25 -0
  147. lightly_studio/resolvers/annotation_label_resolver/get_by_label_name.py +24 -0
  148. lightly_studio/resolvers/annotation_label_resolver/names_by_ids.py +25 -0
  149. lightly_studio/resolvers/annotation_label_resolver/update.py +38 -0
  150. lightly_studio/resolvers/annotation_resolver/__init__.py +33 -0
  151. lightly_studio/resolvers/annotation_resolver/count_annotations_by_dataset.py +120 -0
  152. lightly_studio/resolvers/annotation_resolver/create.py +19 -0
  153. lightly_studio/resolvers/annotation_resolver/create_many.py +96 -0
  154. lightly_studio/resolvers/annotation_resolver/delete_annotation.py +45 -0
  155. lightly_studio/resolvers/annotation_resolver/delete_annotations.py +56 -0
  156. lightly_studio/resolvers/annotation_resolver/get_all.py +74 -0
  157. lightly_studio/resolvers/annotation_resolver/get_by_id.py +18 -0
  158. lightly_studio/resolvers/annotation_resolver/update_annotation_label.py +144 -0
  159. lightly_studio/resolvers/annotation_resolver/update_bounding_box.py +68 -0
  160. lightly_studio/resolvers/annotation_task_resolver.py +31 -0
  161. lightly_studio/resolvers/annotations/__init__.py +1 -0
  162. lightly_studio/resolvers/annotations/annotations_filter.py +89 -0
  163. lightly_studio/resolvers/dataset_resolver.py +278 -0
  164. lightly_studio/resolvers/embedding_model_resolver.py +100 -0
  165. lightly_studio/resolvers/metadata_resolver/__init__.py +15 -0
  166. lightly_studio/resolvers/metadata_resolver/metadata_filter.py +163 -0
  167. lightly_studio/resolvers/metadata_resolver/sample/__init__.py +21 -0
  168. lightly_studio/resolvers/metadata_resolver/sample/bulk_set_metadata.py +48 -0
  169. lightly_studio/resolvers/metadata_resolver/sample/get_by_sample_id.py +24 -0
  170. lightly_studio/resolvers/metadata_resolver/sample/get_metadata_info.py +104 -0
  171. lightly_studio/resolvers/metadata_resolver/sample/get_value_for_sample.py +27 -0
  172. lightly_studio/resolvers/metadata_resolver/sample/set_value_for_sample.py +53 -0
  173. lightly_studio/resolvers/sample_embedding_resolver.py +86 -0
  174. lightly_studio/resolvers/sample_resolver.py +249 -0
  175. lightly_studio/resolvers/samples_filter.py +81 -0
  176. lightly_studio/resolvers/settings_resolver.py +58 -0
  177. lightly_studio/resolvers/tag_resolver.py +276 -0
  178. lightly_studio/selection/README.md +6 -0
  179. lightly_studio/selection/mundig.py +105 -0
  180. lightly_studio/selection/select.py +96 -0
  181. lightly_studio/selection/select_via_db.py +93 -0
  182. lightly_studio/selection/selection_config.py +31 -0
  183. lightly_studio/services/annotations_service/__init__.py +21 -0
  184. lightly_studio/services/annotations_service/get_annotation_by_id.py +31 -0
  185. lightly_studio/services/annotations_service/update_annotation.py +65 -0
  186. lightly_studio/services/annotations_service/update_annotation_label.py +48 -0
  187. lightly_studio/services/annotations_service/update_annotations.py +29 -0
  188. lightly_studio/setup_logging.py +19 -0
  189. lightly_studio/type_definitions.py +19 -0
  190. lightly_studio/vendor/ACKNOWLEDGEMENTS +422 -0
  191. lightly_studio/vendor/LICENSE +31 -0
  192. lightly_studio/vendor/LICENSE_weights_data +50 -0
  193. lightly_studio/vendor/README.md +5 -0
  194. lightly_studio/vendor/__init__.py +1 -0
  195. lightly_studio/vendor/mobileclip/__init__.py +96 -0
  196. lightly_studio/vendor/mobileclip/clip.py +77 -0
  197. lightly_studio/vendor/mobileclip/configs/mobileclip_b.json +18 -0
  198. lightly_studio/vendor/mobileclip/configs/mobileclip_s0.json +18 -0
  199. lightly_studio/vendor/mobileclip/configs/mobileclip_s1.json +18 -0
  200. lightly_studio/vendor/mobileclip/configs/mobileclip_s2.json +18 -0
  201. lightly_studio/vendor/mobileclip/image_encoder.py +67 -0
  202. lightly_studio/vendor/mobileclip/logger.py +154 -0
  203. lightly_studio/vendor/mobileclip/models/__init__.py +10 -0
  204. lightly_studio/vendor/mobileclip/models/mci.py +933 -0
  205. lightly_studio/vendor/mobileclip/models/vit.py +433 -0
  206. lightly_studio/vendor/mobileclip/modules/__init__.py +4 -0
  207. lightly_studio/vendor/mobileclip/modules/common/__init__.py +4 -0
  208. lightly_studio/vendor/mobileclip/modules/common/mobileone.py +341 -0
  209. lightly_studio/vendor/mobileclip/modules/common/transformer.py +451 -0
  210. lightly_studio/vendor/mobileclip/modules/image/__init__.py +4 -0
  211. lightly_studio/vendor/mobileclip/modules/image/image_projection.py +113 -0
  212. lightly_studio/vendor/mobileclip/modules/image/replknet.py +188 -0
  213. lightly_studio/vendor/mobileclip/modules/text/__init__.py +4 -0
  214. lightly_studio/vendor/mobileclip/modules/text/repmixer.py +281 -0
  215. lightly_studio/vendor/mobileclip/modules/text/tokenizer.py +38 -0
  216. lightly_studio/vendor/mobileclip/text_encoder.py +245 -0
  217. lightly_studio-0.3.1.dist-info/METADATA +520 -0
  218. lightly_studio-0.3.1.dist-info/RECORD +219 -0
  219. lightly_studio-0.3.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,45 @@
1
+ """This module contains the API routes for user settings."""
2
+
3
+ from fastapi import APIRouter, Depends
4
+ from sqlmodel import Session
5
+ from typing_extensions import Annotated
6
+
7
+ from lightly_studio.api.db import get_session
8
+ from lightly_studio.models.settings import SettingView
9
+ from lightly_studio.resolvers import settings_resolver
10
+
11
+ settings_router = APIRouter(tags=["settings"])
12
+
13
+ SessionDep = Annotated[Session, Depends(get_session)]
14
+
15
+
16
+ @settings_router.get("/settings")
17
+ def get_settings(
18
+ session: SessionDep,
19
+ ) -> SettingView:
20
+ """Get the current settings.
21
+
22
+ Args:
23
+ session: Database session.
24
+
25
+ Returns:
26
+ The current settings.
27
+ """
28
+ return settings_resolver.get_settings(session=session)
29
+
30
+
31
+ @settings_router.post("/settings")
32
+ def set_settings(
33
+ settings: SettingView,
34
+ session: SessionDep,
35
+ ) -> SettingView:
36
+ """Update user settings.
37
+
38
+ Args:
39
+ settings: New settings to apply.
40
+ session: Database session.
41
+
42
+ Returns:
43
+ Updated settings.
44
+ """
45
+ return settings_resolver.set_settings(session=session, settings=settings)
@@ -0,0 +1,19 @@
1
+ """HTTP status codes for use in tests and responses."""
2
+
3
+ HTTP_STATUS_OK = 200
4
+ HTTP_STATUS_CREATED = 201
5
+ HTTP_STATUS_ACCEPTED = 202
6
+ HTTP_STATUS_NO_CONTENT = 204
7
+
8
+ HTTP_STATUS_BAD_REQUEST = 400
9
+ HTTP_STATUS_UNAUTHORIZED = 401
10
+ HTTP_STATUS_FORBIDDEN = 403
11
+ HTTP_STATUS_NOT_FOUND = 404
12
+ HTTP_STATUS_CONFLICT = 409
13
+ HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415
14
+ HTTP_STATUS_UNPRECESSABLE_ENTITY = 422
15
+
16
+ HTTP_STATUS_INTERNAL_SERVER_ERROR = 500
17
+ HTTP_STATUS_NOT_IMPLEMENTED = 501
18
+ HTTP_STATUS_BAD_GATEWAY = 502
19
+ HTTP_STATUS_SERVICE_UNAVAILABLE = 503
@@ -0,0 +1,48 @@
1
+ """This module contains the API routes for managing text embedding."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List
6
+ from uuid import UUID
7
+
8
+ from fastapi import APIRouter, Depends, HTTPException, Query
9
+ from typing_extensions import Annotated
10
+
11
+ from lightly_studio.api.routes.api.status import (
12
+ HTTP_STATUS_INTERNAL_SERVER_ERROR,
13
+ )
14
+ from lightly_studio.dataset.embedding_manager import (
15
+ EmbeddingManager,
16
+ EmbeddingManagerProvider,
17
+ TextEmbedQuery,
18
+ )
19
+
20
+ text_embedding_router = APIRouter()
21
+ # Define a type alias for the EmbeddingManager dependency
22
+ EmbeddingManagerDep = Annotated[
23
+ EmbeddingManager,
24
+ Depends(lambda: EmbeddingManagerProvider.get_embedding_manager()),
25
+ ]
26
+
27
+
28
+ @text_embedding_router.get("/text_embedding/embed_text", response_model=List[float])
29
+ def embed_text(
30
+ embedding_manager: EmbeddingManagerDep,
31
+ query_text: str = Query(..., description="The text to embed."),
32
+ embedding_model_id: Annotated[
33
+ UUID | None,
34
+ Query(..., description="The ID of the embedding model to use."),
35
+ ] = None,
36
+ ) -> list[float]:
37
+ """Retrieve embeddings for the input text."""
38
+ try:
39
+ text_embeddings = embedding_manager.embed_text(
40
+ TextEmbedQuery(query_text, embedding_model_id)
41
+ )
42
+ except ValueError as exc:
43
+ raise HTTPException(
44
+ status_code=HTTP_STATUS_INTERNAL_SERVER_ERROR,
45
+ detail=f"{exc}",
46
+ ) from None
47
+
48
+ return text_embeddings
@@ -0,0 +1,17 @@
1
+ """General LightlyStudio API models."""
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class Paginated(BaseModel):
7
+ """Paginated query parameters."""
8
+
9
+ offset: int = Field(0, ge=0, description="Offset for pagination")
10
+ limit: int = Field(100, gt=0, le=100, description="Limit for pagination")
11
+
12
+
13
+ class PaginatedWithCursor(BaseModel):
14
+ """Paginated query parameters."""
15
+
16
+ offset: int = Field(0, ge=0, description="Offset for pagination", alias="cursor")
17
+ limit: int = Field(100, gt=0, le=100, description="Limit for pagination")
@@ -0,0 +1,13 @@
1
+ """This module contains the API routes for managing datasets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import APIRouter
6
+
7
+ health_router = APIRouter()
8
+
9
+
10
+ @health_router.get("/healthz", include_in_schema=False)
11
+ def health_check() -> dict[str, str]:
12
+ """Health check endpoint to verify the service is running."""
13
+ return {"status": "healthy"}
@@ -0,0 +1,104 @@
1
+ """Image serving endpoint that supports local files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from collections.abc import Generator
7
+
8
+ from fastapi import APIRouter, Depends, HTTPException
9
+ from fastapi.responses import StreamingResponse
10
+ from sqlmodel import Session
11
+
12
+ from lightly_studio.api import db
13
+ from lightly_studio.api.routes.api import status
14
+ from lightly_studio.models import sample
15
+
16
+ app_router = APIRouter()
17
+
18
+
19
+ @app_router.get("/sample/{sample_id}")
20
+ async def serve_image_by_sample_id(
21
+ sample_id: str,
22
+ session: Session = Depends(db.get_session), # noqa: B008
23
+ ) -> StreamingResponse:
24
+ """Serve an image by sample ID.
25
+
26
+ Args:
27
+ sample_id: The ID of the sample.
28
+ session: Database session.
29
+
30
+ Returns:
31
+ StreamingResponse with the image data.
32
+
33
+ Raises:
34
+ HTTPException: If the sample is not found or the file is not accessible.
35
+ """
36
+ # Retrieve the sample from the database.
37
+ sample_record = session.get(sample.SampleTable, sample_id)
38
+ if not sample_record:
39
+ raise HTTPException(
40
+ status_code=status.HTTP_STATUS_NOT_FOUND,
41
+ detail=f"Sample not found: {sample_id}",
42
+ )
43
+
44
+ file_path = sample_record.file_path_abs
45
+
46
+ try:
47
+ # Open the file.
48
+ with open(file_path, "rb") as file:
49
+ content = file.read()
50
+
51
+ # Determine content type based on file extension.
52
+ content_type = _get_content_type(file_path)
53
+
54
+ # Create a streaming response.
55
+ def generate() -> Generator[bytes, None, None]:
56
+ yield content
57
+
58
+ return StreamingResponse(
59
+ generate(),
60
+ media_type=content_type,
61
+ headers={
62
+ # Cache for 1 hour
63
+ "Cache-Control": "public, max-age=3600",
64
+ "Content-Length": str(len(content)),
65
+ },
66
+ )
67
+
68
+ except FileNotFoundError as exc:
69
+ raise HTTPException(
70
+ status_code=status.HTTP_STATUS_NOT_FOUND,
71
+ detail=f"File not found: {file_path}",
72
+ ) from exc
73
+ except OSError as exc:
74
+ raise HTTPException(
75
+ status_code=status.HTTP_STATUS_NOT_FOUND,
76
+ detail=f"Error accessing file {file_path}: {exc.strerror}",
77
+ ) from exc
78
+
79
+
80
+ def _get_content_type(file_path: str) -> str:
81
+ """Get the appropriate content type for a file based on its extension.
82
+
83
+ Args:
84
+ file_path: Path to the file.
85
+
86
+ Returns:
87
+ MIME type string.
88
+ """
89
+ ext = os.path.splitext(file_path)[1].lower()
90
+
91
+ content_types = {
92
+ ".png": "image/png",
93
+ ".jpg": "image/jpeg",
94
+ ".jpeg": "image/jpeg",
95
+ ".gif": "image/gif",
96
+ ".webp": "image/webp",
97
+ ".bmp": "image/bmp",
98
+ ".tiff": "image/tiff",
99
+ ".mov": "video/quicktime",
100
+ ".mp4": "video/mp4",
101
+ ".avi": "video/x-msvideo",
102
+ }
103
+
104
+ return content_types.get(ext, "application/octet-stream")
@@ -0,0 +1,51 @@
1
+ """This module contains the API routes for managing datasets."""
2
+
3
+ from pathlib import Path
4
+
5
+ from fastapi import APIRouter, HTTPException
6
+ from fastapi.responses import FileResponse
7
+
8
+ from .api.status import HTTP_STATUS_NOT_FOUND
9
+
10
+ app_router = APIRouter()
11
+
12
+ # Get the current project root directory
13
+ project_root = Path(__file__).resolve().parent.parent.parent
14
+
15
+ webapp_dir = project_root / "dist_lightly_studio_view_app"
16
+
17
+ # Check if the webapp directory exists and raise an error if it doesn't
18
+ if not webapp_dir.exists():
19
+ raise RuntimeError(f"Directory '{webapp_dir}' does not exist in '{project_root}'")
20
+
21
+ # Ensure the path is absolute
22
+ webapp_dir = webapp_dir.resolve()
23
+
24
+ # ensure the webapp index.html file exists
25
+ index_file = webapp_dir / "index.html"
26
+ if not index_file.exists():
27
+ raise RuntimeError("No index file. Did you forget to build the webapp?")
28
+
29
+
30
+ @app_router.get("/{path:path}", include_in_schema=False)
31
+ async def serve_static_webapp_files_or_default_index_file(
32
+ path: str,
33
+ ) -> FileResponse:
34
+ """Serve static files of webapp or serve the index.html file.
35
+
36
+ Try to serve static files with file extensions or return 404.
37
+ If no file extension, return the main webapp index.html.
38
+ """
39
+ file_path = webapp_dir / path
40
+
41
+ # if file has an extension, try to return the file
42
+ if file_path.suffix:
43
+ if not file_path.exists() or not file_path.is_file():
44
+ raise HTTPException(
45
+ status_code=HTTP_STATUS_NOT_FOUND,
46
+ detail=f"File '{path}' not found",
47
+ )
48
+ return FileResponse(file_path)
49
+
50
+ # if file has no extension, return the index.html file regardless of path
51
+ return FileResponse(index_file)
@@ -0,0 +1,82 @@
1
+ """This module contains the Server class for running the API using Uvicorn."""
2
+
3
+ import random
4
+ import socket
5
+
6
+ import uvicorn
7
+
8
+ from lightly_studio.api.app import app
9
+ from lightly_studio.dataset import env
10
+
11
+
12
+ class Server:
13
+ """This class represents a server for running the API using Uvicorn."""
14
+
15
+ port: int
16
+ host: str
17
+
18
+ def __init__(self, host: str, port: int) -> None:
19
+ """Initialize the Server with host and port.
20
+
21
+ Args:
22
+ host (str): The hostname to bind the server to.
23
+ port (int): The port number to run the server on.
24
+ """
25
+ self.host = host
26
+ self.port = _get_available_port(host=host, preferred_port=port)
27
+ if port != self.port:
28
+ env.LIGHTLY_STUDIO_PORT = self.port
29
+ env.APP_URL = f"{env.LIGHTLY_STUDIO_PROTOCOL}://{env.LIGHTLY_STUDIO_HOST}:{env.LIGHTLY_STUDIO_PORT}"
30
+
31
+ def start(self) -> None:
32
+ """Start the API server using Uvicorn."""
33
+ # start the app
34
+ uvicorn.run(app, host=self.host, port=self.port, http="h11")
35
+
36
+
37
+ def _get_available_port(host: str, preferred_port: int, max_tries: int = 50) -> int:
38
+ """Get an available port, if possible, otherwise a random one.
39
+
40
+ Args:
41
+ host: The hostname or IP address to bind to.
42
+ preferred_port: The port to try first.
43
+ max_tries: Maximum number of random ports to try.
44
+
45
+ Raises:
46
+ RuntimeError if it cannot find an available port.
47
+
48
+ Returns:
49
+ An available port number.
50
+ """
51
+ if _is_port_available(host=host, port=preferred_port):
52
+ return preferred_port
53
+
54
+ # Try random ports in the range 1024-65535
55
+ for _ in range(max_tries):
56
+ port = random.randint(1024, 65535)
57
+ if _is_port_available(host=host, port=port):
58
+ return port
59
+
60
+ raise RuntimeError("Could not find an available port.")
61
+
62
+
63
+ def _is_port_available(host: str, port: int) -> bool:
64
+ # Determine address family based on host.
65
+ try:
66
+ socket.inet_pton(socket.AF_INET, host)
67
+ families = [socket.AF_INET]
68
+ except OSError:
69
+ try:
70
+ socket.inet_pton(socket.AF_INET6, host)
71
+ families = [socket.AF_INET6]
72
+ except OSError:
73
+ # Fallback for hostnames like 'localhost'
74
+ families = [socket.AF_INET, socket.AF_INET6]
75
+
76
+ for family in families:
77
+ with socket.socket(family, socket.SOCK_STREAM) as s:
78
+ try:
79
+ s.bind((host, port))
80
+ except OSError:
81
+ return False
82
+ return True
File without changes