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,75 @@
1
+ """Implementation of get_dimension_bounds function for images."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlmodel import Session, col, func, select
8
+ from sqlmodel.sql.expression import Select
9
+
10
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
11
+ from lightly_studio.models.annotation_label import AnnotationLabelTable
12
+ from lightly_studio.models.image import ImageTable
13
+ from lightly_studio.models.sample import SampleTable
14
+ from lightly_studio.models.tag import TagTable
15
+
16
+
17
+ def get_dimension_bounds(
18
+ session: Session,
19
+ dataset_id: UUID,
20
+ annotation_label_ids: list[UUID] | None = None,
21
+ tag_ids: list[UUID] | None = None,
22
+ ) -> dict[str, int]:
23
+ """Get min and max dimensions of samples in a dataset."""
24
+ # Prepare the base query for dimensions
25
+ query: Select[tuple[int | None, int | None, int | None, int | None]] = select(
26
+ func.min(ImageTable.width).label("min_width"),
27
+ func.max(ImageTable.width).label("max_width"),
28
+ func.min(ImageTable.height).label("min_height"),
29
+ func.max(ImageTable.height).label("max_height"),
30
+ )
31
+ query = query.join(ImageTable.sample)
32
+
33
+ if annotation_label_ids:
34
+ # Subquery to filter samples matching all annotation labels
35
+ label_filter = (
36
+ select(ImageTable.sample_id)
37
+ .join(ImageTable.sample)
38
+ .join(
39
+ AnnotationBaseTable,
40
+ col(ImageTable.sample_id) == col(AnnotationBaseTable.parent_sample_id),
41
+ )
42
+ .join(
43
+ AnnotationLabelTable,
44
+ col(AnnotationBaseTable.annotation_label_id)
45
+ == col(AnnotationLabelTable.annotation_label_id),
46
+ )
47
+ .where(
48
+ SampleTable.dataset_id == dataset_id,
49
+ col(AnnotationLabelTable.annotation_label_id).in_(annotation_label_ids),
50
+ )
51
+ .group_by(col(ImageTable.sample_id))
52
+ .having(
53
+ func.count(col(AnnotationLabelTable.annotation_label_id).distinct())
54
+ == len(annotation_label_ids)
55
+ )
56
+ )
57
+ # Filter the dimension query based on the subquery
58
+ query = query.where(col(ImageTable.sample_id).in_(label_filter))
59
+ else:
60
+ # If no labels specified, filter dimensions
61
+ # for all samples in the dataset
62
+ query = query.where(SampleTable.dataset_id == dataset_id)
63
+
64
+ if tag_ids:
65
+ query = (
66
+ query.join(SampleTable.tags)
67
+ .where(SampleTable.tags.any(col(TagTable.tag_id).in_(tag_ids)))
68
+ .distinct()
69
+ )
70
+
71
+ # Note: We use SQLAlchemy's session.execute instead of SQLModel's
72
+ # ession.exec to be able to fetch the columns with names with the
73
+ # `mappings()` method.
74
+ result = session.execute(query).mappings().one()
75
+ return {key: value for key, value in result.items() if value is not None}
@@ -0,0 +1,22 @@
1
+ """Implementation of get_many_by_id function for images."""
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.image import ImageTable
10
+
11
+
12
+ def get_many_by_id(session: Session, sample_ids: list[UUID]) -> list[ImageTable]:
13
+ """Retrieve multiple samples by their IDs.
14
+
15
+ Output order matches the input order.
16
+ """
17
+ results = session.exec(
18
+ select(ImageTable).where(col(ImageTable.sample_id).in_(sample_ids))
19
+ ).all()
20
+ # Return samples in the same order as the input IDs
21
+ sample_map = {sample.sample_id: sample for sample in results}
22
+ return [sample_map[id_] for id_ in sample_ids if id_ in sample_map]
@@ -0,0 +1,43 @@
1
+ """Implementation of get_samples_excluding function for images."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from uuid import UUID
7
+
8
+ from sqlmodel import Session, col, func, select
9
+
10
+ from lightly_studio.models.image import ImageTable
11
+ from lightly_studio.models.sample import SampleTable
12
+
13
+
14
+ def get_samples_excluding(
15
+ session: Session,
16
+ dataset_id: UUID,
17
+ excluded_sample_ids: list[UUID],
18
+ limit: int | None = None,
19
+ ) -> Sequence[ImageTable]:
20
+ """Get random samples excluding specified sample IDs.
21
+
22
+ Args:
23
+ session: The database session.
24
+ dataset_id: The dataset ID to filter by.
25
+ excluded_sample_ids: List of sample IDs to exclude from the result.
26
+ limit: Maximum number of samples to return.
27
+ If None, returns all matches.
28
+
29
+ Returns:
30
+ List of samples not associated with the excluded IDs.
31
+ """
32
+ query = (
33
+ select(ImageTable)
34
+ .join(ImageTable.sample)
35
+ .where(SampleTable.dataset_id == dataset_id)
36
+ .where(col(SampleTable.sample_id).not_in(excluded_sample_ids))
37
+ .order_by(func.random())
38
+ )
39
+
40
+ if limit is not None:
41
+ query = query.limit(limit)
42
+
43
+ return session.exec(query).all()
@@ -0,0 +1,15 @@
1
+ """Metadata resolver module."""
2
+
3
+ from lightly_studio.resolvers.metadata_resolver.sample import (
4
+ bulk_update_metadata,
5
+ get_by_sample_id,
6
+ get_value_for_sample,
7
+ set_value_for_sample,
8
+ )
9
+
10
+ __all__ = [
11
+ "bulk_update_metadata",
12
+ "get_by_sample_id",
13
+ "get_value_for_sample",
14
+ "set_value_for_sample",
15
+ ]
@@ -0,0 +1,163 @@
1
+ """Generic metadata filtering utilities."""
2
+
3
+ import json
4
+ import re
5
+ from typing import Any, Dict, List, Literal, Protocol, Type, TypeVar
6
+
7
+ from pydantic import BaseModel
8
+ from sqlalchemy import text
9
+
10
+ from lightly_studio.type_definitions import QueryType
11
+
12
+ # Type variables for generic constraints
13
+ T = TypeVar("T", bound=BaseModel)
14
+ M = TypeVar("M", bound="HasMetadata")
15
+
16
+ # Valid operators for metadata filtering
17
+ MetadataOperator = Literal[">", "<", "==", ">=", "<=", "!="]
18
+
19
+ # Default metadata column name
20
+ METADATA_COLUMN = "metadata.data"
21
+
22
+
23
+ class HasMetadata(Protocol):
24
+ """Protocol for models that have metadata."""
25
+
26
+ data: Dict[str, Any]
27
+ metadata_schema: Dict[str, str]
28
+
29
+
30
+ class MetadataFilter(BaseModel):
31
+ """Encapsulates a single metadata filter condition."""
32
+
33
+ key: str
34
+ op: MetadataOperator
35
+ value: Any
36
+
37
+ def model_post_init(self, __context: Any) -> None:
38
+ """Post-initialization hook to serialize string values."""
39
+ # Pre-serialize string values for JSON comparison
40
+ if isinstance(self.value, str):
41
+ # Avoid double-serialization
42
+ try:
43
+ json.loads(self.value)
44
+ # Already serialized, don't serialize again
45
+ except (json.JSONDecodeError, TypeError):
46
+ # Not serialized, serialize it
47
+ self.value = json.dumps(self.value)
48
+
49
+
50
+ class Metadata:
51
+ """Helper class for creating metadata filters with operator syntax."""
52
+
53
+ def __init__(self, key: str) -> None:
54
+ """Initialize metadata filter with key."""
55
+ self.key = key
56
+
57
+ def __gt__(self, value: Any) -> MetadataFilter:
58
+ """Create greater than filter."""
59
+ return MetadataFilter(key=self.key, op=">", value=value)
60
+
61
+ def __lt__(self, value: Any) -> MetadataFilter:
62
+ """Create less than filter."""
63
+ return MetadataFilter(key=self.key, op="<", value=value)
64
+
65
+ def __ge__(self, value: Any) -> MetadataFilter:
66
+ """Create greater than or equal filter."""
67
+ return MetadataFilter(key=self.key, op=">=", value=value)
68
+
69
+ def __le__(self, value: Any) -> MetadataFilter:
70
+ """Create less than or equal filter."""
71
+ return MetadataFilter(key=self.key, op="<=", value=value)
72
+
73
+ def __eq__(self, value: Any) -> MetadataFilter: # type: ignore
74
+ """Create equality filter."""
75
+ return MetadataFilter(key=self.key, op="==", value=value)
76
+
77
+ def __ne__(self, value: Any) -> MetadataFilter: # type: ignore
78
+ """Create not equal filter."""
79
+ return MetadataFilter(key=self.key, op="!=", value=value)
80
+
81
+
82
+ def _sanitize_param_name(field: str) -> str:
83
+ """Sanitize field name for use as SQL parameter name.
84
+
85
+ Args:
86
+ field: The field name (may contain dots for nested paths).
87
+
88
+ Returns:
89
+ A sanitized parameter name safe for SQL binding.
90
+ """
91
+ # Replace dots and other problematic characters with underscores
92
+ return re.sub(r"[^a-zA-Z0-9_]", "_", field)
93
+
94
+
95
+ def apply_metadata_filters(
96
+ query: QueryType,
97
+ metadata_filters: List[MetadataFilter],
98
+ *,
99
+ metadata_model: Type[M],
100
+ metadata_join_condition: Any,
101
+ ) -> QueryType:
102
+ """Apply metadata filters to a query.
103
+
104
+ Args:
105
+ query: The base query to filter.
106
+ metadata_filters: The list of metadata filters to apply.
107
+ metadata_model: The metadata table/model class.
108
+ metadata_join_condition: The join condition between the main table
109
+ and metadata table.
110
+
111
+ Returns:
112
+ The filtered query.
113
+
114
+ Raises:
115
+ ValueError: If any field name contains invalid characters.
116
+
117
+ Example:
118
+ ```python
119
+ # Simple filters (AND by default)
120
+ query = apply_metadata_filters(
121
+ query,
122
+ metadata_filters=[
123
+ Metadata("temperature") > 25,
124
+ Metadata("location") == "city",
125
+ ],
126
+ metadata_model=SampleMetadataTable,
127
+ metadata_join_condition=SampleMetadataTable.sample_id ==
128
+ ImageTable.sample_id,
129
+ )
130
+ ```
131
+ """
132
+ if not metadata_filters:
133
+ return query
134
+
135
+ # Apply the filters using JSON extraction
136
+ query = query.join(
137
+ metadata_model,
138
+ metadata_join_condition,
139
+ )
140
+
141
+ for i, meta_filter in enumerate(metadata_filters):
142
+ field = meta_filter.key
143
+ value = meta_filter.value
144
+ op = meta_filter.op
145
+
146
+ json_path = "$." + field
147
+ # Add unique identifier to parameter name to avoid conflicts
148
+ param_name = f"{_sanitize_param_name(field)}_{i}"
149
+
150
+ # Build the condition based on value type
151
+ if isinstance(value, (int, float)):
152
+ # For numeric values, use json_extract with CAST
153
+ condition = (
154
+ f"CAST(json_extract({METADATA_COLUMN}, '{json_path}') AS FLOAT) {op} :{param_name}"
155
+ )
156
+ else:
157
+ # For string values, use json_extract with parameter binding
158
+ condition = f"json_extract({METADATA_COLUMN}, '{json_path}') {op} :{param_name}"
159
+
160
+ # Apply the condition (same for both types)
161
+ query = query.where(text(condition).bindparams(**{param_name: value}))
162
+
163
+ return query
@@ -0,0 +1,21 @@
1
+ """Resolvers for metadata operations."""
2
+
3
+ from .bulk_update_metadata import (
4
+ bulk_update_metadata,
5
+ )
6
+ from .get_by_sample_id import (
7
+ get_by_sample_id,
8
+ )
9
+ from .get_value_for_sample import (
10
+ get_value_for_sample,
11
+ )
12
+ from .set_value_for_sample import (
13
+ set_value_for_sample,
14
+ )
15
+
16
+ __all__ = [
17
+ "bulk_update_metadata",
18
+ "get_by_sample_id",
19
+ "get_value_for_sample",
20
+ "set_value_for_sample",
21
+ ]
@@ -0,0 +1,46 @@
1
+ """Resolver for operations for setting metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from uuid import UUID
7
+
8
+ from sqlmodel import Session, col, select
9
+
10
+ from lightly_studio.models.metadata import SampleMetadataTable
11
+
12
+
13
+ def bulk_update_metadata(
14
+ session: Session,
15
+ sample_metadata: list[tuple[UUID, dict[str, Any]]],
16
+ ) -> None:
17
+ """Bulk insert or update metadata for multiple samples.
18
+
19
+ If a sample does not have metadata, a new metadata row is created.
20
+ If a sample already has metadata, the new key-value pairs are merged with the existing metadata.
21
+
22
+ Args:
23
+ session: The database session.
24
+ sample_metadata: List of (sample_id, metadata_dict) tuples.
25
+ """
26
+ # TODO(Mihnea, 10/2025): Consider using SQLAlchemy's bulk operations
27
+ # (Session.bulk_insert/update_mappings) if performance becomes a bottleneck.
28
+ if not sample_metadata:
29
+ return
30
+
31
+ # Get all existing metadata rows for the given sample IDs.
32
+ sample_ids = [s[0] for s in sample_metadata]
33
+ existing_metadata = session.exec(
34
+ select(SampleMetadataTable).where(col(SampleMetadataTable.sample_id).in_(sample_ids))
35
+ ).all()
36
+ sample_id_to_existing_metadata = {meta.sample_id: meta for meta in existing_metadata}
37
+
38
+ for sample_id, new_metadata in sample_metadata:
39
+ metadata = sample_id_to_existing_metadata.get(
40
+ sample_id, SampleMetadataTable(sample_id=sample_id)
41
+ )
42
+ for key, value in new_metadata.items():
43
+ metadata.set_value(key, value)
44
+ session.add(metadata)
45
+
46
+ session.commit()
@@ -0,0 +1,24 @@
1
+ """Resolver for operations for retrieving metadata."""
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.metadata import SampleMetadataTable
10
+
11
+
12
+ def get_by_sample_id(session: Session, sample_id: UUID) -> SampleMetadataTable | None:
13
+ """Retrieve the metadata object for a given sample.
14
+
15
+ Args:
16
+ session: The database session.
17
+ sample_id: The sample's UUID.
18
+
19
+ Returns:
20
+ The CustomMetadataTable instance or None if not found.
21
+ """
22
+ return session.exec(
23
+ select(SampleMetadataTable).where(SampleMetadataTable.sample_id == sample_id)
24
+ ).one_or_none()
@@ -0,0 +1,104 @@
1
+ """Resolver for operations for retrieving metadata info."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from sqlalchemy import Float, func
8
+ from sqlmodel import Session, col, select
9
+
10
+ from lightly_studio.models.metadata import (
11
+ MetadataInfoView,
12
+ SampleMetadataTable,
13
+ )
14
+ from lightly_studio.models.sample import SampleTable
15
+
16
+
17
+ def get_all_metadata_keys_and_schema(
18
+ session: Session,
19
+ dataset_id: UUID,
20
+ ) -> list[MetadataInfoView]:
21
+ """Get all unique metadata keys and their schema for a dataset.
22
+
23
+ Args:
24
+ session: The database session.
25
+ dataset_id: The dataset's UUID.
26
+
27
+ Returns:
28
+ List of dicts with 'name', 'type', and optionally 'min'/'max' for numerical types.
29
+ """
30
+ # Query all metadata_schema dicts for samples in the dataset
31
+ rows = session.exec(
32
+ select(SampleMetadataTable.metadata_schema)
33
+ .select_from(SampleTable)
34
+ .join(
35
+ SampleMetadataTable,
36
+ col(SampleMetadataTable.sample_id) == col(SampleTable.sample_id),
37
+ )
38
+ .where(SampleTable.dataset_id == dataset_id)
39
+ ).all()
40
+ # Merge all schemas
41
+ merged: dict[str, str] = {}
42
+ for schema_dict in rows:
43
+ merged.update(schema_dict)
44
+
45
+ # Get min and max values for numerical metadata
46
+ result = []
47
+ for key, metadata_type in merged.items():
48
+ metadata_info = MetadataInfoView(name=key, type=metadata_type)
49
+
50
+ # Add min and max for numerical types
51
+ if metadata_type in ["integer", "float"]:
52
+ min_max_values = _get_metadata_min_max_values(session, dataset_id, key, metadata_type)
53
+ if min_max_values:
54
+ metadata_info.min = min_max_values[0]
55
+ metadata_info.max = min_max_values[1]
56
+
57
+ result.append(metadata_info)
58
+
59
+ return result
60
+
61
+
62
+ def _get_metadata_min_max_values(
63
+ session: Session,
64
+ dataset_id: UUID,
65
+ metadata_key: str,
66
+ metadata_type: str,
67
+ ) -> tuple[int, int] | tuple[float, float] | None:
68
+ """Get min and max values for a specific numerical metadata key.
69
+
70
+ Args:
71
+ session: The database session.
72
+ dataset_id: The dataset's UUID.
73
+ metadata_key: The metadata key to get min/max for.
74
+ metadata_type: The metadata type ("integer" or "float").
75
+
76
+ Returns:
77
+ Tuple with 'min' and 'max' values, or None if no values found.
78
+ """
79
+ # Build JSON path for the metadata key.
80
+ json_path = f"$.{metadata_key}"
81
+
82
+ query = (
83
+ select(
84
+ func.min(func.cast(func.json_extract(SampleMetadataTable.data, json_path), Float)),
85
+ func.max(func.cast(func.json_extract(SampleMetadataTable.data, json_path), Float)),
86
+ )
87
+ .select_from(SampleTable)
88
+ .join(SampleMetadataTable, col(SampleMetadataTable.sample_id) == col(SampleTable.sample_id))
89
+ .where(
90
+ SampleTable.dataset_id == dataset_id,
91
+ func.json_extract(SampleMetadataTable.data, json_path).is_not(None),
92
+ )
93
+ )
94
+
95
+ result = session.exec(query).first()
96
+
97
+ if result and result[0] is not None and result[1] is not None:
98
+ # Convert to appropriate type
99
+ if metadata_type == "integer":
100
+ return int(result[0]), int(result[1])
101
+ if metadata_type == "float":
102
+ return float(result[0]), float(result[1])
103
+
104
+ return None
@@ -0,0 +1,27 @@
1
+ """Resolver for operations for retrieving metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from uuid import UUID
7
+
8
+ from sqlmodel import Session
9
+
10
+ from .get_by_sample_id import get_by_sample_id
11
+
12
+
13
+ def get_value_for_sample(session: Session, sample_id: UUID, key: str) -> Any | None:
14
+ """Get a specific metadata value for a sample.
15
+
16
+ Args:
17
+ session: The database session.
18
+ sample_id: The sample's UUID.
19
+ key: The metadata key.
20
+
21
+ Returns:
22
+ The value for the given key, or None if not found.
23
+ """
24
+ metadata = get_by_sample_id(session=session, sample_id=sample_id)
25
+ if metadata is None:
26
+ return None
27
+ return metadata.data.get(key)
@@ -0,0 +1,53 @@
1
+ """Resolver for operations for setting metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from uuid import UUID
7
+
8
+ from sqlmodel import Session
9
+
10
+ from lightly_studio.models.metadata import (
11
+ SampleMetadataTable,
12
+ )
13
+ from lightly_studio.resolvers.metadata_resolver.sample.get_by_sample_id import (
14
+ get_by_sample_id,
15
+ )
16
+
17
+
18
+ def set_value_for_sample(
19
+ session: Session,
20
+ sample_id: UUID,
21
+ key: str,
22
+ value: Any,
23
+ ) -> SampleMetadataTable:
24
+ """Set a specific metadata value for a sample.
25
+
26
+ Args:
27
+ session: The database session.
28
+ sample_id: The sample's UUID.
29
+ key: The metadata key.
30
+ value: The value to set.
31
+
32
+ Returns:
33
+ The updated CustomMetadataTable instance.
34
+
35
+ Raises:
36
+ ValueError: If the value type doesn't match the schema.
37
+ """
38
+ metadata = get_by_sample_id(session=session, sample_id=sample_id)
39
+ if metadata is None:
40
+ # Create new metadata row if it does not exist
41
+ metadata = SampleMetadataTable(
42
+ sample_id=sample_id,
43
+ data={},
44
+ metadata_schema={},
45
+ )
46
+ session.add(metadata)
47
+
48
+ metadata.set_value(key, value)
49
+
50
+ # Commit changes and refresh the object
51
+ session.commit()
52
+ session.refresh(metadata)
53
+ return metadata