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,14 @@
1
+ from .boolean_expression import AND, NOT, OR
2
+ from .dataset_query import DatasetQuery
3
+ from .order_by import OrderByExpression, OrderByField
4
+ from .sample_field import SampleField
5
+
6
+ __all__ = [
7
+ "AND",
8
+ "NOT",
9
+ "OR",
10
+ "DatasetQuery",
11
+ "OrderByExpression",
12
+ "OrderByField",
13
+ "SampleField",
14
+ ]
@@ -0,0 +1,67 @@
1
+ """Classes for boolean expressions in dataset queries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from sqlalchemy import ColumnElement, and_, false, not_, or_, true
6
+
7
+ from lightly_studio.core.dataset_query.match_expression import MatchExpression
8
+
9
+
10
+ class AND(MatchExpression):
11
+ """Logical AND operation between other MatchExpression objects."""
12
+
13
+ def __init__(self, *terms: MatchExpression) -> None:
14
+ """Initialize AND Expression with multiple MatchExpression terms.
15
+
16
+ Args:
17
+ terms: The MatchExpression instances to combine with AND. They can also be nested.
18
+ """
19
+ self.terms = terms
20
+
21
+ def get(self) -> ColumnElement[bool]:
22
+ """Combine expressions of all terms using AND.
23
+
24
+ Returns:
25
+ The combined SQLAlchemy expression.
26
+ """
27
+ return and_(true(), *(term.get() for term in self.terms))
28
+
29
+
30
+ class OR(MatchExpression):
31
+ """Logical OR operation between other MatchExpression objects."""
32
+
33
+ def __init__(self, *terms: MatchExpression) -> None:
34
+ """Initialize OR Expression with multiple MatchExpression terms.
35
+
36
+ Args:
37
+ terms: The MatchExpression instances to combine with OR. They can also be nested.
38
+ """
39
+ self.terms = terms
40
+
41
+ def get(self) -> ColumnElement[bool]:
42
+ """Combine expressions of all terms using OR.
43
+
44
+ Returns:
45
+ The combined SQLAlchemy expression.
46
+ """
47
+ return or_(false(), *(term.get() for term in self.terms))
48
+
49
+
50
+ class NOT(MatchExpression):
51
+ """Logical NOT operation for a MatchExpression object."""
52
+
53
+ def __init__(self, term: MatchExpression) -> None:
54
+ """Initialize NOT Expression with a MatchExpression term.
55
+
56
+ Args:
57
+ term: The MatchExpression to be negated. It can also be nested.
58
+ """
59
+ self.term = term
60
+
61
+ def get(self) -> ColumnElement[bool]:
62
+ """Negate the expression of the term using NOT.
63
+
64
+ Returns:
65
+ The combined SQLAlchemy expression.
66
+ """
67
+ return not_(self.term.get())
@@ -0,0 +1,317 @@
1
+ """Dataset query utilities for filtering, ordering, and slicing samples."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterator
6
+
7
+ from sqlmodel import Session, select
8
+
9
+ from lightly_studio.core.dataset_query.match_expression import MatchExpression
10
+ from lightly_studio.core.dataset_query.order_by import OrderByExpression, OrderByField
11
+ from lightly_studio.core.dataset_query.sample_field import SampleField
12
+ from lightly_studio.core.image_sample import ImageSample
13
+ from lightly_studio.export.export_dataset import DatasetExport
14
+ from lightly_studio.models.dataset import DatasetTable
15
+ from lightly_studio.models.image import ImageTable
16
+ from lightly_studio.models.sample import SampleTable
17
+ from lightly_studio.resolvers import tag_resolver
18
+ from lightly_studio.selection.select import Selection
19
+
20
+ _SliceType = slice # to avoid shadowing built-in slice in type annotations
21
+
22
+
23
+ class DatasetQuery:
24
+ """Class for executing a query on a dataset.
25
+
26
+ # Filtering, ordering, and slicing samples in a dataset
27
+ Allows filtering, ordering, and slicing of samples in a dataset.
28
+ This class can be accessed via calling `.query()` on a Dataset instance.
29
+ ```python
30
+ dataset : Dataset = ...
31
+ query = dataset.query()
32
+ ```
33
+ The `match()`, `order_by()`, and `slice()` methods can be chained in this order.
34
+ You can also access the methods directly on the Dataset instance:
35
+ ```python
36
+ dataset.match(...) # shorthand for dataset.query().match(...)
37
+ ```
38
+
39
+ The object is converted to a SQL query that is lazily evaluated when iterating over
40
+ it or converting it to a list.
41
+
42
+ ## match() - Filtering samples
43
+ Filtering is done via the `match()` method.
44
+ ```python
45
+ from lightly_studio.core.dataset_query import SampleField
46
+
47
+ query_1 = dataset.query().match(SampleField.width > 100)
48
+ query_2 = dataset.query().match(SampleField.tags.contains('cat'))
49
+ ```
50
+ AND and OR operators are available for combining multiple conditions.
51
+ ```python
52
+ from lightly_studio.core.dataset_query import SampleField, AND, OR
53
+
54
+ query = dataset.query().match(
55
+ AND(
56
+ SampleField.height < 200,
57
+ OR(
58
+ SampleField.file_name == 'image.png',
59
+ SampleField.file_name == 'image2.png',
60
+ )
61
+ )
62
+ )
63
+ ```
64
+
65
+ ## order_by() - Ordering samples
66
+ The results can be ordered by using `order_by()`. For tie-breaking, multiple fields
67
+ can be provided. The first field has the highest priority. The default is
68
+ ascending order. To order in descending order, use `OrderByField(...).desc()`.
69
+ ```python
70
+ from lightly_studio.core.dataset_query import OrderByField, SampleField
71
+ query = query.order_by(
72
+ OrderByField(SampleField.width),
73
+ OrderByField(SampleField.file_name).desc()
74
+ )
75
+ ```
76
+
77
+ ## slice() - Slicing samples
78
+ Slicing can be applied via `slice()` or bracket notation.
79
+ ```python
80
+ query = query.slice(offset=10, limit=20)
81
+ query = query[10:30] # equivalent to slice(offset=10, limit=20)
82
+ ```
83
+
84
+ # Usage of the filtered, ordered and sliced query
85
+
86
+ ## Iterating and converting to list
87
+ Finally, the query can be executed by iterating over it or converting to a list.
88
+ ```python
89
+ for sample in query:
90
+ print(sample.file_name)
91
+ samples = query.to_list()
92
+ ```
93
+ The samples returned are instances of the `Sample` class. They are writable, and
94
+ changes to them will be persisted to the database.
95
+
96
+ ## Adding tags to matching samples
97
+ The filtered set can also be used to add a tag to all matching samples.
98
+ ```python
99
+ query.add_tag('my_tag')
100
+ ```
101
+
102
+ ## Selecting a subset of samples using smart selection
103
+ A Selection interface can be created from the current query results. It will only
104
+ select the samples matching the current query at the time of calling selection().
105
+ ```python
106
+ # Choosing 100 diverse samples from the 'cat' tag.
107
+ # Save them under the tag name "diverse_cats".
108
+ selection = dataset.query().match(
109
+ SampleField.tags.contains('cat')
110
+ ).selection()
111
+ selection.diverse(100, "diverse_cats")
112
+ ```
113
+
114
+ ## Exporting the query results
115
+ An export interface can be created from the current query results.
116
+ ```python
117
+ export = dataset.query().match(...).export()
118
+ export.to_coco_object_detections('/path/to/coco.json')
119
+ ```
120
+ """
121
+
122
+ def __init__(self, dataset: DatasetTable, session: Session) -> None:
123
+ """Initialize with dataset and database session.
124
+
125
+ Args:
126
+ dataset: The dataset to query.
127
+ session: Database session for executing queries.
128
+ """
129
+ self.dataset = dataset
130
+ self.session = session
131
+ self.match_expression: MatchExpression | None = None
132
+ self.order_by_expressions: list[OrderByExpression] | None = None
133
+ self._slice: _SliceType | None = None
134
+
135
+ def match(self, match_expression: MatchExpression) -> DatasetQuery:
136
+ """Store a field condition for filtering.
137
+
138
+ Args:
139
+ match_expression: Defines the filter.
140
+
141
+ Returns:
142
+ Self for method chaining.
143
+
144
+ Raises:
145
+ ValueError: If match() has already been called on this instance.
146
+ """
147
+ if self.match_expression is not None:
148
+ raise ValueError("match() can only be called once per DatasetQuery instance")
149
+
150
+ self.match_expression = match_expression
151
+ return self
152
+
153
+ def order_by(self, *order_by: OrderByExpression) -> DatasetQuery:
154
+ """Store ordering expressions.
155
+
156
+ Args:
157
+ order_by: One or more ordering expressions. They are applied in order.
158
+ E.g. first ordering by sample width and then by sample file_name will
159
+ only order the samples with the same sample width by file_name.
160
+
161
+ Returns:
162
+ Self for method chaining.
163
+
164
+ Raises:
165
+ ValueError: If order_by() has already been called on this instance.
166
+ """
167
+ if self.order_by_expressions:
168
+ raise ValueError("order_by() can only be called once per DatasetQuery instance")
169
+
170
+ self.order_by_expressions = list(order_by)
171
+ return self
172
+
173
+ def slice(self, offset: int = 0, limit: int | None = None) -> DatasetQuery:
174
+ """Apply offset and limit to results.
175
+
176
+ Args:
177
+ offset: Number of items to skip from beginning (default: 0).
178
+ limit: Maximum number of items to return (None = no limit).
179
+
180
+ Returns:
181
+ Self for method chaining.
182
+
183
+ Raises:
184
+ ValueError: If slice() has already been called on this instance.
185
+ """
186
+ if self._slice is not None:
187
+ raise ValueError("slice() can only be called once per DatasetQuery instance")
188
+
189
+ # Convert to slice object for internal consistency
190
+ stop = None if limit is None else offset + limit
191
+ self._slice = _SliceType(offset, stop)
192
+ return self
193
+
194
+ def __getitem__(self, key: _SliceType) -> DatasetQuery:
195
+ """Enable bracket notation for slicing.
196
+
197
+ Args:
198
+ key: A slice object (e.g., [10:20], [:50], [100:]).
199
+
200
+ Returns:
201
+ Self with slice applied.
202
+
203
+ Raises:
204
+ TypeError: If key is not a slice object.
205
+ ValueError: If slice contains unsupported features or conflicts with existing slice.
206
+ """
207
+ if not isinstance(key, _SliceType):
208
+ raise TypeError(
209
+ "DatasetQuery only supports slice notation, not integer indexing. "
210
+ "Use execute() to get results as a list for element access."
211
+ )
212
+
213
+ # Validate unsupported features
214
+ if key.step is not None:
215
+ raise ValueError("Strides are not supported. Use simple slices like [start:stop].")
216
+
217
+ if (key.start is not None and key.start < 0) or (key.stop is not None and key.stop < 0):
218
+ raise ValueError("Negative indices are not supported. Use positive indices only.")
219
+
220
+ # Check for conflicts with existing slice
221
+ if self._slice is not None:
222
+ raise ValueError("Cannot use bracket notation after slice() has been called.")
223
+
224
+ # Set slice and return self
225
+ self._slice = key
226
+ return self
227
+
228
+ def __iter__(self) -> Iterator[ImageSample]:
229
+ """Iterate over the query results.
230
+
231
+ Returns:
232
+ Iterator of Sample objects from the database.
233
+ """
234
+ # Build query
235
+ query = (
236
+ select(ImageTable)
237
+ .join(ImageTable.sample)
238
+ .where(SampleTable.dataset_id == self.dataset.dataset_id)
239
+ )
240
+
241
+ # Apply filter if present
242
+ if self.match_expression:
243
+ query = query.where(self.match_expression.get())
244
+
245
+ # Apply ordering
246
+ if self.order_by_expressions:
247
+ for order_by in self.order_by_expressions:
248
+ query = order_by.apply(query)
249
+ else:
250
+ # Order by SampleField.created_at by default.
251
+ default_order_by = OrderByField(SampleField.created_at)
252
+ query = default_order_by.apply(query)
253
+
254
+ # Apply slicing if present
255
+ if self._slice is not None:
256
+ start = self._slice.start or 0
257
+ query = query.offset(start)
258
+ if self._slice.stop is not None:
259
+ limit = max(self._slice.stop - start, 0)
260
+ query = query.limit(limit)
261
+
262
+ # Execute query and yield results
263
+ for image_table in self.session.exec(query):
264
+ yield ImageSample(inner=image_table)
265
+
266
+ def to_list(self) -> list[ImageSample]:
267
+ """Execute the query and return the results as a list.
268
+
269
+ Returns:
270
+ List of Sample objects from the database.
271
+ """
272
+ return list(self)
273
+
274
+ def add_tag(self, tag_name: str) -> None:
275
+ """Add a tag to all samples returned by this query.
276
+
277
+ First, creates the tag if it doesn't exist. Then applies the tag to all samples
278
+ that match the current query filters. Samples already having that tag are unchanged,
279
+ as the database prevents duplicates.
280
+
281
+ Args:
282
+ tag_name: Name of the tag to add to matching samples.
283
+ """
284
+ # Get or create the tag
285
+ tag = tag_resolver.get_or_create_sample_tag_by_name(
286
+ session=self.session, dataset_id=self.dataset.dataset_id, tag_name=tag_name
287
+ )
288
+
289
+ # Execute query to get matching samples
290
+ samples = self.to_list()
291
+ sample_ids = [sample.sample_id for sample in samples]
292
+
293
+ # Use resolver to bulk assign tag (handles validation and edge cases)
294
+ tag_resolver.add_sample_ids_to_tag_id(
295
+ session=self.session, tag_id=tag.tag_id, sample_ids=sample_ids
296
+ )
297
+
298
+ def selection(self) -> Selection:
299
+ """Selection interface for this query.
300
+
301
+ The returned Selection snapshots the current query results immediately.
302
+ Mutating the query after calling this method will therefore not affect
303
+ the samples used by that Selection instance.
304
+
305
+ Returns:
306
+ Selection interface operating on the current query result snapshot.
307
+ """
308
+ input_sample_ids = (sample.sample_id for sample in self)
309
+ return Selection(
310
+ dataset_id=self.dataset.dataset_id,
311
+ session=self.session,
312
+ input_sample_ids=input_sample_ids,
313
+ )
314
+
315
+ def export(self) -> DatasetExport:
316
+ """Return a DatasetExport instance which can export the dataset in various formats."""
317
+ return DatasetExport(session=self.session, samples=self)
@@ -0,0 +1,113 @@
1
+ """Base field classes for building dataset queries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from datetime import datetime
7
+ from typing import Any, Generic, TypeVar, Union
8
+
9
+ from sqlalchemy.orm import Mapped
10
+
11
+ from lightly_studio.core.dataset_query.field_expression import (
12
+ OrdinalFieldExpression,
13
+ StringFieldExpression,
14
+ )
15
+
16
+ T = TypeVar("T")
17
+
18
+
19
+ class Field(ABC):
20
+ """Abstract base class for all field types in dataset queries."""
21
+
22
+ @abstractmethod
23
+ def get_sqlmodel_field(self) -> Mapped[Any]:
24
+ """Get the database column or property that this field represents.
25
+
26
+ Returns:
27
+ The database column or property for queries.
28
+ """
29
+
30
+
31
+ class OrdinalField(Field, Generic[T]):
32
+ """Generic field for ordinal values that support comparison operations.
33
+
34
+ Ordinal values have a natural ordering and support all comparison operators:
35
+ >, <, >=, <=, ==, !=
36
+ """
37
+
38
+ def __init__(self, column: Mapped[T]) -> None:
39
+ """Initialize the ordinal field with a database column.
40
+
41
+ Args:
42
+ column: The database column this field represents.
43
+ """
44
+ self._column = column
45
+
46
+ def get_sqlmodel_field(self) -> Mapped[T]:
47
+ """Get the ordinal database column or property.
48
+
49
+ Returns:
50
+ The ordinal column for database queries.
51
+ """
52
+ return self._column
53
+
54
+ def __gt__(self, other: T) -> OrdinalFieldExpression[T]:
55
+ """Create a greater-than expression."""
56
+ return OrdinalFieldExpression(field=self, operator=">", value=other)
57
+
58
+ def __lt__(self, other: T) -> OrdinalFieldExpression[T]:
59
+ """Create a less-than expression."""
60
+ return OrdinalFieldExpression(field=self, operator="<", value=other)
61
+
62
+ def __ge__(self, other: T) -> OrdinalFieldExpression[T]:
63
+ """Create a greater-than-or-equal expression."""
64
+ return OrdinalFieldExpression(field=self, operator=">=", value=other)
65
+
66
+ def __le__(self, other: T) -> OrdinalFieldExpression[T]:
67
+ """Create a less-than-or-equal expression."""
68
+ return OrdinalFieldExpression(field=self, operator="<=", value=other)
69
+
70
+ def __eq__(self, other: T) -> OrdinalFieldExpression[T]: # type: ignore[override]
71
+ """Create an equality expression."""
72
+ return OrdinalFieldExpression(field=self, operator="==", value=other)
73
+
74
+ def __ne__(self, other: T) -> OrdinalFieldExpression[T]: # type: ignore[override]
75
+ """Create a not-equal expression."""
76
+ return OrdinalFieldExpression(field=self, operator="!=", value=other)
77
+
78
+
79
+ NumericalField = OrdinalField[Union[float, int]]
80
+ DatetimeField = OrdinalField[datetime]
81
+
82
+
83
+ class StringField(Field):
84
+ """Field for string values that supports equality operations.
85
+
86
+ Optional refactor when needed: Split into
87
+ - StringField(ABC) with the comparison operators.
88
+ - StringColumnField(StringField) for the __init__ and get_sqlmodel_field implementation.
89
+ """
90
+
91
+ def __init__(self, column: Mapped[str]) -> None:
92
+ """Initialize the string field with a database column.
93
+
94
+ Args:
95
+ column: The database column this field represents.
96
+ """
97
+ self._column = column
98
+
99
+ def get_sqlmodel_field(self) -> Mapped[str]:
100
+ """Get the string database column or property.
101
+
102
+ Returns:
103
+ The string column for database queries.
104
+ """
105
+ return self._column
106
+
107
+ def __eq__(self, other: str) -> StringFieldExpression: # type: ignore[override]
108
+ """Create an equality expression."""
109
+ return StringFieldExpression(field=self, operator="==", value=other)
110
+
111
+ def __ne__(self, other: str) -> StringFieldExpression: # type: ignore[override]
112
+ """Create a not-equal expression."""
113
+ return StringFieldExpression(field=self, operator="!=", value=other)
@@ -0,0 +1,79 @@
1
+ """Field expressions for building specific query conditions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+ from typing import TYPE_CHECKING, Callable, Generic, Literal, TypeVar, Union
8
+
9
+ from sqlalchemy import ColumnElement
10
+ from sqlalchemy.orm import Mapped
11
+
12
+ from lightly_studio.core.dataset_query.match_expression import MatchExpression
13
+
14
+ if TYPE_CHECKING:
15
+ from lightly_studio.core.dataset_query.field import (
16
+ OrdinalField,
17
+ StringField,
18
+ )
19
+
20
+
21
+ T = TypeVar("T")
22
+
23
+ """Conditions themselves, in the format <field> <operator> <value>
24
+
25
+ Example:
26
+ SampleField.file_name == "img1.jpg",
27
+ becomes StringField(file_name) == "img1.jpg",
28
+ becomes StringFieldExpression(field=StringField(file_name), operator="==", value="img1.jpg")
29
+ becomes SQLQuery.where(...)
30
+ """
31
+ StringOperator = Literal["==", "!="]
32
+ OrdinalOperator = Literal[">", "<", "==", ">=", "<=", "!="]
33
+
34
+
35
+ @dataclass
36
+ class OrdinalFieldExpression(MatchExpression, Generic[T]):
37
+ """Generic expression for ordinal field comparisons."""
38
+
39
+ field: OrdinalField[T]
40
+ operator: OrdinalOperator
41
+ value: T
42
+
43
+ def get(self) -> ColumnElement[bool]:
44
+ """Return the SQLAlchemy expression for this ordinal field expression."""
45
+ table_property = self.field.get_sqlmodel_field()
46
+ operations: dict[
47
+ OrdinalOperator,
48
+ Callable[[Mapped[T], T], ColumnElement[bool]],
49
+ ] = {
50
+ "<": lambda tp, v: tp < v,
51
+ "<=": lambda tp, v: tp <= v,
52
+ ">": lambda tp, v: tp > v,
53
+ ">=": lambda tp, v: tp >= v,
54
+ "==": lambda tp, v: tp == v,
55
+ "!=": lambda tp, v: tp != v,
56
+ }
57
+ return operations[self.operator](table_property, self.value)
58
+
59
+
60
+ NumericalFieldExpression = OrdinalFieldExpression[Union[float, int]]
61
+ DatetimeFieldExpression = OrdinalFieldExpression[datetime]
62
+
63
+
64
+ @dataclass
65
+ class StringFieldExpression(MatchExpression):
66
+ """Expression for string field comparisons."""
67
+
68
+ field: StringField
69
+ operator: StringOperator
70
+ value: str
71
+
72
+ def get(self) -> ColumnElement[bool]:
73
+ """Return the SQLAlchemy expression for this string field expression."""
74
+ table_property = self.field.get_sqlmodel_field()
75
+ operations: dict[StringOperator, Callable[[Mapped[str], str], ColumnElement[bool]]] = {
76
+ "==": lambda tp, v: tp == v,
77
+ "!=": lambda tp, v: tp != v,
78
+ }
79
+ return operations[self.operator](table_property, self.value)
@@ -0,0 +1,23 @@
1
+ """Base classes for match expressions in dataset queries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from sqlalchemy import ColumnElement
8
+
9
+
10
+ class MatchExpression(ABC):
11
+ """Base class for all match expressions that can be applied to database queries.
12
+
13
+ This class provides the foundation for implementing complex query expressions
14
+ that can be combined using AND/OR operations in the future.
15
+ """
16
+
17
+ @abstractmethod
18
+ def get(self) -> ColumnElement[bool]:
19
+ """Get the SQLAlchemy expression for this match expression.
20
+
21
+ Returns:
22
+ The combined SQLAlchemy expression.
23
+ """
@@ -0,0 +1,79 @@
1
+ """Classes for order by expressions in dataset queries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ from sqlmodel.sql.expression import SelectOfScalar
8
+ from typing_extensions import Self
9
+
10
+ from lightly_studio.core.dataset_query.field import Field
11
+ from lightly_studio.models.image import ImageTable
12
+
13
+
14
+ class OrderByExpression(ABC):
15
+ """Base class for all order by expressions that can be applied to database queries."""
16
+
17
+ def __init__(self, *, ascending: bool = True) -> None:
18
+ """Initialize the order by expression.
19
+
20
+ Args:
21
+ ascending: Whether to order in ascending (True) or descending (False) order.
22
+ """
23
+ self.ascending = ascending
24
+
25
+ @abstractmethod
26
+ def apply(self, query: SelectOfScalar[ImageTable]) -> SelectOfScalar[ImageTable]:
27
+ """Apply this ordering to a SQLModel Select query.
28
+
29
+ Args:
30
+ query: The SQLModel Select query to modify.
31
+
32
+ Returns:
33
+ The modified query after ordering
34
+ """
35
+
36
+ def asc(self) -> Self:
37
+ """Set the ordering to ascending.
38
+
39
+ Returns:
40
+ Self for method chaining.
41
+ """
42
+ self.ascending = True
43
+ return self
44
+
45
+ def desc(self) -> Self:
46
+ """Set the ordering to descending.
47
+
48
+ Returns:
49
+ Self for method chaining.
50
+ """
51
+ self.ascending = False
52
+ return self
53
+
54
+
55
+ class OrderByField(OrderByExpression):
56
+ """Order by a specific field, either ascending or descending.
57
+
58
+ Args:
59
+ field: The field to order by.
60
+ ascending: Whether to order in ascending (True) or descending (False) order.
61
+ """
62
+
63
+ def __init__(self, field: Field) -> None:
64
+ """Initialize with field and order direction."""
65
+ super().__init__()
66
+ self.field = field
67
+
68
+ def apply(self, query: SelectOfScalar[ImageTable]) -> SelectOfScalar[ImageTable]:
69
+ """Apply this ordering to a SQLModel Select query.
70
+
71
+ Args:
72
+ query: The SQLModel Select query to modify.
73
+
74
+ Returns:
75
+ The modified query after ordering
76
+ """
77
+ if self.ascending:
78
+ return query.order_by(self.field.get_sqlmodel_field().asc())
79
+ return query.order_by(self.field.get_sqlmodel_field().desc())