supervisely 6.73.294__py3-none-any.whl → 6.73.296__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 supervisely might be problematic. Click here for more details.

Files changed (39) hide show
  1. supervisely/cli/release/run.py +34 -51
  2. supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/avg_precision_by_class.py +1 -1
  3. supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/calibration_score.py +10 -0
  4. supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/explore_predictions.py +2 -2
  5. supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/outcome_counts.py +1 -1
  6. supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/overview.py +14 -8
  7. supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/pr_curve.py +1 -1
  8. supervisely/nn/benchmark/comparison/detection_visualization/vis_metrics/precision_recal_f1.py +2 -2
  9. supervisely/nn/benchmark/instance_segmentation/evaluation_params.yaml +6 -1
  10. supervisely/nn/benchmark/instance_segmentation/text_templates.py +4 -4
  11. supervisely/nn/benchmark/object_detection/base_vis_metric.py +1 -1
  12. supervisely/nn/benchmark/object_detection/evaluation_params.yaml +6 -1
  13. supervisely/nn/benchmark/object_detection/evaluator.py +1 -3
  14. supervisely/nn/benchmark/object_detection/metric_provider.py +59 -46
  15. supervisely/nn/benchmark/object_detection/text_templates.py +4 -4
  16. supervisely/nn/benchmark/object_detection/vis_metrics/confidence_distribution.py +20 -2
  17. supervisely/nn/benchmark/object_detection/vis_metrics/confidence_score.py +16 -0
  18. supervisely/nn/benchmark/object_detection/vis_metrics/explore_predictions.py +10 -5
  19. supervisely/nn/benchmark/object_detection/vis_metrics/key_metrics.py +1 -0
  20. supervisely/nn/benchmark/object_detection/vis_metrics/model_predictions.py +1 -1
  21. supervisely/nn/benchmark/object_detection/vis_metrics/outcome_counts.py +2 -57
  22. supervisely/nn/benchmark/object_detection/vis_metrics/outcome_counts_per_class.py +1 -1
  23. supervisely/nn/benchmark/object_detection/vis_metrics/overview.py +11 -3
  24. supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +1 -1
  25. supervisely/nn/benchmark/object_detection/vis_metrics/precision.py +18 -8
  26. supervisely/nn/benchmark/object_detection/vis_metrics/recall.py +13 -3
  27. supervisely/nn/benchmark/object_detection/visualizer.py +1 -1
  28. supervisely/nn/benchmark/utils/__init__.py +0 -1
  29. supervisely/nn/benchmark/utils/detection/__init__.py +1 -2
  30. supervisely/nn/benchmark/utils/detection/calculate_metrics.py +31 -37
  31. supervisely/nn/benchmark/visualization/evaluation_result.py +2 -4
  32. supervisely/nn/benchmark/visualization/vis_click_data.py +1 -3
  33. {supervisely-6.73.294.dist-info → supervisely-6.73.296.dist-info}/METADATA +1 -1
  34. {supervisely-6.73.294.dist-info → supervisely-6.73.296.dist-info}/RECORD +38 -39
  35. supervisely/nn/benchmark/utils/detection/metric_provider.py +0 -533
  36. {supervisely-6.73.294.dist-info → supervisely-6.73.296.dist-info}/LICENSE +0 -0
  37. {supervisely-6.73.294.dist-info → supervisely-6.73.296.dist-info}/WHEEL +0 -0
  38. {supervisely-6.73.294.dist-info → supervisely-6.73.296.dist-info}/entry_points.txt +0 -0
  39. {supervisely-6.73.294.dist-info → supervisely-6.73.296.dist-info}/top_level.txt +0 -0
@@ -1,33 +1,41 @@
1
- from pathlib import Path
2
- import sys
3
- import os
4
1
  import json
5
- import traceback
6
- import click
2
+ import os
7
3
  import re
8
4
  import subprocess
5
+ import sys
6
+ import traceback
7
+ from pathlib import Path
8
+
9
+ import click
9
10
  import git
10
11
  from dotenv import load_dotenv
11
12
  from rich.console import Console
12
13
 
13
-
14
14
  MIN_SUPPORTED_INSTANCE_VERSION = "6.8.3"
15
15
 
16
+ from supervisely._utils import setup_certificates
16
17
  from supervisely.cli.release.release import (
18
+ delete_tag,
17
19
  find_tag_in_repo,
18
- push_tag,
19
20
  get_app_from_instance,
20
- get_module_root,
21
- get_module_path,
22
21
  get_appKey,
23
- release,
24
- slug_is_valid,
25
- delete_tag,
22
+ get_created_at,
26
23
  get_instance_version,
24
+ get_module_path,
25
+ get_module_root,
27
26
  get_user,
28
- get_created_at,
27
+ push_tag,
28
+ release,
29
+ slug_is_valid,
29
30
  )
30
31
 
32
+ load_dotenv(os.path.expanduser("~/supervisely.env"))
33
+ try:
34
+ setup_certificates()
35
+ except Exception as e:
36
+ print(f"Error setting up certificates: {e}")
37
+ traceback.print_exc()
38
+
31
39
 
32
40
  def _check_git(repo: git.Repo):
33
41
  console = Console()
@@ -39,11 +47,7 @@ def _check_git(repo: git.Repo):
39
47
  )
40
48
  console.print(" Untracked files:")
41
49
  console.print(
42
- "\n".join(
43
- [f" {i+1}) " + file for i, file in enumerate(repo.untracked_files)][
44
- :20
45
- ]
46
- )
50
+ "\n".join([f" {i+1}) " + file for i, file in enumerate(repo.untracked_files)][:20])
47
51
  )
48
52
  if len(repo.untracked_files) > 20:
49
53
  console.print(f" ... and {len(repo.untracked_files) - 20} more.")
@@ -98,17 +102,13 @@ def _ask_release_version(repo: git.Repo):
98
102
  console = Console()
99
103
 
100
104
  def extract_version(tag_name):
101
- return tag_name[len("sly-release-"):] if tag_name.startswith("sly-release-") else tag_name
105
+ return tag_name[len("sly-release-") :] if tag_name.startswith("sly-release-") else tag_name
102
106
 
103
107
  try:
104
108
  sly_release_tags = [
105
- tag.name
106
- for tag in repo.tags
107
- if _check_release_version(extract_version(tag.name))
109
+ tag.name for tag in repo.tags if _check_release_version(extract_version(tag.name))
108
110
  ]
109
- sly_release_tags.sort(
110
- key=lambda tag: [int(n) for n in extract_version(tag)[1:].split(".")]
111
- )
111
+ sly_release_tags.sort(key=lambda tag: [int(n) for n in extract_version(tag)[1:].split(".")])
112
112
  current_release_version = extract_version(sly_release_tags[-1])[1:]
113
113
  suggested_release_version = ".".join(
114
114
  [
@@ -232,9 +232,7 @@ def run(
232
232
  # get module path and check if it is a git repo
233
233
  module_root = get_module_root(app_directory)
234
234
  if sub_app_directory is not None:
235
- sub_app_directory = (
236
- Path(sub_app_directory).absolute().relative_to(module_root).as_posix()
237
- )
235
+ sub_app_directory = Path(sub_app_directory).absolute().relative_to(module_root).as_posix()
238
236
  module_path = get_module_path(module_root, sub_app_directory)
239
237
  try:
240
238
  repo = git.Repo(module_root)
@@ -244,8 +242,7 @@ def run(
244
242
  )
245
243
  return False
246
244
 
247
- # get server address
248
- load_dotenv(os.path.expanduser("~/supervisely.env"))
245
+ # load server address
249
246
  server_address = os.getenv("SERVER_ADDRESS", None)
250
247
  if server_address is None:
251
248
  console.print(
@@ -314,9 +311,7 @@ def run(
314
311
  with open(module_path.joinpath("config.json"), "r") as f:
315
312
  config = json.load(f)
316
313
  except FileNotFoundError:
317
- console.print(
318
- f'[red][Error][/] Cannot find "config.json" file at "{module_path}"'
319
- )
314
+ console.print(f'[red][Error][/] Cannot find "config.json" file at "{module_path}"')
320
315
  return False
321
316
  except json.JSONDecodeError as e:
322
317
  console.print(
@@ -379,9 +374,7 @@ def run(
379
374
  f'[red][Error][/] Could not access "{server_address}". Check that instance is running and accessible'
380
375
  )
381
376
  return False
382
- module_exists_label = (
383
- "[yellow bold]updated[/]" if app_exist else "[green bold]created[/]"
384
- )
377
+ module_exists_label = "[yellow bold]updated[/]" if app_exist else "[green bold]created[/]"
385
378
 
386
379
  # print details
387
380
  console.print(f"Application directory:\t[green]{module_path}[/]")
@@ -390,9 +383,7 @@ def run(
390
383
  console.print(f"User:\t\t\t[green]{user_login} (id: {user_id})[/]")
391
384
  if release_token:
392
385
  console.print(f"Release token:\t\t[green]{hided(release_token)}[/]")
393
- console.print(
394
- f"Release User: \t\t[green]{release_user_login} (id: {release_user_id})[/]"
395
- )
386
+ console.print(f"Release User: \t\t[green]{release_user_login} (id: {release_user_id})[/]")
396
387
  console.print(f"Git branch:\t\t[green]{repo.active_branch}[/]")
397
388
  console.print(f"App Name:\t\t[green]{app_name}[/]")
398
389
  console.print(f"App Key:\t\t[green]{hided(appKey)}[/]\n")
@@ -411,14 +402,10 @@ def run(
411
402
  if release_version is None:
412
403
  release_version = _ask_release_version(repo)
413
404
  if not _check_release_version(release_version):
414
- console.print(
415
- '[red][Error][/] Incorrect release version. Should be of format "vX.X.X"'
416
- )
405
+ console.print('[red][Error][/] Incorrect release version. Should be of format "vX.X.X"')
417
406
  return False
418
407
  else:
419
- console.print(
420
- f'Release version will be "{repo.active_branch.name}" as the branch name'
421
- )
408
+ console.print(f'Release version will be "{repo.active_branch.name}" as the branch name')
422
409
  release_version = repo.active_branch.name
423
410
 
424
411
  # get release name
@@ -534,9 +521,7 @@ def run(
534
521
  return True
535
522
 
536
523
 
537
- @click.command(
538
- help="This app allows you to release your aplication to Supervisely platform"
539
- )
524
+ @click.command(help="This app allows you to release your aplication to Supervisely platform")
540
525
  @click.option(
541
526
  "-p",
542
527
  "--path",
@@ -554,9 +539,7 @@ def run(
554
539
  required=False,
555
540
  help='[Optional] Release version in format "vX.X.X"',
556
541
  )
557
- @click.option(
558
- "--release-description", required=False, help="[Optional] Release description"
559
- )
542
+ @click.option("--release-description", required=False, help="[Optional] Release description")
560
543
  @click.option(
561
544
  "--share",
562
545
  is_flag=True,
@@ -114,7 +114,7 @@ class AveragePrecisionByClass(BaseVisMetrics):
114
114
  {
115
115
  "type": "tag",
116
116
  "tagId": "confidence",
117
- "value": [eval_result.mp.f1_optimal_conf, 1],
117
+ "value": [eval_result.mp.conf_threshold, 1],
118
118
  },
119
119
  {"type": "tag", "tagId": "outcome", "value": "TP"},
120
120
  {"type": "specific_objects", "tagId": None, "value": list(obj_ids)},
@@ -154,6 +154,16 @@ class CalibrationScore(BaseVisMetrics):
154
154
  line=dict(color="gray", width=2, dash="dash"),
155
155
  name=f"F1-optimal threshold ({eval_result.name})",
156
156
  )
157
+ if eval_result.mp.custom_conf_threshold is not None:
158
+ fig.add_shape(
159
+ type="line",
160
+ x0=eval_result.mp.custom_conf_threshold,
161
+ x1=eval_result.mp.custom_conf_threshold,
162
+ y0=0,
163
+ y1=eval_result.mp.custom_f1,
164
+ line=dict(color="black", width=2, dash="dash"),
165
+ name=f"Confidence threshold ({eval_result.name})",
166
+ )
157
167
 
158
168
  # Update the layout
159
169
  fig.update_layout(
@@ -76,7 +76,7 @@ class ExplorePredictions(BaseVisMetrics):
76
76
  anns = eval_res.api.annotation.download_batch(dataset_info.id, images_ids, force_metadata_for_links=False)
77
77
  annotations.append(anns)
78
78
  skip_tags_filtering.append(False)
79
- min_conf = min(min_conf, eval_res.mp.f1_optimal_conf)
79
+ min_conf = min(min_conf, eval_res.mp.conf_threshold)
80
80
 
81
81
  images = list(i for x in zip(*images) for i in x)
82
82
  annotations = list(i for x in zip(*annotations) for i in x)
@@ -127,7 +127,7 @@ class ExplorePredictions(BaseVisMetrics):
127
127
  current_images_infos = sorted(current_images_infos, key=lambda x: names.index(x.name))
128
128
  images_ids.append([image_info.id for image_info in current_images_infos])
129
129
 
130
- min_conf = min(min_conf, eval_res.mp.f1_optimal_conf)
130
+ min_conf = min(min_conf, eval_res.mp.conf_threshold)
131
131
 
132
132
  explore["imagesIds"] = list(i for x in zip(*images_ids) for i in x)
133
133
  explore["filters"] = [{"type": "tag", "tagId": "confidence", "value": [min_conf, 1]}]
@@ -276,7 +276,7 @@ class OutcomeCounts(BaseVisMetrics):
276
276
  title = f"{model_name}. {outcome}: {len(obj_ids)} object{'s' if len(obj_ids) > 1 else ''}"
277
277
  outcome_dict["title"] = title
278
278
  outcome_dict["imagesIds"] = list(img_ids)
279
- thr = eval_result.mp.f1_optimal_conf
279
+ thr = eval_result.mp.conf_threshold
280
280
  if outcome == "FN":
281
281
  outcome_dict["filters"] = [
282
282
  {"type": "specific_objects", "tagId": None, "value": list(obj_ids)},
@@ -27,6 +27,7 @@ class Overview(BaseVisMetrics):
27
27
  evaluation result metrics displayed
28
28
  """
29
29
  super().__init__(vis_texts, eval_results)
30
+ self.team_id = None # will be set in the visualizer
30
31
 
31
32
  @property
32
33
  def overview_md(self) -> List[MarkdownWidget]:
@@ -120,8 +121,7 @@ class Overview(BaseVisMetrics):
120
121
  if idx == 3 and not same_iou_thr:
121
122
  continue
122
123
  metric_name = metric_renames_map.get(metric, metric)
123
- values = [m[metric] for m in all_metrics]
124
- values = [v if v is not None else "―" for v in values]
124
+ values = [m.get(metric, "―") for m in all_metrics]
125
125
  values = [round(v, 2) if isinstance(v, float) else v for v in values]
126
126
  row = [metric_name] + values
127
127
  dct = {"row": row, "id": metric, "items": row}
@@ -247,12 +247,18 @@ class Overview(BaseVisMetrics):
247
247
 
248
248
  iou_thrs_map = defaultdict(set)
249
249
  matched = True
250
- for eval_result in self.eval_results:
251
- for cat_id, iou_thr in eval_result.mp.iou_threshold_per_class.items():
252
- iou_thrs_map[cat_id].add(iou_thr)
253
- if len(iou_thrs_map[cat_id]) > 1:
254
- matched = False
255
- break
250
+
251
+ if not all([not r.different_iou_thresholds_per_class for r in self.eval_results]):
252
+ matched = False
253
+ else:
254
+ for eval_result in self.eval_results:
255
+ iou_thrs_per_class = eval_result.mp.iou_threshold_per_class
256
+ if iou_thrs_per_class is not None:
257
+ for cat_id, iou_thr in eval_result.mp.iou_threshold_per_class.items():
258
+ iou_thrs_map[cat_id].add(iou_thr)
259
+ if len(iou_thrs_map[cat_id]) > 1:
260
+ matched = False
261
+ break
256
262
 
257
263
  if matched:
258
264
  return None
@@ -19,7 +19,7 @@ class PrCurve(BaseVisMetrics):
19
19
  @property
20
20
  def markdown_widget(self) -> MarkdownWidget:
21
21
  text: str = getattr(self.vis_texts, self.MARKDOWN_PR_CURVE).format(
22
- self.vis_texts.definitions.f1_score
22
+ self.vis_texts.definitions.about_pr_tradeoffs
23
23
  )
24
24
  return MarkdownWidget(
25
25
  name=self.MARKDOWN_PR_CURVE, title="mAP & Precision-Recall Curve", text=text
@@ -205,7 +205,7 @@ class PrecisionRecallF1(BaseVisMetrics):
205
205
  {
206
206
  "type": "tag",
207
207
  "tagId": "confidence",
208
- "value": [eval_result.mp.f1_optimal_conf, 1],
208
+ "value": [eval_result.mp.conf_threshold, 1],
209
209
  },
210
210
  {"type": "tag", "tagId": "outcome", "value": "TP"},
211
211
  {"type": "specific_objects", "tagId": None, "value": list(obj_ids)},
@@ -293,7 +293,7 @@ class PrecisionRecallF1(BaseVisMetrics):
293
293
  {
294
294
  "type": "tag",
295
295
  "tagId": "confidence",
296
- "value": [eval_result.mp.f1_optimal_conf, 1],
296
+ "value": [eval_result.mp.conf_threshold, 1],
297
297
  },
298
298
  {"type": "tag", "tagId": "outcome", "value": "TP"},
299
299
  {"type": "specific_objects", "tagId": None, "value": list(obj_ids)},
@@ -1,2 +1,7 @@
1
- # Intersection over Union threshold that will be used for objects mathcing
1
+ # Intersection over Union threshold that will be used for object matching.
2
+ # It mostly affects visualizations, such as Outcome Counts, Confusion Matrix, and image previews.
2
3
  iou_threshold: 0.5
4
+
5
+ # Confidence threshold.
6
+ # Set 'auto' to calculate the optimal confidence threshold.
7
+ confidence_threshold: auto
@@ -43,7 +43,7 @@ markdown_overview = """
43
43
  - **Ground Truth project**: <a href="/projects/{}/datasets" target="_blank">{}</a>, {}{}
44
44
  {}
45
45
  - **IoU threshold**: {}
46
- - **Optimal confidence threshold**: {} (calculated automatically), <a href="{}" target="_blank">learn more</a>.
46
+ {}
47
47
  - **Averaging across IoU thresholds:** {}, <a href="{}" target="_blank">learn more</a>.
48
48
 
49
49
  Learn more about Model Benchmark, implementation details, and how to use the charts in our <a href="{}" target="_blank">Technical Report</a>.
@@ -73,7 +73,7 @@ In this section you can visually assess the model performance through examples.
73
73
 
74
74
  > Click on the image to view the **Ground Truth**, **Prediction**, and **Difference** annotations side-by-side.
75
75
 
76
- > Filtering options allow you to adjust the confidence threshold (only for predictions) and the model's false outcomes (only for differences). Differences are calculated only for the optimal confidence threshold, allowing you to focus on the most accurate predictions made by the model.
76
+ > Filtering options allow you to adjust the confidence threshold (only for predictions) and the model's false outcomes (only for differences). {}
77
77
  """
78
78
 
79
79
  markdown_predictions_table = """### Prediction details for every image
@@ -100,7 +100,7 @@ To measure this, we calculate **Recall**. Recall counts errors, when the model d
100
100
  """
101
101
 
102
102
  notification_recall = {
103
- "title": "Recall = {}",
103
+ "title": "{}",
104
104
  "description": "The model correctly found <b>{} of {}</b> total instances in the dataset.",
105
105
  }
106
106
 
@@ -123,7 +123,7 @@ To measure this, we calculate **Precision**. Precision counts errors, when the m
123
123
  """
124
124
 
125
125
  notification_precision = {
126
- "title": "Precision = {}",
126
+ "title": "{}",
127
127
  "description": "The model correctly predicted <b>{} of {}</b> predictions made by the model in total.",
128
128
  }
129
129
 
@@ -42,7 +42,7 @@ class DetectionVisMetric(BaseVisMetric):
42
42
  {
43
43
  "type": "tag",
44
44
  "tagId": "confidence",
45
- "value": [self.eval_result.mp.f1_optimal_conf, 1],
45
+ "value": [self.eval_result.mp.conf_threshold, 1],
46
46
  },
47
47
  {"type": "tag", "tagId": "outcome", "value": "TP"},
48
48
  {"type": "specific_objects", "tagId": None, "value": list(obj_ids)},
@@ -1,2 +1,7 @@
1
- # Intersection over Union threshold that will be used for objects mathcing
1
+ # Intersection over Union threshold that will be used for object matching.
2
+ # It mostly affects visualizations, such as Outcome Counts, Confusion Matrix, and image previews.
2
3
  iou_threshold: 0.5
4
+
5
+ # Confidence threshold.
6
+ # Set 'auto' to calculate the optimal confidence threshold.
7
+ confidence_threshold: auto
@@ -55,9 +55,7 @@ class ObjectDetectionEvalResult(BaseEvalResult):
55
55
  self.coco_gt, self.coco_dt = read_coco_datasets(self.coco_gt, self.coco_dt)
56
56
 
57
57
  self.mp = MetricProvider(
58
- self.eval_data["matches"],
59
- self.eval_data["coco_metrics"],
60
- self.eval_data["params"],
58
+ self.eval_data,
61
59
  self.coco_gt,
62
60
  self.coco_dt,
63
61
  )
@@ -56,7 +56,7 @@ def filter_by_conf(matches: list, conf: float):
56
56
 
57
57
 
58
58
  class MetricProvider:
59
- def __init__(self, matches: list, coco_metrics: dict, params: dict, cocoGt, cocoDt):
59
+ def __init__(self, eval_data: dict, cocoGt, cocoDt):
60
60
  """
61
61
  Main class for calculating prediction metrics.
62
62
 
@@ -71,11 +71,16 @@ class MetricProvider:
71
71
  :param cocoDt: COCO object with predicted annotations
72
72
  :type cocoDt: COCO
73
73
  """
74
- self.matches = matches
75
- self.coco_metrics = coco_metrics
76
- self.params = params
74
+ self.eval_data = eval_data
75
+ self.matches = eval_data["matches"]
76
+ self.coco_metrics = eval_data["coco_metrics"]
77
+ self.params = eval_data["params"]
77
78
  self.cocoGt = cocoGt
78
79
  self.cocoDt = cocoDt
80
+ self.coco_mAP = self.coco_metrics["mAP"]
81
+ self.coco_precision = self.coco_metrics["precision"]
82
+ self.iouThrs = self.params["iouThrs"]
83
+ self.recThrs = self.params["recThrs"]
79
84
 
80
85
  self.metric_names = METRIC_NAMES
81
86
 
@@ -83,41 +88,31 @@ class MetricProvider:
83
88
  self.cat_ids = cocoGt.getCatIds()
84
89
  self.cat_names = [cocoGt.cats[cat_id]["name"] for cat_id in self.cat_ids]
85
90
 
86
- # eval_data
87
- self.matches = matches
88
- self.coco_mAP = coco_metrics["mAP"]
89
- self.coco_precision = coco_metrics["precision"]
90
- self.iouThrs = params["iouThrs"]
91
- self.recThrs = params["recThrs"]
92
-
93
91
  # Evaluation params
94
- eval_params = params.get("evaluation_params", {})
92
+ eval_params = self.params.get("evaluation_params", {})
95
93
  self.iou_threshold = eval_params.get("iou_threshold", 0.5)
96
94
  self.iou_threshold_idx = np.where(np.isclose(self.iouThrs, self.iou_threshold))[0][0]
97
95
  self.iou_threshold_per_class = eval_params.get("iou_threshold_per_class")
98
- self.iou_idx_per_class = params.get("iou_idx_per_class") # {cat id: iou_idx}
99
- if self.iou_threshold_per_class is not None:
100
- # TODO: temporary solution
101
- eval_params["average_across_iou_thresholds"] = False
96
+ self.iou_idx_per_class = self.params.get("iou_idx_per_class") # {cat id: iou_idx}
102
97
  self.average_across_iou_thresholds = eval_params.get("average_across_iou_thresholds", True)
103
98
 
104
99
  def calculate(self):
105
- self.m_full = _MetricProvider(
106
- self.matches, self.coco_metrics, self.params, self.cocoGt, self.cocoDt
107
- )
100
+ self.m_full = _MetricProvider(self.matches, self.eval_data, self.cocoGt, self.cocoDt)
108
101
  self.m_full._calculate_score_profile()
109
102
 
110
103
  # Find optimal confidence threshold
111
104
  self.f1_optimal_conf, self.best_f1 = self.m_full.get_f1_optimal_conf()
105
+ self.custom_conf_threshold, self.custom_f1 = self.m_full.get_custom_conf_threshold()
106
+
107
+ # Confidence threshold that will be used in visualizations
108
+ self.conf_threshold = self.custom_conf_threshold or self.f1_optimal_conf
112
109
 
113
110
  # Filter by optimal confidence threshold
114
- if self.f1_optimal_conf is not None:
115
- matches_filtered = filter_by_conf(self.matches, self.f1_optimal_conf)
111
+ if self.conf_threshold is not None:
112
+ matches_filtered = filter_by_conf(self.matches, self.conf_threshold)
116
113
  else:
117
114
  matches_filtered = self.matches
118
- self.m = _MetricProvider(
119
- matches_filtered, self.coco_metrics, self.params, self.cocoGt, self.cocoDt
120
- )
115
+ self.m = _MetricProvider(matches_filtered, self.eval_data, self.cocoGt, self.cocoDt)
121
116
  self.matches_filtered = matches_filtered
122
117
  self.m._init_counts()
123
118
 
@@ -155,7 +150,7 @@ class MetricProvider:
155
150
  ap_by_class = dict(zip(self.cat_names, ap_by_class))
156
151
  ap_custom_by_class = self.AP_custom_per_class().tolist()
157
152
  ap_custom_by_class = dict(zip(self.cat_names, ap_custom_by_class))
158
- return {
153
+ data = {
159
154
  "mAP": base["mAP"],
160
155
  "AP50": self.coco_metrics.get("AP50"),
161
156
  "AP75": self.coco_metrics.get("AP75"),
@@ -172,6 +167,9 @@ class MetricProvider:
172
167
  "AP_by_class": ap_by_class,
173
168
  f"AP{iou_name}_by_class": ap_custom_by_class,
174
169
  }
170
+ if self.custom_conf_threshold is not None:
171
+ data["custom_confidence_threshold"] = self.custom_conf_threshold
172
+ return data
175
173
 
176
174
  def key_metrics(self):
177
175
  iou_name = int(self.iou_threshold * 100)
@@ -187,7 +185,7 @@ class MetricProvider:
187
185
  iou_name = int(self.iou_threshold * 100)
188
186
  if self.iou_threshold_per_class is not None:
189
187
  iou_name = "_custom"
190
- return {
188
+ data = {
191
189
  "mAP": table["mAP"],
192
190
  "AP50": table["AP50"],
193
191
  "AP75": table["AP75"],
@@ -198,8 +196,11 @@ class MetricProvider:
198
196
  "Avg. IoU": table["iou"],
199
197
  "Classification Acc.": table["classification_accuracy"],
200
198
  "Calibration Score": table["calibration_score"],
201
- "optimal confidence threshold": table["f1_optimal_conf"],
199
+ "Optimal confidence threshold": table["f1_optimal_conf"],
202
200
  }
201
+ if self.custom_conf_threshold is not None:
202
+ data["Custom confidence threshold"] = table["custom_confidence_threshold"]
203
+ return data
203
204
 
204
205
  def AP_per_class(self):
205
206
  s = self.coco_precision[:, :, :, 0, 2].copy()
@@ -262,25 +263,27 @@ class MetricProvider:
262
263
 
263
264
 
264
265
  class _MetricProvider:
265
- def __init__(self, matches: list, coco_metrics: dict, params: dict, cocoGt, cocoDt):
266
+ def __init__(self, matches: list, eval_data: dict, cocoGt, cocoDt):
266
267
  """
267
268
  type cocoGt: COCO
268
269
  type cocoDt: COCO
269
270
  """
270
271
 
272
+ self.matches = matches
273
+ self.eval_data = eval_data
274
+ self.coco_metrics = eval_data["coco_metrics"]
275
+ self.params = eval_data["params"]
271
276
  self.cocoGt = cocoGt
277
+ self.cocoDt = cocoDt
278
+ self.coco_mAP = self.coco_metrics["mAP"]
279
+ self.coco_precision = self.coco_metrics["precision"]
280
+ self.iouThrs = self.params["iouThrs"]
281
+ self.recThrs = self.params["recThrs"]
272
282
 
273
283
  # metainfo
274
284
  self.cat_ids = cocoGt.getCatIds()
275
285
  self.cat_names = [cocoGt.cats[cat_id]["name"] for cat_id in self.cat_ids]
276
286
 
277
- # eval_data
278
- self.matches = matches
279
- self.coco_mAP = coco_metrics["mAP"]
280
- self.coco_precision = coco_metrics["precision"]
281
- self.iouThrs = params["iouThrs"]
282
- self.recThrs = params["recThrs"]
283
-
284
287
  # Matches
285
288
  self.tp_matches = [m for m in self.matches if m["type"] == "TP"]
286
289
  self.fp_matches = [m for m in self.matches if m["type"] == "FP"]
@@ -290,13 +293,12 @@ class _MetricProvider:
290
293
  self.ious = np.array([m["iou"] for m in self.tp_matches])
291
294
 
292
295
  # Evaluation params
293
- self.params = params
294
296
  self.iou_idx_per_class = np.array(
295
- [params["iou_idx_per_class"][cat_id] for cat_id in self.cat_ids]
297
+ [self.params["iou_idx_per_class"][cat_id] for cat_id in self.cat_ids]
296
298
  )[:, None]
297
- eval_params = params.get("evaluation_params", {})
299
+ eval_params = self.params.get("evaluation_params", {})
298
300
  self.average_across_iou_thresholds = eval_params.get("average_across_iou_thresholds", True)
299
-
301
+
300
302
  def _init_counts(self):
301
303
  cat_ids = self.cat_ids
302
304
  iouThrs = self.iouThrs
@@ -307,9 +309,6 @@ class _MetricProvider:
307
309
  ious.append(match["iou"])
308
310
  cats.append(cat_id_to_idx[match["category_id"]])
309
311
  ious = np.array(ious) + np.spacing(1)
310
- if 0.8999999999999999 in iouThrs:
311
- iouThrs = iouThrs.copy()
312
- iouThrs[iouThrs == 0.8999999999999999] = 0.9
313
312
  iou_idxs = np.searchsorted(iouThrs, ious) - 1
314
313
  cats = np.array(cats)
315
314
  # TP
@@ -345,9 +344,16 @@ class _MetricProvider:
345
344
  self.FP_count = int(self._take_iou_thresholds(false_positives).sum())
346
345
  self.FN_count = int(self._take_iou_thresholds(false_negatives).sum())
347
346
 
347
+ # self.true_positives = self.eval_data["true_positives"]
348
+ # self.false_negatives = self.eval_data["false_negatives"]
349
+ # self.false_positives = self.eval_data["false_positives"]
350
+ # self.TP_count = int(self._take_iou_thresholds(self.true_positives).sum())
351
+ # self.FP_count = int(self._take_iou_thresholds(self.false_positives).sum())
352
+ # self.FN_count = int(self._take_iou_thresholds(self.false_negatives).sum())
353
+
348
354
  def _take_iou_thresholds(self, x):
349
355
  return np.take_along_axis(x, self.iou_idx_per_class, axis=1)
350
-
356
+
351
357
  def base_metrics(self):
352
358
  if self.average_across_iou_thresholds:
353
359
  tp = self.true_positives
@@ -495,9 +501,6 @@ class _MetricProvider:
495
501
  )
496
502
  scores = np.array([m["score"] for m in matches_sorted])
497
503
  ious = np.array([m["iou"] if m["type"] == "TP" else 0.0 for m in matches_sorted])
498
- if 0.8999999999999999 in iouThrs:
499
- iouThrs = iouThrs.copy()
500
- iouThrs[iouThrs == 0.8999999999999999] = 0.9
501
504
  iou_idxs = np.searchsorted(iouThrs, ious + np.spacing(1))
502
505
 
503
506
  # Check
@@ -565,6 +568,16 @@ class _MetricProvider:
565
568
  best_f1 = self.score_profile["f1"][argmax]
566
569
  return f1_optimal_conf, best_f1
567
570
 
571
+ def get_custom_conf_threshold(self):
572
+ if (~np.isnan(self.score_profile["f1"])).sum() == 0:
573
+ return None, None
574
+ conf_threshold = self.params.get("evaluation_params", {}).get("confidence_threshold")
575
+ if conf_threshold is not None and conf_threshold != "auto":
576
+ idx = np.argmin(np.abs(self.score_profile["scores"] - conf_threshold))
577
+ custom_f1 = self.score_profile["f1"][idx]
578
+ return conf_threshold, custom_f1
579
+ return None, None
580
+
568
581
  def calibration_curve(self):
569
582
  from sklearn.calibration import ( # pylint: disable=import-error
570
583
  calibration_curve,
@@ -43,7 +43,7 @@ markdown_overview = """
43
43
  - **Ground Truth project**: <a href="/projects/{}/datasets" target="_blank">{}</a>, {}{}
44
44
  {}
45
45
  - **IoU threshold**: {}
46
- - **Optimal confidence threshold**: {} (calculated automatically), <a href="{}" target="_blank">learn more</a>.
46
+ {}
47
47
  - **Averaging across IoU thresholds:** {}, <a href="{}" target="_blank">learn more</a>.
48
48
 
49
49
  Learn more about Model Benchmark, implementation details, and how to use the charts in our <a href="{}" target="_blank">Technical Report</a>.
@@ -78,7 +78,7 @@ In this section you can visually assess the model performance through examples.
78
78
 
79
79
  > Click on the image to view the **Ground Truth**, **Prediction**, and **Difference** annotations side-by-side.
80
80
 
81
- > Filtering options allow you to adjust the confidence threshold (only for predictions) and the model's false outcomes (only for differences). Differences are calculated only for the optimal confidence threshold, allowing you to focus on the most accurate predictions made by the model.
81
+ > Filtering options allow you to adjust the confidence threshold (only for predictions) and the model's false outcomes (only for differences). {}
82
82
  """
83
83
 
84
84
  markdown_predictions_gallery = """
@@ -125,7 +125,7 @@ To measure this, we calculate **Recall**. Recall counts errors, when the model d
125
125
  """
126
126
 
127
127
  notification_recall = {
128
- "title": "Recall = {}",
128
+ "title": "{}",
129
129
  "description": "The model correctly found <b>{} of {}</b> total instances in the dataset.",
130
130
  }
131
131
 
@@ -148,7 +148,7 @@ To measure this, we calculate **Precision**. Precision counts errors, when the m
148
148
  """
149
149
 
150
150
  notification_precision = {
151
- "title": "Precision = {}",
151
+ "title": "{}",
152
152
  "description": "The model correctly predicted <b>{} of {}</b> predictions made by the model in total.",
153
153
  }
154
154