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,299 @@
1
+ """Handler for database operations related to tags."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+ from uuid import UUID
7
+
8
+ import sqlmodel
9
+ from sqlmodel import Session, col, select
10
+
11
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
12
+ from lightly_studio.models.annotation.links import AnnotationTagLinkTable
13
+ from lightly_studio.models.sample import SampleTable, SampleTagLinkTable
14
+ from lightly_studio.models.tag import TagCreate, TagTable, TagUpdate
15
+
16
+
17
+ def create(session: Session, tag: TagCreate) -> TagTable:
18
+ """Create a new tag in the database."""
19
+ db_tag = TagTable.model_validate(tag)
20
+ session.add(db_tag)
21
+ session.commit()
22
+ session.refresh(db_tag)
23
+ return db_tag
24
+
25
+
26
+ # TODO(Michal, 06/2025): Use Paginated struct instead of offset/limit.
27
+ def get_all_by_dataset_id(
28
+ session: Session, dataset_id: UUID, offset: int = 0, limit: int | None = None
29
+ ) -> list[TagTable]:
30
+ """Retrieve all tags with pagination."""
31
+ query = (
32
+ select(TagTable)
33
+ .where(TagTable.dataset_id == dataset_id)
34
+ .order_by(col(TagTable.created_at).asc(), col(TagTable.tag_id).asc())
35
+ .offset(offset)
36
+ )
37
+ if limit is not None:
38
+ query = query.limit(limit)
39
+ tags = session.exec(query).all()
40
+ return list(tags) if tags else []
41
+
42
+
43
+ def get_by_id(session: Session, tag_id: UUID) -> TagTable | None:
44
+ """Retrieve a single tag by ID."""
45
+ return session.exec(select(TagTable).where(TagTable.tag_id == tag_id)).one_or_none()
46
+
47
+
48
+ def get_by_name(session: Session, tag_name: str, dataset_id: UUID | None) -> TagTable | None:
49
+ """Retrieve a single tag by ID."""
50
+ if dataset_id:
51
+ return session.exec(
52
+ select(TagTable)
53
+ .where(TagTable.dataset_id == dataset_id)
54
+ .where(TagTable.name == tag_name)
55
+ ).one_or_none()
56
+ return session.exec(select(TagTable).where(TagTable.name == tag_name)).one_or_none()
57
+
58
+
59
+ def update(session: Session, tag_id: UUID, tag_data: TagUpdate) -> TagTable | None:
60
+ """Update an existing tag."""
61
+ tag = get_by_id(session=session, tag_id=tag_id)
62
+ if not tag:
63
+ return None
64
+
65
+ # due to duckdb/OLAP optimisations, update operations effecting unique
66
+ # constraints (e.g colums) will lead to a unique constraint violation.
67
+ # This is due to a update is implemented as delete+insert. The error
68
+ # happens only within the same session.
69
+ # To fix it, we can delete, commit + insert a new tag.
70
+ # https://duckdb.org/docs/sql/indexes#over-eager-unique-constraint-checking
71
+ session.delete(tag)
72
+ session.commit()
73
+
74
+ # create clone of tag with updated values
75
+ tag_updated = TagTable.model_validate(tag)
76
+ tag_updated.name = tag_data.name
77
+ tag_updated.description = tag_data.description
78
+ tag_updated.updated_at = datetime.now(timezone.utc)
79
+
80
+ session.add(tag_updated)
81
+ session.commit()
82
+ session.refresh(tag_updated)
83
+ return tag_updated
84
+
85
+
86
+ def delete(session: Session, tag_id: UUID) -> bool:
87
+ """Delete a tag."""
88
+ tag = get_by_id(session=session, tag_id=tag_id)
89
+ if not tag:
90
+ return False
91
+
92
+ session.delete(tag)
93
+ session.commit()
94
+ return True
95
+
96
+
97
+ def add_tag_to_sample(
98
+ session: Session,
99
+ tag_id: UUID,
100
+ sample: SampleTable,
101
+ ) -> SampleTable | None:
102
+ """Add a tag to a sample."""
103
+ tag = get_by_id(session=session, tag_id=tag_id)
104
+ if not tag or not tag.tag_id:
105
+ return None
106
+ if tag.kind != "sample":
107
+ raise ValueError(f"Tag {tag_id} is not of kind 'sample'")
108
+
109
+ sample.tags.append(tag)
110
+ session.add(sample)
111
+ session.commit()
112
+ session.refresh(sample)
113
+ return sample
114
+
115
+
116
+ def remove_tag_from_sample(
117
+ session: Session,
118
+ tag_id: UUID,
119
+ sample: SampleTable,
120
+ ) -> SampleTable | None:
121
+ """Remove a tag from a sample."""
122
+ tag = get_by_id(session=session, tag_id=tag_id)
123
+ if not tag or not tag.tag_id:
124
+ return None
125
+ if tag.kind != "sample":
126
+ raise ValueError(f"Tag {tag_id} is not of kind 'sample'")
127
+
128
+ sample.tags.remove(tag)
129
+ session.add(sample)
130
+ session.commit()
131
+ session.refresh(sample)
132
+ return sample
133
+
134
+
135
+ def add_tag_to_annotation(
136
+ session: Session,
137
+ tag_id: UUID,
138
+ annotation: AnnotationBaseTable,
139
+ ) -> AnnotationBaseTable | None:
140
+ """Add a tag to a annotation."""
141
+ tag = get_by_id(session=session, tag_id=tag_id)
142
+ if not tag or not tag.tag_id:
143
+ return None
144
+ if tag.kind != "annotation":
145
+ raise ValueError(f"Tag {tag_id} is not of kind 'annotation'")
146
+
147
+ annotation.tags.append(tag)
148
+ session.add(annotation)
149
+ session.commit()
150
+ session.refresh(annotation)
151
+ return annotation
152
+
153
+
154
+ def assign_tag_to_annotation(
155
+ session: Session,
156
+ tag: TagTable,
157
+ annotation: AnnotationBaseTable,
158
+ ) -> AnnotationBaseTable:
159
+ """Add a tag to a annotation."""
160
+ annotation.tags.append(tag)
161
+ session.add(annotation)
162
+ session.commit()
163
+ session.refresh(annotation)
164
+ return annotation
165
+
166
+
167
+ def remove_tag_from_annotation(
168
+ session: Session,
169
+ tag_id: UUID,
170
+ annotation: AnnotationBaseTable,
171
+ ) -> AnnotationBaseTable | None:
172
+ """Remove a tag from a annotation."""
173
+ tag = get_by_id(session=session, tag_id=tag_id)
174
+ if not tag or not tag.tag_id:
175
+ return None
176
+ if tag.kind != "annotation":
177
+ raise ValueError(f"Tag {tag_id} is not of kind 'annotation'")
178
+
179
+ annotation.tags.remove(tag)
180
+ session.add(annotation)
181
+ session.commit()
182
+ session.refresh(annotation)
183
+ return annotation
184
+
185
+
186
+ def add_sample_ids_to_tag_id(
187
+ session: Session,
188
+ tag_id: UUID,
189
+ sample_ids: list[UUID],
190
+ ) -> TagTable | None:
191
+ """Add a list of sample_ids to a tag."""
192
+ tag = get_by_id(session=session, tag_id=tag_id)
193
+ if not tag or not tag.tag_id:
194
+ return None
195
+ if tag.kind != "sample":
196
+ raise ValueError(f"Tag {tag_id} is not of kind 'sample'")
197
+
198
+ for sample_id in sample_ids:
199
+ session.merge(SampleTagLinkTable(sample_id=sample_id, tag_id=tag_id))
200
+
201
+ session.commit()
202
+ session.refresh(tag)
203
+ return tag
204
+
205
+
206
+ def remove_sample_ids_from_tag_id(
207
+ session: Session,
208
+ tag_id: UUID,
209
+ sample_ids: list[UUID],
210
+ ) -> TagTable | None:
211
+ """Remove a list of sample_ids to a tag."""
212
+ tag = get_by_id(session=session, tag_id=tag_id)
213
+ if not tag or not tag.tag_id:
214
+ return None
215
+ if tag.kind != "sample":
216
+ raise ValueError(f"Tag {tag_id} is not of kind 'sample'")
217
+
218
+ session.exec( # type:ignore[call-overload]
219
+ sqlmodel.delete(SampleTagLinkTable).where(
220
+ col(SampleTagLinkTable.tag_id) == tag_id,
221
+ col(SampleTagLinkTable.sample_id).in_(sample_ids),
222
+ )
223
+ )
224
+
225
+ session.commit()
226
+ session.refresh(tag)
227
+ return tag
228
+
229
+
230
+ def add_annotation_ids_to_tag_id(
231
+ session: Session,
232
+ tag_id: UUID,
233
+ annotation_ids: list[UUID],
234
+ ) -> TagTable | None:
235
+ """Add a list of annotation_ids to a tag."""
236
+ tag = get_by_id(session=session, tag_id=tag_id)
237
+ if not tag or not tag.tag_id:
238
+ return None
239
+ if tag.kind != "annotation":
240
+ raise ValueError(f"Tag {tag_id} is not of kind 'annotation'")
241
+
242
+ for annotation_id in annotation_ids:
243
+ session.merge(
244
+ AnnotationTagLinkTable(
245
+ tag_id=tag_id,
246
+ annotation_sample_id=annotation_id,
247
+ )
248
+ )
249
+
250
+ session.commit()
251
+ session.refresh(tag)
252
+ return tag
253
+
254
+
255
+ def remove_annotation_ids_from_tag_id(
256
+ session: Session,
257
+ tag_id: UUID,
258
+ annotation_ids: list[UUID],
259
+ ) -> TagTable | None:
260
+ """Remove a list of things to a tag."""
261
+ tag = get_by_id(session=session, tag_id=tag_id)
262
+ if not tag or not tag.tag_id:
263
+ return None
264
+ if tag.kind != "annotation":
265
+ raise ValueError(f"Tag {tag_id} is not of kind 'annotation'")
266
+
267
+ session.exec( # type:ignore[call-overload]
268
+ sqlmodel.delete(AnnotationTagLinkTable).where(
269
+ col(AnnotationTagLinkTable.tag_id) == tag_id,
270
+ col(AnnotationTagLinkTable.annotation_sample_id).in_(annotation_ids),
271
+ )
272
+ )
273
+
274
+ session.commit()
275
+ session.refresh(tag)
276
+ return tag
277
+
278
+
279
+ def get_or_create_sample_tag_by_name(
280
+ session: Session,
281
+ dataset_id: UUID,
282
+ tag_name: str,
283
+ ) -> TagTable:
284
+ """Get an existing sample tag by name or create a new one if it doesn't exist.
285
+
286
+ Args:
287
+ session: Database session for executing queries.
288
+ dataset_id: The dataset ID to search/create the tag for.
289
+ tag_name: Name of the tag to get or create.
290
+
291
+ Returns:
292
+ The existing or newly created sample tag.
293
+ """
294
+ existing_tag = get_by_name(session=session, tag_name=tag_name, dataset_id=dataset_id)
295
+ if existing_tag:
296
+ return existing_tag
297
+
298
+ new_tag = TagCreate(name=tag_name, dataset_id=dataset_id, kind="sample")
299
+ return create(session=session, tag=new_tag)
@@ -0,0 +1,119 @@
1
+ """Handler for getting cached 2D embeddings from high-dimensional embeddings."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ import numpy as np
8
+ from lightly_mundig import TwoDimEmbedding # type: ignore[import-untyped]
9
+ from numpy.typing import NDArray
10
+ from sqlmodel import Session, col, select
11
+
12
+ from lightly_studio.dataset.env import LIGHTLY_STUDIO_LICENSE_KEY
13
+ from lightly_studio.models.embedding_model import EmbeddingModelTable
14
+ from lightly_studio.models.sample import SampleTable
15
+ from lightly_studio.models.two_dim_embedding import TwoDimEmbeddingTable
16
+ from lightly_studio.resolvers import sample_embedding_resolver
17
+
18
+
19
+ def get_twodim_embeddings(
20
+ session: Session,
21
+ dataset_id: UUID,
22
+ embedding_model_id: UUID,
23
+ ) -> tuple[NDArray[np.float32], NDArray[np.float32], list[UUID]]:
24
+ """Return cached 2D embeddings together with their sample identifiers.
25
+
26
+ Uses a cache to avoid recomputing the 2D embeddings. The cache key combines the sorted
27
+ sample identifiers with a deterministic 64-bit hash over the stored high-dimensional
28
+ embeddings.
29
+
30
+ Args:
31
+ session: Database session.
32
+ dataset_id: Dataset identifier.
33
+ embedding_model_id: Embedding model identifier.
34
+
35
+ Returns:
36
+ Tuple of (x coordinates, y coordinates, ordered sample IDs).
37
+ """
38
+ embedding_model = session.get(EmbeddingModelTable, embedding_model_id)
39
+ if embedding_model is None:
40
+ raise ValueError(f"Embedding model {embedding_model_id} not found.")
41
+
42
+ # Define a fixed order of sample IDs for the cache key.
43
+ sample_ids_ordered = list(
44
+ session.exec(
45
+ select(SampleTable.sample_id)
46
+ .where(SampleTable.dataset_id == dataset_id)
47
+ .order_by(col(SampleTable.sample_id).asc())
48
+ ).all()
49
+ )
50
+
51
+ # Check if we have a cached 2D embedding for the given samples and embedding model.
52
+ # The order is defined by sample_ids_ordered.
53
+ cache_key, sample_ids_of_samples_with_embeddings = (
54
+ sample_embedding_resolver.get_hash_by_sample_ids(
55
+ session=session,
56
+ sample_ids_ordered=sample_ids_ordered,
57
+ embedding_model_id=embedding_model_id,
58
+ )
59
+ )
60
+
61
+ if not sample_ids_of_samples_with_embeddings:
62
+ empty = np.array([], dtype=np.float32)
63
+ return empty, empty, []
64
+
65
+ # If there is a cached entry, return it.
66
+ cached = session.get(TwoDimEmbeddingTable, cache_key)
67
+ if cached is not None:
68
+ x_values = np.array(cached.x, dtype=np.float32)
69
+ y_values = np.array(cached.y, dtype=np.float32)
70
+ return x_values, y_values, sample_ids_of_samples_with_embeddings
71
+
72
+ # No cached entry found - load the high-dimensional embeddings.
73
+ # The order is defined by sample_ids_of_samples_with_embeddings.
74
+ sample_embeddings = sample_embedding_resolver.get_by_sample_ids(
75
+ session=session,
76
+ sample_ids=sample_ids_of_samples_with_embeddings,
77
+ embedding_model_id=embedding_model_id,
78
+ )
79
+
80
+ # If there are no embeddings, return empty arrays.
81
+ if not sample_embeddings:
82
+ empty = np.array([], dtype=np.float32)
83
+ return empty, empty, []
84
+
85
+ # Compute the 2D embedding from the high-dimensional embeddings.
86
+ # The order is now defined by sample_embeddings. They are the ordered subset of the
87
+ # sample_ids_of_samples_with_embeddings that have embeddings.
88
+ sample_ids_of_samples_with_embeddings = [embedding.sample_id for embedding in sample_embeddings]
89
+ embedding_values = [embedding.embedding for embedding in sample_embeddings]
90
+ planar_embeddings = _calculate_2d_embeddings(embedding_values)
91
+ embeddings_2d = np.asarray(planar_embeddings, dtype=np.float32)
92
+ x_values, y_values = embeddings_2d[:, 0], embeddings_2d[:, 1]
93
+
94
+ # Write the computed 2D embeddings to the cache.
95
+ cache_entry = TwoDimEmbeddingTable(hash=cache_key, x=list(x_values), y=list(y_values))
96
+ session.add(cache_entry)
97
+ session.commit()
98
+
99
+ return x_values, y_values, sample_ids_of_samples_with_embeddings
100
+
101
+
102
+ def _calculate_2d_embeddings(embedding_values: list[list[float]]) -> list[tuple[float, float]]:
103
+ n_samples = len(embedding_values)
104
+ # For 0, 1 or 2 samples we hard-code deterministic coordinates.
105
+ if n_samples == 0:
106
+ return []
107
+ if n_samples == 1:
108
+ return [(0.0, 0.0)]
109
+ if n_samples == 2: # noqa: PLR2004
110
+ return [(0.0, 0.0), (1.0, 1.0)]
111
+
112
+ license_key = LIGHTLY_STUDIO_LICENSE_KEY
113
+ if license_key is None:
114
+ raise ValueError(
115
+ "LIGHTLY_STUDIO_LICENSE_KEY environment variable is not set. "
116
+ "Please set it to your LightlyStudio license key."
117
+ )
118
+ embedding_calculator = TwoDimEmbedding(embedding_values, license_key)
119
+ return embedding_calculator.calculate_2d_embedding() # type: ignore[no-any-return]
@@ -0,0 +1,23 @@
1
+ """Resolvers for video_frame database operations."""
2
+
3
+ from lightly_studio.resolvers.video_frame_resolver.count_video_frames_annotations import (
4
+ count_video_frames_annotations,
5
+ )
6
+ from lightly_studio.resolvers.video_frame_resolver.create_many import create_many
7
+ from lightly_studio.resolvers.video_frame_resolver.get_all_by_dataset_id import (
8
+ get_all_by_dataset_id,
9
+ )
10
+ from lightly_studio.resolvers.video_frame_resolver.get_by_id import (
11
+ get_by_id,
12
+ )
13
+ from lightly_studio.resolvers.video_frame_resolver.get_table_fields_bounds import (
14
+ get_table_fields_bounds,
15
+ )
16
+
17
+ __all__ = [
18
+ "count_video_frames_annotations",
19
+ "create_many",
20
+ "get_all_by_dataset_id",
21
+ "get_by_id",
22
+ "get_table_fields_bounds",
23
+ ]
@@ -0,0 +1,83 @@
1
+ """Count video frames annotations."""
2
+
3
+ from typing import Any, List, Optional, Tuple
4
+ from uuid import UUID
5
+
6
+ from sqlmodel import Session, asc, col, func, select
7
+ from sqlmodel.sql.expression import Select
8
+
9
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
10
+ from lightly_studio.models.annotation_label import AnnotationLabelTable
11
+ from lightly_studio.models.sample import SampleTable
12
+ from lightly_studio.models.video import VideoFrameTable
13
+ from lightly_studio.resolvers.video_frame_resolver.video_frame_annotations_counter_filter import (
14
+ VideoFrameAnnotationsCounterFilter,
15
+ )
16
+ from lightly_studio.resolvers.video_resolver.count_video_frame_annotations_by_video_dataset import (
17
+ CountAnnotationsView,
18
+ )
19
+
20
+
21
+ def count_video_frames_annotations(
22
+ session: Session, dataset_id: UUID, filters: Optional[VideoFrameAnnotationsCounterFilter] = None
23
+ ) -> List[CountAnnotationsView]:
24
+ """Count the annotations by video frames."""
25
+ unfiltered_query = (
26
+ _build_base_query(dataset_id=dataset_id, count_column_name="total")
27
+ .group_by(col(AnnotationBaseTable.annotation_label_id))
28
+ .subquery("unfiltered")
29
+ )
30
+
31
+ filtered_query = _build_base_query(dataset_id=dataset_id, count_column_name="filtered_count")
32
+
33
+ if filters:
34
+ filtered_query = filters.apply(filtered_query)
35
+
36
+ filtered_subquery = filtered_query.group_by(
37
+ col(AnnotationBaseTable.annotation_label_id)
38
+ ).subquery("filtered")
39
+
40
+ final_query: Select[Any] = (
41
+ select(
42
+ col(AnnotationLabelTable.annotation_label_name).label("label"),
43
+ col(unfiltered_query.c.total).label("total"),
44
+ func.coalesce(filtered_subquery.c.filtered_count, 0).label("filtered_count"),
45
+ )
46
+ .select_from(AnnotationLabelTable)
47
+ .join(
48
+ unfiltered_query,
49
+ unfiltered_query.c.label_id == col(AnnotationLabelTable.annotation_label_id),
50
+ )
51
+ .outerjoin(
52
+ filtered_subquery,
53
+ filtered_subquery.c.label_id == col(AnnotationLabelTable.annotation_label_id),
54
+ )
55
+ .order_by(asc(AnnotationLabelTable.annotation_label_name))
56
+ )
57
+
58
+ rows = session.execute(final_query).mappings().all()
59
+
60
+ return [
61
+ CountAnnotationsView(
62
+ label_name=row["label"],
63
+ total_count=row["total"],
64
+ current_count=row["filtered_count"],
65
+ )
66
+ for row in rows
67
+ ]
68
+
69
+
70
+ def _build_base_query(dataset_id: UUID, count_column_name: str) -> Select[Tuple[Any, int]]:
71
+ return (
72
+ select(
73
+ col(AnnotationBaseTable.annotation_label_id).label("label_id"),
74
+ func.count(col(AnnotationBaseTable.annotation_label_id)).label(count_column_name),
75
+ )
76
+ .select_from(AnnotationBaseTable)
77
+ .join(
78
+ VideoFrameTable,
79
+ col(VideoFrameTable.sample_id) == col(AnnotationBaseTable.parent_sample_id),
80
+ )
81
+ .join(SampleTable, col(VideoFrameTable.parent_sample_id) == col(SampleTable.sample_id))
82
+ .where(col(SampleTable.dataset_id) == dataset_id)
83
+ )
@@ -0,0 +1,57 @@
1
+ """Implementation of create functions for video_frames."""
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 VideoFrameCreate, VideoFrameTable
12
+ from lightly_studio.resolvers import dataset_resolver, sample_resolver
13
+
14
+
15
+ class VideoFrameCreateHelper(VideoFrameCreate):
16
+ """Helper class to create VideoFrameTable with sample_id."""
17
+
18
+ sample_id: UUID
19
+
20
+
21
+ def create_many(session: Session, dataset_id: UUID, samples: list[VideoFrameCreate]) -> list[UUID]:
22
+ """Create multiple video_frame 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 video_frames to create in the database.
28
+
29
+ Returns:
30
+ List of UUIDs of VideoFrameTable 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_FRAME,
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 VideoFrameTable entries using the generated sample_ids.
42
+ db_video_frames = [
43
+ VideoFrameTable.model_validate(
44
+ VideoFrameCreateHelper(
45
+ frame_number=sample.frame_number,
46
+ frame_timestamp_s=sample.frame_timestamp_s,
47
+ frame_timestamp_pts=sample.frame_timestamp_pts,
48
+ parent_sample_id=sample.parent_sample_id,
49
+ sample_id=sample_id,
50
+ rotation_deg=sample.rotation_deg,
51
+ )
52
+ )
53
+ for sample_id, sample in zip(sample_ids, samples)
54
+ ]
55
+ session.bulk_save_objects(db_video_frames)
56
+ session.commit()
57
+ return sample_ids
@@ -0,0 +1,63 @@
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 typing import Any
7
+ from uuid import UUID
8
+
9
+ from pydantic import BaseModel
10
+ from sqlmodel import Session, col, func, select
11
+
12
+ from lightly_studio.api.routes.api.validators import Paginated
13
+ from lightly_studio.models.sample import SampleTable
14
+ from lightly_studio.models.video import VideoFrameTable, VideoTable
15
+ from lightly_studio.resolvers.video_frame_resolver.video_frame_filter import VideoFrameFilter
16
+
17
+
18
+ class VideoFramesWithCount(BaseModel):
19
+ """Result of getting all samples."""
20
+
21
+ samples: Sequence[VideoFrameTable]
22
+ total_count: int
23
+ next_cursor: int | None = None
24
+
25
+
26
+ def get_all_by_dataset_id(
27
+ session: Session,
28
+ dataset_id: UUID,
29
+ pagination: Paginated | None = None,
30
+ video_frame_filter: VideoFrameFilter | None = None,
31
+ ) -> VideoFramesWithCount:
32
+ """Retrieve video frame samples for a specific dataset with optional filtering."""
33
+ filters: list[Any] = [SampleTable.dataset_id == dataset_id]
34
+
35
+ base_query = (
36
+ select(VideoFrameTable)
37
+ .join(VideoFrameTable.sample)
38
+ .join(VideoFrameTable.video)
39
+ .where(*filters)
40
+ )
41
+
42
+ if video_frame_filter:
43
+ base_query = video_frame_filter.apply(base_query)
44
+
45
+ samples_query = base_query.order_by(
46
+ col(VideoTable.file_path_abs).asc(), col(VideoFrameTable.frame_number).asc()
47
+ )
48
+
49
+ # Apply pagination if provided
50
+ if pagination is not None:
51
+ samples_query = samples_query.offset(pagination.offset).limit(pagination.limit)
52
+
53
+ total_count_query = select(func.count()).select_from(base_query.subquery())
54
+ total_count = session.exec(total_count_query).one()
55
+ next_cursor = None
56
+ if pagination and pagination.offset + pagination.limit < total_count:
57
+ next_cursor = pagination.offset + pagination.limit
58
+
59
+ return VideoFramesWithCount(
60
+ samples=session.exec(samples_query).all(),
61
+ total_count=total_count,
62
+ next_cursor=next_cursor,
63
+ )
@@ -0,0 +1,13 @@
1
+ """Retrieve the video frame by ID resolver implementation."""
2
+
3
+ from uuid import UUID
4
+
5
+ from sqlmodel import Session, select
6
+
7
+ from lightly_studio.models.video import VideoFrameTable
8
+
9
+
10
+ def get_by_id(session: Session, sample_id: UUID) -> VideoFrameTable:
11
+ """Retrieve a single video frame by ID within a dataset."""
12
+ query = select(VideoFrameTable).where(VideoFrameTable.sample_id == sample_id)
13
+ return session.exec(query).one()