lightly-studio 0.3.1__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.

Potentially problematic release.


This version of lightly-studio might be problematic. Click here for more details.

Files changed (219) hide show
  1. lightly_studio/__init__.py +11 -0
  2. lightly_studio/api/__init__.py +0 -0
  3. lightly_studio/api/app.py +110 -0
  4. lightly_studio/api/cache.py +77 -0
  5. lightly_studio/api/db.py +133 -0
  6. lightly_studio/api/db_tables.py +32 -0
  7. lightly_studio/api/features.py +7 -0
  8. lightly_studio/api/routes/api/annotation.py +233 -0
  9. lightly_studio/api/routes/api/annotation_label.py +90 -0
  10. lightly_studio/api/routes/api/annotation_task.py +38 -0
  11. lightly_studio/api/routes/api/classifier.py +387 -0
  12. lightly_studio/api/routes/api/dataset.py +182 -0
  13. lightly_studio/api/routes/api/dataset_tag.py +257 -0
  14. lightly_studio/api/routes/api/exceptions.py +96 -0
  15. lightly_studio/api/routes/api/features.py +17 -0
  16. lightly_studio/api/routes/api/metadata.py +37 -0
  17. lightly_studio/api/routes/api/metrics.py +80 -0
  18. lightly_studio/api/routes/api/sample.py +196 -0
  19. lightly_studio/api/routes/api/settings.py +45 -0
  20. lightly_studio/api/routes/api/status.py +19 -0
  21. lightly_studio/api/routes/api/text_embedding.py +48 -0
  22. lightly_studio/api/routes/api/validators.py +17 -0
  23. lightly_studio/api/routes/healthz.py +13 -0
  24. lightly_studio/api/routes/images.py +104 -0
  25. lightly_studio/api/routes/webapp.py +51 -0
  26. lightly_studio/api/server.py +82 -0
  27. lightly_studio/core/__init__.py +0 -0
  28. lightly_studio/core/dataset.py +523 -0
  29. lightly_studio/core/sample.py +77 -0
  30. lightly_studio/core/start_gui.py +15 -0
  31. lightly_studio/dataset/__init__.py +0 -0
  32. lightly_studio/dataset/edge_embedding_generator.py +144 -0
  33. lightly_studio/dataset/embedding_generator.py +91 -0
  34. lightly_studio/dataset/embedding_manager.py +163 -0
  35. lightly_studio/dataset/env.py +16 -0
  36. lightly_studio/dataset/file_utils.py +35 -0
  37. lightly_studio/dataset/loader.py +622 -0
  38. lightly_studio/dataset/mobileclip_embedding_generator.py +144 -0
  39. lightly_studio/dist_lightly_studio_view_app/_app/env.js +1 -0
  40. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/0.DenzbfeK.css +1 -0
  41. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/LightlyLogo.BNjCIww-.png +0 -0
  42. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans- +0 -0
  43. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Bold.DGvYQtcs.ttf +0 -0
  44. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Italic-VariableFont_wdth_wght.B4AZ-wl6.ttf +0 -0
  45. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-Regular.DxJTClRG.ttf +0 -0
  46. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-SemiBold.D3TTYgdB.ttf +0 -0
  47. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/OpenSans-VariableFont_wdth_wght.BZBpG5Iz.ttf +0 -0
  48. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.OwPEPQZu.css +1 -0
  49. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/SelectableSvgGroup.b653GmVf.css +1 -0
  50. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/_layout.T-zjSUd3.css +1 -0
  51. lightly_studio/dist_lightly_studio_view_app/_app/immutable/assets/useFeatureFlags.CV-KWLNP.css +1 -0
  52. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/69_IOA4Y.js +1 -0
  53. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B2FVR0s0.js +1 -0
  54. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B90CZVMX.js +1 -0
  55. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/B9zumHo5.js +1 -0
  56. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BJXwVxaE.js +1 -0
  57. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bsi3UGy5.js +1 -0
  58. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bu7uvVrG.js +1 -0
  59. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/Bx1xMsFy.js +1 -0
  60. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/BylOuP6i.js +1 -0
  61. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/C8I8rFJQ.js +1 -0
  62. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CDnpyLsT.js +1 -0
  63. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CWj6FrbW.js +1 -0
  64. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CYgJF_JY.js +1 -0
  65. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CcaPhhk3.js +1 -0
  66. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CvOmgdoc.js +93 -0
  67. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/CxtLVaYz.js +3 -0
  68. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D5-A_Ffd.js +4 -0
  69. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6RI2Zrd.js +1 -0
  70. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D6su9Aln.js +1 -0
  71. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/D98V7j6A.js +1 -0
  72. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIRAtgl0.js +1 -0
  73. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DIeogL5L.js +1 -0
  74. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DOlTMNyt.js +1 -0
  75. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DjUWrjOv.js +1 -0
  76. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/DjfY96ND.js +1 -0
  77. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/H7C68rOM.js +1 -0
  78. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/O-EABkf9.js +1 -0
  79. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/XO7A28GO.js +1 -0
  80. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/hQVEETDE.js +1 -0
  81. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/l7KrR96u.js +1 -0
  82. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/nAHhluT7.js +1 -0
  83. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/r64xT6ao.js +1 -0
  84. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/vC4nQVEB.js +1 -0
  85. lightly_studio/dist_lightly_studio_view_app/_app/immutable/chunks/x9G_hzyY.js +1 -0
  86. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/app.CjnvpsmS.js +2 -0
  87. lightly_studio/dist_lightly_studio_view_app/_app/immutable/entry/start.0o1H7wM9.js +1 -0
  88. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/0.XRq_TUwu.js +1 -0
  89. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/1.B4rNYwVp.js +1 -0
  90. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/10.DfBwOEhN.js +1 -0
  91. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/11.CWG1ehzT.js +1 -0
  92. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/12.CwF2_8mP.js +1 -0
  93. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/2.CS4muRY-.js +6 -0
  94. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/3.CWHpKonm.js +1 -0
  95. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/4.OUWOLQeV.js +1 -0
  96. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/5.Dm6t9F5W.js +1 -0
  97. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/6.Bw5ck4gK.js +1 -0
  98. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/7.CF0EDTR6.js +1 -0
  99. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/8.Cw30LEcV.js +1 -0
  100. lightly_studio/dist_lightly_studio_view_app/_app/immutable/nodes/9.CPu3CiBc.js +1 -0
  101. lightly_studio/dist_lightly_studio_view_app/_app/version.json +1 -0
  102. lightly_studio/dist_lightly_studio_view_app/apple-touch-icon-precomposed.png +0 -0
  103. lightly_studio/dist_lightly_studio_view_app/apple-touch-icon.png +0 -0
  104. lightly_studio/dist_lightly_studio_view_app/favicon.png +0 -0
  105. lightly_studio/dist_lightly_studio_view_app/index.html +44 -0
  106. lightly_studio/examples/example.py +23 -0
  107. lightly_studio/examples/example_metadata.py +338 -0
  108. lightly_studio/examples/example_selection.py +39 -0
  109. lightly_studio/examples/example_split_work.py +67 -0
  110. lightly_studio/examples/example_v2.py +21 -0
  111. lightly_studio/export_schema.py +18 -0
  112. lightly_studio/few_shot_classifier/__init__.py +0 -0
  113. lightly_studio/few_shot_classifier/classifier.py +80 -0
  114. lightly_studio/few_shot_classifier/classifier_manager.py +663 -0
  115. lightly_studio/few_shot_classifier/random_forest_classifier.py +489 -0
  116. lightly_studio/metadata/complex_metadata.py +47 -0
  117. lightly_studio/metadata/gps_coordinate.py +41 -0
  118. lightly_studio/metadata/metadata_protocol.py +17 -0
  119. lightly_studio/metrics/__init__.py +0 -0
  120. lightly_studio/metrics/detection/__init__.py +0 -0
  121. lightly_studio/metrics/detection/map.py +268 -0
  122. lightly_studio/models/__init__.py +1 -0
  123. lightly_studio/models/annotation/__init__.py +0 -0
  124. lightly_studio/models/annotation/annotation_base.py +171 -0
  125. lightly_studio/models/annotation/instance_segmentation.py +56 -0
  126. lightly_studio/models/annotation/links.py +17 -0
  127. lightly_studio/models/annotation/object_detection.py +47 -0
  128. lightly_studio/models/annotation/semantic_segmentation.py +44 -0
  129. lightly_studio/models/annotation_label.py +47 -0
  130. lightly_studio/models/annotation_task.py +28 -0
  131. lightly_studio/models/classifier.py +20 -0
  132. lightly_studio/models/dataset.py +84 -0
  133. lightly_studio/models/embedding_model.py +30 -0
  134. lightly_studio/models/metadata.py +208 -0
  135. lightly_studio/models/sample.py +180 -0
  136. lightly_studio/models/sample_embedding.py +37 -0
  137. lightly_studio/models/settings.py +60 -0
  138. lightly_studio/models/tag.py +96 -0
  139. lightly_studio/py.typed +0 -0
  140. lightly_studio/resolvers/__init__.py +7 -0
  141. lightly_studio/resolvers/annotation_label_resolver/__init__.py +21 -0
  142. lightly_studio/resolvers/annotation_label_resolver/create.py +27 -0
  143. lightly_studio/resolvers/annotation_label_resolver/delete.py +28 -0
  144. lightly_studio/resolvers/annotation_label_resolver/get_all.py +22 -0
  145. lightly_studio/resolvers/annotation_label_resolver/get_by_id.py +24 -0
  146. lightly_studio/resolvers/annotation_label_resolver/get_by_ids.py +25 -0
  147. lightly_studio/resolvers/annotation_label_resolver/get_by_label_name.py +24 -0
  148. lightly_studio/resolvers/annotation_label_resolver/names_by_ids.py +25 -0
  149. lightly_studio/resolvers/annotation_label_resolver/update.py +38 -0
  150. lightly_studio/resolvers/annotation_resolver/__init__.py +33 -0
  151. lightly_studio/resolvers/annotation_resolver/count_annotations_by_dataset.py +120 -0
  152. lightly_studio/resolvers/annotation_resolver/create.py +19 -0
  153. lightly_studio/resolvers/annotation_resolver/create_many.py +96 -0
  154. lightly_studio/resolvers/annotation_resolver/delete_annotation.py +45 -0
  155. lightly_studio/resolvers/annotation_resolver/delete_annotations.py +56 -0
  156. lightly_studio/resolvers/annotation_resolver/get_all.py +74 -0
  157. lightly_studio/resolvers/annotation_resolver/get_by_id.py +18 -0
  158. lightly_studio/resolvers/annotation_resolver/update_annotation_label.py +144 -0
  159. lightly_studio/resolvers/annotation_resolver/update_bounding_box.py +68 -0
  160. lightly_studio/resolvers/annotation_task_resolver.py +31 -0
  161. lightly_studio/resolvers/annotations/__init__.py +1 -0
  162. lightly_studio/resolvers/annotations/annotations_filter.py +89 -0
  163. lightly_studio/resolvers/dataset_resolver.py +278 -0
  164. lightly_studio/resolvers/embedding_model_resolver.py +100 -0
  165. lightly_studio/resolvers/metadata_resolver/__init__.py +15 -0
  166. lightly_studio/resolvers/metadata_resolver/metadata_filter.py +163 -0
  167. lightly_studio/resolvers/metadata_resolver/sample/__init__.py +21 -0
  168. lightly_studio/resolvers/metadata_resolver/sample/bulk_set_metadata.py +48 -0
  169. lightly_studio/resolvers/metadata_resolver/sample/get_by_sample_id.py +24 -0
  170. lightly_studio/resolvers/metadata_resolver/sample/get_metadata_info.py +104 -0
  171. lightly_studio/resolvers/metadata_resolver/sample/get_value_for_sample.py +27 -0
  172. lightly_studio/resolvers/metadata_resolver/sample/set_value_for_sample.py +53 -0
  173. lightly_studio/resolvers/sample_embedding_resolver.py +86 -0
  174. lightly_studio/resolvers/sample_resolver.py +249 -0
  175. lightly_studio/resolvers/samples_filter.py +81 -0
  176. lightly_studio/resolvers/settings_resolver.py +58 -0
  177. lightly_studio/resolvers/tag_resolver.py +276 -0
  178. lightly_studio/selection/README.md +6 -0
  179. lightly_studio/selection/mundig.py +105 -0
  180. lightly_studio/selection/select.py +96 -0
  181. lightly_studio/selection/select_via_db.py +93 -0
  182. lightly_studio/selection/selection_config.py +31 -0
  183. lightly_studio/services/annotations_service/__init__.py +21 -0
  184. lightly_studio/services/annotations_service/get_annotation_by_id.py +31 -0
  185. lightly_studio/services/annotations_service/update_annotation.py +65 -0
  186. lightly_studio/services/annotations_service/update_annotation_label.py +48 -0
  187. lightly_studio/services/annotations_service/update_annotations.py +29 -0
  188. lightly_studio/setup_logging.py +19 -0
  189. lightly_studio/type_definitions.py +19 -0
  190. lightly_studio/vendor/ACKNOWLEDGEMENTS +422 -0
  191. lightly_studio/vendor/LICENSE +31 -0
  192. lightly_studio/vendor/LICENSE_weights_data +50 -0
  193. lightly_studio/vendor/README.md +5 -0
  194. lightly_studio/vendor/__init__.py +1 -0
  195. lightly_studio/vendor/mobileclip/__init__.py +96 -0
  196. lightly_studio/vendor/mobileclip/clip.py +77 -0
  197. lightly_studio/vendor/mobileclip/configs/mobileclip_b.json +18 -0
  198. lightly_studio/vendor/mobileclip/configs/mobileclip_s0.json +18 -0
  199. lightly_studio/vendor/mobileclip/configs/mobileclip_s1.json +18 -0
  200. lightly_studio/vendor/mobileclip/configs/mobileclip_s2.json +18 -0
  201. lightly_studio/vendor/mobileclip/image_encoder.py +67 -0
  202. lightly_studio/vendor/mobileclip/logger.py +154 -0
  203. lightly_studio/vendor/mobileclip/models/__init__.py +10 -0
  204. lightly_studio/vendor/mobileclip/models/mci.py +933 -0
  205. lightly_studio/vendor/mobileclip/models/vit.py +433 -0
  206. lightly_studio/vendor/mobileclip/modules/__init__.py +4 -0
  207. lightly_studio/vendor/mobileclip/modules/common/__init__.py +4 -0
  208. lightly_studio/vendor/mobileclip/modules/common/mobileone.py +341 -0
  209. lightly_studio/vendor/mobileclip/modules/common/transformer.py +451 -0
  210. lightly_studio/vendor/mobileclip/modules/image/__init__.py +4 -0
  211. lightly_studio/vendor/mobileclip/modules/image/image_projection.py +113 -0
  212. lightly_studio/vendor/mobileclip/modules/image/replknet.py +188 -0
  213. lightly_studio/vendor/mobileclip/modules/text/__init__.py +4 -0
  214. lightly_studio/vendor/mobileclip/modules/text/repmixer.py +281 -0
  215. lightly_studio/vendor/mobileclip/modules/text/tokenizer.py +38 -0
  216. lightly_studio/vendor/mobileclip/text_encoder.py +245 -0
  217. lightly_studio-0.3.1.dist-info/METADATA +520 -0
  218. lightly_studio-0.3.1.dist-info/RECORD +219 -0
  219. lightly_studio-0.3.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,268 @@
1
+ """Implementation for calculating Mean Average Precision (MAP)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections import defaultdict
7
+ from collections.abc import Sequence
8
+ from uuid import UUID
9
+
10
+ import torch
11
+ from pydantic import BaseModel
12
+ from torch import Tensor
13
+ from torchmetrics.detection.mean_ap import MeanAveragePrecision
14
+
15
+ from lightly_studio.models.annotation.annotation_base import AnnotationBaseTable
16
+
17
+
18
+ class DetectionMetricsMAP(BaseModel):
19
+ """Response for computing the MAP detection metric."""
20
+
21
+ name: str
22
+ map: float
23
+ map_small: float
24
+ map_medium: float
25
+ map_large: float
26
+ mar_1: float
27
+ mar_10: float
28
+ mar_100: float
29
+ mar_small: float
30
+ mar_medium: float
31
+ mar_large: float
32
+ map_50: float
33
+ map_75: float
34
+ map_per_class: dict[str, float] | None = None
35
+ mar_100_per_class: dict[str, float] | None = None
36
+ classes: list[int]
37
+
38
+ @property
39
+ def value(self) -> float:
40
+ """Backwards compatibility alias for map."""
41
+ return self.map
42
+
43
+ @property
44
+ def per_class(self) -> dict[str, float] | None:
45
+ """Backwards compatibility alias for map_per_class."""
46
+ return self.map_per_class
47
+
48
+
49
+ def calculate_map_metric( # noqa: C901
50
+ pred_annotations: Sequence[AnnotationBaseTable],
51
+ gt_annotations: Sequence[AnnotationBaseTable],
52
+ ) -> DetectionMetricsMAP:
53
+ """Calculate the Mean Average Precision (MAP) metric."""
54
+ if not gt_annotations or not pred_annotations:
55
+ return DetectionMetricsMAP(
56
+ name="mAP@.5:.95",
57
+ map=0.0,
58
+ map_small=0.0,
59
+ map_medium=0.0,
60
+ map_large=0.0,
61
+ mar_1=0.0,
62
+ mar_10=0.0,
63
+ mar_100=0.0,
64
+ mar_small=0.0,
65
+ mar_medium=0.0,
66
+ mar_large=0.0,
67
+ map_50=-1.0,
68
+ map_75=-1.0,
69
+ map_per_class=None,
70
+ mar_100_per_class=None,
71
+ classes=[],
72
+ )
73
+ uuid_to_int_label: dict[UUID, int] = {}
74
+
75
+ # Map sample_id to annotations.
76
+ sample_id_to_annotations = _group_by_sample_id(
77
+ pred_annotations=pred_annotations,
78
+ gt_annotations=gt_annotations,
79
+ )
80
+
81
+ # Create the MeanAveragePrecision object.
82
+ map_metric = MeanAveragePrecision(
83
+ box_format="xywh",
84
+ iou_thresholds=None,
85
+ class_metrics=True,
86
+ average="macro",
87
+ backend="faster_coco_eval",
88
+ )
89
+
90
+ # Compute MAP updating the object sample by sample.
91
+ for _sample_id, (
92
+ sample_pred_annotations,
93
+ sample_gt_annotations,
94
+ ) in sample_id_to_annotations.items():
95
+ # Convert to torchmetrics format.
96
+ prediction_tm = _convert_to_torchmetrics(
97
+ annotations=sample_pred_annotations,
98
+ is_prediction=True,
99
+ uuid_to_int_label_map=uuid_to_int_label,
100
+ )
101
+ ground_truth_tm = _convert_to_torchmetrics(
102
+ annotations=sample_gt_annotations,
103
+ is_prediction=False,
104
+ uuid_to_int_label_map=uuid_to_int_label,
105
+ )
106
+
107
+ # Update the metric.
108
+ map_metric.update(preds=[prediction_tm], target=[ground_truth_tm])
109
+
110
+ # Compute the final results.
111
+ results = map_metric.compute()
112
+ # Invert label map to recover original UUIDs
113
+ int_to_uuid = {v: k for k, v in uuid_to_int_label.items()}
114
+
115
+ # Helper to convert Tensor to Python list
116
+ def to_list(t: Tensor) -> list[float]:
117
+ if t.dim() == 0:
118
+ return [t.item()]
119
+ return t.tolist()
120
+
121
+ # Scalar metrics
122
+ map_val = results["map"].item()
123
+ map_small = results["map_small"].item()
124
+ map_medium = results["map_medium"].item()
125
+ map_large = results["map_large"].item()
126
+ mar_1 = results["mar_1"].item()
127
+ mar_10 = results["mar_10"].item()
128
+ mar_100 = results["mar_100"].item()
129
+ mar_small = results["mar_small"].item()
130
+ mar_medium = results["mar_medium"].item()
131
+ mar_large = results["mar_large"].item()
132
+
133
+ # IoU-specific metrics
134
+ map_50 = results.get("map_50")
135
+ map_50 = map_50.item() if isinstance(map_50, Tensor) and map_50.dim() == 0 else -1.0
136
+ map_75 = results.get("map_75")
137
+ map_75 = map_75.item() if isinstance(map_75, Tensor) and map_75.dim() == 0 else -1.0
138
+
139
+ # Per-class metrics
140
+ raw_map_pc = results.get("map_per_class")
141
+ per_class_dict: dict[str, float] | None = None
142
+ if raw_map_pc is not None:
143
+ pc_list = to_list(raw_map_pc)
144
+ per_class_dict = {}
145
+ for idx, val in enumerate(pc_list):
146
+ if not math.isnan(val):
147
+ per_class_dict[str(int_to_uuid[idx])] = val
148
+
149
+ # Per-class recall at 100 detections
150
+ raw_mar100_pc = results.get("mar_100_per_class")
151
+ mar_100_pc_dict: dict[str, float] | None = None
152
+ if raw_mar100_pc is not None:
153
+ m100_list = to_list(raw_mar100_pc)
154
+ mar_100_pc_dict = {}
155
+ for idx, val in enumerate(m100_list):
156
+ if not math.isnan(val):
157
+ mar_100_pc_dict[str(int_to_uuid[idx])] = val
158
+
159
+ # Observed classes
160
+ raw_classes = results.get("classes")
161
+ classes_list_output: list[int] = []
162
+ if raw_classes is not None:
163
+ classes_list_output = [int(x) for x in to_list(raw_classes)]
164
+
165
+ return DetectionMetricsMAP(
166
+ name="mAP@.5:.95",
167
+ map=map_val,
168
+ map_small=map_small,
169
+ map_medium=map_medium,
170
+ map_large=map_large,
171
+ mar_1=mar_1,
172
+ mar_10=mar_10,
173
+ mar_100=mar_100,
174
+ mar_small=mar_small,
175
+ mar_medium=mar_medium,
176
+ mar_large=mar_large,
177
+ map_50=map_50,
178
+ map_75=map_75,
179
+ map_per_class=per_class_dict,
180
+ mar_100_per_class=mar_100_pc_dict,
181
+ classes=classes_list_output,
182
+ )
183
+
184
+
185
+ def _group_by_sample_id(
186
+ pred_annotations: Sequence[AnnotationBaseTable],
187
+ gt_annotations: Sequence[AnnotationBaseTable],
188
+ ) -> dict[
189
+ UUID,
190
+ tuple[list[AnnotationBaseTable], list[AnnotationBaseTable]],
191
+ ]:
192
+ """Group prediction and ground truth annotations by sample_id.
193
+
194
+ Returns a dictionary with sample_id as key and a tuple of
195
+ (list of prediction annotations, list of ground truth annotations) as value.
196
+ """
197
+ sample_id_to_annotations: defaultdict[
198
+ UUID,
199
+ tuple[list[AnnotationBaseTable], list[AnnotationBaseTable]],
200
+ ] = defaultdict(lambda: ([], []))
201
+ for ann in pred_annotations:
202
+ sample_id_to_annotations[ann.sample_id][0].append(ann)
203
+ for ann in gt_annotations:
204
+ sample_id_to_annotations[ann.sample_id][1].append(ann)
205
+ return sample_id_to_annotations
206
+
207
+
208
+ def _convert_to_torchmetrics(
209
+ annotations: Sequence[AnnotationBaseTable],
210
+ is_prediction: bool,
211
+ uuid_to_int_label_map: dict[UUID, int],
212
+ ) -> dict[str, Tensor]:
213
+ """Convert annotations to torchmetrics format.
214
+
215
+ Args:
216
+ annotations: List of bounding box annotations.
217
+ is_prediction: Whether the annotations are predictions.
218
+ uuid_to_int_label_map: Map from UUID to integer label.
219
+
220
+ Returns:
221
+ Dictionary in torchmetrics format. For predictions the keys are
222
+ `boxes`, `scores`, `labels`. For ground truth they are `boxes`,
223
+ `labels`.
224
+
225
+ """
226
+ if not annotations:
227
+ empty_boxes = torch.empty((0, 4), dtype=torch.float32)
228
+ empty_labels = torch.empty((0,), dtype=torch.int64)
229
+ if is_prediction:
230
+ return {
231
+ "boxes": empty_boxes,
232
+ "scores": torch.empty((0,), dtype=torch.float32),
233
+ "labels": empty_labels,
234
+ }
235
+ return {"boxes": empty_boxes, "labels": empty_labels}
236
+
237
+ boxes = torch.tensor(
238
+ [
239
+ [
240
+ a.object_detection_details.x,
241
+ a.object_detection_details.y,
242
+ a.object_detection_details.width,
243
+ a.object_detection_details.height,
244
+ ]
245
+ for a in annotations
246
+ if a.object_detection_details is not None
247
+ ],
248
+ dtype=torch.float32,
249
+ )
250
+
251
+ mapped_labels = []
252
+ # Use the passed-in map instead of a global one
253
+ for uuid_label in (a.annotation_label_id for a in annotations):
254
+ if uuid_label not in uuid_to_int_label_map:
255
+ # Assign the next available integer ID based on the current map size
256
+ uuid_to_int_label_map[uuid_label] = len(uuid_to_int_label_map)
257
+ mapped_labels.append(uuid_to_int_label_map[uuid_label])
258
+
259
+ labels = torch.tensor(mapped_labels, dtype=torch.int64)
260
+
261
+ if is_prediction:
262
+ scores = torch.tensor([a.confidence for a in annotations], dtype=torch.float32)
263
+ return {
264
+ "boxes": boxes,
265
+ "scores": scores,
266
+ "labels": labels,
267
+ }
268
+ return {"boxes": boxes, "labels": labels}
@@ -0,0 +1 @@
1
+
File without changes
@@ -0,0 +1,171 @@
1
+ """This module defines the base annotation model."""
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import TYPE_CHECKING, List, Optional
5
+ from uuid import UUID, uuid4
6
+
7
+ from pydantic import BaseModel
8
+ from pydantic import Field as PydanticField
9
+ from sqlalchemy.orm import Mapped
10
+ from sqlmodel import Field, Relationship, SQLModel
11
+
12
+ from lightly_studio.models.annotation.instance_segmentation import (
13
+ InstanceSegmentationAnnotationTable,
14
+ InstanceSegmentationAnnotationView,
15
+ )
16
+ from lightly_studio.models.annotation.links import AnnotationTagLinkTable
17
+ from lightly_studio.models.annotation.object_detection import (
18
+ ObjectDetectionAnnotationTable,
19
+ ObjectDetectionAnnotationView,
20
+ )
21
+ from lightly_studio.models.annotation.semantic_segmentation import (
22
+ SemanticSegmentationAnnotationTable,
23
+ SemanticSegmentationAnnotationView,
24
+ )
25
+ from lightly_studio.models.annotation_task import (
26
+ AnnotationTaskTable,
27
+ AnnotationType,
28
+ )
29
+
30
+ if TYPE_CHECKING:
31
+ from lightly_studio.models.annotation_label import (
32
+ AnnotationLabelTable,
33
+ )
34
+ from lightly_studio.models.sample import (
35
+ SampleTable,
36
+ )
37
+ from lightly_studio.models.tag import TagTable
38
+ else:
39
+ TagTable = object
40
+ SampleTable = object
41
+ AnnotationLabelTable = object
42
+
43
+
44
+ class AnnotationBaseTable(SQLModel, table=True):
45
+ """Base class for all annotation models."""
46
+
47
+ __tablename__ = "annotation_base"
48
+
49
+ created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc), index=True)
50
+
51
+ annotation_id: UUID = Field(default_factory=uuid4, primary_key=True)
52
+ annotation_type: AnnotationType
53
+ annotation_label_id: UUID = Field(foreign_key="annotation_labels.annotation_label_id")
54
+ annotation_task_id: UUID = Field(
55
+ foreign_key="annotation_tasks.annotation_task_id",
56
+ )
57
+ confidence: Optional[float] = None
58
+ dataset_id: UUID = Field(foreign_key="datasets.dataset_id")
59
+ sample_id: UUID = Field(foreign_key="samples.sample_id")
60
+
61
+ annotation_label: Mapped["AnnotationLabelTable"] = Relationship(
62
+ sa_relationship_kwargs={"lazy": "select"},
63
+ )
64
+ annotation_task: Mapped["AnnotationTaskTable"] = Relationship(
65
+ sa_relationship_kwargs={"lazy": "select"},
66
+ )
67
+ sample: Mapped[Optional["SampleTable"]] = Relationship(
68
+ sa_relationship_kwargs={"lazy": "select"},
69
+ )
70
+ tags: Mapped[List["TagTable"]] = Relationship(
71
+ back_populates="annotations",
72
+ link_model=AnnotationTagLinkTable,
73
+ )
74
+
75
+ """ Details about object detection. """
76
+ object_detection_details: Mapped[Optional["ObjectDetectionAnnotationTable"]] = Relationship(
77
+ back_populates="annotation_base",
78
+ sa_relationship_kwargs={"lazy": "select"},
79
+ )
80
+
81
+ """ Details about instance segmentation. """
82
+ instance_segmentation_details: Mapped[Optional["InstanceSegmentationAnnotationTable"]] = (
83
+ Relationship(
84
+ back_populates="annotation_base",
85
+ sa_relationship_kwargs={"lazy": "select"},
86
+ )
87
+ )
88
+
89
+ """ Details about semantic segmentation. """
90
+ semantic_segmentation_details: Mapped[Optional["SemanticSegmentationAnnotationTable"]] = (
91
+ Relationship(
92
+ back_populates="annotation_base",
93
+ sa_relationship_kwargs={"lazy": "select"},
94
+ )
95
+ )
96
+
97
+
98
+ class AnnotationCreate(SQLModel):
99
+ """Input model for creating annotations."""
100
+
101
+ """ Required properties for all annotations. """
102
+ annotation_label_id: UUID
103
+ annotation_type: AnnotationType
104
+ annotation_task_id: UUID
105
+ confidence: Optional[float] = None
106
+ dataset_id: UUID
107
+ sample_id: UUID
108
+
109
+ """ Optional properties for object detection. """
110
+ x: Optional[float] = None
111
+ y: Optional[float] = None
112
+ width: Optional[float] = None
113
+ height: Optional[float] = None
114
+
115
+ """ Optional properties for instance and semantic segmentation. """
116
+ segmentation_mask: Optional[List[int]] = None
117
+
118
+
119
+ class AnnotationSampleView(SQLModel):
120
+ """Sample class for annotation view."""
121
+
122
+ file_path_abs: str
123
+ file_name: str
124
+ dataset_id: UUID
125
+ sample_id: UUID
126
+ width: int
127
+ height: int
128
+
129
+
130
+ class AnnotationView(SQLModel):
131
+ """Response model for bounding box annotation."""
132
+
133
+ class AnnotationLabel(SQLModel):
134
+ """Model used when retrieving an annotation label."""
135
+
136
+ annotation_label_name: str
137
+
138
+ sample_id: UUID
139
+ dataset_id: UUID
140
+ annotation_id: UUID
141
+ annotation_type: AnnotationType
142
+ annotation_label: AnnotationLabel
143
+ annotation_task_id: UUID
144
+ confidence: Optional[float] = None
145
+
146
+ object_detection_details: Optional[ObjectDetectionAnnotationView] = None
147
+ instance_segmentation_details: Optional[InstanceSegmentationAnnotationView] = None
148
+ semantic_segmentation_details: Optional[SemanticSegmentationAnnotationView] = None
149
+
150
+
151
+ class AnnotationWithSampleView(AnnotationView):
152
+ """Response model for bounding box annotation."""
153
+
154
+ sample: AnnotationSampleView
155
+
156
+
157
+ class AnnotationViewsWithCount(BaseModel):
158
+ """Response model for counted annotations."""
159
+
160
+ annotations: List[AnnotationWithSampleView] = PydanticField(..., alias="data")
161
+ total_count: int
162
+ next_cursor: Optional[int] = PydanticField(..., alias="nextCursor")
163
+
164
+ class Config: # noqa: D106
165
+ populate_by_name = True
166
+
167
+
168
+ class AnnotationDetailsView(AnnotationView):
169
+ """Representing detailed view of an annotation."""
170
+
171
+ sample: AnnotationSampleView
@@ -0,0 +1,56 @@
1
+ """Instance segmentation annotation models.
2
+
3
+ Instance segmentation combines object detection and semantic segmentation,
4
+ identifying objects and providing pixel-level masks for each instance.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING, List, Optional
8
+ from uuid import UUID, uuid4
9
+
10
+ from sqlalchemy import ARRAY, Column, Integer
11
+ from sqlalchemy.orm import Mapped
12
+ from sqlmodel import Field, Relationship, SQLModel
13
+
14
+ if TYPE_CHECKING:
15
+ from lightly_studio.models.annotation.annotation_base import (
16
+ AnnotationBaseTable,
17
+ )
18
+ else:
19
+ AnnotationBaseTable = object
20
+
21
+
22
+ class InstanceSegmentationAnnotationTable(SQLModel, table=True):
23
+ """Database table model for instance segmentation annotations."""
24
+
25
+ __tablename__ = "instance_segmentation_annotations"
26
+
27
+ annotation_id: UUID = Field(
28
+ default_factory=uuid4,
29
+ primary_key=True,
30
+ foreign_key="annotation_base.annotation_id",
31
+ )
32
+
33
+ annotation_base: Mapped["AnnotationBaseTable"] = Relationship(
34
+ back_populates="instance_segmentation_details"
35
+ )
36
+
37
+ x: float
38
+ y: float
39
+ width: float
40
+ height: float
41
+ # TODO(Kondrat 06/2025): We need to fix logic in the loader,
42
+ # because it shouldn't be optional.
43
+ # lightly_studio/dataset/loader.py#L148
44
+ segmentation_mask: Optional[List[int]] = Field(
45
+ default=None, sa_column=Column(ARRAY(Integer), nullable=True)
46
+ )
47
+
48
+
49
+ class InstanceSegmentationAnnotationView(SQLModel):
50
+ """API response model for instance segmentation annotations."""
51
+
52
+ x: float
53
+ y: float
54
+ width: float
55
+ height: float
56
+ segmentation_mask: Optional[List[int]] = None
@@ -0,0 +1,17 @@
1
+ """This module defines the base annotation model."""
2
+
3
+ from typing import Optional
4
+ from uuid import UUID
5
+
6
+ from sqlmodel import Field, SQLModel
7
+
8
+
9
+ class AnnotationTagLinkTable(SQLModel, table=True):
10
+ """Model defines the link table between annotations and tags."""
11
+
12
+ annotation_id: Optional[UUID] = Field(
13
+ default=None,
14
+ foreign_key="annotation_base.annotation_id",
15
+ primary_key=True,
16
+ )
17
+ tag_id: Optional[UUID] = Field(default=None, foreign_key="tags.tag_id", primary_key=True)
@@ -0,0 +1,47 @@
1
+ """Object detection annotation models.
2
+
3
+ Object detection identifies and locates objects in images using bounding boxes.
4
+ """
5
+
6
+ from typing import TYPE_CHECKING
7
+ from uuid import UUID, uuid4
8
+
9
+ from sqlalchemy.orm import Mapped
10
+ from sqlmodel import Field, Relationship, SQLModel
11
+
12
+ if TYPE_CHECKING:
13
+ from lightly_studio.models.annotation.annotation_base import (
14
+ AnnotationBaseTable,
15
+ )
16
+ else:
17
+ AnnotationBaseTable = object
18
+
19
+
20
+ class ObjectDetectionAnnotationTable(SQLModel, table=True):
21
+ """Database table model for object detection annotations."""
22
+
23
+ __tablename__ = "object_detection_annotations"
24
+
25
+ annotation_id: UUID = Field(
26
+ default_factory=uuid4,
27
+ primary_key=True,
28
+ foreign_key="annotation_base.annotation_id",
29
+ )
30
+
31
+ annotation_base: Mapped["AnnotationBaseTable"] = Relationship(
32
+ back_populates="object_detection_details"
33
+ )
34
+
35
+ x: float
36
+ y: float
37
+ width: float
38
+ height: float
39
+
40
+
41
+ class ObjectDetectionAnnotationView(SQLModel):
42
+ """API response model for object detection annotations."""
43
+
44
+ x: float
45
+ y: float
46
+ width: float
47
+ height: float
@@ -0,0 +1,44 @@
1
+ """This module defines the semantic segmentation annotation model.
2
+
3
+ Semantic segmentation is a computer vision task that assigns a class label to
4
+ each pixel in an image. This module provides the data models for storing and
5
+ managing semantic segmentation annotations.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, List
9
+ from uuid import UUID, uuid4
10
+
11
+ from sqlalchemy import ARRAY, Column, Integer
12
+ from sqlalchemy.orm import Mapped
13
+ from sqlmodel import Field, Relationship, SQLModel
14
+
15
+ if TYPE_CHECKING:
16
+ from lightly_studio.models.annotation.annotation_base import (
17
+ AnnotationBaseTable,
18
+ )
19
+ else:
20
+ AnnotationBaseTable = object
21
+
22
+
23
+ class SemanticSegmentationAnnotationTable(SQLModel, table=True):
24
+ """Model used to define semantic segmentation annotation table."""
25
+
26
+ __tablename__ = "semantic_segmentation_annotations"
27
+
28
+ annotation_id: UUID = Field(
29
+ default_factory=uuid4,
30
+ primary_key=True,
31
+ foreign_key="annotation_base.annotation_id",
32
+ )
33
+
34
+ segmentation_mask: List[int] = Field(sa_column=Column(ARRAY(Integer), nullable=True))
35
+
36
+ annotation_base: Mapped["AnnotationBaseTable"] = Relationship(
37
+ back_populates="semantic_segmentation_details"
38
+ )
39
+
40
+
41
+ class SemanticSegmentationAnnotationView(SQLModel):
42
+ """Response model for semantic segmentation annotation."""
43
+
44
+ segmentation_mask: List[int]
@@ -0,0 +1,47 @@
1
+ """This module defines the AnnotationLabel model for the application."""
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import TYPE_CHECKING, List
5
+ from uuid import UUID, uuid4
6
+
7
+ from sqlalchemy.orm import Mapped
8
+ from sqlmodel import Field, Relationship, SQLModel
9
+
10
+ if TYPE_CHECKING:
11
+ from lightly_studio.models.annotation.annotation_base import (
12
+ AnnotationBaseTable,
13
+ )
14
+
15
+ else:
16
+ AnnotationBaseTable = object
17
+
18
+
19
+ class AnnotationLabelBase(SQLModel):
20
+ """Base class for the AnnotationLabel model."""
21
+
22
+ annotation_label_name: str
23
+
24
+
25
+ class AnnotationLabelCreate(AnnotationLabelBase):
26
+ """Model used when creating an annotation label."""
27
+
28
+
29
+ class AnnotationLabelView(AnnotationLabelBase):
30
+ """Model used when retrieving an annotation label."""
31
+
32
+ annotation_label_id: UUID
33
+
34
+
35
+ class AnnotationLabelTable(AnnotationLabelBase, table=True):
36
+ """This class defines the AnnotationLabel model."""
37
+
38
+ __tablename__ = "annotation_labels"
39
+
40
+ annotation_label_id: UUID = Field(default_factory=uuid4, primary_key=True)
41
+ created_at: str = Field(
42
+ default_factory=lambda: datetime.now(timezone.utc),
43
+ index=True,
44
+ )
45
+ annotations: Mapped[List["AnnotationBaseTable"]] = Relationship(
46
+ back_populates="annotation_label",
47
+ )
@@ -0,0 +1,28 @@
1
+ """This module defines the AnnotationTask model."""
2
+
3
+ from datetime import datetime
4
+ from enum import Enum
5
+ from uuid import UUID, uuid4
6
+
7
+ from sqlmodel import Field, SQLModel
8
+
9
+
10
+ class AnnotationType(str, Enum):
11
+ """The type of annotation task."""
12
+
13
+ BBOX = "bbox"
14
+ CLASSIFICATION = "classification"
15
+ SEMANTIC_SEGMENTATION = "semantic_segmentation"
16
+ INSTANCE_SEGMENTATION = "instance_segmentation"
17
+ OBJECT_DETECTION = "object_detection"
18
+
19
+
20
+ class AnnotationTaskTable(SQLModel, table=True):
21
+ """The annotation task model."""
22
+
23
+ __tablename__ = "annotation_tasks"
24
+ annotation_task_id: UUID = Field(default_factory=uuid4, primary_key=True)
25
+ name: str
26
+ created_at: datetime = Field(default_factory=datetime.utcnow)
27
+ annotation_type: AnnotationType
28
+ is_prediction: bool