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,44 @@
1
+ """Retrieve the minimum and maximum values of each video frame table field."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from uuid import UUID
7
+
8
+ from sqlalchemy import Select, select
9
+ from sqlmodel import Session, col, func
10
+
11
+ from lightly_studio.models.range import IntRange
12
+ from lightly_studio.models.sample import SampleTable
13
+ from lightly_studio.models.video import (
14
+ VideoFrameFieldsBoundsView,
15
+ VideoFrameTable,
16
+ )
17
+
18
+
19
+ def get_table_fields_bounds(
20
+ session: Session,
21
+ dataset_id: UUID,
22
+ ) -> VideoFrameFieldsBoundsView | None:
23
+ """Find the minimum and maximum values (bounds) of the video frames fields.
24
+
25
+ This includes frame number.
26
+ """
27
+ query: Select[tuple[Any, ...]] = (
28
+ select(
29
+ func.min(VideoFrameTable.frame_number).label("min_frame_number"),
30
+ func.max(VideoFrameTable.frame_number).label("max_frame_number"),
31
+ )
32
+ .join(SampleTable)
33
+ .where(col(SampleTable.dataset_id) == dataset_id)
34
+ )
35
+
36
+ result = session.execute(query).mappings().one()
37
+
38
+ ## If the min_frame_number is empty, then no data is found.
39
+ if result["min_frame_number"] is None:
40
+ return None
41
+
42
+ return VideoFrameFieldsBoundsView(
43
+ frame_number=IntRange(min=result["min_frame_number"], max=result["max_frame_number"]),
44
+ )
@@ -0,0 +1,47 @@
1
+ """Utility functions for building database queries."""
2
+
3
+ from typing import List, Optional
4
+
5
+ from pydantic import BaseModel
6
+ from sqlmodel import col, select
7
+
8
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
9
+ from lightly_studio.models.annotation_label import AnnotationLabelTable
10
+ from lightly_studio.models.video import VideoFrameTable
11
+ from lightly_studio.resolvers.video_frame_resolver.video_frame_filter import VideoFrameFilter
12
+ from lightly_studio.type_definitions import QueryType
13
+
14
+
15
+ class VideoFrameAnnotationsCounterFilter(BaseModel):
16
+ """Encapsulates filter parameters for querying video frame annotations counter."""
17
+
18
+ video_filter: Optional[VideoFrameFilter] = None
19
+ annotations_labels: Optional[List[str]] = None
20
+
21
+ def apply(self, query: QueryType) -> QueryType:
22
+ """Apply the filters to the given query."""
23
+ query = self._apply_annotations_label(query)
24
+
25
+ if self.video_filter:
26
+ query = self.video_filter.apply(query)
27
+
28
+ return query
29
+
30
+ def _apply_annotations_label(self, query: QueryType) -> QueryType:
31
+ if not self.annotations_labels:
32
+ return query
33
+
34
+ frame_filtered_video_ids_subquery = (
35
+ select(VideoFrameTable.sample_id)
36
+ .join(
37
+ AnnotationBaseTable,
38
+ col(AnnotationBaseTable.parent_sample_id) == VideoFrameTable.sample_id,
39
+ )
40
+ .join(AnnotationBaseTable.annotation_label)
41
+ .where(
42
+ col(AnnotationLabelTable.annotation_label_name).in_(self.annotations_labels or [])
43
+ )
44
+ .distinct()
45
+ )
46
+
47
+ return query.where(col(VideoFrameTable.sample_id).in_(frame_filtered_video_ids_subquery))
@@ -0,0 +1,57 @@
1
+ """Utility functions for building database queries."""
2
+
3
+ from typing import Optional
4
+ from uuid import UUID
5
+
6
+ from pydantic import BaseModel
7
+ from sqlmodel import col
8
+
9
+ from lightly_studio.models.video import VideoFrameTable, VideoTable
10
+ from lightly_studio.resolvers.image_filter import FilterDimensions
11
+ from lightly_studio.resolvers.sample_resolver.sample_filter import SampleFilter
12
+ from lightly_studio.type_definitions import QueryType
13
+
14
+
15
+ class VideoFrameFilter(BaseModel):
16
+ """Encapsulates filter parameters for querying video frames."""
17
+
18
+ frame_number: Optional[FilterDimensions] = None
19
+ video_id: Optional[UUID] = None
20
+ sample_filter: Optional[SampleFilter] = None
21
+
22
+ def apply(self, query: QueryType) -> QueryType:
23
+ """Apply the filters to the given query."""
24
+ query = self._apply_frame_number_filters(query)
25
+ query = self._apply_video_id(query)
26
+
27
+ if self.sample_filter:
28
+ query = self.sample_filter.apply(query)
29
+
30
+ return query
31
+
32
+ def _apply_video_id(self, query: QueryType) -> QueryType:
33
+ if self.video_id:
34
+ return query.where(col(VideoTable.sample_id) == self.video_id)
35
+
36
+ return query
37
+
38
+ def _apply_frame_number_filters(self, query: QueryType) -> QueryType:
39
+ min_frame_number = (
40
+ self.frame_number.min
41
+ if self.frame_number and self.frame_number.min is not None
42
+ else None
43
+ )
44
+
45
+ max_frame_number = (
46
+ self.frame_number.max
47
+ if self.frame_number and self.frame_number.max is not None
48
+ else None
49
+ )
50
+
51
+ if min_frame_number is not None:
52
+ query = query.where(col(VideoFrameTable.frame_number) >= min_frame_number)
53
+
54
+ if max_frame_number is not None:
55
+ query = query.where(col(VideoFrameTable.frame_number) <= max_frame_number)
56
+
57
+ return query
@@ -0,0 +1,27 @@
1
+ """Resolvers for video database operations."""
2
+
3
+ from lightly_studio.resolvers.video_resolver.count_video_frame_annotations_by_video_dataset import (
4
+ count_video_frame_annotations_by_video_dataset,
5
+ )
6
+ from lightly_studio.resolvers.video_resolver.create_many import create_many
7
+ from lightly_studio.resolvers.video_resolver.filter_new_paths import filter_new_paths
8
+ from lightly_studio.resolvers.video_resolver.get_all_by_dataset_id import (
9
+ get_all_by_dataset_id,
10
+ get_all_by_dataset_id_with_frames,
11
+ )
12
+ from lightly_studio.resolvers.video_resolver.get_by_id import get_by_id
13
+ from lightly_studio.resolvers.video_resolver.get_table_fields_bounds import (
14
+ get_table_fields_bounds,
15
+ )
16
+ from lightly_studio.resolvers.video_resolver.get_view_by_id import get_view_by_id
17
+
18
+ __all__ = [
19
+ "count_video_frame_annotations_by_video_dataset",
20
+ "create_many",
21
+ "filter_new_paths",
22
+ "get_all_by_dataset_id",
23
+ "get_all_by_dataset_id_with_frames",
24
+ "get_by_id",
25
+ "get_table_fields_bounds",
26
+ "get_view_by_id",
27
+ ]
@@ -0,0 +1,86 @@
1
+ """Count video frame annotations by video dataset."""
2
+
3
+ from typing import Any, List, Optional, Tuple
4
+ from uuid import UUID
5
+
6
+ from pydantic import BaseModel
7
+ from sqlmodel import Session, asc, col, func, select
8
+ from sqlmodel.sql.expression import Select
9
+
10
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
11
+ from lightly_studio.models.annotation_label import AnnotationLabelTable
12
+ from lightly_studio.models.sample import SampleTable
13
+ from lightly_studio.models.video import VideoFrameTable, VideoTable
14
+ from lightly_studio.resolvers.video_resolver.video_count_annotations_filter import (
15
+ VideoCountAnnotationsFilter,
16
+ )
17
+
18
+
19
+ class CountAnnotationsView(BaseModel):
20
+ """Count annotations view."""
21
+
22
+ label_name: str
23
+ total_count: int
24
+ current_count: int
25
+
26
+
27
+ def count_video_frame_annotations_by_video_dataset(
28
+ session: Session, dataset_id: UUID, filters: Optional[VideoCountAnnotationsFilter] = None
29
+ ) -> List[CountAnnotationsView]:
30
+ """Count the annotations by video frames."""
31
+ unfiltered_query = (
32
+ _build_base_query(dataset_id=dataset_id, count_column_name="total")
33
+ .group_by(col(AnnotationBaseTable.annotation_label_id))
34
+ .subquery("unfiltered")
35
+ )
36
+ filtered_query = _build_base_query(dataset_id=dataset_id, count_column_name="filtered_count")
37
+
38
+ if filters:
39
+ filtered_query = filters.apply(filtered_query)
40
+
41
+ filtered_subquery = filtered_query.group_by(
42
+ col(AnnotationBaseTable.annotation_label_id)
43
+ ).subquery("filtered")
44
+
45
+ final_query: Select[Any] = (
46
+ select(
47
+ col(AnnotationLabelTable.annotation_label_name).label("label"),
48
+ col(unfiltered_query.c.total).label("total"),
49
+ func.coalesce(filtered_subquery.c.filtered_count, 0).label("filtered_count"),
50
+ )
51
+ .select_from(AnnotationLabelTable)
52
+ .join(
53
+ unfiltered_query,
54
+ unfiltered_query.c.label_id == col(AnnotationLabelTable.annotation_label_id),
55
+ )
56
+ .outerjoin(
57
+ filtered_subquery,
58
+ filtered_subquery.c.label_id == col(AnnotationLabelTable.annotation_label_id),
59
+ )
60
+ .order_by(asc(AnnotationLabelTable.annotation_label_name))
61
+ )
62
+
63
+ rows = session.execute(final_query).mappings().all()
64
+ return [
65
+ CountAnnotationsView(
66
+ label_name=row["label"], total_count=row["total"], current_count=row["filtered_count"]
67
+ )
68
+ for row in rows
69
+ ]
70
+
71
+
72
+ def _build_base_query(dataset_id: UUID, count_column_name: str) -> Select[Tuple[Any, int]]:
73
+ return (
74
+ select(
75
+ col(AnnotationBaseTable.annotation_label_id).label("label_id"),
76
+ func.count(func.distinct(VideoTable.sample_id)).label(count_column_name),
77
+ )
78
+ .select_from(AnnotationBaseTable)
79
+ .join(
80
+ VideoFrameTable,
81
+ col(VideoFrameTable.sample_id) == col(AnnotationBaseTable.parent_sample_id),
82
+ )
83
+ .join(SampleTable, col(SampleTable.sample_id) == col(VideoFrameTable.parent_sample_id))
84
+ .join(VideoTable, col(VideoTable.sample_id) == col(SampleTable.sample_id))
85
+ .where(col(SampleTable.dataset_id) == dataset_id)
86
+ )
@@ -0,0 +1,58 @@
1
+ """Implementation of create functions for videos."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlmodel import Session
8
+
9
+ from lightly_studio.models.dataset import SampleType
10
+ from lightly_studio.models.sample import SampleCreate
11
+ from lightly_studio.models.video import VideoCreate, VideoTable
12
+ from lightly_studio.resolvers import dataset_resolver, sample_resolver
13
+
14
+
15
+ class VideoCreateHelper(VideoCreate):
16
+ """Helper class to create VideoTable with sample_id."""
17
+
18
+ sample_id: UUID
19
+
20
+
21
+ def create_many(session: Session, dataset_id: UUID, samples: list[VideoCreate]) -> list[UUID]:
22
+ """Create multiple video samples in a single database commit.
23
+
24
+ Args:
25
+ session: The database session.
26
+ dataset_id: The uuid of the dataset to attach to.
27
+ samples: The videos to create in the database.
28
+
29
+ Returns:
30
+ List of IDs of VideoTable entries that got added to the database.
31
+ """
32
+ dataset_resolver.check_dataset_type(
33
+ session=session,
34
+ dataset_id=dataset_id,
35
+ expected_type=SampleType.VIDEO,
36
+ )
37
+ sample_ids = sample_resolver.create_many(
38
+ session=session,
39
+ samples=[SampleCreate(dataset_id=dataset_id) for _ in samples],
40
+ )
41
+ # Bulk create VideoTable entries using the generated sample_ids.
42
+ db_videos = [
43
+ VideoTable.model_validate(
44
+ VideoCreateHelper(
45
+ file_name=sample.file_name,
46
+ width=sample.width,
47
+ height=sample.height,
48
+ duration_s=sample.duration_s,
49
+ fps=sample.fps,
50
+ file_path_abs=sample.file_path_abs,
51
+ sample_id=sample_id,
52
+ )
53
+ )
54
+ for sample_id, sample in zip(sample_ids, samples)
55
+ ]
56
+ session.bulk_save_objects(db_videos)
57
+ session.commit()
58
+ return sample_ids
@@ -0,0 +1,33 @@
1
+ """Implementation of filter_new_paths function for videos."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from sqlmodel import Session, col, select
6
+
7
+ from lightly_studio.models.video import VideoTable
8
+
9
+
10
+ # TODO(Horatiu, 11/2025): Add dataset_id parameter to support multiple datasets.
11
+ def filter_new_paths(session: Session, file_paths_abs: list[str]) -> tuple[list[str], list[str]]:
12
+ """Filter the file_paths into existing in DB and non existing in DB.
13
+
14
+ Args:
15
+ session: The database session.
16
+ file_paths_abs: The file paths to filter.
17
+
18
+ Returns:
19
+ file_paths_abs that don't exist in the database,
20
+ file_paths_abs that exist in the database
21
+ """
22
+ existing_file_paths_abs = set(
23
+ session.exec(
24
+ select(col(VideoTable.file_path_abs)).where(
25
+ col(VideoTable.file_path_abs).in_(file_paths_abs)
26
+ )
27
+ ).all()
28
+ )
29
+ file_paths_abs_set = set(file_paths_abs)
30
+ return (
31
+ list(file_paths_abs_set - existing_file_paths_abs), # paths that are not in the DB
32
+ list(file_paths_abs_set & existing_file_paths_abs), # paths that are in the DB
33
+ )
@@ -0,0 +1,181 @@
1
+ """Implementation of get_all_by_dataset_id function for videos."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from uuid import UUID
7
+
8
+ from sqlalchemy import and_
9
+ from sqlalchemy.orm import joinedload, selectinload
10
+ from sqlmodel import Session, col, func, select
11
+
12
+ from lightly_studio.api.routes.api.frame import build_frame_view
13
+ from lightly_studio.api.routes.api.validators import Paginated
14
+ from lightly_studio.models.embedding_model import EmbeddingModelTable
15
+ from lightly_studio.models.sample import SampleTable, SampleView
16
+ from lightly_studio.models.sample_embedding import SampleEmbeddingTable
17
+ from lightly_studio.models.video import (
18
+ VideoFrameTable,
19
+ VideoTable,
20
+ VideoView,
21
+ VideoViewsWithCount,
22
+ )
23
+ from lightly_studio.resolvers.video_resolver.video_filter import VideoFilter
24
+
25
+
26
+ def get_all_by_dataset_id( # noqa: PLR0913
27
+ session: Session,
28
+ dataset_id: UUID,
29
+ pagination: Paginated | None = None,
30
+ sample_ids: list[UUID] | None = None,
31
+ filters: VideoFilter | None = None,
32
+ text_embedding: list[float] | None = None,
33
+ ) -> VideoViewsWithCount:
34
+ """Retrieve samples for a specific dataset with optional filtering."""
35
+ # Subquery to find the minimum frame_number for each video
36
+ min_frame_subquery = (
37
+ select(
38
+ VideoFrameTable.parent_sample_id,
39
+ func.min(col(VideoFrameTable.frame_number)).label("min_frame_number"),
40
+ )
41
+ .group_by(col(VideoFrameTable.parent_sample_id))
42
+ .subquery()
43
+ )
44
+ # TODO(Horatiu, 11/2025): Check if it is possible to optimize this query.
45
+ # Query to get videos with their first frame (frame with min frame_number)
46
+ # First join the subquery to VideoTable, then join VideoFrameTable
47
+ samples_query = (
48
+ select(VideoTable, VideoFrameTable)
49
+ .join(VideoTable.sample)
50
+ .outerjoin(
51
+ min_frame_subquery,
52
+ min_frame_subquery.c.parent_sample_id == VideoTable.sample_id,
53
+ )
54
+ .outerjoin(
55
+ VideoFrameTable,
56
+ and_(
57
+ col(VideoFrameTable.parent_sample_id) == col(VideoTable.sample_id),
58
+ col(VideoFrameTable.frame_number) == min_frame_subquery.c.min_frame_number,
59
+ ),
60
+ )
61
+ .where(SampleTable.dataset_id == dataset_id)
62
+ .options(
63
+ selectinload(VideoFrameTable.sample).options(
64
+ joinedload(SampleTable.tags),
65
+ # Ignore type checker error - false positive from TYPE_CHECKING.
66
+ joinedload(SampleTable.metadata_dict), # type: ignore[arg-type]
67
+ selectinload(SampleTable.captions),
68
+ ),
69
+ selectinload(VideoTable.sample).options(
70
+ joinedload(SampleTable.tags),
71
+ # Ignore type checker error - false positive from TYPE_CHECKING.
72
+ joinedload(SampleTable.metadata_dict), # type: ignore[arg-type]
73
+ selectinload(SampleTable.captions),
74
+ ),
75
+ )
76
+ )
77
+
78
+ total_count_query = (
79
+ select(func.count())
80
+ .select_from(VideoTable)
81
+ .join(VideoTable.sample)
82
+ .where(SampleTable.dataset_id == dataset_id)
83
+ )
84
+
85
+ if text_embedding:
86
+ # Fetch the first embedding_model_id for the given dataset_id
87
+ embedding_model_id = session.exec(
88
+ select(EmbeddingModelTable.embedding_model_id)
89
+ .where(EmbeddingModelTable.dataset_id == dataset_id)
90
+ .limit(1)
91
+ ).first()
92
+
93
+ if text_embedding and embedding_model_id:
94
+ # Join with SampleEmbedding table to access embeddings
95
+ samples_query = (
96
+ samples_query.join(
97
+ SampleEmbeddingTable,
98
+ col(VideoTable.sample_id) == col(SampleEmbeddingTable.sample_id),
99
+ )
100
+ .where(SampleEmbeddingTable.embedding_model_id == embedding_model_id)
101
+ .order_by(
102
+ func.list_cosine_distance(
103
+ SampleEmbeddingTable.embedding,
104
+ text_embedding,
105
+ )
106
+ )
107
+ )
108
+ total_count_query = total_count_query.join(
109
+ SampleEmbeddingTable,
110
+ col(VideoTable.sample_id) == col(SampleEmbeddingTable.sample_id),
111
+ ).where(SampleEmbeddingTable.embedding_model_id == embedding_model_id)
112
+ else:
113
+ samples_query = samples_query.order_by(col(VideoTable.file_path_abs).asc())
114
+
115
+ if sample_ids:
116
+ samples_query = samples_query.where(col(VideoTable.sample_id).in_(sample_ids))
117
+ total_count_query = total_count_query.where(col(VideoTable.sample_id).in_(sample_ids))
118
+
119
+ if filters:
120
+ samples_query = filters.apply(samples_query)
121
+ total_count_query = filters.apply(total_count_query)
122
+
123
+ samples_query = samples_query.order_by(col(VideoTable.file_path_abs).asc())
124
+
125
+ # Apply pagination if provided
126
+ if pagination is not None:
127
+ samples_query = samples_query.offset(pagination.offset).limit(pagination.limit)
128
+
129
+ total_count = session.exec(total_count_query).one()
130
+
131
+ next_cursor = None
132
+ if pagination and pagination.offset + pagination.limit < total_count:
133
+ next_cursor = pagination.offset + pagination.limit
134
+
135
+ # Fetch videos with their first frames and convert to VideoView
136
+ results = session.exec(samples_query).all()
137
+ video_views = [
138
+ convert_video_table_to_view(video=video, first_frame=first_frame)
139
+ for video, first_frame in results
140
+ ]
141
+
142
+ return VideoViewsWithCount(
143
+ samples=video_views,
144
+ total_count=total_count,
145
+ next_cursor=next_cursor,
146
+ )
147
+
148
+
149
+ # TODO(Horatiu, 11/2025): This should be deleted when we have proper way of getting all frames for
150
+ # a video.
151
+ def get_all_by_dataset_id_with_frames(
152
+ session: Session,
153
+ dataset_id: UUID,
154
+ ) -> Sequence[VideoTable]:
155
+ """Retrieve video table with all the samples."""
156
+ samples_query = (
157
+ select(VideoTable).join(VideoTable.sample).where(SampleTable.dataset_id == dataset_id)
158
+ )
159
+ samples_query = samples_query.order_by(col(VideoTable.file_path_abs).asc())
160
+ return session.exec(samples_query).all()
161
+
162
+
163
+ def convert_video_table_to_view(
164
+ video: VideoTable, first_frame: VideoFrameTable | None
165
+ ) -> VideoView:
166
+ """Convert VideoTable to VideoView with only the first frame."""
167
+ first_frame_view = None
168
+ if first_frame:
169
+ first_frame_view = build_frame_view(first_frame)
170
+
171
+ return VideoView(
172
+ width=video.width,
173
+ height=video.height,
174
+ duration_s=video.duration_s,
175
+ fps=video.fps,
176
+ file_name=video.file_name,
177
+ file_path_abs=video.file_path_abs,
178
+ sample_id=video.sample_id,
179
+ sample=SampleView.model_validate(video.sample),
180
+ frame=first_frame_view,
181
+ )
@@ -0,0 +1,22 @@
1
+ """Find a video by its id."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlmodel import Session, select
8
+
9
+ from lightly_studio.models.video import VideoTable
10
+
11
+
12
+ def get_by_id(session: Session, sample_id: UUID) -> VideoTable | None:
13
+ """Retrieve a single video sample by ID.
14
+
15
+ Args:
16
+ session: The database session.
17
+ sample_id: The ID of the video to retrieve.
18
+
19
+ Returns:
20
+ A VideoTable object or none.
21
+ """
22
+ return session.exec(select(VideoTable).where(VideoTable.sample_id == sample_id)).one()
@@ -0,0 +1,72 @@
1
+ """Retrieve the minimum and maximum values of each video table field."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from uuid import UUID
7
+
8
+ from sqlalchemy import Select, select
9
+ from sqlmodel import Session, col, func
10
+
11
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
12
+ from lightly_studio.models.range import FloatRange, IntRange
13
+ from lightly_studio.models.sample import SampleTable
14
+ from lightly_studio.models.video import (
15
+ VideoFieldsBoundsView,
16
+ VideoFrameTable,
17
+ VideoTable,
18
+ )
19
+
20
+
21
+ def get_table_fields_bounds(
22
+ session: Session, dataset_id: UUID, annotations_frames_labels_id: list[UUID] | None = None
23
+ ) -> VideoFieldsBoundsView | None:
24
+ """Find the minimum and maximum values (bounds) of the video fields.
25
+
26
+ This includes fps, width, height, and duration. If annotation label
27
+ filters are provided, only videos containing those annotations are
28
+ considered.
29
+ """
30
+ query: Select[tuple[Any, ...]] = select(
31
+ func.min(VideoTable.width).label("min_width"),
32
+ func.max(VideoTable.width).label("max_width"),
33
+ func.min(VideoTable.height).label("min_height"),
34
+ func.max(VideoTable.height).label("max_height"),
35
+ func.min(VideoTable.duration_s).label("min_duration_s"),
36
+ func.max(VideoTable.duration_s).label("max_duration_s"),
37
+ func.min(VideoTable.fps).label("min_fps"),
38
+ func.max(VideoTable.fps).label("max_fps"),
39
+ )
40
+
41
+ query = query.join(SampleTable)
42
+
43
+ if annotations_frames_labels_id:
44
+ annotation_video_ids_subquery = (
45
+ select(col(VideoTable.sample_id))
46
+ .select_from(VideoTable)
47
+ .join(VideoFrameTable)
48
+ .join(SampleTable, col(SampleTable.sample_id) == col(VideoFrameTable.sample_id))
49
+ .join(
50
+ AnnotationBaseTable,
51
+ col(AnnotationBaseTable.parent_sample_id) == col(SampleTable.sample_id),
52
+ )
53
+ .where(col(AnnotationBaseTable.annotation_label_id).in_(annotations_frames_labels_id))
54
+ .distinct()
55
+ )
56
+
57
+ query = query.where(col(VideoTable.sample_id).in_(annotation_video_ids_subquery))
58
+
59
+ query = query.where(col(SampleTable.dataset_id) == dataset_id)
60
+
61
+ result = session.execute(query).mappings().one()
62
+
63
+ ## If the min_width is empty, then no data is found.
64
+ if result["min_width"] is None:
65
+ return None
66
+
67
+ return VideoFieldsBoundsView(
68
+ width=IntRange(min=result["min_width"], max=result["max_width"]),
69
+ height=IntRange(min=result["min_height"], max=result["max_height"]),
70
+ duration_s=FloatRange(min=result["min_duration_s"], max=result["max_duration_s"]),
71
+ fps=FloatRange(min=result["min_fps"], max=result["max_fps"]),
72
+ )
@@ -0,0 +1,52 @@
1
+ """Find a video view by its id."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlalchemy import and_
8
+ from sqlmodel import Session, col, func, select
9
+
10
+ from lightly_studio.models.video import VideoFrameTable, VideoTable, VideoView
11
+ from lightly_studio.resolvers.video_resolver.get_all_by_dataset_id import (
12
+ convert_video_table_to_view,
13
+ )
14
+
15
+
16
+ def get_view_by_id(session: Session, sample_id: UUID) -> VideoView | None:
17
+ """Retrieve a video view by its ID.
18
+
19
+ Args:
20
+ session: The database session.
21
+ sample_id: The ID of the video to retrieve.
22
+
23
+ Returns:
24
+ A VideoView object or none.
25
+ """
26
+ min_frame_subquery = (
27
+ select(
28
+ VideoFrameTable.parent_sample_id,
29
+ func.min(VideoFrameTable.frame_number).label("min_frame_number"),
30
+ )
31
+ .group_by(col(VideoFrameTable.parent_sample_id))
32
+ .subquery()
33
+ )
34
+
35
+ query = (
36
+ select(VideoTable, VideoFrameTable)
37
+ .outerjoin(
38
+ min_frame_subquery,
39
+ min_frame_subquery.c.parent_sample_id == VideoTable.sample_id,
40
+ )
41
+ .outerjoin(
42
+ VideoFrameTable,
43
+ and_(
44
+ col(VideoFrameTable.parent_sample_id) == col(VideoTable.sample_id),
45
+ col(VideoFrameTable.frame_number) == min_frame_subquery.c.min_frame_number,
46
+ ),
47
+ )
48
+ .where(VideoTable.sample_id == sample_id)
49
+ )
50
+
51
+ video, first_frame = session.exec(query).one()
52
+ return convert_video_table_to_view(video, first_frame)