lightly-studio 0.4.6__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.
Files changed (356) hide show
  1. lightly_studio/__init__.py +12 -0
  2. lightly_studio/api/__init__.py +0 -0
  3. lightly_studio/api/app.py +131 -0
  4. lightly_studio/api/cache.py +77 -0
  5. lightly_studio/api/db_tables.py +35 -0
  6. lightly_studio/api/features.py +5 -0
  7. lightly_studio/api/routes/api/annotation.py +305 -0
  8. lightly_studio/api/routes/api/annotation_label.py +87 -0
  9. lightly_studio/api/routes/api/annotations/__init__.py +7 -0
  10. lightly_studio/api/routes/api/annotations/create_annotation.py +52 -0
  11. lightly_studio/api/routes/api/caption.py +100 -0
  12. lightly_studio/api/routes/api/classifier.py +384 -0
  13. lightly_studio/api/routes/api/dataset.py +191 -0
  14. lightly_studio/api/routes/api/dataset_tag.py +266 -0
  15. lightly_studio/api/routes/api/embeddings2d.py +90 -0
  16. lightly_studio/api/routes/api/exceptions.py +114 -0
  17. lightly_studio/api/routes/api/export.py +114 -0
  18. lightly_studio/api/routes/api/features.py +17 -0
  19. lightly_studio/api/routes/api/frame.py +241 -0
  20. lightly_studio/api/routes/api/image.py +155 -0
  21. lightly_studio/api/routes/api/metadata.py +161 -0
  22. lightly_studio/api/routes/api/operator.py +75 -0
  23. lightly_studio/api/routes/api/sample.py +103 -0
  24. lightly_studio/api/routes/api/selection.py +87 -0
  25. lightly_studio/api/routes/api/settings.py +41 -0
  26. lightly_studio/api/routes/api/status.py +19 -0
  27. lightly_studio/api/routes/api/text_embedding.py +50 -0
  28. lightly_studio/api/routes/api/validators.py +17 -0
  29. lightly_studio/api/routes/api/video.py +133 -0
  30. lightly_studio/api/routes/healthz.py +13 -0
  31. lightly_studio/api/routes/images.py +104 -0
  32. lightly_studio/api/routes/video_frames_media.py +116 -0
  33. lightly_studio/api/routes/video_media.py +223 -0
  34. lightly_studio/api/routes/webapp.py +51 -0
  35. lightly_studio/api/server.py +94 -0
  36. lightly_studio/core/__init__.py +0 -0
  37. lightly_studio/core/add_samples.py +533 -0
  38. lightly_studio/core/add_videos.py +294 -0
  39. lightly_studio/core/dataset.py +780 -0
  40. lightly_studio/core/dataset_query/__init__.py +14 -0
  41. lightly_studio/core/dataset_query/boolean_expression.py +67 -0
  42. lightly_studio/core/dataset_query/dataset_query.py +317 -0
  43. lightly_studio/core/dataset_query/field.py +113 -0
  44. lightly_studio/core/dataset_query/field_expression.py +79 -0
  45. lightly_studio/core/dataset_query/match_expression.py +23 -0
  46. lightly_studio/core/dataset_query/order_by.py +79 -0
  47. lightly_studio/core/dataset_query/sample_field.py +37 -0
  48. lightly_studio/core/dataset_query/tags_expression.py +46 -0
  49. lightly_studio/core/image_sample.py +36 -0
  50. lightly_studio/core/loading_log.py +56 -0
  51. lightly_studio/core/sample.py +291 -0
  52. lightly_studio/core/start_gui.py +54 -0
  53. lightly_studio/core/video_sample.py +38 -0
  54. lightly_studio/dataset/__init__.py +0 -0
  55. lightly_studio/dataset/edge_embedding_generator.py +155 -0
  56. lightly_studio/dataset/embedding_generator.py +129 -0
  57. lightly_studio/dataset/embedding_manager.py +349 -0
  58. lightly_studio/dataset/env.py +20 -0
  59. lightly_studio/dataset/file_utils.py +49 -0
  60. lightly_studio/dataset/fsspec_lister.py +275 -0
  61. lightly_studio/dataset/mobileclip_embedding_generator.py +158 -0
  62. lightly_studio/dataset/perception_encoder_embedding_generator.py +260 -0
  63. lightly_studio/db_manager.py +166 -0
  64. lightly_studio/dist_lightly_studio_view_app/_app/env.js +1 -0
  65. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.GcXvs2l7.css +1 -0
  66. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/12.Dx6SXgAb.css +1 -0
  67. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/17.9X9_k6TP.css +1 -0
  68. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/18.BxiimdIO.css +1 -0
  69. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/2.CkOblLn7.css +1 -0
  70. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/ClassifierSamplesGrid.BJbCDlvs.css +1 -0
  71. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/LightlyLogo.BNjCIww-.png +0 -0
  72. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Bold.DGvYQtcs.ttf +0 -0
  73. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Italic-VariableFont_wdth_wght.B4AZ-wl6.ttf +0 -0
  74. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Medium.DVUZMR_6.ttf +0 -0
  75. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Regular.DxJTClRG.ttf +0 -0
  76. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-SemiBold.D3TTYgdB.ttf +0 -0
  77. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-VariableFont_wdth_wght.BZBpG5Iz.ttf +0 -0
  78. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.CefECEWA.css +1 -0
  79. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.D5tDcjY-.css +1 -0
  80. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_page.9X9_k6TP.css +1 -0
  81. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_page.BxiimdIO.css +1 -0
  82. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_page.Dx6SXgAb.css +1 -0
  83. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/transform._-1mPSEI.css +1 -0
  84. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/0dDyq72A.js +20 -0
  85. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/69_IOA4Y.js +1 -0
  86. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BK4An2kI.js +1 -0
  87. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BRmB-kJ9.js +1 -0
  88. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B_1cpokE.js +1 -0
  89. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BiqpDEr0.js +1 -0
  90. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BpLiSKgx.js +1 -0
  91. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BscxbINH.js +39 -0
  92. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C1FmrZbK.js +1 -0
  93. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C80h3dJx.js +1 -0
  94. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C8mfFM-u.js +2 -0
  95. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CGY1p9L4.js +517 -0
  96. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/COfLknXM.js +1 -0
  97. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWj6FrbW.js +1 -0
  98. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CYgJF_JY.js +1 -0
  99. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CmLg0ys7.js +1 -0
  100. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CvGjimpO.js +1 -0
  101. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D3RDXHoj.js +39 -0
  102. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D4y7iiT3.js +1 -0
  103. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D9SC3jBb.js +1 -0
  104. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DCuAdx1Q.js +20 -0
  105. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DDBy-_jD.js +1 -0
  106. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIeogL5L.js +1 -0
  107. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DL9a7v5o.js +1 -0
  108. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DSKECuqX.js +39 -0
  109. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D_FFv0Oe.js +1 -0
  110. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DiZ5o5vz.js +1 -0
  111. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DkbXUtyG.js +1 -0
  112. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DmK2hulV.js +1 -0
  113. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DqnHaLTj.js +1 -0
  114. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DtWZc_tl.js +1 -0
  115. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DuUalyFS.js +1 -0
  116. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DwIonDAZ.js +1 -0
  117. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Il-mSPmK.js +1 -0
  118. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/KNLP4aJU.js +1 -0
  119. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/KjYeVjkE.js +1 -0
  120. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/MErlcOXj.js +1 -0
  121. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/VRI4prUD.js +1 -0
  122. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/VYb2dkNs.js +1 -0
  123. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/VqWvU2yF.js +1 -0
  124. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/dHC3otuL.js +1 -0
  125. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/da7Oy_lO.js +1 -0
  126. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/eAy8rZzC.js +2 -0
  127. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/erjNR5MX.js +1 -0
  128. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/f1oG3eFE.js +1 -0
  129. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/rsLi1iKv.js +20 -0
  130. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/rwuuBP9f.js +1 -0
  131. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/xGHZQ1pe.js +3 -0
  132. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.DrTRUgT3.js +2 -0
  133. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.BK5EOJl2.js +1 -0
  134. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.CIvTuljF.js +4 -0
  135. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1.UBvSzxdA.js +1 -0
  136. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.CQ_tiLJa.js +1 -0
  137. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/11.KqkAcaxW.js +1 -0
  138. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.DoYsmxQc.js +1 -0
  139. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/13.571n2LZA.js +1 -0
  140. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/14.DGs689M-.js +1 -0
  141. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/15.CWG1ehzT.js +1 -0
  142. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/16.Dpq6jbSh.js +1 -0
  143. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/17.B5AZbHUU.js +1 -0
  144. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/18.CBga8cnq.js +1 -0
  145. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.D2HXgz-8.js +1090 -0
  146. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/3.f4HAg-y3.js +1 -0
  147. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/4.BKF4xuKQ.js +1 -0
  148. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.BAE0Pm_f.js +39 -0
  149. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/6.CouWWpzA.js +1 -0
  150. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.UBHT0ktp.js +1 -0
  151. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.FiYNElcc.js +1 -0
  152. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.B3-UaT23.js +1 -0
  153. lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/clustering.worker-DKqeLtG0.js +2 -0
  154. lightly_studio/dist_lightly_studio_view_app/_app/immutable/workers/search.worker-vNSty3B0.js +1 -0
  155. lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -0
  156. lightly_studio/dist_lightly_studio_view_app/apple-touch-icon-precomposed.png +0 -0
  157. lightly_studio/dist_lightly_studio_view_app/apple-touch-icon.png +0 -0
  158. lightly_studio/dist_lightly_studio_view_app/favicon.png +0 -0
  159. lightly_studio/dist_lightly_studio_view_app/index.html +45 -0
  160. lightly_studio/errors.py +5 -0
  161. lightly_studio/examples/example.py +25 -0
  162. lightly_studio/examples/example_coco.py +27 -0
  163. lightly_studio/examples/example_coco_caption.py +29 -0
  164. lightly_studio/examples/example_metadata.py +369 -0
  165. lightly_studio/examples/example_operators.py +111 -0
  166. lightly_studio/examples/example_selection.py +28 -0
  167. lightly_studio/examples/example_split_work.py +48 -0
  168. lightly_studio/examples/example_video.py +22 -0
  169. lightly_studio/examples/example_video_annotations.py +157 -0
  170. lightly_studio/examples/example_yolo.py +22 -0
  171. lightly_studio/export/coco_captions.py +69 -0
  172. lightly_studio/export/export_dataset.py +104 -0
  173. lightly_studio/export/lightly_studio_label_input.py +120 -0
  174. lightly_studio/export_schema.py +18 -0
  175. lightly_studio/export_version.py +57 -0
  176. lightly_studio/few_shot_classifier/__init__.py +0 -0
  177. lightly_studio/few_shot_classifier/classifier.py +80 -0
  178. lightly_studio/few_shot_classifier/classifier_manager.py +644 -0
  179. lightly_studio/few_shot_classifier/random_forest_classifier.py +495 -0
  180. lightly_studio/metadata/complex_metadata.py +47 -0
  181. lightly_studio/metadata/compute_similarity.py +84 -0
  182. lightly_studio/metadata/compute_typicality.py +67 -0
  183. lightly_studio/metadata/gps_coordinate.py +41 -0
  184. lightly_studio/metadata/metadata_protocol.py +17 -0
  185. lightly_studio/models/__init__.py +1 -0
  186. lightly_studio/models/annotation/__init__.py +0 -0
  187. lightly_studio/models/annotation/annotation_base.py +303 -0
  188. lightly_studio/models/annotation/instance_segmentation.py +56 -0
  189. lightly_studio/models/annotation/links.py +17 -0
  190. lightly_studio/models/annotation/object_detection.py +47 -0
  191. lightly_studio/models/annotation/semantic_segmentation.py +44 -0
  192. lightly_studio/models/annotation_label.py +47 -0
  193. lightly_studio/models/caption.py +49 -0
  194. lightly_studio/models/classifier.py +20 -0
  195. lightly_studio/models/dataset.py +70 -0
  196. lightly_studio/models/embedding_model.py +30 -0
  197. lightly_studio/models/image.py +96 -0
  198. lightly_studio/models/metadata.py +208 -0
  199. lightly_studio/models/range.py +17 -0
  200. lightly_studio/models/sample.py +154 -0
  201. lightly_studio/models/sample_embedding.py +36 -0
  202. lightly_studio/models/settings.py +69 -0
  203. lightly_studio/models/tag.py +96 -0
  204. lightly_studio/models/two_dim_embedding.py +16 -0
  205. lightly_studio/models/video.py +161 -0
  206. lightly_studio/plugins/__init__.py +0 -0
  207. lightly_studio/plugins/base_operator.py +60 -0
  208. lightly_studio/plugins/operator_registry.py +47 -0
  209. lightly_studio/plugins/parameter.py +70 -0
  210. lightly_studio/py.typed +0 -0
  211. lightly_studio/resolvers/__init__.py +0 -0
  212. lightly_studio/resolvers/annotation_label_resolver/__init__.py +22 -0
  213. lightly_studio/resolvers/annotation_label_resolver/create.py +27 -0
  214. lightly_studio/resolvers/annotation_label_resolver/delete.py +28 -0
  215. lightly_studio/resolvers/annotation_label_resolver/get_all.py +37 -0
  216. lightly_studio/resolvers/annotation_label_resolver/get_by_id.py +24 -0
  217. lightly_studio/resolvers/annotation_label_resolver/get_by_ids.py +25 -0
  218. lightly_studio/resolvers/annotation_label_resolver/get_by_label_name.py +24 -0
  219. lightly_studio/resolvers/annotation_label_resolver/names_by_ids.py +25 -0
  220. lightly_studio/resolvers/annotation_label_resolver/update.py +38 -0
  221. lightly_studio/resolvers/annotation_resolver/__init__.py +40 -0
  222. lightly_studio/resolvers/annotation_resolver/count_annotations_by_dataset.py +129 -0
  223. lightly_studio/resolvers/annotation_resolver/create_many.py +124 -0
  224. lightly_studio/resolvers/annotation_resolver/delete_annotation.py +87 -0
  225. lightly_studio/resolvers/annotation_resolver/delete_annotations.py +60 -0
  226. lightly_studio/resolvers/annotation_resolver/get_all.py +85 -0
  227. lightly_studio/resolvers/annotation_resolver/get_all_with_payload.py +179 -0
  228. lightly_studio/resolvers/annotation_resolver/get_by_id.py +34 -0
  229. lightly_studio/resolvers/annotation_resolver/get_by_id_with_payload.py +130 -0
  230. lightly_studio/resolvers/annotation_resolver/update_annotation_label.py +142 -0
  231. lightly_studio/resolvers/annotation_resolver/update_bounding_box.py +68 -0
  232. lightly_studio/resolvers/annotations/__init__.py +1 -0
  233. lightly_studio/resolvers/annotations/annotations_filter.py +88 -0
  234. lightly_studio/resolvers/caption_resolver.py +129 -0
  235. lightly_studio/resolvers/dataset_resolver/__init__.py +55 -0
  236. lightly_studio/resolvers/dataset_resolver/check_dataset_type.py +29 -0
  237. lightly_studio/resolvers/dataset_resolver/create.py +20 -0
  238. lightly_studio/resolvers/dataset_resolver/delete.py +20 -0
  239. lightly_studio/resolvers/dataset_resolver/export.py +267 -0
  240. lightly_studio/resolvers/dataset_resolver/get_all.py +19 -0
  241. lightly_studio/resolvers/dataset_resolver/get_by_id.py +16 -0
  242. lightly_studio/resolvers/dataset_resolver/get_by_name.py +12 -0
  243. lightly_studio/resolvers/dataset_resolver/get_dataset_details.py +27 -0
  244. lightly_studio/resolvers/dataset_resolver/get_hierarchy.py +31 -0
  245. lightly_studio/resolvers/dataset_resolver/get_or_create_child_dataset.py +58 -0
  246. lightly_studio/resolvers/dataset_resolver/get_parent_dataset_by_sample_id.py +27 -0
  247. lightly_studio/resolvers/dataset_resolver/get_parent_dataset_id.py +22 -0
  248. lightly_studio/resolvers/dataset_resolver/get_root_dataset.py +61 -0
  249. lightly_studio/resolvers/dataset_resolver/get_root_datasets_overview.py +41 -0
  250. lightly_studio/resolvers/dataset_resolver/update.py +25 -0
  251. lightly_studio/resolvers/embedding_model_resolver.py +120 -0
  252. lightly_studio/resolvers/image_filter.py +50 -0
  253. lightly_studio/resolvers/image_resolver/__init__.py +21 -0
  254. lightly_studio/resolvers/image_resolver/create_many.py +52 -0
  255. lightly_studio/resolvers/image_resolver/delete.py +20 -0
  256. lightly_studio/resolvers/image_resolver/filter_new_paths.py +23 -0
  257. lightly_studio/resolvers/image_resolver/get_all_by_dataset_id.py +117 -0
  258. lightly_studio/resolvers/image_resolver/get_by_id.py +14 -0
  259. lightly_studio/resolvers/image_resolver/get_dimension_bounds.py +75 -0
  260. lightly_studio/resolvers/image_resolver/get_many_by_id.py +22 -0
  261. lightly_studio/resolvers/image_resolver/get_samples_excluding.py +43 -0
  262. lightly_studio/resolvers/metadata_resolver/__init__.py +15 -0
  263. lightly_studio/resolvers/metadata_resolver/metadata_filter.py +163 -0
  264. lightly_studio/resolvers/metadata_resolver/sample/__init__.py +21 -0
  265. lightly_studio/resolvers/metadata_resolver/sample/bulk_update_metadata.py +46 -0
  266. lightly_studio/resolvers/metadata_resolver/sample/get_by_sample_id.py +24 -0
  267. lightly_studio/resolvers/metadata_resolver/sample/get_metadata_info.py +104 -0
  268. lightly_studio/resolvers/metadata_resolver/sample/get_value_for_sample.py +27 -0
  269. lightly_studio/resolvers/metadata_resolver/sample/set_value_for_sample.py +53 -0
  270. lightly_studio/resolvers/sample_embedding_resolver.py +132 -0
  271. lightly_studio/resolvers/sample_resolver/__init__.py +17 -0
  272. lightly_studio/resolvers/sample_resolver/count_by_dataset_id.py +16 -0
  273. lightly_studio/resolvers/sample_resolver/create.py +16 -0
  274. lightly_studio/resolvers/sample_resolver/create_many.py +25 -0
  275. lightly_studio/resolvers/sample_resolver/get_by_id.py +14 -0
  276. lightly_studio/resolvers/sample_resolver/get_filtered_samples.py +56 -0
  277. lightly_studio/resolvers/sample_resolver/get_many_by_id.py +22 -0
  278. lightly_studio/resolvers/sample_resolver/sample_filter.py +74 -0
  279. lightly_studio/resolvers/settings_resolver.py +62 -0
  280. lightly_studio/resolvers/tag_resolver.py +299 -0
  281. lightly_studio/resolvers/twodim_embedding_resolver.py +119 -0
  282. lightly_studio/resolvers/video_frame_resolver/__init__.py +23 -0
  283. lightly_studio/resolvers/video_frame_resolver/count_video_frames_annotations.py +83 -0
  284. lightly_studio/resolvers/video_frame_resolver/create_many.py +57 -0
  285. lightly_studio/resolvers/video_frame_resolver/get_all_by_dataset_id.py +63 -0
  286. lightly_studio/resolvers/video_frame_resolver/get_by_id.py +13 -0
  287. lightly_studio/resolvers/video_frame_resolver/get_table_fields_bounds.py +44 -0
  288. lightly_studio/resolvers/video_frame_resolver/video_frame_annotations_counter_filter.py +47 -0
  289. lightly_studio/resolvers/video_frame_resolver/video_frame_filter.py +57 -0
  290. lightly_studio/resolvers/video_resolver/__init__.py +27 -0
  291. lightly_studio/resolvers/video_resolver/count_video_frame_annotations_by_video_dataset.py +86 -0
  292. lightly_studio/resolvers/video_resolver/create_many.py +58 -0
  293. lightly_studio/resolvers/video_resolver/filter_new_paths.py +33 -0
  294. lightly_studio/resolvers/video_resolver/get_all_by_dataset_id.py +181 -0
  295. lightly_studio/resolvers/video_resolver/get_by_id.py +22 -0
  296. lightly_studio/resolvers/video_resolver/get_table_fields_bounds.py +72 -0
  297. lightly_studio/resolvers/video_resolver/get_view_by_id.py +52 -0
  298. lightly_studio/resolvers/video_resolver/video_count_annotations_filter.py +50 -0
  299. lightly_studio/resolvers/video_resolver/video_filter.py +98 -0
  300. lightly_studio/selection/__init__.py +1 -0
  301. lightly_studio/selection/mundig.py +143 -0
  302. lightly_studio/selection/select.py +203 -0
  303. lightly_studio/selection/select_via_db.py +273 -0
  304. lightly_studio/selection/selection_config.py +49 -0
  305. lightly_studio/services/annotations_service/__init__.py +33 -0
  306. lightly_studio/services/annotations_service/create_annotation.py +64 -0
  307. lightly_studio/services/annotations_service/delete_annotation.py +22 -0
  308. lightly_studio/services/annotations_service/get_annotation_by_id.py +31 -0
  309. lightly_studio/services/annotations_service/update_annotation.py +54 -0
  310. lightly_studio/services/annotations_service/update_annotation_bounding_box.py +36 -0
  311. lightly_studio/services/annotations_service/update_annotation_label.py +48 -0
  312. lightly_studio/services/annotations_service/update_annotations.py +29 -0
  313. lightly_studio/setup_logging.py +59 -0
  314. lightly_studio/type_definitions.py +31 -0
  315. lightly_studio/utils/__init__.py +3 -0
  316. lightly_studio/utils/download.py +94 -0
  317. lightly_studio/vendor/__init__.py +1 -0
  318. lightly_studio/vendor/mobileclip/ACKNOWLEDGEMENTS +422 -0
  319. lightly_studio/vendor/mobileclip/LICENSE +31 -0
  320. lightly_studio/vendor/mobileclip/LICENSE_weights_data +50 -0
  321. lightly_studio/vendor/mobileclip/README.md +5 -0
  322. lightly_studio/vendor/mobileclip/__init__.py +96 -0
  323. lightly_studio/vendor/mobileclip/clip.py +77 -0
  324. lightly_studio/vendor/mobileclip/configs/mobileclip_b.json +18 -0
  325. lightly_studio/vendor/mobileclip/configs/mobileclip_s0.json +18 -0
  326. lightly_studio/vendor/mobileclip/configs/mobileclip_s1.json +18 -0
  327. lightly_studio/vendor/mobileclip/configs/mobileclip_s2.json +18 -0
  328. lightly_studio/vendor/mobileclip/image_encoder.py +67 -0
  329. lightly_studio/vendor/mobileclip/logger.py +154 -0
  330. lightly_studio/vendor/mobileclip/models/__init__.py +10 -0
  331. lightly_studio/vendor/mobileclip/models/mci.py +933 -0
  332. lightly_studio/vendor/mobileclip/models/vit.py +433 -0
  333. lightly_studio/vendor/mobileclip/modules/__init__.py +4 -0
  334. lightly_studio/vendor/mobileclip/modules/common/__init__.py +4 -0
  335. lightly_studio/vendor/mobileclip/modules/common/mobileone.py +341 -0
  336. lightly_studio/vendor/mobileclip/modules/common/transformer.py +451 -0
  337. lightly_studio/vendor/mobileclip/modules/image/__init__.py +4 -0
  338. lightly_studio/vendor/mobileclip/modules/image/image_projection.py +113 -0
  339. lightly_studio/vendor/mobileclip/modules/image/replknet.py +188 -0
  340. lightly_studio/vendor/mobileclip/modules/text/__init__.py +4 -0
  341. lightly_studio/vendor/mobileclip/modules/text/repmixer.py +281 -0
  342. lightly_studio/vendor/mobileclip/modules/text/tokenizer.py +38 -0
  343. lightly_studio/vendor/mobileclip/text_encoder.py +245 -0
  344. lightly_studio/vendor/perception_encoder/LICENSE.PE +201 -0
  345. lightly_studio/vendor/perception_encoder/README.md +11 -0
  346. lightly_studio/vendor/perception_encoder/vision_encoder/__init__.py +0 -0
  347. lightly_studio/vendor/perception_encoder/vision_encoder/bpe_simple_vocab_16e6.txt.gz +0 -0
  348. lightly_studio/vendor/perception_encoder/vision_encoder/config.py +205 -0
  349. lightly_studio/vendor/perception_encoder/vision_encoder/config_src.py +264 -0
  350. lightly_studio/vendor/perception_encoder/vision_encoder/pe.py +766 -0
  351. lightly_studio/vendor/perception_encoder/vision_encoder/rope.py +352 -0
  352. lightly_studio/vendor/perception_encoder/vision_encoder/tokenizer.py +347 -0
  353. lightly_studio/vendor/perception_encoder/vision_encoder/transforms.py +36 -0
  354. lightly_studio-0.4.6.dist-info/METADATA +88 -0
  355. lightly_studio-0.4.6.dist-info/RECORD +356 -0
  356. lightly_studio-0.4.6.dist-info/WHEEL +4 -0
@@ -0,0 +1,114 @@
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
+ @export_router.get("/export/captions")
65
+ def export_dataset_captions(
66
+ dataset: Annotated[
67
+ DatasetTable,
68
+ Path(title="Dataset Id"),
69
+ Depends(dataset_api.get_and_validate_dataset_id),
70
+ ],
71
+ session: SessionDep,
72
+ ) -> StreamingResponse:
73
+ """Export dataset captions in COCO format."""
74
+ # Query to export - all samples in the dataset.
75
+ dataset_query = DatasetQuery(dataset=dataset, session=session)
76
+
77
+ # Create the export in a temporary directory. We cannot use a context manager
78
+ # because the directory should be deleted only after the file has finished streaming.
79
+ temp_dir = TemporaryDirectory()
80
+ output_path = PathlibPath(temp_dir.name) / "coco_captions_export.json"
81
+
82
+ try:
83
+ export_dataset.to_coco_captions(
84
+ samples=dataset_query,
85
+ output_json=output_path,
86
+ )
87
+ except Exception:
88
+ temp_dir.cleanup()
89
+ # Reraise.
90
+ raise
91
+
92
+ return StreamingResponse(
93
+ content=_stream_export_file(
94
+ temp_dir=temp_dir,
95
+ file_path=output_path,
96
+ ),
97
+ media_type="application/json",
98
+ headers={
99
+ "Access-Control-Expose-Headers": "Content-Disposition",
100
+ "Content-Disposition": f"attachment; filename={output_path.name}",
101
+ },
102
+ )
103
+
104
+
105
+ def _stream_export_file(
106
+ temp_dir: TemporaryDirectory[str],
107
+ file_path: PathlibPath,
108
+ ) -> Generator[bytes, None, None]:
109
+ """Stream the export file and clean up the temporary directory afterwards."""
110
+ try:
111
+ with file_path.open("rb") as file:
112
+ yield from file
113
+ finally:
114
+ temp_dir.cleanup()
@@ -0,0 +1,17 @@
1
+ """This module contains the API routes for active features."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fastapi import APIRouter
6
+
7
+ from lightly_studio.api.features import lightly_studio_active_features
8
+
9
+ __all__ = ["features_router", "lightly_studio_active_features"]
10
+
11
+ features_router = APIRouter()
12
+
13
+
14
+ @features_router.get("/features")
15
+ def get_features() -> list[str]:
16
+ """Get the list of active features in the LightlyStudio app."""
17
+ return lightly_studio_active_features
@@ -0,0 +1,241 @@
1
+ """API routes for dataset frames."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import List
6
+ from uuid import UUID
7
+
8
+ from fastapi import APIRouter, Depends, Path
9
+ from pydantic import BaseModel, Field
10
+ from typing_extensions import Annotated
11
+
12
+ from lightly_studio.api.routes.api.validators import Paginated, PaginatedWithCursor
13
+ from lightly_studio.db_manager import SessionDep
14
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable, AnnotationView
15
+ from lightly_studio.models.annotation.instance_segmentation import (
16
+ InstanceSegmentationAnnotationView,
17
+ )
18
+ from lightly_studio.models.annotation.object_detection import ObjectDetectionAnnotationView
19
+ from lightly_studio.models.annotation.semantic_segmentation import (
20
+ SemanticSegmentationAnnotationView,
21
+ )
22
+ from lightly_studio.models.sample import SampleTable, SampleView
23
+ from lightly_studio.models.video import (
24
+ FrameView,
25
+ VideoFrameFieldsBoundsView,
26
+ VideoFrameTable,
27
+ VideoFrameView,
28
+ VideoFrameViewsWithCount,
29
+ VideoTable,
30
+ VideoView,
31
+ )
32
+ from lightly_studio.resolvers import video_frame_resolver
33
+ from lightly_studio.resolvers.video_frame_resolver.video_frame_annotations_counter_filter import (
34
+ VideoFrameAnnotationsCounterFilter,
35
+ )
36
+ from lightly_studio.resolvers.video_frame_resolver.video_frame_filter import (
37
+ VideoFrameFilter,
38
+ )
39
+ from lightly_studio.resolvers.video_resolver.count_video_frame_annotations_by_video_dataset import (
40
+ CountAnnotationsView,
41
+ )
42
+
43
+ frame_router = APIRouter(prefix="/datasets/{video_frame_dataset_id}/frame", tags=["frame"])
44
+
45
+
46
+ class ReadVideoFramesRequest(BaseModel):
47
+ """Request body for reading videos."""
48
+
49
+ filter: VideoFrameFilter | None = Field(None, description="Filter parameters for video frames")
50
+
51
+
52
+ class ReadCountVideoFramesAnnotationsRequest(BaseModel):
53
+ """Request body for reading video frames annotations counter."""
54
+
55
+ filter: VideoFrameAnnotationsCounterFilter | None = Field(
56
+ None, description="Filter parameters for video frames annotations counter"
57
+ )
58
+
59
+
60
+ @frame_router.post("/", response_model=VideoFrameViewsWithCount)
61
+ def get_all_frames(
62
+ video_frame_dataset_id: Annotated[UUID, Path(title="Video dataset Id")],
63
+ session: SessionDep,
64
+ pagination: Annotated[PaginatedWithCursor, Depends()],
65
+ body: ReadVideoFramesRequest,
66
+ ) -> VideoFrameViewsWithCount:
67
+ """Retrieve a list of all frames for a given dataset ID with pagination.
68
+
69
+ Args:
70
+ session: The database session.
71
+ video_frame_dataset_id: The ID of the dataset to retrieve frames for.
72
+ pagination: Pagination parameters including offset and limit.
73
+ body: The body containing the filters
74
+ Returns:
75
+ A list of frames along with the total count.
76
+ """
77
+ result = video_frame_resolver.get_all_by_dataset_id(
78
+ session=session,
79
+ dataset_id=video_frame_dataset_id,
80
+ pagination=Paginated(offset=pagination.offset, limit=pagination.limit),
81
+ video_frame_filter=body.filter,
82
+ )
83
+
84
+ return VideoFrameViewsWithCount(
85
+ samples=[_build_video_frame_view(vf=frame) for frame in result.samples],
86
+ total_count=result.total_count,
87
+ next_cursor=result.next_cursor,
88
+ )
89
+
90
+
91
+ @frame_router.get("/bounds")
92
+ def get_video_frames_fields_bounds(
93
+ session: SessionDep,
94
+ video_frame_dataset_id: Annotated[UUID, Path(title="Dataset Id")],
95
+ ) -> VideoFrameFieldsBoundsView | None:
96
+ """Retrieve the video fields bounds for a given dataset ID.
97
+
98
+ Args:
99
+ session: The database session.
100
+ video_frame_dataset_id: The ID of the dataset to retrieve video frames bounds.
101
+ body: The body containg the filters.
102
+
103
+ Returns:
104
+ A video frame fields bounds object.
105
+ """
106
+ return video_frame_resolver.get_table_fields_bounds(
107
+ dataset_id=video_frame_dataset_id,
108
+ session=session,
109
+ )
110
+
111
+
112
+ @frame_router.get("/{sample_id}", response_model=VideoFrameView)
113
+ def get_frame_by_id(
114
+ session: SessionDep,
115
+ sample_id: Annotated[UUID, Path(title="Sample Id")],
116
+ ) -> VideoFrameView:
117
+ """Retrieve a frame by its sample ID within a given dataset.
118
+
119
+ Args:
120
+ session: The database session.
121
+ sample_id: The ID of the sample to retrieve.
122
+
123
+ Returns:
124
+ A frame corresponding to the given sample ID.
125
+ """
126
+ result = video_frame_resolver.get_by_id(session=session, sample_id=sample_id)
127
+
128
+ return _build_video_frame_view(result)
129
+
130
+
131
+ @frame_router.post("/annotations/count", response_model=List[CountAnnotationsView])
132
+ def count_video_frame_annotations(
133
+ session: SessionDep,
134
+ video_frame_dataset_id: Annotated[UUID, Path(title="Video dataset Id")],
135
+ body: ReadCountVideoFramesAnnotationsRequest,
136
+ ) -> list[CountAnnotationsView]:
137
+ """Retrieve a list of annotations along with total count and filtered count.
138
+
139
+ Args:
140
+ session: The database session.
141
+ video_frame_dataset_id: The ID of the dataset to retrieve videos for.
142
+ body: The body containing filters.
143
+
144
+ Returns:
145
+ A list of annotations and counters.
146
+ """
147
+ return video_frame_resolver.count_video_frames_annotations(
148
+ session=session,
149
+ dataset_id=video_frame_dataset_id,
150
+ filters=body.filter,
151
+ )
152
+
153
+
154
+ # TODO (Leonardo 11/25): These manual conversions are needed because
155
+ # of the circular import between Annotation and Sample.
156
+ def _build_annotation_view(a: AnnotationBaseTable) -> AnnotationView:
157
+ return AnnotationView(
158
+ parent_sample_id=a.parent_sample_id,
159
+ sample_id=a.sample_id,
160
+ annotation_type=a.annotation_type,
161
+ confidence=a.confidence,
162
+ created_at=a.created_at,
163
+ annotation_label=AnnotationView.AnnotationLabel(
164
+ annotation_label_name=a.annotation_label.annotation_label_name
165
+ ),
166
+ object_detection_details=(
167
+ ObjectDetectionAnnotationView(
168
+ x=a.object_detection_details.x,
169
+ y=a.object_detection_details.y,
170
+ width=a.object_detection_details.width,
171
+ height=a.object_detection_details.height,
172
+ )
173
+ if a.object_detection_details
174
+ else None
175
+ ),
176
+ instance_segmentation_details=(
177
+ InstanceSegmentationAnnotationView(
178
+ width=a.instance_segmentation_details.width,
179
+ height=a.instance_segmentation_details.height,
180
+ x=a.instance_segmentation_details.x,
181
+ y=a.instance_segmentation_details.y,
182
+ )
183
+ if a.instance_segmentation_details
184
+ else None
185
+ ),
186
+ semantic_segmentation_details=(
187
+ SemanticSegmentationAnnotationView(
188
+ segmentation_mask=a.semantic_segmentation_details.segmentation_mask,
189
+ )
190
+ if a.semantic_segmentation_details
191
+ else None
192
+ ),
193
+ tags=[AnnotationView.AnnotationViewTag(tag_id=t.tag_id, name=t.name) for t in a.tags],
194
+ sample=_build_sample_view(a.sample),
195
+ )
196
+
197
+
198
+ def _build_sample_view(sample: SampleTable) -> SampleView:
199
+ return SampleView(
200
+ dataset_id=sample.dataset_id,
201
+ sample_id=sample.sample_id,
202
+ created_at=sample.created_at,
203
+ updated_at=sample.updated_at,
204
+ tags=sample.tags,
205
+ metadata_dict=sample.metadata_dict,
206
+ captions=sample.captions,
207
+ annotations=[_build_annotation_view(a) for a in sample.annotations],
208
+ )
209
+
210
+
211
+ def _build_video_view(video: VideoTable) -> VideoView:
212
+ return VideoView(
213
+ width=video.width,
214
+ height=video.height,
215
+ duration_s=video.duration_s,
216
+ fps=video.fps,
217
+ file_name=video.file_name,
218
+ file_path_abs=video.file_path_abs,
219
+ sample_id=video.sample_id,
220
+ sample=_build_sample_view(video.sample),
221
+ )
222
+
223
+
224
+ def _build_video_frame_view(vf: VideoFrameTable) -> VideoFrameView:
225
+ return VideoFrameView(
226
+ frame_number=vf.frame_number,
227
+ frame_timestamp_s=vf.frame_timestamp_s,
228
+ sample_id=vf.sample_id,
229
+ video=_build_video_view(vf.video),
230
+ sample=_build_sample_view(vf.sample),
231
+ )
232
+
233
+
234
+ def build_frame_view(vf: VideoFrameTable) -> FrameView:
235
+ """Create a FrameView."""
236
+ return FrameView(
237
+ frame_number=vf.frame_number,
238
+ frame_timestamp_s=vf.frame_timestamp_s,
239
+ sample_id=vf.sample_id,
240
+ sample=_build_sample_view(vf.sample),
241
+ )
@@ -0,0 +1,155 @@
1
+ """This module contains the API routes for managing samples."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from fastapi import APIRouter, Depends, HTTPException, Path, Query
8
+ from pydantic import BaseModel, Field
9
+ from typing_extensions import Annotated
10
+
11
+ from lightly_studio.api.routes.api.dataset import get_and_validate_dataset_id
12
+ from lightly_studio.api.routes.api.status import (
13
+ HTTP_STATUS_NOT_FOUND,
14
+ )
15
+ from lightly_studio.api.routes.api.validators import Paginated
16
+ from lightly_studio.db_manager import SessionDep
17
+ from lightly_studio.models.dataset import DatasetTable
18
+ from lightly_studio.models.image import (
19
+ ImageView,
20
+ ImageViewsWithCount,
21
+ )
22
+ from lightly_studio.resolvers import (
23
+ image_resolver,
24
+ )
25
+ from lightly_studio.resolvers.image_filter import (
26
+ ImageFilter,
27
+ )
28
+
29
+ image_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["image"])
30
+
31
+
32
+ class ReadImagesRequest(BaseModel):
33
+ """Request body for reading samples with text embedding."""
34
+
35
+ filters: ImageFilter | None = Field(None, description="Filter parameters for samples")
36
+ text_embedding: list[float] | None = Field(None, description="Text embedding to search for")
37
+ sample_ids: list[UUID] | None = Field(None, description="The list of requested sample IDs")
38
+ pagination: Paginated | None = Field(
39
+ None, description="Pagination parameters for offset and limit"
40
+ )
41
+
42
+
43
+ @image_router.post("/images/list")
44
+ def read_images(
45
+ session: SessionDep,
46
+ dataset_id: Annotated[UUID, Path(title="Dataset Id")],
47
+ body: ReadImagesRequest,
48
+ ) -> ImageViewsWithCount:
49
+ """Retrieve a list of samples from the database with optional filtering.
50
+
51
+ Args:
52
+ session: The database session.
53
+ dataset_id: The ID of the dataset to filter samples by.
54
+ body: Optional request body containing text embedding.
55
+
56
+ Returns:
57
+ A list of filtered samples.
58
+ """
59
+ result = image_resolver.get_all_by_dataset_id(
60
+ session=session,
61
+ dataset_id=dataset_id,
62
+ pagination=body.pagination,
63
+ filters=body.filters,
64
+ text_embedding=body.text_embedding,
65
+ sample_ids=body.sample_ids,
66
+ )
67
+ # TODO(Michal, 10/2025): Add SampleView to ImageView and then use a response model
68
+ # instead of manual conversion.
69
+ return ImageViewsWithCount(
70
+ samples=[
71
+ ImageView(
72
+ file_name=image.file_name,
73
+ file_path_abs=image.file_path_abs,
74
+ sample_id=image.sample_id,
75
+ annotations=image.sample.annotations,
76
+ captions=image.sample.captions,
77
+ tags=[
78
+ ImageView.ImageViewTag(
79
+ tag_id=tag.tag_id,
80
+ name=tag.name,
81
+ kind=tag.kind,
82
+ created_at=tag.created_at,
83
+ updated_at=tag.updated_at,
84
+ )
85
+ for tag in image.sample.tags
86
+ ],
87
+ metadata_dict=image.sample.metadata_dict,
88
+ width=image.width,
89
+ height=image.height,
90
+ sample=image.sample,
91
+ )
92
+ for image in result.samples
93
+ ],
94
+ total_count=result.total_count,
95
+ next_cursor=result.next_cursor,
96
+ )
97
+
98
+
99
+ @image_router.get("/images/dimensions")
100
+ def get_image_dimensions(
101
+ session: SessionDep,
102
+ dataset: Annotated[
103
+ DatasetTable,
104
+ Path(title="Dataset Id"),
105
+ Depends(get_and_validate_dataset_id),
106
+ ],
107
+ annotation_label_ids: Annotated[list[UUID] | None, Query()] = None,
108
+ ) -> dict[str, int]:
109
+ """Get min and max dimensions of samples in a dataset."""
110
+ return image_resolver.get_dimension_bounds(
111
+ session=session,
112
+ dataset_id=dataset.dataset_id,
113
+ annotation_label_ids=annotation_label_ids,
114
+ )
115
+
116
+
117
+ @image_router.get("/images/{sample_id}")
118
+ def read_image(
119
+ session: SessionDep,
120
+ sample_id: Annotated[UUID, Path(title="Sample Id")],
121
+ ) -> ImageView:
122
+ """Retrieve a single sample from the database."""
123
+ image = image_resolver.get_by_id(session=session, sample_id=sample_id)
124
+ if not image:
125
+ raise HTTPException(status_code=HTTP_STATUS_NOT_FOUND, detail="Sample not found")
126
+ # TODO(Michal, 10/2025): Add SampleView to ImageView and then use a response model
127
+ # instead of manual conversion.
128
+ return ImageView(
129
+ file_name=image.file_name,
130
+ file_path_abs=image.file_path_abs,
131
+ sample_id=image.sample_id,
132
+ annotations=image.sample.annotations,
133
+ captions=image.sample.captions,
134
+ tags=[
135
+ ImageView.ImageViewTag(
136
+ tag_id=tag.tag_id,
137
+ name=tag.name,
138
+ kind=tag.kind,
139
+ created_at=tag.created_at,
140
+ updated_at=tag.updated_at,
141
+ )
142
+ for tag in image.sample.tags
143
+ ],
144
+ metadata_dict=image.sample.metadata_dict,
145
+ width=image.width,
146
+ height=image.height,
147
+ sample=image.sample,
148
+ )
149
+
150
+
151
+ class SampleAdjacentsParams(BaseModel):
152
+ """Parameters for getting adjacent samples."""
153
+
154
+ filters: ImageFilter | None = None
155
+ text_embedding: list[float] | None = None
@@ -0,0 +1,161 @@
1
+ """This module contains the API routes for managing datasets."""
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, Path
9
+ from pydantic import BaseModel, Field
10
+ from typing_extensions import Annotated
11
+
12
+ from lightly_studio.api.routes.api.dataset import get_and_validate_dataset_id
13
+ from lightly_studio.api.routes.api.status import HTTP_STATUS_NOT_FOUND
14
+ from lightly_studio.db_manager import SessionDep
15
+ from lightly_studio.errors import TagNotFoundError
16
+ from lightly_studio.metadata import compute_similarity, compute_typicality
17
+ from lightly_studio.models.dataset import DatasetTable
18
+ from lightly_studio.models.metadata import MetadataInfoView
19
+ from lightly_studio.resolvers import embedding_model_resolver
20
+ from lightly_studio.resolvers.metadata_resolver.sample.get_metadata_info import (
21
+ get_all_metadata_keys_and_schema,
22
+ )
23
+
24
+ metadata_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["metadata"])
25
+
26
+
27
+ @metadata_router.get("/metadata/info", response_model=List[MetadataInfoView])
28
+ def get_metadata_info(
29
+ session: SessionDep,
30
+ dataset_id: Annotated[UUID, Path(title="Dataset Id")],
31
+ ) -> list[MetadataInfoView]:
32
+ """Get all metadata keys and their schema for a dataset.
33
+
34
+ Args:
35
+ session: The database session.
36
+ dataset_id: The ID of the dataset.
37
+
38
+ Returns:
39
+ List of metadata info objects with name, type, and optionally min/max values
40
+ for numerical metadata types.
41
+ """
42
+ return get_all_metadata_keys_and_schema(session=session, dataset_id=dataset_id)
43
+
44
+
45
+ class ComputeTypicalityRequest(BaseModel):
46
+ """Request model for computing typicality metadata."""
47
+
48
+ embedding_model_name: str | None = Field(
49
+ default=None,
50
+ description="Embedding model name (uses default if not specified)",
51
+ )
52
+ metadata_name: str = Field(
53
+ default="typicality",
54
+ description="Metadata field name (defaults to 'typicality')",
55
+ )
56
+
57
+
58
+ @metadata_router.post(
59
+ "/metadata/typicality",
60
+ status_code=204,
61
+ response_model=None,
62
+ )
63
+ def compute_typicality_metadata(
64
+ session: SessionDep,
65
+ dataset: Annotated[
66
+ DatasetTable,
67
+ Depends(get_and_validate_dataset_id),
68
+ ],
69
+ request: ComputeTypicalityRequest,
70
+ ) -> None:
71
+ """Compute typicality metadata for a dataset.
72
+
73
+ Args:
74
+ session: The database session.
75
+ dataset: The dataset to compute typicality for.
76
+ request: Request parameters including optional embedding model name
77
+ and metadata field name.
78
+
79
+ Returns:
80
+ None (204 No Content on success).
81
+ """
82
+ embedding_model = embedding_model_resolver.get_by_name(
83
+ session=session,
84
+ dataset_id=dataset.dataset_id,
85
+ embedding_model_name=request.embedding_model_name,
86
+ )
87
+
88
+ compute_typicality.compute_typicality_metadata(
89
+ session=session,
90
+ dataset_id=dataset.dataset_id,
91
+ embedding_model_id=embedding_model.embedding_model_id,
92
+ metadata_name=request.metadata_name,
93
+ )
94
+
95
+
96
+ class ComputeSimilarityRequest(BaseModel):
97
+ """Request model for computing typicality metadata."""
98
+
99
+ embedding_model_name: str | None = Field(
100
+ default=None,
101
+ description="Embedding model name (uses default if not specified)",
102
+ )
103
+ metadata_name: str | None = Field(
104
+ default=None,
105
+ description="Metadata field name (defaults to None)",
106
+ )
107
+
108
+
109
+ @metadata_router.post(
110
+ "/metadata/similarity/{query_tag_id}",
111
+ response_model=str,
112
+ )
113
+ def compute_similarity_metadata(
114
+ session: SessionDep,
115
+ dataset: Annotated[
116
+ DatasetTable,
117
+ Depends(get_and_validate_dataset_id),
118
+ ],
119
+ query_tag_id: Annotated[UUID, Path(title="Query Tag ID")],
120
+ request: ComputeSimilarityRequest,
121
+ ) -> str:
122
+ """Compute similarity metadata for a dataset.
123
+
124
+ Args:
125
+ session: The database session.
126
+ dataset: The dataset to compute similarity for.
127
+ query_tag_id: The ID of the tag to use for the query
128
+ request: Request parameters including optional embedding model name
129
+ and metadata field name.
130
+
131
+ Returns:
132
+ Metadata name used for the similarity.
133
+
134
+ Raises:
135
+ HTTPException: 404 if invalid embedding model or query tag is given.
136
+ """
137
+ try:
138
+ embedding_model = embedding_model_resolver.get_by_name(
139
+ session=session,
140
+ dataset_id=dataset.dataset_id,
141
+ embedding_model_name=request.embedding_model_name,
142
+ )
143
+ except ValueError as e:
144
+ raise HTTPException(
145
+ status_code=HTTP_STATUS_NOT_FOUND,
146
+ detail="Embedding model not found",
147
+ ) from e
148
+
149
+ try:
150
+ return compute_similarity.compute_similarity_metadata(
151
+ session=session,
152
+ key_dataset_id=dataset.dataset_id,
153
+ query_tag_id=query_tag_id,
154
+ embedding_model_id=embedding_model.embedding_model_id,
155
+ metadata_name=request.metadata_name,
156
+ )
157
+ except TagNotFoundError as e:
158
+ raise HTTPException(
159
+ status_code=HTTP_STATUS_NOT_FOUND,
160
+ detail=f"Query tag {query_tag_id} not found",
161
+ ) from e