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,12 @@
1
+ # Set up logging before importing any other modules.
2
+ # Add noqa to silence unused import and unsorted imports linter warnings.
3
+ from . import setup_logging # noqa: F401 I001
4
+ from lightly_studio.core.dataset import Dataset
5
+ from lightly_studio.core.start_gui import start_gui
6
+ from lightly_studio.models.dataset import SampleType
7
+
8
+
9
+ # TODO (Jonas 08/25): This will be removed as soon as the new interface is used in the examples
10
+ from lightly_studio.models.annotation.annotation_base import AnnotationType
11
+
12
+ __all__ = ["AnnotationType", "Dataset", "SampleType", "start_gui"]
File without changes
@@ -0,0 +1,131 @@
1
+ """This module contains the FastAPI app configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncGenerator
6
+ from contextlib import asynccontextmanager
7
+
8
+ from fastapi import APIRouter, Depends, FastAPI
9
+ from fastapi.middleware.cors import CORSMiddleware
10
+ from fastapi.routing import APIRoute
11
+ from sqlmodel import Session
12
+ from typing_extensions import Annotated
13
+
14
+ from lightly_studio import db_manager
15
+ from lightly_studio.api.routes import (
16
+ healthz,
17
+ images,
18
+ video_frames_media,
19
+ video_media,
20
+ webapp,
21
+ )
22
+ from lightly_studio.api.routes.api import (
23
+ annotation,
24
+ annotation_label,
25
+ caption,
26
+ classifier,
27
+ dataset,
28
+ dataset_tag,
29
+ embeddings2d,
30
+ export,
31
+ features,
32
+ frame,
33
+ image,
34
+ metadata,
35
+ operator,
36
+ sample,
37
+ selection,
38
+ settings,
39
+ text_embedding,
40
+ video,
41
+ )
42
+ from lightly_studio.api.routes.api.exceptions import (
43
+ register_exception_handlers,
44
+ )
45
+ from lightly_studio.dataset.env import LIGHTLY_STUDIO_DEBUG
46
+
47
+ SessionDep = Annotated[Session, Depends(db_manager.session)]
48
+
49
+
50
+ @asynccontextmanager
51
+ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
52
+ """Lifespan context for initializing and cleaning up resources.
53
+
54
+ Args:
55
+ _: The FastAPI application instance.
56
+
57
+ Yields:
58
+ None when the application is ready.
59
+ """
60
+ yield
61
+
62
+
63
+ if LIGHTLY_STUDIO_DEBUG:
64
+ import logging
65
+
66
+ # TODO(Lukas, 12/2025): move this into setup_logging.py, drop `basicConfig()`. Also everything
67
+ # seems to be on the INFO level. `logging.DEBUG` maybe doesn't make a difference.
68
+ logging.basicConfig()
69
+ logger = logging.getLogger("sqlalchemy.engine")
70
+ logger.setLevel(logging.DEBUG)
71
+
72
+ """Create the FastAPI app."""
73
+ app = FastAPI(lifespan=lifespan)
74
+
75
+ app.add_middleware(
76
+ CORSMiddleware,
77
+ allow_origins=["*"],
78
+ allow_credentials=True,
79
+ allow_methods=["*"],
80
+ allow_headers=["*"],
81
+ )
82
+
83
+
84
+ def use_route_names_as_operation_ids(app: FastAPI) -> None:
85
+ """Use API function name for operation IDs.
86
+
87
+ Should be called only after all routes have been added.
88
+ """
89
+ for route in app.routes:
90
+ if isinstance(route, APIRoute):
91
+ route.operation_id = route.name # in this case, 'read_items'
92
+
93
+
94
+ register_exception_handlers(app)
95
+
96
+ # api routes
97
+ api_router = APIRouter(prefix="/api", tags=["api"])
98
+
99
+ api_router.include_router(dataset.dataset_router)
100
+ api_router.include_router(dataset_tag.tag_router)
101
+ api_router.include_router(export.export_router)
102
+ api_router.include_router(image.image_router)
103
+ api_router.include_router(sample.sample_router)
104
+ api_router.include_router(annotation_label.annotations_label_router)
105
+ api_router.include_router(annotation.annotations_router)
106
+ api_router.include_router(caption.captions_router)
107
+ api_router.include_router(text_embedding.text_embedding_router)
108
+ api_router.include_router(settings.settings_router)
109
+ api_router.include_router(classifier.classifier_router)
110
+ api_router.include_router(embeddings2d.embeddings2d_router)
111
+ api_router.include_router(features.features_router)
112
+ api_router.include_router(metadata.metadata_router)
113
+ api_router.include_router(selection.selection_router)
114
+ api_router.include_router(operator.operator_router)
115
+ api_router.include_router(frame.frame_router)
116
+ api_router.include_router(video.video_router)
117
+
118
+ app.include_router(api_router)
119
+
120
+ # images serving
121
+ app.include_router(images.app_router, prefix="/images")
122
+ app.include_router(video_frames_media.frames_router)
123
+ app.include_router(video_media.app_router)
124
+
125
+ # health status check
126
+ app.include_router(healthz.health_router)
127
+
128
+ # webapp routes
129
+ app.include_router(webapp.app_router)
130
+
131
+ use_route_names_as_operation_ids(app)
@@ -0,0 +1,77 @@
1
+ """This module contains the FastAPI cache configuration for static files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timedelta, timezone
6
+ from os import PathLike, stat_result
7
+
8
+ from fastapi import Response
9
+ from fastapi.staticfiles import StaticFiles
10
+ from starlette.types import Scope
11
+
12
+ from .routes.api.status import HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE
13
+
14
+
15
+ class StaticFilesCache(StaticFiles):
16
+ """StaticFiles class with cache headers."""
17
+
18
+ days_to_expire = 1
19
+
20
+ def __init__( # noqa: PLR0913 (too-many-arguments)
21
+ self,
22
+ *,
23
+ directory: str | PathLike[str] | None = None,
24
+ packages: list[str | tuple[str, str]] | None = None,
25
+ html: bool = False,
26
+ check_dir: bool = True,
27
+ follow_symlink: bool = False,
28
+ cachecontrol: str | None = None,
29
+ ) -> None:
30
+ """Initialize the StaticFilesCache class."""
31
+ self.cachecontrol = cachecontrol or f"private, max-age={self.days_to_expire * 24 * 60 * 60}"
32
+ super().__init__(
33
+ directory=directory,
34
+ packages=packages,
35
+ html=html,
36
+ check_dir=check_dir,
37
+ follow_symlink=follow_symlink,
38
+ )
39
+
40
+ def file_response(
41
+ self,
42
+ full_path: str | PathLike[str],
43
+ stat_result: stat_result,
44
+ scope: Scope,
45
+ status_code: int = 200,
46
+ ) -> Response:
47
+ """Override the file_response method to add cache headers."""
48
+ allowed_extensions = (
49
+ # Images
50
+ ".png",
51
+ ".jpg",
52
+ ".jpeg",
53
+ ".gif",
54
+ ".webp",
55
+ ".bmp",
56
+ ".tiff",
57
+ # Movies
58
+ ".mov",
59
+ ".mp4",
60
+ ".avi",
61
+ )
62
+
63
+ if not str(full_path).lower().endswith(allowed_extensions):
64
+ return Response(
65
+ status_code=HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE
66
+ ) # Unsupported Media Type
67
+ resp: Response = super().file_response(full_path, stat_result, scope, status_code)
68
+ resp.headers.setdefault("Cache-Control", self.cachecontrol)
69
+
70
+ # Calculate expiration date
71
+ expire_date = datetime.now(timezone.utc) + timedelta(days=self.days_to_expire)
72
+ resp.headers.setdefault("Expires", expire_date.strftime("%a, %d %b %Y %H:%M:%S GMT"))
73
+
74
+ # Add Vary header to make sure caches respect the query parameters
75
+ resp.headers.setdefault("Vary", "Accept-Encoding, Origin, v")
76
+
77
+ return resp
@@ -0,0 +1,35 @@
1
+ """Module provides functions to initialize and manage the DuckDB."""
2
+
3
+ from lightly_studio.models.annotation.annotation_base import (
4
+ AnnotationBaseTable, # noqa: F401, required for SQLModel to work properly
5
+ )
6
+ from lightly_studio.models.annotation_label import (
7
+ AnnotationLabelTable, # noqa: F401, required for SQLModel to work properly
8
+ )
9
+ from lightly_studio.models.dataset import (
10
+ DatasetTable, # noqa: F401, required for SQLModel to work properly
11
+ )
12
+ from lightly_studio.models.embedding_model import (
13
+ EmbeddingModelTable, # noqa: F401, required for SQLModel to work properly
14
+ )
15
+ from lightly_studio.models.image import (
16
+ ImageTable, # noqa: F401, required for SQLModel to work properly
17
+ )
18
+ from lightly_studio.models.metadata import (
19
+ SampleMetadataTable, # noqa: F401, required for SQLModel to work properly
20
+ )
21
+ from lightly_studio.models.sample import (
22
+ SampleTable, # noqa: F401, required for SQLModel to work properly
23
+ )
24
+ from lightly_studio.models.sample_embedding import (
25
+ SampleEmbeddingTable, # noqa: F401, required for SQLModel to work properly
26
+ )
27
+ from lightly_studio.models.settings import (
28
+ SettingTable, # noqa: F401, required for SQLModel to work properly
29
+ )
30
+ from lightly_studio.models.tag import (
31
+ TagTable, # noqa: F401, required for SQLModel to work properly
32
+ )
33
+ from lightly_studio.models.two_dim_embedding import (
34
+ TwoDimEmbeddingTable, # noqa: F401, required for SQLModel to work properly
35
+ )
@@ -0,0 +1,5 @@
1
+ """Global list of the active features."""
2
+
3
+ from __future__ import annotations
4
+
5
+ lightly_studio_active_features: list[str] = []
@@ -0,0 +1,305 @@
1
+ """This module contains the API routes for managing annotations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from fastapi import APIRouter, Body, Depends, HTTPException, Path
8
+ from fastapi.params import Query
9
+ from pydantic import BaseModel
10
+ from typing_extensions import Annotated
11
+
12
+ from lightly_studio.api.routes.api import annotations as annotations_module
13
+ from lightly_studio.api.routes.api.dataset import get_and_validate_dataset_id
14
+ from lightly_studio.api.routes.api.status import (
15
+ HTTP_STATUS_CREATED,
16
+ HTTP_STATUS_NOT_FOUND,
17
+ )
18
+ from lightly_studio.api.routes.api.validators import Paginated, PaginatedWithCursor
19
+ from lightly_studio.db_manager import SessionDep
20
+ from lightly_studio.models.annotation.annotation_base import (
21
+ AnnotationBaseTable,
22
+ AnnotationDetailsWithPayloadView,
23
+ AnnotationView,
24
+ AnnotationViewsWithCount,
25
+ AnnotationWithPayloadAndCountView,
26
+ )
27
+ from lightly_studio.models.dataset import DatasetTable
28
+ from lightly_studio.resolvers import annotation_resolver, tag_resolver
29
+ from lightly_studio.resolvers.annotation_resolver.get_all import (
30
+ GetAllAnnotationsResult,
31
+ )
32
+ from lightly_studio.resolvers.annotation_resolver.update_bounding_box import BoundingBoxCoordinates
33
+ from lightly_studio.resolvers.annotations.annotations_filter import (
34
+ AnnotationsFilter,
35
+ )
36
+ from lightly_studio.services import annotations_service
37
+ from lightly_studio.services.annotations_service.update_annotation import (
38
+ AnnotationUpdate,
39
+ )
40
+
41
+ annotations_router = APIRouter(prefix="/datasets/{dataset_id}", tags=["annotations"])
42
+ annotations_router.include_router(annotations_module.create_annotation_router)
43
+
44
+
45
+ class AnnotationQueryParamsModel(BaseModel):
46
+ """Model for all annotation query parameters."""
47
+
48
+ pagination: PaginatedWithCursor
49
+ annotation_label_ids: list[UUID] | None = None
50
+ tag_ids: list[UUID] | None = None
51
+
52
+
53
+ def _get_annotation_query_params(
54
+ pagination: Annotated[PaginatedWithCursor, Depends()],
55
+ annotation_label_ids: Annotated[list[UUID] | None, Query()] = None,
56
+ tag_ids: Annotated[list[UUID] | None, Query()] = None,
57
+ ) -> AnnotationQueryParamsModel:
58
+ return AnnotationQueryParamsModel(
59
+ pagination=pagination,
60
+ annotation_label_ids=annotation_label_ids,
61
+ tag_ids=tag_ids,
62
+ )
63
+
64
+
65
+ @annotations_router.get("/annotations/count")
66
+ def count_annotations_by_dataset( # noqa: PLR0913 // FIXME: refactor to use proper pydantic
67
+ dataset: Annotated[
68
+ DatasetTable,
69
+ Path(title="Dataset Id"),
70
+ Depends(get_and_validate_dataset_id),
71
+ ],
72
+ session: SessionDep,
73
+ filtered_labels: Annotated[list[str] | None, Query()] = None,
74
+ min_width: Annotated[int | None, Query(ge=0)] = None,
75
+ max_width: Annotated[int | None, Query(ge=0)] = None,
76
+ min_height: Annotated[int | None, Query(ge=0)] = None,
77
+ max_height: Annotated[int | None, Query(ge=0)] = None,
78
+ tag_ids: list[UUID] | None = None,
79
+ ) -> list[dict[str, str | int]]:
80
+ """Get annotation counts for a specific dataset.
81
+
82
+ Returns a list of dictionaries with label name and count.
83
+ """
84
+ counts = annotation_resolver.count_annotations_by_dataset(
85
+ session=session,
86
+ dataset_id=dataset.dataset_id,
87
+ filtered_labels=filtered_labels,
88
+ min_width=min_width,
89
+ max_width=max_width,
90
+ min_height=min_height,
91
+ max_height=max_height,
92
+ tag_ids=tag_ids,
93
+ )
94
+
95
+ return [
96
+ {
97
+ "label_name": label_name,
98
+ "current_count": current_count,
99
+ "total_count": total_count,
100
+ }
101
+ for label_name, current_count, total_count in counts
102
+ ]
103
+
104
+
105
+ @annotations_router.get(
106
+ "/annotations",
107
+ response_model=AnnotationViewsWithCount,
108
+ )
109
+ def read_annotations(
110
+ dataset_id: Annotated[UUID, Path(title="Dataset Id", description="The ID of the dataset")],
111
+ session: SessionDep,
112
+ pagination: Annotated[PaginatedWithCursor, Depends()],
113
+ annotation_label_ids: Annotated[list[UUID] | None, Query()] = None,
114
+ tag_ids: Annotated[list[UUID] | None, Query()] = None,
115
+ ) -> GetAllAnnotationsResult:
116
+ """Retrieve a list of annotations from the database."""
117
+ return annotation_resolver.get_all(
118
+ session=session,
119
+ pagination=Paginated(
120
+ offset=pagination.offset,
121
+ limit=pagination.limit,
122
+ ),
123
+ filters=AnnotationsFilter(
124
+ dataset_ids=[dataset_id],
125
+ annotation_label_ids=annotation_label_ids,
126
+ annotation_tag_ids=tag_ids,
127
+ ),
128
+ )
129
+
130
+
131
+ @annotations_router.get(
132
+ "/annotations/payload",
133
+ )
134
+ def read_annotations_with_payload(
135
+ dataset_id: Annotated[UUID, Path(title="Dataset Id", description="The ID of the dataset")],
136
+ session: SessionDep,
137
+ params: Annotated[AnnotationQueryParamsModel, Depends(_get_annotation_query_params)],
138
+ ) -> AnnotationWithPayloadAndCountView:
139
+ """Retrieve a list of annotations along with the parent sample data from the database."""
140
+ return annotation_resolver.get_all_with_payload(
141
+ session=session,
142
+ pagination=Paginated(
143
+ offset=params.pagination.offset,
144
+ limit=params.pagination.limit,
145
+ ),
146
+ filters=AnnotationsFilter(
147
+ dataset_ids=[dataset_id],
148
+ annotation_label_ids=params.annotation_label_ids,
149
+ annotation_tag_ids=params.tag_ids,
150
+ ),
151
+ dataset_id=dataset_id,
152
+ )
153
+
154
+
155
+ @annotations_router.post(
156
+ "/annotations/{annotation_id}/tag/{tag_id}",
157
+ status_code=HTTP_STATUS_CREATED,
158
+ )
159
+ def add_tag_to_annotation(
160
+ session: SessionDep,
161
+ annotation_id: UUID,
162
+ tag_id: UUID,
163
+ ) -> bool:
164
+ """Add annotation to a tag."""
165
+ annotation = annotation_resolver.get_by_id(session=session, annotation_id=annotation_id)
166
+ if not annotation:
167
+ raise HTTPException(
168
+ status_code=HTTP_STATUS_NOT_FOUND,
169
+ detail=f"Annotation {annotation_id} not found",
170
+ )
171
+
172
+ if not tag_resolver.add_tag_to_annotation(
173
+ session=session, tag_id=tag_id, annotation=annotation
174
+ ):
175
+ raise HTTPException(status_code=HTTP_STATUS_NOT_FOUND, detail=f"Tag {tag_id} not found")
176
+
177
+ return True
178
+
179
+
180
+ class AnnotationUpdateInput(BaseModel):
181
+ """API input model for updating an annotation."""
182
+
183
+ annotation_id: UUID
184
+ dataset_id: UUID
185
+ label_name: str | None = None
186
+ bounding_box: BoundingBoxCoordinates | None = None
187
+
188
+
189
+ @annotations_router.put("/annotations/{annotation_id}")
190
+ def update_annotation(
191
+ session: SessionDep,
192
+ dataset_id: Annotated[
193
+ UUID,
194
+ Path(title="Dataset Id"),
195
+ ],
196
+ annotation_id: Annotated[
197
+ UUID,
198
+ Path(title="Annotation ID", description="ID of the annotation to update"),
199
+ ],
200
+ annotation_update_input: Annotated[AnnotationUpdateInput, Body()],
201
+ ) -> AnnotationBaseTable:
202
+ """Update an existing annotation in the database."""
203
+ return annotations_service.update_annotation(
204
+ session=session,
205
+ annotation_update=AnnotationUpdate(
206
+ annotation_id=annotation_id,
207
+ dataset_id=dataset_id,
208
+ label_name=annotation_update_input.label_name,
209
+ bounding_box=annotation_update_input.bounding_box,
210
+ ),
211
+ )
212
+
213
+
214
+ @annotations_router.put(
215
+ "/annotations",
216
+ )
217
+ def update_annotations(
218
+ session: SessionDep,
219
+ dataset_id: Annotated[
220
+ UUID,
221
+ Path(title="Dataset Id"),
222
+ ],
223
+ annotation_update_inputs: Annotated[list[AnnotationUpdateInput], Body()],
224
+ ) -> list[AnnotationBaseTable]:
225
+ """Update multiple annotations in the database."""
226
+ return annotations_service.update_annotations(
227
+ session=session,
228
+ annotation_updates=[
229
+ AnnotationUpdate(
230
+ annotation_id=annotation_update_input.annotation_id,
231
+ dataset_id=dataset_id,
232
+ label_name=annotation_update_input.label_name,
233
+ bounding_box=annotation_update_input.bounding_box,
234
+ )
235
+ for annotation_update_input in annotation_update_inputs
236
+ ],
237
+ )
238
+
239
+
240
+ @annotations_router.get("/annotations/{annotation_id}", response_model=AnnotationView)
241
+ def get_annotation(
242
+ session: SessionDep,
243
+ dataset_id: Annotated[ # noqa: ARG001
244
+ UUID,
245
+ Path(title="Dataset Id", description="The ID of the dataset"),
246
+ ], # We need dataset_id because otherwise the path would not match
247
+ annotation_id: Annotated[UUID, Path(title="Annotation ID")],
248
+ ) -> AnnotationBaseTable:
249
+ """Retrieve an existing annotation from the database."""
250
+ return annotations_service.get_annotation_by_id(session=session, annotation_id=annotation_id)
251
+
252
+
253
+ @annotations_router.delete("/annotations/{annotation_id}/tag/{tag_id}")
254
+ def remove_tag_from_annotation(
255
+ session: SessionDep,
256
+ tag_id: UUID,
257
+ annotation_id: UUID,
258
+ ) -> bool:
259
+ """Remove annotation from a tag."""
260
+ annotation = annotation_resolver.get_by_id(session=session, annotation_id=annotation_id)
261
+ if not annotation:
262
+ raise HTTPException(
263
+ status_code=HTTP_STATUS_NOT_FOUND,
264
+ detail=f"Annotation {annotation_id} not found",
265
+ )
266
+
267
+ if not tag_resolver.remove_tag_from_annotation(
268
+ session=session, tag_id=tag_id, annotation=annotation
269
+ ):
270
+ raise HTTPException(status_code=HTTP_STATUS_NOT_FOUND, detail=f"Tag {tag_id} not found")
271
+
272
+ return True
273
+
274
+
275
+ @annotations_router.delete("/annotations/{annotation_id}")
276
+ def delete_annotation(
277
+ session: SessionDep,
278
+ # We need dataset_id because generator doesn't include it
279
+ # actuall path for this route is /datasets/{dataset_id}/annotations/{annotation_id}
280
+ dataset_id: Annotated[ # noqa: ARG001
281
+ UUID,
282
+ Path(title="Dataset Id", description="The ID of the dataset"),
283
+ ],
284
+ annotation_id: Annotated[
285
+ UUID, Path(title="Annotation ID", description="ID of the annotation to delete")
286
+ ],
287
+ ) -> dict[str, str]:
288
+ """Delete an annotation from the database."""
289
+ try:
290
+ annotations_service.delete_annotation(session=session, annotation_id=annotation_id)
291
+ return {"status": "deleted"}
292
+ except ValueError as e:
293
+ raise HTTPException(
294
+ status_code=HTTP_STATUS_NOT_FOUND,
295
+ detail="Annotation not found",
296
+ ) from e
297
+
298
+
299
+ @annotations_router.get("/annotations/payload/{sample_id}")
300
+ def get_annotation_with_payload(
301
+ session: SessionDep,
302
+ sample_id: Annotated[UUID, Path(title="Annotation ID")],
303
+ ) -> AnnotationDetailsWithPayloadView | None:
304
+ """Retrieve an existing annotation with payload from the database."""
305
+ return annotation_resolver.get_by_id_with_payload(session=session, sample_id=sample_id)
@@ -0,0 +1,87 @@
1
+ """This module contains the API routes for managing annotation labels."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from uuid import UUID
6
+
7
+ from fastapi import APIRouter, HTTPException
8
+
9
+ from lightly_studio.api.routes.api.status import (
10
+ HTTP_STATUS_CREATED,
11
+ HTTP_STATUS_NOT_FOUND,
12
+ )
13
+ from lightly_studio.db_manager import SessionDep
14
+ from lightly_studio.models.annotation_label import (
15
+ AnnotationLabelCreate,
16
+ AnnotationLabelTable,
17
+ )
18
+ from lightly_studio.resolvers import annotation_label_resolver
19
+
20
+ annotations_label_router = APIRouter()
21
+
22
+
23
+ @annotations_label_router.post(
24
+ "/annotation_labels",
25
+ status_code=HTTP_STATUS_CREATED,
26
+ )
27
+ def create_annotation_label(
28
+ input_label: AnnotationLabelCreate,
29
+ session: SessionDep,
30
+ ) -> AnnotationLabelTable:
31
+ """Create a new annotation label in the database."""
32
+ return annotation_label_resolver.create(session=session, label=input_label)
33
+
34
+
35
+ @annotations_label_router.get("/annotation_labels")
36
+ def read_annotation_labels(
37
+ session: SessionDep,
38
+ ) -> list[AnnotationLabelTable]:
39
+ """Retrieve a list of annotation labels from the database."""
40
+ return annotation_label_resolver.get_all(session=session)
41
+
42
+
43
+ @annotations_label_router.get("/annotation_labels/{label_id}")
44
+ def read_annotation_label(
45
+ label_id: UUID,
46
+ session: SessionDep,
47
+ ) -> AnnotationLabelTable:
48
+ """Retrieve a single annotation label from the database."""
49
+ label = annotation_label_resolver.get_by_id(session=session, label_id=label_id)
50
+ if not label:
51
+ raise HTTPException(
52
+ status_code=HTTP_STATUS_NOT_FOUND,
53
+ detail="Annotation label not found",
54
+ )
55
+ return label
56
+
57
+
58
+ @annotations_label_router.put("/annotation_labels/{label_id}")
59
+ def update_annotation_label(
60
+ label_id: UUID,
61
+ label_input: AnnotationLabelCreate,
62
+ session: SessionDep,
63
+ ) -> AnnotationLabelTable:
64
+ """Update an existing annotation label in the database."""
65
+ label = annotation_label_resolver.update(
66
+ session=session, label_id=label_id, label_data=label_input
67
+ )
68
+ if not label:
69
+ raise HTTPException(
70
+ status_code=HTTP_STATUS_NOT_FOUND,
71
+ detail="Annotation label not found",
72
+ )
73
+ return label
74
+
75
+
76
+ @annotations_label_router.delete("/annotation_labels/{label_id}")
77
+ def delete_annotation_label(
78
+ label_id: UUID,
79
+ session: SessionDep,
80
+ ) -> dict[str, str]:
81
+ """Delete an annotation label from the database."""
82
+ if not annotation_label_resolver.delete(session=session, label_id=label_id):
83
+ raise HTTPException(
84
+ status_code=HTTP_STATUS_NOT_FOUND,
85
+ detail="Annotation label not found",
86
+ )
87
+ return {"status": "deleted"}
@@ -0,0 +1,7 @@
1
+ from .create_annotation import (
2
+ create_annotation_router,
3
+ )
4
+
5
+ __all__ = [
6
+ "create_annotation_router",
7
+ ]