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,58 @@
1
+ """Function to get or create a unique child dataset with a given sample type."""
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 DatasetCreate, SampleType
10
+ from lightly_studio.resolvers import dataset_resolver
11
+
12
+
13
+ def get_or_create_child_dataset(
14
+ session: Session, dataset_id: UUID, sample_type: SampleType
15
+ ) -> UUID:
16
+ """Checks if a unique child dataset with the given sample type exists for the given dataset.
17
+
18
+ If it exists, returns its ID. If not, creates it and then returns its ID.
19
+ If multiple such datasets exist, raises an error.
20
+
21
+ The returned child is a direct child of the given dataset.
22
+
23
+ Args:
24
+ session: The database session.
25
+ dataset_id: The uuid of the dataset to attach to.
26
+ sample_type: The sample type of the child dataset to get or create.
27
+
28
+ Returns:
29
+ The uuid of the child dataset.
30
+
31
+ Raises:
32
+ ValueError: If multiple child datasets with the given sample type exist.
33
+ """
34
+ # Get filtered child datasets.
35
+ dataset = dataset_resolver.get_by_id(session=session, dataset_id=dataset_id)
36
+ if dataset is None:
37
+ raise ValueError(f"Dataset with id {dataset_id} not found.")
38
+ child_datasets = [ds for ds in dataset.children if ds.sample_type == sample_type]
39
+
40
+ # If we have children check if any have the given sample type.
41
+ if len(child_datasets) == 1:
42
+ return child_datasets[0].dataset_id
43
+ if len(child_datasets) > 1:
44
+ raise ValueError(
45
+ f"Multiple child datasets with sample type {sample_type.value} found "
46
+ f"for dataset id {dataset_id}."
47
+ )
48
+
49
+ # No child dataset with the given sample type found, create one.
50
+ child_dataset = dataset_resolver.create(
51
+ session=session,
52
+ dataset=DatasetCreate(
53
+ name=f"{dataset.name}__{sample_type.value.lower()}",
54
+ sample_type=sample_type,
55
+ parent_dataset_id=dataset_id,
56
+ ),
57
+ )
58
+ return child_dataset.dataset_id
@@ -0,0 +1,27 @@
1
+ """Retrieve the parent dataset for a given sample ID."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlmodel import Session, col, select
8
+
9
+ from lightly_studio.models.dataset import DatasetTable
10
+ from lightly_studio.models.sample import SampleTable
11
+
12
+
13
+ def get_parent_dataset_by_sample_id(session: Session, sample_id: UUID) -> DatasetTable | None:
14
+ """Get parent dataset by sample ID.
15
+
16
+ Args:
17
+ session: Database session
18
+ sample_id: ID of the sample for which to get the parent dataset
19
+
20
+ Returns:
21
+ Returns parent dataset
22
+ """
23
+ child = session.exec(
24
+ select(DatasetTable).join(SampleTable).where(col(SampleTable.sample_id) == sample_id)
25
+ ).one_or_none()
26
+
27
+ return child.parent if child else None
@@ -0,0 +1,22 @@
1
+ """Retrieve the parent dataset ID for a given dataset ID."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlalchemy.orm import aliased
8
+ from sqlmodel import Session, col, select
9
+
10
+ from lightly_studio.models.dataset import DatasetTable
11
+
12
+ ParentDataset = aliased(DatasetTable)
13
+ ChildDataset = aliased(DatasetTable)
14
+
15
+
16
+ def get_parent_dataset_id(session: Session, dataset_id: UUID) -> DatasetTable | None:
17
+ """Retrieve the parent dataset for a given dataset ID."""
18
+ return session.exec(
19
+ select(ParentDataset)
20
+ .join(ChildDataset, col(ChildDataset.parent_dataset_id) == col(ParentDataset.dataset_id))
21
+ .where(ChildDataset.dataset_id == dataset_id)
22
+ ).one_or_none()
@@ -0,0 +1,61 @@
1
+ """Implementation of get_root_dataset resolver function."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlmodel import Session, col, select
8
+
9
+ from lightly_studio.models.dataset import DatasetTable
10
+
11
+
12
+ # TODO (Mihnea, 12/2025): Update the dataset_id to be required.
13
+ # The dataset_id is currently optional for backwards compatibility.
14
+ def get_root_dataset(session: Session, dataset_id: UUID | None = None) -> DatasetTable:
15
+ """Retrieve the root dataset for a given dataset or the first root dataset.
16
+
17
+ If dataset_id is provided, traverses up the hierarchy to find the root ancestor.
18
+ If dataset_id is None, returns the first root dataset (backwards compatibility).
19
+
20
+ A root dataset is defined as a dataset where parent_dataset_id is None.
21
+ The root dataset may or may not have children.
22
+
23
+ Args:
24
+ session: The database session.
25
+ dataset_id: Optional ID of a dataset to find the root for.
26
+
27
+ Returns:
28
+ The root dataset.
29
+
30
+ Raises:
31
+ ValueError: If no root dataset is found or dataset_id doesn't exist.
32
+ """
33
+ if dataset_id is not None:
34
+ # Find the dataset.
35
+ dataset = session.get(DatasetTable, dataset_id)
36
+ if dataset is None:
37
+ raise ValueError(f"Dataset with ID {dataset_id} not found.")
38
+
39
+ # Traverse up the hierarchy until we find the root.
40
+ # TODO (Mihnea, 12/2025): Consider replacing the loop with a recursive CTE,
41
+ # if this becomes a bottleneck.
42
+ while dataset.parent_dataset_id is not None:
43
+ parent = session.get(DatasetTable, dataset.parent_dataset_id)
44
+ if parent is None:
45
+ raise ValueError(
46
+ f"Parent dataset {dataset.parent_dataset_id} not found "
47
+ f"for dataset {dataset.dataset_id}."
48
+ )
49
+ dataset = parent
50
+
51
+ return dataset
52
+
53
+ # Backwards compatibility: return first root dataset
54
+ root_datasets = session.exec(
55
+ select(DatasetTable).where(col(DatasetTable.parent_dataset_id).is_(None))
56
+ ).all()
57
+
58
+ if len(root_datasets) == 0:
59
+ raise ValueError("No root dataset found. A root dataset must exist.")
60
+
61
+ return root_datasets[0]
@@ -0,0 +1,41 @@
1
+ """Handler for database operations related to fetching root datasets with details."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from sqlmodel import Session, col, func, select
6
+
7
+ from lightly_studio.models.dataset import DatasetOverviewView, DatasetTable
8
+ from lightly_studio.models.sample import SampleTable
9
+
10
+
11
+ def get_root_datasets_overview(session: Session) -> list[DatasetOverviewView]:
12
+ """Get root datasets with detailed metadata including sample counts."""
13
+ datasets_query = (
14
+ select( # type: ignore[call-overload]
15
+ DatasetTable.dataset_id,
16
+ DatasetTable.name,
17
+ DatasetTable.sample_type,
18
+ DatasetTable.created_at,
19
+ func.count(col(SampleTable.dataset_id)).label("sample_count"),
20
+ )
21
+ .outerjoin(SampleTable)
22
+ .where(col(DatasetTable.parent_dataset_id).is_(None))
23
+ .group_by(
24
+ DatasetTable.dataset_id,
25
+ DatasetTable.name,
26
+ DatasetTable.sample_type,
27
+ DatasetTable.created_at,
28
+ )
29
+ .order_by(col(DatasetTable.created_at).desc())
30
+ )
31
+
32
+ return [
33
+ DatasetOverviewView(
34
+ dataset_id=row.dataset_id,
35
+ name=row.name,
36
+ sample_type=row.sample_type,
37
+ created_at=row.created_at,
38
+ total_sample_count=row.sample_count,
39
+ )
40
+ for row in session.exec(datasets_query).all()
41
+ ]
@@ -0,0 +1,25 @@
1
+ """Implementation of update dataset resolver function."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timezone
6
+ from uuid import UUID
7
+
8
+ from sqlmodel import Session
9
+
10
+ from lightly_studio.models.dataset import DatasetCreate, DatasetTable
11
+ from lightly_studio.resolvers.dataset_resolver.get_by_id import get_by_id
12
+
13
+
14
+ def update(session: Session, dataset_id: UUID, dataset_data: DatasetCreate) -> DatasetTable:
15
+ """Update an existing dataset."""
16
+ dataset = get_by_id(session=session, dataset_id=dataset_id)
17
+ if not dataset:
18
+ raise ValueError(f"Dataset ID was not found '{dataset_id}'.")
19
+
20
+ dataset.name = dataset_data.name
21
+ dataset.updated_at = datetime.now(timezone.utc)
22
+
23
+ session.commit()
24
+ session.refresh(dataset)
25
+ return dataset
@@ -0,0 +1,120 @@
1
+ """Handler for database operations related to embedding models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlmodel import Session, col, select
8
+
9
+ from lightly_studio.models.embedding_model import (
10
+ EmbeddingModelCreate,
11
+ EmbeddingModelTable,
12
+ )
13
+
14
+
15
+ def create(session: Session, embedding_model: EmbeddingModelCreate) -> EmbeddingModelTable:
16
+ """Create a new EmbeddingModel in the database."""
17
+ db_embedding_model = EmbeddingModelTable.model_validate(embedding_model)
18
+ session.add(db_embedding_model)
19
+ session.commit()
20
+ session.refresh(db_embedding_model)
21
+ return db_embedding_model
22
+
23
+
24
+ def get_or_create(session: Session, embedding_model: EmbeddingModelCreate) -> EmbeddingModelTable:
25
+ """Retrieve an existing EmbeddingModel by hash or create a new one if it does not exist."""
26
+ db_model = get_by_model_hash(
27
+ session=session, embedding_model_hash=embedding_model.embedding_model_hash
28
+ )
29
+ if db_model is None:
30
+ return create(session=session, embedding_model=embedding_model)
31
+
32
+ # Validate that the existing model matches the provided data.
33
+ if (
34
+ db_model.name != embedding_model.name
35
+ or db_model.parameter_count_in_mb != embedding_model.parameter_count_in_mb
36
+ or db_model.embedding_dimension != embedding_model.embedding_dimension
37
+ ):
38
+ raise ValueError(
39
+ "An embedding model with the same hash but different parameters already exists."
40
+ )
41
+ return db_model
42
+
43
+
44
+ def get_all_by_dataset_id(session: Session, dataset_id: UUID) -> list[EmbeddingModelTable]:
45
+ """Retrieve all embedding models."""
46
+ embedding_models = session.exec(
47
+ select(EmbeddingModelTable)
48
+ .where(EmbeddingModelTable.dataset_id == dataset_id)
49
+ .order_by(col(EmbeddingModelTable.created_at).asc())
50
+ ).all()
51
+ return list(embedding_models)
52
+
53
+
54
+ def get_by_id(session: Session, embedding_model_id: UUID) -> EmbeddingModelTable | None:
55
+ """Retrieve a single embedding model by ID."""
56
+ return session.exec(
57
+ select(EmbeddingModelTable).where(
58
+ EmbeddingModelTable.embedding_model_id == embedding_model_id
59
+ )
60
+ ).one_or_none()
61
+
62
+
63
+ def get_by_model_hash(session: Session, embedding_model_hash: str) -> EmbeddingModelTable | None:
64
+ """Retrieve a single embedding model by hash."""
65
+ return session.exec(
66
+ select(EmbeddingModelTable).where(
67
+ EmbeddingModelTable.embedding_model_hash == embedding_model_hash
68
+ )
69
+ ).one_or_none()
70
+
71
+
72
+ def get_by_name(
73
+ session: Session, dataset_id: UUID, embedding_model_name: str | None
74
+ ) -> EmbeddingModelTable:
75
+ """Helper function to resolve the embedding model name to its ID.
76
+
77
+ Args:
78
+ session: The database session.
79
+ dataset_id: The ID of the dataset.
80
+ embedding_model_name: The name of the embedding model.
81
+ If None, expects the dataset to have exactly one embedding model and
82
+ returns it. Otherwise raises a ValueError.
83
+ If set, expects the dataset to have an embedding model with the given name.
84
+ Otherwise raises a ValueError.
85
+
86
+ Returns:
87
+ The embedding model with the given name.
88
+ """
89
+ embedding_models = get_all_by_dataset_id(
90
+ session=session,
91
+ dataset_id=dataset_id,
92
+ )
93
+
94
+ if embedding_model_name is None:
95
+ if len(embedding_models) != 1:
96
+ raise ValueError(
97
+ f"Expected exactly one embedding model, "
98
+ f"but found {len(embedding_models)} with names "
99
+ f"{[model.name for model in embedding_models]}."
100
+ )
101
+ return embedding_models[0]
102
+
103
+ embedding_model_with_name = next(
104
+ (model for model in embedding_models if model.name == embedding_model_name), None
105
+ )
106
+ if embedding_model_with_name is None:
107
+ raise ValueError(f"Embedding model with name `{embedding_model_name}` not found.")
108
+
109
+ return embedding_model_with_name
110
+
111
+
112
+ def delete(session: Session, embedding_model_id: UUID) -> bool:
113
+ """Delete an embedding model."""
114
+ embedding_model = get_by_id(session=session, embedding_model_id=embedding_model_id)
115
+ if not embedding_model:
116
+ return False
117
+
118
+ session.delete(embedding_model)
119
+ session.commit()
120
+ return True
@@ -0,0 +1,50 @@
1
+ """Utility functions for building database queries."""
2
+ # TODO(Michal, 11/2025): Move to image_resolver once DatasetTable.get_samples() is removed.
3
+
4
+ from typing import Optional
5
+
6
+ from pydantic import BaseModel
7
+
8
+ from lightly_studio.models.image import ImageTable
9
+ from lightly_studio.resolvers.sample_resolver.sample_filter import SampleFilter
10
+ from lightly_studio.type_definitions import QueryType
11
+
12
+
13
+ class FilterDimensions(BaseModel):
14
+ """Encapsulates dimension-based filter parameters for querying samples."""
15
+
16
+ min: Optional[int] = None
17
+ max: Optional[int] = None
18
+
19
+
20
+ class ImageFilter(BaseModel):
21
+ """Encapsulates filter parameters for querying samples."""
22
+
23
+ sample_filter: Optional[SampleFilter] = None
24
+ width: Optional[FilterDimensions] = None
25
+ height: Optional[FilterDimensions] = None
26
+
27
+ def apply(self, query: QueryType) -> QueryType:
28
+ """Apply the filters to the given query."""
29
+ # Apply sample filters to the query.
30
+ if self.sample_filter is not None:
31
+ query = self.sample_filter.apply(query)
32
+
33
+ # Apply dimension-based filters to the query.
34
+ query = self._apply_dimension_filters(query)
35
+
36
+ # Return the modified query.
37
+ return query # noqa: RET504
38
+
39
+ def _apply_dimension_filters(self, query: QueryType) -> QueryType:
40
+ if self.width:
41
+ if self.width.min is not None:
42
+ query = query.where(ImageTable.width >= self.width.min)
43
+ if self.width.max is not None:
44
+ query = query.where(ImageTable.width <= self.width.max)
45
+ if self.height:
46
+ if self.height.min is not None:
47
+ query = query.where(ImageTable.height >= self.height.min)
48
+ if self.height.max is not None:
49
+ query = query.where(ImageTable.height <= self.height.max)
50
+ return query
@@ -0,0 +1,21 @@
1
+ """Resolvers for database operations."""
2
+
3
+ from lightly_studio.resolvers.image_resolver.create_many import create_many
4
+ from lightly_studio.resolvers.image_resolver.delete import delete
5
+ from lightly_studio.resolvers.image_resolver.filter_new_paths import filter_new_paths
6
+ from lightly_studio.resolvers.image_resolver.get_all_by_dataset_id import get_all_by_dataset_id
7
+ from lightly_studio.resolvers.image_resolver.get_by_id import get_by_id
8
+ from lightly_studio.resolvers.image_resolver.get_dimension_bounds import get_dimension_bounds
9
+ from lightly_studio.resolvers.image_resolver.get_many_by_id import get_many_by_id
10
+ from lightly_studio.resolvers.image_resolver.get_samples_excluding import get_samples_excluding
11
+
12
+ __all__ = [
13
+ "create_many",
14
+ "delete",
15
+ "filter_new_paths",
16
+ "get_all_by_dataset_id",
17
+ "get_by_id",
18
+ "get_dimension_bounds",
19
+ "get_many_by_id",
20
+ "get_samples_excluding",
21
+ ]
@@ -0,0 +1,52 @@
1
+ """Implementation of create functions for images."""
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.image import ImageCreate, ImageTable
11
+ from lightly_studio.models.sample import SampleCreate
12
+ from lightly_studio.resolvers import dataset_resolver, sample_resolver
13
+
14
+
15
+ class ImageCreateHelper(ImageCreate):
16
+ """Helper class to create ImageTable with sample_id."""
17
+
18
+ sample_id: UUID
19
+ dataset_id: UUID
20
+
21
+
22
+ def create_many(session: Session, dataset_id: UUID, samples: list[ImageCreate]) -> list[UUID]:
23
+ """Create multiple samples in a single database commit.
24
+
25
+ Returns the list of created sample IDs that matches the order of input samples.
26
+ """
27
+ dataset_resolver.check_dataset_type(
28
+ session=session,
29
+ dataset_id=dataset_id,
30
+ expected_type=SampleType.IMAGE,
31
+ )
32
+ sample_ids = sample_resolver.create_many(
33
+ session=session,
34
+ samples=[SampleCreate(dataset_id=dataset_id) for _ in samples],
35
+ )
36
+ # Bulk create ImageTable entries using the generated sample_ids.
37
+ db_images = [
38
+ ImageTable.model_validate(
39
+ ImageCreateHelper(
40
+ file_name=sample.file_name,
41
+ width=sample.width,
42
+ height=sample.height,
43
+ dataset_id=dataset_id,
44
+ file_path_abs=sample.file_path_abs,
45
+ sample_id=sample_id,
46
+ )
47
+ )
48
+ for sample_id, sample in zip(sample_ids, samples)
49
+ ]
50
+ session.bulk_save_objects(db_images)
51
+ session.commit()
52
+ return sample_ids
@@ -0,0 +1,20 @@
1
+ """Implementation of delete function for images."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlmodel import Session
8
+
9
+ from lightly_studio.resolvers import image_resolver
10
+
11
+
12
+ def delete(session: Session, sample_id: UUID) -> bool:
13
+ """Delete a sample."""
14
+ sample = image_resolver.get_by_id(session=session, sample_id=sample_id)
15
+ if not sample:
16
+ return False
17
+
18
+ session.delete(sample)
19
+ session.commit()
20
+ return True
@@ -0,0 +1,23 @@
1
+ """Implementation of filter_new_paths function for images."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from sqlmodel import Session, col, select
6
+
7
+ from lightly_studio.models.image import ImageTable
8
+
9
+
10
+ def filter_new_paths(session: Session, file_paths_abs: list[str]) -> tuple[list[str], list[str]]:
11
+ """Return a) file_path_abs that do not already exist in the database and b) those that do."""
12
+ existing_file_paths_abs = set(
13
+ session.exec(
14
+ select(col(ImageTable.file_path_abs)).where(
15
+ col(ImageTable.file_path_abs).in_(file_paths_abs)
16
+ )
17
+ ).all()
18
+ )
19
+ file_paths_abs_set = set(file_paths_abs)
20
+ return (
21
+ list(file_paths_abs_set - existing_file_paths_abs), # paths that are not in the DB
22
+ list(file_paths_abs_set & existing_file_paths_abs), # paths that are already in the DB
23
+ )
@@ -0,0 +1,117 @@
1
+ """Implementation of get_all_by_dataset_id function for images."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from uuid import UUID
7
+
8
+ from pydantic import BaseModel
9
+ from sqlalchemy.orm import joinedload, selectinload
10
+ from sqlmodel import Session, col, func, select
11
+
12
+ from lightly_studio.api.routes.api.validators import Paginated
13
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
14
+ from lightly_studio.models.embedding_model import EmbeddingModelTable
15
+ from lightly_studio.models.image import ImageTable
16
+ from lightly_studio.models.sample import SampleTable
17
+ from lightly_studio.models.sample_embedding import SampleEmbeddingTable
18
+ from lightly_studio.resolvers.image_filter import ImageFilter
19
+
20
+
21
+ class GetAllSamplesByDatasetIdResult(BaseModel):
22
+ """Result of getting all samples."""
23
+
24
+ samples: Sequence[ImageTable]
25
+ total_count: int
26
+ next_cursor: int | None = None
27
+
28
+
29
+ def get_all_by_dataset_id( # noqa: PLR0913
30
+ session: Session,
31
+ dataset_id: UUID,
32
+ pagination: Paginated | None = None,
33
+ filters: ImageFilter | None = None,
34
+ text_embedding: list[float] | None = None,
35
+ sample_ids: list[UUID] | None = None,
36
+ ) -> GetAllSamplesByDatasetIdResult:
37
+ """Retrieve samples for a specific dataset with optional filtering."""
38
+ samples_query = (
39
+ select(ImageTable)
40
+ .options(
41
+ selectinload(ImageTable.sample).options(
42
+ joinedload(SampleTable.tags),
43
+ # Ignore type checker error below as it's a false positive caused by TYPE_CHECKING.
44
+ joinedload(SampleTable.metadata_dict), # type: ignore[arg-type]
45
+ selectinload(SampleTable.captions),
46
+ selectinload(SampleTable.annotations).options(
47
+ joinedload(AnnotationBaseTable.annotation_label),
48
+ joinedload(AnnotationBaseTable.object_detection_details),
49
+ joinedload(AnnotationBaseTable.instance_segmentation_details),
50
+ joinedload(AnnotationBaseTable.semantic_segmentation_details),
51
+ selectinload(AnnotationBaseTable.tags),
52
+ ),
53
+ ),
54
+ )
55
+ .join(ImageTable.sample)
56
+ .where(SampleTable.dataset_id == dataset_id)
57
+ )
58
+ total_count_query = (
59
+ select(func.count())
60
+ .select_from(ImageTable)
61
+ .join(ImageTable.sample)
62
+ .where(SampleTable.dataset_id == dataset_id)
63
+ )
64
+
65
+ if filters:
66
+ samples_query = filters.apply(samples_query)
67
+ total_count_query = filters.apply(total_count_query)
68
+
69
+ # TODO(Michal, 06/2025): Consider adding sample_ids to the filters.
70
+ if sample_ids:
71
+ samples_query = samples_query.where(col(ImageTable.sample_id).in_(sample_ids))
72
+ total_count_query = total_count_query.where(col(ImageTable.sample_id).in_(sample_ids))
73
+
74
+ if text_embedding:
75
+ # Fetch the first embedding_model_id for the given dataset_id
76
+ embedding_model_id = session.exec(
77
+ select(EmbeddingModelTable.embedding_model_id)
78
+ .where(EmbeddingModelTable.dataset_id == dataset_id)
79
+ .limit(1)
80
+ ).first()
81
+ if embedding_model_id:
82
+ # Join with SampleEmbedding table to access embeddings
83
+ samples_query = (
84
+ samples_query.join(
85
+ SampleEmbeddingTable,
86
+ col(ImageTable.sample_id) == col(SampleEmbeddingTable.sample_id),
87
+ )
88
+ .where(SampleEmbeddingTable.embedding_model_id == embedding_model_id)
89
+ .order_by(
90
+ func.list_cosine_distance(
91
+ SampleEmbeddingTable.embedding,
92
+ text_embedding,
93
+ )
94
+ )
95
+ )
96
+ total_count_query = total_count_query.join(
97
+ SampleEmbeddingTable,
98
+ col(ImageTable.sample_id) == col(SampleEmbeddingTable.sample_id),
99
+ ).where(SampleEmbeddingTable.embedding_model_id == embedding_model_id)
100
+ else:
101
+ samples_query = samples_query.order_by(col(ImageTable.file_path_abs).asc())
102
+
103
+ # Apply pagination if provided
104
+ if pagination is not None:
105
+ samples_query = samples_query.offset(pagination.offset).limit(pagination.limit)
106
+
107
+ total_count = session.exec(total_count_query).one()
108
+
109
+ next_cursor = None
110
+ if pagination and pagination.offset + pagination.limit < total_count:
111
+ next_cursor = pagination.offset + pagination.limit
112
+
113
+ return GetAllSamplesByDatasetIdResult(
114
+ samples=session.exec(samples_query).all(),
115
+ total_count=total_count,
116
+ next_cursor=next_cursor,
117
+ )
@@ -0,0 +1,14 @@
1
+ """Implementation of get_by_id function for images."""
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.image import ImageTable
10
+
11
+
12
+ def get_by_id(session: Session, sample_id: UUID) -> ImageTable | None:
13
+ """Retrieve a single sample by ID."""
14
+ return session.exec(select(ImageTable).where(ImageTable.sample_id == sample_id)).one_or_none()